| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com> |
| */ |
| |
| #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 = devfdt_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 = devfdt_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), |
| offset_to_ofnode(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, |
| }; |