Blame view

drivers/leds/leds-mc13783.c 7.9 KB
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
1
  /*
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
2
   * LEDs driver for Freescale MC13783/MC13892/MC34708
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   *
   * Copyright (C) 2010 Philippe Rétornaz
   *
   * Based on leds-da903x:
   * Copyright (C) 2008 Compulab, Ltd.
   *      Mike Rapoport <mike@compulab.co.il>
   *
   * Copyright (C) 2006-2008 Marvell International Ltd.
   *      Eric Miao <eric.miao@marvell.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   */
  
  #include <linux/module.h>
  #include <linux/kernel.h>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
20
21
  #include <linux/platform_device.h>
  #include <linux/leds.h>
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
22
  #include <linux/of.h>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
23
  #include <linux/workqueue.h>
f3ca07824   David Jander   leds: Convert mc1...
24
  #include <linux/mfd/mc13xxx.h>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
25

9d263813c   Alexander Shiyan   leds: leds-mc1378...
26
27
28
29
  struct mc13xxx_led_devtype {
  	int	led_min;
  	int	led_max;
  	int	num_regs;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
30
  	u32	ledctrl_base;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
31
32
33
  };
  
  struct mc13xxx_led {
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
34
35
  	struct led_classdev	cdev;
  	struct work_struct	work;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
36
37
  	enum led_brightness	new_brightness;
  	int			id;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
38
  	struct mc13xxx_leds	*leds;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
39
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
40
  struct mc13xxx_leds {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
41
  	struct mc13xxx			*master;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
42
43
  	struct mc13xxx_led_devtype	*devtype;
  	int				num_leds;
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
44
  	struct mc13xxx_led		*led;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
45
  };
677d13f27   Alexander Shiyan   leds: leds-mc1378...
46
47
48
49
50
51
52
53
54
  static unsigned int mc13xxx_max_brightness(int id)
  {
  	if (id >= MC13783_LED_MD && id <= MC13783_LED_KP)
  		return 0x0f;
  	else if (id >= MC13783_LED_R1 && id <= MC13783_LED_B3)
  		return 0x1f;
  
  	return 0x3f;
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
55
  static void mc13xxx_led_work(struct work_struct *work)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
56
  {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
57
  	struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work);
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
58
  	struct mc13xxx_leds *leds = led->leds;
677d13f27   Alexander Shiyan   leds: leds-mc1378...
59
  	unsigned int reg, bank, off, shift;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
60
61
62
  
  	switch (led->id) {
  	case MC13783_LED_MD:
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
63
  	case MC13783_LED_AD:
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
64
  	case MC13783_LED_KP:
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
65
66
  		reg = 2;
  		shift = 9 + (led->id - MC13783_LED_MD) * 4;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
67
68
69
70
71
72
73
74
75
76
77
  		break;
  	case MC13783_LED_R1:
  	case MC13783_LED_G1:
  	case MC13783_LED_B1:
  	case MC13783_LED_R2:
  	case MC13783_LED_G2:
  	case MC13783_LED_B2:
  	case MC13783_LED_R3:
  	case MC13783_LED_G3:
  	case MC13783_LED_B3:
  		off = led->id - MC13783_LED_R1;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
78
  		bank = off / 3;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
79
  		reg = 3 + bank;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
80
  		shift = (off - bank * 3) * 5 + 6;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
81
  		break;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
82
  	case MC13892_LED_MD:
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
83
  	case MC13892_LED_AD:
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
84
  	case MC13892_LED_KP:
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
85
86
  		reg = (led->id - MC13892_LED_MD) / 2;
  		shift = 3 + (led->id - MC13892_LED_MD) * 12;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
87
88
89
90
91
92
  		break;
  	case MC13892_LED_R:
  	case MC13892_LED_G:
  	case MC13892_LED_B:
  		off = led->id - MC13892_LED_R;
  		bank = off / 2;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
93
  		reg = 2 + bank;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
94
  		shift = (off - bank * 2) * 12 + 3;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
95
  		break;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
96
97
98
99
  	case MC34708_LED_R:
  	case MC34708_LED_G:
  		reg = 0;
  		shift = 3 + (led->id - MC34708_LED_R) * 12;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
100
  		break;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
101
102
  	default:
  		BUG();
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
103
  	}
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
104
  	mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg,
677d13f27   Alexander Shiyan   leds: leds-mc1378...
105
106
  			mc13xxx_max_brightness(led->id) << shift,
  			led->new_brightness << shift);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
107
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
108
109
  static void mc13xxx_led_set(struct led_classdev *led_cdev,
  			    enum led_brightness value)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
110
  {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
111
112
  	struct mc13xxx_led *led =
  		container_of(led_cdev, struct mc13xxx_led, cdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
113

7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
114
115
116
  	led->new_brightness = value;
  	schedule_work(&led->work);
  }
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
117
118
119
120
121
122
123
124
125
126
127
128
129
  #ifdef CONFIG_OF
  static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
  	struct platform_device *pdev)
  {
  	struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
  	struct mc13xxx_leds_platform_data *pdata;
  	struct device_node *parent, *child;
  	struct device *dev = &pdev->dev;
  	int i = 0, ret = -ENODATA;
  
  	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
  	if (!pdata)
  		return ERR_PTR(-ENOMEM);
452bc1499   Geert Uytterhoeven   leds: leds-mc1378...
130
  	parent = of_get_child_by_name(dev->parent->of_node, "leds");
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  	if (!parent)
  		goto out_node_put;
  
  	ret = of_property_read_u32_array(parent, "led-control",
  					 pdata->led_control,
  					 leds->devtype->num_regs);
  	if (ret)
  		goto out_node_put;
  
  	pdata->num_leds = of_get_child_count(parent);
  
  	pdata->led = devm_kzalloc(dev, pdata->num_leds * sizeof(*pdata->led),
  				  GFP_KERNEL);
  	if (!pdata->led) {
  		ret = -ENOMEM;
  		goto out_node_put;
  	}
  
  	for_each_child_of_node(parent, child) {
  		const char *str;
  		u32 tmp;
  
  		if (of_property_read_u32(child, "reg", &tmp))
  			continue;
  		pdata->led[i].id = leds->devtype->led_min + tmp;
  
  		if (!of_property_read_string(child, "label", &str))
  			pdata->led[i].name = str;
  		if (!of_property_read_string(child, "linux,default-trigger",
  					     &str))
  			pdata->led[i].default_trigger = str;
  
  		i++;
  	}
  
  	pdata->num_leds = i;
  	ret = i > 0 ? 0 : -ENODATA;
  
  out_node_put:
  	of_node_put(parent);
  
  	return ret ? ERR_PTR(ret) : pdata;
  }
  #else
  static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
  	struct platform_device *pdev)
  {
  	return ERR_PTR(-ENOSYS);
  }
  #endif
