All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Álvaro Fernández Rojas" <noltari@gmail.com>
To: linux-leds@vger.kernel.org, cooloney@gmail.com, jogo@openwrt.org,
	f.fainelli@gmail.com, cernekee@gmail.com
Cc: "Álvaro Fernández Rojas" <noltari@gmail.com>
Subject: [PATCH 2/2] leds: add BCM6328 LED driver
Date: Sun,  5 Apr 2015 17:08:05 +0200	[thread overview]
Message-ID: <1428246485-32305-3-git-send-email-noltari@gmail.com> (raw)
In-Reply-To: <1428246485-32305-1-git-send-email-noltari@gmail.com>

This adds support for the LED controller on Broadcom's BCM6328.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Signed-off-by: Jonas Gorski <jogo@openwrt.org>
---
v2: introduce changes suggested by Florian and Jonas.
 - Improve Kconfig description.
 - Remove unused imports.
 - Fix bug in blink_set so it works for multiple LEDs.
 - "brcm,link-selection" -> "brcm,link-signal-sources".
 - "brcm,activity-selection" -> "brcm,activity-signal-sources".
 - Use of_property_read_bool instead of of_get_property for booleans.
 - Add MODULE_AUTHOR strings.

 drivers/leds/Kconfig        |   8 +
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-bcm6328.c | 409 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 418 insertions(+)
 create mode 100644 drivers/leds/leds-bcm6328.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 25b320d..64a12e4 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -42,6 +42,14 @@ config LEDS_88PM860X
 	  This option enables support for on-chip LED drivers found on Marvell
 	  Semiconductor 88PM8606 PMIC.
 
+config LEDS_BCM6328
+	tristate "LED Support for Broadcom BCM6328"
+	depends on LEDS_CLASS
+	depends on OF
+	help
+	  This option enables support for LEDs connected to the BCM6328
+	  LED HW controller accessed via MMIO registers.
+
 config LEDS_LM3530
 	tristate "LCD Backlight driver for LM3530"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index cbba921..bf69f49 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGERS)		+= led-triggers.o
 
 # LED Platform Drivers
 obj-$(CONFIG_LEDS_88PM860X)		+= leds-88pm860x.o
