dm: led: add BCM6328 led driver
authorÁlvaro Fernández Rojas <noltari@gmail.com>
Sun, 7 May 2017 18:10:24 +0000 (20:10 +0200)
committerDaniel Schwierzeck <daniel.schwierzeck@gmail.com>
Wed, 10 May 2017 14:16:09 +0000 (16:16 +0200)
This driver is a simplified version of linux/drivers/leds/leds-bcm6328.c,
simplified to remove HW leds and blink fallbacks.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
doc/device-tree-bindings/leds/leds-bcm6328.txt [new file with mode: 0644]
drivers/led/Kconfig
drivers/led/Makefile
drivers/led/led_bcm6328.c [new file with mode: 0644]

diff --git a/doc/device-tree-bindings/leds/leds-bcm6328.txt b/doc/device-tree-bindings/leds/leds-bcm6328.txt
new file mode 100644 (file)
index 0000000..7f5597b
--- /dev/null
@@ -0,0 +1,106 @@
+LEDs connected to Broadcom BCM6328 controller
+
+This controller is present on BCM6318, BCM6328, BCM6362 and BCM63268.
+In these SoCs it's possible to control LEDs both as GPIOs or by hardware.
+However, on some devices 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.
+Some of these Serial LEDs are hardware controlled (e.g. ethernet LEDs) and
+exporting the 74x164 as spi-gpio prevents those LEDs to be hardware
+controlled, so the only chance to keep them working is by using this driver.
+
+Required properties:
+  - compatible : should be "brcm,bcm6328-leds".
+  - #address-cells : must be 1.
+  - #size-cells : must be 0.
+  - reg : BCM6328 LED controller address and size.
+
+Optional properties:
+  - brcm,serial-leds : Boolean, enables Serial LEDs.
+    Default : false
+  - brcm,serial-mux : Boolean, enables Serial LEDs multiplexing.
+    Default : false
+  - brcm,serial-clk-low : Boolean, makes clock signal active low.
+    Default : false
+  - brcm,serial-dat-low : Boolean, makes data signal active low.
+    Default : false
+  - brcm,serial-shift-inv : Boolean, inverts Serial LEDs shift direction.
+    Default : false
+
+Each LED is represented as a sub-node of the brcm,bcm6328-leds device.
+
+LED sub-node required properties:
+  - reg : LED pin number (only LEDs 0 to 23 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 : BCM6328 with 4 GPIO LEDs
+       leds0: led-controller@10000800 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10000800 0x24>;
+
+               alarm_red@2 {
+                       reg = <2>;
+                       active-low;
+                       label = "red:alarm";
+               };
+               inet_green@3 {
+                       reg = <3>;
+                       active-low;
+                       label = "green:inet";
+               };
+               power_green@4 {
+                       reg = <4>;
+                       active-low;
+                       label = "green:power";
+               };
+       };
+
+Scenario 2 : BCM63268 with Serial LEDs
+       leds0: led-controller@10001900 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10001900 0x24>;
+               brcm,serial-leds;
+               brcm,serial-dat-low;
+               brcm,serial-shift-inv;
+
+               inet_red@2 {
+                       reg = <2>;
+                       active-low;
+                       label = "red:inet";
+               };
+               dsl_green@3 {
+                       reg = <3>;
+                       active-low;
+                       label = "green:dsl";
+               };
+               usb_green@4 {
+                       reg = <4>;
+                       active-low;
+                       label = "green:usb";
+               };
+               wps_green@7 {
+                       reg = <7>;
+                       active-low;
+                       label = "green:wps";
+               };
+               inet_green@8 {
+                       reg = <8>;
+                       active-low;
+                       label = "green:inet";
+               };
+               power_green@20 {
+                       reg = <20>;
+                       active-low;
+                       label = "green:power";
+               };
+       };
index 309372ab56484509694d3f5401c73a8053b53a67..dc19f4fc40865c859fe7277eff2a8a5af4869e56 100644 (file)
@@ -9,6 +9,17 @@ config LED
          can provide access to board-specific LEDs. Use of the device tree
          for configuration is encouraged.
 
