diff options
-rw-r--r-- | drivers/video/backlight/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/as3711_bl.c | 380 |
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 | ||
408 | config 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 | |||
408 | endif # BACKLIGHT_CLASS_DEVICE | 415 | endif # BACKLIGHT_CLASS_DEVICE |
409 | 416 | ||
410 | endif # BACKLIGHT_LCD_SUPPORT | 417 | endif # 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 | |||
23 | obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o | 23 | obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o |
24 | obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o | 24 | obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o |
25 | obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o | 25 | obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o |
26 | obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o | ||
26 | obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o | 27 | obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o |
27 | obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o | 28 | obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o |
28 | obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o | 29 | obj-$(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 | |||
24 | enum as3711_bl_type { | ||
25 | AS3711_BL_SU1, | ||
26 | AS3711_BL_SU2, | ||
27 | }; | ||
28 | |||
29 | struct 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 | |||
38 | struct 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 | |||
45 | static 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 | |||
56 | static 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 | |||
78 | static 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 | |||
89 | static 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 | */ | ||
107 | static 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 | |||
173 | static 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 | |||
180 | static const struct backlight_ops as3711_bl_ops = { | ||
181 | .update_status = as3711_bl_update_status, | ||
182 | .get_brightness = as3711_bl_get_brightness, | ||
183 | }; | ||
184 | |||
185 | static 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 | |||
234 | static 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 | |||
261 | static 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 | |||
351 | esu2: | ||
352 | backlight_device_unregister(supply->su1.bl); | ||
353 | return ret; | ||
354 | } | ||
355 | |||
356 | static 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 | |||
366 | static 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 | |||
375 | module_platform_driver(as3711_backlight_driver); | ||
376 | |||
377 | MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); | ||
378 | MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); | ||
379 | MODULE_LICENSE("GPL"); | ||
380 | MODULE_ALIAS("platform:as3711-backlight"); | ||