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/workqueue.h>
19 #include <linux/mfd/88pm860x.h>
21 #define LED_PWM_SHIFT (3)
22 #define LED_PWM_MASK (0x1F)
23 #define LED_CURRENT_MASK (0x07 << 5)
25 #define LED_BLINK_ON_MASK (0x07)
26 #define LED_BLINK_PERIOD_MASK (0x0F << 3)
27 #define LED_BLINK_MASK (0x7F)
29 #define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
30 #define LED_BLINK_PERIOD(x) ((x & 0xF) * 530 + 930)
31 #define LED_BLINK_ON_MIN LED_BLINK_ON(0)
32 #define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
33 #define LED_BLINK_PERIOD_MIN LED_BLINK_PERIOD(0)
34 #define LED_BLINK_PERIOD_MAX LED_BLINK_PERIOD(0xE)
35 #define LED_TO_ON(x) ((x - 66) / 66)
36 #define LED_TO_PERIOD(x) ((x - 930) / 530)
38 #define LED1_BLINK_EN (1 << 1)
39 #define LED2_BLINK_EN (1 << 2)
47 struct led_classdev cdev
;
48 struct i2c_client
*i2c
;
49 struct work_struct work
;
50 struct pm860x_chip
*chip
;
52 char name
[MFD_NAME_SIZE
];
58 unsigned char brightness
;
59 unsigned char current_brightness
;
67 /* return offset of color register */
68 static inline int __led_off(int port
)
74 case PM8606_LED1_GREEN
:
75 case PM8606_LED1_BLUE
:
76 ret
= port
- PM8606_LED1_RED
+ PM8606_RGB1B
;
79 case PM8606_LED2_GREEN
:
80 case PM8606_LED2_BLUE
:
81 ret
= port
- PM8606_LED2_RED
+ PM8606_RGB2B
;
87 /* return offset of blink register */
88 static inline int __blink_off(int port
)
94 case PM8606_LED1_GREEN
:
95 case PM8606_LED1_BLUE
:
98 case PM8606_LED2_GREEN
:
99 case PM8606_LED2_BLUE
:
105 static inline int __blink_ctl_mask(int port
)
110 case PM8606_LED1_RED
:
111 case PM8606_LED1_GREEN
:
112 case PM8606_LED1_BLUE
:
115 case PM8606_LED2_RED
:
116 case PM8606_LED2_GREEN
:
117 case PM8606_LED2_BLUE
:
124 static int __led_set(struct pm860x_led
*led
, int command
)
126 struct pm860x_chip
*chip
= led
->chip
;
129 mutex_lock(&led
->lock
);
132 if ((led
->current_brightness
== 0) && led
->brightness
) {
134 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
135 LED_CURRENT_MASK
, led
->iset
);
139 } else if (led
->brightness
== 0) {
140 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
141 LED_CURRENT_MASK
, 0);
145 ret
= pm860x_set_bits(led
->i2c
, led
->offset
, LED_PWM_MASK
,
149 led
->current_brightness
= led
->brightness
;
150 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
151 led
->offset
, led
->brightness
);
154 ret
= pm860x_set_bits(led
->i2c
, led
->offset
,
155 LED_BLINK_MASK
, led
->blink_data
);
159 mask
= __blink_ctl_mask(led
->port
);
160 ret
= pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
163 dev_dbg(chip
->dev
, "LED blink delay on:%dms, delay off:%dms\n",
164 led
->blink_on
, led
->blink_off
);
168 mutex_unlock(&led
->lock
);
172 static void pm860x_led_work(struct work_struct
*work
)
174 struct pm860x_led
*led
;
176 led
= container_of(work
, struct pm860x_led
, work
);
177 __led_set(led
, led
->command
);
180 static void pm860x_led_set(struct led_classdev
*cdev
,
181 enum led_brightness value
)
183 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
185 data
->offset
= __led_off(data
->port
);
186 data
->brightness
= value
>> 3;
187 data
->command
= SET_BRIGHTNESS
;
188 schedule_work(&data
->work
);
191 static int pm860x_led_blink(struct led_classdev
*cdev
,
192 unsigned long *delay_on
,
193 unsigned long *delay_off
)
195 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
199 if ((on
< LED_BLINK_ON_MIN
) || (on
> LED_BLINK_ON_MAX
))
203 on
= LED_BLINK_ON(on
);
205 period
= on
+ *delay_off
;
206 if ((period
< LED_BLINK_PERIOD_MIN
) || (period
> LED_BLINK_PERIOD_MAX
))
208 period
= LED_TO_PERIOD(period
);
209 period
= LED_BLINK_PERIOD(period
);
211 data
->offset
= __blink_off(data
->port
);
213 data
->blink_off
= period
- data
->blink_on
;
214 data
->blink_data
= (period
<< 3) | data
->blink_on
;
215 data
->command
= SET_BLINK
;
216 schedule_work(&data
->work
);
221 static int __check_device(struct pm860x_led_pdata
*pdata
, char *name
)
223 struct pm860x_led_pdata
*p
= pdata
;
227 if ((p
->id
!= PM8606_ID_LED
) || (p
->flags
< 0))
230 if (!strncmp(name
, pm860x_led_name
[p
->flags
],
240 static int pm860x_led_probe(struct platform_device
*pdev
)
242 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
243 struct pm860x_platform_data
*pm860x_pdata
;
244 struct pm860x_led_pdata
*pdata
;
245 struct pm860x_led
*data
;
246 struct resource
*res
;
249 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
251 dev_err(&pdev
->dev
, "No I/O resource!\n");
255 if (pdev
->dev
.parent
->platform_data
) {
256 pm860x_pdata
= pdev
->dev
.parent
->platform_data
;
257 pdata
= pm860x_pdata
->led
;
261 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
264 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
);
265 dev_set_drvdata(&pdev
->dev
, data
);
267 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
268 data
->iset
= pdata
->iset
;
269 data
->port
= __check_device(pdata
, data
->name
);
273 data
->current_brightness
= 0;
274 data
->cdev
.name
= data
->name
;
275 data
->cdev
.brightness_set
= pm860x_led_set
;
276 data
->cdev
.blink_set
= pm860x_led_blink
;
277 mutex_init(&data
->lock
);
278 INIT_WORK(&data
->work
, pm860x_led_work
);
280 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
282 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
291 static int pm860x_led_remove(struct platform_device
*pdev
)
293 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
295 led_classdev_unregister(&data
->cdev
);
301 static struct platform_driver pm860x_led_driver
= {
303 .name
= "88pm860x-led",
304 .owner
= THIS_MODULE
,
306 .probe
= pm860x_led_probe
,
307 .remove
= pm860x_led_remove
,
310 static int __devinit
pm860x_led_init(void)
312 return platform_driver_register(&pm860x_led_driver
);
314 module_init(pm860x_led_init
);
316 static void __devexit
pm860x_led_exit(void)
318 platform_driver_unregister(&pm860x_led_driver
);
320 module_exit(pm860x_led_exit
);
322 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
323 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
324 MODULE_LICENSE("GPL");
325 MODULE_ALIAS("platform:88pm860x-led");