dm: led: add BCM6358 led driver

This driver is a simplified version of linux/drivers/leds/leds-bcm6358.c

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
diff --git a/doc/device-tree-bindings/leds/leds-bcm6358.txt b/doc/device-tree-bindings/leds/leds-bcm6358.txt
new file mode 100644
index 0000000..e394d9e
--- /dev/null
+++ b/doc/device-tree-bindings/leds/leds-bcm6358.txt
@@ -0,0 +1,141 @@
+LEDs connected to Broadcom BCM6358 controller
+
+This controller is present on BCM6358 and BCM6368.
+In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller),
+which can either be controlled by software (exporting the 74x164 as spi-gpio.
+See Documentation/devicetree/bindings/gpio/gpio-74x164.txt), or
+by hardware using this driver.
+
+Required properties:
+  - compatible : should be "brcm,bcm6358-leds".
+  - #address-cells : must be 1.
+  - #size-cells : must be 0.
+  - reg : BCM6358 LED controller address and size.
+
+Optional properties:
+  - brcm,clk-div : SCK signal divider. Possible values are 1, 2, 4 and 8.
+    Default : 1
+  - brcm,clk-dat-low : Boolean, makes clock and data signals active low.
+    Default : false
+
+Each LED is represented as a sub-node of the brcm,bcm6358-leds device.
+
+LED sub-node required properties:
+  - reg : LED pin number (only LEDs 0 to 31 are valid).
+
+LED sub-node optional properties:
+  - label : see Documentation/devicetree/bindings/leds/common.txt
+  - active-low : Boolean, makes LED active low.
+    Default : false
+
+Examples:
+Scenario 1 : BCM6358
+	leds0: led-controller@fffe00d0 {
+		compatible = "brcm,bcm6358-leds";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0xfffe00d0 0x8>;
+
+		alarm_white {
+			reg = <0>;
+			active-low;
+			label = "white:alarm";
+		};
+		tv_white {
+			reg = <2>;
+			active-low;
+			label = "white:tv";
+		};
+		tel_white {
+			reg = <3>;
+			active-low;
+			label = "white:tel";
+		};
+		adsl_white {
+			reg = <4>;
+			active-low;
+			label = "white:adsl";
+		};
+	};
+
+Scenario 2 : BCM6368
+	leds0: led-controller@100000d0 {
+		compatible = "brcm,bcm6358-leds";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0x100000d0 0x8>;
+		brcm,pol-low;
+		brcm,clk-div = <4>;
+
+		power_red {
+			reg = <0>;
+			active-low;
+			label = "red:power";
+		};
+		power_green {
+			reg = <1>;
+			active-low;
+			label = "green:power";
+			default-state = "on";
+		};
+		power_blue {
+			reg = <2>;
+			label = "blue:power";
+		};
+		broadband_red {
+			reg = <3>;
+			active-low;
+			label = "red:broadband";
+		};
+		broadband_green {
+			reg = <4>;
+			label = "green:broadband";
+		};
+		broadband_blue {
+			reg = <5>;
+			active-low;
+			label = "blue:broadband";
+		};
+		wireless_red {
+			reg = <6>;
+			active-low;
+			label = "red:wireless";
+		};
+		wireless_green {
+			reg = <7>;
+			active-low;
+			label = "green:wireless";
+		};
+		wireless_blue {
+			reg = <8>;
+			label = "blue:wireless";
+		};
+		phone_red {
+			reg = <9>;
+			active-low;
+			label = "red:phone";
+		};
+		phone_green {
+			reg = <10>;
+			active-low;
+			label = "green:phone";
+		};
+		phone_blue {
+			reg = <11>;
+			label = "blue:phone";
+		};
+		upgrading_red {
+			reg = <12>;
+			active-low;
+			label = "red:upgrading";
+		};
+		upgrading_green {
+			reg = <13>;
+			active-low;
+			label = "green:upgrading";
+		};
+		upgrading_blue {
+			reg = <14>;
+			label = "blue:upgrading";
+		};
+	};
diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig
index dc19f4f..5da5c4a 100644
--- a/drivers/led/Kconfig
+++ b/drivers/led/Kconfig
@@ -20,6 +20,14 @@
 	  means that if one LED is set to blink at 100ms and then a different
 	  LED is set to blink at 200ms, both will blink at 200ms.
 
