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
;
123 led
= container_of(work
, struct pm860x_led
, work
);
125 mutex_lock(&led
->lock
);
126 if ((led
->current_brightness
== 0) && led
->brightness
) {
128 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
129 LED_CURRENT_MASK
, led
->iset
);
131 mask
= __blink_ctl_mask(led
->port
);
132 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
133 } else if (led
->brightness
== 0) {
134 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
135 LED_CURRENT_MASK
, 0);
136 mask
= __blink_ctl_mask(led
->port
);
137 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, 0);
139 pm860x_set_bits(led
->i2c
, __led_off(led
->port
), LED_PWM_MASK
,
141 led
->current_brightness
= led
->brightness
;
142 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
143 __led_off(led
->port
), led
->brightness
);
144 mutex_unlock(&led
->lock
);
147 static void pm860x_led_set(struct led_classdev
*cdev
,
148 enum led_brightness value
)
150 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
152 data
->brightness
= value
>> 3;
153 schedule_work(&data
->work
);
156 static int __check_device(struct pm860x_led_pdata
*pdata
, char *name
)
158 struct pm860x_led_pdata
*p
= pdata
;
162 if ((p
->id
!= PM8606_ID_LED
) || (p
->flags
< 0))
165 if (!strncmp(name
, pm860x_led_name
[p
->flags
],
175 static int pm860x_led_probe(struct platform_device
*pdev
)
177 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
178 struct pm860x_platform_data
*pm860x_pdata
;
179 struct pm860x_led_pdata
*pdata
;
180 struct pm860x_led
*data
;
181 struct resource
*res
;
184 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
186 dev_err(&pdev
->dev
, "No I/O resource!\n");
190 if (pdev
->dev
.parent
->platform_data
) {
191 pm860x_pdata
= pdev
->dev
.parent
->platform_data
;
192 pdata
= pm860x_pdata
->led
;
194 dev_err(&pdev
->dev
, "No platform data!\n");
198 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
201 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
);
202 dev_set_drvdata(&pdev
->dev
, data
);
204 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
205 data
->iset
= pdata
->iset
;
206 data
->port
= __check_device(pdata
, data
->name
);
207 if (data
->port
< 0) {
208 dev_err(&pdev
->dev
, "check device failed\n");
213 data
->current_brightness
= 0;
214 data
->cdev
.name
= data
->name
;
215 data
->cdev
.brightness_set
= pm860x_led_set
;
216 mutex_init(&data
->lock
);
217 INIT_WORK(&data
->work
, pm860x_led_work
);
219 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
221 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
230 static int pm860x_led_remove(struct platform_device
*pdev
)
232 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
234 led_classdev_unregister(&data
->cdev
);
240 static struct platform_driver pm860x_led_driver
= {
242 .name
= "88pm860x-led",
243 .owner
= THIS_MODULE
,
245 .probe
= pm860x_led_probe
,
246 .remove
= pm860x_led_remove
,
249 static int __devinit
pm860x_led_init(void)
251 return platform_driver_register(&pm860x_led_driver
);
253 module_init(pm860x_led_init
);
255 static void __devexit
pm860x_led_exit(void)
257 platform_driver_unregister(&pm860x_led_driver
);
259 module_exit(pm860x_led_exit
);
261 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
262 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
263 MODULE_LICENSE("GPL");
264 MODULE_ALIAS("platform:88pm860x-led");