2 * LEDs driver for Freescale MC13783/MC13892
4 * Copyright (C) 2010 Philippe Rétornaz
6 * Based on leds-da903x:
7 * Copyright (C) 2008 Compulab, Ltd.
8 * Mike Rapoport <mike@compulab.co.il>
10 * Copyright (C) 2006-2008 Marvell International Ltd.
11 * Eric Miao <eric.miao@marvell.com>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License version 2 as
15 * published by the Free Software Foundation.
18 #include <linux/module.h>
19 #include <linux/kernel.h>
20 #include <linux/init.h>
21 #include <linux/platform_device.h>
22 #include <linux/leds.h>
23 #include <linux/workqueue.h>
24 #include <linux/mfd/mc13xxx.h>
26 #define MC13XXX_REG_LED_CONTROL(x) (51 + (x))
28 struct mc13xxx_led_devtype
{
35 struct led_classdev cdev
;
36 struct work_struct work
;
37 struct mc13xxx
*master
;
38 enum led_brightness new_brightness
;
43 struct mc13xxx_led_devtype
*devtype
;
45 struct mc13xxx_led led
[0];
48 static void mc13xxx_led_work(struct work_struct
*work
)
50 struct mc13xxx_led
*led
= container_of(work
, struct mc13xxx_led
, work
);
51 int reg
, mask
, value
, bank
, off
, shift
;
55 reg
= MC13XXX_REG_LED_CONTROL(2);
58 value
= led
->new_brightness
>> 4;
61 reg
= MC13XXX_REG_LED_CONTROL(2);
64 value
= led
->new_brightness
>> 4;
67 reg
= MC13XXX_REG_LED_CONTROL(2);
70 value
= led
->new_brightness
>> 4;
81 off
= led
->id
- MC13783_LED_R1
;
83 reg
= MC13XXX_REG_LED_CONTROL(3) + bank
;
84 shift
= (off
- bank
* 3) * 5 + 6;
85 value
= led
->new_brightness
>> 3;
89 reg
= MC13XXX_REG_LED_CONTROL(0);
92 value
= led
->new_brightness
>> 2;
95 reg
= MC13XXX_REG_LED_CONTROL(0);
98 value
= led
->new_brightness
>> 2;
101 reg
= MC13XXX_REG_LED_CONTROL(1);
104 value
= led
->new_brightness
>> 2;
109 off
= led
->id
- MC13892_LED_R
;
111 reg
= MC13XXX_REG_LED_CONTROL(2) + bank
;
112 shift
= (off
- bank
* 2) * 12 + 3;
113 value
= led
->new_brightness
>> 2;
120 mc13xxx_reg_rmw(led
->master
, reg
, mask
<< shift
, value
<< shift
);
123 static void mc13xxx_led_set(struct led_classdev
*led_cdev
,
124 enum led_brightness value
)
126 struct mc13xxx_led
*led
=
127 container_of(led_cdev
, struct mc13xxx_led
, cdev
);
129 led
->new_brightness
= value
;
130 schedule_work(&led
->work
);
133 static int __init
mc13xxx_led_probe(struct platform_device
*pdev
)
135 struct mc13xxx_leds_platform_data
*pdata
= dev_get_platdata(&pdev
->dev
);
136 struct mc13xxx
*mcdev
= dev_get_drvdata(pdev
->dev
.parent
);
137 struct mc13xxx_led_devtype
*devtype
=
138 (struct mc13xxx_led_devtype
*)pdev
->id_entry
->driver_data
;
139 struct mc13xxx_leds
*leds
;
140 int i
, id
, num_leds
, ret
= -ENODATA
;
141 u32 reg
, init_led
= 0;
144 dev_err(&pdev
->dev
, "Missing platform data\n");
148 num_leds
= pdata
->num_leds
;
150 if ((num_leds
< 1) ||
151 (num_leds
> (devtype
->led_max
- devtype
->led_min
+ 1))) {
152 dev_err(&pdev
->dev
, "Invalid LED count %d\n", num_leds
);
156 leds
= devm_kzalloc(&pdev
->dev
, num_leds
* sizeof(struct mc13xxx_led
) +
157 sizeof(struct mc13xxx_leds
), GFP_KERNEL
);
161 leds
->devtype
= devtype
;
162 leds
->num_leds
= num_leds
;
163 platform_set_drvdata(pdev
, leds
);
165 for (i
= 0; i
< devtype
->num_regs
; i
++) {
166 reg
= pdata
->led_control
[i
];
167 WARN_ON(reg
>= (1 << 24));
168 ret
= mc13xxx_reg_write(mcdev
, MC13XXX_REG_LED_CONTROL(i
), reg
);
173 for (i
= 0; i
< num_leds
; i
++) {
174 const char *name
, *trig
;
178 id
= pdata
->led
[i
].id
;
179 name
= pdata
->led
[i
].name
;
180 trig
= pdata
->led
[i
].default_trigger
;
182 if ((id
> devtype
->led_max
) || (id
< devtype
->led_min
)) {
183 dev_err(&pdev
->dev
, "Invalid ID %i\n", id
);
187 if (init_led
& (1 << id
)) {
189 "LED %i already initialized\n", id
);
194 leds
->led
[i
].id
= id
;
195 leds
->led
[i
].master
= mcdev
;
196 leds
->led
[i
].cdev
.name
= name
;
197 leds
->led
[i
].cdev
.default_trigger
= trig
;
198 leds
->led
[i
].cdev
.brightness_set
= mc13xxx_led_set
;
199 leds
->led
[i
].cdev
.brightness
= LED_OFF
;
201 INIT_WORK(&leds
->led
[i
].work
, mc13xxx_led_work
);
203 ret
= led_classdev_register(pdev
->dev
.parent
,
206 dev_err(&pdev
->dev
, "Failed to register LED %i\n", id
);
213 led_classdev_unregister(&leds
->led
[i
].cdev
);
214 cancel_work_sync(&leds
->led
[i
].work
);
220 static int mc13xxx_led_remove(struct platform_device
*pdev
)
222 struct mc13xxx
*mcdev
= dev_get_drvdata(pdev
->dev
.parent
);
223 struct mc13xxx_leds
*leds
= platform_get_drvdata(pdev
);
226 for (i
= 0; i
< leds
->num_leds
; i
++) {
227 led_classdev_unregister(&leds
->led
[i
].cdev
);
228 cancel_work_sync(&leds
->led
[i
].work
);
231 for (i
= 0; i
< leds
->devtype
->num_regs
; i
++)
232 mc13xxx_reg_write(mcdev
, MC13XXX_REG_LED_CONTROL(i
), 0);
237 static const struct mc13xxx_led_devtype mc13783_led_devtype
= {
238 .led_min
= MC13783_LED_MD
,
239 .led_max
= MC13783_LED_B3
,
243 static const struct mc13xxx_led_devtype mc13892_led_devtype
= {
244 .led_min
= MC13892_LED_MD
,
245 .led_max
= MC13892_LED_B
,
249 static const struct platform_device_id mc13xxx_led_id_table
[] = {
250 { "mc13783-led", (kernel_ulong_t
)&mc13783_led_devtype
, },
251 { "mc13892-led", (kernel_ulong_t
)&mc13892_led_devtype
, },
254 MODULE_DEVICE_TABLE(platform
, mc13xxx_led_id_table
);
256 static struct platform_driver mc13xxx_led_driver
= {
258 .name
= "mc13xxx-led",
259 .owner
= THIS_MODULE
,
261 .remove
= mc13xxx_led_remove
,
262 .id_table
= mc13xxx_led_id_table
,
264 module_platform_driver_probe(mc13xxx_led_driver
, mc13xxx_led_probe
);
266 MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC");
267 MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>");
268 MODULE_LICENSE("GPL");