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