1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LED Flash class interface |
4 | * |
5 | * Copyright (C) 2015 Samsung Electronics Co., Ltd. |
6 | * Author: Jacek Anaszewski <j.anaszewski@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/init.h> |
11 | #include <linux/led-class-flash.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/module.h> |
14 | #include <linux/slab.h> |
15 | #include "leds.h" |
16 | |
17 | #define has_flash_op(fled_cdev, op) \ |
18 | (fled_cdev && fled_cdev->ops->op) |
19 | |
20 | #define call_flash_op(fled_cdev, op, args...) \ |
21 | ((has_flash_op(fled_cdev, op)) ? \ |
22 | (fled_cdev->ops->op(fled_cdev, args)) : \ |
23 | -EINVAL) |
24 | |
25 | static const char * const led_flash_fault_names[] = { |
26 | "led-over-voltage" , |
27 | "flash-timeout-exceeded" , |
28 | "controller-over-temperature" , |
29 | "controller-short-circuit" , |
30 | "led-power-supply-over-current" , |
31 | "indicator-led-fault" , |
32 | "led-under-voltage" , |
33 | "controller-under-voltage" , |
34 | "led-over-temperature" , |
35 | }; |
36 | |
37 | static ssize_t flash_brightness_store(struct device *dev, |
38 | struct device_attribute *attr, const char *buf, size_t size) |
39 | { |
40 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
41 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
42 | unsigned long state; |
43 | ssize_t ret; |
44 | |
45 | mutex_lock(&led_cdev->led_access); |
46 | |
47 | if (led_sysfs_is_disabled(led_cdev)) { |
48 | ret = -EBUSY; |
49 | goto unlock; |
50 | } |
51 | |
52 | ret = kstrtoul(s: buf, base: 10, res: &state); |
53 | if (ret) |
54 | goto unlock; |
55 | |
56 | ret = led_set_flash_brightness(fled_cdev, brightness: state); |
57 | if (ret < 0) |
58 | goto unlock; |
59 | |
60 | ret = size; |
61 | unlock: |
62 | mutex_unlock(lock: &led_cdev->led_access); |
63 | return ret; |
64 | } |
65 | |
66 | static ssize_t flash_brightness_show(struct device *dev, |
67 | struct device_attribute *attr, char *buf) |
68 | { |
69 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
70 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
71 | |
72 | /* no lock needed for this */ |
73 | led_update_flash_brightness(fled_cdev); |
74 | |
75 | return sprintf(buf, fmt: "%u\n" , fled_cdev->brightness.val); |
76 | } |
77 | static DEVICE_ATTR_RW(flash_brightness); |
78 | |
79 | static ssize_t max_flash_brightness_show(struct device *dev, |
80 | struct device_attribute *attr, char *buf) |
81 | { |
82 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
83 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
84 | |
85 | return sprintf(buf, fmt: "%u\n" , fled_cdev->brightness.max); |
86 | } |
87 | static DEVICE_ATTR_RO(max_flash_brightness); |
88 | |
89 | static ssize_t flash_strobe_store(struct device *dev, |
90 | struct device_attribute *attr, const char *buf, size_t size) |
91 | { |
92 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
93 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
94 | unsigned long state; |
95 | ssize_t ret = -EBUSY; |
96 | |
97 | mutex_lock(&led_cdev->led_access); |
98 | |
99 | if (led_sysfs_is_disabled(led_cdev)) |
100 | goto unlock; |
101 | |
102 | ret = kstrtoul(s: buf, base: 10, res: &state); |
103 | if (ret) |
104 | goto unlock; |
105 | |
106 | if (state > 1) { |
107 | ret = -EINVAL; |
108 | goto unlock; |
109 | } |
110 | |
111 | ret = led_set_flash_strobe(fled_cdev, state); |
112 | if (ret < 0) |
113 | goto unlock; |
114 | ret = size; |
115 | unlock: |
116 | mutex_unlock(lock: &led_cdev->led_access); |
117 | return ret; |
118 | } |
119 | |
120 | static ssize_t flash_strobe_show(struct device *dev, |
121 | struct device_attribute *attr, char *buf) |
122 | { |
123 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
124 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
125 | bool state; |
126 | int ret; |
127 | |
128 | /* no lock needed for this */ |
129 | ret = led_get_flash_strobe(fled_cdev, state: &state); |
130 | if (ret < 0) |
131 | return ret; |
132 | |
133 | return sprintf(buf, fmt: "%u\n" , state); |
134 | } |
135 | static DEVICE_ATTR_RW(flash_strobe); |
136 | |
137 | static ssize_t flash_timeout_store(struct device *dev, |
138 | struct device_attribute *attr, const char *buf, size_t size) |
139 | { |
140 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
141 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
142 | unsigned long flash_timeout; |
143 | ssize_t ret; |
144 | |
145 | mutex_lock(&led_cdev->led_access); |
146 | |
147 | if (led_sysfs_is_disabled(led_cdev)) { |
148 | ret = -EBUSY; |
149 | goto unlock; |
150 | } |
151 | |
152 | ret = kstrtoul(s: buf, base: 10, res: &flash_timeout); |
153 | if (ret) |
154 | goto unlock; |
155 | |
156 | ret = led_set_flash_timeout(fled_cdev, timeout: flash_timeout); |
157 | if (ret < 0) |
158 | goto unlock; |
159 | |
160 | ret = size; |
161 | unlock: |
162 | mutex_unlock(lock: &led_cdev->led_access); |
163 | return ret; |
164 | } |
165 | |
166 | static ssize_t flash_timeout_show(struct device *dev, |
167 | struct device_attribute *attr, char *buf) |
168 | { |
169 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
170 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
171 | |
172 | return sprintf(buf, fmt: "%u\n" , fled_cdev->timeout.val); |
173 | } |
174 | static DEVICE_ATTR_RW(flash_timeout); |
175 | |
176 | static ssize_t max_flash_timeout_show(struct device *dev, |
177 | struct device_attribute *attr, char *buf) |
178 | { |
179 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
180 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
181 | |
182 | return sprintf(buf, fmt: "%u\n" , fled_cdev->timeout.max); |
183 | } |
184 | static DEVICE_ATTR_RO(max_flash_timeout); |
185 | |
186 | static ssize_t flash_fault_show(struct device *dev, |
187 | struct device_attribute *attr, char *buf) |
188 | { |
189 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
190 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
191 | u32 fault, mask = 0x1; |
192 | char *pbuf = buf; |
193 | int i, ret, buf_len; |
194 | |
195 | ret = led_get_flash_fault(fled_cdev, fault: &fault); |
196 | if (ret < 0) |
197 | return -EINVAL; |
198 | |
199 | *buf = '\0'; |
200 | |
201 | for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) { |
202 | if (fault & mask) { |
203 | buf_len = sprintf(buf: pbuf, fmt: "%s " , |
204 | led_flash_fault_names[i]); |
205 | pbuf += buf_len; |
206 | } |
207 | mask <<= 1; |
208 | } |
209 | |
210 | return strlen(strcat(buf, "\n" )); |
211 | } |
212 | static DEVICE_ATTR_RO(flash_fault); |
213 | |
214 | static struct attribute *led_flash_strobe_attrs[] = { |
215 | &dev_attr_flash_strobe.attr, |
216 | NULL, |
217 | }; |
218 | |
219 | static struct attribute *led_flash_timeout_attrs[] = { |
220 | &dev_attr_flash_timeout.attr, |
221 | &dev_attr_max_flash_timeout.attr, |
222 | NULL, |
223 | }; |
224 | |
225 | static struct attribute *led_flash_brightness_attrs[] = { |
226 | &dev_attr_flash_brightness.attr, |
227 | &dev_attr_max_flash_brightness.attr, |
228 | NULL, |
229 | }; |
230 | |
231 | static struct attribute *led_flash_fault_attrs[] = { |
232 | &dev_attr_flash_fault.attr, |
233 | NULL, |
234 | }; |
235 | |
236 | static const struct attribute_group led_flash_strobe_group = { |
237 | .attrs = led_flash_strobe_attrs, |
238 | }; |
239 | |
240 | static const struct attribute_group led_flash_timeout_group = { |
241 | .attrs = led_flash_timeout_attrs, |
242 | }; |
243 | |
244 | static const struct attribute_group led_flash_brightness_group = { |
245 | .attrs = led_flash_brightness_attrs, |
246 | }; |
247 | |
248 | static const struct attribute_group led_flash_fault_group = { |
249 | .attrs = led_flash_fault_attrs, |
250 | }; |
251 | |
252 | static void led_flash_resume(struct led_classdev *led_cdev) |
253 | { |
254 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(lcdev: led_cdev); |
255 | |
256 | call_flash_op(fled_cdev, flash_brightness_set, |
257 | fled_cdev->brightness.val); |
258 | call_flash_op(fled_cdev, timeout_set, fled_cdev->timeout.val); |
259 | } |
260 | |
261 | static void led_flash_init_sysfs_groups(struct led_classdev_flash *fled_cdev) |
262 | { |
263 | struct led_classdev *led_cdev = &fled_cdev->led_cdev; |
264 | const struct led_flash_ops *ops = fled_cdev->ops; |
265 | const struct attribute_group **flash_groups = fled_cdev->sysfs_groups; |
266 | |
267 | int num_sysfs_groups = 0; |
268 | |
269 | flash_groups[num_sysfs_groups++] = &led_flash_strobe_group; |
270 | |
271 | if (ops->flash_brightness_set) |
272 | flash_groups[num_sysfs_groups++] = &led_flash_brightness_group; |
273 | |
274 | if (ops->timeout_set) |
275 | flash_groups[num_sysfs_groups++] = &led_flash_timeout_group; |
276 | |
277 | if (ops->fault_get) |
278 | flash_groups[num_sysfs_groups++] = &led_flash_fault_group; |
279 | |
280 | led_cdev->groups = flash_groups; |
281 | } |
282 | |
283 | int led_classdev_flash_register_ext(struct device *parent, |
284 | struct led_classdev_flash *fled_cdev, |
285 | struct led_init_data *init_data) |
286 | { |
287 | struct led_classdev *led_cdev; |
288 | const struct led_flash_ops *ops; |
289 | int ret; |
290 | |
291 | if (!fled_cdev) |
292 | return -EINVAL; |
293 | |
294 | led_cdev = &fled_cdev->led_cdev; |
295 | |
296 | if (led_cdev->flags & LED_DEV_CAP_FLASH) { |
297 | if (!led_cdev->brightness_set_blocking) |
298 | return -EINVAL; |
299 | |
300 | ops = fled_cdev->ops; |
301 | if (!ops || !ops->strobe_set) |
302 | return -EINVAL; |
303 | |
304 | led_cdev->flash_resume = led_flash_resume; |
305 | |
306 | /* Select the sysfs attributes to be created for the device */ |
307 | led_flash_init_sysfs_groups(fled_cdev); |
308 | } |
309 | |
310 | /* Register led class device */ |
311 | ret = led_classdev_register_ext(parent, led_cdev, init_data); |
312 | if (ret < 0) |
313 | return ret; |
314 | |
315 | return 0; |
316 | } |
317 | EXPORT_SYMBOL_GPL(led_classdev_flash_register_ext); |
318 | |
319 | void led_classdev_flash_unregister(struct led_classdev_flash *fled_cdev) |
320 | { |
321 | if (!fled_cdev) |
322 | return; |
323 | |
324 | led_classdev_unregister(led_cdev: &fled_cdev->led_cdev); |
325 | } |
326 | EXPORT_SYMBOL_GPL(led_classdev_flash_unregister); |
327 | |
328 | static void devm_led_classdev_flash_release(struct device *dev, void *res) |
329 | { |
330 | led_classdev_flash_unregister(*(struct led_classdev_flash **)res); |
331 | } |
332 | |
333 | int devm_led_classdev_flash_register_ext(struct device *parent, |
334 | struct led_classdev_flash *fled_cdev, |
335 | struct led_init_data *init_data) |
336 | { |
337 | struct led_classdev_flash **dr; |
338 | int ret; |
339 | |
340 | dr = devres_alloc(devm_led_classdev_flash_release, sizeof(*dr), |
341 | GFP_KERNEL); |
342 | if (!dr) |
343 | return -ENOMEM; |
344 | |
345 | ret = led_classdev_flash_register_ext(parent, fled_cdev, init_data); |
346 | if (ret) { |
347 | devres_free(res: dr); |
348 | return ret; |
349 | } |
350 | |
351 | *dr = fled_cdev; |
352 | devres_add(dev: parent, res: dr); |
353 | |
354 | return 0; |
355 | } |
356 | EXPORT_SYMBOL_GPL(devm_led_classdev_flash_register_ext); |
357 | |
358 | static int devm_led_classdev_flash_match(struct device *dev, |
359 | void *res, void *data) |
360 | { |
361 | struct led_classdev_flash **p = res; |
362 | |
363 | if (WARN_ON(!p || !*p)) |
364 | return 0; |
365 | |
366 | return *p == data; |
367 | } |
368 | |
369 | void devm_led_classdev_flash_unregister(struct device *dev, |
370 | struct led_classdev_flash *fled_cdev) |
371 | { |
372 | WARN_ON(devres_release(dev, |
373 | devm_led_classdev_flash_release, |
374 | devm_led_classdev_flash_match, fled_cdev)); |
375 | } |
376 | EXPORT_SYMBOL_GPL(devm_led_classdev_flash_unregister); |
377 | |
378 | static void led_clamp_align(struct led_flash_setting *s) |
379 | { |
380 | u32 v, offset; |
381 | |
382 | v = s->val + s->step / 2; |
383 | v = clamp(v, s->min, s->max); |
384 | offset = v - s->min; |
385 | offset = s->step * (offset / s->step); |
386 | s->val = s->min + offset; |
387 | } |
388 | |
389 | int led_set_flash_timeout(struct led_classdev_flash *fled_cdev, u32 timeout) |
390 | { |
391 | struct led_classdev *led_cdev = &fled_cdev->led_cdev; |
392 | struct led_flash_setting *s = &fled_cdev->timeout; |
393 | |
394 | s->val = timeout; |
395 | led_clamp_align(s); |
396 | |
397 | if (!(led_cdev->flags & LED_SUSPENDED)) |
398 | return call_flash_op(fled_cdev, timeout_set, s->val); |
399 | |
400 | return 0; |
401 | } |
402 | EXPORT_SYMBOL_GPL(led_set_flash_timeout); |
403 | |
404 | int led_get_flash_fault(struct led_classdev_flash *fled_cdev, u32 *fault) |
405 | { |
406 | return call_flash_op(fled_cdev, fault_get, fault); |
407 | } |
408 | EXPORT_SYMBOL_GPL(led_get_flash_fault); |
409 | |
410 | int led_set_flash_brightness(struct led_classdev_flash *fled_cdev, |
411 | u32 brightness) |
412 | { |
413 | struct led_classdev *led_cdev = &fled_cdev->led_cdev; |
414 | struct led_flash_setting *s = &fled_cdev->brightness; |
415 | |
416 | s->val = brightness; |
417 | led_clamp_align(s); |
418 | |
419 | if (!(led_cdev->flags & LED_SUSPENDED)) |
420 | return call_flash_op(fled_cdev, flash_brightness_set, s->val); |
421 | |
422 | return 0; |
423 | } |
424 | EXPORT_SYMBOL_GPL(led_set_flash_brightness); |
425 | |
426 | int led_update_flash_brightness(struct led_classdev_flash *fled_cdev) |
427 | { |
428 | struct led_flash_setting *s = &fled_cdev->brightness; |
429 | u32 brightness; |
430 | |
431 | if (has_flash_op(fled_cdev, flash_brightness_get)) { |
432 | int ret = call_flash_op(fled_cdev, flash_brightness_get, |
433 | &brightness); |
434 | if (ret < 0) |
435 | return ret; |
436 | |
437 | s->val = brightness; |
438 | } |
439 | |
440 | return 0; |
441 | } |
442 | EXPORT_SYMBOL_GPL(led_update_flash_brightness); |
443 | |
444 | MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>" ); |
445 | MODULE_DESCRIPTION("LED Flash class interface" ); |
446 | MODULE_LICENSE("GPL v2" ); |
447 | |