diff options
Diffstat (limited to 'drivers/video/backlight/adp5520_bl.c')
-rw-r--r-- | drivers/video/backlight/adp5520_bl.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/drivers/video/backlight/adp5520_bl.c b/drivers/video/backlight/adp5520_bl.c new file mode 100644 index 000000000000..ad05da5ba3c7 --- /dev/null +++ b/drivers/video/backlight/adp5520_bl.c | |||
@@ -0,0 +1,377 @@ | |||
1 | /* | ||
2 | * Backlight driver for Analog Devices ADP5520/ADP5501 MFD PMICs | ||
3 | * | ||
4 | * Copyright 2009 Analog Devices Inc. | ||
5 | * | ||
6 | * Licensed under the GPL-2 or later. | ||
7 | */ | ||
8 | |||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/init.h> | ||
11 | #include <linux/platform_device.h> | ||
12 | #include <linux/fb.h> | ||
13 | #include <linux/backlight.h> | ||
14 | #include <linux/mfd/adp5520.h> | ||
15 | |||
16 | struct adp5520_bl { | ||
17 | struct device *master; | ||
18 | struct adp5520_backlight_platfrom_data *pdata; | ||
19 | struct mutex lock; | ||
20 | unsigned long cached_daylight_max; | ||
21 | int id; | ||
22 | int current_brightness; | ||
23 | }; | ||
24 | |||
25 | static int adp5520_bl_set(struct backlight_device *bl, int brightness) | ||
26 | { | ||
27 | struct adp5520_bl *data = bl_get_data(bl); | ||
28 | struct device *master = data->master; | ||
29 | int ret = 0; | ||
30 | |||
31 | if (data->pdata->en_ambl_sens) { | ||
32 | if ((brightness > 0) && (brightness < ADP5020_MAX_BRIGHTNESS)) { | ||
33 | /* Disable Ambient Light auto adjust */ | ||
34 | ret |= adp5520_clr_bits(master, BL_CONTROL, | ||
35 | BL_AUTO_ADJ); | ||
36 | ret |= adp5520_write(master, DAYLIGHT_MAX, brightness); | ||
37 | } else { | ||
38 | /* | ||
39 | * MAX_BRIGHTNESS -> Enable Ambient Light auto adjust | ||
40 | * restore daylight l3 sysfs brightness | ||
41 | */ | ||
42 | ret |= adp5520_write(master, DAYLIGHT_MAX, | ||
43 | data->cached_daylight_max); | ||
44 | ret |= adp5520_set_bits(master, BL_CONTROL, | ||
45 | BL_AUTO_ADJ); | ||
46 | } | ||
47 | } else { | ||
48 | ret |= adp5520_write(master, DAYLIGHT_MAX, brightness); | ||
49 | } | ||
50 | |||
51 | if (data->current_brightness && brightness == 0) | ||
52 | ret |= adp5520_set_bits(master, | ||
53 | MODE_STATUS, DIM_EN); | ||
54 | else if (data->current_brightness == 0 && brightness) | ||
55 | ret |= adp5520_clr_bits(master, | ||
56 | MODE_STATUS, DIM_EN); | ||
57 | |||
58 | if (!ret) | ||
59 | data->current_brightness = brightness; | ||
60 | |||
61 | return ret; | ||
62 | } | ||
63 | |||
64 | static int adp5520_bl_update_status(struct backlight_device *bl) | ||
65 | { | ||
66 | int brightness = bl->props.brightness; | ||
67 | if (bl->props.power != FB_BLANK_UNBLANK) | ||
68 | brightness = 0; | ||
69 | |||
70 | if (bl->props.fb_blank != FB_BLANK_UNBLANK) | ||
71 | brightness = 0; | ||
72 | |||
73 | return adp5520_bl_set(bl, brightness); | ||
74 | } | ||
75 | |||
76 | static int adp5520_bl_get_brightness(struct backlight_device *bl) | ||
77 | { | ||
78 | struct adp5520_bl *data = bl_get_data(bl); | ||
79 | int error; | ||
80 | uint8_t reg_val; | ||
81 | |||
82 | error = adp5520_read(data->master, BL_VALUE, ®_val); | ||
83 | |||
84 | return error ? data->current_brightness : reg_val; | ||
85 | } | ||
86 | |||
87 | static struct backlight_ops adp5520_bl_ops = { | ||
88 | .update_status = adp5520_bl_update_status, | ||
89 | .get_brightness = adp5520_bl_get_brightness, | ||
90 | }; | ||
91 | |||
92 | static int adp5520_bl_setup(struct backlight_device *bl) | ||
93 | { | ||
94 | struct adp5520_bl *data = bl_get_data(bl); | ||
95 | struct device *master = data->master; | ||
96 | struct adp5520_backlight_platfrom_data *pdata = data->pdata; | ||
97 | int ret = 0; | ||
98 | |||
99 | ret |= adp5520_write(master, DAYLIGHT_MAX, pdata->l1_daylight_max); | ||
100 | ret |= adp5520_write(master, DAYLIGHT_DIM, pdata->l1_daylight_dim); | ||
101 | |||
102 | if (pdata->en_ambl_sens) { | ||
103 | data->cached_daylight_max = pdata->l1_daylight_max; | ||
104 | ret |= adp5520_write(master, OFFICE_MAX, pdata->l2_office_max); | ||
105 | ret |= adp5520_write(master, OFFICE_DIM, pdata->l2_office_dim); | ||
106 | ret |= adp5520_write(master, DARK_MAX, pdata->l3_dark_max); | ||
107 | ret |= adp5520_write(master, DARK_DIM, pdata->l3_dark_dim); | ||
108 | ret |= adp5520_write(master, L2_TRIP, pdata->l2_trip); | ||
109 | ret |= adp5520_write(master, L2_HYS, pdata->l2_hyst); | ||
110 | ret |= adp5520_write(master, L3_TRIP, pdata->l3_trip); | ||
111 | ret |= adp5520_write(master, L3_HYS, pdata->l3_hyst); | ||
112 | ret |= adp5520_write(master, ALS_CMPR_CFG, | ||
113 | ALS_CMPR_CFG_VAL(pdata->abml_filt, L3_EN)); | ||
114 | } | ||
115 | |||
116 | ret |= adp5520_write(master, BL_CONTROL, | ||
117 | BL_CTRL_VAL(pdata->fade_led_law, pdata->en_ambl_sens)); | ||
118 | |||
119 | ret |= adp5520_write(master, BL_FADE, FADE_VAL(pdata->fade_in, | ||
120 | pdata->fade_out)); | ||
121 | |||
122 | ret |= adp5520_set_bits(master, MODE_STATUS, BL_EN | DIM_EN); | ||
123 | |||
124 | return ret; | ||
125 | } | ||
126 | |||
127 | static ssize_t adp5520_show(struct device *dev, char *buf, int reg) | ||
128 | { | ||
129 | struct adp5520_bl *data = dev_get_drvdata(dev); | ||
130 | int error; | ||
131 | uint8_t reg_val; | ||
132 | |||
133 | mutex_lock(&data->lock); | ||
134 | error = adp5520_read(data->master, reg, ®_val); | ||
135 | mutex_unlock(&data->lock); | ||
136 | |||
137 | return sprintf(buf, "%u\n", reg_val); | ||
138 | } | ||
139 | |||
140 | static ssize_t adp5520_store(struct device *dev, const char *buf, | ||
141 | size_t count, int reg) | ||
142 | { | ||
143 | struct adp5520_bl *data = dev_get_drvdata(dev); | ||
144 | unsigned long val; | ||
145 | int ret; | ||
146 | |||
147 | ret = strict_strtoul(buf, 10, &val); | ||
148 | if (ret) | ||
149 | return ret; | ||
150 | |||
151 | mutex_lock(&data->lock); | ||
152 | adp5520_write(data->master, reg, val); | ||
153 | mutex_unlock(&data->lock); | ||
154 | |||
155 | return count; | ||
156 | } | ||
157 | |||
158 | static ssize_t adp5520_bl_dark_max_show(struct device *dev, | ||
159 | struct device_attribute *attr, char *buf) | ||
160 | { | ||
161 | return adp5520_show(dev, buf, DARK_MAX); | ||
162 | } | ||
163 | |||
164 | static ssize_t adp5520_bl_dark_max_store(struct device *dev, | ||
165 | struct device_attribute *attr, const char *buf, size_t count) | ||
166 | { | ||
167 | return adp5520_store(dev, buf, count, DARK_MAX); | ||
168 | } | ||
169 | static DEVICE_ATTR(dark_max, 0664, adp5520_bl_dark_max_show, | ||
170 | adp5520_bl_dark_max_store); | ||
171 | |||
172 | static ssize_t adp5520_bl_office_max_show(struct device *dev, | ||
173 | struct device_attribute *attr, char *buf) | ||
174 | { | ||
175 | return adp5520_show(dev, buf, OFFICE_MAX); | ||
176 | } | ||
177 | |||
178 | static ssize_t adp5520_bl_office_max_store(struct device *dev, | ||
179 | struct device_attribute *attr, const char *buf, size_t count) | ||
180 | { | ||
181 | return adp5520_store(dev, buf, count, OFFICE_MAX); | ||
182 | } | ||
183 | static DEVICE_ATTR(office_max, 0664, adp5520_bl_office_max_show, | ||
184 | adp5520_bl_office_max_store); | ||
185 | |||
186 | static ssize_t adp5520_bl_daylight_max_show(struct device *dev, | ||
187 | struct device_attribute *attr, char *buf) | ||
188 | { | ||
189 | return adp5520_show(dev, buf, DAYLIGHT_MAX); | ||
190 | } | ||
191 | |||
192 | static ssize_t adp5520_bl_daylight_max_store(struct device *dev, | ||
193 | struct device_attribute *attr, const char *buf, size_t count) | ||
194 | { | ||
195 | struct adp5520_bl *data = dev_get_drvdata(dev); | ||
196 | |||
197 | strict_strtoul(buf, 10, &data->cached_daylight_max); | ||
198 | return adp5520_store(dev, buf, count, DAYLIGHT_MAX); | ||
199 | } | ||
200 | static DEVICE_ATTR(daylight_max, 0664, adp5520_bl_daylight_max_show, | ||
201 | adp5520_bl_daylight_max_store); | ||
202 | |||
203 | static ssize_t adp5520_bl_dark_dim_show(struct device *dev, | ||
204 | struct device_attribute *attr, char *buf) | ||
205 | { | ||
206 | return adp5520_show(dev, buf, DARK_DIM); | ||
207 | } | ||
208 | |||
209 | static ssize_t adp5520_bl_dark_dim_store(struct device *dev, | ||
210 | struct device_attribute *attr, | ||
211 | const char *buf, size_t count) | ||
212 | { | ||
213 | return adp5520_store(dev, buf, count, DARK_DIM); | ||
214 | } | ||
215 | static DEVICE_ATTR(dark_dim, 0664, adp5520_bl_dark_dim_show, | ||
216 | adp5520_bl_dark_dim_store); | ||
217 | |||
218 | static ssize_t adp5520_bl_office_dim_show(struct device *dev, | ||
219 | struct device_attribute *attr, char *buf) | ||
220 | { | ||
221 | return adp5520_show(dev, buf, OFFICE_DIM); | ||
222 | } | ||
223 | |||
224 | static ssize_t adp5520_bl_office_dim_store(struct device *dev, | ||
225 | struct device_attribute *attr, | ||
226 | const char *buf, size_t count) | ||
227 | { | ||
228 | return adp5520_store(dev, buf, count, OFFICE_DIM); | ||
229 | } | ||
230 | static DEVICE_ATTR(office_dim, 0664, adp5520_bl_office_dim_show, | ||
231 | adp5520_bl_office_dim_store); | ||
232 | |||
233 | static ssize_t adp5520_bl_daylight_dim_show(struct device *dev, | ||
234 | struct device_attribute *attr, char *buf) | ||
235 | { | ||
236 | return adp5520_show(dev, buf, DAYLIGHT_DIM); | ||
237 | } | ||
238 | |||
239 | static ssize_t adp5520_bl_daylight_dim_store(struct device *dev, | ||
240 | struct device_attribute *attr, | ||
241 | const char *buf, size_t count) | ||
242 | { | ||
243 | return adp5520_store(dev, buf, count, DAYLIGHT_DIM); | ||
244 | } | ||
245 | static DEVICE_ATTR(daylight_dim, 0664, adp5520_bl_daylight_dim_show, | ||
246 | adp5520_bl_daylight_dim_store); | ||
247 | |||
248 | static struct attribute *adp5520_bl_attributes[] = { | ||
249 | &dev_attr_dark_max.attr, | ||
250 | &dev_attr_dark_dim.attr, | ||
251 | &dev_attr_office_max.attr, | ||
252 | &dev_attr_office_dim.attr, | ||
253 | &dev_attr_daylight_max.attr, | ||
254 | &dev_attr_daylight_dim.attr, | ||
255 | NULL | ||
256 | }; | ||
257 | |||
258 | static const struct attribute_group adp5520_bl_attr_group = { | ||
259 | .attrs = adp5520_bl_attributes, | ||
260 | }; | ||
261 | |||
262 | static int __devinit adp5520_bl_probe(struct platform_device *pdev) | ||
263 | { | ||
264 | struct backlight_device *bl; | ||
265 | struct adp5520_bl *data; | ||
266 | int ret = 0; | ||
267 | |||
268 | data = kzalloc(sizeof(*data), GFP_KERNEL); | ||
269 | if (data == NULL) | ||
270 | return -ENOMEM; | ||
271 | |||
272 | data->master = pdev->dev.parent; | ||
273 | data->pdata = pdev->dev.platform_data; | ||
274 | |||
275 | if (data->pdata == NULL) { | ||
276 | dev_err(&pdev->dev, "missing platform data\n"); | ||
277 | kfree(data); | ||
278 | return -ENODEV; | ||
279 | } | ||
280 | |||
281 | data->id = pdev->id; | ||
282 | data->current_brightness = 0; | ||
283 | |||
284 | mutex_init(&data->lock); | ||
285 | |||
286 | bl = backlight_device_register(pdev->name, data->master, | ||
287 | data, &adp5520_bl_ops); | ||
288 | if (IS_ERR(bl)) { | ||
289 | dev_err(&pdev->dev, "failed to register backlight\n"); | ||
290 | kfree(data); | ||
291 | return PTR_ERR(bl); | ||
292 | } | ||
293 | |||
294 | bl->props.max_brightness = | ||
295 | bl->props.brightness = ADP5020_MAX_BRIGHTNESS; | ||
296 | |||
297 | if (data->pdata->en_ambl_sens) | ||
298 | ret = sysfs_create_group(&bl->dev.kobj, | ||
299 | &adp5520_bl_attr_group); | ||
300 | |||
301 | if (ret) { | ||
302 | dev_err(&pdev->dev, "failed to register sysfs\n"); | ||
303 | backlight_device_unregister(bl); | ||
304 | kfree(data); | ||
305 | } | ||
306 | |||
307 | platform_set_drvdata(pdev, bl); | ||
308 | ret |= adp5520_bl_setup(bl); | ||
309 | backlight_update_status(bl); | ||
310 | |||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | static int __devexit adp5520_bl_remove(struct platform_device *pdev) | ||
315 | { | ||
316 | struct backlight_device *bl = platform_get_drvdata(pdev); | ||
317 | struct adp5520_bl *data = bl_get_data(bl); | ||
318 | |||
319 | adp5520_clr_bits(data->master, MODE_STATUS, BL_EN); | ||
320 | |||
321 | if (data->pdata->en_ambl_sens) | ||
322 | sysfs_remove_group(&bl->dev.kobj, | ||
323 | &adp5520_bl_attr_group); | ||
324 | |||
325 | backlight_device_unregister(bl); | ||
326 | kfree(data); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | #ifdef CONFIG_PM | ||
332 | static int adp5520_bl_suspend(struct platform_device *pdev, | ||
333 | pm_message_t state) | ||
334 | { | ||
335 | struct backlight_device *bl = platform_get_drvdata(pdev); | ||
336 | return adp5520_bl_set(bl, 0); | ||
337 | } | ||
338 | |||
339 | static int adp5520_bl_resume(struct platform_device *pdev) | ||
340 | { | ||
341 | struct backlight_device *bl = platform_get_drvdata(pdev); | ||
342 | |||
343 | backlight_update_status(bl); | ||
344 | return 0; | ||
345 | } | ||
346 | #else | ||
347 | #define adp5520_bl_suspend NULL | ||
348 | #define adp5520_bl_resume NULL | ||
349 | #endif | ||
350 | |||
351 | static struct platform_driver adp5520_bl_driver = { | ||
352 | .driver = { | ||
353 | .name = "adp5520-backlight", | ||
354 | .owner = THIS_MODULE, | ||
355 | }, | ||
356 | .probe = adp5520_bl_probe, | ||
357 | .remove = __devexit_p(adp5520_bl_remove), | ||
358 | .suspend = adp5520_bl_suspend, | ||
359 | .resume = adp5520_bl_resume, | ||
360 | }; | ||
361 | |||
362 | static int __init adp5520_bl_init(void) | ||
363 | { | ||
364 | return platform_driver_register(&adp5520_bl_driver); | ||
365 | } | ||
366 | module_init(adp5520_bl_init); | ||
367 | |||
368 | static void __exit adp5520_bl_exit(void) | ||
369 | { | ||
370 | platform_driver_unregister(&adp5520_bl_driver); | ||
371 | } | ||
372 | module_exit(adp5520_bl_exit); | ||
373 | |||
374 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | ||
375 | MODULE_DESCRIPTION("ADP5520(01) Backlight Driver"); | ||
376 | MODULE_LICENSE("GPL"); | ||
377 | MODULE_ALIAS("platform:adp5520-backlight"); | ||