aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/backlight/tps65217_bl.c
diff options
context:
space:
mode:
authorMatthias Kaehlcke <matthias@kaehlcke.net>2012-09-24 16:25:28 -0400
committerSamuel Ortiz <sameo@linux.intel.com>2012-09-25 04:42:02 -0400
commiteebfdc17cc6c9f184a713d84b84e7602236360c6 (patch)
tree6f591d7a70cb540db68520b5c91361b1cb604bc5 /drivers/video/backlight/tps65217_bl.c
parentb6290ffe1f4ed4d8521fd7e46738d42ddd9f1935 (diff)
backlight: Add TPS65217 WLED driver
The TPS65217 chip contains a boost converter and current sinks which can be used to drive LEDs for use as backlights. Expose this functionality via the backlight API. Tested on an AM335x based custom board with a single WLED string, using different values for ISEL and FDIM (though it would be hard to tell the difference except for the value in WLEDCTRL1). Both instantiation through the device tree and by passing platform data have been tested. Testing has been done with an Androidized 3.2 kernel from the rowboat project. Koen Kooi reported the driver to be working on a Beaglebone board with LCD3 cape Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/video/backlight/tps65217_bl.c')
-rw-r--r--drivers/video/backlight/tps65217_bl.c352
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/video/backlight/tps65217_bl.c b/drivers/video/backlight/tps65217_bl.c
new file mode 100644
index 000000000000..6ac2ef5da32c
--- /dev/null
+++ b/drivers/video/backlight/tps65217_bl.c
@@ -0,0 +1,352 @@
1/*
2 * tps65217_bl.c
3 *
4 * TPS65217 backlight driver
5 *
6 * Copyright (C) 2012 Matthias Kaehlcke
7 * Author: Matthias Kaehlcke <matthias@kaehlcke.net>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation version 2.
12 *
13 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
14 * kind, whether express or implied; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 */
18
19#include <linux/kernel.h>
20#include <linux/backlight.h>
21#include <linux/err.h>
22#include <linux/fb.h>
23#include <linux/mfd/tps65217.h>
24#include <linux/module.h>
25#include <linux/platform_device.h>
26#include <linux/slab.h>
27
28struct tps65217_bl {
29 struct tps65217 *tps;
30 struct device *dev;
31 struct backlight_device *bl;
32 bool is_enabled;
33};
34
35static int tps65217_bl_enable(struct tps65217_bl *tps65217_bl)
36{
37 int rc;
38
39 rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
40 TPS65217_WLEDCTRL1_ISINK_ENABLE,
41 TPS65217_WLEDCTRL1_ISINK_ENABLE, TPS65217_PROTECT_NONE);
42 if (rc) {
43 dev_err(tps65217_bl->dev,
44 "failed to enable backlight: %d\n", rc);
45 return rc;
46 }
47
48 tps65217_bl->is_enabled = true;
49
50 dev_dbg(tps65217_bl->dev, "backlight enabled\n");
51
52 return 0;
53}
54
55static int tps65217_bl_disable(struct tps65217_bl *tps65217_bl)
56{
57 int rc;
58
59 rc = tps65217_clear_bits(tps65217_bl->tps,
60 TPS65217_REG_WLEDCTRL1,
61 TPS65217_WLEDCTRL1_ISINK_ENABLE,
62 TPS65217_PROTECT_NONE);
63 if (rc) {
64 dev_err(tps65217_bl->dev,
65 "failed to disable backlight: %d\n", rc);
66 return rc;
67 }
68
69 tps65217_bl->is_enabled = false;
70
71 dev_dbg(tps65217_bl->dev, "backlight disabled\n");
72
73 return 0;
74}
75
76static int tps65217_bl_update_status(struct backlight_device *bl)
77{
78 struct tps65217_bl *tps65217_bl = bl_get_data(bl);
79 int rc;
80 int brightness = bl->props.brightness;
81
82 if (bl->props.state & BL_CORE_SUSPENDED)
83 brightness = 0;
84
85 if ((bl->props.power != FB_BLANK_UNBLANK) ||
86 (bl->props.fb_blank != FB_BLANK_UNBLANK))
87 /* framebuffer in low power mode or blanking active */
88 brightness = 0;
89
90 if (brightness > 0) {
91 rc = tps65217_reg_write(tps65217_bl->tps,
92 TPS65217_REG_WLEDCTRL2,
93 brightness - 1,
94 TPS65217_PROTECT_NONE);
95 if (rc) {
96 dev_err(tps65217_bl->dev,
97 "failed to set brightness level: %d\n", rc);
98 return rc;
99 }
100
101 dev_dbg(tps65217_bl->dev, "brightness set to %d\n", brightness);
102
103 if (!tps65217_bl->is_enabled)
104 rc = tps65217_bl_enable(tps65217_bl);
105 } else {
106 rc = tps65217_bl_disable(tps65217_bl);
107 }
108
109 return rc;
110}
111
112static int tps65217_bl_get_brightness(struct backlight_device *bl)
113{
114 return bl->props.brightness;
115}
116
117static const struct backlight_ops tps65217_bl_ops = {
118 .options = BL_CORE_SUSPENDRESUME,
119 .update_status = tps65217_bl_update_status,
120 .get_brightness = tps65217_bl_get_brightness
121};
122
123static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl,
124 struct tps65217_bl_pdata *pdata)
125{
126 int rc;
127
128 rc = tps65217_bl_disable(tps65217_bl);
129 if (rc)
130 return rc;
131
132 switch (pdata->isel) {
133 case TPS65217_BL_ISET1:
134 /* select ISET_1 current level */
135 rc = tps65217_clear_bits(tps65217_bl->tps,
136 TPS65217_REG_WLEDCTRL1,
137 TPS65217_WLEDCTRL1_ISEL,
138 TPS65217_PROTECT_NONE);
139 if (rc) {
140 dev_err(tps65217_bl->dev,
141 "failed to select ISET1 current level: %d)\n",
142 rc);
143 return rc;
144 }
145
146 dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n");
147
148 break;
149
150 case TPS65217_BL_ISET2:
151 /* select ISET2 current level */
152 rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
153 TPS65217_WLEDCTRL1_ISEL,
154 TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE);
155 if (rc) {
156 dev_err(tps65217_bl->dev,
157 "failed to select ISET2 current level: %d\n",
158 rc);
159 return rc;
160 }
161
162 dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n");
163
164 break;
165
166 default:
167 dev_err(tps65217_bl->dev,
168 "invalid value for current level: %d\n", pdata->isel);
169 return -EINVAL;
170 }
171
172 /* set PWM frequency */
173 rc = tps65217_set_bits(tps65217_bl->tps,
174 TPS65217_REG_WLEDCTRL1,
175 TPS65217_WLEDCTRL1_FDIM_MASK,
176 pdata->fdim,
177 TPS65217_PROTECT_NONE);
178 if (rc) {
179 dev_err(tps65217_bl->dev,
180 "failed to select PWM dimming frequency: %d\n",
181 rc);
182 return rc;
183 }
184
185 return 0;
186}
187
188#ifdef CONFIG_OF
189static struct tps65217_bl_pdata *
190tps65217_bl_parse_dt(struct platform_device *pdev)
191{
192 struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
193 struct device_node *node = of_node_get(tps->dev->of_node);
194 struct tps65217_bl_pdata *pdata, *err;
195 u32 val;
196
197 node = of_find_node_by_name(node, "backlight");
198 if (!node)
199 return ERR_PTR(-ENODEV);
200
201 pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
202 if (!pdata) {
203 dev_err(&pdev->dev, "failed to allocate platform data\n");
204 err = ERR_PTR(-ENOMEM);
205 goto err;
206 }
207
208 pdata->isel = TPS65217_BL_ISET1;
209 if (!of_property_read_u32(node, "isel", &val)) {
210 if (val < TPS65217_BL_ISET1 ||
211 val > TPS65217_BL_ISET2) {
212 dev_err(&pdev->dev,
213 "invalid 'isel' value in the device tree\n");
214 err = ERR_PTR(-EINVAL);
215 goto err;
216 }
217
218 pdata->isel = val;
219 }
220
221 pdata->fdim = TPS65217_BL_FDIM_200HZ;
222 if (!of_property_read_u32(node, "fdim", &val)) {
223 switch (val) {
224 case 100:
225 pdata->fdim = TPS65217_BL_FDIM_100HZ;
226 break;
227
228 case 200:
229 pdata->fdim = TPS65217_BL_FDIM_200HZ;
230 break;
231
232 case 500:
233 pdata->fdim = TPS65217_BL_FDIM_500HZ;
234 break;
235
236 case 1000:
237 pdata->fdim = TPS65217_BL_FDIM_1000HZ;
238 break;
239
240 default:
241 dev_err(&pdev->dev,
242 "invalid 'fdim' value in the device tree\n");
243 err = ERR_PTR(-EINVAL);
244 goto err;
245 }
246 }
247
248 of_node_put(node);
249
250 return pdata;
251
252err:
253 of_node_put(node);
254
255 return err;
256}
257#else
258static struct tps65217_bl_pdata *
259tps65217_bl_parse_dt(struct platform_device *pdev)
260{
261 return NULL;
262}
263#endif
264
265static int tps65217_bl_probe(struct platform_device *pdev)
266{
267 int rc;
268 struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
269 struct tps65217_bl *tps65217_bl;
270 struct tps65217_bl_pdata *pdata;
271 struct backlight_properties bl_props;
272
273 if (tps->dev->of_node) {
274 pdata = tps65217_bl_parse_dt(pdev);
275 if (IS_ERR(pdata))
276 return PTR_ERR(pdata);
277 } else {
278 if (!pdev->dev.platform_data) {
279 dev_err(&pdev->dev, "no platform data provided\n");
280 return -EINVAL;
281 }
282
283 pdata = pdev->dev.platform_data;
284 }
285
286 tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl),
287 GFP_KERNEL);
288 if (tps65217_bl == NULL) {
289 dev_err(&pdev->dev, "allocation of struct tps65217_bl failed\n");
290 return -ENOMEM;
291 }
292
293 tps65217_bl->tps = tps;
294 tps65217_bl->dev = &pdev->dev;
295 tps65217_bl->is_enabled = false;
296
297 rc = tps65217_bl_hw_init(tps65217_bl, pdata);
298 if (rc)
299 return rc;
300
301 memset(&bl_props, 0, sizeof(struct backlight_properties));
302 bl_props.type = BACKLIGHT_RAW;
303 bl_props.max_brightness = 100;
304
305 tps65217_bl->bl = backlight_device_register(pdev->name,
306 tps65217_bl->dev, tps65217_bl,
307 &tps65217_bl_ops, &bl_props);
308 if (IS_ERR(tps65217_bl->bl)) {
309 dev_err(tps65217_bl->dev,
310 "registration of backlight device failed: %d\n", rc);
311 return PTR_ERR(tps65217_bl->bl);
312 }
313
314 tps65217_bl->bl->props.brightness = 0;
315
316 return 0;
317}
318
319static int tps65217_bl_remove(struct platform_device *pdev)
320{
321 struct tps65217_bl *tps65217_bl = platform_get_drvdata(pdev);
322
323 backlight_device_unregister(tps65217_bl->bl);
324
325 return 0;
326}
327
328static struct platform_driver tps65217_bl_driver = {
329 .probe = tps65217_bl_probe,
330 .remove = tps65217_bl_remove,
331 .driver = {
332 .owner = THIS_MODULE,
333 .name = "tps65217-bl",
334 },
335};
336
337static int __init tps65217_bl_init(void)
338{
339 return platform_driver_register(&tps65217_bl_driver);
340}
341
342static void __exit tps65217_bl_exit(void)
343{
344 platform_driver_unregister(&tps65217_bl_driver);
345}
346
347module_init(tps65217_bl_init);
348module_exit(tps65217_bl_exit);
349
350MODULE_DESCRIPTION("TPS65217 Backlight driver");
351MODULE_LICENSE("GPL v2");
352MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>");