+config LED_BCM6358
+	bool "LED Support for BCM6358"
+	depends on LED && ARCH_BMIPS
+	help
+	  This option enables support for LEDs connected to the BCM6358
+	  LED HW controller accessed via MMIO registers.
+	  HW has no blinking capabilities and up to 32 LEDs can be controlled.
+
 config LED_BLINK
 	bool "Support LED blinking"
 	depends on LED
diff --git a/drivers/led/Makefile b/drivers/led/Makefile
index d371ed5..9d079f8 100644
--- a/drivers/led/Makefile
+++ b/drivers/led/Makefile
@@ -7,4 +7,5 @@
 
 obj-y += led-uclass.o
 obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o
+obj-$(CONFIG_LED_BCM6358) += led_bcm6358.o
 obj-$(CONFIG_$(SPL_)LED_GPIO) += led_gpio.o
diff --git a/drivers/led/led_bcm6358.c b/drivers/led/led_bcm6358.c
new file mode 100644
index 0000000..11caecd
--- /dev/null
+++ b/drivers/led/led_bcm6358.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <asm/io.h>
+#include <dm/lists.h>
+
+#define LEDS_MAX		32
+#define LEDS_WAIT		100
+
+/* LED Mode register */
+#define LED_MODE_REG		0x0
+#define LED_MODE_OFF		0
+#define LED_MODE_ON		1
+#define LED_MODE_MASK		1
+
+/* LED Control register */
+#define LED_CTRL_REG		0x4
+#define LED_CTRL_CLK_MASK	0x3
+#define LED_CTRL_CLK_1		0
+#define LED_CTRL_CLK_2		1
+#define LED_CTRL_CLK_4		2
+#define LED_CTRL_CLK_8		3
+#define LED_CTRL_POL_SHIFT	2
+#define LED_CTRL_POL_MASK	(1 << LED_CTRL_POL_SHIFT)
+#define LED_CTRL_BUSY_SHIFT	3
+#define LED_CTRL_BUSY_MASK	(1 << LED_CTRL_BUSY_SHIFT)
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct bcm6358_led_priv {
+	void __iomem *regs;
+	uint8_t pin;
+	bool active_low;
+};
+
+static void bcm6358_led_busy(void __iomem *regs)
+{
+	while (readl_be(regs + LED_CTRL_REG) & LED_CTRL_BUSY_MASK)
+		udelay(LEDS_WAIT);
+}
+
+static unsigned long bcm6358_led_get_mode(struct bcm6358_led_priv *priv)
+{
+	bcm6358_led_busy(priv->regs);
+
+	return (readl_be(priv->regs + LED_MODE_REG) >> priv->pin) &
+	       LED_MODE_MASK;
+}
+
+static int bcm6358_led_set_mode(struct bcm6358_led_priv *priv, uint8_t mode)
+{
+	bcm6358_led_busy(priv->regs);
+
+	clrsetbits_be32(priv->regs + LED_MODE_REG,
+			(LED_MODE_MASK << priv->pin),
+			(mode << priv->pin));
+
+	return 0;
+}
+
+static enum led_state_t bcm6358_led_get_state(struct udevice *dev)
+{
+	struct bcm6358_led_priv *priv = dev_get_priv(dev);
+	enum led_state_t state = LEDST_OFF;
+
+	switch (bcm6358_led_get_mode(priv)) {
+	case LED_MODE_OFF:
+		state = (priv->active_low ? LEDST_ON : LEDST_OFF);
+		break;
+	case LED_MODE_ON:
+		state = (priv->active_low ? LEDST_OFF : LEDST_ON);
+		break;
+	}
+
+	return state;
+}
+
+static int bcm6358_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+	struct bcm6358_led_priv *priv = dev_get_priv(dev);
+	unsigned long mode;
+
+	switch (state) {
+	case LEDST_OFF:
+		mode = (priv->active_low ? LED_MODE_ON : LED_MODE_OFF);
+		break;
+	case LEDST_ON:
+		mode = (priv->active_low ? LED_MODE_OFF : LED_MODE_ON);
+		break;
+	case LEDST_TOGGLE:
+		if (bcm6358_led_get_state(dev) == LEDST_OFF)
+			return bcm6358_led_set_state(dev, LEDST_ON);
+		else
+			return bcm6358_led_set_state(dev, LEDST_OFF);
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	return bcm6358_led_set_mode(priv, mode);
+}
+
+static const struct led_ops bcm6358_led_ops = {
+	.get_state = bcm6358_led_get_state,
+	.set_state = bcm6358_led_set_state,
+};
+
+static int bcm6358_led_probe(struct udevice *dev)
+{
+	struct led_uc_plat *uc_plat = dev_get_uclass_platdata(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+
+	/* Top-level LED node */
+	if (!uc_plat->label) {
+		void __iomem *regs;
+		unsigned int clk_div;
+		u32 set_bits = 0;
+
+		addr = dev_get_addr_size_index(dev, 0, &size);
+		if (addr == FDT_ADDR_T_NONE)
+			return -EINVAL;
+
+		regs = ioremap(addr, size);
+
+		if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+				    "brcm,clk-dat-low"))
+			set_bits |= LED_CTRL_POL_MASK;
+		clk_div = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev),
+					  "brcm,clk-div", LED_CTRL_CLK_1);
+		switch (clk_div) {
+		case 8:
+			set_bits |= LED_CTRL_CLK_8;
+			break;
+		case 4:
+			set_bits |= LED_CTRL_CLK_4;
+			break;
+		case 2:
+			set_bits |= LED_CTRL_CLK_2;
+			break;
+		default:
+			set_bits |= LED_CTRL_CLK_1;
+			break;
+		}
+
+		bcm6358_led_busy(regs);
+		clrsetbits_be32(regs + LED_CTRL_REG,
+				LED_CTRL_POL_MASK | LED_CTRL_CLK_MASK,
+				set_bits);
+	} else {
+		struct bcm6358_led_priv *priv = dev_get_priv(dev);
+		unsigned int pin;
+
+		addr = dev_get_addr_size_index(dev_get_parent(dev), 0, &size);
+		if (addr == FDT_ADDR_T_NONE)
+			return -EINVAL;
+
+		pin = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), "reg",
+				      LEDS_MAX);
+		if (pin >= LEDS_MAX)
+			return -EINVAL;
+
+		priv->regs = ioremap(addr, size);
+		priv->pin = pin;
+
+		if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+				    "active-low"))
+			priv->active_low = true;
+	}
+
+	return 0;
+}
+
+static int bcm6358_led_bind(struct udevice *parent)
+{
+	const void *blob = gd->fdt_blob;
+	int node;
+
+	for (node = fdt_first_subnode(blob, dev_of_offset(parent));
+	     node > 0;
+	     node = fdt_next_subnode(blob, node)) {
+		struct led_uc_plat *uc_plat;
+		struct udevice *dev;
+		const char *label;
+		int ret;
+
+		label = fdt_getprop(blob, node, "label", NULL);
+		if (!label) {
+			debug("%s: node %s has no label\n", __func__,
+			      fdt_get_name(blob, node, NULL));
+			return -EINVAL;
+		}
+
+		ret = device_bind_driver_to_node(parent, "bcm6358-led",
+						 fdt_get_name(blob, node, NULL),
+						 node, &dev);
+		if (ret)
+			return ret;
+
+		uc_plat = dev_get_uclass_platdata(dev);
+		uc_plat->label = label;
+	}
+
+	return 0;
+}
+
+static const struct udevice_id bcm6358_led_ids[] = {
+	{ .compatible = "brcm,bcm6358-leds" },
+	{ /* sentinel */ }
+};
+
+U_BOOT_DRIVER(bcm6358_led) = {
+	.name = "bcm6358-led",
+	.id = UCLASS_LED,
+	.of_match = bcm6358_led_ids,
+	.bind = bcm6358_led_bind,
+	.probe = bcm6358_led_probe,
+	.priv_auto_alloc_size = sizeof(struct bcm6358_led_priv),
+	.ops = &bcm6358_led_ops,
+};