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>
21 #include <linux/module.h>
23 #define LED_PWM_SHIFT (3)
24 #define LED_PWM_MASK (0x1F)
25 #define LED_CURRENT_MASK (0x07 << 5)
27 #define LED_BLINK_ON_MASK (0x07)
28 #define LED_BLINK_MASK (0x7F)
30 #define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
31 #define LED_BLINK_ON_MIN LED_BLINK_ON(0)
32 #define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
33 #define LED_ON_CONTINUOUS (0x0F << 3)
34 #define LED_TO_ON(x) ((x - 66) / 66)
36 #define LED1_BLINK_EN (1 << 1)
37 #define LED2_BLINK_EN (1 << 2)
40 struct led_classdev cdev
;
41 struct i2c_client
*i2c
;
42 struct work_struct work
;
43 struct pm860x_chip
*chip
;
45 char name
[MFD_NAME_SIZE
];
49 unsigned char brightness
;
50 unsigned char current_brightness
;
58 /* return offset of color register */
59 static inline int __led_off(int port
)
65 case PM8606_LED1_GREEN
:
66 case PM8606_LED1_BLUE
:
67 ret
= port
- PM8606_LED1_RED
+ PM8606_RGB1B
;
70 case PM8606_LED2_GREEN
:
71 case PM8606_LED2_BLUE
:
72 ret
= port
- PM8606_LED2_RED
+ PM8606_RGB2B
;
78 /* return offset of blink register */
79 static inline int __blink_off(int port
)
85 case PM8606_LED1_GREEN
:
86 case PM8606_LED1_BLUE
:
90 case PM8606_LED2_GREEN
:
91 case PM8606_LED2_BLUE
:
98 static inline int __blink_ctl_mask(int port
)
103 case PM8606_LED1_RED
:
104 case PM8606_LED1_GREEN
:
105 case PM8606_LED1_BLUE
:
108 case PM8606_LED2_RED
:
109 case PM8606_LED2_GREEN
:
110 case PM8606_LED2_BLUE
:
117 static int led_power_set(struct pm860x_chip
*chip
, int port
, int on
)
122 case PM8606_LED1_RED
:
123 case PM8606_LED1_GREEN
:
124 case PM8606_LED1_BLUE
:
125 ret
= on
? pm8606_osc_enable(chip
, RGB1_ENABLE
) :
126 pm8606_osc_disable(chip
, RGB1_ENABLE
);
128 case PM8606_LED2_RED
:
129 case PM8606_LED2_GREEN
:
130 case PM8606_LED2_BLUE
:
131 ret
= on
? pm8606_osc_enable(chip
, RGB2_ENABLE
) :
132 pm8606_osc_disable(chip
, RGB2_ENABLE
);
138 static void pm860x_led_work(struct work_struct
*work
)
141 struct pm860x_led
*led
;
142 struct pm860x_chip
*chip
;
143 unsigned char buf
[3];
146 led
= container_of(work
, struct pm860x_led
, work
);
148 mutex_lock(&led
->lock
);
149 if ((led
->current_brightness
== 0) && led
->brightness
) {
150 led_power_set(chip
, led
->port
, 1);
152 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
153 LED_CURRENT_MASK
, led
->iset
);
155 pm860x_set_bits(led
->i2c
, __blink_off(led
->port
),
156 LED_BLINK_MASK
, LED_ON_CONTINUOUS
);
157 mask
= __blink_ctl_mask(led
->port
);
158 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
160 pm860x_set_bits(led
->i2c
, __led_off(led
->port
), LED_PWM_MASK
,
163 if (led
->brightness
== 0) {
164 pm860x_bulk_read(led
->i2c
, __led_off(led
->port
), 3, buf
);
165 ret
= buf
[0] & LED_PWM_MASK
;
166 ret
|= buf
[1] & LED_PWM_MASK
;
167 ret
|= buf
[2] & LED_PWM_MASK
;
169 /* unset current since no led is lighting */
170 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
171 LED_CURRENT_MASK
, 0);
172 mask
= __blink_ctl_mask(led
->port
);
173 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, 0);
174 led_power_set(chip
, led
->port
, 0);
177 led
->current_brightness
= led
->brightness
;
178 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
179 __led_off(led
->port
), led
->brightness
);
180 mutex_unlock(&led
->lock
);
183 static void pm860x_led_set(struct led_classdev
*cdev
,
184 enum led_brightness value
)
186 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
188 data
->brightness
= value
>> 3;
189 schedule_work(&data
->work
);
192 static int pm860x_led_probe(struct platform_device
*pdev
)
194 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
195 struct pm860x_led_pdata
*pdata
;
196 struct pm860x_led
*data
;
197 struct resource
*res
;
200 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
202 dev_err(&pdev
->dev
, "No I/O resource!\n");
206 pdata
= pdev
->dev
.platform_data
;
208 dev_err(&pdev
->dev
, "No platform data!\n");
212 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
215 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
- 1);
216 dev_set_drvdata(&pdev
->dev
, data
);
218 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
219 data
->iset
= pdata
->iset
;
220 data
->port
= pdata
->flags
;
221 if (data
->port
< 0) {
222 dev_err(&pdev
->dev
, "check device failed\n");
227 data
->current_brightness
= 0;
228 data
->cdev
.name
= data
->name
;
229 data
->cdev
.brightness_set
= pm860x_led_set
;
230 mutex_init(&data
->lock
);
231 INIT_WORK(&data
->work
, pm860x_led_work
);
233 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
235 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
238 pm860x_led_set(&data
->cdev
, 0);
245 static int pm860x_led_remove(struct platform_device
*pdev
)
247 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
249 led_classdev_unregister(&data
->cdev
);
255 static struct platform_driver pm860x_led_driver
= {
257 .name
= "88pm860x-led",
258 .owner
= THIS_MODULE
,
260 .probe
= pm860x_led_probe
,
261 .remove
= pm860x_led_remove
,
264 module_platform_driver(pm860x_led_driver
);
266 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
267 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
268 MODULE_LICENSE("GPL");
269 MODULE_ALIAS("platform:88pm860x-led");