Commit | Line | Data |
---|---|---|
eebfdc17 MK |
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 | ||
eebfdc17 MK |
112 | static const struct backlight_ops tps65217_bl_ops = { |
113 | .options = BL_CORE_SUSPENDRESUME, | |
114 | .update_status = tps65217_bl_update_status, | |
eebfdc17 MK |
115 | }; |
116 | ||
117 | static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl, | |
118 | struct tps65217_bl_pdata *pdata) | |
119 | { | |
120 | int rc; | |
121 | ||
122 | rc = tps65217_bl_disable(tps65217_bl); | |
123 | if (rc) | |
124 | return rc; | |
125 | ||
126 | switch (pdata->isel) { | |
127 | case TPS65217_BL_ISET1: | |
128 | /* select ISET_1 current level */ | |
129 | rc = tps65217_clear_bits(tps65217_bl->tps, | |
130 | TPS65217_REG_WLEDCTRL1, | |
131 | TPS65217_WLEDCTRL1_ISEL, | |
132 | TPS65217_PROTECT_NONE); | |
133 | if (rc) { | |
134 | dev_err(tps65217_bl->dev, | |
135 | "failed to select ISET1 current level: %d)\n", | |
136 | rc); | |
137 | return rc; | |
138 | } | |
139 | ||
140 | dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n"); | |
141 | ||
142 | break; | |
143 | ||
144 | case TPS65217_BL_ISET2: | |
145 | /* select ISET2 current level */ | |
146 | rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1, | |
147 | TPS65217_WLEDCTRL1_ISEL, | |
148 | TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE); | |
149 | if (rc) { | |
150 | dev_err(tps65217_bl->dev, | |
151 | "failed to select ISET2 current level: %d\n", | |
152 | rc); | |
153 | return rc; | |
154 | } | |
155 | ||
156 | dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n"); | |
157 | ||
158 | break; | |
159 | ||
160 | default: | |
161 | dev_err(tps65217_bl->dev, | |
162 | "invalid value for current level: %d\n", pdata->isel); | |
163 | return -EINVAL; | |
164 | } | |
165 | ||
166 | /* set PWM frequency */ | |
167 | rc = tps65217_set_bits(tps65217_bl->tps, | |
168 | TPS65217_REG_WLEDCTRL1, | |
169 | TPS65217_WLEDCTRL1_FDIM_MASK, | |
170 | pdata->fdim, | |
171 | TPS65217_PROTECT_NONE); | |
172 | if (rc) { | |
173 | dev_err(tps65217_bl->dev, | |
174 | "failed to select PWM dimming frequency: %d\n", | |
175 | rc); | |
176 | return rc; | |
177 | } | |
178 | ||
179 | return 0; | |
180 | } | |
181 | ||
182 | #ifdef CONFIG_OF | |
183 | static struct tps65217_bl_pdata * | |
184 | tps65217_bl_parse_dt(struct platform_device *pdev) | |
185 | { | |
186 | struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); | |
187 | struct device_node *node = of_node_get(tps->dev->of_node); | |
188 | struct tps65217_bl_pdata *pdata, *err; | |
189 | u32 val; | |
190 | ||
191 | node = of_find_node_by_name(node, "backlight"); | |
192 | if (!node) | |
193 | return ERR_PTR(-ENODEV); | |
194 | ||
195 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | |
196 | if (!pdata) { | |
eebfdc17 MK |
197 | err = ERR_PTR(-ENOMEM); |
198 | goto err; | |
199 | } | |
200 | ||
201 | pdata->isel = TPS65217_BL_ISET1; | |
202 | if (!of_property_read_u32(node, "isel", &val)) { | |
203 | if (val < TPS65217_BL_ISET1 || | |
204 | val > TPS65217_BL_ISET2) { | |
205 | dev_err(&pdev->dev, | |
206 | "invalid 'isel' value in the device tree\n"); | |
207 | err = ERR_PTR(-EINVAL); | |
208 | goto err; | |
209 | } | |
210 | ||
211 | pdata->isel = val; | |
212 | } | |
213 | ||
214 | pdata->fdim = TPS65217_BL_FDIM_200HZ; | |
215 | if (!of_property_read_u32(node, "fdim", &val)) { | |
216 | switch (val) { | |
217 | case 100: | |
218 | pdata->fdim = TPS65217_BL_FDIM_100HZ; | |
219 | break; | |
220 | ||
221 | case 200: | |
222 | pdata->fdim = TPS65217_BL_FDIM_200HZ; | |
223 | break; | |
224 | ||
225 | case 500: | |
226 | pdata->fdim = TPS65217_BL_FDIM_500HZ; | |
227 | break; | |
228 | ||
229 | case 1000: | |
230 | pdata->fdim = TPS65217_BL_FDIM_1000HZ; | |
231 | break; | |
232 | ||
233 | default: | |
234 | dev_err(&pdev->dev, | |
235 | "invalid 'fdim' value in the device tree\n"); | |
236 | err = ERR_PTR(-EINVAL); | |
237 | goto err; | |
238 | } | |
239 | } | |
240 | ||
4d22f8c3 MU |
241 | if (!of_property_read_u32(node, "default-brightness", &val)) { |
242 | if (val < 0 || | |
243 | val > 100) { | |
244 | dev_err(&pdev->dev, | |
245 | "invalid 'default-brightness' value in the device tree\n"); | |
246 | err = ERR_PTR(-EINVAL); | |
247 | goto err; | |
248 | } | |
249 | ||
250 | pdata->dft_brightness = val; | |
251 | } | |
252 | ||
eebfdc17 MK |
253 | of_node_put(node); |
254 | ||
255 | return pdata; | |
256 | ||
257 | err: | |
258 | of_node_put(node); | |
259 | ||
260 | return err; | |
261 | } | |
262 | #else | |
263 | static struct tps65217_bl_pdata * | |
264 | tps65217_bl_parse_dt(struct platform_device *pdev) | |
265 | { | |
266 | return NULL; | |
267 | } | |
268 | #endif | |
269 | ||
270 | static int tps65217_bl_probe(struct platform_device *pdev) | |
271 | { | |
272 | int rc; | |
273 | struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); | |
274 | struct tps65217_bl *tps65217_bl; | |
275 | struct tps65217_bl_pdata *pdata; | |
276 | struct backlight_properties bl_props; | |
277 | ||
278 | if (tps->dev->of_node) { | |
279 | pdata = tps65217_bl_parse_dt(pdev); | |
280 | if (IS_ERR(pdata)) | |
281 | return PTR_ERR(pdata); | |
282 | } else { | |
c512794c JH |
283 | pdata = dev_get_platdata(&pdev->dev); |
284 | if (!pdata) { | |
eebfdc17 MK |
285 | dev_err(&pdev->dev, "no platform data provided\n"); |
286 | return -EINVAL; | |
287 | } | |
eebfdc17 MK |
288 | } |
289 | ||
290 | tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl), | |
291 | GFP_KERNEL); | |
3d8e4b40 | 292 | if (tps65217_bl == NULL) |
eebfdc17 | 293 | return -ENOMEM; |
eebfdc17 MK |
294 | |
295 | tps65217_bl->tps = tps; | |
296 | tps65217_bl->dev = &pdev->dev; | |
297 | tps65217_bl->is_enabled = false; | |
298 | ||
299 | rc = tps65217_bl_hw_init(tps65217_bl, pdata); | |
300 | if (rc) | |
301 | return rc; | |
302 | ||
303 | memset(&bl_props, 0, sizeof(struct backlight_properties)); | |
304 | bl_props.type = BACKLIGHT_RAW; | |
305 | bl_props.max_brightness = 100; | |
306 | ||
626d0908 | 307 | tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name, |
eebfdc17 MK |
308 | tps65217_bl->dev, tps65217_bl, |
309 | &tps65217_bl_ops, &bl_props); | |
310 | if (IS_ERR(tps65217_bl->bl)) { | |
311 | dev_err(tps65217_bl->dev, | |
312 | "registration of backlight device failed: %d\n", rc); | |
313 | return PTR_ERR(tps65217_bl->bl); | |
314 | } | |
315 | ||
4d22f8c3 MU |
316 | tps65217_bl->bl->props.brightness = pdata->dft_brightness; |
317 | backlight_update_status(tps65217_bl->bl); | |
c6bed9de | 318 | platform_set_drvdata(pdev, tps65217_bl); |
eebfdc17 MK |
319 | |
320 | return 0; | |
321 | } | |
322 | ||
fcf13f0b EBS |
323 | #ifdef CONFIG_OF |
324 | static const struct of_device_id tps65217_bl_of_match[] = { | |
325 | { .compatible = "ti,tps65217-bl", }, | |
326 | { /* sentinel */ }, | |
327 | }; | |
328 | MODULE_DEVICE_TABLE(of, tps65217_bl_of_match); | |
329 | #endif | |
330 | ||
eebfdc17 MK |
331 | static struct platform_driver tps65217_bl_driver = { |
332 | .probe = tps65217_bl_probe, | |
eebfdc17 | 333 | .driver = { |
eebfdc17 | 334 | .name = "tps65217-bl", |
fcf13f0b | 335 | .of_match_table = of_match_ptr(tps65217_bl_of_match), |
eebfdc17 MK |
336 | }, |
337 | }; | |
338 | ||
c6bed9de | 339 | module_platform_driver(tps65217_bl_driver); |
eebfdc17 MK |
340 | |
341 | MODULE_DESCRIPTION("TPS65217 Backlight driver"); | |
342 | MODULE_LICENSE("GPL v2"); | |
343 | MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>"); |