aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video
diff options
context:
space:
mode:
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>2013-02-21 19:43:59 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2013-02-21 20:22:25 -0500
commitde474b5bfcd5448681549369efb36c51f870ea60 (patch)
tree8926d10426e33e1079b32487aa4d6761f8ec6ef5 /drivers/video
parenta020a063c435767003f4379d5e89afc4c988b187 (diff)
backlight: add an AS3711 PMIC backlight driver
This is an initial commit of a backlight driver, using step-up DCDC power supplies on AS3711 PMIC. Only one mode has actually been tested, several further modes have been implemented "dry," but disabled to avoid accidental hardware damage. Anyone wishing to use any of those modes will have to modify the driver. Tested on sh73a0-based kzm9g board. Only one mode has been tested and is enabled. That mode copies the sample code from the manufacturer. Deviations from that code proved to be fatal for the hardware... Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de> Cc: Magnus Damm <magnus.damm@gmail.com> Cc: Richard Purdie <rpurdie@rpsys.net> Acked-by: Jingoo Han <jg1.han@samsung.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/backlight/Kconfig7
-rw-r--r--drivers/video/backlight/Makefile1
-rw-r--r--drivers/video/backlight/as3711_bl.c380
3 files changed, 388 insertions, 0 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 4615aca2cee2..a942a2488480 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -405,6 +405,13 @@ config BACKLIGHT_TPS65217
405 If you have a Texas Instruments TPS65217 say Y to enable the 405 If you have a Texas Instruments TPS65217 say Y to enable the
406 backlight driver. 406 backlight driver.
407 407
408config BACKLIGHT_AS3711
409 tristate "AS3711 Backlight"
410 depends on BACKLIGHT_CLASS_DEVICE && MFD_AS3711
411 help
412 If you have an Austrian Microsystems AS3711 say Y to enable the
413 backlight driver.
414
408endif # BACKLIGHT_CLASS_DEVICE 415endif # BACKLIGHT_CLASS_DEVICE
409 416
410endif # BACKLIGHT_LCD_SUPPORT 417endif # BACKLIGHT_LCD_SUPPORT
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 6c53e5b4707d..4606c218e8e4 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o
23obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o 23obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o
24obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o 24obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o
25obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o 25obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o
26obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o
26obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o 27obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o
27obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o 28obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
28obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o 29obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
diff --git a/drivers/video/backlight/as3711_bl.c b/drivers/video/backlight/as3711_bl.c
new file mode 100644
index 000000000000..41d52fe52543
--- /dev/null
+++ b/drivers/video/backlight/as3711_bl.c
@@ -0,0 +1,380 @@
1/*
2 * AS3711 PMIC backlight driver, using DCDC Step Up Converters
3 *
4 * Copyright (C) 2012 Renesas Electronics Corporation
5 * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the version 2 of the GNU General Public License as
9 * published by the Free Software Foundation
10 */
11
12#include <linux/backlight.h>
13#include <linux/delay.h>
14#include <linux/device.h>
15#include <linux/err.h>
16#include <linux/fb.h>
17#include <linux/kernel.h>
18#include <linux/mfd/as3711.h>
19#include <linux/module.h>
20#include <linux/platform_device.h>
21#include <linux/regmap.h>
22#include <linux/slab.h>
23
24enum as3711_bl_type {
25 AS3711_BL_SU1,
26 AS3711_BL_SU2,
27};
28
29struct as3711_bl_data {
30 bool powered;
31 const char *fb_name;
32 struct device *fb_dev;
33 enum as3711_bl_type type;
34 int brightness;
35 struct backlight_device *bl;
36};
37
38struct as3711_bl_supply {
39 struct as3711_bl_data su1;
40 struct as3711_bl_data su2;
41 const struct as3711_bl_pdata *pdata;
42 struct as3711 *as3711;
43};
44
45static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su)
46{
47 switch (su->type) {
48 case AS3711_BL_SU1:
49 return container_of(su, struct as3711_bl_supply, su1);
50 case AS3711_BL_SU2:
51 return container_of(su, struct as3711_bl_supply, su2);
52 }
53 return NULL;
54}
55
56static int as3711_set_brightness_auto_i(struct as3711_bl_data *data,
57 unsigned int brightness)
58{
59 struct as3711_bl_supply *supply = to_supply(data);
60 struct as3711 *as3711 = supply->as3711;
61 const struct as3711_bl_pdata *pdata = supply->pdata;
62 int ret = 0;
63
64 /* Only all equal current values are supported */
65 if (pdata->su2_auto_curr1)
66 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
67 brightness);
68 if (!ret && pdata->su2_auto_curr2)
69 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
70 brightness);
71 if (!ret && pdata->su2_auto_curr3)
72 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
73 brightness);
74
75 return ret;
76}
77
78static int as3711_set_brightness_v(struct as3711 *as3711,
79 unsigned int brightness,
80 unsigned int reg)
81{
82 if (brightness > 31)
83 return -EINVAL;
84
85 return regmap_update_bits(as3711->regmap, reg, 0xf0,
86 brightness << 4);
87}
88
89static int as3711_bl_su2_reset(struct as3711_bl_supply *supply)
90{
91 struct as3711 *as3711 = supply->as3711;
92 int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5,
93 3, supply->pdata->su2_fbprot);
94 if (!ret)
95 ret = regmap_update_bits(as3711->regmap,
96 AS3711_STEPUP_CONTROL_2, 1, 0);
97 if (!ret)
98 ret = regmap_update_bits(as3711->regmap,
99 AS3711_STEPUP_CONTROL_2, 1, 1);
100 return ret;
101}
102
103/*
104 * Someone with less fragile or less expensive hardware could try to simplify
105 * the brightness adjustment procedure.
106 */
107static int as3711_bl_update_status(struct backlight_device *bl)
108{
109 struct as3711_bl_data *data = bl_get_data(bl);
110 struct as3711_bl_supply *supply = to_supply(data);
111 struct as3711 *as3711 = supply->as3711;
112 int brightness = bl->props.brightness;
113 int ret = 0;
114
115 dev_dbg(&bl->dev, "%s(): brightness %u, pwr %x, blank %x, state %x\n",
116 __func__, bl->props.brightness, bl->props.power,
117 bl->props.fb_blank, bl->props.state);
118
119 if (bl->props.power != FB_BLANK_UNBLANK ||
120 bl->props.fb_blank != FB_BLANK_UNBLANK ||
121 bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
122 brightness = 0;
123
124 if (data->type == AS3711_BL_SU1) {
125 ret = as3711_set_brightness_v(as3711, brightness,
126 AS3711_STEPUP_CONTROL_1);
127 } else {
128 const struct as3711_bl_pdata *pdata = supply->pdata;
129
130 switch (pdata->su2_feedback) {
131 case AS3711_SU2_VOLTAGE:
132 ret = as3711_set_brightness_v(as3711, brightness,
133 AS3711_STEPUP_CONTROL_2);
134 break;
135 case AS3711_SU2_CURR_AUTO:
136 ret = as3711_set_brightness_auto_i(data, brightness / 4);
137 if (ret < 0)
138 return ret;
139 if (brightness) {
140 ret = as3711_bl_su2_reset(supply);
141 if (ret < 0)
142 return ret;
143 udelay(500);
144 ret = as3711_set_brightness_auto_i(data, brightness);
145 } else {
146 ret = regmap_update_bits(as3711->regmap,
147 AS3711_STEPUP_CONTROL_2, 1, 0);
148 }
149 break;
150 /* Manual one current feedback pin below */
151 case AS3711_SU2_CURR1:
152 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
153 brightness);
154 break;
155 case AS3711_SU2_CURR2:
156 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
157 brightness);
158 break;
159 case AS3711_SU2_CURR3:
160 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
161 brightness);
162 break;
163 default:
164 ret = -EINVAL;
165 }
166 }
167 if (!ret)
168 data->brightness = brightness;
169
170 return ret;
171}
172
173static int as3711_bl_get_brightness(struct backlight_device *bl)
174{
175 struct as3711_bl_data *data = bl_get_data(bl);
176
177 return data->brightness;
178}
179
180static const struct backlight_ops as3711_bl_ops = {
181 .update_status = as3711_bl_update_status,
182 .get_brightness = as3711_bl_get_brightness,
183};
184
185static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
186{
187 struct as3711 *as3711 = supply->as3711;
188 const struct as3711_bl_pdata *pdata = supply->pdata;
189 u8 ctl = 0;
190 int ret;
191
192 dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
193
194 /* Turn SU2 off */
195 ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
196 if (ret < 0)
197 return ret;
198
199 switch (pdata->su2_feedback) {
200 case AS3711_SU2_VOLTAGE:
201 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
202 break;
203 case AS3711_SU2_CURR1:
204 ctl = 1;
205 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
206 break;
207 case AS3711_SU2_CURR2:
208 ctl = 4;
209 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
210 break;
211 case AS3711_SU2_CURR3:
212 ctl = 0x10;
213 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
214 break;
215 case AS3711_SU2_CURR_AUTO:
216 if (pdata->su2_auto_curr1)
217 ctl = 2;
218 if (pdata->su2_auto_curr2)
219 ctl |= 8;
220 if (pdata->su2_auto_curr3)
221 ctl |= 0x20;
222 ret = 0;
223 break;
224 default:
225 return -EINVAL;
226 }
227
228 if (!ret)
229 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
230
231 return ret;
232}
233
234static int as3711_bl_register(struct platform_device *pdev,
235 unsigned int max_brightness, struct as3711_bl_data *su)
236{
237 struct backlight_properties props = {.type = BACKLIGHT_RAW,};
238 struct backlight_device *bl;
239
240 /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
241 props.max_brightness = max_brightness;
242
243 bl = backlight_device_register(su->type == AS3711_BL_SU1 ?
244 "as3711-su1" : "as3711-su2",
245 &pdev->dev, su,
246 &as3711_bl_ops, &props);
247 if (IS_ERR(bl)) {
248 dev_err(&pdev->dev, "failed to register backlight\n");
249 return PTR_ERR(bl);
250 }
251
252 bl->props.brightness = props.max_brightness;
253
254 backlight_update_status(bl);
255
256 su->bl = bl;
257
258 return 0;
259}
260
261static int as3711_backlight_probe(struct platform_device *pdev)
262{
263 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
264 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
265 struct as3711_bl_supply *supply;
266 struct as3711_bl_data *su;
267 unsigned int max_brightness;
268 int ret;
269
270 if (!pdata || (!pdata->su1_fb && !pdata->su2_fb)) {
271 dev_err(&pdev->dev, "No platform data, exiting...\n");
272 return -ENODEV;
273 }
274
275 /*
276 * Due to possible hardware damage I chose to block all modes,
277 * unsupported on my hardware. Anyone, wishing to use any of those modes
278 * will have to first review the code, then activate and test it.
279 */
280 if (pdata->su1_fb ||
281 pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
282 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
283 dev_warn(&pdev->dev,
284 "Attention! An untested mode has been chosen!\n"
285 "Please, review the code, enable, test, and report success:-)\n");
286 return -EINVAL;
287 }
288
289 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
290 if (!supply)
291 return -ENOMEM;
292
293 supply->as3711 = as3711;
294 supply->pdata = pdata;
295
296 if (pdata->su1_fb) {
297 su = &supply->su1;
298 su->fb_name = pdata->su1_fb;
299 su->type = AS3711_BL_SU1;
300
301 max_brightness = min(pdata->su1_max_uA, 31);
302 ret = as3711_bl_register(pdev, max_brightness, su);
303 if (ret < 0)
304 return ret;
305 }
306
307 if (pdata->su2_fb) {
308 su = &supply->su2;
309 su->fb_name = pdata->su2_fb;
310 su->type = AS3711_BL_SU2;
311
312 switch (pdata->su2_fbprot) {
313 case AS3711_SU2_GPIO2:
314 case AS3711_SU2_GPIO3:
315 case AS3711_SU2_GPIO4:
316 case AS3711_SU2_LX_SD4:
317 break;
318 default:
319 ret = -EINVAL;
320 goto esu2;
321 }
322
323 switch (pdata->su2_feedback) {
324 case AS3711_SU2_VOLTAGE:
325 max_brightness = min(pdata->su2_max_uA, 31);
326 break;
327 case AS3711_SU2_CURR1:
328 case AS3711_SU2_CURR2:
329 case AS3711_SU2_CURR3:
330 case AS3711_SU2_CURR_AUTO:
331 max_brightness = min(pdata->su2_max_uA / 150, 255);
332 break;
333 default:
334 ret = -EINVAL;
335 goto esu2;
336 }
337
338 ret = as3711_bl_init_su2(supply);
339 if (ret < 0)
340 return ret;
341
342 ret = as3711_bl_register(pdev, max_brightness, su);
343 if (ret < 0)
344 goto esu2;
345 }
346
347 platform_set_drvdata(pdev, supply);
348
349 return 0;
350
351esu2:
352 backlight_device_unregister(supply->su1.bl);
353 return ret;
354}
355
356static int as3711_backlight_remove(struct platform_device *pdev)
357{
358 struct as3711_bl_supply *supply = platform_get_drvdata(pdev);
359
360 backlight_device_unregister(supply->su1.bl);
361 backlight_device_unregister(supply->su2.bl);
362
363 return 0;
364}
365
366static struct platform_driver as3711_backlight_driver = {
367 .driver = {
368 .name = "as3711-backlight",
369 .owner = THIS_MODULE,
370 },
371 .probe = as3711_backlight_probe,
372 .remove = as3711_backlight_remove,
373};
374
375module_platform_driver(as3711_backlight_driver);
376
377MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
378MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
379MODULE_LICENSE("GPL");
380MODULE_ALIAS("platform:as3711-backlight");