+config LED_BCM6328
+       bool "LED Support for BCM6328"
+       depends on LED && ARCH_BMIPS
+       help
+         This option enables support for LEDs connected to the BCM6328
+         LED HW controller accessed via MMIO registers.
+         HW blinking is supported and up to 24 LEDs can be controlled.
+         All LEDs can blink at the same time but the delay is shared, which
+         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_BLINK
        bool "Support LED blinking"
        depends on LED
index 02367fdacbc10e77abbe51979b2a40bc1fa2bc56..d371ed55d908e97121f7bfdfe591ed618d7409f4 100644 (file)
@@ -6,4 +6,5 @@
 #
 
 obj-y += led-uclass.o
+obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o
 obj-$(CONFIG_$(SPL_)LED_GPIO) += led_gpio.o
diff --git a/drivers/led/led_bcm6328.c b/drivers/led/led_bcm6328.c
new file mode 100644 (file)
index 0000000..ef8c6a7
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * 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                       24
+
+/* LED Init register */
+#define LED_INIT_REG                   0x00
+#define LED_INIT_FASTINTV_MS           20
+#define LED_INIT_FASTINTV_SHIFT                6
+#define LED_INIT_FASTINTV_MASK         (0x3f << LED_INIT_FASTINTV_SHIFT)
+#define LED_INIT_SLEDEN_SHIFT          12
+#define LED_INIT_SLEDEN_MASK           (1 << LED_INIT_SLEDEN_SHIFT)
+#define LED_INIT_SLEDMUX_SHIFT         13
+#define LED_INIT_SLEDMUX_MASK          (1 << LED_INIT_SLEDMUX_SHIFT)
+#define LED_INIT_SLEDCLKNPOL_SHIFT     14
+#define LED_INIT_SLEDCLKNPOL_MASK      (1 << LED_INIT_SLEDCLKNPOL_SHIFT)
+#define LED_INIT_SLEDDATAPPOL_SHIFT    15
+#define LED_INIT_SLEDDATANPOL_MASK     (1 << LED_INIT_SLEDDATAPPOL_SHIFT)
+#define LED_INIT_SLEDSHIFTDIR_SHIFT    16
+#define LED_INIT_SLEDSHIFTDIR_MASK     (1 << LED_INIT_SLEDSHIFTDIR_SHIFT)
+
+/* LED Mode registers */
+#define LED_MODE_REG_HI                        0x04
+#define LED_MODE_REG_LO                        0x08
+#define LED_MODE_ON                    0
+#define LED_MODE_FAST                  1
+#define LED_MODE_BLINK                 2
+#define LED_MODE_OFF                   3
+#define LED_MODE_MASK                  0x3
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct bcm6328_led_priv {
+       void __iomem *regs;
+       void __iomem *mode;
+       uint8_t shift;
+       bool active_low;
+};
+
+static unsigned long bcm6328_led_get_mode(struct bcm6328_led_priv *priv)
+{
+       return ((readl_be(priv->mode) >> priv->shift) & LED_MODE_MASK);
+}
+
+static int bcm6328_led_set_mode(struct bcm6328_led_priv *priv, uint8_t mode)
+{
+       clrsetbits_be32(priv->mode, (LED_MODE_MASK << priv->shift),
+                       (mode << priv->shift));
+
+       return 0;
+}
+
+static enum led_state_t bcm6328_led_get_state(struct udevice *dev)
+{
+       struct bcm6328_led_priv *priv = dev_get_priv(dev);
+       enum led_state_t state = LEDST_OFF;
+
+       switch (bcm6328_led_get_mode(priv)) {
+#ifdef CONFIG_LED_BLINK
+       case LED_MODE_BLINK:
+       case LED_MODE_FAST:
+               state = LEDST_BLINK;
+               break;
+#endif
+       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 bcm6328_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+       struct bcm6328_led_priv *priv = dev_get_priv(dev);
+       unsigned long mode;
+
+       switch (state) {
+#ifdef CONFIG_LED_BLINK
+       case LEDST_BLINK:
+               mode = LED_MODE_BLINK;
+               break;
+#endif
+       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 (bcm6328_led_get_state(dev) == LEDST_OFF)
+                       return bcm6328_led_set_state(dev, LEDST_ON);
+               else
+                       return bcm6328_led_set_state(dev, LEDST_OFF);
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return bcm6328_led_set_mode(priv, mode);
+}
+
+#ifdef CONFIG_LED_BLINK
+static unsigned long bcm6328_blink_delay(int delay)
+{
+       unsigned long bcm6328_delay = delay;
+
+       bcm6328_delay += (LED_INIT_FASTINTV_MS / 2);
+       bcm6328_delay /= LED_INIT_FASTINTV_MS;
+       bcm6328_delay <<= LED_INIT_FASTINTV_SHIFT;
+
+       if (bcm6328_delay > LED_INIT_FASTINTV_MASK)
+               return LED_INIT_FASTINTV_MASK;
+       else
+               return bcm6328_delay;
+}
+
+static int bcm6328_led_set_period(struct udevice *dev, int period_ms)
+{
+       struct bcm6328_led_priv *priv = dev_get_priv(dev);
+
+       clrsetbits_be32(priv->regs + LED_INIT_REG, LED_INIT_FASTINTV_MASK,
+                       bcm6328_blink_delay(period_ms));
+
+       return 0;
+}
+#endif
+
+static const struct led_ops bcm6328_led_ops = {
+       .get_state = bcm6328_led_get_state,
+       .set_state = bcm6328_led_set_state,
+#ifdef CONFIG_LED_BLINK
+       .set_period = bcm6328_led_set_period,
+#endif
+};
+
+static int bcm6328_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;
+               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,serial-leds"))
+                       set_bits |= LED_INIT_SLEDEN_MASK;
+               if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+                                   "brcm,serial-mux"))
+                       set_bits |= LED_INIT_SLEDMUX_MASK;
+               if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+                                   "brcm,serial-clk-low"))
+                       set_bits |= LED_INIT_SLEDCLKNPOL_MASK;
+               if (!fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+                                    "brcm,serial-dat-low"))
+                       set_bits |= LED_INIT_SLEDDATANPOL_MASK;
+               if (!fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+                                    "brcm,serial-shift-inv"))
+                       set_bits |= LED_INIT_SLEDSHIFTDIR_MASK;
+
+               clrsetbits_be32(regs + LED_INIT_REG, ~0, set_bits);
+       } else {
+               struct bcm6328_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);
+               if (pin < 8) {
+                       /* LEDs 0-7 (bits 47:32) */
+                       priv->mode = priv->regs + LED_MODE_REG_HI;
+                       priv->shift = (pin << 1);
+               } else {
+                       /* LEDs 8-23 (bits 31:0) */
+                       priv->mode = priv->regs + LED_MODE_REG_LO;
+                       priv->shift = ((pin - 8) << 1);
+               }
+
+               if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
+                                   "active-low"))
+                       priv->active_low = true;
+       }
+
+       return 0;
+}
+
+static int bcm6328_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, "bcm6328-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 bcm6328_led_ids[] = {
+       { .compatible = "brcm,bcm6328-leds" },
+       { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(bcm6328_led) = {
+       .name = "bcm6328-led",
+       .id = UCLASS_LED,
+       .of_match = bcm6328_led_ids,
+       .ops = &bcm6328_led_ops,
+       .bind = bcm6328_led_bind,
+       .probe = bcm6328_led_probe,
+       .priv_auto_alloc_size = sizeof(struct bcm6328_led_priv),
+};