Re: [PATCH v1 leds-next 1/3] leds: Add support for Turris 1.x LEDs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

I have been thinking that the solution I went for is not very nice for
the LAN LEDs in the sense that user can control them only as a group.

Would it be okay if I wrote the driver in such a way that for the
non-LAN LEDs, the driver would create 3 led_cdevs R, G and B for each
non-LAN RGB LED, and for the LAN LEDs it would create only one
led_cdev per LED? The color of the LAN LEDs would then be changeable
from a attribute file, and the brightness values would be binary (so
either on or off).

This way the user would be able to set a sw led-trigger for each
LAN LED, and for each color channel of the non-LAN LEDs.

Marek

On Thu, 21 Mar 2019 19:21:48 +0100
Marek Behún <marek.behun@xxxxxx> wrote:

> This adds proper kernel support for the 8 RGB LEDs on the front panel
> of CZ.NIC's Turris 1.x router.
> 
> The LEDs are controlled by a CPLD device which manages the whole
> router. This CPLD controls the LEDs with PWMs and exposes to CPU
> these functions via memory mapped registers:
>  - enabling/disabling each RGB LED
>  - for every LED (*) separately setting brightness for the R, G, and B
>    color channel (and also reading this values)
>  - setting/getting current global intensity level (there are 8 levels
>    and the level can also be changed by pressing a button on the back
>    side of the router)
>  - setting/getting the intensity of each global intensity level
>  - enabling/disabling HW trigger for each LED
>  - changing WIFI LED to STATUS LED and back (in the sense of HW
> trigger)
> 
> (*) The colors of the LAN LEDs, LAN1-LAN5, are controlled together.
> These LEDs cannot have different colors.
> 
> For each LED three sysfs entries are created, with names "turris:C:N",
> where C is from {r, g, b} and N is from {wan, lan, wifi, power}.
> 
> Although there are 8 LEDs, because of (*) we only work with them as
> with 4 LEDs - LAN1-LAN5 are visible as one LAN LED.
> 
> Each LED has a hw_trigger attribute, which can be set according to
> this table:
> 	LED	dis HW trigger	en HW trigger
> 	wan	none		wan
> 	lan	none		lan
> 	wifi	none		wifi/status
> 	power	none		power
> 
> The parent device also exposes attributes with names
> intensity_level_N for N from {0, ..., 7} to control the values of the
> intensity levels, and an attribute current_intensity_level, to get/set
> current intensity level.
> 
> Signed-off-by: Marek Behún <marek.behun@xxxxxx>
> ---
>  drivers/leds/Kconfig       |  12 ++
>  drivers/leds/Makefile      |   1 +
>  drivers/leds/leds-turris.c | 346
> +++++++++++++++++++++++++++++++++++++ 3 files changed, 359
> insertions(+) create mode 100644 drivers/leds/leds-turris.c
> 
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 2b5ae50f8c9a..8c813c0fdfd2 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -128,6 +128,18 @@ config LEDS_CR0014114
>  	  To compile this driver as a module, choose M here: the
> module will be called leds-cr0014114.
>  
> +config LEDS_TURRIS
> +	tristate "LED support for CZ.NIC's Turris 1.x"
> +	depends on LEDS_CLASS
> +	depends on OF
> +	help
> +	  This option enables support for the RGB LEDs found on the
> front
> +	  side of CZ.NIC's Turris router. There are 8 RGB LEDs on
> the front
> +	  panel, but hardware groups 5 of them (LAN1-LAN5) into one
> LAN LED,
> +	  so the system sees the 8 LEDs only as four.
> +	  For each of these 4 RGB LEDs three sysfs entries are
> created, each
> +	  for one color channel.
> +
>  config LEDS_LM3530
>  	tristate "LCD Backlight driver for LM3530"
>  	depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 4c1b0054f379..807a091adb05 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -79,6 +79,7 @@ obj-$(CONFIG_LEDS_MT6323)		+=
> leds-mt6323.o obj-$(CONFIG_LEDS_LM3692X)		+=
> leds-lm3692x.o obj-$(CONFIG_LEDS_SC27XX_BLTC)		+=
> leds-sc27xx-bltc.o obj-$(CONFIG_LEDS_LM3601X)		+=
> leds-lm3601x.o +obj-$(CONFIG_LEDS_TURRIS)		+=
> leds-turris.o 
>  # LED SPI Drivers
>  obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
> diff --git a/drivers/leds/leds-turris.c b/drivers/leds/leds-turris.c
> new file mode 100644
> index 000000000000..c9c80c2c63e8
> --- /dev/null
> +++ b/drivers/leds/leds-turris.c
> @@ -0,0 +1,346 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * CZ.NIC's Turris 1.x LEDs driver
> + *
> + * 2019 by Marek Behun <marek.behun@xxxxxx>
> + */
> +#include <linux/ctype.h>
> +#include <linux/io.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#define CPLD_WIFI_LED_MODE			0x08
> +#define CPLD_LED_BRIGHTNESS(idx)		(0x13 + (idx))
> +#define CPLD_LED_INTENSITY_LEVEL		0x20
> +#define CPLD_LED_SW_OVERRIDE			0x22
> +#define CPLD_LED_SW_ENABLE			0x23
> +#define CPLD_LED_INTENSITY_LEVEL_VALUE(lvl)	(0x28 + ((lvl) &
> 0x7)) +
> +#define TURRIS_BOARD_LEDS	12
> +
> +static const struct {
> +	u8 mask;
> +	const char *group;
> +	const char *alt_hw_trigger;
> +	const char *name;
> +} turris_leds_info[TURRIS_BOARD_LEDS] = {
> +	{ 0x01, "wan",   NULL,     "turris:r:wan" },
> +	{ 0x01, "wan",   NULL,     "turris:g:wan" },
> +	{ 0x01, "wan",   NULL,     "turris:b:wan" },
> +	{ 0x3e, "lan",   NULL,     "turris:r:lan" },
> +	{ 0x3e, "lan",   NULL,     "turris:g:lan" },
> +	{ 0x3e, "lan",   NULL,     "turris:b:lan" },
> +	{ 0x40, "wifi",  "status", "turris:r:wifi" },
> +	{ 0x40, "wifi",  "status", "turris:g:wifi" },
> +	{ 0x40, "wifi",  "status", "turris:b:wifi" },
> +	{ 0x80, "power", NULL,     "turris:r:power" },
> +	{ 0x80, "power", NULL,     "turris:g:power" },
> +	{ 0x80, "power", NULL,     "turris:b:power" },
> +};
> +
> +struct turris_leds {
> +	struct device *dev;
> +	void __iomem *regs;
> +	struct mutex lock;
> +	struct led_classdev leds[TURRIS_BOARD_LEDS];
> +};
> +
> +static int turris_led_idx(struct turris_leds *leds, struct
> led_classdev *led) +{
> +	int idx = led - &leds->leds[0];
> +
> +	if (idx < 0 || idx >= TURRIS_BOARD_LEDS)
> +		return -ENXIO;
> +
> +	return idx;
> +}
> +
> +static ssize_t hw_trigger_show(struct device *d, struct
> device_attribute *a,
> +			       char *buf)
> +{
> +	struct led_classdev *led = dev_get_drvdata(d);
> +	struct turris_leds *leds = dev_get_drvdata(led->dev->parent);
> +	int idx = turris_led_idx(leds, led);
> +	u8 reg, hwtrig;
> +	char *p = buf;
> +
> +	if (idx < 0)
> +		return idx;
> +
> +	reg = readb(leds->regs + CPLD_LED_SW_OVERRIDE);
> +	hwtrig = reg & turris_leds_info[idx].mask ? 0 : 1;
> +
> +	if (hwtrig && turris_leds_info[idx].alt_hw_trigger) {
> +		/* only wifi/status LED supports alternative HW
> trigger */
> +		reg = readb(leds->regs + CPLD_WIFI_LED_MODE);
> +		if (reg & 1)
> +			hwtrig = 2;
> +	}
> +
> +	p += sprintf(p, "%s ", hwtrig == 0 ? "[none]" : "none");
> +	p += sprintf(p, "%s%s%s ", hwtrig == 1 ? "[" : "",
> +		     turris_leds_info[idx].group, hwtrig == 1 ?
> "]" : "");
> +	if (turris_leds_info[idx].alt_hw_trigger)
> +		p += sprintf(p, "%s%s%s ", hwtrig == 2 ? "[" : "",
> +			     turris_leds_info[idx].alt_hw_trigger,
> +			     hwtrig == 2 ? "]" : "");
> +	p[-1] = '\n';
> +
> +	return p - buf;
> +}
> +
> +static ssize_t hw_trigger_store(struct device *d, struct
> device_attribute *a,
> +				const char *buf, size_t size)
> +{
> +	struct led_classdev *led = dev_get_drvdata(d);
> +	struct turris_leds *leds = dev_get_drvdata(led->dev->parent);
> +	int idx = turris_led_idx(leds, led);
> +	char dup[16];
> +	u8 reg;
> +
> +	if (idx < 0)
> +		return idx;
> +
> +	if (size > 15)
> +		return -EINVAL;
> +
> +	memcpy(dup, buf, size);
> +
> +	if (size > 0 && dup[size-1] == '\n')
> +		dup[size-1] = '\0';
> +
> +	mutex_lock(&leds->lock);
> +	reg = readb(leds->regs + CPLD_LED_SW_OVERRIDE);
> +
> +	if (!strcmp(dup, "none")) {
> +		reg |= turris_leds_info[idx].mask;
> +	} else if (!strcmp(dup, turris_leds_info[idx].group)) {
> +		reg &= ~turris_leds_info[idx].mask;
> +
> +		if (turris_leds_info[idx].alt_hw_trigger)
> +			writel(0, leds->regs + CPLD_WIFI_LED_MODE);
> +	} else if (turris_leds_info[idx].alt_hw_trigger &&
> +		   !strcmp(dup,
> turris_leds_info[idx].alt_hw_trigger)) {
> +		reg &= ~turris_leds_info[idx].mask;
> +
> +		writel(1, leds->regs + CPLD_WIFI_LED_MODE);
> +	} else {
> +		size = -EINVAL;
> +	}
> +
> +	if (size > 0)
> +		writeb(reg, leds->regs + CPLD_LED_SW_OVERRIDE);
> +	mutex_unlock(&leds->lock);
> +
> +	return size;
> +}
> +
> +static DEVICE_ATTR_RW(hw_trigger);
> +
> +static struct attribute *turris_led_attrs[] = {
> +	&dev_attr_hw_trigger.attr,
> +	NULL
> +};
> +ATTRIBUTE_GROUPS(turris_led);
> +
> +static enum led_brightness turris_led_brightness_get(struct
> led_classdev *led) +{
> +	struct turris_leds *leds = dev_get_drvdata(led->dev->parent);
> +	int idx = turris_led_idx(leds, led);
> +
> +	if (idx < 0)
> +		return idx;
> +
> +	return readb(leds->regs + CPLD_LED_BRIGHTNESS(idx));
> +}
> +
> +static void turris_led_brightness_set(struct led_classdev *led,
> +				      enum led_brightness brightness)
> +{
> +	struct turris_leds *leds = dev_get_drvdata(led->dev->parent);
> +	int idx = turris_led_idx(leds, led);
> +
> +	if (idx < 0)
> +		return;
> +
> +	writeb(brightness, leds->regs + CPLD_LED_BRIGHTNESS(idx));
> +}
> +
> +static ssize_t intensity_level_show(struct device *d,
> +				    struct device_attribute *a,
> +				    char *buf)
> +{
> +	struct turris_leds *leds = dev_get_drvdata(d);
> +	int idx;
> +	u8 reg;
> +
> +	if (strlen(a->attr.name) < 17 || !isdigit(a->attr.name[16]))
> +		return -ENXIO;
> +
> +	idx = a->attr.name[16] - '0';
> +	if (idx > 7)
> +		return -ENXIO;
> +
> +	reg = readb(leds->regs +
> CPLD_LED_INTENSITY_LEVEL_VALUE(idx));
> +	return sprintf(buf, "%u\n", reg);
> +}
> +
> +static ssize_t intensity_level_store(struct device *d,
> +				     struct device_attribute *a,
> +				     const char *buf, size_t size)
> +{
> +	struct turris_leds *leds = dev_get_drvdata(d);
> +	int idx;
> +	u8 reg;
> +
> +	if (strlen(a->attr.name) < 17 || !isdigit(a->attr.name[16]))
> +		return -ENXIO;
> +
> +	idx = a->attr.name[16] - '0';
> +	if (idx > 7)
> +		return -ENXIO;
> +
> +	if (kstrtou8(buf, 10, &reg) < 0)
> +		return -EINVAL;
> +
> +	writeb(reg, leds->regs +
> CPLD_LED_INTENSITY_LEVEL_VALUE(idx));
> +	return size;
> +}
> +
> +static DEVICE_ATTR(intensity_level_0, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_1, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_2, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_3, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_4, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_5, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_6, 0644, intensity_level_show,
> +		   intensity_level_store);
> +static DEVICE_ATTR(intensity_level_7, 0644, intensity_level_show,
> +		   intensity_level_store);
> +
> +static ssize_t current_intensity_level_show(struct device *d,
> +					    struct device_attribute
> *a,
> +					    char *buf)
> +{
> +	struct turris_leds *leds = dev_get_drvdata(d);
> +	u8 reg;
> +
> +	reg = readb(leds->regs + CPLD_LED_INTENSITY_LEVEL);
> +	return sprintf(buf, "%u\n", reg);
> +}
> +
> +static ssize_t current_intensity_level_store(struct device *d,
> +					     struct device_attribute
> *a,
> +					     const char *buf, size_t
> size) +{
> +	struct turris_leds *leds = dev_get_drvdata(d);
> +	u8 reg;
> +
> +	if (kstrtou8(buf, 10, &reg) < 0)
> +		return -EINVAL;
> +
> +	if (reg > 7)
> +		return -EINVAL;
> +
> +	writeb(reg, leds->regs + CPLD_LED_INTENSITY_LEVEL);
> +	return size;
> +}
> +
> +static DEVICE_ATTR_RW(current_intensity_level);
> +
> +static struct attribute *turris_leds_attrs[] = {
> +	&dev_attr_intensity_level_0.attr,
> +	&dev_attr_intensity_level_1.attr,
> +	&dev_attr_intensity_level_2.attr,
> +	&dev_attr_intensity_level_3.attr,
> +	&dev_attr_intensity_level_4.attr,
> +	&dev_attr_intensity_level_5.attr,
> +	&dev_attr_intensity_level_6.attr,
> +	&dev_attr_intensity_level_7.attr,
> +	&dev_attr_current_intensity_level.attr,
> +	NULL
> +};
> +ATTRIBUTE_GROUPS(turris_leds);
> +
> +static int turris_leds_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct turris_leds *leds;
> +	struct resource *res;
> +	void __iomem *regs;
> +	int i, ret;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res)
> +		return -ENODEV;
> +
> +	regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +
> +	leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
> +	if (!leds)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, leds);
> +
> +	leds->dev = dev;
> +	leds->regs = regs;
> +	mutex_init(&leds->lock);
> +
> +	for (i = 0; i < TURRIS_BOARD_LEDS; ++i) {
> +		leds->leds[i].name = turris_leds_info[i].name;
> +		leds->leds[i].brightness_get =
> turris_led_brightness_get;
> +		leds->leds[i].brightness_set =
> turris_led_brightness_set;
> +		leds->leds[i].default_trigger = "default-on";
> +		leds->leds[i].groups = turris_led_groups;
> +
> +		ret = devm_led_classdev_register(dev,
> &leds->leds[i]);
> +		if (ret < 0) {
> +			dev_err(dev, "Cannot register LED %s: %i\n",
> +				turris_leds_info[i].name, ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = devm_device_add_groups(dev, turris_leds_groups);
> +	if (ret < 0)
> +		dev_warn(dev, "Cannot create device attributes:
> %i\n", ret); +
> +	/* enable all LEDs */
> +	writeb(0x00, leds->regs + CPLD_LED_SW_ENABLE);
> +
> +	dev_info(dev, "Turris LEDs registered\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_turris_leds_match[] = {
> +	{ .compatible = "cznic,turris-leds", },
> +	{},
> +};
> +
> +static struct platform_driver turris_leds_driver = {
> +	.probe		= turris_leds_probe,
> +	.driver		= {
> +		.name	= "leds-turris",
> +		.of_match_table = of_turris_leds_match,
> +	},
> +};
> +
> +module_platform_driver(turris_leds_driver);
> +
> +MODULE_AUTHOR("Marek Behun <marek.behun@xxxxxx>");
> +MODULE_DESCRIPTION("CZ.NIC's Turris 1.x LEDs");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:turris_leds");





[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux