Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit db3ecdee authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull LED subsystem update from Bryan Wu:
 "The big change of LED subsystem is introducing a new LED class for
  Flash type LEDs which will be used for V4L2 subsystem.

  Also we got some cleanup and fixes"

* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds:
  leds: leds-gpio: Pass on error codes unmodified
  DT: leds: Add led-sources property
  leds: Add LED Flash class extension to the LED subsystem
  leds: leds-mc13783: Use of_get_child_by_name() instead of refcount hack
  leds: Use setup_timer
  leds: Don't allow brightness values greater than max_brightness
  DT: leds: Add flash LED devices related properties
parents a42cf70e c6e71f81
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
Common leds properties.

LED and flash LED devices provide the same basic functionality as current
regulators, but extended with LED and flash LED specific features like
blinking patterns, flash timeout, flash faults and external flash strobe mode.

Many LED devices expose more than one current output that can be connected
to one or more discrete LED component. Since the arrangement of connections
can influence the way of the LED device initialization, the LED components
have to be tightly coupled with the LED device binding. They are represented
by child nodes of the parent LED device binding.

Optional properties for child nodes:
- led-sources : List of device current outputs the LED is connected to. The
		outputs are identified by the numbers that must be defined
		in the LED device binding documentation.
- label : The label for this LED.  If omitted, the label is
  taken from the node name (excluding the unit address).

@@ -14,6 +27,15 @@ Optional properties for child nodes:
     "ide-disk" - LED indicates disk activity
     "timer" - LED flashes at a fixed, configurable rate

- max-microamp : maximum intensity in microamperes of the LED
		 (torch LED for flash devices)
- flash-max-microamp : maximum intensity in microamperes of the
                       flash LED; it is mandatory if the LED should
		       support the flash mode
- flash-timeout-us : timeout in microseconds after which the flash
                     LED is turned off


Examples:

system-status {
@@ -21,3 +43,11 @@ system-status {
	linux,default-trigger = "heartbeat";
	...
};

camera-flash {
	label = "Flash";
	led-sources = <0>, <1>;
	max-microamp = <50000>;
	flash-max-microamp = <320000>;
	flash-timeout-us = <500000>;
};
+10 −0
Original line number Diff line number Diff line
@@ -22,6 +22,16 @@ config LEDS_CLASS
	  This option enables the led sysfs class in /sys/class/leds.  You'll
	  need this to do anything useful with LEDs.  If unsure, say N.

config LEDS_CLASS_FLASH
	tristate "LED Flash Class Support"
	depends on LEDS_CLASS
	help
	  This option enables the flash led sysfs class in /sys/class/leds.
	  It wrapps LED Class and adds flash LEDs specific sysfs attributes
	  and kernel internal API to it. You'll need this to provide support
	  for the flash related features of a LED device. It can be built
	  as a module.

comment "LED drivers"

config LEDS_88PM860X
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
# LED Core
obj-$(CONFIG_NEW_LEDS)			+= led-core.o
obj-$(CONFIG_LEDS_CLASS)		+= led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH)		+= led-class-flash.o
obj-$(CONFIG_LEDS_TRIGGERS)		+= led-triggers.o

# LED Platform Drivers
+486 −0
Original line number Diff line number Diff line
/*
 * 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 < 0 || 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 ssize_t available_sync_leds_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);
	char *pbuf = buf;
	int i, buf_len;

	buf_len = sprintf(pbuf, "[0: none] ");
	pbuf += buf_len;

	for (i = 0; i < fled_cdev->num_sync_leds; ++i) {
		buf_len = sprintf(pbuf, "[%d: %s] ", i + 1,
				  fled_cdev->sync_leds[i]->led_cdev.name);
		pbuf += buf_len;
	}

	return sprintf(buf, "%s\n", buf);
}
static DEVICE_ATTR_RO(available_sync_leds);

static ssize_t flash_sync_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 led_id;
	ssize_t ret;

	mutex_lock(&led_cdev->led_access);

	if (led_sysfs_is_disabled(led_cdev)) {
		ret = -EBUSY;
		goto unlock;
	}

	ret = kstrtoul(buf, 10, &led_id);
	if (ret)
		goto unlock;

	if (led_id > fled_cdev->num_sync_leds) {
		ret = -ERANGE;
		goto unlock;
	}

	fled_cdev->sync_led_id = led_id;

	ret = size;
unlock:
	mutex_unlock(&led_cdev->led_access);
	return ret;
}

static ssize_t flash_sync_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);
	int sled_id = fled_cdev->sync_led_id;
	char *sync_led_name = "none";

	if (fled_cdev->sync_led_id > 0)
		sync_led_name = (char *)
			fled_cdev->sync_leds[sled_id - 1]->led_cdev.name;

	return sprintf(buf, "[%d: %s]\n", sled_id, sync_led_name);
}
static DEVICE_ATTR_RW(flash_sync_strobe);

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 struct attribute *led_flash_sync_strobe_attrs[] = {
	&dev_attr_available_sync_leds.attr,
	&dev_attr_flash_sync_strobe.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 const struct attribute_group led_flash_sync_strobe_group = {
	.attrs = led_flash_sync_strobe_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;

	if (led_cdev->flags & LED_DEV_CAP_SYNC_STROBE)
		flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_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_sync)
			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;

	/* Setting a torch brightness needs to have immediate effect */
	led_cdev->flags &= ~SET_BRIGHTNESS_ASYNC;
	led_cdev->flags |= SET_BRIGHTNESS_SYNC;

	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");
+6 −3
Original line number Diff line number Diff line
@@ -179,6 +179,10 @@ EXPORT_SYMBOL_GPL(led_classdev_suspend);
void led_classdev_resume(struct led_classdev *led_cdev)
{
	led_cdev->brightness_set(led_cdev, led_cdev->brightness);

	if (led_cdev->flash_resume)
		led_cdev->flash_resume(led_cdev);

	led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
@@ -239,9 +243,8 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

	INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

	init_timer(&led_cdev->blink_timer);
	led_cdev->blink_timer.function = led_timer_function;
	led_cdev->blink_timer.data = (unsigned long)led_cdev;
	setup_timer(&led_cdev->blink_timer, led_timer_function,
		    (unsigned long)led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
Loading