2 * LED driver for Marvell 88PM860x
4 * Copyright (C) 2009 Marvell International Ltd.
5 * Haojian Zhuang <haojian.zhuang@marvell.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
13 #include <linux/kernel.h>
14 #include <linux/init.h>
15 #include <linux/platform_device.h>
16 #include <linux/i2c.h>
17 #include <linux/leds.h>
18 #include <linux/slab.h>
19 #include <linux/workqueue.h>
20 #include <linux/mfd/88pm860x.h>
22 #define LED_PWM_SHIFT (3)
23 #define LED_PWM_MASK (0x1F)
24 #define LED_CURRENT_MASK (0x07 << 5)
26 #define LED_BLINK_ON_MASK (0x07)
27 #define LED_BLINK_PERIOD_MASK (0x0F << 3)
28 #define LED_BLINK_MASK (0x7F)
30 #define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
31 #define LED_BLINK_PERIOD(x) ((x & 0xF) * 530 + 930)
32 #define LED_BLINK_ON_MIN LED_BLINK_ON(0)
33 #define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
34 #define LED_BLINK_PERIOD_MIN LED_BLINK_PERIOD(0)
35 #define LED_BLINK_PERIOD_MAX LED_BLINK_PERIOD(0xE)
36 #define LED_TO_ON(x) ((x - 66) / 66)
37 #define LED_TO_PERIOD(x) ((x - 930) / 530)
39 #define LED1_BLINK_EN (1 << 1)
40 #define LED2_BLINK_EN (1 << 2)
48 struct led_classdev cdev
;
49 struct i2c_client
*i2c
;
50 struct work_struct work
;
51 struct pm860x_chip
*chip
;
53 char name
[MFD_NAME_SIZE
];
59 unsigned char brightness
;
60 unsigned char current_brightness
;
68 /* return offset of color register */
69 static inline int __led_off(int port
)
75 case PM8606_LED1_GREEN
:
76 case PM8606_LED1_BLUE
:
77 ret
= port
- PM8606_LED1_RED
+ PM8606_RGB1B
;
80 case PM8606_LED2_GREEN
:
81 case PM8606_LED2_BLUE
:
82 ret
= port
- PM8606_LED2_RED
+ PM8606_RGB2B
;
88 /* return offset of blink register */
89 static inline int __blink_off(int port
)
95 case PM8606_LED1_GREEN
:
96 case PM8606_LED1_BLUE
:
99 case PM8606_LED2_GREEN
:
100 case PM8606_LED2_BLUE
:
106 static inline int __blink_ctl_mask(int port
)
111 case PM8606_LED1_RED
:
112 case PM8606_LED1_GREEN
:
113 case PM8606_LED1_BLUE
:
116 case PM8606_LED2_RED
:
117 case PM8606_LED2_GREEN
:
118 case PM8606_LED2_BLUE
:
125 static int __led_set(struct pm860x_led
*led
, int command
)
127 struct pm860x_chip
*chip
= led
->chip
;
130 mutex_lock(&led
->lock
);
133 if ((led
->current_brightness
== 0) && led
->brightness
) {
135 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
136 LED_CURRENT_MASK
, led
->iset
);
140 } else if (led
->brightness
== 0) {
141 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
142 LED_CURRENT_MASK
, 0);
146 ret
= pm860x_set_bits(led
->i2c
, led
->offset
, LED_PWM_MASK
,
150 led
->current_brightness
= led
->brightness
;
151 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
152 led
->offset
, led
->brightness
);
155 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
156 LED_BLINK_MASK
, led
->blink_data
);
160 mask
= __blink_ctl_mask(led
->port
);
161 ret
= pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
164 dev_dbg(chip
->dev
, "LED blink delay on:%dms, delay off:%dms\n",
165 led
->blink_on
, led
->blink_off
);
169 mutex_unlock(&led
->lock
);
173 static void pm860x_led_work(struct work_struct
*work
)
175 struct pm860x_led
*led
;
177 led
= container_of(work
, struct pm860x_led
, work
);
178 __led_set(led
, led
->command
);
181 static void pm860x_led_set(struct led_classdev
*cdev
,
182 enum led_brightness value
)
184 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
186 data
->offset
= __led_off(data
->port
);
187 data
->brightness
= value
>> 3;
188 data
->command
= SET_BRIGHTNESS
;
189 schedule_work(&data
->work
);
192 static int pm860x_led_blink(struct led_classdev
*cdev
,
193 unsigned long *delay_on
,
194 unsigned long *delay_off
)
196 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
200 if ((on
< LED_BLINK_ON_MIN
) || (on
> LED_BLINK_ON_MAX
))
204 on
= LED_BLINK_ON(on
);
206 period
= on
+ *delay_off
;
207 if ((period
< LED_BLINK_PERIOD_MIN
) || (period
> LED_BLINK_PERIOD_MAX
))
209 period
= LED_TO_PERIOD(period
);
210 period
= LED_BLINK_PERIOD(period
);
212 data
->offset
= __blink_off(data
->port
);
214 data
->blink_off
= period
- data
->blink_on
;
215 data
->blink_data
= (period
<< 3) | data
->blink_on
;
216 data
->command
= SET_BLINK
;
217 schedule_work(&data
->work
);
222 static int __check_device(struct pm860x_led_pdata
*pdata
, char *name
)
224 struct pm860x_led_pdata
*p
= pdata
;
228 if ((p
->id
!= PM8606_ID_LED
) || (p
->flags
< 0))
231 if (!strncmp(name
, pm860x_led_name
[p
->flags
],
241 static int pm860x_led_probe(struct platform_device
*pdev
)
243 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
244 struct pm860x_platform_data
*pm860x_pdata
;
245 struct pm860x_led_pdata
*pdata
;
246 struct pm860x_led
*data
;
247 struct resource
*res
;
250 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
252 dev_err(&pdev
->dev
, "No I/O resource!\n");
256 if (pdev
->dev
.parent
->platform_data
) {
257 pm860x_pdata
= pdev
->dev
.parent
->platform_data
;
258 pdata
= pm860x_pdata
->led
;
260 dev_err(&pdev
->dev
, "missing platform data\n");
264 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
267 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
);
268 dev_set_drvdata(&pdev
->dev
, data
);
270 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
271 data
->iset
= pdata
->iset
;
272 data
->port
= __check_device(pdata
, data
->name
);
273 if (data
->port
< 0) {
274 dev_err(&pdev
->dev
, "check device failed\n");
279 data
->current_brightness
= 0;
280 data
->cdev
.name
= data
->name
;
281 data
->cdev
.brightness_set
= pm860x_led_set
;
282 data
->cdev
.blink_set
= pm860x_led_blink
;
283 mutex_init(&data
->lock
);
284 INIT_WORK(&data
->work
, pm860x_led_work
);
286 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
288 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
297 static int pm860x_led_remove(struct platform_device
*pdev
)
299 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
301 led_classdev_unregister(&data
->cdev
);
307 static struct platform_driver pm860x_led_driver
= {
309 .name
= "88pm860x-led",
310 .owner
= THIS_MODULE
,
312 .probe
= pm860x_led_probe
,
313 .remove
= pm860x_led_remove
,
316 static int __devinit
pm860x_led_init(void)
318 return platform_driver_register(&pm860x_led_driver
);
320 module_init(pm860x_led_init
);
322 static void __devexit
pm860x_led_exit(void)
324 platform_driver_unregister(&pm860x_led_driver
);
326 module_exit(pm860x_led_exit
);
328 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
329 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
330 MODULE_LICENSE("GPL");
331 MODULE_ALIAS("platform:88pm860x-led");