1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * AS3711 PMIC backlight driver, using DCDC Step Up Converters
4 *
5 * Copyright (C) 2012 Renesas Electronics Corporation
6 * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
7 */
8
9 #include <linux/backlight.h>
10 #include <linux/delay.h>
11 #include <linux/device.h>
12 #include <linux/err.h>
13 #include <linux/kernel.h>
14 #include <linux/mfd/as3711.h>
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/regmap.h>
18 #include <linux/slab.h>
19
20 enum as3711_bl_type {
21 AS3711_BL_SU1,
22 AS3711_BL_SU2,
23 };
24
25 struct as3711_bl_data {
26 bool powered;
27 enum as3711_bl_type type;
28 int brightness;
29 struct backlight_device *bl;
30 };
31
32 struct as3711_bl_supply {
33 struct as3711_bl_data su1;
34 struct as3711_bl_data su2;
35 const struct as3711_bl_pdata *pdata;
36 struct as3711 *as3711;
37 };
38
to_supply(struct as3711_bl_data * su)39 static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su)
40 {
41 switch (su->type) {
42 case AS3711_BL_SU1:
43 return container_of(su, struct as3711_bl_supply, su1);
44 case AS3711_BL_SU2:
45 return container_of(su, struct as3711_bl_supply, su2);
46 }
47 return NULL;
48 }
49
as3711_set_brightness_auto_i(struct as3711_bl_data * data,unsigned int brightness)50 static int as3711_set_brightness_auto_i(struct as3711_bl_data *data,
51 unsigned int brightness)
52 {
53 struct as3711_bl_supply *supply = to_supply(data);
54 struct as3711 *as3711 = supply->as3711;
55 const struct as3711_bl_pdata *pdata = supply->pdata;
56 int ret = 0;
57
58 /* Only all equal current values are supported */
59 if (pdata->su2_auto_curr1)
60 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
61 brightness);
62 if (!ret && pdata->su2_auto_curr2)
63 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
64 brightness);
65 if (!ret && pdata->su2_auto_curr3)
66 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
67 brightness);
68
69 return ret;
70 }
71
as3711_set_brightness_v(struct as3711 * as3711,unsigned int brightness,unsigned int reg)72 static int as3711_set_brightness_v(struct as3711 *as3711,
73 unsigned int brightness,
74 unsigned int reg)
75 {
76 if (brightness > 31)
77 return -EINVAL;
78
79 return regmap_update_bits(as3711->regmap, reg, 0xf0,
80 brightness << 4);
81 }
82
as3711_bl_su2_reset(struct as3711_bl_supply * supply)83 static int as3711_bl_su2_reset(struct as3711_bl_supply *supply)
84 {
85 struct as3711 *as3711 = supply->as3711;
86 int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5,
87 3, supply->pdata->su2_fbprot);
88 if (!ret)
89 ret = regmap_update_bits(as3711->regmap,
90 AS3711_STEPUP_CONTROL_2, 1, 0);
91 if (!ret)
92 ret = regmap_update_bits(as3711->regmap,
93 AS3711_STEPUP_CONTROL_2, 1, 1);
94 return ret;
95 }
96
97 /*
98 * Someone with less fragile or less expensive hardware could try to simplify
99 * the brightness adjustment procedure.
100 */
as3711_bl_update_status(struct backlight_device * bl)101 static int as3711_bl_update_status(struct backlight_device *bl)
102 {
103 struct as3711_bl_data *data = bl_get_data(bl);
104 struct as3711_bl_supply *supply = to_supply(data);
105 struct as3711 *as3711 = supply->as3711;
106 int brightness;
107 int ret = 0;
108
109 brightness = backlight_get_brightness(bl);
110
111 if (data->type == AS3711_BL_SU1) {
112 ret = as3711_set_brightness_v(as3711, brightness,
113 AS3711_STEPUP_CONTROL_1);
114 } else {
115 const struct as3711_bl_pdata *pdata = supply->pdata;
116
117 switch (pdata->su2_feedback) {
118 case AS3711_SU2_VOLTAGE:
119 ret = as3711_set_brightness_v(as3711, brightness,
120 AS3711_STEPUP_CONTROL_2);
121 break;
122 case AS3711_SU2_CURR_AUTO:
123 ret = as3711_set_brightness_auto_i(data, brightness / 4);
124 if (ret < 0)
125 return ret;
126 if (brightness) {
127 ret = as3711_bl_su2_reset(supply);
128 if (ret < 0)
129 return ret;
130 udelay(500);
131 ret = as3711_set_brightness_auto_i(data, brightness);
132 } else {
133 ret = regmap_update_bits(as3711->regmap,
134 AS3711_STEPUP_CONTROL_2, 1, 0);
135 }
136 break;
137 /* Manual one current feedback pin below */
138 case AS3711_SU2_CURR1:
139 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
140 brightness);
141 break;
142 case AS3711_SU2_CURR2:
143 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
144 brightness);
145 break;
146 case AS3711_SU2_CURR3:
147 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
148 brightness);
149 break;
150 default:
151 ret = -EINVAL;
152 }
153 }
154 if (!ret)
155 data->brightness = brightness;
156
157 return ret;
158 }
159
as3711_bl_get_brightness(struct backlight_device * bl)160 static int as3711_bl_get_brightness(struct backlight_device *bl)
161 {
162 struct as3711_bl_data *data = bl_get_data(bl);
163
164 return data->brightness;
165 }
166
167 static const struct backlight_ops as3711_bl_ops = {
168 .update_status = as3711_bl_update_status,
169 .get_brightness = as3711_bl_get_brightness,
170 };
171
as3711_bl_init_su2(struct as3711_bl_supply * supply)172 static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
173 {
174 struct as3711 *as3711 = supply->as3711;
175 const struct as3711_bl_pdata *pdata = supply->pdata;
176 u8 ctl = 0;
177 int ret;
178
179 dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
180
181 /* Turn SU2 off */
182 ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
183 if (ret < 0)
184 return ret;
185
186 switch (pdata->su2_feedback) {
187 case AS3711_SU2_VOLTAGE:
188 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
189 break;
190 case AS3711_SU2_CURR1:
191 ctl = 1;
192 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
193 break;
194 case AS3711_SU2_CURR2:
195 ctl = 4;
196 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
197 break;
198 case AS3711_SU2_CURR3:
199 ctl = 0x10;
200 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
201 break;
202 case AS3711_SU2_CURR_AUTO:
203 if (pdata->su2_auto_curr1)
204 ctl = 2;
205 if (pdata->su2_auto_curr2)
206 ctl |= 8;
207 if (pdata->su2_auto_curr3)
208 ctl |= 0x20;
209 ret = 0;
210 break;
211 default:
212 return -EINVAL;
213 }
214
215 if (!ret)
216 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
217
218 return ret;
219 }
220
as3711_bl_register(struct platform_device * pdev,unsigned int max_brightness,struct as3711_bl_data * su)221 static int as3711_bl_register(struct platform_device *pdev,
222 unsigned int max_brightness, struct as3711_bl_data *su)
223 {
224 struct backlight_properties props = {.type = BACKLIGHT_RAW,};
225 struct backlight_device *bl;
226
227 /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
228 props.max_brightness = max_brightness;
229
230 bl = devm_backlight_device_register(&pdev->dev,
231 su->type == AS3711_BL_SU1 ?
232 "as3711-su1" : "as3711-su2",
233 &pdev->dev, su,
234 &as3711_bl_ops, &props);
235 if (IS_ERR(bl)) {
236 dev_err(&pdev->dev, "failed to register backlight\n");
237 return PTR_ERR(bl);
238 }
239
240 bl->props.brightness = props.max_brightness;
241
242 backlight_update_status(bl);
243
244 su->bl = bl;
245
246 return 0;
247 }
248
as3711_backlight_parse_dt(struct device * dev)249 static int as3711_backlight_parse_dt(struct device *dev)
250 {
251 struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
252 struct device_node *bl, *fb;
253 int ret;
254
255 bl = of_get_child_by_name(dev->parent->of_node, "backlight");
256 if (!bl) {
257 dev_dbg(dev, "backlight node not found\n");
258 return -ENODEV;
259 }
260
261 fb = of_parse_phandle(bl, "su1-dev", 0);
262 if (fb) {
263 of_node_put(fb);
264
265 pdata->su1_fb = true;
266
267 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
268 if (pdata->su1_max_uA <= 0)
269 ret = -EINVAL;
270 if (ret < 0)
271 goto err_put_bl;
272 }
273
274 fb = of_parse_phandle(bl, "su2-dev", 0);
275 if (fb) {
276 int count = 0;
277
278 of_node_put(fb);
279
280 pdata->su2_fb = true;
281
282 ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
283 if (pdata->su2_max_uA <= 0)
284 ret = -EINVAL;
285 if (ret < 0)
286 goto err_put_bl;
287
288 if (of_property_read_bool(bl, "su2-feedback-voltage")) {
289 pdata->su2_feedback = AS3711_SU2_VOLTAGE;
290 count++;
291 }
292 if (of_property_read_bool(bl, "su2-feedback-curr1")) {
293 pdata->su2_feedback = AS3711_SU2_CURR1;
294 count++;
295 }
296 if (of_property_read_bool(bl, "su2-feedback-curr2")) {
297 pdata->su2_feedback = AS3711_SU2_CURR2;
298 count++;
299 }
300 if (of_property_read_bool(bl, "su2-feedback-curr3")) {
301 pdata->su2_feedback = AS3711_SU2_CURR3;
302 count++;
303 }
304 if (of_property_read_bool(bl, "su2-feedback-curr-auto")) {
305 pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
306 count++;
307 }
308 if (count != 1) {
309 ret = -EINVAL;
310 goto err_put_bl;
311 }
312
313 count = 0;
314 if (of_property_read_bool(bl, "su2-fbprot-lx-sd4")) {
315 pdata->su2_fbprot = AS3711_SU2_LX_SD4;
316 count++;
317 }
318 if (of_property_read_bool(bl, "su2-fbprot-gpio2")) {
319 pdata->su2_fbprot = AS3711_SU2_GPIO2;
320 count++;
321 }
322 if (of_property_read_bool(bl, "su2-fbprot-gpio3")) {
323 pdata->su2_fbprot = AS3711_SU2_GPIO3;
324 count++;
325 }
326 if (of_property_read_bool(bl, "su2-fbprot-gpio4")) {
327 pdata->su2_fbprot = AS3711_SU2_GPIO4;
328 count++;
329 }
330 if (count != 1) {
331 ret = -EINVAL;
332 goto err_put_bl;
333 }
334
335 count = 0;
336 if (of_property_read_bool(bl, "su2-auto-curr1")) {
337 pdata->su2_auto_curr1 = true;
338 count++;
339 }
340 if (of_property_read_bool(bl, "su2-auto-curr2")) {
341 pdata->su2_auto_curr2 = true;
342 count++;
343 }
344 if (of_property_read_bool(bl, "su2-auto-curr3")) {
345 pdata->su2_auto_curr3 = true;
346 count++;
347 }
348
349 /*
350 * At least one su2-auto-curr* must be specified iff
351 * AS3711_SU2_CURR_AUTO is used
352 */
353 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) {
354 ret = -EINVAL;
355 goto err_put_bl;
356 }
357 }
358
359 of_node_put(bl);
360
361 return 0;
362
363 err_put_bl:
364 of_node_put(bl);
365
366 return ret;
367 }
368
as3711_backlight_probe(struct platform_device * pdev)369 static int as3711_backlight_probe(struct platform_device *pdev)
370 {
371 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
372 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
373 struct as3711_bl_supply *supply;
374 struct as3711_bl_data *su;
375 unsigned int max_brightness;
376 int ret;
377
378 if (!pdata) {
379 dev_err(&pdev->dev, "No platform data, exiting...\n");
380 return -ENODEV;
381 }
382
383 if (pdev->dev.parent->of_node) {
384 ret = as3711_backlight_parse_dt(&pdev->dev);
385 if (ret < 0)
386 return dev_err_probe(&pdev->dev, ret, "DT parsing failed\n");
387 }
388
389 if (!pdata->su1_fb && !pdata->su2_fb) {
390 dev_err(&pdev->dev, "No framebuffer specified\n");
391 return -EINVAL;
392 }
393
394 /*
395 * Due to possible hardware damage I chose to block all modes,
396 * unsupported on my hardware. Anyone, wishing to use any of those modes
397 * will have to first review the code, then activate and test it.
398 */
399 if (pdata->su1_fb ||
400 pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
401 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
402 dev_warn(&pdev->dev,
403 "Attention! An untested mode has been chosen!\n"
404 "Please, review the code, enable, test, and report success:-)\n");
405 return -EINVAL;
406 }
407
408 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
409 if (!supply)
410 return -ENOMEM;
411
412 supply->as3711 = as3711;
413 supply->pdata = pdata;
414
415 if (pdata->su1_fb) {
416 su = &supply->su1;
417 su->type = AS3711_BL_SU1;
418
419 max_brightness = min(pdata->su1_max_uA, 31);
420 ret = as3711_bl_register(pdev, max_brightness, su);
421 if (ret < 0)
422 return ret;
423 }
424
425 if (pdata->su2_fb) {
426 su = &supply->su2;
427 su->type = AS3711_BL_SU2;
428
429 switch (pdata->su2_fbprot) {
430 case AS3711_SU2_GPIO2:
431 case AS3711_SU2_GPIO3:
432 case AS3711_SU2_GPIO4:
433 case AS3711_SU2_LX_SD4:
434 break;
435 default:
436 return -EINVAL;
437 }
438
439 switch (pdata->su2_feedback) {
440 case AS3711_SU2_VOLTAGE:
441 max_brightness = min(pdata->su2_max_uA, 31);
442 break;
443 case AS3711_SU2_CURR1:
444 case AS3711_SU2_CURR2:
445 case AS3711_SU2_CURR3:
446 case AS3711_SU2_CURR_AUTO:
447 max_brightness = min(pdata->su2_max_uA / 150, 255);
448 break;
449 default:
450 return -EINVAL;
451 }
452
453 ret = as3711_bl_init_su2(supply);
454 if (ret < 0)
455 return ret;
456
457 ret = as3711_bl_register(pdev, max_brightness, su);
458 if (ret < 0)
459 return ret;
460 }
461
462 platform_set_drvdata(pdev, supply);
463
464 return 0;
465 }
466
467 static struct platform_driver as3711_backlight_driver = {
468 .driver = {
469 .name = "as3711-backlight",
470 },
471 .probe = as3711_backlight_probe,
472 };
473
474 module_platform_driver(as3711_backlight_driver);
475
476 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
477 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
478 MODULE_LICENSE("GPL v2");
479 MODULE_ALIAS("platform:as3711-backlight");
480