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