Commit | Line | Data |
---|---|---|
0c2a665a SJ |
1 | /* |
2 | * Simple driver for Texas Instruments LM3630 Backlight driver chip | |
3 | * Copyright (C) 2012 Texas Instruments | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | */ | |
10 | #include <linux/module.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/i2c.h> | |
13 | #include <linux/backlight.h> | |
14 | #include <linux/err.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/uaccess.h> | |
17 | #include <linux/interrupt.h> | |
18 | #include <linux/regmap.h> | |
19 | #include <linux/platform_data/lm3630_bl.h> | |
20 | ||
21 | #define REG_CTRL 0x00 | |
22 | #define REG_CONFIG 0x01 | |
23 | #define REG_BRT_A 0x03 | |
24 | #define REG_BRT_B 0x04 | |
25 | #define REG_INT_STATUS 0x09 | |
26 | #define REG_INT_EN 0x0A | |
27 | #define REG_FAULT 0x0B | |
28 | #define REG_PWM_OUTLOW 0x12 | |
29 | #define REG_PWM_OUTHIGH 0x13 | |
30 | #define REG_MAX 0x1F | |
31 | ||
32 | #define INT_DEBOUNCE_MSEC 10 | |
33 | ||
34 | enum lm3630_leds { | |
35 | BLED_ALL = 0, | |
36 | BLED_1, | |
37 | BLED_2 | |
38 | }; | |
39 | ||
d7b6bdaa | 40 | static const char * const bled_name[] = { |
0c2a665a SJ |
41 | [BLED_ALL] = "lm3630_bled", /*Bank1 controls all string */ |
42 | [BLED_1] = "lm3630_bled1", /*Bank1 controls bled1 */ | |
43 | [BLED_2] = "lm3630_bled2", /*Bank1 or 2 controls bled2 */ | |
44 | }; | |
45 | ||
46 | struct lm3630_chip_data { | |
47 | struct device *dev; | |
48 | struct delayed_work work; | |
49 | int irq; | |
50 | struct workqueue_struct *irqthread; | |
51 | struct lm3630_platform_data *pdata; | |
52 | struct backlight_device *bled1; | |
53 | struct backlight_device *bled2; | |
54 | struct regmap *regmap; | |
55 | }; | |
56 | ||
57 | /* initialize chip */ | |
1b9e450d | 58 | static int lm3630_chip_init(struct lm3630_chip_data *pchip) |
0c2a665a SJ |
59 | { |
60 | int ret; | |
61 | unsigned int reg_val; | |
62 | struct lm3630_platform_data *pdata = pchip->pdata; | |
63 | ||
64 | /*pwm control */ | |
65 | reg_val = ((pdata->pwm_active & 0x01) << 2) | (pdata->pwm_ctrl & 0x03); | |
66 | ret = regmap_update_bits(pchip->regmap, REG_CONFIG, 0x07, reg_val); | |
67 | if (ret < 0) | |
68 | goto out; | |
69 | ||
70 | /* bank control */ | |
71 | reg_val = ((pdata->bank_b_ctrl & 0x01) << 1) | | |
72 | (pdata->bank_a_ctrl & 0x07); | |
73 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x07, reg_val); | |
74 | if (ret < 0) | |
75 | goto out; | |
76 | ||
77 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
78 | if (ret < 0) | |
79 | goto out; | |
80 | ||
81 | /* set initial brightness */ | |
82 | if (pdata->bank_a_ctrl != BANK_A_CTRL_DISABLE) { | |
83 | ret = regmap_write(pchip->regmap, | |
84 | REG_BRT_A, pdata->init_brt_led1); | |
85 | if (ret < 0) | |
86 | goto out; | |
87 | } | |
88 | ||
89 | if (pdata->bank_b_ctrl != BANK_B_CTRL_DISABLE) { | |
90 | ret = regmap_write(pchip->regmap, | |
91 | REG_BRT_B, pdata->init_brt_led2); | |
92 | if (ret < 0) | |
93 | goto out; | |
94 | } | |
95 | return ret; | |
96 | ||
97 | out: | |
98 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
99 | return ret; | |
100 | } | |
101 | ||
102 | /* interrupt handling */ | |
103 | static void lm3630_delayed_func(struct work_struct *work) | |
104 | { | |
105 | int ret; | |
106 | unsigned int reg_val; | |
107 | struct lm3630_chip_data *pchip; | |
108 | ||
109 | pchip = container_of(work, struct lm3630_chip_data, work.work); | |
110 | ||
111 | ret = regmap_read(pchip->regmap, REG_INT_STATUS, ®_val); | |
112 | if (ret < 0) { | |
113 | dev_err(pchip->dev, | |
114 | "i2c failed to access REG_INT_STATUS Register\n"); | |
115 | return; | |
116 | } | |
117 | ||
118 | dev_info(pchip->dev, "REG_INT_STATUS Register is 0x%x\n", reg_val); | |
119 | } | |
120 | ||
121 | static irqreturn_t lm3630_isr_func(int irq, void *chip) | |
122 | { | |
123 | int ret; | |
124 | struct lm3630_chip_data *pchip = chip; | |
125 | unsigned long delay = msecs_to_jiffies(INT_DEBOUNCE_MSEC); | |
126 | ||
127 | queue_delayed_work(pchip->irqthread, &pchip->work, delay); | |
128 | ||
129 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
130 | if (ret < 0) | |
131 | goto out; | |
132 | ||
133 | return IRQ_HANDLED; | |
134 | out: | |
135 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
136 | return IRQ_HANDLED; | |
137 | } | |
138 | ||
139 | static int lm3630_intr_config(struct lm3630_chip_data *pchip) | |
140 | { | |
141 | INIT_DELAYED_WORK(&pchip->work, lm3630_delayed_func); | |
142 | pchip->irqthread = create_singlethread_workqueue("lm3630-irqthd"); | |
143 | if (!pchip->irqthread) { | |
144 | dev_err(pchip->dev, "create irq thread fail...\n"); | |
145 | return -1; | |
146 | } | |
147 | if (request_threaded_irq | |
148 | (pchip->irq, NULL, lm3630_isr_func, | |
149 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm3630_irq", pchip)) { | |
150 | dev_err(pchip->dev, "request threaded irq fail..\n"); | |
151 | return -1; | |
152 | } | |
153 | return 0; | |
154 | } | |
155 | ||
156 | static bool | |
157 | set_intensity(struct backlight_device *bl, struct lm3630_chip_data *pchip) | |
158 | { | |
159 | if (!pchip->pdata->pwm_set_intensity) | |
160 | return false; | |
161 | pchip->pdata->pwm_set_intensity(bl->props.brightness - 1, | |
162 | pchip->pdata->pwm_period); | |
163 | return true; | |
164 | } | |
165 | ||
166 | /* update and get brightness */ | |
167 | static int lm3630_bank_a_update_status(struct backlight_device *bl) | |
168 | { | |
169 | int ret; | |
170 | struct lm3630_chip_data *pchip = bl_get_data(bl); | |
171 | enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; | |
172 | ||
173 | /* brightness 0 means disable */ | |
174 | if (!bl->props.brightness) { | |
175 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x04, 0x00); | |
176 | if (ret < 0) | |
177 | goto out; | |
178 | return bl->props.brightness; | |
179 | } | |
180 | ||
181 | /* pwm control */ | |
182 | if (pwm_ctrl == PWM_CTRL_BANK_A || pwm_ctrl == PWM_CTRL_BANK_ALL) { | |
183 | if (!set_intensity(bl, pchip)) | |
184 | dev_err(pchip->dev, "No pwm control func. in plat-data\n"); | |
185 | } else { | |
186 | ||
187 | /* i2c control */ | |
188 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
189 | if (ret < 0) | |
190 | goto out; | |
191 | mdelay(1); | |
192 | ret = regmap_write(pchip->regmap, | |
193 | REG_BRT_A, bl->props.brightness - 1); | |
194 | if (ret < 0) | |
195 | goto out; | |
196 | } | |
197 | return bl->props.brightness; | |
198 | out: | |
199 | dev_err(pchip->dev, "i2c failed to access REG_CTRL\n"); | |
200 | return bl->props.brightness; | |
201 | } | |
202 | ||
203 | static int lm3630_bank_a_get_brightness(struct backlight_device *bl) | |
204 | { | |
205 | unsigned int reg_val; | |
206 | int brightness, ret; | |
207 | struct lm3630_chip_data *pchip = bl_get_data(bl); | |
208 | enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; | |
209 | ||
210 | if (pwm_ctrl == PWM_CTRL_BANK_A || pwm_ctrl == PWM_CTRL_BANK_ALL) { | |
211 | ret = regmap_read(pchip->regmap, REG_PWM_OUTHIGH, ®_val); | |
212 | if (ret < 0) | |
213 | goto out; | |
214 | brightness = reg_val & 0x01; | |
215 | ret = regmap_read(pchip->regmap, REG_PWM_OUTLOW, ®_val); | |
216 | if (ret < 0) | |
217 | goto out; | |
218 | brightness = ((brightness << 8) | reg_val) + 1; | |
219 | } else { | |
220 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
221 | if (ret < 0) | |
222 | goto out; | |
223 | mdelay(1); | |
224 | ret = regmap_read(pchip->regmap, REG_BRT_A, ®_val); | |
225 | if (ret < 0) | |
226 | goto out; | |
227 | brightness = reg_val + 1; | |
228 | } | |
229 | bl->props.brightness = brightness; | |
230 | return bl->props.brightness; | |
231 | out: | |
232 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
233 | return 0; | |
234 | } | |
235 | ||
236 | static const struct backlight_ops lm3630_bank_a_ops = { | |
237 | .options = BL_CORE_SUSPENDRESUME, | |
238 | .update_status = lm3630_bank_a_update_status, | |
239 | .get_brightness = lm3630_bank_a_get_brightness, | |
240 | }; | |
241 | ||
242 | static int lm3630_bank_b_update_status(struct backlight_device *bl) | |
243 | { | |
244 | int ret; | |
245 | struct lm3630_chip_data *pchip = bl_get_data(bl); | |
246 | enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; | |
247 | ||
248 | if (pwm_ctrl == PWM_CTRL_BANK_B || pwm_ctrl == PWM_CTRL_BANK_ALL) { | |
249 | if (!set_intensity(bl, pchip)) | |
250 | dev_err(pchip->dev, | |
251 | "no pwm control func. in plat-data\n"); | |
252 | } else { | |
253 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
254 | if (ret < 0) | |
255 | goto out; | |
256 | mdelay(1); | |
257 | ret = regmap_write(pchip->regmap, | |
258 | REG_BRT_B, bl->props.brightness - 1); | |
259 | } | |
260 | return bl->props.brightness; | |
261 | out: | |
262 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
263 | return bl->props.brightness; | |
264 | } | |
265 | ||
266 | static int lm3630_bank_b_get_brightness(struct backlight_device *bl) | |
267 | { | |
268 | unsigned int reg_val; | |
269 | int brightness, ret; | |
270 | struct lm3630_chip_data *pchip = bl_get_data(bl); | |
271 | enum lm3630_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl; | |
272 | ||
273 | if (pwm_ctrl == PWM_CTRL_BANK_B || pwm_ctrl == PWM_CTRL_BANK_ALL) { | |
274 | ret = regmap_read(pchip->regmap, REG_PWM_OUTHIGH, ®_val); | |
275 | if (ret < 0) | |
276 | goto out; | |
277 | brightness = reg_val & 0x01; | |
278 | ret = regmap_read(pchip->regmap, REG_PWM_OUTLOW, ®_val); | |
279 | if (ret < 0) | |
280 | goto out; | |
281 | brightness = ((brightness << 8) | reg_val) + 1; | |
282 | } else { | |
283 | ret = regmap_update_bits(pchip->regmap, REG_CTRL, 0x80, 0x00); | |
284 | if (ret < 0) | |
285 | goto out; | |
286 | mdelay(1); | |
287 | ret = regmap_read(pchip->regmap, REG_BRT_B, ®_val); | |
288 | if (ret < 0) | |
289 | goto out; | |
290 | brightness = reg_val + 1; | |
291 | } | |
292 | bl->props.brightness = brightness; | |
293 | ||
294 | return bl->props.brightness; | |
295 | out: | |
296 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
297 | return bl->props.brightness; | |
298 | } | |
299 | ||
300 | static const struct backlight_ops lm3630_bank_b_ops = { | |
301 | .options = BL_CORE_SUSPENDRESUME, | |
302 | .update_status = lm3630_bank_b_update_status, | |
303 | .get_brightness = lm3630_bank_b_get_brightness, | |
304 | }; | |
305 | ||
306 | static int lm3630_backlight_register(struct lm3630_chip_data *pchip, | |
307 | enum lm3630_leds ledno) | |
308 | { | |
309 | const char *name = bled_name[ledno]; | |
310 | struct backlight_properties props; | |
311 | struct lm3630_platform_data *pdata = pchip->pdata; | |
312 | ||
313 | props.type = BACKLIGHT_RAW; | |
314 | switch (ledno) { | |
315 | case BLED_1: | |
316 | case BLED_ALL: | |
317 | props.brightness = pdata->init_brt_led1; | |
318 | props.max_brightness = pdata->max_brt_led1; | |
319 | pchip->bled1 = | |
320 | backlight_device_register(name, pchip->dev, pchip, | |
321 | &lm3630_bank_a_ops, &props); | |
322 | if (IS_ERR(pchip->bled1)) | |
5a429bdd | 323 | return PTR_ERR(pchip->bled1); |
0c2a665a SJ |
324 | break; |
325 | case BLED_2: | |
326 | props.brightness = pdata->init_brt_led2; | |
327 | props.max_brightness = pdata->max_brt_led2; | |
328 | pchip->bled2 = | |
329 | backlight_device_register(name, pchip->dev, pchip, | |
330 | &lm3630_bank_b_ops, &props); | |
331 | if (IS_ERR(pchip->bled2)) | |
5a429bdd | 332 | return PTR_ERR(pchip->bled2); |
0c2a665a SJ |
333 | break; |
334 | } | |
335 | return 0; | |
336 | } | |
337 | ||
338 | static void lm3630_backlight_unregister(struct lm3630_chip_data *pchip) | |
339 | { | |
340 | if (pchip->bled1) | |
341 | backlight_device_unregister(pchip->bled1); | |
342 | if (pchip->bled2) | |
343 | backlight_device_unregister(pchip->bled2); | |
344 | } | |
345 | ||
346 | static const struct regmap_config lm3630_regmap = { | |
347 | .reg_bits = 8, | |
348 | .val_bits = 8, | |
349 | .max_register = REG_MAX, | |
350 | }; | |
351 | ||
1b9e450d | 352 | static int lm3630_probe(struct i2c_client *client, |
0c2a665a SJ |
353 | const struct i2c_device_id *id) |
354 | { | |
355 | struct lm3630_platform_data *pdata = client->dev.platform_data; | |
356 | struct lm3630_chip_data *pchip; | |
357 | int ret; | |
358 | ||
359 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | |
360 | dev_err(&client->dev, "fail : i2c functionality check...\n"); | |
361 | return -EOPNOTSUPP; | |
362 | } | |
363 | ||
364 | if (pdata == NULL) { | |
365 | dev_err(&client->dev, "fail : no platform data.\n"); | |
366 | return -ENODATA; | |
367 | } | |
368 | ||
369 | pchip = devm_kzalloc(&client->dev, sizeof(struct lm3630_chip_data), | |
370 | GFP_KERNEL); | |
371 | if (!pchip) | |
372 | return -ENOMEM; | |
373 | pchip->pdata = pdata; | |
374 | pchip->dev = &client->dev; | |
375 | ||
376 | pchip->regmap = devm_regmap_init_i2c(client, &lm3630_regmap); | |
377 | if (IS_ERR(pchip->regmap)) { | |
378 | ret = PTR_ERR(pchip->regmap); | |
379 | dev_err(&client->dev, "fail : allocate register map: %d\n", | |
380 | ret); | |
381 | return ret; | |
382 | } | |
383 | i2c_set_clientdata(client, pchip); | |
384 | ||
385 | /* chip initialize */ | |
386 | ret = lm3630_chip_init(pchip); | |
387 | if (ret < 0) { | |
388 | dev_err(&client->dev, "fail : init chip\n"); | |
389 | goto err_chip_init; | |
390 | } | |
391 | ||
392 | switch (pdata->bank_a_ctrl) { | |
393 | case BANK_A_CTRL_ALL: | |
394 | ret = lm3630_backlight_register(pchip, BLED_ALL); | |
395 | pdata->bank_b_ctrl = BANK_B_CTRL_DISABLE; | |
396 | break; | |
397 | case BANK_A_CTRL_LED1: | |
398 | ret = lm3630_backlight_register(pchip, BLED_1); | |
399 | break; | |
400 | case BANK_A_CTRL_LED2: | |
401 | ret = lm3630_backlight_register(pchip, BLED_2); | |
402 | pdata->bank_b_ctrl = BANK_B_CTRL_DISABLE; | |
403 | break; | |
404 | default: | |
405 | break; | |
406 | } | |
407 | ||
408 | if (ret < 0) | |
409 | goto err_bl_reg; | |
410 | ||
411 | if (pdata->bank_b_ctrl && pchip->bled2 == NULL) { | |
412 | ret = lm3630_backlight_register(pchip, BLED_2); | |
413 | if (ret < 0) | |
414 | goto err_bl_reg; | |
415 | } | |
416 | ||
417 | /* interrupt enable : irq 0 is not allowed for lm3630 */ | |
418 | pchip->irq = client->irq; | |
419 | if (pchip->irq) | |
420 | lm3630_intr_config(pchip); | |
421 | ||
422 | dev_info(&client->dev, "LM3630 backlight register OK.\n"); | |
423 | return 0; | |
424 | ||
425 | err_bl_reg: | |
426 | dev_err(&client->dev, "fail : backlight register.\n"); | |
427 | lm3630_backlight_unregister(pchip); | |
428 | err_chip_init: | |
429 | return ret; | |
430 | } | |
431 | ||
7e4b9d0b | 432 | static int lm3630_remove(struct i2c_client *client) |
0c2a665a SJ |
433 | { |
434 | int ret; | |
435 | struct lm3630_chip_data *pchip = i2c_get_clientdata(client); | |
436 | ||
437 | ret = regmap_write(pchip->regmap, REG_BRT_A, 0); | |
438 | if (ret < 0) | |
439 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
440 | ||
441 | ret = regmap_write(pchip->regmap, REG_BRT_B, 0); | |
442 | if (ret < 0) | |
443 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
444 | ||
445 | lm3630_backlight_unregister(pchip); | |
446 | if (pchip->irq) { | |
447 | free_irq(pchip->irq, pchip); | |
448 | flush_workqueue(pchip->irqthread); | |
449 | destroy_workqueue(pchip->irqthread); | |
450 | } | |
451 | return 0; | |
452 | } | |
453 | ||
454 | static const struct i2c_device_id lm3630_id[] = { | |
455 | {LM3630_NAME, 0}, | |
456 | {} | |
457 | }; | |
458 | ||
459 | MODULE_DEVICE_TABLE(i2c, lm3630_id); | |
460 | ||
461 | static struct i2c_driver lm3630_i2c_driver = { | |
462 | .driver = { | |
463 | .name = LM3630_NAME, | |
464 | }, | |
465 | .probe = lm3630_probe, | |
d1723fa2 | 466 | .remove = lm3630_remove, |
0c2a665a SJ |
467 | .id_table = lm3630_id, |
468 | }; | |
469 | ||
470 | module_i2c_driver(lm3630_i2c_driver); | |
471 | ||
472 | MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3630"); | |
473 | MODULE_AUTHOR("G.Shark Jeong <gshark.jeong@gmail.com>"); | |
474 | MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>"); | |
475 | MODULE_LICENSE("GPL v2"); |