[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]

 



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");
-- 
2.19.2




[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