+obj-$(CONFIG_LEDS_BCM6328)		+= leds-bcm6328.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
diff --git a/drivers/leds/leds-bcm6328.c b/drivers/leds/leds-bcm6328.c
new file mode 100644
index 0000000..6a63f9c
--- /dev/null
+++ b/drivers/leds/leds-bcm6328.c
@@ -0,0 +1,409 @@
+/*
+ * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ * Copyright 2015 Jonas Gorski <jogo@openwrt.org>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define REG_INIT		0x00
+#define REG_MODE_HI		0x04
+#define REG_MODE_LO		0x08
+#define REG_HWDIS		0x0c
+#define REG_STROBE		0x10
+#define REG_LNKACTSEL_HI	0x14
+#define REG_LNKACTSEL_LO	0x18
+#define REG_RBACK		0x1c
+#define REG_SERMUX		0x20
+
+#define LED_MAX_COUNT		24
+#define LED_DEF_DELAY		500
+#define LED_INTERVAL_MS		20
+
+#define LED_INTV_MASK		0x3f
+#define LED_FAST_INTV_SHIFT	6
+#define LED_FAST_INTV_MASK	(LED_INTV_MASK << LED_FAST_INTV_SHIFT)
+#define SERIAL_LED_EN		BIT(12)
+#define SERIAL_LED_MUX		BIT(13)
+#define SERIAL_LED_CLK_NPOL	BIT(14)
+#define SERIAL_LED_DATA_PPOL	BIT(15)
+#define SERIAL_LED_SHIFT_DIR	BIT(16)
+#define LED_SHIFT_TEST		BIT(30)
+#define LED_TEST		BIT(31)
+
+#define LED_MODE_MASK		3
+#define LED_MODE_OFF		0
+#define LED_MODE_FAST		1
+#define LED_MODE_BLINK		2
+#define LED_MODE_ON		3
+#define LED_SHIFT(X)		((X) << 1)
+
+/**
+ * struct bcm6328_led - state container for bcm6328 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @blink_leds: blinking LEDs
+ * @blink_del: blinking delay
+ * @active_low: LED is active low
+ */
+struct bcm6328_led {
+	struct led_classdev cdev;
+	void __iomem *mem;
+	spinlock_t *lock;
+	unsigned long pin;
+	unsigned long *blink_leds;
+	unsigned long *blink_del;
+	bool active_low;
+};
+
+static void bcm6328_led_write(void __iomem *reg, unsigned long data)
+{
+	iowrite32be(data, reg);
+}
+
+static unsigned long bcm6328_led_read(void __iomem *reg)
+{
+	return ioread32be(reg);
+}
+
+/**
+ * LEDMode 64 bits / 24 LEDs
+ * bits [31:0] -> LEDs 8-23
+ * bits [47:32] -> LEDs 0-7
+ * bits [63:48] -> unused
+ */
+static unsigned long bcm6328_pin2shift(unsigned long pin)
+{
+	if (pin < 8)
+		return pin + 16; /* LEDs 0-7 (bits 47:32) */
+	else
+		return pin - 8; /* LEDs 8-23 (bits 31:0) */
+}
+
+static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
+{
+	void __iomem *mode;
+	unsigned long val, shift;
+
+	shift = bcm6328_pin2shift(led->pin);
+	if (shift / 16)
+		mode = led->mem + REG_MODE_HI;
+	else
+		mode = led->mem + REG_MODE_LO;
+
+	val = bcm6328_led_read(mode);
+	val &= ~(LED_MODE_MASK << LED_SHIFT(shift % 16));
+	val |= (value << LED_SHIFT(shift % 16));
+	bcm6328_led_write(mode, val);
+}
+
+static void bcm6328_led_set(struct led_classdev *led_cdev,
+	enum led_brightness value)
+{
+	struct bcm6328_led *led =
+		container_of(led_cdev, struct bcm6328_led, cdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(led->lock, flags);
+	*(led->blink_leds) &= ~BIT(led->pin);
+	if ((led->active_low && value == LED_OFF) ||
+	    (!led->active_low && value != LED_OFF))
+		bcm6328_led_mode(led, LED_MODE_OFF);
+	else
+		bcm6328_led_mode(led, LED_MODE_ON);
+	spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6328_blink_set(struct led_classdev *led_cdev,
+			     unsigned long *delay_on, unsigned long *delay_off)
+{
+	struct bcm6328_led *led =
+		container_of(led_cdev, struct bcm6328_led, cdev);
+	unsigned long delay, flags;
+
+	if (!*delay_on)
+		*delay_on = LED_DEF_DELAY;
+	if (!*delay_off)
+		*delay_off = LED_DEF_DELAY;
+
+	if (*delay_on != *delay_off) {
+		dev_dbg(led_cdev->dev,
+			"fallback to soft blinking (delay_on != delay_off)\n");
+		return -EINVAL;
+	}
+
+	delay = *delay_on / LED_INTERVAL_MS;
+	if (delay == 0)
+		delay = 1;
+	else if (delay > LED_INTV_MASK) {
+		dev_dbg(led_cdev->dev,
+			"fallback to soft blinking (delay > %ums)\n",
+			LED_INTV_MASK * LED_INTERVAL_MS);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(led->lock, flags);
+	if (*(led->blink_leds) == 0 ||
+	    *(led->blink_leds) == BIT(led->pin) ||
+	    *(led->blink_del) == delay) {
+		unsigned long val;
+
+		*(led->blink_leds) |= BIT(led->pin);
+		*(led->blink_del) = delay;
+
+		val = bcm6328_led_read(led->mem + REG_INIT);
+		val &= ~LED_FAST_INTV_MASK;
+		val |= (delay << LED_FAST_INTV_SHIFT);
+		bcm6328_led_write(led->mem + REG_INIT, val);
+
+		bcm6328_led_mode(led, LED_MODE_BLINK);
+
+		spin_unlock_irqrestore(led->lock, flags);
+	} else {
+		spin_unlock_irqrestore(led->lock, flags);
+		dev_dbg(led_cdev->dev,
+			"fallback to soft blinking (delay already set)\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
+			 void __iomem *mem, spinlock_t *lock)
+{
+	int i, cnt;
+	unsigned long flags, val;
+
+	spin_lock_irqsave(lock, flags);
+	val = bcm6328_led_read(mem + REG_HWDIS);
+	val &= ~BIT(reg);
+	bcm6328_led_write(mem + REG_HWDIS, val);
+	spin_unlock_irqrestore(lock, flags);
+
+	/* Only LEDs 0-7 can be activity/link controlled */
+	if (reg >= 8)
+		return 0;
+
+	cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
+					      sizeof(u32));
+	for (i = 0; i < cnt; i++) {
+		u32 sel;
+		void __iomem *addr;
+
+		if (reg < 4)
+			addr = mem + REG_LNKACTSEL_LO;
+		else
+			addr = mem + REG_LNKACTSEL_HI;
+
+		of_property_read_u32_index(nc, "brcm,link-signal-sources", i, &sel);
+
+		if (reg / 4 != sel / 4) {
+			dev_warn(dev, "invalid link signal source\n");
+			continue;
+		}
+
+		spin_lock_irqsave(lock, flags);
+		val = bcm6328_led_read(addr);
+		val |= (BIT(reg) << (((sel % 4) * 4) + 16));
+		bcm6328_led_write(addr, val);
+		spin_unlock_irqrestore(lock, flags);
+	}
+
+	cnt = of_property_count_elems_of_size(nc, "brcm,activity-signal-sources",
+					      sizeof(u32));
+	for (i = 0; i < cnt; i++) {
+		u32 sel;
+		void __iomem *addr;
+
+		if (reg < 4)
+			addr = mem + REG_LNKACTSEL_LO;
+		else
+			addr = mem + REG_LNKACTSEL_HI;
+
+		of_property_read_u32_index(nc, "brcm,activity-signal-sources", i,
+					   &sel);
+
+		if (reg / 4 != sel / 4) {
+			dev_warn(dev, "invalid activity signal source\n");
+			continue;
+		}
+
+		spin_lock_irqsave(lock, flags);
+		val = bcm6328_led_read(addr);
+		val |= (BIT(reg) << ((sel % 4) * 4));
+		bcm6328_led_write(addr, val);
+		spin_unlock_irqrestore(lock, flags);
+	}
+
+	return 0;
+}
+
+static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
+		       void __iomem *mem, spinlock_t *lock,
+		       unsigned long *blink_leds, unsigned long *blink_del)
+{
+	struct bcm6328_led *led;
+	unsigned long flags;
+	const char *state;
+	int rc;
+
+	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->pin = reg;
+	led->mem = mem;
+	led->lock = lock;
+	led->blink_leds = blink_leds;
+	led->blink_del = blink_del;
+
+	if (of_property_read_bool(nc, "active-low"))
+		led->active_low = 1;
+
+	led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+	led->cdev.default_trigger = of_get_property(nc,
+						    "linux,default-trigger",
+						    NULL);
+
+	if (!of_property_read_string(nc, "default-state", &state)) {
+		spin_lock_irqsave(lock, flags);
+		if (!strcmp(state, "on")) {
+			led->cdev.brightness = LED_FULL;
+			bcm6328_led_mode(led, LED_MODE_ON);
+		} else if (!strcmp(state, "keep")) {
+			void __iomem *mode;
+			unsigned long val, shift;
+
+			shift = bcm6328_pin2shift(led->pin);
+			if (shift / 16)
+				mode = mem + REG_MODE_HI;
+			else
+				mode = mem + REG_MODE_LO;
+
+			val = bcm6328_led_read(mode) >> (shift % 16);
+			val &= LED_MODE_MASK;
+			if (val == LED_MODE_ON)
+				led->cdev.brightness = LED_FULL;
+			else {
+				led->cdev.brightness = LED_OFF;
+				bcm6328_led_mode(led, LED_MODE_OFF);
+			}
+		} else {
+			led->cdev.brightness = LED_OFF;
+			bcm6328_led_mode(led, LED_MODE_OFF);
+		}
+		spin_unlock_irqrestore(lock, flags);
+	}
+
+	led->cdev.brightness_set = bcm6328_led_set;
+	led->cdev.blink_set = bcm6328_blink_set;
+
+	rc = led_classdev_register(dev, &led->cdev);
+	if (rc < 0)
+		return rc;
+
+	dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+	return 0;
+}
+
+static int bcm6328_leds_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *child;
+	struct resource *mem_r;
+	void __iomem *mem;
+	spinlock_t *lock;
+	unsigned long val, *blink_leds, *blink_del;
+
+	mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem_r)
+		return -EINVAL;
+
+	mem = devm_ioremap_resource(dev, mem_r);
+	if (IS_ERR(mem))
+		return PTR_ERR(mem);
+
+	lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+	if (!lock)
+		return -ENOMEM;
+
+	blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL);
+	if (!blink_leds)
+		return -ENOMEM;
+
+	blink_del = devm_kzalloc(dev, sizeof(*blink_del), GFP_KERNEL);
+	if (!blink_del)
+		return -ENOMEM;
+
+	spin_lock_init(lock);
+
+	bcm6328_led_write(mem + REG_HWDIS, ~0);
+	bcm6328_led_write(mem + REG_LNKACTSEL_HI, 0);
+	bcm6328_led_write(mem + REG_LNKACTSEL_LO, 0);
+
+	val = bcm6328_led_read(mem + REG_INIT);
+	val &= ~SERIAL_LED_EN;
+	if (of_property_read_bool(np, "brcm,serial-leds"))
+		val |= SERIAL_LED_EN;
+	bcm6328_led_write(mem + REG_INIT, val);
+
+	for_each_available_child_of_node(np, child) {
+		int rc;
+		u32 reg;
+
+		if (of_property_read_u32(child, "reg", &reg))
+			continue;
+
+		if (reg >= LED_MAX_COUNT) {
+			dev_err(dev, "invalid LED (>= %d)\n", LED_MAX_COUNT);
+			continue;
+		}
+
+		if (of_property_read_bool(child, "brcm,hardware-controlled"))
+			rc = bcm6328_hwled(dev, child, reg, mem, lock);
+		else
+			rc = bcm6328_led(dev, child, reg, mem, lock,
+					  blink_leds, blink_del);
+
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id bcm6328_leds_of_match[] = {
+	{ .compatible = "brcm,bcm6328-leds-ctrl", },
+	{ },
+};
+
+static struct platform_driver bcm6328_leds_driver = {
+	.probe = bcm6328_leds_probe,
+	.driver = {
+		.name = "leds-bcm6328",
+		.of_match_table = bcm6328_leds_of_match,
+	},
+};
+
+module_platform_driver(bcm6328_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6328");
-- 
1.9.1

  parent reply	other threads:[~2015-04-05 15:08 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-04-02 11:54 [PATCH 0/2] BCM6328 LED driver Álvaro Fernández Rojas
2015-04-02 11:54 ` [PATCH 1/2] leds: add DT binding for BCM6328 LED controller Álvaro Fernández Rojas
2015-04-03  2:06   ` Florian Fainelli
2015-04-03 12:19     ` Álvaro Fernández Rojas
2015-04-03 14:46       ` Jonas Gorski
2015-04-02 11:54 ` [PATCH 2/2] leds: add BCM6328 LED driver Álvaro Fernández Rojas
2015-04-05 15:08 ` [PATCH v2 0/2] " Álvaro Fernández Rojas
2015-04-05 15:08   ` [PATCH v2 1/2] leds: add DT binding for BCM6328 LED controller Álvaro Fernández Rojas
2015-04-16 14:39     ` Jacek Anaszewski
2015-04-05 15:08   ` Álvaro Fernández Rojas [this message]
2015-04-16 14:37     ` [PATCH 2/2] leds: add BCM6328 LED driver Jacek Anaszewski
2015-04-18  9:14       ` Jonas Gorski
2015-04-20  7:12         ` Jacek Anaszewski
2015-04-24 17:06   ` [PATCH v3 0/2] " Álvaro Fernández Rojas
2015-04-24 17:06     ` [PATCH v3 1/2] leds: add DT binding for BCM6328 LED controller Álvaro Fernández Rojas
2015-04-26 11:09       ` Jacek Anaszewski
2015-04-24 17:06     ` [PATCH v3 2/2] leds: add BCM6328 LED driver Álvaro Fernández Rojas
2015-04-26 12:15     ` [PATCH v4 0/2] " Álvaro Fernández Rojas
2015-04-26 12:15       ` [PATCH v4 1/2] leds: add DT binding for BCM6328 LED controller Álvaro Fernández Rojas
2015-04-28  8:18         ` Jacek Anaszewski
2015-04-26 12:15       ` [PATCH v4 2/2] leds: add BCM6328 LED driver Álvaro Fernández Rojas
2015-04-28  8:18         ` Jacek Anaszewski
2015-04-28 16:50       ` [PATCH v5 0/2] " Álvaro Fernández Rojas
2015-04-28 16:50         ` [PATCH v5 1/2] leds: add DT binding for BCM6328 LED controller Álvaro Fernández Rojas
2015-04-28 16:50         ` [PATCH v5 2/2] leds: add BCM6328 LED driver Álvaro Fernández Rojas
     [not found]         ` <1430239850-26120-1-git-send-email-noltari-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2015-04-29  0:46           ` [PATCH v5 0/2] " Bryan Wu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1428246485-32305-3-git-send-email-noltari@gmail.com \
    --to=noltari@gmail.com \
    --cc=cernekee@gmail.com \
    --cc=cooloney@gmail.com \
    --cc=f.fainelli@gmail.com \
    --cc=jogo@openwrt.org \
    --cc=linux-leds@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.