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