]> git.sur5r.net Git - u-boot/blobdiff - drivers/led/led_bcm6358.c
dm: led: add BCM6358 led driver
[u-boot] / drivers / led / led_bcm6358.c
diff --git a/drivers/led/led_bcm6358.c b/drivers/led/led_bcm6358.c
new file mode 100644 (file)
index 0000000..11caecd
--- /dev/null
@@ -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,
+};