9d263813c   Alexander Shiyan   leds: leds-mc1378...
181
  static int __init mc13xxx_led_probe(struct platform_device *pdev)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
182
  {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
183
184
185
  	struct device *dev = &pdev->dev;
  	struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(dev);
  	struct mc13xxx *mcdev = dev_get_drvdata(dev->parent);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
186
187
188
  	struct mc13xxx_led_devtype *devtype =
  		(struct mc13xxx_led_devtype *)pdev->id_entry->driver_data;
  	struct mc13xxx_leds *leds;
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
189
  	int i, id, ret = -ENODATA;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
190
  	u32 init_led = 0;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
191

25c6579f8   Alexander Shiyan   leds: leds-mc1378...
192
  	leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
193
  	if (!leds)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
194
  		return -ENOMEM;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
195
196
  
  	leds->devtype = devtype;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
197
  	leds->master = mcdev;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
198
  	platform_set_drvdata(pdev, leds);
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  	if (dev->parent->of_node) {
  		pdata = mc13xxx_led_probe_dt(pdev);
  		if (IS_ERR(pdata))
  			return PTR_ERR(pdata);
  	} else if (!pdata)
  		return -ENODATA;
  
  	leds->num_leds = pdata->num_leds;
  
  	if ((leds->num_leds < 1) ||
  	    (leds->num_leds > (devtype->led_max - devtype->led_min + 1))) {
  		dev_err(dev, "Invalid LED count %d
  ", leds->num_leds);
  		return -EINVAL;
  	}
  
  	leds->led = devm_kzalloc(dev, leds->num_leds * sizeof(*leds->led),
  				 GFP_KERNEL);
  	if (!leds->led)
  		return -ENOMEM;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
219
  	for (i = 0; i < devtype->num_regs; i++) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
220
221
  		ret = mc13xxx_reg_write(mcdev, leds->devtype->ledctrl_base + i,
  					pdata->led_control[i]);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
222
  		if (ret)
3df22c06b   Alexander Shiyan   leds: leds-mc1378...
223
  			return ret;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
224
  	}
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
225
  	for (i = 0; i < leds->num_leds; i++) {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
226
  		const char *name, *trig;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
227

9d263813c   Alexander Shiyan   leds: leds-mc1378...
228
229
230
231
232
  		ret = -EINVAL;
  
  		id = pdata->led[i].id;
  		name = pdata->led[i].name;
  		trig = pdata->led[i].default_trigger;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
233
234
  
  		if ((id > devtype->led_max) || (id < devtype->led_min)) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
235
236
  			dev_err(dev, "Invalid ID %i
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
237
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
238
  		}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
239
  		if (init_led & (1 << id)) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
240
241
  			dev_warn(dev, "LED %i already initialized
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
242
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
243
  		}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
244
245
  		init_led |= 1 << id;
  		leds->led[i].id = id;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
246
  		leds->led[i].leds = leds;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
247
248
  		leds->led[i].cdev.name = name;
  		leds->led[i].cdev.default_trigger = trig;
02e9e11e2   Alexander Shiyan   leds: leds-mc1378...
249
  		leds->led[i].cdev.flags = LED_CORE_SUSPENDRESUME;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
250
  		leds->led[i].cdev.brightness_set = mc13xxx_led_set;
677d13f27   Alexander Shiyan   leds: leds-mc1378...
251
  		leds->led[i].cdev.max_brightness = mc13xxx_max_brightness(id);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
252

9d263813c   Alexander Shiyan   leds: leds-mc1378...
253
  		INIT_WORK(&leds->led[i].work, mc13xxx_led_work);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
254

a59ce6584   Alexander Shiyan   leds: leds-mc1378...
255
  		ret = led_classdev_register(dev->parent, &leds->led[i].cdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
256
  		if (ret) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
257
258
  			dev_err(dev, "Failed to register LED %i
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
259
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
260
261
  		}
  	}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
262
263
264
265
266
  	if (ret)
  		while (--i >= 0) {
  			led_classdev_unregister(&leds->led[i].cdev);
  			cancel_work_sync(&leds->led[i].work);
  		}
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
267

7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
268
269
  	return ret;
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
270
  static int mc13xxx_led_remove(struct platform_device *pdev)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
271
  {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
272
  	struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
273
  	int i;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
274
275
276
  	for (i = 0; i < leds->num_leds; i++) {
  		led_classdev_unregister(&leds->led[i].cdev);
  		cancel_work_sync(&leds->led[i].work);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
277
  	}
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
278
279
  	return 0;
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
280
281
282
283
  static const struct mc13xxx_led_devtype mc13783_led_devtype = {
  	.led_min	= MC13783_LED_MD,
  	.led_max	= MC13783_LED_B3,
  	.num_regs	= 6,
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
284
  	.ledctrl_base	= 51,
9d263813c   Alexander Shiyan   leds: leds-mc1378...
285
  };
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
286
287
288
289
  static const struct mc13xxx_led_devtype mc13892_led_devtype = {
  	.led_min	= MC13892_LED_MD,
  	.led_max	= MC13892_LED_B,
  	.num_regs	= 4,
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
290
291
292
293
294
295
296
297
  	.ledctrl_base	= 51,
  };
  
  static const struct mc13xxx_led_devtype mc34708_led_devtype = {
  	.led_min	= MC34708_LED_R,
  	.led_max	= MC34708_LED_G,
  	.num_regs	= 1,
  	.ledctrl_base	= 54,
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
298
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
299
300
  static const struct platform_device_id mc13xxx_led_id_table[] = {
  	{ "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, },
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
301
  	{ "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, },
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
302
  	{ "mc34708-led", (kernel_ulong_t)&mc34708_led_devtype, },
9d263813c   Alexander Shiyan   leds: leds-mc1378...
303
304
305
306
307
  	{ }
  };
  MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
  
  static struct platform_driver mc13xxx_led_driver = {
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
308
  	.driver	= {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
309
  		.name	= "mc13xxx-led",
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
310
  	},
9d263813c   Alexander Shiyan   leds: leds-mc1378...
311
312
  	.remove		= mc13xxx_led_remove,
  	.id_table	= mc13xxx_led_id_table,
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
313
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
314
  module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
315

9d263813c   Alexander Shiyan   leds: leds-mc1378...
316
  MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC");
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
317
318
  MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>");
  MODULE_LICENSE("GPL");