linux/drivers/leds/led-class-flash.c

447 lines
11 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Flash class interface
*
* Copyright (C) 2015 Samsung Electronics Co., Ltd.
* Author: Jacek Anaszewski <j.anaszewski@samsung.com>
*/
#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 = -EBUSY;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev))
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 strlen(strcat(buf, "\n"));
}
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;
}
leds: class: Improve LED and LED flash class registration API Replace of_led_classdev_register() with led_classdev_register_ext(), which accepts easily extendable struct led_init_data, instead of the fixed struct device_node argument. The latter can be now passed in an fwnode property of the struct led_init_data. The modification is driven by the need for passing additional arguments required for the forthcoming generic mechanism for composing LED names. Currently the LED name is conveyed in the "name" char pointer property of the struct led_classdev. This is redundant since LED class device name is accessible throughout the whole LED class device life time via associated struct device's kobj->name property. The change will not break any existing clients since the patch alters also existing led_classdev{_flash}_register() macro wrappers, that pass NULL in place of init_data, which leads to using legacy name initialization path basing on the struct led_classdev's "name" property. Three existing users of devm_of_led_classdev_registers() are modified to use devm_led_classdev_register(), which will not impact their operation since they in fact didn't need to pass struct device_node on registration from the beginning. Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com> Cc: Baolin Wang <baolin.wang@linaro.org> Cc: Dan Murphy <dmurphy@ti.com> Cc: Daniel Mack <daniel@zonque.org> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: Oleh Kravchenko <oleg@kaa.org.ua> Cc: Sakari Ailus <sakari.ailus@linux.intel.com> Cc: Simon Shields <simon@lineageos.org> Acked-by: Pavel Machek <pavel@ucw.cz>
2019-06-09 21:19:03 +03:00
int led_classdev_flash_register_ext(struct device *parent,
struct led_classdev_flash *fled_cdev,
struct led_init_data *init_data)
{
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 */
leds: class: Improve LED and LED flash class registration API Replace of_led_classdev_register() with led_classdev_register_ext(), which accepts easily extendable struct led_init_data, instead of the fixed struct device_node argument. The latter can be now passed in an fwnode property of the struct led_init_data. The modification is driven by the need for passing additional arguments required for the forthcoming generic mechanism for composing LED names. Currently the LED name is conveyed in the "name" char pointer property of the struct led_classdev. This is redundant since LED class device name is accessible throughout the whole LED class device life time via associated struct device's kobj->name property. The change will not break any existing clients since the patch alters also existing led_classdev{_flash}_register() macro wrappers, that pass NULL in place of init_data, which leads to using legacy name initialization path basing on the struct led_classdev's "name" property. Three existing users of devm_of_led_classdev_registers() are modified to use devm_led_classdev_register(), which will not impact their operation since they in fact didn't need to pass struct device_node on registration from the beginning. Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com> Cc: Baolin Wang <baolin.wang@linaro.org> Cc: Dan Murphy <dmurphy@ti.com> Cc: Daniel Mack <daniel@zonque.org> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: Oleh Kravchenko <oleg@kaa.org.ua> Cc: Sakari Ailus <sakari.ailus@linux.intel.com> Cc: Simon Shields <simon@lineageos.org> Acked-by: Pavel Machek <pavel@ucw.cz>
2019-06-09 21:19:03 +03:00
ret = led_classdev_register_ext(parent, led_cdev, init_data);
if (ret < 0)
return ret;
return 0;
}
leds: class: Improve LED and LED flash class registration API Replace of_led_classdev_register() with led_classdev_register_ext(), which accepts easily extendable struct led_init_data, instead of the fixed struct device_node argument. The latter can be now passed in an fwnode property of the struct led_init_data. The modification is driven by the need for passing additional arguments required for the forthcoming generic mechanism for composing LED names. Currently the LED name is conveyed in the "name" char pointer property of the struct led_classdev. This is redundant since LED class device name is accessible throughout the whole LED class device life time via associated struct device's kobj->name property. The change will not break any existing clients since the patch alters also existing led_classdev{_flash}_register() macro wrappers, that pass NULL in place of init_data, which leads to using legacy name initialization path basing on the struct led_classdev's "name" property. Three existing users of devm_of_led_classdev_registers() are modified to use devm_led_classdev_register(), which will not impact their operation since they in fact didn't need to pass struct device_node on registration from the beginning. Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com> Cc: Baolin Wang <baolin.wang@linaro.org> Cc: Dan Murphy <dmurphy@ti.com> Cc: Daniel Mack <daniel@zonque.org> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: Oleh Kravchenko <oleg@kaa.org.ua> Cc: Sakari Ailus <sakari.ailus@linux.intel.com> Cc: Simon Shields <simon@lineageos.org> Acked-by: Pavel Machek <pavel@ucw.cz>
2019-06-09 21:19:03 +03:00
EXPORT_SYMBOL_GPL(led_classdev_flash_register_ext);
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 devm_led_classdev_flash_release(struct device *dev, void *res)
{
led_classdev_flash_unregister(*(struct led_classdev_flash **)res);
}
int devm_led_classdev_flash_register_ext(struct device *parent,
struct led_classdev_flash *fled_cdev,
struct led_init_data *init_data)
{
struct led_classdev_flash **dr;
int ret;
dr = devres_alloc(devm_led_classdev_flash_release, sizeof(*dr),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
ret = led_classdev_flash_register_ext(parent, fled_cdev, init_data);
if (ret) {
devres_free(dr);
return ret;
}
*dr = fled_cdev;
devres_add(parent, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_led_classdev_flash_register_ext);
static int devm_led_classdev_flash_match(struct device *dev,
void *res, void *data)
{
struct led_classdev_flash **p = res;
if (WARN_ON(!p || !*p))
return 0;
return *p == data;
}
void devm_led_classdev_flash_unregister(struct device *dev,
struct led_classdev_flash *fled_cdev)
{
WARN_ON(devres_release(dev,
devm_led_classdev_flash_release,
devm_led_classdev_flash_match, fled_cdev));
}
EXPORT_SYMBOL_GPL(devm_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");