// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2017 Álvaro Fernández Rojas */ #include #include #include #include #include #include #include #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 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_plat(dev); /* Top-level LED node */ if (!uc_plat->label) { void __iomem *regs; u32 set_bits = 0; regs = dev_remap_addr(dev); if (!regs) return -EINVAL; if (dev_read_bool(dev, "brcm,serial-leds")) set_bits |= LED_INIT_SLEDEN_MASK; if (dev_read_bool(dev, "brcm,serial-mux")) set_bits |= LED_INIT_SLEDMUX_MASK; if (dev_read_bool(dev, "brcm,serial-clk-low")) set_bits |= LED_INIT_SLEDCLKNPOL_MASK; if (!dev_read_bool(dev, "brcm,serial-dat-low")) set_bits |= LED_INIT_SLEDDATANPOL_MASK; if (!dev_read_bool(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; priv->regs = dev_remap_addr(dev_get_parent(dev)); if (!priv->regs) return -EINVAL; pin = dev_read_u32_default(dev, "reg", LEDS_MAX); if (pin >= LEDS_MAX) return -EINVAL; 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 (dev_read_bool(dev, "active-low")) priv->active_low = true; } return 0; } static int bcm6328_led_bind(struct udevice *parent) { ofnode node; dev_for_each_subnode(node, parent) { struct udevice *dev; int ret; ret = device_bind_driver_to_node(parent, "bcm6328-led", ofnode_get_name(node), node, &dev); if (ret) return ret; } 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 = sizeof(struct bcm6328_led_priv), };