1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Simple driver for Texas Instruments LM3639 Backlight + Flash LED driver chip |
4 | * Copyright (C) 2012 Texas Instruments |
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 */ |
47 | static int lm3639_chip_init(struct lm3639_chip_data *pchip) |
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(map: pchip->regmap, REG_BL_CONF_1, mask: 0x08, |
56 | val: 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(map: pchip->regmap, REG_IO_CTRL, mask: 0x7C, val: reg_val); |
62 | if (ret < 0) |
63 | goto out; |
64 | |
65 | /* init brightness */ |
66 | ret = regmap_write(map: pchip->regmap, REG_BL_CONF_4, val: pdata->init_brt_led); |
67 | if (ret < 0) |
68 | goto out; |
69 | |
70 | ret = regmap_write(map: pchip->regmap, REG_BL_CONF_3, val: pdata->init_brt_led); |
71 | if (ret < 0) |
72 | goto out; |
73 | |
74 | /* output pins config. */ |
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 | } |
82 | |
83 | ret = regmap_update_bits(map: pchip->regmap, REG_ENABLE, mask: 0x79, val: 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_dev: bl); |
99 | struct lm3639_platform_data *pdata = pchip->pdata; |
100 | |
101 | ret = regmap_read(map: pchip->regmap, REG_FLAG, val: ®_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(map: pchip->regmap, REG_BL_CONF_4, val: bl->props.brightness); |
121 | if (ret < 0) |
122 | goto out; |
123 | ret = regmap_write(map: pchip->regmap, REG_BL_CONF_3, val: bl->props.brightness); |
124 | if (ret < 0) |
125 | goto out; |
126 | |
127 | if (!bl->props.brightness) |
128 | ret = regmap_update_bits(map: pchip->regmap, REG_ENABLE, mask: 0x01, val: 0x00); |
129 | else |
130 | ret = regmap_update_bits(map: pchip->regmap, REG_ENABLE, mask: 0x01, val: 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_dev: 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(map: pchip->regmap, REG_BL_CONF_1, val: ®_val); |
157 | if (ret < 0) |
158 | goto out; |
159 | if (reg_val & 0x10) |
160 | ret = regmap_read(map: pchip->regmap, REG_BL_CONF_4, val: ®_val); |
161 | else |
162 | ret = regmap_read(map: pchip->regmap, REG_BL_CONF_3, val: ®_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(s: buf, base: 10, res: &state); |
189 | if (ret) |
190 | goto out_input; |
191 | |
192 | if (!state) |
193 | ret = |
194 | regmap_update_bits(map: pchip->regmap, REG_BL_CONF_1, mask: 0x10, |
195 | val: 0x00); |
196 | else |
197 | ret = |
198 | regmap_update_bits(map: pchip->regmap, REG_BL_CONF_1, mask: 0x10, |
199 | val: 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__); |
208 | return ret; |
209 | |
210 | out_input: |
211 | dev_err(pchip->dev, "%s:input conversion fail\n" , __func__); |
212 | return ret; |
213 | |
214 | } |
215 | |
216 | static DEVICE_ATTR(bled_mode, S_IWUSR, NULL, lm3639_bled_mode_store); |
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(map: pchip->regmap, REG_FLAG, val: ®_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(map: pchip->regmap, REG_ENABLE, mask: 0x06, val: 0x00); |
237 | if (ret < 0) |
238 | goto out; |
239 | return; |
240 | } |
241 | |
242 | ret = regmap_update_bits(map: pchip->regmap, |
243 | REG_FL_CONF_1, mask: 0x70, val: (brightness - 1) << 4); |
244 | if (ret < 0) |
245 | goto out; |
246 | ret = regmap_update_bits(map: pchip->regmap, REG_ENABLE, mask: 0x06, val: 0x02); |
247 | if (ret < 0) |
248 | goto out; |
249 | |
250 | return; |
251 | out: |
252 | dev_err(pchip->dev, "i2c failed to access register\n" ); |
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(map: pchip->regmap, REG_FLAG, val: ®_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(map: pchip->regmap, REG_ENABLE, mask: 0x06, val: 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(map: pchip->regmap, |
281 | REG_FL_CONF_1, mask: 0x0F, val: brightness - 1); |
282 | if (ret < 0) |
283 | goto out; |
284 | ret = regmap_update_bits(map: pchip->regmap, REG_ENABLE, mask: 0x06, val: 0x06); |
285 | if (ret < 0) |
286 | goto out; |
287 | |
288 | return; |
289 | out: |
290 | dev_err(pchip->dev, "i2c failed to access register\n" ); |
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 | |
299 | static int lm3639_probe(struct i2c_client *client) |
300 | { |
301 | int ret; |
302 | struct lm3639_chip_data *pchip; |
303 | struct lm3639_platform_data *pdata = dev_get_platdata(dev: &client->dev); |
304 | struct backlight_properties props; |
305 | |
306 | if (!i2c_check_functionality(adap: 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(dev: &client->dev, |
317 | size: 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(ptr: pchip->regmap)) { |
326 | ret = PTR_ERR(ptr: pchip->regmap); |
327 | dev_err(&client->dev, "fail : allocate register map: %d\n" , |
328 | ret); |
329 | return ret; |
330 | } |
331 | i2c_set_clientdata(client, data: 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 | memset(&props, 0, sizeof(struct backlight_properties)); |
342 | props.type = BACKLIGHT_RAW; |
343 | props.brightness = pdata->init_brt_led; |
344 | props.max_brightness = pdata->max_brt_led; |
345 | pchip->bled = |
346 | devm_backlight_device_register(dev: pchip->dev, name: "lm3639_bled" , |
347 | parent: pchip->dev, devdata: pchip, ops: &lm3639_bled_ops, |
348 | props: &props); |
349 | if (IS_ERR(ptr: pchip->bled)) { |
350 | dev_err(&client->dev, "fail : backlight register\n" ); |
351 | ret = PTR_ERR(ptr: pchip->bled); |
352 | goto err_out; |
353 | } |
354 | |
355 | ret = device_create_file(device: &(pchip->bled->dev), entry: &dev_attr_bled_mode); |
356 | if (ret < 0) { |
357 | dev_err(&client->dev, "failed : add sysfs entries\n" ); |
358 | goto err_out; |
359 | } |
360 | |
361 | /* flash */ |
362 | pchip->cdev_flash.name = "lm3639_flash" ; |
363 | pchip->cdev_flash.max_brightness = 16; |
364 | pchip->cdev_flash.brightness_set = lm3639_flash_brightness_set; |
365 | ret = led_classdev_register(parent: (struct device *) |
366 | &client->dev, led_cdev: &pchip->cdev_flash); |
367 | if (ret < 0) { |
368 | dev_err(&client->dev, "fail : flash register\n" ); |
369 | goto err_flash; |
370 | } |
371 | |
372 | /* torch */ |
373 | pchip->cdev_torch.name = "lm3639_torch" ; |
374 | pchip->cdev_torch.max_brightness = 8; |
375 | pchip->cdev_torch.brightness_set = lm3639_torch_brightness_set; |
376 | ret = led_classdev_register(parent: (struct device *) |
377 | &client->dev, led_cdev: &pchip->cdev_torch); |
378 | if (ret < 0) { |
379 | dev_err(&client->dev, "fail : torch register\n" ); |
380 | goto err_torch; |
381 | } |
382 | |
383 | return 0; |
384 | |
385 | err_torch: |
386 | led_classdev_unregister(led_cdev: &pchip->cdev_flash); |
387 | err_flash: |
388 | device_remove_file(dev: &(pchip->bled->dev), attr: &dev_attr_bled_mode); |
389 | err_out: |
390 | return ret; |
391 | } |
392 | |
393 | static void lm3639_remove(struct i2c_client *client) |
394 | { |
395 | struct lm3639_chip_data *pchip = i2c_get_clientdata(client); |
396 | |
397 | regmap_write(map: pchip->regmap, REG_ENABLE, val: 0x00); |
398 | |
399 | led_classdev_unregister(led_cdev: &pchip->cdev_torch); |
400 | led_classdev_unregister(led_cdev: &pchip->cdev_flash); |
401 | if (pchip->bled) |
402 | device_remove_file(dev: &(pchip->bled->dev), attr: &dev_attr_bled_mode); |
403 | } |
404 | |
405 | static const struct i2c_device_id lm3639_id[] = { |
406 | {LM3639_NAME, 0}, |
407 | {} |
408 | }; |
409 | |
410 | MODULE_DEVICE_TABLE(i2c, lm3639_id); |
411 | static struct i2c_driver lm3639_i2c_driver = { |
412 | .driver = { |
413 | .name = LM3639_NAME, |
414 | }, |
415 | .probe = lm3639_probe, |
416 | .remove = lm3639_remove, |
417 | .id_table = lm3639_id, |
418 | }; |
419 | |
420 | module_i2c_driver(lm3639_i2c_driver); |
421 | |
422 | MODULE_DESCRIPTION("Texas Instruments Backlight+Flash LED driver for LM3639" ); |
423 | MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>" ); |
424 | MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>" ); |
425 | MODULE_LICENSE("GPL v2" ); |
426 | |