blob: 3b3a24af28be6cbcf75b91d630d3bccc607aa536 [file] [log] [blame]
Anirudh Ghayalc2019332011-11-12 06:29:10 +05301/* Copyright (c) 2010, 2011, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12#include <linux/kernel.h>
13#include <linux/init.h>
14#include <linux/platform_device.h>
15#include <linux/leds.h>
16#include <linux/workqueue.h>
17#include <linux/spinlock.h>
Anirudh Ghayalc2019332011-11-12 06:29:10 +053018#include <linux/mfd/pm8xxx/core.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070019#include <linux/leds-pmic8058.h>
20
21#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
22#define PM8058_DRV_KEYPAD_BL_MASK 0xf0
23#define PM8058_DRV_KEYPAD_BL_SHIFT 0x04
24
25#define SSBI_REG_ADDR_FLASH_DRV0 0x49
26#define PM8058_DRV_FLASH_MASK 0xf0
27#define PM8058_DRV_FLASH_SHIFT 0x04
28
29#define SSBI_REG_ADDR_FLASH_DRV1 0xFB
30
31#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131
32#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n))
33#define PM8058_DRV_LED_CTRL_MASK 0xf8
34#define PM8058_DRV_LED_CTRL_SHIFT 0x03
35
36#define MAX_FLASH_CURRENT 300
37#define MAX_KEYPAD_CURRENT 300
38#define MAX_KEYPAD_BL_LEVEL (1 << 4)
39#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */
40
41#define PMIC8058_LED_OFFSET(id) ((id) - PMIC8058_ID_LED_0)
42
43struct pmic8058_led_data {
Anirudh Ghayalc2019332011-11-12 06:29:10 +053044 struct device *dev;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070045 struct led_classdev cdev;
46 int id;
47 enum led_brightness brightness;
48 u8 flags;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070049 struct work_struct work;
50 struct mutex lock;
51 spinlock_t value_lock;
52 u8 reg_kp;
53 u8 reg_led_ctrl[3];
54 u8 reg_flash_led0;
55 u8 reg_flash_led1;
56};
57
58#define PM8058_MAX_LEDS 7
59static struct pmic8058_led_data led_data[PM8058_MAX_LEDS];
60
61static void kp_bl_set(struct pmic8058_led_data *led, enum led_brightness value)
62{
63 int rc;
64 u8 level;
65 unsigned long flags;
66
67 spin_lock_irqsave(&led->value_lock, flags);
68 level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
69 PM8058_DRV_KEYPAD_BL_MASK;
70
71 led->reg_kp &= ~PM8058_DRV_KEYPAD_BL_MASK;
72 led->reg_kp |= level;
73 spin_unlock_irqrestore(&led->value_lock, flags);
74
Anirudh Ghayalc2019332011-11-12 06:29:10 +053075 rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_DRV_KEYPAD,
76 led->reg_kp);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070077 if (rc)
78 pr_err("%s: can't set keypad backlight level\n", __func__);
79}
80
81static enum led_brightness kp_bl_get(struct pmic8058_led_data *led)
82{
83 if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >>
84 PM8058_DRV_KEYPAD_BL_SHIFT)
85 return LED_FULL;
86 else
87 return LED_OFF;
88}
89
90static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
91{
92 unsigned long flags;
93 int rc, offset;
94 u8 level, tmp;
95
96 spin_lock_irqsave(&led->value_lock, flags);
97
98 level = (led->brightness << PM8058_DRV_LED_CTRL_SHIFT) &
99 PM8058_DRV_LED_CTRL_MASK;
100
101 offset = PMIC8058_LED_OFFSET(led->id);
102 tmp = led->reg_led_ctrl[offset];
103
104 tmp &= ~PM8058_DRV_LED_CTRL_MASK;
105 tmp |= level;
106 spin_unlock_irqrestore(&led->value_lock, flags);
107
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530108 rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset),
109 tmp);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700110 if (rc) {
111 dev_err(led->cdev.dev, "can't set (%d) led value\n",
112 led->id);
113 return;
114 }
115
116 spin_lock_irqsave(&led->value_lock, flags);
117 led->reg_led_ctrl[offset] = tmp;
118 spin_unlock_irqrestore(&led->value_lock, flags);
119}
120
121static enum led_brightness led_lc_get(struct pmic8058_led_data *led)
122{
123 int offset;
124 u8 value;
125
126 offset = PMIC8058_LED_OFFSET(led->id);
127 value = led->reg_led_ctrl[offset];
128
129 if ((value & PM8058_DRV_LED_CTRL_MASK) >>
130 PM8058_DRV_LED_CTRL_SHIFT)
131 return LED_FULL;
132 else
133 return LED_OFF;
134}
135
136static void
137led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
138{
139 int rc;
140 u8 level;
141 unsigned long flags;
142 u8 reg_flash_led;
143 u16 reg_addr;
144
145 spin_lock_irqsave(&led->value_lock, flags);
146 level = (value << PM8058_DRV_FLASH_SHIFT) &
147 PM8058_DRV_FLASH_MASK;
148
149 if (led->id == PMIC8058_ID_FLASH_LED_0) {
150 led->reg_flash_led0 &= ~PM8058_DRV_FLASH_MASK;
151 led->reg_flash_led0 |= level;
152 reg_flash_led = led->reg_flash_led0;
153 reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
154 } else {
155 led->reg_flash_led1 &= ~PM8058_DRV_FLASH_MASK;
156 led->reg_flash_led1 |= level;
157 reg_flash_led = led->reg_flash_led1;
158 reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
159 }
160 spin_unlock_irqrestore(&led->value_lock, flags);
161
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530162 rc = pm8xxx_writeb(led->dev->parent, reg_addr, reg_flash_led);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700163 if (rc)
164 pr_err("%s: can't set flash led%d level %d\n", __func__,
165 led->id, rc);
166}
167
168int pm8058_set_flash_led_current(enum pmic8058_leds id, unsigned mA)
169{
170 struct pmic8058_led_data *led;
171
172 if ((id < PMIC8058_ID_FLASH_LED_0) || (id > PMIC8058_ID_FLASH_LED_1)) {
173 pr_err("%s: invalid LED ID (%d) specified\n", __func__, id);
174 return -EINVAL;
175 }
176
177 led = &led_data[id];
178 if (!led) {
179 pr_err("%s: flash led not available\n", __func__);
180 return -EINVAL;
181 }
182
183 if (mA > MAX_FLASH_CURRENT)
184 return -EINVAL;
185
186 led_flash_set(led, mA / 20);
187
188 return 0;
189}
190EXPORT_SYMBOL(pm8058_set_flash_led_current);
191
192int pm8058_set_led_current(enum pmic8058_leds id, unsigned mA)
193{
194 struct pmic8058_led_data *led;
195 int brightness = 0;
196
197 if ((id < PMIC8058_ID_LED_KB_LIGHT) || (id > PMIC8058_ID_FLASH_LED_1)) {
198 pr_err("%s: invalid LED ID (%d) specified\n", __func__, id);
199 return -EINVAL;
200 }
201
202 led = &led_data[id];
203 if (!led) {
204 pr_err("%s: flash led not available\n", __func__);
205 return -EINVAL;
206 }
207
208 switch (id) {
209 case PMIC8058_ID_LED_0:
210 case PMIC8058_ID_LED_1:
211 case PMIC8058_ID_LED_2:
212 brightness = mA / 2;
213 if (brightness > led->cdev.max_brightness)
214 return -EINVAL;
215 led_lc_set(led, brightness);
216 break;
217
218 case PMIC8058_ID_LED_KB_LIGHT:
219 case PMIC8058_ID_FLASH_LED_0:
220 case PMIC8058_ID_FLASH_LED_1:
221 brightness = mA / 20;
222 if (brightness > led->cdev.max_brightness)
223 return -EINVAL;
224 if (id == PMIC8058_ID_LED_KB_LIGHT)
225 kp_bl_set(led, brightness);
226 else
227 led_flash_set(led, brightness);
228 break;
229 }
230
231 return 0;
232}
233EXPORT_SYMBOL(pm8058_set_led_current);
234
235static void pmic8058_led_set(struct led_classdev *led_cdev,
236 enum led_brightness value)
237{
238 struct pmic8058_led_data *led;
239 unsigned long flags;
240
241 led = container_of(led_cdev, struct pmic8058_led_data, cdev);
242
243 spin_lock_irqsave(&led->value_lock, flags);
244 led->brightness = value;
245 schedule_work(&led->work);
246 spin_unlock_irqrestore(&led->value_lock, flags);
247}
248
249static void pmic8058_led_work(struct work_struct *work)
250{
251 struct pmic8058_led_data *led = container_of(work,
252 struct pmic8058_led_data, work);
253
254 mutex_lock(&led->lock);
255
256 switch (led->id) {
257 case PMIC8058_ID_LED_KB_LIGHT:
258 kp_bl_set(led, led->brightness);
259 break;
260 case PMIC8058_ID_LED_0:
261 case PMIC8058_ID_LED_1:
262 case PMIC8058_ID_LED_2:
263 led_lc_set(led, led->brightness);
264 break;
265 case PMIC8058_ID_FLASH_LED_0:
266 case PMIC8058_ID_FLASH_LED_1:
267 led_flash_set(led, led->brightness);
268 break;
269 }
270
271 mutex_unlock(&led->lock);
272}
273
274static enum led_brightness pmic8058_led_get(struct led_classdev *led_cdev)
275{
276 struct pmic8058_led_data *led;
277
278 led = container_of(led_cdev, struct pmic8058_led_data, cdev);
279
280 switch (led->id) {
281 case PMIC8058_ID_LED_KB_LIGHT:
282 return kp_bl_get(led);
283 case PMIC8058_ID_LED_0:
284 case PMIC8058_ID_LED_1:
285 case PMIC8058_ID_LED_2:
286 return led_lc_get(led);
287 }
288 return LED_OFF;
289}
290
291static int pmic8058_led_probe(struct platform_device *pdev)
292{
293 struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
294 struct pmic8058_led_data *led_dat;
295 struct pmic8058_led *curr_led;
296 int rc, i = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700297 u8 reg_kp;
298 u8 reg_led_ctrl[3];
299 u8 reg_flash_led0;
300 u8 reg_flash_led1;
301
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700302 if (pdata == NULL) {
303 dev_err(&pdev->dev, "platform data not supplied\n");
304 return -EINVAL;
305 }
306
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530307 rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_DRV_KEYPAD, &reg_kp);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700308 if (rc) {
309 dev_err(&pdev->dev, "can't get keypad backlight level\n");
310 goto err_reg_read;
311 }
312
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530313 rc = pm8xxx_read_buf(pdev->dev.parent, SSBI_REG_ADDR_LED_CTRL_BASE,
314 reg_led_ctrl, 3);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700315 if (rc) {
316 dev_err(&pdev->dev, "can't get led levels\n");
317 goto err_reg_read;
318 }
319
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530320 rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV0,
321 &reg_flash_led0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700322 if (rc) {
323 dev_err(&pdev->dev, "can't read flash led0\n");
324 goto err_reg_read;
325 }
326
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530327 rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV1,
328 &reg_flash_led1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700329 if (rc) {
330 dev_err(&pdev->dev, "can't get flash led1\n");
331 goto err_reg_read;
332 }
333
334 for (i = 0; i < pdata->num_leds; i++) {
335 curr_led = &pdata->leds[i];
336 led_dat = &led_data[curr_led->id];
337
338 led_dat->cdev.name = curr_led->name;
339 led_dat->cdev.default_trigger = curr_led->default_trigger;
340 led_dat->cdev.brightness_set = pmic8058_led_set;
341 led_dat->cdev.brightness_get = pmic8058_led_get;
342 led_dat->cdev.brightness = LED_OFF;
343 led_dat->cdev.max_brightness = curr_led->max_brightness;
344 led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
345
346 led_dat->id = curr_led->id;
347 led_dat->reg_kp = reg_kp;
348 memcpy(led_data->reg_led_ctrl, reg_led_ctrl,
349 sizeof(reg_led_ctrl));
350 led_dat->reg_flash_led0 = reg_flash_led0;
351 led_dat->reg_flash_led1 = reg_flash_led1;
352
353 if (!((led_dat->id >= PMIC8058_ID_LED_KB_LIGHT) &&
354 (led_dat->id <= PMIC8058_ID_FLASH_LED_1))) {
355 dev_err(&pdev->dev, "invalid LED ID (%d) specified\n",
356 led_dat->id);
357 rc = -EINVAL;
358 goto fail_id_check;
359 }
360
Anirudh Ghayalc2019332011-11-12 06:29:10 +0530361 led_dat->dev = &pdev->dev;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700362
363 mutex_init(&led_dat->lock);
364 spin_lock_init(&led_dat->value_lock);
365 INIT_WORK(&led_dat->work, pmic8058_led_work);
366
367 rc = led_classdev_register(&pdev->dev, &led_dat->cdev);
368 if (rc) {
369 dev_err(&pdev->dev, "unable to register led %d\n",
370 led_dat->id);
371 goto fail_id_check;
372 }
373 }
374
375 platform_set_drvdata(pdev, led_data);
376
377 return 0;
378
379err_reg_read:
380fail_id_check:
381 if (i > 0) {
382 for (i = i - 1; i >= 0; i--)
383 led_classdev_unregister(&led_data[i].cdev);
384 }
385 return rc;
386}
387
388static int __devexit pmic8058_led_remove(struct platform_device *pdev)
389{
390 int i;
391 struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
392 struct pmic8058_led_data *led = platform_get_drvdata(pdev);
393
394 for (i = 0; i < pdata->num_leds; i++) {
395 led_classdev_unregister(&led[led->id].cdev);
396 cancel_work_sync(&led[led->id].work);
397 }
398
399 return 0;
400}
401
402static struct platform_driver pmic8058_led_driver = {
403 .probe = pmic8058_led_probe,
404 .remove = __devexit_p(pmic8058_led_remove),
405 .driver = {
406 .name = "pm8058-led",
407 .owner = THIS_MODULE,
408 },
409};
410
411static int __init pmic8058_led_init(void)
412{
413 return platform_driver_register(&pmic8058_led_driver);
414}
415module_init(pmic8058_led_init);
416
417static void __exit pmic8058_led_exit(void)
418{
419 platform_driver_unregister(&pmic8058_led_driver);
420}
421module_exit(pmic8058_led_exit);
422
423MODULE_DESCRIPTION("PMIC8058 LEDs driver");
424MODULE_LICENSE("GPL v2");
425MODULE_VERSION("1.0");
426MODULE_ALIAS("platform:pmic8058-led");