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