Blame view

drivers/leds/leds-mc13783.c 7.36 KB
d2912cb15   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
2
  /*
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
3
   * LEDs driver for Freescale MC13783/MC13892/MC34708
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
4
5
6
7
8
9
10
11
12
   *
   * 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>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
13
14
15
16
   */
  
  #include <linux/module.h>
  #include <linux/kernel.h>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
17
18
  #include <linux/platform_device.h>
  #include <linux/leds.h>
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
19
  #include <linux/of.h>
f3ca07824   David Jander   leds: Convert mc1...
20
  #include <linux/mfd/mc13xxx.h>
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
21

9d263813c   Alexander Shiyan   leds: leds-mc1378...
22
23
24
25
  struct mc13xxx_led_devtype {
  	int	led_min;
  	int	led_max;
  	int	num_regs;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
26
  	u32	ledctrl_base;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
27
28
29
  };
  
  struct mc13xxx_led {
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
30
  	struct led_classdev	cdev;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
31
  	int			id;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
32
  	struct mc13xxx_leds	*leds;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
33
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
34
  struct mc13xxx_leds {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
35
  	struct mc13xxx			*master;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
36
37
  	struct mc13xxx_led_devtype	*devtype;
  	int				num_leds;
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
38
  	struct mc13xxx_led		*led;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
39
  };
677d13f27   Alexander Shiyan   leds: leds-mc1378...
40
41
42
43
44
45
46
47
48
  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;
  }
4330f2f26   Andrew Lunn   leds: mc13783: Re...
49
50
  static int mc13xxx_led_set(struct led_classdev *led_cdev,
  			    enum led_brightness value)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
51
  {
4330f2f26   Andrew Lunn   leds: mc13783: Re...
52
53
  	struct mc13xxx_led *led =
  		container_of(led_cdev, struct mc13xxx_led, cdev);
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
54
  	struct mc13xxx_leds *leds = led->leds;
677d13f27   Alexander Shiyan   leds: leds-mc1378...
55
  	unsigned int reg, bank, off, shift;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
56
57
58
  
  	switch (led->id) {
  	case MC13783_LED_MD:
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
59
  	case MC13783_LED_AD:
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
60
  	case MC13783_LED_KP:
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
61
62
  		reg = 2;
  		shift = 9 + (led->id - MC13783_LED_MD) * 4;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
63
64
65
66
67
68
69
70
71
72
73
  		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...
74
  		bank = off / 3;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
75
  		reg = 3 + bank;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
76
  		shift = (off - bank * 3) * 5 + 6;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
77
  		break;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
78
  	case MC13892_LED_MD:
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
79
  	case MC13892_LED_AD:
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
80
  	case MC13892_LED_KP:
cce35f357   Alexander Kurz   leds: mc13783: Fi...
81
82
83
  		off = led->id - MC13892_LED_MD;
  		reg = off / 2;
  		shift = 3 + (off - reg * 2) * 12;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
84
85
86
87
88
89
  		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...
90
  		reg = 2 + bank;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
91
  		shift = (off - bank * 2) * 12 + 3;
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
92
  		break;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
93
94
95
96
  	case MC34708_LED_R:
  	case MC34708_LED_G:
  		reg = 0;
  		shift = 3 + (led->id - MC34708_LED_R) * 12;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
97
  		break;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
98
99
  	default:
  		BUG();
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
100
  	}
4330f2f26   Andrew Lunn   leds: mc13783: Re...
101
  	return mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg,
677d13f27   Alexander Shiyan   leds: leds-mc1378...
102
  			mc13xxx_max_brightness(led->id) << shift,
4330f2f26   Andrew Lunn   leds: mc13783: Re...
103
  			value << shift);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
104
  }
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
105
106
107
108
109
110
111
112
113
114
115
116
117
  #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);
8853c95e9   Marek Behún   leds: various: us...
118
  	parent = of_get_child_by_name(dev_of_node(dev->parent), "leds");
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
119
120
121
122
123
124
125
126
  	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;
99a013c84   Marek Behún   leds: various: us...
127
  	pdata->num_leds = of_get_available_child_count(parent);
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
128

a86854d0c   Kees Cook   treewide: devm_kz...
129
  	pdata->led = devm_kcalloc(dev, pdata->num_leds, sizeof(*pdata->led),
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
130
131
132
133
134
  				  GFP_KERNEL);
  	if (!pdata->led) {
  		ret = -ENOMEM;
  		goto out_node_put;
  	}
