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