Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
0f59858d SJ |
2 | /* |
3 | * Simple driver for Texas Instruments LM3639 Backlight + Flash LED driver chip | |
4 | * Copyright (C) 2012 Texas Instruments | |
0f59858d SJ |
5 | */ |
6 | #include <linux/module.h> | |
7 | #include <linux/slab.h> | |
8 | #include <linux/i2c.h> | |
9 | #include <linux/leds.h> | |
10 | #include <linux/backlight.h> | |
11 | #include <linux/err.h> | |
12 | #include <linux/delay.h> | |
13 | #include <linux/uaccess.h> | |
14 | #include <linux/interrupt.h> | |
15 | #include <linux/regmap.h> | |
16 | #include <linux/platform_data/lm3639_bl.h> | |
17 | ||
18 | #define REG_DEV_ID 0x00 | |
19 | #define REG_CHECKSUM 0x01 | |
20 | #define REG_BL_CONF_1 0x02 | |
21 | #define REG_BL_CONF_2 0x03 | |
22 | #define REG_BL_CONF_3 0x04 | |
23 | #define REG_BL_CONF_4 0x05 | |
24 | #define REG_FL_CONF_1 0x06 | |
25 | #define REG_FL_CONF_2 0x07 | |
26 | #define REG_FL_CONF_3 0x08 | |
27 | #define REG_IO_CTRL 0x09 | |
28 | #define REG_ENABLE 0x0A | |
29 | #define REG_FLAG 0x0B | |
30 | #define REG_MAX REG_FLAG | |
31 | ||
32 | struct lm3639_chip_data { | |
33 | struct device *dev; | |
34 | struct lm3639_platform_data *pdata; | |
35 | ||
36 | struct backlight_device *bled; | |
37 | struct led_classdev cdev_flash; | |
38 | struct led_classdev cdev_torch; | |
39 | struct regmap *regmap; | |
40 | ||
41 | unsigned int bled_mode; | |
42 | unsigned int bled_map; | |
43 | unsigned int last_flag; | |
44 | }; | |
45 | ||
46 | /* initialize chip */ | |
1b9e450d | 47 | static int lm3639_chip_init(struct lm3639_chip_data *pchip) |
0f59858d SJ |
48 | { |
49 | int ret; | |
50 | unsigned int reg_val; | |
51 | struct lm3639_platform_data *pdata = pchip->pdata; | |
52 | ||
53 | /* input pins config. */ | |
54 | ret = | |
55 | regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x08, | |
56 | pdata->pin_pwm); | |
57 | if (ret < 0) | |
58 | goto out; | |
59 | ||
60 | reg_val = (pdata->pin_pwm & 0x40) | pdata->pin_strobe | pdata->pin_tx; | |
61 | ret = regmap_update_bits(pchip->regmap, REG_IO_CTRL, 0x7C, reg_val); | |
62 | if (ret < 0) | |
63 | goto out; | |
64 | ||
65 | /* init brightness */ | |
66 | ret = regmap_write(pchip->regmap, REG_BL_CONF_4, pdata->init_brt_led); | |
67 | if (ret < 0) | |
68 | goto out; | |
69 | ||
70 | ret = regmap_write(pchip->regmap, REG_BL_CONF_3, pdata->init_brt_led); | |
71 | if (ret < 0) | |
72 | goto out; | |
73 | ||
74 | /* output pins config. */ | |
0ab7b20f JH |
75 | if (!pdata->init_brt_led) { |
76 | reg_val = pdata->fled_pins; | |
77 | reg_val |= pdata->bled_pins; | |
78 | } else { | |
79 | reg_val = pdata->fled_pins; | |
80 | reg_val |= pdata->bled_pins | 0x01; | |
81 | } | |
0f59858d SJ |
82 | |
83 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x79, reg_val); | |
84 | if (ret < 0) | |
85 | goto out; | |
86 | ||
87 | return ret; | |
88 | out: | |
89 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
90 | return ret; | |
91 | } | |
92 | ||
93 | /* update and get brightness */ | |
94 | static int lm3639_bled_update_status(struct backlight_device *bl) | |
95 | { | |
96 | int ret; | |
97 | unsigned int reg_val; | |
98 | struct lm3639_chip_data *pchip = bl_get_data(bl); | |
99 | struct lm3639_platform_data *pdata = pchip->pdata; | |
100 | ||
101 | ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
102 | if (ret < 0) | |
103 | goto out; | |
104 | ||
105 | if (reg_val != 0) | |
106 | dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
107 | ||
108 | /* pwm control */ | |
109 | if (pdata->pin_pwm) { | |
110 | if (pdata->pwm_set_intensity) | |
111 | pdata->pwm_set_intensity(bl->props.brightness, | |
112 | pdata->max_brt_led); | |
113 | else | |
114 | dev_err(pchip->dev, | |
115 | "No pwm control func. in plat-data\n"); | |
116 | return bl->props.brightness; | |
117 | } | |
118 | ||
119 | /* i2c control and set brigtness */ | |
120 | ret = regmap_write(pchip->regmap, REG_BL_CONF_4, bl->props.brightness); | |
121 | if (ret < 0) | |
122 | goto out; | |
123 | ret = regmap_write(pchip->regmap, REG_BL_CONF_3, bl->props.brightness); | |
124 | if (ret < 0) | |
125 | goto out; | |
126 | ||
127 | if (!bl->props.brightness) | |
128 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x00); | |
129 | else | |
130 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x01); | |
131 | if (ret < 0) | |
132 | goto out; | |
133 | ||
134 | return bl->props.brightness; | |
135 | out: | |
136 | dev_err(pchip->dev, "i2c failed to access registers\n"); | |
137 | return bl->props.brightness; | |
138 | } | |
139 | ||
140 | static int lm3639_bled_get_brightness(struct backlight_device *bl) | |
141 | { | |
142 | int ret; | |
143 | unsigned int reg_val; | |
144 | struct lm3639_chip_data *pchip = bl_get_data(bl); | |
145 | struct lm3639_platform_data *pdata = pchip->pdata; | |
146 | ||
147 | if (pdata->pin_pwm) { | |
148 | if (pdata->pwm_get_intensity) | |
149 | bl->props.brightness = pdata->pwm_get_intensity(); | |
150 | else | |
151 | dev_err(pchip->dev, | |
152 | "No pwm control func. in plat-data\n"); | |
153 | return bl->props.brightness; | |
154 | } | |
155 | ||
156 | ret = regmap_read(pchip->regmap, REG_BL_CONF_1, ®_val); | |
157 | if (ret < 0) | |
158 | goto out; | |
159 | if (reg_val & 0x10) | |
160 | ret = regmap_read(pchip->regmap, REG_BL_CONF_4, ®_val); | |
161 | else | |
162 | ret = regmap_read(pchip->regmap, REG_BL_CONF_3, ®_val); | |
163 | if (ret < 0) | |
164 | goto out; | |
165 | bl->props.brightness = reg_val; | |
166 | ||
167 | return bl->props.brightness; | |
168 | out: | |
169 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
170 | return bl->props.brightness; | |
171 | } | |
172 | ||
173 | static const struct backlight_ops lm3639_bled_ops = { | |
174 | .options = BL_CORE_SUSPENDRESUME, | |
175 | .update_status = lm3639_bled_update_status, | |
176 | .get_brightness = lm3639_bled_get_brightness, | |
177 | }; | |
178 | ||
179 | /* backlight mapping mode */ | |
180 | static ssize_t lm3639_bled_mode_store(struct device *dev, | |
181 | struct device_attribute *devAttr, | |
182 | const char *buf, size_t size) | |
183 | { | |
184 | ssize_t ret; | |
185 | struct lm3639_chip_data *pchip = dev_get_drvdata(dev); | |
186 | unsigned int state; | |
187 | ||
188 | ret = kstrtouint(buf, 10, &state); | |
189 | if (ret) | |
190 | goto out_input; | |
191 | ||
192 | if (!state) | |
193 | ret = | |
194 | regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10, | |
195 | 0x00); | |
196 | else | |
197 | ret = | |
198 | regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10, | |
199 | 0x10); | |
200 | ||
201 | if (ret < 0) | |
202 | goto out; | |
203 | ||
204 | return size; | |
205 | ||
206 | out: | |
207 | dev_err(pchip->dev, "%s:i2c access fail to register\n", __func__); | |
dc36d7e7 | 208 | return ret; |
0f59858d SJ |
209 | |
210 | out_input: | |
211 | dev_err(pchip->dev, "%s:input conversion fail\n", __func__); | |
dc36d7e7 | 212 | return ret; |
0f59858d SJ |
213 | |
214 | } | |
215 | ||
fb08cd9b | 216 | static DEVICE_ATTR(bled_mode, S_IWUSR, NULL, lm3639_bled_mode_store); |
0f59858d SJ |
217 | |
218 | /* torch */ | |
219 | static void lm3639_torch_brightness_set(struct led_classdev *cdev, | |
220 | enum led_brightness brightness) | |
221 | { | |
222 | int ret; | |
223 | unsigned int reg_val; | |
224 | struct lm3639_chip_data *pchip; | |
225 | ||
226 | pchip = container_of(cdev, struct lm3639_chip_data, cdev_torch); | |
227 | ||
228 | ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
229 | if (ret < 0) | |
230 | goto out; | |
231 | if (reg_val != 0) | |
232 | dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
233 | ||
234 | /* brightness 0 means off state */ | |
235 | if (!brightness) { | |
236 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00); | |
237 | if (ret < 0) | |
238 | goto out; | |
239 | return; | |
240 | } | |
241 | ||
242 | ret = regmap_update_bits(pchip->regmap, | |
243 | REG_FL_CONF_1, 0x70, (brightness - 1) << 4); | |
244 | if (ret < 0) | |
245 | goto out; | |
246 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x02); | |
247 | if (ret < 0) | |
248 | goto out; | |
249 | ||
250 | return; | |
251 | out: | |
252 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
0f59858d SJ |
253 | } |
254 | ||
255 | /* flash */ | |
256 | static void lm3639_flash_brightness_set(struct led_classdev *cdev, | |
257 | enum led_brightness brightness) | |
258 | { | |
259 | int ret; | |
260 | unsigned int reg_val; | |
261 | struct lm3639_chip_data *pchip; | |
262 | ||
263 | pchip = container_of(cdev, struct lm3639_chip_data, cdev_flash); | |
264 | ||
265 | ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
266 | if (ret < 0) | |
267 | goto out; | |
268 | if (reg_val != 0) | |
269 | dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
270 | ||
271 | /* torch off before flash control */ | |
272 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00); | |
273 | if (ret < 0) | |
274 | goto out; | |
275 | ||
276 | /* brightness 0 means off state */ | |
277 | if (!brightness) | |
278 | return; | |
279 | ||
280 | ret = regmap_update_bits(pchip->regmap, | |
281 | REG_FL_CONF_1, 0x0F, brightness - 1); | |
282 | if (ret < 0) | |
283 | goto out; | |
284 | ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x06); | |
285 | if (ret < 0) | |
286 | goto out; | |
287 | ||
288 | return; | |
289 | out: | |
290 | dev_err(pchip->dev, "i2c failed to access register\n"); | |
0f59858d SJ |
291 | } |
292 | ||
293 | static const struct regmap_config lm3639_regmap = { | |
294 | .reg_bits = 8, | |
295 | .val_bits = 8, | |
296 | .max_register = REG_MAX, | |
297 | }; | |
298 | ||
3065efe8 | 299 | static int lm3639_probe(struct i2c_client *client) |
0f59858d SJ |
300 | { |
301 | int ret; | |
302 | struct lm3639_chip_data *pchip; | |
c512794c | 303 | struct lm3639_platform_data *pdata = dev_get_platdata(&client->dev); |
0f59858d SJ |
304 | struct backlight_properties props; |
305 | ||
306 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | |
307 | dev_err(&client->dev, "i2c functionality check fail.\n"); | |
308 | return -EOPNOTSUPP; | |
309 | } | |
310 | ||
311 | if (pdata == NULL) { | |
312 | dev_err(&client->dev, "Needs Platform Data.\n"); | |
313 | return -ENODATA; | |
314 | } | |
315 | ||
316 | pchip = devm_kzalloc(&client->dev, | |
317 | sizeof(struct lm3639_chip_data), GFP_KERNEL); | |
318 | if (!pchip) | |
319 | return -ENOMEM; | |
320 | ||
321 | pchip->pdata = pdata; | |
322 | pchip->dev = &client->dev; | |
323 | ||
324 | pchip->regmap = devm_regmap_init_i2c(client, &lm3639_regmap); | |
325 | if (IS_ERR(pchip->regmap)) { | |
326 | ret = PTR_ERR(pchip->regmap); | |
327 | dev_err(&client->dev, "fail : allocate register map: %d\n", | |
328 | ret); | |
329 | return ret; | |
330 | } | |
331 | i2c_set_clientdata(client, pchip); | |
332 | ||
333 | /* chip initialize */ | |
334 | ret = lm3639_chip_init(pchip); | |
335 | if (ret < 0) { | |
336 | dev_err(&client->dev, "fail : chip init\n"); | |
337 | goto err_out; | |
338 | } | |
339 | ||
340 | /* backlight */ | |
341 | props.type = BACKLIGHT_RAW; | |
342 | props.brightness = pdata->init_brt_led; | |
343 | props.max_brightness = pdata->max_brt_led; | |
344 | pchip->bled = | |
f1740e4c DJ |
345 | devm_backlight_device_register(pchip->dev, "lm3639_bled", |
346 | pchip->dev, pchip, &lm3639_bled_ops, | |
347 | &props); | |
0f59858d SJ |
348 | if (IS_ERR(pchip->bled)) { |
349 | dev_err(&client->dev, "fail : backlight register\n"); | |
aea00a6c | 350 | ret = PTR_ERR(pchip->bled); |
0f59858d SJ |
351 | goto err_out; |
352 | } | |
353 | ||
354 | ret = device_create_file(&(pchip->bled->dev), &dev_attr_bled_mode); | |
355 | if (ret < 0) { | |
356 | dev_err(&client->dev, "failed : add sysfs entries\n"); | |
f1740e4c | 357 | goto err_out; |
0f59858d SJ |
358 | } |
359 | ||
360 | /* flash */ | |
361 | pchip->cdev_flash.name = "lm3639_flash"; | |
362 | pchip->cdev_flash.max_brightness = 16; | |
363 | pchip->cdev_flash.brightness_set = lm3639_flash_brightness_set; | |
364 | ret = led_classdev_register((struct device *) | |
365 | &client->dev, &pchip->cdev_flash); | |
366 | if (ret < 0) { | |
367 | dev_err(&client->dev, "fail : flash register\n"); | |
0f59858d SJ |
368 | goto err_flash; |
369 | } | |
370 | ||
371 | /* torch */ | |
372 | pchip->cdev_torch.name = "lm3639_torch"; | |
373 | pchip->cdev_torch.max_brightness = 8; | |
374 | pchip->cdev_torch.brightness_set = lm3639_torch_brightness_set; | |
375 | ret = led_classdev_register((struct device *) | |
376 | &client->dev, &pchip->cdev_torch); | |
377 | if (ret < 0) { | |
378 | dev_err(&client->dev, "fail : torch register\n"); | |
0f59858d SJ |
379 | goto err_torch; |
380 | } | |
381 | ||
382 | return 0; | |
383 | ||
384 | err_torch: | |
385 | led_classdev_unregister(&pchip->cdev_flash); | |
386 | err_flash: | |
387 | device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode); | |
0f59858d SJ |
388 | err_out: |
389 | return ret; | |
390 | } | |
391 | ||
ed5c2f5f | 392 | static void lm3639_remove(struct i2c_client *client) |
0f59858d SJ |
393 | { |
394 | struct lm3639_chip_data *pchip = i2c_get_clientdata(client); | |
395 | ||
396 | regmap_write(pchip->regmap, REG_ENABLE, 0x00); | |
397 | ||
7cea645a NC |
398 | led_classdev_unregister(&pchip->cdev_torch); |
399 | led_classdev_unregister(&pchip->cdev_flash); | |
f1740e4c | 400 | if (pchip->bled) |
0f59858d | 401 | device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode); |
0f59858d SJ |
402 | } |
403 | ||
404 | static const struct i2c_device_id lm3639_id[] = { | |
405 | {LM3639_NAME, 0}, | |
406 | {} | |
407 | }; | |
408 | ||
409 | MODULE_DEVICE_TABLE(i2c, lm3639_id); | |
410 | static struct i2c_driver lm3639_i2c_driver = { | |
411 | .driver = { | |
412 | .name = LM3639_NAME, | |
413 | }, | |
29554f2e | 414 | .probe = lm3639_probe, |
d1723fa2 | 415 | .remove = lm3639_remove, |
0f59858d SJ |
416 | .id_table = lm3639_id, |
417 | }; | |
418 | ||
419 | module_i2c_driver(lm3639_i2c_driver); | |
420 | ||
421 | MODULE_DESCRIPTION("Texas Instruments Backlight+Flash LED driver for LM3639"); | |
f1740e4c DJ |
422 | MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>"); |
423 | MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>"); | |
0f59858d | 424 | MODULE_LICENSE("GPL v2"); |