1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * TI LP855x Backlight Driver |
4 | * |
5 | * Copyright (C) 2011 Texas Instruments |
6 | */ |
7 | |
8 | #include <linux/acpi.h> |
9 | #include <linux/module.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/backlight.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/err.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_data/lp855x.h> |
17 | #include <linux/pwm.h> |
18 | #include <linux/regulator/consumer.h> |
19 | |
20 | /* LP8550/1/2/3/6 Registers */ |
21 | #define LP855X_BRIGHTNESS_CTRL 0x00 |
22 | #define LP855X_DEVICE_CTRL 0x01 |
23 | #define LP855X_EEPROM_START 0xA0 |
24 | #define LP855X_EEPROM_END 0xA7 |
25 | #define LP8556_EPROM_START 0xA0 |
26 | #define LP8556_EPROM_END 0xAF |
27 | |
28 | /* LP8555/7 Registers */ |
29 | #define LP8557_BL_CMD 0x00 |
30 | #define LP8557_BL_MASK 0x01 |
31 | #define LP8557_BL_ON 0x01 |
32 | #define LP8557_BL_OFF 0x00 |
33 | #define LP8557_BRIGHTNESS_CTRL 0x04 |
34 | #define LP8557_CONFIG 0x10 |
35 | #define LP8555_EPROM_START 0x10 |
36 | #define LP8555_EPROM_END 0x7A |
37 | #define LP8557_EPROM_START 0x10 |
38 | #define LP8557_EPROM_END 0x1E |
39 | |
40 | #define DEFAULT_BL_NAME "lcd-backlight" |
41 | #define MAX_BRIGHTNESS 255 |
42 | |
43 | enum lp855x_brightness_ctrl_mode { |
44 | PWM_BASED = 1, |
45 | REGISTER_BASED, |
46 | }; |
47 | |
48 | struct lp855x; |
49 | |
50 | /* |
51 | * struct lp855x_device_config |
52 | * @pre_init_device: init device function call before updating the brightness |
53 | * @reg_brightness: register address for brigthenss control |
54 | * @reg_devicectrl: register address for device control |
55 | * @post_init_device: late init device function call |
56 | */ |
57 | struct lp855x_device_config { |
58 | int (*pre_init_device)(struct lp855x *); |
59 | u8 reg_brightness; |
60 | u8 reg_devicectrl; |
61 | int (*post_init_device)(struct lp855x *); |
62 | }; |
63 | |
64 | struct lp855x { |
65 | const char *chipname; |
66 | enum lp855x_chip_id chip_id; |
67 | enum lp855x_brightness_ctrl_mode mode; |
68 | struct lp855x_device_config *cfg; |
69 | struct i2c_client *client; |
70 | struct backlight_device *bl; |
71 | struct device *dev; |
72 | struct lp855x_platform_data *pdata; |
73 | struct pwm_device *pwm; |
74 | bool needs_pwm_init; |
75 | struct regulator *supply; /* regulator for VDD input */ |
76 | struct regulator *enable; /* regulator for EN/VDDIO input */ |
77 | }; |
78 | |
79 | static int lp855x_write_byte(struct lp855x *lp, u8 reg, u8 data) |
80 | { |
81 | return i2c_smbus_write_byte_data(client: lp->client, command: reg, value: data); |
82 | } |
83 | |
84 | static int lp855x_update_bit(struct lp855x *lp, u8 reg, u8 mask, u8 data) |
85 | { |
86 | int ret; |
87 | u8 tmp; |
88 | |
89 | ret = i2c_smbus_read_byte_data(client: lp->client, command: reg); |
90 | if (ret < 0) { |
91 | dev_err(lp->dev, "failed to read 0x%.2x\n" , reg); |
92 | return ret; |
93 | } |
94 | |
95 | tmp = (u8)ret; |
96 | tmp &= ~mask; |
97 | tmp |= data & mask; |
98 | |
99 | return lp855x_write_byte(lp, reg, data: tmp); |
100 | } |
101 | |
102 | static bool lp855x_is_valid_rom_area(struct lp855x *lp, u8 addr) |
103 | { |
104 | u8 start, end; |
105 | |
106 | switch (lp->chip_id) { |
107 | case LP8550: |
108 | case LP8551: |
109 | case LP8552: |
110 | case LP8553: |
111 | start = LP855X_EEPROM_START; |
112 | end = LP855X_EEPROM_END; |
113 | break; |
114 | case LP8556: |
115 | start = LP8556_EPROM_START; |
116 | end = LP8556_EPROM_END; |
117 | break; |
118 | case LP8555: |
119 | start = LP8555_EPROM_START; |
120 | end = LP8555_EPROM_END; |
121 | break; |
122 | case LP8557: |
123 | start = LP8557_EPROM_START; |
124 | end = LP8557_EPROM_END; |
125 | break; |
126 | default: |
127 | return false; |
128 | } |
129 | |
130 | return addr >= start && addr <= end; |
131 | } |
132 | |
133 | static int lp8557_bl_off(struct lp855x *lp) |
134 | { |
135 | /* BL_ON = 0 before updating EPROM settings */ |
136 | return lp855x_update_bit(lp, LP8557_BL_CMD, LP8557_BL_MASK, |
137 | LP8557_BL_OFF); |
138 | } |
139 | |
140 | static int lp8557_bl_on(struct lp855x *lp) |
141 | { |
142 | /* BL_ON = 1 after updating EPROM settings */ |
143 | return lp855x_update_bit(lp, LP8557_BL_CMD, LP8557_BL_MASK, |
144 | LP8557_BL_ON); |
145 | } |
146 | |
147 | static struct lp855x_device_config lp855x_dev_cfg = { |
148 | .reg_brightness = LP855X_BRIGHTNESS_CTRL, |
149 | .reg_devicectrl = LP855X_DEVICE_CTRL, |
150 | }; |
151 | |
152 | static struct lp855x_device_config lp8557_dev_cfg = { |
153 | .reg_brightness = LP8557_BRIGHTNESS_CTRL, |
154 | .reg_devicectrl = LP8557_CONFIG, |
155 | .pre_init_device = lp8557_bl_off, |
156 | .post_init_device = lp8557_bl_on, |
157 | }; |
158 | |
159 | /* |
160 | * Device specific configuration flow |
161 | * |
162 | * a) pre_init_device(optional) |
163 | * b) update the brightness register |
164 | * c) update device control register |
165 | * d) update ROM area(optional) |
166 | * e) post_init_device(optional) |
167 | * |
168 | */ |
169 | static int lp855x_configure(struct lp855x *lp) |
170 | { |
171 | u8 val, addr; |
172 | int i, ret; |
173 | struct lp855x_platform_data *pd = lp->pdata; |
174 | |
175 | if (lp->cfg->pre_init_device) { |
176 | ret = lp->cfg->pre_init_device(lp); |
177 | if (ret) { |
178 | dev_err(lp->dev, "pre init device err: %d\n" , ret); |
179 | goto err; |
180 | } |
181 | } |
182 | |
183 | val = pd->initial_brightness; |
184 | ret = lp855x_write_byte(lp, reg: lp->cfg->reg_brightness, data: val); |
185 | if (ret) |
186 | goto err; |
187 | |
188 | val = pd->device_control; |
189 | ret = lp855x_write_byte(lp, reg: lp->cfg->reg_devicectrl, data: val); |
190 | if (ret) |
191 | goto err; |
192 | |
193 | if (pd->size_program > 0) { |
194 | for (i = 0; i < pd->size_program; i++) { |
195 | addr = pd->rom_data[i].addr; |
196 | val = pd->rom_data[i].val; |
197 | if (!lp855x_is_valid_rom_area(lp, addr)) |
198 | continue; |
199 | |
200 | ret = lp855x_write_byte(lp, reg: addr, data: val); |
201 | if (ret) |
202 | goto err; |
203 | } |
204 | } |
205 | |
206 | if (lp->cfg->post_init_device) { |
207 | ret = lp->cfg->post_init_device(lp); |
208 | if (ret) { |
209 | dev_err(lp->dev, "post init device err: %d\n" , ret); |
210 | goto err; |
211 | } |
212 | } |
213 | |
214 | return 0; |
215 | |
216 | err: |
217 | return ret; |
218 | } |
219 | |
220 | static int lp855x_pwm_ctrl(struct lp855x *lp, int br, int max_br) |
221 | { |
222 | struct pwm_state state; |
223 | |
224 | if (lp->needs_pwm_init) { |
225 | pwm_init_state(pwm: lp->pwm, state: &state); |
226 | /* Legacy platform data compatibility */ |
227 | if (lp->pdata->period_ns > 0) |
228 | state.period = lp->pdata->period_ns; |
229 | lp->needs_pwm_init = false; |
230 | } else { |
231 | pwm_get_state(pwm: lp->pwm, state: &state); |
232 | } |
233 | |
234 | state.duty_cycle = div_u64(dividend: br * state.period, divisor: max_br); |
235 | state.enabled = state.duty_cycle; |
236 | |
237 | return pwm_apply_might_sleep(pwm: lp->pwm, state: &state); |
238 | } |
239 | |
240 | static int lp855x_bl_update_status(struct backlight_device *bl) |
241 | { |
242 | struct lp855x *lp = bl_get_data(bl_dev: bl); |
243 | int brightness = bl->props.brightness; |
244 | |
245 | if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) |
246 | brightness = 0; |
247 | |
248 | if (lp->mode == PWM_BASED) |
249 | return lp855x_pwm_ctrl(lp, br: brightness, |
250 | max_br: bl->props.max_brightness); |
251 | else if (lp->mode == REGISTER_BASED) |
252 | return lp855x_write_byte(lp, reg: lp->cfg->reg_brightness, |
253 | data: (u8)brightness); |
254 | return -EINVAL; |
255 | } |
256 | |
257 | static const struct backlight_ops lp855x_bl_ops = { |
258 | .options = BL_CORE_SUSPENDRESUME, |
259 | .update_status = lp855x_bl_update_status, |
260 | }; |
261 | |
262 | static int lp855x_backlight_register(struct lp855x *lp) |
263 | { |
264 | struct backlight_device *bl; |
265 | struct backlight_properties props; |
266 | struct lp855x_platform_data *pdata = lp->pdata; |
267 | const char *name = pdata->name ? : DEFAULT_BL_NAME; |
268 | |
269 | memset(&props, 0, sizeof(props)); |
270 | props.type = BACKLIGHT_PLATFORM; |
271 | props.max_brightness = MAX_BRIGHTNESS; |
272 | |
273 | if (pdata->initial_brightness > props.max_brightness) |
274 | pdata->initial_brightness = props.max_brightness; |
275 | |
276 | props.brightness = pdata->initial_brightness; |
277 | |
278 | bl = devm_backlight_device_register(dev: lp->dev, name, parent: lp->dev, devdata: lp, |
279 | ops: &lp855x_bl_ops, props: &props); |
280 | if (IS_ERR(ptr: bl)) |
281 | return PTR_ERR(ptr: bl); |
282 | |
283 | lp->bl = bl; |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static ssize_t lp855x_get_chip_id(struct device *dev, |
289 | struct device_attribute *attr, char *buf) |
290 | { |
291 | struct lp855x *lp = dev_get_drvdata(dev); |
292 | |
293 | return scnprintf(buf, PAGE_SIZE, fmt: "%s\n" , lp->chipname); |
294 | } |
295 | |
296 | static ssize_t lp855x_get_bl_ctl_mode(struct device *dev, |
297 | struct device_attribute *attr, char *buf) |
298 | { |
299 | struct lp855x *lp = dev_get_drvdata(dev); |
300 | char *strmode = NULL; |
301 | |
302 | if (lp->mode == PWM_BASED) |
303 | strmode = "pwm based" ; |
304 | else if (lp->mode == REGISTER_BASED) |
305 | strmode = "register based" ; |
306 | |
307 | return scnprintf(buf, PAGE_SIZE, fmt: "%s\n" , strmode); |
308 | } |
309 | |
310 | static DEVICE_ATTR(chip_id, S_IRUGO, lp855x_get_chip_id, NULL); |
311 | static DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp855x_get_bl_ctl_mode, NULL); |
312 | |
313 | static struct attribute *lp855x_attributes[] = { |
314 | &dev_attr_chip_id.attr, |
315 | &dev_attr_bl_ctl_mode.attr, |
316 | NULL, |
317 | }; |
318 | |
319 | static const struct attribute_group lp855x_attr_group = { |
320 | .attrs = lp855x_attributes, |
321 | }; |
322 | |
323 | #ifdef CONFIG_OF |
324 | static int lp855x_parse_dt(struct lp855x *lp) |
325 | { |
326 | struct device *dev = lp->dev; |
327 | struct device_node *node = dev->of_node; |
328 | struct lp855x_platform_data *pdata = lp->pdata; |
329 | int rom_length; |
330 | |
331 | if (!node) { |
332 | dev_err(dev, "no platform data\n" ); |
333 | return -EINVAL; |
334 | } |
335 | |
336 | of_property_read_string(np: node, propname: "bl-name" , out_string: &pdata->name); |
337 | of_property_read_u8(np: node, propname: "dev-ctrl" , out_value: &pdata->device_control); |
338 | of_property_read_u8(np: node, propname: "init-brt" , out_value: &pdata->initial_brightness); |
339 | /* Deprecated, specify period in pwms property instead */ |
340 | of_property_read_u32(np: node, propname: "pwm-period" , out_value: &pdata->period_ns); |
341 | |
342 | /* Fill ROM platform data if defined */ |
343 | rom_length = of_get_child_count(np: node); |
344 | if (rom_length > 0) { |
345 | struct lp855x_rom_data *rom; |
346 | struct device_node *child; |
347 | int i = 0; |
348 | |
349 | rom = devm_kcalloc(dev, n: rom_length, size: sizeof(*rom), GFP_KERNEL); |
350 | if (!rom) |
351 | return -ENOMEM; |
352 | |
353 | for_each_child_of_node(node, child) { |
354 | of_property_read_u8(np: child, propname: "rom-addr" , out_value: &rom[i].addr); |
355 | of_property_read_u8(np: child, propname: "rom-val" , out_value: &rom[i].val); |
356 | i++; |
357 | } |
358 | |
359 | pdata->size_program = rom_length; |
360 | pdata->rom_data = &rom[0]; |
361 | } |
362 | |
363 | return 0; |
364 | } |
365 | #else |
366 | static int lp855x_parse_dt(struct lp855x *lp) |
367 | { |
368 | return -EINVAL; |
369 | } |
370 | #endif |
371 | |
372 | static int lp855x_parse_acpi(struct lp855x *lp) |
373 | { |
374 | int ret; |
375 | |
376 | /* |
377 | * On ACPI the device has already been initialized by the firmware |
378 | * and is in register mode, so we can read back the settings from |
379 | * the registers. |
380 | */ |
381 | ret = i2c_smbus_read_byte_data(client: lp->client, command: lp->cfg->reg_brightness); |
382 | if (ret < 0) |
383 | return ret; |
384 | |
385 | lp->pdata->initial_brightness = ret; |
386 | |
387 | ret = i2c_smbus_read_byte_data(client: lp->client, command: lp->cfg->reg_devicectrl); |
388 | if (ret < 0) |
389 | return ret; |
390 | |
391 | lp->pdata->device_control = ret; |
392 | return 0; |
393 | } |
394 | |
395 | static int lp855x_probe(struct i2c_client *cl) |
396 | { |
397 | const struct i2c_device_id *id = i2c_client_get_device_id(client: cl); |
398 | const struct acpi_device_id *acpi_id = NULL; |
399 | struct device *dev = &cl->dev; |
400 | struct lp855x *lp; |
401 | int ret; |
402 | |
403 | if (!i2c_check_functionality(adap: cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) |
404 | return -EIO; |
405 | |
406 | lp = devm_kzalloc(dev, size: sizeof(struct lp855x), GFP_KERNEL); |
407 | if (!lp) |
408 | return -ENOMEM; |
409 | |
410 | lp->client = cl; |
411 | lp->dev = dev; |
412 | lp->pdata = dev_get_platdata(dev); |
413 | |
414 | if (id) { |
415 | lp->chipname = id->name; |
416 | lp->chip_id = id->driver_data; |
417 | } else { |
418 | acpi_id = acpi_match_device(ids: dev->driver->acpi_match_table, dev); |
419 | if (!acpi_id) |
420 | return -ENODEV; |
421 | |
422 | lp->chipname = acpi_id->id; |
423 | lp->chip_id = acpi_id->driver_data; |
424 | } |
425 | |
426 | switch (lp->chip_id) { |
427 | case LP8550: |
428 | case LP8551: |
429 | case LP8552: |
430 | case LP8553: |
431 | case LP8556: |
432 | lp->cfg = &lp855x_dev_cfg; |
433 | break; |
434 | case LP8555: |
435 | case LP8557: |
436 | lp->cfg = &lp8557_dev_cfg; |
437 | break; |
438 | default: |
439 | return -EINVAL; |
440 | } |
441 | |
442 | if (!lp->pdata) { |
443 | lp->pdata = devm_kzalloc(dev, size: sizeof(*lp->pdata), GFP_KERNEL); |
444 | if (!lp->pdata) |
445 | return -ENOMEM; |
446 | |
447 | if (id) { |
448 | ret = lp855x_parse_dt(lp); |
449 | if (ret < 0) |
450 | return ret; |
451 | } else { |
452 | ret = lp855x_parse_acpi(lp); |
453 | if (ret < 0) |
454 | return ret; |
455 | } |
456 | } |
457 | |
458 | lp->supply = devm_regulator_get(dev, id: "power" ); |
459 | if (IS_ERR(ptr: lp->supply)) { |
460 | if (PTR_ERR(ptr: lp->supply) == -EPROBE_DEFER) |
461 | return -EPROBE_DEFER; |
462 | lp->supply = NULL; |
463 | } |
464 | |
465 | lp->enable = devm_regulator_get_optional(dev, id: "enable" ); |
466 | if (IS_ERR(ptr: lp->enable)) { |
467 | ret = PTR_ERR(ptr: lp->enable); |
468 | if (ret == -ENODEV) |
469 | lp->enable = NULL; |
470 | else |
471 | return dev_err_probe(dev, err: ret, fmt: "getting enable regulator\n" ); |
472 | } |
473 | |
474 | lp->pwm = devm_pwm_get(dev: lp->dev, con_id: lp->chipname); |
475 | if (IS_ERR(ptr: lp->pwm)) { |
476 | ret = PTR_ERR(ptr: lp->pwm); |
477 | if (ret == -ENODEV || ret == -EINVAL) |
478 | lp->pwm = NULL; |
479 | else |
480 | return dev_err_probe(dev, err: ret, fmt: "getting PWM\n" ); |
481 | |
482 | lp->needs_pwm_init = false; |
483 | lp->mode = REGISTER_BASED; |
484 | dev_dbg(dev, "mode: register based\n" ); |
485 | } else { |
486 | lp->needs_pwm_init = true; |
487 | lp->mode = PWM_BASED; |
488 | dev_dbg(dev, "mode: PWM based\n" ); |
489 | } |
490 | |
491 | if (lp->supply) { |
492 | ret = regulator_enable(regulator: lp->supply); |
493 | if (ret < 0) { |
494 | dev_err(dev, "failed to enable supply: %d\n" , ret); |
495 | return ret; |
496 | } |
497 | } |
498 | |
499 | if (lp->enable) { |
500 | ret = regulator_enable(regulator: lp->enable); |
501 | if (ret < 0) { |
502 | dev_err(dev, "failed to enable vddio: %d\n" , ret); |
503 | goto disable_supply; |
504 | } |
505 | |
506 | /* |
507 | * LP8555 datasheet says t_RESPONSE (time between VDDIO and |
508 | * I2C) is 1ms. |
509 | */ |
510 | usleep_range(min: 1000, max: 2000); |
511 | } |
512 | |
513 | i2c_set_clientdata(client: cl, data: lp); |
514 | |
515 | ret = lp855x_configure(lp); |
516 | if (ret) { |
517 | dev_err(dev, "device config err: %d" , ret); |
518 | goto disable_vddio; |
519 | } |
520 | |
521 | ret = lp855x_backlight_register(lp); |
522 | if (ret) { |
523 | dev_err(dev, "failed to register backlight. err: %d\n" , ret); |
524 | goto disable_vddio; |
525 | } |
526 | |
527 | ret = sysfs_create_group(kobj: &dev->kobj, grp: &lp855x_attr_group); |
528 | if (ret) { |
529 | dev_err(dev, "failed to register sysfs. err: %d\n" , ret); |
530 | goto disable_vddio; |
531 | } |
532 | |
533 | backlight_update_status(bd: lp->bl); |
534 | |
535 | return 0; |
536 | |
537 | disable_vddio: |
538 | if (lp->enable) |
539 | regulator_disable(regulator: lp->enable); |
540 | disable_supply: |
541 | if (lp->supply) |
542 | regulator_disable(regulator: lp->supply); |
543 | |
544 | return ret; |
545 | } |
546 | |
547 | static void lp855x_remove(struct i2c_client *cl) |
548 | { |
549 | struct lp855x *lp = i2c_get_clientdata(client: cl); |
550 | |
551 | lp->bl->props.brightness = 0; |
552 | backlight_update_status(bd: lp->bl); |
553 | if (lp->enable) |
554 | regulator_disable(regulator: lp->enable); |
555 | if (lp->supply) |
556 | regulator_disable(regulator: lp->supply); |
557 | sysfs_remove_group(kobj: &lp->dev->kobj, grp: &lp855x_attr_group); |
558 | } |
559 | |
560 | static const struct of_device_id lp855x_dt_ids[] __maybe_unused = { |
561 | { .compatible = "ti,lp8550" , }, |
562 | { .compatible = "ti,lp8551" , }, |
563 | { .compatible = "ti,lp8552" , }, |
564 | { .compatible = "ti,lp8553" , }, |
565 | { .compatible = "ti,lp8555" , }, |
566 | { .compatible = "ti,lp8556" , }, |
567 | { .compatible = "ti,lp8557" , }, |
568 | { } |
569 | }; |
570 | MODULE_DEVICE_TABLE(of, lp855x_dt_ids); |
571 | |
572 | static const struct i2c_device_id lp855x_ids[] = { |
573 | {"lp8550" , LP8550}, |
574 | {"lp8551" , LP8551}, |
575 | {"lp8552" , LP8552}, |
576 | {"lp8553" , LP8553}, |
577 | {"lp8555" , LP8555}, |
578 | {"lp8556" , LP8556}, |
579 | {"lp8557" , LP8557}, |
580 | { } |
581 | }; |
582 | MODULE_DEVICE_TABLE(i2c, lp855x_ids); |
583 | |
584 | #ifdef CONFIG_ACPI |
585 | static const struct acpi_device_id lp855x_acpi_match[] = { |
586 | /* Xiaomi specific HID used for the LP8556 on the Mi Pad 2 */ |
587 | { "XMCC0001" , LP8556 }, |
588 | { } |
589 | }; |
590 | MODULE_DEVICE_TABLE(acpi, lp855x_acpi_match); |
591 | #endif |
592 | |
593 | static struct i2c_driver lp855x_driver = { |
594 | .driver = { |
595 | .name = "lp855x" , |
596 | .of_match_table = of_match_ptr(lp855x_dt_ids), |
597 | .acpi_match_table = ACPI_PTR(lp855x_acpi_match), |
598 | }, |
599 | .probe = lp855x_probe, |
600 | .remove = lp855x_remove, |
601 | .id_table = lp855x_ids, |
602 | }; |
603 | |
604 | module_i2c_driver(lp855x_driver); |
605 | |
606 | MODULE_DESCRIPTION("Texas Instruments LP855x Backlight driver" ); |
607 | MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>" ); |
608 | MODULE_LICENSE("GPL" ); |
609 | |