99a013c84   Marek Behún   leds: various: us...
135
  	for_each_available_child_of_node(parent, child) {
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
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
  		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...
167
  static int __init mc13xxx_led_probe(struct platform_device *pdev)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
168
  {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
169
170
171
  	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...
172
173
174
  	struct mc13xxx_led_devtype *devtype =
  		(struct mc13xxx_led_devtype *)pdev->id_entry->driver_data;
  	struct mc13xxx_leds *leds;
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
175
  	int i, id, ret = -ENODATA;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
176
  	u32 init_led = 0;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
177

25c6579f8   Alexander Shiyan   leds: leds-mc1378...
178
  	leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
179
  	if (!leds)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
180
  		return -ENOMEM;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
181
182
  
  	leds->devtype = devtype;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
183
  	leds->master = mcdev;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
184
  	platform_set_drvdata(pdev, leds);
8853c95e9   Marek Behún   leds: various: us...
185
  	if (dev_of_node(dev->parent)) {
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
  		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;
  	}
a86854d0c   Kees Cook   treewide: devm_kz...
200
  	leds->led = devm_kcalloc(dev, leds->num_leds, sizeof(*leds->led),
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
201
202
203
  				 GFP_KERNEL);
  	if (!leds->led)
  		return -ENOMEM;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
204
  	for (i = 0; i < devtype->num_regs; i++) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
205
206
  		ret = mc13xxx_reg_write(mcdev, leds->devtype->ledctrl_base + i,
  					pdata->led_control[i]);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
207
  		if (ret)
3df22c06b   Alexander Shiyan   leds: leds-mc1378...
208
  			return ret;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
209
  	}
25c6579f8   Alexander Shiyan   leds: leds-mc1378...
210
  	for (i = 0; i < leds->num_leds; i++) {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
211
  		const char *name, *trig;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
212

9d263813c   Alexander Shiyan   leds: leds-mc1378...
213
214
215
216
217
  		ret = -EINVAL;
  
  		id = pdata->led[i].id;
  		name = pdata->led[i].name;
  		trig = pdata->led[i].default_trigger;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
218
219
  
  		if ((id > devtype->led_max) || (id < devtype->led_min)) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
220
221
  			dev_err(dev, "Invalid ID %i
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
222
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
223
  		}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
224
  		if (init_led & (1 << id)) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
225
226
  			dev_warn(dev, "LED %i already initialized
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
227
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
228
  		}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
229
230
  		init_led |= 1 << id;
  		leds->led[i].id = id;
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
231
  		leds->led[i].leds = leds;
9d263813c   Alexander Shiyan   leds: leds-mc1378...
232
233
  		leds->led[i].cdev.name = name;
  		leds->led[i].cdev.default_trigger = trig;
02e9e11e2   Alexander Shiyan   leds: leds-mc1378...
234
  		leds->led[i].cdev.flags = LED_CORE_SUSPENDRESUME;
4330f2f26   Andrew Lunn   leds: mc13783: Re...
235
  		leds->led[i].cdev.brightness_set_blocking = mc13xxx_led_set;
677d13f27   Alexander Shiyan   leds: leds-mc1378...
236
  		leds->led[i].cdev.max_brightness = mc13xxx_max_brightness(id);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
237

a59ce6584   Alexander Shiyan   leds: leds-mc1378...
238
  		ret = led_classdev_register(dev->parent, &leds->led[i].cdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
239
  		if (ret) {
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
240
241
  			dev_err(dev, "Failed to register LED %i
  ", id);
9d263813c   Alexander Shiyan   leds: leds-mc1378...
242
  			break;
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
243
244
  		}
  	}
9d263813c   Alexander Shiyan   leds: leds-mc1378...
245
  	if (ret)
4330f2f26   Andrew Lunn   leds: mc13783: Re...
246
  		while (--i >= 0)
9d263813c   Alexander Shiyan   leds: leds-mc1378...
247
  			led_classdev_unregister(&leds->led[i].cdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
248

7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
249
250
  	return ret;
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
251
  static int mc13xxx_led_remove(struct platform_device *pdev)
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
252
  {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
253
  	struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
254
  	int i;
4330f2f26   Andrew Lunn   leds: mc13783: Re...
255
  	for (i = 0; i < leds->num_leds; i++)
9d263813c   Alexander Shiyan   leds: leds-mc1378...
256
  		led_classdev_unregister(&leds->led[i].cdev);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
257

7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
258
259
  	return 0;
  }
9d263813c   Alexander Shiyan   leds: leds-mc1378...
260
261
262
263
  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...
264
  	.ledctrl_base	= 51,
9d263813c   Alexander Shiyan   leds: leds-mc1378...
265
  };
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
266
267
268
269
  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...
270
271
272
273
274
275
276
277
  	.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...
278
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
279
280
  static const struct platform_device_id mc13xxx_led_id_table[] = {
  	{ "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, },
ae6cdb03e   Alexander Shiyan   leds: leds-mc1378...
281
  	{ "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, },
a59ce6584   Alexander Shiyan   leds: leds-mc1378...
282
  	{ "mc34708-led", (kernel_ulong_t)&mc34708_led_devtype, },
9d263813c   Alexander Shiyan   leds: leds-mc1378...
283
284
285
286
287
  	{ }
  };
  MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
  
  static struct platform_driver mc13xxx_led_driver = {
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
288
  	.driver	= {
9d263813c   Alexander Shiyan   leds: leds-mc1378...
289
  		.name	= "mc13xxx-led",
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
290
  	},
9d263813c   Alexander Shiyan   leds: leds-mc1378...
291
292
  	.remove		= mc13xxx_led_remove,
  	.id_table	= mc13xxx_led_id_table,
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
293
  };
9d263813c   Alexander Shiyan   leds: leds-mc1378...
294
  module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
7fdcef8a4   Philippe Rétornaz   leds: Add mc13783...
295

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