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_MASK (0x7F)
29 #define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
30 #define LED_BLINK_ON_MIN LED_BLINK_ON(0)
31 #define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
32 #define LED_ON_CONTINUOUS (0x0F << 3)
33 #define LED_TO_ON(x) ((x - 66) / 66)
35 #define LED1_BLINK_EN (1 << 1)
36 #define LED2_BLINK_EN (1 << 2)
39 struct led_classdev cdev
;
40 struct i2c_client
*i2c
;
41 struct work_struct work
;
42 struct pm860x_chip
*chip
;
44 char name
[MFD_NAME_SIZE
];
48 unsigned char brightness
;
49 unsigned char current_brightness
;
57 /* return offset of color register */
58 static inline int __led_off(int port
)
64 case PM8606_LED1_GREEN
:
65 case PM8606_LED1_BLUE
:
66 ret
= port
- PM8606_LED1_RED
+ PM8606_RGB1B
;
69 case PM8606_LED2_GREEN
:
70 case PM8606_LED2_BLUE
:
71 ret
= port
- PM8606_LED2_RED
+ PM8606_RGB2B
;
77 /* return offset of blink register */
78 static inline int __blink_off(int port
)
84 case PM8606_LED1_GREEN
:
85 case PM8606_LED1_BLUE
:
89 case PM8606_LED2_GREEN
:
90 case PM8606_LED2_BLUE
:
97 static inline int __blink_ctl_mask(int port
)
102 case PM8606_LED1_RED
:
103 case PM8606_LED1_GREEN
:
104 case PM8606_LED1_BLUE
:
107 case PM8606_LED2_RED
:
108 case PM8606_LED2_GREEN
:
109 case PM8606_LED2_BLUE
:
116 static void pm860x_led_work(struct work_struct
*work
)
119 struct pm860x_led
*led
;
120 struct pm860x_chip
*chip
;
121 unsigned char buf
[3];
124 led
= container_of(work
, struct pm860x_led
, work
);
126 mutex_lock(&led
->lock
);
127 if ((led
->current_brightness
== 0) && led
->brightness
) {
129 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
130 LED_CURRENT_MASK
, led
->iset
);
132 pm860x_set_bits(led
->i2c
, __blink_off(led
->port
),
133 LED_BLINK_MASK
, LED_ON_CONTINUOUS
);
134 mask
= __blink_ctl_mask(led
->port
);
135 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
137 pm860x_set_bits(led
->i2c
, __led_off(led
->port
), LED_PWM_MASK
,
140 if (led
->brightness
== 0) {
141 pm860x_bulk_read(led
->i2c
, __led_off(led
->port
), 3, buf
);
142 ret
= buf
[0] & LED_PWM_MASK
;
143 ret
|= buf
[1] & LED_PWM_MASK
;
144 ret
|= buf
[2] & LED_PWM_MASK
;
146 /* unset current since no led is lighting */
147 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
148 LED_CURRENT_MASK
, 0);
149 mask
= __blink_ctl_mask(led
->port
);
150 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, 0);
153 led
->current_brightness
= led
->brightness
;
154 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
155 __led_off(led
->port
), led
->brightness
);
156 mutex_unlock(&led
->lock
);
159 static void pm860x_led_set(struct led_classdev
*cdev
,
160 enum led_brightness value
)
162 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
164 data
->brightness
= value
>> 3;
165 schedule_work(&data
->work
);
168 static int pm860x_led_probe(struct platform_device
*pdev
)
170 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
171 struct pm860x_led_pdata
*pdata
;
172 struct pm860x_led
*data
;
173 struct resource
*res
;
176 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
178 dev_err(&pdev
->dev
, "No I/O resource!\n");
182 pdata
= pdev
->dev
.platform_data
;
184 dev_err(&pdev
->dev
, "No platform data!\n");
188 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
191 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
- 1);
192 dev_set_drvdata(&pdev
->dev
, data
);
194 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
195 data
->iset
= pdata
->iset
;
196 data
->port
= pdata
->flags
;
197 if (data
->port
< 0) {
198 dev_err(&pdev
->dev
, "check device failed\n");
203 data
->current_brightness
= 0;
204 data
->cdev
.name
= data
->name
;
205 data
->cdev
.brightness_set
= pm860x_led_set
;
206 mutex_init(&data
->lock
);
207 INIT_WORK(&data
->work
, pm860x_led_work
);
209 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
211 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
214 pm860x_led_set(&data
->cdev
, 0);
221 static int pm860x_led_remove(struct platform_device
*pdev
)
223 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
225 led_classdev_unregister(&data
->cdev
);
231 static struct platform_driver pm860x_led_driver
= {
233 .name
= "88pm860x-led",
234 .owner
= THIS_MODULE
,
236 .probe
= pm860x_led_probe
,
237 .remove
= pm860x_led_remove
,
240 static int __devinit
pm860x_led_init(void)
242 return platform_driver_register(&pm860x_led_driver
);
244 module_init(pm860x_led_init
);
246 static void __devexit
pm860x_led_exit(void)
248 platform_driver_unregister(&pm860x_led_driver
);
250 module_exit(pm860x_led_exit
);
252 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
253 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
254 MODULE_LICENSE("GPL");
255 MODULE_ALIAS("platform:88pm860x-led");