diff options
author | Daniel jeong | 2014-05-02 05:50:07 -0500 |
---|---|---|
committer | Daniel jeong | 2014-05-02 05:50:07 -0500 |
commit | c5e82cede1b4e649dc7bc42486f3959df935aaca (patch) | |
tree | 274c3d214df3e26cd78146d36db17610c529d68e /drivers | |
parent | 91ddf54ee4f018514af93cac252e3170ad635fa3 (diff) | |
download | kernel-c5e82cede1b4e649dc7bc42486f3959df935aaca.tar.gz kernel-c5e82cede1b4e649dc7bc42486f3959df935aaca.tar.xz kernel-c5e82cede1b4e649dc7bc42486f3959df935aaca.zip |
Add LP8860 Device Driver - working with Android Test Applicationv0.1.LP8860.V0
Signed-off-by: Daniel jeong <daniel.jeong@ti.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/video/backlight/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/lp8860_bl.c | 650 |
3 files changed, 658 insertions, 0 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 5a3eb2e..908048f 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig | |||
@@ -397,6 +397,13 @@ config BACKLIGHT_LP8788 | |||
397 | help | 397 | help |
398 | This supports TI LP8788 backlight driver. | 398 | This supports TI LP8788 backlight driver. |
399 | 399 | ||
400 | config BACKLIGHT_LP8860 | ||
401 | tristate "Backlight Driver for LP8860" | ||
402 | depends on BACKLIGHT_CLASS_DEVICE && I2C | ||
403 | select REGMAP_I2C | ||
404 | help | ||
405 | This supports TI LP8860 Backlight Driver | ||
406 | |||
400 | config BACKLIGHT_OT200 | 407 | config BACKLIGHT_OT200 |
401 | tristate "Backlight driver for ot200 visualisation device" | 408 | tristate "Backlight driver for ot200 visualisation device" |
402 | depends on BACKLIGHT_CLASS_DEVICE && CS5535_MFGPT && GPIO_CS5535 | 409 | depends on BACKLIGHT_CLASS_DEVICE && CS5535_MFGPT && GPIO_CS5535 |
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index bb82002..cbc5ac3 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile | |||
@@ -42,6 +42,7 @@ obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o | |||
42 | obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o | 42 | obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o |
43 | obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o | 43 | obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o |
44 | obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o | 44 | obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o |
45 | obj-$(CONFIG_BACKLIGHT_LP8860) += lp8860_bl.o | ||
45 | obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o | 46 | obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o |
46 | obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o | 47 | obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o |
47 | obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o | 48 | obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o |
diff --git a/drivers/video/backlight/lp8860_bl.c b/drivers/video/backlight/lp8860_bl.c new file mode 100644 index 0000000..7a5017e --- /dev/null +++ b/drivers/video/backlight/lp8860_bl.c | |||
@@ -0,0 +1,650 @@ | |||
1 | /* | ||
2 | * Simple driver for Texas Instruments lp8860 Backlight driver chip | ||
3 | * | ||
4 | * Copyright (C) 2014 Texas Instruments | ||
5 | * Author: Daniel Jeong <gshark.jeong@gmail.com> | ||
6 | * Ldd Mlp <ldd-mlp@list.ti.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | * | ||
12 | */ | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/backlight.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <linux/platform_data/lp8860_bl.h> | ||
19 | #include <linux/regmap.h> | ||
20 | #include <linux/slab.h> | ||
21 | #include <linux/uaccess.h> | ||
22 | |||
23 | #define REG_CL0_BRT_H 0x00 | ||
24 | #define REG_CL0_BRT_L 0x01 | ||
25 | #define REG_CL0_I_H 0x02 | ||
26 | #define REG_CL0_I_L 0x03 | ||
27 | |||
28 | #define REG_CL1_BRT_H 0x04 | ||
29 | #define REG_CL1_BRT_L 0x05 | ||
30 | #define REG_CL1_I 0x06 | ||
31 | |||
32 | #define REG_CL2_BRT_H 0x07 | ||
33 | #define REG_CL2_BRT_L 0x08 | ||
34 | #define REG_CL2_I 0x09 | ||
35 | |||
36 | #define REG_CL3_BRT_H 0x0a | ||
37 | #define REG_CL3_BRT_L 0x0b | ||
38 | #define REG_CL3_I 0x0c | ||
39 | |||
40 | #define REG_CONF 0x0d | ||
41 | #define REG_STATUS 0x0e | ||
42 | #define REG_ID 0x12 | ||
43 | |||
44 | #define REG_ROM_CTRL 0x19 | ||
45 | #define REG_ROM_ULOCK 0x1a | ||
46 | #define REG_ROM_START 0x60 | ||
47 | #define REG_ROM_END 0x78 | ||
48 | |||
49 | #define REG_EEPROM_START 0x60 | ||
50 | #define REG_EEPROM_END 0x78 | ||
51 | #define REG_MAX 0xFF | ||
52 | |||
53 | #define ULCODE0 0x08 | ||
54 | #define ULCODE1 0xba | ||
55 | #define ULCODE2 0xef | ||
56 | #define LCODE 0x00 | ||
57 | |||
58 | struct lp8860_chip { | ||
59 | struct device *dev; | ||
60 | |||
61 | struct lp8860_platform_data *pdata; | ||
62 | struct backlight_device *bled[LP8860_LED_MAX]; | ||
63 | struct regmap *regmap; | ||
64 | |||
65 | /* mlp test interface */ | ||
66 | unsigned int reg; | ||
67 | unsigned int reg_data; | ||
68 | }; | ||
69 | |||
70 | /* brightness control */ | ||
71 | static int lp8860_bled_update_status(struct backlight_device *bl, | ||
72 | enum lp8860_leds nsr) | ||
73 | { | ||
74 | int ret = -EINVAL; | ||
75 | struct lp8860_chip *pchip = bl_get_data(bl); | ||
76 | |||
77 | if (pchip->pdata->mode) | ||
78 | return 0; | ||
79 | |||
80 | if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) | ||
81 | bl->props.brightness = 0; | ||
82 | |||
83 | switch (nsr) { | ||
84 | case LP8860_LED0: | ||
85 | ret = regmap_write(pchip->regmap, | ||
86 | REG_CL0_BRT_H, bl->props.brightness >> 8); | ||
87 | ret |= regmap_write(pchip->regmap, | ||
88 | REG_CL0_BRT_L, bl->props.brightness & 0xff); | ||
89 | break; | ||
90 | case LP8860_LED1: | ||
91 | ret = regmap_write(pchip->regmap, | ||
92 | REG_CL1_BRT_H, | ||
93 | (bl->props.brightness >> 8) & 0x1f); | ||
94 | ret |= regmap_write(pchip->regmap, REG_CL1_BRT_L, | ||
95 | bl->props.brightness & 0xff); | ||
96 | break; | ||
97 | case LP8860_LED2: | ||
98 | ret = regmap_write(pchip->regmap, | ||
99 | REG_CL2_BRT_H, | ||
100 | (bl->props.brightness >> 8) & 0x1f); | ||
101 | ret |= regmap_write(pchip->regmap, REG_CL2_BRT_L, | ||
102 | bl->props.brightness & 0xff); | ||
103 | break; | ||
104 | case LP8860_LED3: | ||
105 | ret = regmap_write(pchip->regmap, | ||
106 | REG_CL3_BRT_H, | ||
107 | (bl->props.brightness >> 8) & 0x1f); | ||
108 | ret |= regmap_write(pchip->regmap, REG_CL3_BRT_L, | ||
109 | bl->props.brightness & 0xff); | ||
110 | break; | ||
111 | default: | ||
112 | BUG(); | ||
113 | } | ||
114 | if (ret < 0) | ||
115 | dev_err(pchip->dev, "fail : i2c access to register.\n"); | ||
116 | else | ||
117 | ret = bl->props.brightness; | ||
118 | |||
119 | return ret; | ||
120 | } | ||
121 | |||
122 | static int lp8860_bled_get_brightness(struct backlight_device *bl, | ||
123 | enum lp8860_leds nsr) | ||
124 | { | ||
125 | struct lp8860_chip *pchip = bl_get_data(bl); | ||
126 | unsigned int rval_h, rval_l; | ||
127 | int ret = -EINVAL; | ||
128 | |||
129 | switch (nsr) { | ||
130 | case LP8860_LED0: | ||
131 | ret = regmap_read(pchip->regmap, REG_CL0_BRT_H, &rval_h); | ||
132 | ret |= regmap_read(pchip->regmap, REG_CL0_BRT_L, &rval_l); | ||
133 | break; | ||
134 | case LP8860_LED1: | ||
135 | ret = regmap_read(pchip->regmap, REG_CL1_BRT_H, &rval_h); | ||
136 | ret |= regmap_read(pchip->regmap, REG_CL1_BRT_L, &rval_l); | ||
137 | break; | ||
138 | case LP8860_LED2: | ||
139 | ret = regmap_read(pchip->regmap, REG_CL2_BRT_H, &rval_h); | ||
140 | ret |= regmap_read(pchip->regmap, REG_CL2_BRT_L, &rval_l); | ||
141 | break; | ||
142 | case LP8860_LED3: | ||
143 | ret = regmap_read(pchip->regmap, REG_CL3_BRT_H, &rval_h); | ||
144 | ret |= regmap_read(pchip->regmap, REG_CL3_BRT_L, &rval_l); | ||
145 | break; | ||
146 | default: | ||
147 | BUG(); | ||
148 | } | ||
149 | if (ret < 0) { | ||
150 | dev_err(pchip->dev, "fail : i2c access to register.\n"); | ||
151 | return ret; | ||
152 | } | ||
153 | bl->props.brightness = (rval_h << 8) | rval_l; | ||
154 | return bl->props.brightness; | ||
155 | } | ||
156 | |||
157 | static int lp8860_update_status_bled0(struct backlight_device *bl) | ||
158 | { | ||
159 | return lp8860_bled_update_status(bl, LP8860_LED0); | ||
160 | } | ||
161 | |||
162 | static int lp8860_get_brightness_bled0(struct backlight_device *bl) | ||
163 | { | ||
164 | return lp8860_bled_get_brightness(bl, LP8860_LED0); | ||
165 | } | ||
166 | |||
167 | static int lp8860_update_status_bled1(struct backlight_device *bl) | ||
168 | { | ||
169 | return lp8860_bled_update_status(bl, LP8860_LED1); | ||
170 | } | ||
171 | |||
172 | static int lp8860_get_brightness_bled1(struct backlight_device *bl) | ||
173 | { | ||
174 | return lp8860_bled_get_brightness(bl, LP8860_LED1); | ||
175 | } | ||
176 | |||
177 | static int lp8860_update_status_bled2(struct backlight_device *bl) | ||
178 | { | ||
179 | return lp8860_bled_update_status(bl, LP8860_LED2); | ||
180 | } | ||
181 | |||
182 | static int lp8860_get_brightness_bled2(struct backlight_device *bl) | ||
183 | { | ||
184 | return lp8860_bled_get_brightness(bl, LP8860_LED2); | ||
185 | } | ||
186 | |||
187 | static int lp8860_update_status_bled3(struct backlight_device *bl) | ||
188 | { | ||
189 | return lp8860_bled_update_status(bl, LP8860_LED3); | ||
190 | } | ||
191 | |||
192 | static int lp8860_get_brightness_bled3(struct backlight_device *bl) | ||
193 | { | ||
194 | return lp8860_bled_get_brightness(bl, LP8860_LED3); | ||
195 | } | ||
196 | |||
197 | #define lp8860_bled_ops(_id)\ | ||
198 | {\ | ||
199 | .options = BL_CORE_SUSPENDRESUME,\ | ||
200 | .update_status = lp8860_update_status_bled##_id,\ | ||
201 | .get_brightness = lp8860_get_brightness_bled##_id,\ | ||
202 | } | ||
203 | |||
204 | static const struct backlight_ops lp8860_bled_ops[LP8860_LED_MAX] = { | ||
205 | [LP8860_LED0] = lp8860_bled_ops(0), | ||
206 | [LP8860_LED1] = lp8860_bled_ops(1), | ||
207 | [LP8860_LED2] = lp8860_bled_ops(2), | ||
208 | [LP8860_LED3] = lp8860_bled_ops(3), | ||
209 | }; | ||
210 | |||
211 | /* current control */ | ||
212 | static int lp8860_set_current(struct device *dev, | ||
213 | const char *buf, enum lp8860_leds nsr) | ||
214 | { | ||
215 | struct lp8860_chip *pchip = dev_get_drvdata(dev); | ||
216 | unsigned int ival; | ||
217 | ssize_t ret; | ||
218 | |||
219 | ret = kstrtouint(buf, 10, &ival); | ||
220 | if (ret) | ||
221 | return ret; | ||
222 | |||
223 | switch (nsr) { | ||
224 | case LP8860_LED0: | ||
225 | ival = min_t(unsigned int, ival, LP8860_LED0_BR_MAX); | ||
226 | ret = regmap_write(pchip->regmap, REG_CL0_I_H, ival >> 8); | ||
227 | ret |= regmap_write(pchip->regmap, REG_CL0_I_L, ival & 0xff); | ||
228 | break; | ||
229 | case LP8860_LED1: | ||
230 | ival = min_t(unsigned int, ival, LP8860_LED1_BR_MAX); | ||
231 | ret = regmap_write(pchip->regmap, REG_CL1_I, ival & 0xff); | ||
232 | break; | ||
233 | case LP8860_LED2: | ||
234 | ival = min_t(unsigned int, ival, LP8860_LED2_BR_MAX); | ||
235 | ret = regmap_write(pchip->regmap, REG_CL2_I, ival & 0xff); | ||
236 | break; | ||
237 | case LP8860_LED3: | ||
238 | ival = min_t(unsigned int, ival, LP8860_LED3_BR_MAX); | ||
239 | ret = regmap_write(pchip->regmap, REG_CL3_I, ival & 0xff); | ||
240 | break; | ||
241 | default: | ||
242 | BUG(); | ||
243 | } | ||
244 | if (ret < 0) | ||
245 | dev_err(pchip->dev, "fail : i2c access error.\n"); | ||
246 | |||
247 | return ret; | ||
248 | } | ||
249 | |||
250 | static ssize_t lp8860_current_store_bled0(struct device *dev, | ||
251 | struct device_attribute *devAttr, | ||
252 | const char *buf, size_t size) | ||
253 | { | ||
254 | int ret; | ||
255 | |||
256 | ret = lp8860_set_current(dev, buf, LP8860_LED0); | ||
257 | if (ret < 0) | ||
258 | return ret; | ||
259 | return size; | ||
260 | } | ||
261 | |||
262 | static ssize_t lp8860_current_store_bled1(struct device *dev, | ||
263 | struct device_attribute *devAttr, | ||
264 | const char *buf, size_t size) | ||
265 | { | ||
266 | int ret; | ||
267 | |||
268 | ret = lp8860_set_current(dev, buf, LP8860_LED1); | ||
269 | if (ret < 0) | ||
270 | return ret; | ||
271 | return size; | ||
272 | } | ||
273 | |||
274 | static ssize_t lp8860_current_store_bled2(struct device *dev, | ||
275 | struct device_attribute *devAttr, | ||
276 | const char *buf, size_t size) | ||
277 | { | ||
278 | int ret; | ||
279 | |||
280 | ret = lp8860_set_current(dev, buf, LP8860_LED2); | ||
281 | if (ret < 0) | ||
282 | return ret; | ||
283 | return size; | ||
284 | } | ||
285 | |||
286 | static ssize_t lp8860_current_store_bled3(struct device *dev, | ||
287 | struct device_attribute *devAttr, | ||
288 | const char *buf, size_t size) | ||
289 | { | ||
290 | int ret; | ||
291 | |||
292 | ret = lp8860_set_current(dev, buf, LP8860_LED3); | ||
293 | if (ret < 0) | ||
294 | return ret; | ||
295 | return size; | ||
296 | } | ||
297 | |||
298 | #define lp8860_attr(_name, _show, _store)\ | ||
299 | {\ | ||
300 | .attr = {\ | ||
301 | .name = _name,\ | ||
302 | .mode = S_IWUSR|S_IRUSR,\ | ||
303 | },\ | ||
304 | .show = _show,\ | ||
305 | .store = _store,\ | ||
306 | } | ||
307 | |||
308 | static struct device_attribute lp8860_dev_attr[LP8860_LED_MAX] = { | ||
309 | [LP8860_LED0] = lp8860_attr("current", NULL, | ||
310 | lp8860_current_store_bled0), | ||
311 | [LP8860_LED1] = lp8860_attr("current", NULL, | ||
312 | lp8860_current_store_bled1), | ||
313 | [LP8860_LED2] = lp8860_attr("current", NULL, | ||
314 | lp8860_current_store_bled2), | ||
315 | [LP8860_LED3] = lp8860_attr("current", NULL, | ||
316 | lp8860_current_store_bled3), | ||
317 | }; | ||
318 | |||
319 | /* get argument from input buffer */ | ||
320 | static int token_store_data(char *buf, char *cmd, unsigned int *reg, | ||
321 | unsigned int *mask, unsigned int *data) | ||
322 | { | ||
323 | char *token; | ||
324 | char *desc = " >\t\n"; | ||
325 | unsigned int tokenval; | ||
326 | |||
327 | /* command */ | ||
328 | token = strsep(&buf, desc); | ||
329 | if (token == NULL) | ||
330 | goto err_out; | ||
331 | *cmd = *token; | ||
332 | if (*cmd == 'w' || *cmd == 'r') { | ||
333 | /* register no. to read/write */ | ||
334 | token = strsep(&buf, desc); | ||
335 | if (token == NULL) | ||
336 | goto err_out; | ||
337 | if (kstrtouint((const char *)token, 16, &tokenval) < 0) | ||
338 | goto err_out; | ||
339 | *reg = tokenval; | ||
340 | |||
341 | /* write command */ | ||
342 | if (*cmd == 'w') { | ||
343 | /* mask bit */ | ||
344 | token = strsep(&buf, desc); | ||
345 | if (token == NULL) | ||
346 | goto err_out; | ||
347 | if (kstrtouint((const char *)token, 16, &tokenval) < 0) | ||
348 | goto err_out; | ||
349 | *mask = tokenval; | ||
350 | |||
351 | /* write data */ | ||
352 | token = strsep(&buf, desc); | ||
353 | if (token == NULL) | ||
354 | goto err_out; | ||
355 | if (kstrtouint((const char *)token, 16, &tokenval) < 0) | ||
356 | goto err_out; | ||
357 | *data = tokenval; | ||
358 | } else { | ||
359 | *mask = 0; | ||
360 | *data = 0; | ||
361 | } | ||
362 | return 0; | ||
363 | } | ||
364 | err_out: | ||
365 | return -EINVAL; | ||
366 | } | ||
367 | |||
368 | /* register access */ | ||
369 | static ssize_t lp8860_register_show(struct device *dev, | ||
370 | struct device_attribute *devAttr, char *buf) | ||
371 | { | ||
372 | struct lp8860_chip *pchip = dev_get_drvdata(dev); | ||
373 | |||
374 | if (pchip->reg < 0x0 || pchip->reg > 0xff) | ||
375 | return sprintf(buf, "Fail : invalid access %x\n", pchip->reg); | ||
376 | return sprintf(buf, "%x %x", pchip->reg, pchip->reg_data); | ||
377 | } | ||
378 | |||
379 | static ssize_t lp8860_register_store(struct device *dev, | ||
380 | struct device_attribute *devAttr, | ||
381 | const char *buf, size_t size) | ||
382 | { | ||
383 | struct lp8860_chip *pchip = dev_get_drvdata(dev); | ||
384 | int ret; | ||
385 | char cmd; | ||
386 | unsigned int reg, mask, data; | ||
387 | |||
388 | ret = token_store_data((char *)buf, &cmd, ®, &mask, &data); | ||
389 | if (ret < 0) | ||
390 | goto err_input; | ||
391 | pchip->reg = reg; | ||
392 | if (cmd == 'w' || cmd == 'W') { | ||
393 | ret = regmap_update_bits(pchip->regmap, reg, mask, data); | ||
394 | if (ret < 0) { | ||
395 | pchip->reg_data = ret; | ||
396 | goto err_i2c; | ||
397 | } | ||
398 | } | ||
399 | ret = regmap_read(pchip->regmap, reg, &data); | ||
400 | if (ret < 0) { | ||
401 | pchip->reg_data = ret; | ||
402 | goto err_i2c; | ||
403 | } | ||
404 | pchip->reg_data = data; | ||
405 | return size; | ||
406 | |||
407 | err_i2c: | ||
408 | dev_err(pchip->dev, "fail : i2c access error\n"); | ||
409 | return size; | ||
410 | |||
411 | err_input: | ||
412 | dev_err(pchip->dev, "failed : input fail\n"); | ||
413 | return size; | ||
414 | } | ||
415 | |||
416 | static DEVICE_ATTR(registers, S_IWUSR | S_IRUSR, | ||
417 | lp8860_register_show, lp8860_register_store); | ||
418 | |||
419 | /* | ||
420 | * Input data | ||
421 | * 0 : read from eeprom to eeprom registers. | ||
422 | * 1 : eeprom unlock before writing | ||
423 | * 2 : brun and lock | ||
424 | */ | ||
425 | static ssize_t lp8860_eeprom_store(struct device *dev, | ||
426 | struct device_attribute *devAttr, | ||
427 | const char *buf, size_t size) | ||
428 | { | ||
429 | struct lp8860_chip *pchip = dev_get_drvdata(dev); | ||
430 | unsigned int ival; | ||
431 | int ret; | ||
432 | |||
433 | ret = kstrtouint(buf, 10, &ival); | ||
434 | if (ret) | ||
435 | goto err_input; | ||
436 | |||
437 | switch (ival) { | ||
438 | /* | ||
439 | * Read sequence | ||
440 | * 1. Unlock EEPROM by writing | ||
441 | * the unlock codes to register 1Ah(08, BA, EF) | ||
442 | * 2. Write EE_PROG to 1 in address 19h. (02h to address 19h) | ||
443 | * 3. Wait 100ms | ||
444 | * 4. Write EE_PROG to 0 in address 19h. (00h to address 19h) | ||
445 | * 5. Lock EEPROM by writing 0h to register 1Ah. | ||
446 | */ | ||
447 | case 0: | ||
448 | ret = regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE0); | ||
449 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE1); | ||
450 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE2); | ||
451 | ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x01); | ||
452 | msleep(100); | ||
453 | ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x00); | ||
454 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, LCODE); | ||
455 | if (ret < 0) | ||
456 | goto err_i2c; | ||
457 | dev_info(pchip->dev, "LP8860 EEPROM Read Done\n"); | ||
458 | break; | ||
459 | /* | ||
460 | * Programming sequence | ||
461 | * (program data permanently from registers to NVM) | ||
462 | * 1. Unlock EEPROM by writing | ||
463 | * the unlock codes to register 1Ah(08, BA, EF) | ||
464 | * 2. Write data to EEPROM registers (address 60h...78h) | ||
465 | * (use registers device node) | ||
466 | * 3. Write EE_PROG to 1 in address 19h. (02h to address 19h) | ||
467 | * 4. Wait 100ms | ||
468 | * 5. Write EE_PROG to 0 in address 19h. (00h to address 19h) | ||
469 | * 6. Lock EEPROM by writing 0h to register 1Ah. | ||
470 | */ | ||
471 | case 1: | ||
472 | ret = regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE0); | ||
473 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE1); | ||
474 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, ULCODE2); | ||
475 | if (ret < 0) | ||
476 | goto err_i2c; | ||
477 | dev_info(pchip->dev, "LP8860 EEPROM is Unlocked\n"); | ||
478 | break; | ||
479 | case 2: | ||
480 | ret = regmap_write(pchip->regmap, REG_ROM_CTRL, 0x02); | ||
481 | msleep(100); | ||
482 | ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x00); | ||
483 | ret |= regmap_write(pchip->regmap, REG_ROM_ULOCK, LCODE); | ||
484 | if (ret < 0) | ||
485 | goto err_i2c; | ||
486 | dev_info(pchip->dev, "LP8860 EEPROM is Locked\n"); | ||
487 | break; | ||
488 | default: | ||
489 | goto err_input; | ||
490 | } | ||
491 | |||
492 | return size; | ||
493 | |||
494 | err_i2c: | ||
495 | dev_err(pchip->dev, "fail : i2c access error.\n"); | ||
496 | return ret; | ||
497 | |||
498 | err_input: | ||
499 | dev_err(pchip->dev, "fail : input fail.\n"); | ||
500 | return -EINVAL; | ||
501 | } | ||
502 | |||
503 | static DEVICE_ATTR(eeprom, S_IWUSR, NULL, lp8860_eeprom_store); | ||
504 | |||
505 | /* backlight register and remove */ | ||
506 | static char *lp8860_bled_name[LP8860_LED_MAX] = { | ||
507 | [LP8860_LED0] = "bled0", | ||
508 | [LP8860_LED1] = "bled1", | ||
509 | [LP8860_LED2] = "bled2", | ||
510 | [LP8860_LED3] = "bled3", | ||
511 | }; | ||
512 | |||
513 | static int lp8860_backlight_remove(struct lp8860_chip *pchip) | ||
514 | { | ||
515 | int icnt; | ||
516 | |||
517 | device_remove_file(&(pchip->bled[0]->dev), &dev_attr_registers); | ||
518 | device_remove_file(&(pchip->bled[0]->dev), &dev_attr_eeprom); | ||
519 | for (icnt = LP8860_LED0; icnt < LP8860_LED_MAX; icnt++) { | ||
520 | if (pchip->bled[icnt]) { | ||
521 | backlight_device_unregister(pchip->bled[icnt]); | ||
522 | device_remove_file(&(pchip->bled[icnt]->dev), | ||
523 | &lp8860_dev_attr[icnt]); | ||
524 | } | ||
525 | } | ||
526 | return 0; | ||
527 | } | ||
528 | |||
529 | static int lp8860_backlight_registers(struct lp8860_chip *pchip) | ||
530 | { | ||
531 | struct backlight_properties props; | ||
532 | struct lp8860_platform_data *pdata = pchip->pdata; | ||
533 | int icnt, ret; | ||
534 | |||
535 | props.type = BACKLIGHT_RAW; | ||
536 | for (icnt = LP8860_LED0; icnt < LP8860_LED_MAX; icnt++) { | ||
537 | props.max_brightness = pdata->max_brt[icnt]; | ||
538 | pchip->bled[icnt] = | ||
539 | backlight_device_register(lp8860_bled_name[icnt], | ||
540 | pchip->dev, pchip, | ||
541 | &lp8860_bled_ops[icnt], &props); | ||
542 | if (IS_ERR(pchip->bled[icnt])) { | ||
543 | dev_err(pchip->dev, "fail : backlight register.\n"); | ||
544 | ret = PTR_ERR(pchip->bled); | ||
545 | goto err_out; | ||
546 | } | ||
547 | |||
548 | ret = device_create_file(&(pchip->bled[icnt]->dev), | ||
549 | &lp8860_dev_attr[icnt]); | ||
550 | if (ret < 0) { | ||
551 | dev_err(pchip->dev, "fail : to add sysfs entries.\n"); | ||
552 | goto err_out; | ||
553 | } | ||
554 | } | ||
555 | /* to access eeprom */ | ||
556 | ret = device_create_file(&(pchip->bled[LP8860_LED0]->dev), | ||
557 | &dev_attr_eeprom); | ||
558 | if (ret < 0) { | ||
559 | dev_err(pchip->dev, "fail : to add sysfs entries.\n"); | ||
560 | goto err_out; | ||
561 | } | ||
562 | /* to access registers */ | ||
563 | ret = device_create_file(&(pchip->bled[LP8860_LED0]->dev), | ||
564 | &dev_attr_registers); | ||
565 | if (ret < 0) { | ||
566 | dev_err(pchip->dev, "failed : add sysfs entries\n"); | ||
567 | goto err_out; | ||
568 | } | ||
569 | return 0; | ||
570 | |||
571 | err_out: | ||
572 | lp8860_backlight_remove(pchip); | ||
573 | return ret; | ||
574 | } | ||
575 | |||
576 | static const struct regmap_config lp8860_regmap = { | ||
577 | .reg_bits = 8, | ||
578 | .val_bits = 8, | ||
579 | .max_register = REG_MAX, | ||
580 | }; | ||
581 | |||
582 | static int lp8860_probe(struct i2c_client *client, | ||
583 | const struct i2c_device_id *id) | ||
584 | { | ||
585 | struct lp8860_chip *pchip; | ||
586 | struct lp8860_platform_data *pdata = dev_get_platdata(&client->dev); | ||
587 | int ret, icnt; | ||
588 | |||
589 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | ||
590 | dev_err(&client->dev, "fail : i2c functionality check.\n"); | ||
591 | return -EOPNOTSUPP; | ||
592 | } | ||
593 | |||
594 | pchip = devm_kzalloc(&client->dev, | ||
595 | sizeof(struct lp8860_chip), GFP_KERNEL); | ||
596 | if (!pchip) | ||
597 | return -ENOMEM; | ||
598 | pchip->dev = &client->dev; | ||
599 | |||
600 | pchip->regmap = devm_regmap_init_i2c(client, &lp8860_regmap); | ||
601 | if (IS_ERR(pchip->regmap)) { | ||
602 | ret = PTR_ERR(pchip->regmap); | ||
603 | dev_err(pchip->dev, "fail : allocate i2c register map.\n"); | ||
604 | return ret; | ||
605 | } | ||
606 | |||
607 | if (pdata == NULL) { | ||
608 | pdata = devm_kzalloc(pchip->dev, | ||
609 | sizeof(struct lp8860_platform_data), | ||
610 | GFP_KERNEL); | ||
611 | if (pdata == NULL) | ||
612 | return -ENOMEM; | ||
613 | pdata->max_brt[LP8860_LED0] = LP8860_LED0_BR_MAX; | ||
614 | for (icnt = LP8860_LED1; icnt < LP8860_LED_MAX; icnt++) | ||
615 | pdata->max_brt[icnt] = LP8860_LED1_BR_MAX; | ||
616 | pchip->pdata = pdata; | ||
617 | } else { | ||
618 | pchip->pdata = pdata; | ||
619 | } | ||
620 | i2c_set_clientdata(client, pchip); | ||
621 | ret = lp8860_backlight_registers(pchip); | ||
622 | return ret; | ||
623 | } | ||
624 | |||
625 | static int lp8860_remove(struct i2c_client *client) | ||
626 | { | ||
627 | return lp8860_backlight_remove(i2c_get_clientdata(client)); | ||
628 | } | ||
629 | |||
630 | static const struct i2c_device_id lp8860_id[] = { | ||
631 | {LP8860_NAME, 0}, | ||
632 | {} | ||
633 | }; | ||
634 | |||
635 | MODULE_DEVICE_TABLE(i2c, lp8860_id); | ||
636 | static struct i2c_driver lp8860_i2c_driver = { | ||
637 | .driver = { | ||
638 | .name = LP8860_NAME, | ||
639 | }, | ||
640 | .probe = lp8860_probe, | ||
641 | .remove = lp8860_remove, | ||
642 | .id_table = lp8860_id, | ||
643 | }; | ||
644 | |||
645 | module_i2c_driver(lp8860_i2c_driver); | ||
646 | |||
647 | MODULE_DESCRIPTION("Texas Instruments LP8860 Backlight Driver"); | ||
648 | MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>"); | ||
649 | MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>"); | ||
650 | MODULE_LICENSE("GPL v2"); | ||