| /* | |
| * LED Flash class interface | |
| * | |
| * Copyright (C) 2015 Samsung Electronics Co., Ltd. | |
| * Author: Jacek Anaszewski <j.anaszewski@samsung.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/init.h> | |
| #include <linux/led-class-flash.h> | |
| #include <linux/leds.h> | |
| #include <linux/module.h> | |
| #include <linux/slab.h> | |
| #include "leds.h" | |
| #define has_flash_op(fled_cdev, op) \ | |
| (fled_cdev && fled_cdev->ops->op) | |
| #define call_flash_op(fled_cdev, op, args...) \ | |
| ((has_flash_op(fled_cdev, op)) ? \ | |
| (fled_cdev->ops->op(fled_cdev, args)) : \ | |
| -EINVAL) | |
| static const char * const led_flash_fault_names[] = { | |
| "led-over-voltage", | |
| "flash-timeout-exceeded", | |
| "controller-over-temperature", | |
| "controller-short-circuit", | |
| "led-power-supply-over-current", | |
| "indicator-led-fault", | |
| "led-under-voltage", | |
| "controller-under-voltage", | |
| "led-over-temperature", | |
| }; | |
| static ssize_t flash_brightness_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| unsigned long state; | |
| ssize_t ret; | |
| mutex_lock(&led_cdev->led_access); | |
| if (led_sysfs_is_disabled(led_cdev)) { | |
| ret = -EBUSY; | |
| goto unlock; | |
| } | |
| ret = kstrtoul(buf, 10, &state); | |
| if (ret) | |
| goto unlock; | |
| ret = led_set_flash_brightness(fled_cdev, state); | |
| if (ret < 0) | |
| goto unlock; | |
| ret = size; | |
| unlock: | |
| mutex_unlock(&led_cdev->led_access); | |
| return ret; | |
| } | |
| static ssize_t flash_brightness_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| /* no lock needed for this */ | |
| led_update_flash_brightness(fled_cdev); | |
| return sprintf(buf, "%u\n", fled_cdev->brightness.val); | |
| } | |
| static DEVICE_ATTR_RW(flash_brightness); | |
| static ssize_t max_flash_brightness_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| return sprintf(buf, "%u\n", fled_cdev->brightness.max); | |
| } | |
| static DEVICE_ATTR_RO(max_flash_brightness); | |
| static ssize_t flash_strobe_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| unsigned long state; | |
| ssize_t ret = -EINVAL; | |
| mutex_lock(&led_cdev->led_access); | |
| if (led_sysfs_is_disabled(led_cdev)) { | |
| ret = -EBUSY; | |
| goto unlock; | |
| } | |
| ret = kstrtoul(buf, 10, &state); | |
| if (ret) | |
| goto unlock; | |
| if (state > 1) { | |
| ret = -EINVAL; | |
| goto unlock; | |
| } | |
| ret = led_set_flash_strobe(fled_cdev, state); | |
| if (ret < 0) | |
| goto unlock; | |
| ret = size; | |
| unlock: | |
| mutex_unlock(&led_cdev->led_access); | |
| return ret; | |
| } | |
| static ssize_t flash_strobe_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| bool state; | |
| int ret; | |
| /* no lock needed for this */ | |
| ret = led_get_flash_strobe(fled_cdev, &state); | |
| if (ret < 0) | |
| return ret; | |
| return sprintf(buf, "%u\n", state); | |
| } | |
| static DEVICE_ATTR_RW(flash_strobe); | |
| static ssize_t flash_timeout_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| unsigned long flash_timeout; | |
| ssize_t ret; | |
| mutex_lock(&led_cdev->led_access); | |
| if (led_sysfs_is_disabled(led_cdev)) { | |
| ret = -EBUSY; | |
| goto unlock; | |
| } | |
| ret = kstrtoul(buf, 10, &flash_timeout); | |
| if (ret) | |
| goto unlock; | |
| ret = led_set_flash_timeout(fled_cdev, flash_timeout); | |
| if (ret < 0) | |
| goto unlock; | |
| ret = size; | |
| unlock: | |
| mutex_unlock(&led_cdev->led_access); | |
| return ret; | |
| } | |
| static ssize_t flash_timeout_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| return sprintf(buf, "%u\n", fled_cdev->timeout.val); | |
| } | |
| static DEVICE_ATTR_RW(flash_timeout); | |
| static ssize_t max_flash_timeout_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| return sprintf(buf, "%u\n", fled_cdev->timeout.max); | |
| } | |
| static DEVICE_ATTR_RO(max_flash_timeout); | |
| static ssize_t flash_fault_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| u32 fault, mask = 0x1; | |
| char *pbuf = buf; | |
| int i, ret, buf_len; | |
| ret = led_get_flash_fault(fled_cdev, &fault); | |
| if (ret < 0) | |
| return -EINVAL; | |
| *buf = '\0'; | |
| for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) { | |
| if (fault & mask) { | |
| buf_len = sprintf(pbuf, "%s ", | |
| led_flash_fault_names[i]); | |
| pbuf += buf_len; | |
| } | |
| mask <<= 1; | |
| } | |
| return sprintf(buf, "%s\n", buf); | |
| } | |
| static DEVICE_ATTR_RO(flash_fault); | |
| static struct attribute *led_flash_strobe_attrs[] = { | |
| &dev_attr_flash_strobe.attr, | |
| NULL, | |
| }; | |
| static struct attribute *led_flash_timeout_attrs[] = { | |
| &dev_attr_flash_timeout.attr, | |
| &dev_attr_max_flash_timeout.attr, | |
| NULL, | |
| }; | |
| static struct attribute *led_flash_brightness_attrs[] = { | |
| &dev_attr_flash_brightness.attr, | |
| &dev_attr_max_flash_brightness.attr, | |
| NULL, | |
| }; | |
| static struct attribute *led_flash_fault_attrs[] = { | |
| &dev_attr_flash_fault.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group led_flash_strobe_group = { | |
| .attrs = led_flash_strobe_attrs, | |
| }; | |
| static const struct attribute_group led_flash_timeout_group = { | |
| .attrs = led_flash_timeout_attrs, | |
| }; | |
| static const struct attribute_group led_flash_brightness_group = { | |
| .attrs = led_flash_brightness_attrs, | |
| }; | |
| static const struct attribute_group led_flash_fault_group = { | |
| .attrs = led_flash_fault_attrs, | |
| }; | |
| static void led_flash_resume(struct led_classdev *led_cdev) | |
| { | |
| struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); | |
| call_flash_op(fled_cdev, flash_brightness_set, | |
| fled_cdev->brightness.val); | |
| call_flash_op(fled_cdev, timeout_set, fled_cdev->timeout.val); | |
| } | |
| static void led_flash_init_sysfs_groups(struct led_classdev_flash *fled_cdev) | |
| { | |
| struct led_classdev *led_cdev = &fled_cdev->led_cdev; | |
| const struct led_flash_ops *ops = fled_cdev->ops; | |
| const struct attribute_group **flash_groups = fled_cdev->sysfs_groups; | |
| int num_sysfs_groups = 0; | |
| flash_groups[num_sysfs_groups++] = &led_flash_strobe_group; | |
| if (ops->flash_brightness_set) | |
| flash_groups[num_sysfs_groups++] = &led_flash_brightness_group; | |
| if (ops->timeout_set) | |
| flash_groups[num_sysfs_groups++] = &led_flash_timeout_group; | |
| if (ops->fault_get) | |
| flash_groups[num_sysfs_groups++] = &led_flash_fault_group; | |
| led_cdev->groups = flash_groups; | |
| } | |
| int led_classdev_flash_register(struct device *parent, | |
| struct led_classdev_flash *fled_cdev) | |
| { | |
| struct led_classdev *led_cdev; | |
| const struct led_flash_ops *ops; | |
| int ret; | |
| if (!fled_cdev) | |
| return -EINVAL; | |
| led_cdev = &fled_cdev->led_cdev; | |
| if (led_cdev->flags & LED_DEV_CAP_FLASH) { | |
| if (!led_cdev->brightness_set_blocking) | |
| return -EINVAL; | |
| ops = fled_cdev->ops; | |
| if (!ops || !ops->strobe_set) | |
| return -EINVAL; | |
| led_cdev->flash_resume = led_flash_resume; | |
| /* Select the sysfs attributes to be created for the device */ | |
| led_flash_init_sysfs_groups(fled_cdev); | |
| } | |
| /* Register led class device */ | |
| ret = led_classdev_register(parent, led_cdev); | |
| if (ret < 0) | |
| return ret; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(led_classdev_flash_register); | |
| void led_classdev_flash_unregister(struct led_classdev_flash *fled_cdev) | |
| { | |
| if (!fled_cdev) | |
| return; | |
| led_classdev_unregister(&fled_cdev->led_cdev); | |
| } | |
| EXPORT_SYMBOL_GPL(led_classdev_flash_unregister); | |
| static void led_clamp_align(struct led_flash_setting *s) | |
| { | |
| u32 v, offset; | |
| v = s->val + s->step / 2; | |
| v = clamp(v, s->min, s->max); | |
| offset = v - s->min; | |
| offset = s->step * (offset / s->step); | |
| s->val = s->min + offset; | |
| } | |
| int led_set_flash_timeout(struct led_classdev_flash *fled_cdev, u32 timeout) | |
| { | |
| struct led_classdev *led_cdev = &fled_cdev->led_cdev; | |
| struct led_flash_setting *s = &fled_cdev->timeout; | |
| s->val = timeout; | |
| led_clamp_align(s); | |
| if (!(led_cdev->flags & LED_SUSPENDED)) | |
| return call_flash_op(fled_cdev, timeout_set, s->val); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(led_set_flash_timeout); | |
| int led_get_flash_fault(struct led_classdev_flash *fled_cdev, u32 *fault) | |
| { | |
| return call_flash_op(fled_cdev, fault_get, fault); | |
| } | |
| EXPORT_SYMBOL_GPL(led_get_flash_fault); | |
| int led_set_flash_brightness(struct led_classdev_flash *fled_cdev, | |
| u32 brightness) | |
| { | |
| struct led_classdev *led_cdev = &fled_cdev->led_cdev; | |
| struct led_flash_setting *s = &fled_cdev->brightness; | |
| s->val = brightness; | |
| led_clamp_align(s); | |
| if (!(led_cdev->flags & LED_SUSPENDED)) | |
| return call_flash_op(fled_cdev, flash_brightness_set, s->val); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(led_set_flash_brightness); | |
| int led_update_flash_brightness(struct led_classdev_flash *fled_cdev) | |
| { | |
| struct led_flash_setting *s = &fled_cdev->brightness; | |
| u32 brightness; | |
| if (has_flash_op(fled_cdev, flash_brightness_get)) { | |
| int ret = call_flash_op(fled_cdev, flash_brightness_get, | |
| &brightness); | |
| if (ret < 0) | |
| return ret; | |
| s->val = brightness; | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(led_update_flash_brightness); | |
| MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); | |
| MODULE_DESCRIPTION("LED Flash class interface"); | |
| MODULE_LICENSE("GPL v2"); |