diff options
Diffstat (limited to 'drivers/video/backlight/tps65217_bl.c')
-rw-r--r-- | drivers/video/backlight/tps65217_bl.c | 352 |
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 | |||
28 | struct tps65217_bl { | ||
29 | struct tps65217 *tps; | ||
30 | struct device *dev; | ||
31 | struct backlight_device *bl; | ||
32 | bool is_enabled; | ||
33 | }; | ||
34 | |||
35 | static 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 | |||
55 | static 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 | |||
76 | static 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 | |||
112 | static int tps65217_bl_get_brightness(struct backlight_device *bl) | ||
113 | { | ||
114 | return bl->props.brightness; | ||
115 | } | ||
116 | |||
117 | static 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 | |||
123 | static 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 | ||
189 | static struct tps65217_bl_pdata * | ||
190 | tps65217_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 | |||
252 | err: | ||
253 | of_node_put(node); | ||
254 | |||
255 | return err; | ||
256 | } | ||
257 | #else | ||
258 | static struct tps65217_bl_pdata * | ||
259 | tps65217_bl_parse_dt(struct platform_device *pdev) | ||
260 | { | ||
261 | return NULL; | ||
262 | } | ||
263 | #endif | ||
264 | |||
265 | static 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 | |||
319 | static 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 | |||
328 | static 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 | |||
337 | static int __init tps65217_bl_init(void) | ||
338 | { | ||
339 | return platform_driver_register(&tps65217_bl_driver); | ||
340 | } | ||
341 | |||
342 | static void __exit tps65217_bl_exit(void) | ||
343 | { | ||
344 | platform_driver_unregister(&tps65217_bl_driver); | ||
345 | } | ||
346 | |||
347 | module_init(tps65217_bl_init); | ||
348 | module_exit(tps65217_bl_exit); | ||
349 | |||
350 | MODULE_DESCRIPTION("TPS65217 Backlight driver"); | ||
351 | MODULE_LICENSE("GPL v2"); | ||
352 | MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>"); | ||