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/core.h>
21 #include <linux/mfd/88pm860x.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 void pm860x_led_work(struct work_struct
*work
)
120 struct pm860x_led
*led
;
121 struct pm860x_chip
*chip
;
122 unsigned char buf
[3];
125 led
= container_of(work
, struct pm860x_led
, work
);
127 mutex_lock(&led
->lock
);
128 if ((led
->current_brightness
== 0) && led
->brightness
) {
130 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
131 LED_CURRENT_MASK
, led
->iset
);
133 pm860x_set_bits(led
->i2c
, __blink_off(led
->port
),
134 LED_BLINK_MASK
, LED_ON_CONTINUOUS
);
135 mask
= __blink_ctl_mask(led
->port
);
136 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, mask
);
138 pm860x_set_bits(led
->i2c
, __led_off(led
->port
), LED_PWM_MASK
,
141 if (led
->brightness
== 0) {
142 pm860x_bulk_read(led
->i2c
, __led_off(led
->port
), 3, buf
);
143 ret
= buf
[0] & LED_PWM_MASK
;
144 ret
|= buf
[1] & LED_PWM_MASK
;
145 ret
|= buf
[2] & LED_PWM_MASK
;
147 /* unset current since no led is lighting */
148 pm860x_set_bits(led
->i2c
, __led_off(led
->port
),
149 LED_CURRENT_MASK
, 0);
150 mask
= __blink_ctl_mask(led
->port
);
151 pm860x_set_bits(led
->i2c
, PM8606_WLED3B
, mask
, 0);
154 led
->current_brightness
= led
->brightness
;
155 dev_dbg(chip
->dev
, "Update LED. (reg:%d, brightness:%d)\n",
156 __led_off(led
->port
), led
->brightness
);
157 mutex_unlock(&led
->lock
);
160 static void pm860x_led_set(struct led_classdev
*cdev
,
161 enum led_brightness value
)
163 struct pm860x_led
*data
= container_of(cdev
, struct pm860x_led
, cdev
);
165 data
->brightness
= value
>> 3;
166 schedule_work(&data
->work
);
169 static int pm860x_led_probe(struct platform_device
*pdev
)
171 struct pm860x_chip
*chip
= dev_get_drvdata(pdev
->dev
.parent
);
172 struct pm860x_led_pdata
*pdata
;
173 struct pm860x_led
*data
;
174 struct mfd_cell
*cell
;
175 struct resource
*res
;
178 res
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
180 dev_err(&pdev
->dev
, "No I/O resource!\n");
184 cell
= pdev
->dev
.platform_data
;
187 pdata
= cell
->mfd_data
;
189 dev_err(&pdev
->dev
, "No platform data!\n");
193 data
= kzalloc(sizeof(struct pm860x_led
), GFP_KERNEL
);
196 strncpy(data
->name
, res
->name
, MFD_NAME_SIZE
- 1);
197 dev_set_drvdata(&pdev
->dev
, data
);
199 data
->i2c
= (chip
->id
== CHIP_PM8606
) ? chip
->client
: chip
->companion
;
200 data
->iset
= pdata
->iset
;
201 data
->port
= pdata
->flags
;
202 if (data
->port
< 0) {
203 dev_err(&pdev
->dev
, "check device failed\n");
208 data
->current_brightness
= 0;
209 data
->cdev
.name
= data
->name
;
210 data
->cdev
.brightness_set
= pm860x_led_set
;
211 mutex_init(&data
->lock
);
212 INIT_WORK(&data
->work
, pm860x_led_work
);
214 ret
= led_classdev_register(chip
->dev
, &data
->cdev
);
216 dev_err(&pdev
->dev
, "Failed to register LED: %d\n", ret
);
219 pm860x_led_set(&data
->cdev
, 0);
226 static int pm860x_led_remove(struct platform_device
*pdev
)
228 struct pm860x_led
*data
= platform_get_drvdata(pdev
);
230 led_classdev_unregister(&data
->cdev
);
236 static struct platform_driver pm860x_led_driver
= {
238 .name
= "88pm860x-led",
239 .owner
= THIS_MODULE
,
241 .probe
= pm860x_led_probe
,
242 .remove
= pm860x_led_remove
,
245 static int __devinit
pm860x_led_init(void)
247 return platform_driver_register(&pm860x_led_driver
);
249 module_init(pm860x_led_init
);
251 static void __devexit
pm860x_led_exit(void)
253 platform_driver_unregister(&pm860x_led_driver
);
255 module_exit(pm860x_led_exit
);
257 MODULE_DESCRIPTION("LED driver for Marvell PM860x");
258 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
259 MODULE_LICENSE("GPL");
260 MODULE_ALIAS("platform:88pm860x-led");