summaryrefslogtreecommitdiff
path: root/drivers/led
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/led')
-rw-r--r--drivers/led/Kconfig461
-rw-r--r--drivers/led/Makefile15
-rw-r--r--drivers/led/led-uclass.c413
-rw-r--r--drivers/led/led_bcm6328.c232
-rw-r--r--drivers/led/led_bcm6358.c202
-rw-r--r--drivers/led/led_bcm6753.c263
-rw-r--r--drivers/led/led_bcm6858.c275
-rw-r--r--drivers/led/led_cortina.c286
-rw-r--r--drivers/led/led_gpio.c108
-rw-r--r--drivers/led/led_lp5562.c575
-rw-r--r--drivers/led/led_pwm.c164
-rw-r--r--drivers/led/led_sw_blink.c131
12 files changed, 3125 insertions, 0 deletions
diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig
new file mode 100644
index 00000000000..c98cbf92fab
--- /dev/null
+++ b/drivers/led/Kconfig
@@ -0,0 +1,461 @@
+menu "LED Support"
+
+config LED
+ bool "Enable LED support"
+ depends on DM
+ help
+ Many boards have LEDs which can be used to signal status or alerts.
+ U-Boot provides a uclass API to implement this feature. LED drivers
+ can provide access to board-specific LEDs. Use of the device tree
+ for configuration is encouraged.
+
+config LED_BOOT
+ bool "Enable LED boot support"
+ help
+ Enable LED boot support.
+
+ LED boot is a specific LED assigned to signal boot operation status.
+ Defined in Device Tree /options/u-boot node. Refer here for the supported
+ options [1].
+
+ [1] dtschema/schemas/options/u-boot.yaml
+
+config LED_ACTIVITY
+ bool "Enable LED activity support"
+ help
+ Enable LED activity support.
+
+ LED activity is a specific LED assigned to signal activity operation
+ like file trasnfer, flash write/erase...
+
+ Defined in Device Tree /options/u-boot node. Refer here for the supported
+ options [1].
+
+ [1] dtschema/schemas/options/u-boot.yaml
+
+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_BCM6358
+ bool "LED Support for BCM6358"
+ depends on LED && ARCH_BMIPS
+ help
+ This option enables support for LEDs connected to the BCM6358
+ LED HW controller accessed via MMIO registers.
+ HW has no blinking capabilities and up to 32 LEDs can be controlled.
+
+config LED_BCM6753
+ bool "LED Support for BCM6753"
+ depends on LED && BCM6855
+ help
+ This option enables support for LEDs connected to the BCM6753
+ HW has blinking and fading capabilities and up to 32 LEDs can be controlled.
+
+config LED_BCM6858
+ bool "LED Support for BCM6858"
+ depends on LED && (BCM6856 || BCM6858 || BCM63158)
+ help
+ This option enables support for LEDs connected to the BCM6858
+ HW has blinking capabilities and up to 32 LEDs can be controlled.
+
+config LED_CORTINA
+ bool "LED Support for Cortina Access CAxxxx SoCs"
+ depends on LED && (CORTINA_PLATFORM)
+ help
+ This option enables support for LEDs connected to the Cortina
+ Access CAxxxx SOCs.
+
+config LED_LP5562
+ bool "LED Support for LP5562"
+ depends on LED && DM_I2C
+ help
+ This option enables support for LEDs connected to the TI LP5562
+ 4 channel I2C LED controller. Driver fully supports blink on the
+ B/G/R LEDs. White LED can blink, but re-uses the period from blue.
+
+config LED_PWM
+ bool "LED PWM"
+ depends on LED && DM_PWM
+ help
+ Enable support for LEDs connected to PWM.
+ Linux compatible ofdata.
+
+config LED_BLINK
+ bool "Support hardware LED blinking"
+ depends on LED
+ help
+ Some drivers can support automatic blinking of LEDs with a given
+ period, without needing timers or extra code to handle the timing.
+ This option enables support for this which adds slightly to the
+ code size.
+
+config LED_SW_BLINK
+ bool "Support software LED blinking"
+ depends on LED
+ select CYCLIC
+ help
+ Turns on led blinking implemented in the software, useful when
+ the hardware doesn't support led blinking. Half of the period
+ led will be ON and the rest time it will be OFF. Standard
+ led commands can be used to configure blinking. Does nothing
+ if driver supports hardware blinking.
+ WARNING: Blinking may be inaccurate during execution of time
+ consuming commands (ex. flash reading). Also it completely
+ stops during OS booting.
+
+config SPL_LED
+ bool "Enable LED support in SPL"
+ depends on SPL_DM
+ help
+ The LED subsystem adds a small amount of overhead to the image.
+ If this is acceptable and you have a need to use LEDs in SPL,
+ enable this option. You will need to enable device tree in SPL
+ for this to work.
+
+config LED_GPIO
+ bool "LED support for GPIO-connected LEDs"
+ depends on LED && DM_GPIO
+ help
+ Enable support for LEDs which are connected to GPIO lines. These
+ GPIOs may be on the SoC or some other device which provides GPIOs.
+ The GPIO driver must used driver model. LEDs are configured using
+ the device tree.
+
+config SPL_LED_GPIO
+ bool "LED support for GPIO-connected LEDs in SPL"
+ depends on SPL_LED && SPL_DM_GPIO
+ help
+ This option is an SPL-variant of the LED_GPIO option.
+ See the help of LED_GPIO for details.
+
+config LED_STATUS
+ bool "Enable status LED API"
+ help
+ Allows common u-boot commands to use a board's leds to
+ provide status for activities like booting and downloading files.
+
+if LED_STATUS
+
+# Hidden constants
+
+config LED_STATUS_OFF
+ int
+ default 0
+
+config LED_STATUS_BLINKING
+ int
+ default 1
+
+config LED_STATUS_ON
+ int
+ default 2
+
+# Hidden constants end
+
+config LED_STATUS_GPIO
+ bool "GPIO status LED implementation"
+ help
+ The status LED can be connected to a GPIO pin. In such cases, the
+ gpio_led driver can be used as a status LED backend implementation.
+
+config LED_STATUS_BOARD_SPECIFIC
+ bool "Specific board"
+ default y
+ help
+ LED support is only for a specific board.
+
+comment "LEDs parameters"
+
+config LED_STATUS0
+ bool "Enable status LED 0"
+
+if LED_STATUS0
+
+config LED_STATUS_BIT
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT is passed into the __led_* functions to identify
+ which LED is being acted on. As such, the chosen value must be unique
+ with respect to the other CONFIG_LED_STATUS_BIT's. Mapping the value
+ to a physical LED is the responsibility of the __led_* function.
+
+config LED_STATUS_STATE
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ:
+ LED_STATUS_PERIOD = CONFIG_SYS_HZ/LED_STATUS_FREQ
+ Values range: 2 - 10
+
+endif # LED_STATUS0
+
+config LED_STATUS1
+ bool "Enable status LED 1"
+
+if LED_STATUS1
+
+config LED_STATUS_BIT1
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT1 is passed into the __led_* functions to
+ identify which LED is being acted on. As such, the chosen value must
+ be unique with respect to the other CONFIG_LED_STATUS_BIT's. Mapping
+ the value to a physical LED is the responsibility of the __led_*
+ function.
+
+config LED_STATUS_STATE1
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ1
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ1:
+ LED_STATUS_PERIOD1 = CONFIG_SYS_HZ/LED_STATUS_FREQ1
+ Values range: 2 - 10
+
+endif # LED_STATUS1
+
+config LED_STATUS2
+ bool "Enable status LED 2"
+
+if LED_STATUS2
+
+config LED_STATUS_BIT2
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT2 is passed into the __led_* functions to
+ identify which LED is being acted on. As such, the chosen value must
+ be unique with respect to the other CONFIG_LED_STATUS_BIT's. Mapping
+ the value to a physical LED is the responsibility of the __led_*
+ function.
+
+config LED_STATUS_STATE2
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ2
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ2:
+ LED_STATUS_PERIOD2 = CONFIG_SYS_HZ/LED_STATUS_FREQ2
+ Values range: 2 - 10
+
+endif # LED_STATUS2
+
+config LED_STATUS3
+ bool "Enable status LED 3"
+
+if LED_STATUS3
+
+config LED_STATUS_BIT3
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT3 is passed into the __led_* functions to
+ identify which LED is being acted on. As such, the chosen value must
+ be unique with respect to the other CONFIG_LED_STATUS_BIT's. Mapping
+ the value to a physical LED is the responsibility of the __led_*
+ function.
+
+config LED_STATUS_STATE3
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ3
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ3:
+ LED_STATUS_PERIOD3 = CONFIG_SYS_HZ/LED_STATUS_FREQ3
+ Values range: 2 - 10
+
+endif # LED_STATUS3
+
+config LED_STATUS4
+ bool "Enable status LED 4"
+
+if LED_STATUS4
+
+config LED_STATUS_BIT4
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT4 is passed into the __led_* functions to
+ identify which LED is being acted on. As such, the chosen value must
+ be unique with respect to the other CONFIG_LED_STATUS_BIT's. Mapping
+ the value to a physical LED is the responsibility of the __led_*
+ function.
+
+config LED_STATUS_STATE4
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ4
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ4:
+ LED_STATUS_PERIOD4 = CONFIG_SYS_HZ/LED_STATUS_FREQ4
+ Values range: 2 - 10
+
+endif # LED_STATUS4
+
+config LED_STATUS5
+ bool "Enable status LED 5"
+
+if LED_STATUS5
+
+config LED_STATUS_BIT5
+ int "identification"
+ help
+ CONFIG_LED_STATUS_BIT5 is passed into the __led_* functions to
+ identify which LED is being acted on. As such, the chosen value must
+ be unique with respect to the other CONFIG_LED_STATUS_BIT's. Mapping
+ the value to a physical LED is the responsibility of the __led_*
+ function.
+
+config LED_STATUS_STATE5
+ int "initial state"
+ range LED_STATUS_OFF LED_STATUS_ON
+ default LED_STATUS_OFF
+ help
+ Should be set one of the following:
+ 0 - off
+ 1 - blinking
+ 2 - on
+
+config LED_STATUS_FREQ5
+ int "blink frequency"
+ range 2 10
+ default 2
+ help
+ The LED blink period calculated from LED_STATUS_FREQ5:
+ LED_STATUS_PERIOD5 = CONFIG_SYS_HZ/LED_STATUS_FREQ5
+ Values range: 2 - 10
+
+endif # LED_STATUS5
+
+config LED_STATUS_BOOT_ENABLE
+ bool "Enable BOOT LED"
+ help
+ Enable to turn an LED on when the board is booting.
+
+if LED_STATUS_BOOT_ENABLE
+
+config LED_STATUS_BOOT
+ int "LED to light when the board is booting"
+ help
+ Valid enabled LED device number.
+
+endif # LED_STATUS_BOOT_ENABLE
+
+config LED_STATUS_RED_ENABLE
+ bool "Enable red LED"
+ help
+ Enable red status LED.
+
+if LED_STATUS_RED_ENABLE
+
+config LED_STATUS_RED
+ int "Red LED identification"
+ help
+ Valid enabled LED device number.
+
+endif # LED_STATUS_RED_ENABLE
+
+config LED_STATUS_YELLOW_ENABLE
+ bool "Enable yellow LED"
+ help
+ Enable yellow status LED.
+
+if LED_STATUS_YELLOW_ENABLE
+
+config LED_STATUS_YELLOW
+ int "Yellow LED identification"
+ help
+ Valid enabled LED device number.
+
+endif # LED_STATUS_YELLOW_ENABLE
+
+config LED_STATUS_BLUE_ENABLE
+ bool "Enable blue LED"
+ help
+ Enable blue status LED.
+
+if LED_STATUS_BLUE_ENABLE
+
+config LED_STATUS_BLUE
+ int "Blue LED identification"
+ help
+ Valid enabled LED device number.
+
+endif # LED_STATUS_BLUE_ENABLE
+
+config LED_STATUS_GREEN_ENABLE
+ bool "Enable green LED"
+ help
+ Enable green status LED.
+
+if LED_STATUS_GREEN_ENABLE
+
+config LED_STATUS_GREEN
+ int "Green LED identification"
+ help
+ Valid enabled LED device number (0-5).
+
+endif # LED_STATUS_GREEN_ENABLE
+
+config LED_STATUS_CMD
+ bool "Enable status LED commands"
+
+endif # LED_STATUS
+
+endmenu
diff --git a/drivers/led/Makefile b/drivers/led/Makefile
new file mode 100644
index 00000000000..996753b88ae
--- /dev/null
+++ b/drivers/led/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2015 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+
+obj-y += led-uclass.o
+obj-$(CONFIG_LED_SW_BLINK) += led_sw_blink.o
+obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o
+obj-$(CONFIG_LED_BCM6358) += led_bcm6358.o
+obj-$(CONFIG_LED_BCM6753) += led_bcm6753.o
+obj-$(CONFIG_LED_BCM6858) += led_bcm6858.o
+obj-$(CONFIG_LED_PWM) += led_pwm.o
+obj-$(CONFIG_$(PHASE_)LED_GPIO) += led_gpio.o
+obj-$(CONFIG_LED_CORTINA) += led_cortina.o
+obj-$(CONFIG_LED_LP5562) += led_lp5562.o
diff --git a/drivers/led/led-uclass.c b/drivers/led/led-uclass.c
new file mode 100644
index 00000000000..edcdeee1e9a
--- /dev/null
+++ b/drivers/led/led-uclass.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_LED
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+#include <dm/uclass-internal.h>
+#include <dt-bindings/leds/common.h>
+
+static const char * const led_colors[LED_COLOR_ID_MAX] = {
+ [LED_COLOR_ID_WHITE] = "white",
+ [LED_COLOR_ID_RED] = "red",
+ [LED_COLOR_ID_GREEN] = "green",
+ [LED_COLOR_ID_BLUE] = "blue",
+ [LED_COLOR_ID_AMBER] = "amber",
+ [LED_COLOR_ID_VIOLET] = "violet",
+ [LED_COLOR_ID_YELLOW] = "yellow",
+ [LED_COLOR_ID_IR] = "ir",
+ [LED_COLOR_ID_MULTI] = "multicolor",
+ [LED_COLOR_ID_RGB] = "rgb",
+ [LED_COLOR_ID_PURPLE] = "purple",
+ [LED_COLOR_ID_ORANGE] = "orange",
+ [LED_COLOR_ID_PINK] = "pink",
+ [LED_COLOR_ID_CYAN] = "cyan",
+ [LED_COLOR_ID_LIME] = "lime",
+};
+
+int led_bind_generic(struct udevice *parent, const char *driver_name)
+{
+ struct udevice *dev;
+ ofnode node;
+ int ret;
+
+ dev_for_each_subnode(node, parent) {
+ ret = device_bind_driver_to_node(parent, driver_name,
+ ofnode_get_name(node),
+ node, &dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int led_get_by_label(const char *label, struct udevice **devp)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_LED, &uc);
+ if (ret)
+ return ret;
+ uclass_foreach_dev(dev, uc) {
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+
+ /* Ignore the top-level LED node */
+ if (uc_plat->label && !strcmp(label, uc_plat->label))
+ return uclass_get_device_tail(dev, 0, devp);
+ }
+
+ return -ENODEV;
+}
+
+int led_set_state(struct udevice *dev, enum led_state_t state)
+{
+ struct led_ops *ops = led_get_ops(dev);
+
+ if (!ops->set_state)
+ return -ENOSYS;
+
+ if (IS_ENABLED(CONFIG_LED_SW_BLINK) &&
+ led_sw_on_state_change(dev, state))
+ return 0;
+
+ return ops->set_state(dev, state);
+}
+
+enum led_state_t led_get_state(struct udevice *dev)
+{
+ struct led_ops *ops = led_get_ops(dev);
+
+ if (!ops->get_state)
+ return -ENOSYS;
+
+ if (IS_ENABLED(CONFIG_LED_SW_BLINK) &&
+ led_sw_is_blinking(dev))
+ return LEDST_BLINK;
+
+ return ops->get_state(dev);
+}
+
+int led_set_period(struct udevice *dev, int period_ms)
+{
+#ifdef CONFIG_LED_BLINK
+ struct led_ops *ops = led_get_ops(dev);
+
+ if (ops->set_period)
+ return ops->set_period(dev, period_ms);
+#endif
+
+ if (IS_ENABLED(CONFIG_LED_SW_BLINK))
+ return led_sw_set_period(dev, period_ms);
+
+ return -ENOSYS;
+}
+
+#ifdef CONFIG_LED_BOOT
+static int led_boot_get(struct udevice **devp, int *period_ms)
+{
+ struct led_uc_priv *priv;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_LED, &uc);
+ if (ret)
+ return ret;
+
+ priv = uclass_get_priv(uc);
+ if (!priv->boot_led_label)
+ return -ENOENT;
+
+ if (period_ms)
+ *period_ms = priv->boot_led_period;
+
+ return led_get_by_label(priv->boot_led_label, devp);
+}
+
+int led_boot_on(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = led_boot_get(&dev, NULL);
+ if (ret)
+ return ret;
+
+ return led_set_state(dev, LEDST_ON);
+}
+
+int led_boot_off(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = led_boot_get(&dev, NULL);
+ if (ret)
+ return ret;
+
+ return led_set_state(dev, LEDST_OFF);
+}
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+int led_boot_blink(void)
+{
+ struct udevice *dev;
+ int period_ms, ret;
+
+ ret = led_boot_get(&dev, &period_ms);
+ if (ret)
+ return ret;
+
+ ret = led_set_period(dev, period_ms);
+ if (ret) {
+ if (ret != -ENOSYS)
+ return ret;
+
+ /* fallback to ON with no set_period and no SW_BLINK */
+ return led_set_state(dev, LEDST_ON);
+ }
+
+ return led_set_state(dev, LEDST_BLINK);
+}
+#endif
+#endif
+
+#ifdef CONFIG_LED_ACTIVITY
+static int led_activity_get(struct udevice **devp, int *period_ms)
+{
+ struct led_uc_priv *priv;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_LED, &uc);
+ if (ret)
+ return ret;
+
+ priv = uclass_get_priv(uc);
+ if (!priv->activity_led_label)
+ return -ENOENT;
+
+ if (period_ms)
+ *period_ms = priv->activity_led_period;
+
+ return led_get_by_label(priv->activity_led_label, devp);
+}
+
+int led_activity_on(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = led_activity_get(&dev, NULL);
+ if (ret)
+ return ret;
+
+ return led_set_state(dev, LEDST_ON);
+}
+
+int led_activity_off(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = led_activity_get(&dev, NULL);
+ if (ret)
+ return ret;
+
+ return led_set_state(dev, LEDST_OFF);
+}
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+int led_activity_blink(void)
+{
+ struct udevice *dev;
+ int period_ms, ret;
+
+ ret = led_activity_get(&dev, &period_ms);
+ if (ret)
+ return ret;
+
+ ret = led_set_period(dev, period_ms);
+ if (ret) {
+ if (ret != -ENOSYS)
+ return ret;
+
+ /* fallback to ON with no set_period and no SW_BLINK */
+ return led_set_state(dev, LEDST_ON);
+ }
+
+ return led_set_state(dev, LEDST_BLINK);
+}
+#endif
+#endif
+
+static const char *led_get_function_name(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat;
+ const char *func;
+ u32 color;
+ u32 enumerator;
+ int ret;
+ int cp;
+
+ if (!dev)
+ return NULL;
+
+ uc_plat = dev_get_uclass_plat(dev);
+ if (!uc_plat)
+ return NULL;
+
+ if (uc_plat->label)
+ return uc_plat->label;
+
+ /* Now try to detect function label name */
+ func = dev_read_string(dev, "function");
+ cp = dev_read_u32(dev, "color", &color);
+ /*
+ * prevent coverity scan error CID 541279: (TAINTED_SCALAR)
+ * only check the upper bound. No need to check the lower bound
+ * as color is from type u32 and never can be lower than 0.
+ */
+ if (color >= LED_COLOR_ID_MAX)
+ cp = -EINVAL;
+
+ if (cp == 0 || func) {
+ ret = dev_read_u32(dev, "function-enumerator", &enumerator);
+ if (!ret) {
+ snprintf(uc_plat->name, LED_MAX_NAME_SIZE,
+ "%s:%s-%d",
+ cp ? "" : led_colors[color],
+ func ? func : "", enumerator);
+ } else {
+ snprintf(uc_plat->name, LED_MAX_NAME_SIZE,
+ "%s:%s",
+ cp ? "" : led_colors[color],
+ func ? func : "");
+ }
+ uc_plat->label = uc_plat->name;
+ }
+
+ return uc_plat->label;
+}
+
+static const char *led_get_label(struct udevice *dev, ofnode node)
+{
+ const char *label;
+
+ label = ofnode_read_string(node, "label");
+ if (!label)
+ label = led_get_function_name(dev);
+ if (!label && !ofnode_read_string(node, "compatible"))
+ label = ofnode_get_name(node);
+
+ return label;
+}
+
+static int led_post_bind(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+ const char *default_state;
+
+ if (!uc_plat->label)
+ uc_plat->label = led_get_label(dev, dev_ofnode(dev));
+
+ uc_plat->default_state = LEDST_COUNT;
+
+ default_state = dev_read_string(dev, "default-state");
+ if (!default_state)
+ return 0;
+
+ if (!strncmp(default_state, "on", 2))
+ uc_plat->default_state = LEDST_ON;
+ else if (!strncmp(default_state, "off", 3))
+ uc_plat->default_state = LEDST_OFF;
+ else
+ return 0;
+
+ if (IS_ENABLED(CONFIG_LED_BLINK)) {
+ const char *trigger;
+
+ trigger = dev_read_string(dev, "linux,default-trigger");
+ if (trigger && !strncmp(trigger, "pattern", 7))
+ uc_plat->default_state = LEDST_BLINK;
+ }
+
+ /*
+ * In case the LED has default-state DT property, trigger
+ * probe() to configure its default state during startup.
+ */
+ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+
+ return 0;
+}
+
+static int led_post_probe(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+ int default_period_ms = 1000;
+ int ret = 0;
+
+ switch (uc_plat->default_state) {
+ case LEDST_ON:
+ case LEDST_OFF:
+ ret = led_set_state(dev, uc_plat->default_state);
+ break;
+ case LEDST_BLINK:
+ ret = led_set_period(dev, default_period_ms);
+ if (!ret)
+ ret = led_set_state(dev, uc_plat->default_state);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+#if defined(CONFIG_LED_BOOT) || defined(CONFIG_LED_ACTIVITY)
+static int led_init(struct uclass *uc)
+{
+ struct led_uc_priv *priv = uclass_get_priv(uc);
+ ofnode led_node;
+ int ret;
+
+#ifdef CONFIG_LED_BOOT
+ ret = ofnode_options_get_by_phandle("boot-led", &led_node);
+ if (!ret)
+ priv->boot_led_label = led_get_label(NULL, led_node);
+ priv->boot_led_period = ofnode_options_read_int("boot-led-period-ms", 250);
+#endif
+
+#ifdef CONFIG_LED_ACTIVITY
+ ret = ofnode_options_get_by_phandle("activity-led", &led_node);
+ if (!ret)
+ priv->activity_led_label = led_get_label(NULL, led_node);
+ priv->activity_led_period = ofnode_options_read_int("activity-led-period-ms",
+ 250);
+#endif
+
+ return 0;
+}
+#endif
+
+UCLASS_DRIVER(led) = {
+ .id = UCLASS_LED,
+ .name = "led",
+ .per_device_plat_auto = sizeof(struct led_uc_plat),
+ .post_bind = led_post_bind,
+ .post_probe = led_post_probe,
+#if defined(CONFIG_LED_BOOT) || defined(CONFIG_LED_ACTIVITY)
+ .init = led_init,
+ .priv_auto = sizeof(struct led_uc_priv),
+#endif
+};
diff --git a/drivers/led/led_bcm6328.c b/drivers/led/led_bcm6328.c
new file mode 100644
index 00000000000..dcc5741195c
--- /dev/null
+++ b/drivers/led/led_bcm6328.c
@@ -0,0 +1,232 @@
+// 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),
+};
diff --git a/drivers/led/led_bcm6358.c b/drivers/led/led_bcm6358.c
new file mode 100644
index 00000000000..b1373ab7426
--- /dev/null
+++ b/drivers/led/led_bcm6358.c
@@ -0,0 +1,202 @@
+// 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>
+#include <linux/delay.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)
+
+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_plat(dev);
+
+ /* Top-level LED node */
+ if (!uc_plat->label) {
+ void __iomem *regs;
+ unsigned int clk_div;
+ u32 set_bits = 0;
+
+ regs = dev_remap_addr(dev);
+ if (!regs)
+ return -EINVAL;
+
+ if (dev_read_bool(dev, "brcm,clk-dat-low"))
+ set_bits |= LED_CTRL_POL_MASK;
+ clk_div = dev_read_u32_default(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;
+
+ priv->regs = dev_remap_addr(dev);
+ if (!priv->regs)
+ return -EINVAL;
+
+ pin = dev_read_u32_default(dev, "reg", LEDS_MAX);
+ if (pin >= LEDS_MAX)
+ return -EINVAL;
+
+ priv->pin = pin;
+
+ if (dev_read_bool(dev, "active-low"))
+ priv->active_low = true;
+ }
+
+ return 0;
+}
+
+static int bcm6358_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, "bcm6358-led",
+ ofnode_get_name(node),
+ node, &dev);
+ if (ret)
+ return ret;
+ }
+
+ 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 = sizeof(struct bcm6358_led_priv),
+ .ops = &bcm6358_led_ops,
+};
diff --git a/drivers/led/led_bcm6753.c b/drivers/led/led_bcm6753.c
new file mode 100644
index 00000000000..170caf7bdca
--- /dev/null
+++ b/drivers/led/led_bcm6753.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Philippe Reynes <philippe.reynes@softathome.com>
+ *
+ * based on:
+ * drivers/led/led_bcm6858.c
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <log.h>
+#include <asm/io.h>
+#include <dm/lists.h>
+#include <linux/bitops.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 Controller Global settings register */
+#define CLED_CTRL_REG 0x00
+#define CLED_CTRL_SERIAL_LED_DATA_PPOL BIT(1)
+#define CLED_CTRL_SERIAL_LED_CLK_POL BIT(2)
+#define CLED_CTRL_SERIAL_LED_EN_POL BIT(3)
+#define CLED_CTRL_SERIAL_LED_MSB_FIRST BIT(4)
+#define CLED_CTRL_MASK 0x1E
+/* LED Controller IP LED source select register */
+#define CLED_HW_LED_EN_REG 0x04
+/* Hardware LED Polarity register */
+#define CLED_HW_LED_IP_PPOL_REG 0x0c
+/* Soft LED Set Register */
+#define CLED_SW_LED_IP_SET_REG 0x10
+/* Parallel LED Output Polarity Register */
+#define CLED_PLED_OP_PPOL_REG 0x18
+/* LED Channel activate register */
+#define CLED_LED_CH_ACTIVATE_REG 0x1c
+/* LED 0 Config 0 reg */
+#define CLED_LED_0_CONFIG_0 0x20
+/* Soft LED Clear Register */
+#define CLED_SW_LED_IP_CLEAR_REG 0x444
+/* Soft LED Status Register */
+#define CLED_SW_LED_IP_STATUS_REG 0x448
+
+/* Size of all registers used for the config of one LED */
+#define CLED_CONFIG_SIZE (4 * sizeof(u32))
+
+#define CLED_CONFIG0_MODE 0
+#define CLED_CONFIG0_MODE_MASK (BIT(0) | BIT(1))
+#define CLED_CONFIG0_MODE_STEADY 0
+#define CLED_CONFIG0_MODE_FADING 1
+#define CLED_CONFIG0_MODE_PULSATING 2
+
+#define CLED_CONFIG0_FLASH_CTRL_SHIFT 3
+#define CLED_CONFIG0_FLASH_CTRL_MASK (BIT(3) | BIT(4) | BIT(5))
+
+struct bcm6753_led_priv {
+ void __iomem *regs;
+ u8 pin;
+};
+
+/*
+ * The value for flash rate are:
+ * 0 : no blinking
+ * 1 : rate is 25 Hz => 40 ms (period)
+ * 2 : rate is 12.5 Hz => 80 ms (period)
+ * 3 : rate is 6.25 Hz => 160 ms (period)
+ * 4 : rate is 3.125 Hz => 320 ms (period)
+ * 5 : rate is 1.5625 Hz => 640 ms (period)
+ * 6 : rate is 0.7815 Hz => 1280 ms (period)
+ * 7 : rate is 0.390625 Hz => 2560 ms (period)
+ */
+static const int bcm6753_flash_rate[8] = {
+ 0, 40, 80, 160, 320, 640, 1280, 2560
+};
+
+static u32 bcm6753_flash_rate_value(int period_ms)
+{
+ unsigned long value = 7;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bcm6753_flash_rate); i++) {
+ if (period_ms <= bcm6753_flash_rate[i]) {
+ value = i;
+ break;
+ }
+ }
+
+ return value;
+}
+
+static int bcm6753_led_set_period(struct udevice *dev, int period_ms)
+{
+ struct bcm6753_led_priv *priv = dev_get_priv(dev);
+ u32 offset, shift, value;
+
+ offset = CLED_LED_0_CONFIG_0 + (CLED_CONFIG_SIZE * priv->pin);
+ value = bcm6753_flash_rate_value(period_ms);
+ shift = CLED_CONFIG0_FLASH_CTRL_SHIFT;
+
+ /* set mode steady */
+ clrbits_32(priv->regs + offset, CLED_CONFIG0_MODE_MASK);
+ setbits_32(priv->regs + offset, CLED_CONFIG0_MODE_STEADY);
+
+ /* set flash rate */
+ clrbits_32(priv->regs + offset, CLED_CONFIG0_FLASH_CTRL_MASK);
+ setbits_32(priv->regs + offset, value << shift);
+
+ /* enable config */
+ setbits_32(priv->regs + CLED_LED_CH_ACTIVATE_REG, 1 << priv->pin);
+
+ return 0;
+}
+
+static enum led_state_t bcm6753_led_get_state(struct udevice *dev)
+{
+ struct bcm6753_led_priv *priv = dev_get_priv(dev);
+ enum led_state_t state = LEDST_OFF;
+ u32 sw_led_ip_status;
+
+ sw_led_ip_status = readl(priv->regs + CLED_SW_LED_IP_STATUS_REG);
+ if (sw_led_ip_status & (1 << priv->pin))
+ state = LEDST_ON;
+
+ return state;
+}
+
+static int bcm6753_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+ struct bcm6753_led_priv *priv = dev_get_priv(dev);
+
+ switch (state) {
+ case LEDST_OFF:
+ setbits_32(priv->regs + CLED_SW_LED_IP_CLEAR_REG, (1 << priv->pin));
+ if (IS_ENABLED(CONFIG_LED_BLINK))
+ bcm6753_led_set_period(dev, 0);
+ break;
+ case LEDST_ON:
+ setbits_32(priv->regs + CLED_SW_LED_IP_SET_REG, (1 << priv->pin));
+ if (IS_ENABLED(CONFIG_LED_BLINK))
+ bcm6753_led_set_period(dev, 0);
+ break;
+ case LEDST_TOGGLE:
+ if (bcm6753_led_get_state(dev) == LEDST_OFF)
+ return bcm6753_led_set_state(dev, LEDST_ON);
+ else
+ return bcm6753_led_set_state(dev, LEDST_OFF);
+ break;
+#ifdef CONFIG_LED_BLINK
+ case LEDST_BLINK:
+ setbits_32(priv->regs + CLED_SW_LED_IP_SET_REG, (1 << priv->pin));
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct led_ops bcm6753_led_ops = {
+ .get_state = bcm6753_led_get_state,
+ .set_state = bcm6753_led_set_state,
+#ifdef CONFIG_LED_BLINK
+ .set_period = bcm6753_led_set_period,
+#endif
+};
+
+static int bcm6753_led_probe(struct udevice *dev)
+{
+ struct bcm6753_led_priv *priv = dev_get_priv(dev);
+ void __iomem *regs;
+ unsigned int pin;
+
+ regs = dev_remap_addr(dev_get_parent(dev));
+ if (!regs)
+ return -EINVAL;
+
+ pin = dev_read_u32_default(dev, "reg", LEDS_MAX);
+ if (pin >= LEDS_MAX)
+ return -EINVAL;
+
+ priv->regs = regs;
+ priv->pin = pin;
+
+ /* this led is managed by software */
+ clrbits_32(regs + CLED_HW_LED_EN_REG, 1 << pin);
+
+ /* configure the polarity */
+ if (dev_read_bool(dev, "active-low"))
+ clrbits_32(regs + CLED_PLED_OP_PPOL_REG, 1 << pin);
+ else
+ setbits_32(regs + CLED_PLED_OP_PPOL_REG, 1 << pin);
+
+ return 0;
+}
+
+U_BOOT_DRIVER(bcm6753_led) = {
+ .name = "bcm6753-led",
+ .id = UCLASS_LED,
+ .probe = bcm6753_led_probe,
+ .priv_auto = sizeof(struct bcm6753_led_priv),
+ .ops = &bcm6753_led_ops,
+};
+
+static int bcm6753_led_wrap_probe(struct udevice *dev)
+{
+ void __iomem *regs;
+ u32 set_bits = 0;
+
+ regs = dev_remap_addr(dev);
+ if (!regs)
+ return -EINVAL;
+
+ if (dev_read_bool(dev, "brcm,serial-led-msb-first"))
+ set_bits |= CLED_CTRL_SERIAL_LED_MSB_FIRST;
+ if (dev_read_bool(dev, "brcm,serial-led-en-pol"))
+ set_bits |= CLED_CTRL_SERIAL_LED_EN_POL;
+ if (dev_read_bool(dev, "brcm,serial-led-clk-pol"))
+ set_bits |= CLED_CTRL_SERIAL_LED_CLK_POL;
+ if (dev_read_bool(dev, "brcm,serial-led-data-ppol"))
+ set_bits |= CLED_CTRL_SERIAL_LED_DATA_PPOL;
+
+ clrsetbits_32(regs + CLED_CTRL_REG, CLED_CTRL_MASK, set_bits);
+
+ return 0;
+}
+
+static int bcm6753_led_wrap_bind(struct udevice *parent)
+{
+ ofnode node;
+
+ dev_for_each_subnode(node, parent) {
+ struct udevice *dev;
+ int ret;
+
+ ret = device_bind_driver_to_node(parent, "bcm6753-led",
+ ofnode_get_name(node),
+ node, &dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id bcm6753_led_ids[] = {
+ { .compatible = "brcm,bcm6753-leds" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(bcm6753_led_wrap) = {
+ .name = "bcm6753_led_wrap",
+ .id = UCLASS_NOP,
+ .of_match = bcm6753_led_ids,
+ .probe = bcm6753_led_wrap_probe,
+ .bind = bcm6753_led_wrap_bind,
+};
diff --git a/drivers/led/led_bcm6858.c b/drivers/led/led_bcm6858.c
new file mode 100644
index 00000000000..a6efdcf6405
--- /dev/null
+++ b/drivers/led/led_bcm6858.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Philippe Reynes <philippe.reynes@softathome.com>
+ *
+ * based on:
+ * drivers/led/led_bcm6328.c
+ * drivers/led/led_bcm6358.c
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <log.h>
+#include <asm/io.h>
+#include <dm/lists.h>
+#include <linux/bitops.h>
+
+#define LEDS_MAX 32
+#define LEDS_WAIT 100
+#define LEDS_MAX_BRIGHTNESS 7
+
+/* LED Mode register */
+#define LED_MODE_REG 0x0
+#define LED_MODE_OFF 0
+#define LED_MODE_ON 1
+#define LED_MODE_MASK 1
+
+/* LED Controller Global settings register */
+#define LED_CTRL_REG 0x00
+#define LED_CTRL_MASK 0x1f
+#define LED_CTRL_LED_TEST_MODE BIT(0)
+#define LED_CTRL_SERIAL_LED_DATA_PPOL BIT(1)
+#define LED_CTRL_SERIAL_LED_CLK_POL BIT(2)
+#define LED_CTRL_SERIAL_LED_EN_POL BIT(3)
+#define LED_CTRL_SERIAL_LED_MSB_FIRST BIT(4)
+
+/* LED Controller IP LED source select register */
+#define LED_HW_LED_EN_REG 0x08
+/* LED Flash control register0 */
+#define LED_FLASH_RATE_CONTROL_REG0 0x10
+/* LED Brightness control register0 */
+#define LED_BRIGHTNESS_CONTROL_REG0 0x20
+/* Soft LED input register */
+#define LED_SW_LED_IP_REG 0xb8
+/* Parallel LED Output Polarity Register */
+#define LED_PLED_OP_PPOL_REG 0xc0
+
+struct bcm6858_led_priv {
+ void __iomem *regs;
+ u8 pin;
+};
+
+#ifdef CONFIG_LED_BLINK
+/*
+ * The value for flash rate are:
+ * 0 : no blinking
+ * 1 : rate is 25 Hz => 40 ms (period)
+ * 2 : rate is 12.5 Hz => 80 ms (period)
+ * 3 : rate is 6.25 Hz => 160 ms (period)
+ * 4 : rate is 3.125 Hz => 320 ms (period)
+ * 5 : rate is 1.5625 Hz => 640 ms (period)
+ * 6 : rate is 0.7815 Hz => 1280 ms (period)
+ * 7 : rate is 0.390625 Hz => 2560 ms (period)
+ */
+static const int bcm6858_flash_rate[8] = {
+ 0, 40, 80, 160, 320, 640, 1280, 2560
+};
+
+static u32 bcm6858_flash_rate_value(int period_ms)
+{
+ unsigned long value = 7;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bcm6858_flash_rate); i++) {
+ if (period_ms <= bcm6858_flash_rate[i]) {
+ value = i;
+ break;
+ }
+ }
+
+ return value;
+}
+
+static int bcm6858_led_set_period(struct udevice *dev, int period_ms)
+{
+ struct bcm6858_led_priv *priv = dev_get_priv(dev);
+ u32 offset, shift, mask, value;
+
+ offset = (priv->pin / 8) * 4;
+ shift = (priv->pin % 8) * 4;
+ mask = 0x7 << shift;
+ value = bcm6858_flash_rate_value(period_ms) << shift;
+
+ clrbits_32(priv->regs + LED_FLASH_RATE_CONTROL_REG0 + offset, mask);
+ setbits_32(priv->regs + LED_FLASH_RATE_CONTROL_REG0 + offset, value);
+
+ return 0;
+}
+#endif
+
+static int led_set_brightness(struct udevice *dev, unsigned int brightness)
+{
+ struct bcm6858_led_priv *priv = dev_get_priv(dev);
+ u32 offset, shift, mask, value;
+
+ offset = (priv->pin / 8) * 4;
+ shift = (priv->pin % 8) * 4;
+ mask = 0xf << shift;
+
+ /* 8 levels of brightness achieved through PWM */
+ value = (brightness > LEDS_MAX_BRIGHTNESS ?
+ LEDS_MAX_BRIGHTNESS : brightness) << shift;
+
+ debug("%s: %s brightness set to %u\n", __func__, dev->name, value >> shift);
+
+ clrbits_32(priv->regs + LED_BRIGHTNESS_CONTROL_REG0 + offset, mask);
+ setbits_32(priv->regs + LED_BRIGHTNESS_CONTROL_REG0 + offset, value);
+
+ return 0;
+}
+
+static enum led_state_t bcm6858_led_get_state(struct udevice *dev)
+{
+ struct bcm6858_led_priv *priv = dev_get_priv(dev);
+ enum led_state_t state = LEDST_OFF;
+ u32 sw_led_ip;
+
+ sw_led_ip = readl(priv->regs + LED_SW_LED_IP_REG);
+ if (sw_led_ip & (1 << priv->pin))
+ state = LEDST_ON;
+
+ return state;
+}
+
+static int bcm6858_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+ struct bcm6858_led_priv *priv = dev_get_priv(dev);
+
+ debug("%s: Set led %s to %d\n", __func__, dev->name, state);
+
+ switch (state) {
+ case LEDST_OFF:
+ clrbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin));
+#ifdef CONFIG_LED_BLINK
+ bcm6858_led_set_period(dev, 0);
+#endif
+ break;
+ case LEDST_ON:
+ setbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin));
+#ifdef CONFIG_LED_BLINK
+ bcm6858_led_set_period(dev, 0);
+#endif
+ break;
+ case LEDST_TOGGLE:
+ if (bcm6858_led_get_state(dev) == LEDST_OFF)
+ return bcm6858_led_set_state(dev, LEDST_ON);
+ else
+ return bcm6858_led_set_state(dev, LEDST_OFF);
+ break;
+#ifdef CONFIG_LED_BLINK
+ case LEDST_BLINK:
+ setbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin));
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct led_ops bcm6858_led_ops = {
+ .get_state = bcm6858_led_get_state,
+ .set_state = bcm6858_led_set_state,
+#ifdef CONFIG_LED_BLINK
+ .set_period = bcm6858_led_set_period,
+#endif
+};
+
+static int bcm6858_led_probe(struct udevice *dev)
+{
+ struct bcm6858_led_priv *priv = dev_get_priv(dev);
+ void __iomem *regs;
+ unsigned int pin, brightness;
+
+ regs = dev_remap_addr(dev_get_parent(dev));
+ if (!regs)
+ return -EINVAL;
+
+ pin = dev_read_u32_default(dev, "reg", LEDS_MAX);
+ if (pin >= LEDS_MAX)
+ return -EINVAL;
+
+ priv->regs = regs;
+ priv->pin = pin;
+
+ /* this led is managed by software */
+ clrbits_32(regs + LED_HW_LED_EN_REG, 1 << pin);
+
+ /* configure the polarity */
+ if (dev_read_bool(dev, "active-low"))
+ clrbits_32(regs + LED_PLED_OP_PPOL_REG, 1 << pin);
+ else
+ setbits_32(regs + LED_PLED_OP_PPOL_REG, 1 << pin);
+
+ brightness = dev_read_u32_default(dev, "default-brightness",
+ LEDS_MAX_BRIGHTNESS);
+ led_set_brightness(dev, brightness);
+
+ return 0;
+}
+
+U_BOOT_DRIVER(bcm6858_led) = {
+ .name = "bcm6858-led",
+ .id = UCLASS_LED,
+ .probe = bcm6858_led_probe,
+ .priv_auto = sizeof(struct bcm6858_led_priv),
+ .ops = &bcm6858_led_ops,
+};
+
+static int bcm6858_led_wrap_probe(struct udevice *dev)
+{
+ void __iomem *regs;
+ u32 set_bits = 0;
+
+ regs = dev_remap_addr(dev);
+ if (!regs)
+ return -EINVAL;
+
+ if (dev_read_bool(dev, "brcm,serial-led-msb-first"))
+ set_bits |= LED_CTRL_SERIAL_LED_MSB_FIRST;
+ if (dev_read_bool(dev, "brcm,serial-led-en-pol"))
+ set_bits |= LED_CTRL_SERIAL_LED_EN_POL;
+ if (dev_read_bool(dev, "brcm,serial-led-clk-pol"))
+ set_bits |= LED_CTRL_SERIAL_LED_CLK_POL;
+ if (dev_read_bool(dev, "brcm,serial-led-data-ppol"))
+ set_bits |= LED_CTRL_SERIAL_LED_DATA_PPOL;
+ if (dev_read_bool(dev, "brcm,led-test-mode"))
+ set_bits |= LED_CTRL_LED_TEST_MODE;
+
+ clrsetbits_32(regs + LED_CTRL_REG, ~0, set_bits);
+
+ return 0;
+}
+
+static int bcm6858_led_wrap_bind(struct udevice *parent)
+{
+ ofnode node;
+
+ dev_for_each_subnode(node, parent) {
+ struct udevice *dev;
+ int ret;
+
+ ret = device_bind_driver_to_node(parent, "bcm6858-led",
+ ofnode_get_name(node),
+ node, &dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id bcm6858_led_ids[] = {
+ { .compatible = "brcm,bcm6858-leds" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(bcm6858_led_wrap) = {
+ .name = "bcm6858_led_wrap",
+ .id = UCLASS_NOP,
+ .of_match = bcm6858_led_ids,
+ .probe = bcm6858_led_wrap_probe,
+ .bind = bcm6858_led_wrap_bind,
+};
diff --git a/drivers/led/led_cortina.c b/drivers/led/led_cortina.c
new file mode 100644
index 00000000000..2d3ad323d33
--- /dev/null
+++ b/drivers/led/led_cortina.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright (C) 2020 Cortina-Access
+ * Author: Jway Lin <jway.lin@cortina-access.com>
+ *
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <log.h>
+#include <asm/io.h>
+#include <dm/lists.h>
+#include <linux/bitops.h>
+
+#define LED_MAX_HW_BLINK 127
+#define LED_MAX_COUNT 16
+
+/* LED_CONTROL fields */
+#define LED_BLINK_RATE1_SHIFT 0
+#define LED_BLINK_RATE1_MASK 0xff
+#define LED_BLINK_RATE2_SHIFT 8
+#define LED_BLINK_RATE2_MASK 0xff
+#define LED_CLK_TEST BIT(16)
+#define LED_CLK_POLARITY BIT(17)
+#define LED_CLK_TEST_MODE BIT(16)
+#define LED_CLK_TEST_RX_TEST BIT(30)
+#define LED_CLK_TEST_TX_TEST BIT(31)
+
+/* LED_CONFIG fields */
+#define LED_EVENT_ON_SHIFT 0
+#define LED_EVENT_ON_MASK 0x7
+#define LED_EVENT_BLINK_SHIFT 3
+#define LED_EVENT_BLINK_MASK 0x7
+#define LED_EVENT_OFF_SHIFT 6
+#define LED_EVENT_OFF_MASK 0x7
+#define LED_OFF_ON_SHIFT 9
+#define LED_OFF_ON_MASK 0x3
+#define LED_PORT_SHIFT 11
+#define LED_PORT_MASK 0x7
+#define LED_OFF_VAL BIT(14)
+#define LED_SW_EVENT BIT(15)
+#define LED_BLINK_SEL BIT(16)
+
+/* LED_CONFIG structures */
+struct cortina_led_cfg {
+ void __iomem *regs;
+ u32 pin; /* LED pin nubmer */
+ bool active_low; /*Active-High or Active-Low*/
+ u32 off_event; /* set led off event (RX,TX,SW)*/
+ u32 blink_event; /* set led blink event (RX,TX,SW)*/
+ u32 on_event; /* set led on event (RX,TX,SW)*/
+ u32 port; /* corresponding ethernet port */
+ int blink_sel; /* select blink-rate1 or blink-rate2 */
+};
+
+/* LED_control structures */
+struct cortina_led_plat {
+ void __iomem *ctrl_regs;
+ u16 rate1; /* blink rate setting 0 */
+ u16 rate2; /* blink rate setting 1 */
+};
+
+enum ca_led_state_t {
+ CA_EVENT_MODE = 0,
+ CA_LED_ON = 1,
+ CA_LED_OFF,
+};
+
+static void cortina_led_write(void __iomem *reg, unsigned long data)
+{
+ writel(data, reg);
+}
+
+static unsigned long cortina_led_read(void __iomem *reg)
+{
+ return readl(reg);
+}
+
+static enum led_state_t cortina_led_get_state(struct udevice *dev)
+{
+ struct cortina_led_cfg *priv = dev_get_priv(dev);
+ enum led_state_t state = LEDST_OFF;
+ u32 val;
+
+ val = readl(priv->regs);
+
+ if (val & LED_SW_EVENT)
+ state = LEDST_ON;
+
+ return state;
+}
+
+static int cortina_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+ u32 val;
+ struct cortina_led_cfg *priv = dev_get_priv(dev);
+
+ val = readl(priv->regs);
+ val &= ~(LED_OFF_ON_MASK << LED_OFF_ON_SHIFT);
+
+ switch (state) {
+ case LEDST_OFF:
+ val &= ~LED_SW_EVENT;
+ val |= CA_LED_OFF << LED_OFF_ON_SHIFT;
+ cortina_led_write(priv->regs, val);
+ break;
+ case LEDST_ON:
+ val |= LED_SW_EVENT;
+ val |= CA_LED_ON << LED_OFF_ON_SHIFT;
+ cortina_led_write(priv->regs, val);
+ break;
+ case LEDST_TOGGLE:
+ if (cortina_led_get_state(dev) == LEDST_OFF)
+ return cortina_led_set_state(dev, LEDST_ON);
+ else
+ return cortina_led_set_state(dev, LEDST_OFF);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct led_ops cortina_led_ops = {
+ .get_state = cortina_led_get_state,
+ .set_state = cortina_led_set_state,
+};
+
+static int ca_led_of_to_plat(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+
+ /* Top-level LED node */
+ if (!uc_plat->label) {
+ struct cortina_led_plat *plt = dev_get_plat(dev);
+
+ plt->rate1 =
+ dev_read_u32_default(dev, "Cortina,blink-rate1", 256);
+ plt->rate2 =
+ dev_read_u32_default(dev, "Cortina,blink-rate2", 512);
+ plt->ctrl_regs = dev_remap_addr(dev);
+ } else {
+ struct cortina_led_cfg *priv = dev_get_priv(dev);
+
+ priv->regs = dev_remap_addr(dev_get_parent(dev));
+ priv->pin = dev_read_u32_default(dev, "pin", LED_MAX_COUNT);
+ priv->blink_sel = dev_read_u32_default(dev, "blink-sel", 0);
+ priv->off_event = dev_read_u32_default(dev, "off-event", 0);
+ priv->blink_event = dev_read_u32_default(dev, "blink-event", 0);
+ priv->on_event = dev_read_u32_default(dev, "on-event", 0);
+ priv->port = dev_read_u32_default(dev, "port", 0);
+
+ if (dev_read_bool(dev, "active-low"))
+ priv->active_low = true;
+ else
+ priv->active_low = false;
+ }
+
+ return 0;
+}
+
+static int cortina_led_probe(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+
+ /* Top-level LED node */
+ if (!uc_plat->label) {
+ struct cortina_led_plat *plat = dev_get_plat(dev);
+ u32 reg_value, val;
+ u16 rate1, rate2;
+
+ if (!plat->ctrl_regs)
+ return -EINVAL;
+
+ reg_value = 0;
+ reg_value |= LED_CLK_POLARITY;
+
+ rate1 = plat->rate1;
+ rate2 = plat->rate2;
+
+ val = rate1 / 16 - 1;
+ rate1 = val > LED_MAX_HW_BLINK ?
+ LED_MAX_HW_BLINK : val;
+ reg_value |= (rate1 & LED_BLINK_RATE1_MASK) <<
+ LED_BLINK_RATE1_SHIFT;
+
+ val = rate2 / 16 - 1;
+ rate2 = val > LED_MAX_HW_BLINK ?
+ LED_MAX_HW_BLINK : val;
+ reg_value |= (rate2 & LED_BLINK_RATE2_MASK) <<
+ LED_BLINK_RATE2_SHIFT;
+
+ cortina_led_write(plat->ctrl_regs, reg_value);
+
+ } else {
+ struct cortina_led_cfg *priv = dev_get_priv(dev);
+ void __iomem *regs;
+ u32 val, port, off_event, blink_event, on_event;
+
+ regs = priv->regs;
+ if (!regs)
+ return -EINVAL;
+
+ if (priv->pin >= LED_MAX_COUNT)
+ return -EINVAL;
+
+ priv->regs = regs + 4 + priv->pin * 4;
+
+ val = cortina_led_read(priv->regs);
+
+ if (priv->active_low)
+ val |= LED_OFF_VAL;
+ else
+ val &= ~LED_OFF_VAL;
+
+ if (priv->blink_sel == 0)
+ val &= ~LED_BLINK_SEL;
+ else if (priv->blink_sel == 1)
+ val |= LED_BLINK_SEL;
+
+ off_event = priv->off_event;
+ val &= ~(LED_EVENT_OFF_MASK << LED_EVENT_OFF_SHIFT);
+ if (off_event != 0)
+ val |= BIT(off_event) << LED_EVENT_OFF_SHIFT;
+
+ blink_event = priv->blink_event;
+ val &= ~(LED_EVENT_BLINK_MASK << LED_EVENT_BLINK_SHIFT);
+ if (blink_event != 0)
+ val |= BIT(blink_event) << LED_EVENT_BLINK_SHIFT;
+
+ on_event = priv->on_event;
+ val &= ~(LED_EVENT_ON_MASK << LED_EVENT_ON_SHIFT);
+ if (on_event != 0)
+ val |= BIT(on_event) << LED_EVENT_ON_SHIFT;
+
+ port = priv->port;
+ val &= ~(LED_PORT_MASK << LED_PORT_SHIFT);
+ val |= port << LED_PORT_SHIFT;
+
+ /* force off */
+ val &= ~(LED_OFF_ON_MASK << LED_OFF_ON_SHIFT);
+ val |= CA_LED_OFF << LED_OFF_ON_SHIFT;
+
+ cortina_led_write(priv->regs, val);
+ }
+
+ return 0;
+}
+
+static int cortina_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, "ca-leds",
+ ofnode_get_name(node),
+ node, &dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id ca_led_ids[] = {
+ { .compatible = "cortina,ca-leds" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(cortina_led) = {
+ .name = "ca-leds",
+ .id = UCLASS_LED,
+ .of_match = ca_led_ids,
+ .of_to_plat = ca_led_of_to_plat,
+ .bind = cortina_led_bind,
+ .probe = cortina_led_probe,
+ .plat_auto = sizeof(struct cortina_led_plat),
+ .priv_auto = sizeof(struct cortina_led_cfg),
+ .ops = &cortina_led_ops,
+};
diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c
new file mode 100644
index 00000000000..ce22fb49f2a
--- /dev/null
+++ b/drivers/led/led_gpio.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <log.h>
+#include <malloc.h>
+#include <asm/gpio.h>
+
+struct led_gpio_priv {
+ struct gpio_desc gpio;
+};
+
+static int gpio_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+ struct led_gpio_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (!dm_gpio_is_valid(&priv->gpio))
+ return -EREMOTEIO;
+ switch (state) {
+ case LEDST_OFF:
+ case LEDST_ON:
+ break;
+ case LEDST_TOGGLE:
+ ret = dm_gpio_get_value(&priv->gpio);
+ if (ret < 0)
+ return ret;
+ state = !ret;
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ return dm_gpio_set_value(&priv->gpio, state);
+}
+
+static enum led_state_t gpio_led_get_state(struct udevice *dev)
+{
+ struct led_gpio_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (!dm_gpio_is_valid(&priv->gpio))
+ return -EREMOTEIO;
+ ret = dm_gpio_get_value(&priv->gpio);
+ if (ret < 0)
+ return ret;
+
+ return ret ? LEDST_ON : LEDST_OFF;
+}
+
+static int led_gpio_probe(struct udevice *dev)
+{
+ struct led_gpio_priv *priv = dev_get_priv(dev);
+
+ return gpio_request_by_name(dev, "gpios", 0, &priv->gpio, GPIOD_IS_OUT);
+}
+
+static int led_gpio_remove(struct udevice *dev)
+{
+ /*
+ * The GPIO driver may have already been removed. We will need to
+ * address this more generally.
+ */
+#ifndef CONFIG_SANDBOX
+ struct led_gpio_priv *priv = dev_get_priv(dev);
+
+ if (dm_gpio_is_valid(&priv->gpio))
+ dm_gpio_free(dev, &priv->gpio);
+#endif
+
+ return 0;
+}
+
+static int led_gpio_bind(struct udevice *parent)
+{
+ return led_bind_generic(parent, "gpio_led");
+}
+
+static const struct led_ops gpio_led_ops = {
+ .set_state = gpio_led_set_state,
+ .get_state = gpio_led_get_state,
+};
+
+U_BOOT_DRIVER(led_gpio) = {
+ .name = "gpio_led",
+ .id = UCLASS_LED,
+ .ops = &gpio_led_ops,
+ .priv_auto = sizeof(struct led_gpio_priv),
+ .probe = led_gpio_probe,
+ .remove = led_gpio_remove,
+};
+
+static const struct udevice_id led_gpio_ids[] = {
+ { .compatible = "gpio-leds" },
+ { }
+};
+
+U_BOOT_DRIVER(led_gpio_wrap) = {
+ .name = "gpio_led_wrap",
+ .id = UCLASS_NOP,
+ .of_match = led_gpio_ids,
+ .bind = led_gpio_bind,
+};
diff --git a/drivers/led/led_lp5562.c b/drivers/led/led_lp5562.c
new file mode 100644
index 00000000000..a5776d3174f
--- /dev/null
+++ b/drivers/led/led_lp5562.c
@@ -0,0 +1,575 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Doug Zobel <douglas.zobel@climate.com>
+ *
+ * Driver for TI lp5562 4 channel LED driver. There are only 3
+ * engines available for the 4 LEDs, so white and blue LEDs share
+ * the same engine. This means that the blink period is shared
+ * between them. Changing the period of blue blink will affect
+ * the white period (and vice-versa). Blue and white On/Off
+ * states remain independent (as would PWM brightness if that's
+ * ever added to the LED core).
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <i2c.h>
+#include <asm/gpio.h>
+#include <linux/delay.h>
+
+#define DEFAULT_CURRENT 100 /* 10 mA */
+#define MIN_BLINK_PERIOD 32 /* ms */
+#define MAX_BLINK_PERIOD 2248 /* ms */
+
+/* Register Map */
+#define REG_ENABLE 0x00
+#define REG_OP_MODE 0x01
+#define REG_B_PWM 0x02
+#define REG_G_PWM 0x03
+#define REG_R_PWM 0x04
+#define REG_B_CUR 0x05
+#define REG_G_CUR 0x06
+#define REG_R_CUR 0x07
+#define REG_CONFIG 0x08
+#define REG_ENG1_PC 0x09
+#define REG_ENG2_PC 0x0A
+#define REG_ENG3_PC 0x0B
+#define REG_STATUS 0x0C
+#define REG_RESET 0x0D
+#define REG_W_PWM 0x0E
+#define REG_W_CUR 0x0F
+#define REG_ENG1_MEM_BEGIN 0x10
+#define REG_ENG2_MEM_BEGIN 0x30
+#define REG_ENG3_MEM_BEGIN 0x50
+#define REG_LED_MAP 0x70
+
+/* LED Register Values */
+/* 0x00 ENABLE */
+#define REG_ENABLE_CHIP_ENABLE (0x1 << 6)
+#define REG_ENABLE_ENG_EXEC_HOLD 0x0
+#define REG_ENABLE_ENG_EXEC_RUN 0x2
+#define REG_ENABLE_ENG_EXEC_MASK 0x3
+
+/* 0x01 OP MODE */
+#define REG_OP_MODE_DISABLED 0x0
+#define REG_OP_MODE_LOAD_SRAM 0x1
+#define REG_OP_MODE_RUN 0x2
+#define REG_OP_MODE_MASK 0x3
+
+/* 0x02, 0x03, 0x04, 0x0E PWM */
+#define REG_PWM_MIN_VALUE 0
+#define REG_PWM_MAX_VALUE 0xFF
+
+/* 0x08 CONFIG */
+#define REG_CONFIG_EXT_CLK 0x0
+#define REG_CONFIG_INT_CLK 0x1
+#define REG_CONFIG_AUTO_CLK 0x2
+#define REG_CONFIG_CLK_MASK 0x3
+
+/* 0x0D RESET */
+#define REG_RESET_RESET 0xFF
+
+/* 0x70 LED MAP */
+#define REG_LED_MAP_ENG_MASK 0x03
+#define REG_LED_MAP_W_ENG_SHIFT 6
+#define REG_LED_MAP_R_ENG_SHIFT 4
+#define REG_LED_MAP_G_ENG_SHIFT 2
+#define REG_LED_MAP_B_ENG_SHIFT 0
+
+/* Engine program related */
+#define REG_ENGINE_MEM_SIZE 0x20
+#define LED_PGRM_RAMP_INCREMENT_SHIFT 0
+#define LED_PGRM_RAMP_SIGN_SHIFT 7
+#define LED_PGRM_RAMP_STEP_SHIFT 8
+#define LED_PGRM_RAMP_PRESCALE_SHIFT 14
+
+struct lp5562_led_wrap_priv {
+ struct gpio_desc enable_gpio;
+};
+
+struct lp5562_led_priv {
+ u8 reg_pwm;
+ u8 reg_current;
+ u8 map_shift;
+ u8 enginenum;
+};
+
+/* enum values map to LED_MAP (0x70) values */
+enum lp5562_led_ctl_mode {
+ I2C = 0x0,
+#ifdef CONFIG_LED_BLINK
+ ENGINE1 = 0x1,
+ ENGINE2 = 0x2,
+ ENGINE3 = 0x3
+#endif
+};
+
+/*
+ * Update a register value
+ * dev - I2C udevice (parent of led)
+ * regnum - register number to update
+ * value - value to write to register
+ * mask - mask of bits that should be changed
+ */
+static int lp5562_led_reg_update(struct udevice *dev, int regnum,
+ u8 value, u8 mask)
+{
+ int ret;
+
+ if (mask == 0xFF)
+ ret = dm_i2c_reg_write(dev, regnum, value);
+ else
+ ret = dm_i2c_reg_clrset(dev, regnum, mask, value);
+
+ /*
+ * Data sheet says "Delay between consecutive I2C writes to
+ * ENABLE register (00h) need to be longer than 488 us
+ * (typical)." and "Delay between consecutive I2C writes to
+ * OP_MODE register need to be longer than 153 us (typ)."
+ *
+ * The linux driver does usleep_range(500, 600) and
+ * usleep_range(200, 300), respectively.
+ */
+ switch (regnum) {
+ case REG_ENABLE:
+ udelay(600);
+ break;
+ case REG_OP_MODE:
+ udelay(300);
+ break;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_LED_BLINK
+/*
+ * Program the lp5562 engine
+ * dev - I2C udevice (parent of led)
+ * program - array of commands
+ * size - number of commands in program array (1-16)
+ * engine - engine number (1-3)
+ */
+static int lp5562_led_program_engine(struct udevice *dev, u16 *program,
+ u8 size, u8 engine)
+{
+ int ret, cmd;
+ u8 engine_reg = REG_ENG1_MEM_BEGIN +
+ ((engine - 1) * REG_ENGINE_MEM_SIZE);
+ u8 shift = (3 - engine) * 2;
+ __be16 prog_be[16];
+
+ if (size < 1 || size > 16 || engine < 1 || engine > 3)
+ return -EINVAL;
+
+ for (cmd = 0; cmd < size; cmd++)
+ prog_be[cmd] = cpu_to_be16(program[cmd]);
+
+ /* set engine mode to 'disabled' */
+ ret = lp5562_led_reg_update(dev, REG_OP_MODE,
+ REG_OP_MODE_DISABLED << shift,
+ REG_OP_MODE_MASK << shift);
+ if (ret != 0)
+ goto done;
+
+ /* set exec mode to 'hold' */
+ ret = lp5562_led_reg_update(dev, REG_ENABLE,
+ REG_ENABLE_ENG_EXEC_HOLD << shift,
+ REG_ENABLE_ENG_EXEC_MASK << shift);
+ if (ret != 0)
+ goto done;
+
+ /* set engine mode to 'load SRAM' */
+ ret = lp5562_led_reg_update(dev, REG_OP_MODE,
+ REG_OP_MODE_LOAD_SRAM << shift,
+ REG_OP_MODE_MASK << shift);
+ if (ret != 0)
+ goto done;
+
+ /* send the re-ordered program sequence */
+ ret = dm_i2c_write(dev, engine_reg, (uchar *)prog_be, sizeof(u16) * size);
+ if (ret != 0)
+ goto done;
+
+ /* set engine mode to 'run' */
+ ret = lp5562_led_reg_update(dev, REG_OP_MODE,
+ REG_OP_MODE_RUN << shift,
+ REG_OP_MODE_MASK << shift);
+ if (ret != 0)
+ goto done;
+
+ /* set engine exec to 'run' */
+ ret = lp5562_led_reg_update(dev, REG_ENABLE,
+ REG_ENABLE_ENG_EXEC_RUN << shift,
+ REG_ENABLE_ENG_EXEC_MASK << shift);
+
+done:
+ return ret;
+}
+
+/*
+ * Get the LED's current control mode (I2C or ENGINE[1-3])
+ * dev - led udevice (child udevice)
+ */
+static enum lp5562_led_ctl_mode lp5562_led_get_control_mode(struct udevice *dev)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+ u8 data;
+ enum lp5562_led_ctl_mode mode = I2C;
+
+ if (dm_i2c_read(dev_get_parent(dev), REG_LED_MAP, &data, 1) == 0)
+ mode = (data & (REG_LED_MAP_ENG_MASK << priv->map_shift))
+ >> priv->map_shift;
+
+ return mode;
+}
+#endif
+
+/*
+ * Set the LED's control mode to I2C or ENGINE[1-3]
+ * dev - led udevice (child udevice)
+ * mode - mode to change to
+ */
+static int lp5562_led_set_control_mode(struct udevice *dev,
+ enum lp5562_led_ctl_mode mode)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+
+ return (lp5562_led_reg_update(dev_get_parent(dev), REG_LED_MAP,
+ mode << priv->map_shift,
+ REG_LED_MAP_ENG_MASK << priv->map_shift));
+}
+
+/*
+ * Return the LED's PWM value; If LED is in BLINK state, then it is
+ * under engine control mode which doesn't use this PWM value.
+ * dev - led udevice (child udevice)
+ */
+static int lp5562_led_get_pwm(struct udevice *dev)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+ u8 data;
+
+ if (dm_i2c_read(dev_get_parent(dev), priv->reg_pwm, &data, 1) != 0)
+ return -EINVAL;
+
+ return data;
+}
+
+/*
+ * Set the LED's PWM value and configure it to use this (I2C mode).
+ * dev - led udevice (child udevice)
+ * value - PWM value (0 - 255)
+ */
+static int lp5562_led_set_pwm(struct udevice *dev, u8 value)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+
+ if (lp5562_led_reg_update(dev_get_parent(dev), priv->reg_pwm,
+ value, 0xff) != 0)
+ return -EINVAL;
+
+ /* set LED to I2C register mode */
+ return lp5562_led_set_control_mode(dev, I2C);
+}
+
+/*
+ * Return the led's current state
+ * dev - led udevice (child udevice)
+ *
+ */
+static enum led_state_t lp5562_led_get_state(struct udevice *dev)
+{
+ enum led_state_t state = LEDST_ON;
+
+ if (lp5562_led_get_pwm(dev) == REG_PWM_MIN_VALUE)
+ state = LEDST_OFF;
+
+#ifdef CONFIG_LED_BLINK
+ if (lp5562_led_get_control_mode(dev) != I2C)
+ state = LEDST_BLINK;
+#endif
+
+ return state;
+}
+
+/*
+ * Set the led state
+ * dev - led udevice (child udevice)
+ * state - State to set the LED to
+ */
+static int lp5562_led_set_state(struct udevice *dev, enum led_state_t state)
+{
+#ifdef CONFIG_LED_BLINK
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+#endif
+
+ switch (state) {
+ case LEDST_OFF:
+ return lp5562_led_set_pwm(dev, REG_PWM_MIN_VALUE);
+ case LEDST_ON:
+ return lp5562_led_set_pwm(dev, REG_PWM_MAX_VALUE);
+#ifdef CONFIG_LED_BLINK
+ case LEDST_BLINK:
+ return lp5562_led_set_control_mode(dev, priv->enginenum);
+#endif
+ case LEDST_TOGGLE:
+ if (lp5562_led_get_state(dev) == LEDST_OFF)
+ return lp5562_led_set_state(dev, LEDST_ON);
+ else
+ return lp5562_led_set_state(dev, LEDST_OFF);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_LED_BLINK
+/*
+ * Set the blink period of an LED; note blue and white share the same
+ * engine so changing the period of one affects the other.
+ * dev - led udevice (child udevice)
+ * period_ms - blink period in ms
+ */
+static int lp5562_led_set_period(struct udevice *dev, int period_ms)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+ u8 opcode = 0;
+ u16 program[7];
+ u16 wait_time;
+
+ /* Blink is implemented as an engine program. Simple on/off
+ * for short periods, or fade in/fade out for longer periods:
+ *
+ * if (period_ms < 500):
+ * set PWM to 100%
+ * pause for period / 2
+ * set PWM to 0%
+ * pause for period / 2
+ * goto start
+ *
+ * else
+ * raise PWM 0% -> 50% in 62.7 ms
+ * raise PWM 50% -> 100% in 62.7 ms
+ * pause for (period - 4 * 62.7) / 2
+ * lower PWM 100% -> 50% in 62.7 ms
+ * lower PWM 50% -> 0% in 62.7 ms
+ * pause for (period - 4 * 62.7) / 2
+ * goto start
+ */
+
+ if (period_ms < MIN_BLINK_PERIOD)
+ period_ms = MIN_BLINK_PERIOD;
+ else if (period_ms > MAX_BLINK_PERIOD)
+ period_ms = MAX_BLINK_PERIOD;
+
+ if (period_ms < 500) {
+ /* Simple on/off blink */
+ wait_time = period_ms / 2;
+
+ /* 1st command is full brightness */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ REG_PWM_MAX_VALUE;
+
+ /* 2nd command is wait (period / 2) using 15.6ms steps */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
+ (0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 3rd command is 0% brightness */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT);
+
+ /* 4th command is wait (period / 2) using 15.6ms steps */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
+ (0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 5th command: repeat */
+ program[opcode++] = 0x00;
+ } else {
+ /* fade-in / fade-out blink */
+ wait_time = ((period_ms - 251) / 2);
+
+ /* ramp up time is 256 * 0.49ms (125.4ms) done in 2 steps */
+ /* 1st command is ramp up 1/2 way */
+ program[opcode++] =
+ (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (1 << LED_PGRM_RAMP_STEP_SHIFT) |
+ (127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 2nd command is ramp up rest of the way */
+ program[opcode++] =
+ (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (1 << LED_PGRM_RAMP_STEP_SHIFT) |
+ (127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 3rd: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
+ (0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* ramp down is same as ramp up with sign bit set */
+ /* 4th command is ramp down 1/2 way */
+ program[opcode++] =
+ (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (1 << LED_PGRM_RAMP_STEP_SHIFT) |
+ (1 << LED_PGRM_RAMP_SIGN_SHIFT) |
+ (127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 5th command is ramp down rest of the way */
+ program[opcode++] =
+ (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (1 << LED_PGRM_RAMP_STEP_SHIFT) |
+ (1 << LED_PGRM_RAMP_SIGN_SHIFT) |
+ (127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 6th: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */
+ program[opcode++] =
+ (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
+ (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
+ (0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
+
+ /* 7th command: repeat */
+ program[opcode++] = 0x00;
+ }
+
+ return lp5562_led_program_engine(dev_get_parent(dev), program,
+ opcode, priv->enginenum);
+}
+#endif
+
+static const struct led_ops lp5562_led_ops = {
+ .get_state = lp5562_led_get_state,
+ .set_state = lp5562_led_set_state,
+#ifdef CONFIG_LED_BLINK
+ .set_period = lp5562_led_set_period,
+#endif
+};
+
+static int lp5562_led_probe(struct udevice *dev)
+{
+ struct lp5562_led_priv *priv = dev_get_priv(dev);
+ u8 current;
+ int ret = 0;
+
+ /* Child LED nodes */
+ switch (dev_read_addr(dev)) {
+ case 0:
+ priv->reg_current = REG_R_CUR;
+ priv->reg_pwm = REG_R_PWM;
+ priv->map_shift = REG_LED_MAP_R_ENG_SHIFT;
+ priv->enginenum = 1;
+ break;
+ case 1:
+ priv->reg_current = REG_G_CUR;
+ priv->reg_pwm = REG_G_PWM;
+ priv->map_shift = REG_LED_MAP_G_ENG_SHIFT;
+ priv->enginenum = 2;
+ break;
+ case 2:
+ priv->reg_current = REG_B_CUR;
+ priv->reg_pwm = REG_B_PWM;
+ priv->map_shift = REG_LED_MAP_B_ENG_SHIFT;
+ priv->enginenum = 3; /* shared with white */
+ break;
+ case 3:
+ priv->reg_current = REG_W_CUR;
+ priv->map_shift = REG_LED_MAP_W_ENG_SHIFT;
+ priv->enginenum = 3; /* shared with blue */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ current = dev_read_u8_default(dev, "max-cur", DEFAULT_CURRENT);
+
+ ret = lp5562_led_reg_update(dev_get_parent(dev), priv->reg_current,
+ current, 0xff);
+
+ return ret;
+}
+
+static int lp5562_led_bind(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+
+ /*
+ * For the child nodes, parse a "chan-name" property, since
+ * the DT bindings for this device use that instead of
+ * "label".
+ */
+ uc_plat->label = dev_read_string(dev, "chan-name");
+
+ return 0;
+}
+
+U_BOOT_DRIVER(lp5562_led) = {
+ .name = "lp5562-led",
+ .id = UCLASS_LED,
+ .bind = lp5562_led_bind,
+ .probe = lp5562_led_probe,
+ .priv_auto = sizeof(struct lp5562_led_priv),
+ .ops = &lp5562_led_ops,
+};
+
+static int lp5562_led_wrap_probe(struct udevice *dev)
+{
+ struct lp5562_led_wrap_priv *priv = dev_get_priv(dev);
+ u8 clock_mode;
+ int ret;
+
+ /* Enable gpio if needed */
+ if (gpio_request_by_name(dev, "enabled-gpios", 0,
+ &priv->enable_gpio, GPIOD_IS_OUT) == 0) {
+ dm_gpio_set_value(&priv->enable_gpio, 1);
+ udelay(1000);
+ }
+
+ /* Ensure all registers have default values. */
+ ret = lp5562_led_reg_update(dev, REG_RESET, REG_RESET_RESET, 0xff);
+ if (ret)
+ return ret;
+ udelay(10000);
+
+ /* Enable the chip */
+ ret = lp5562_led_reg_update(dev, REG_ENABLE, REG_ENABLE_CHIP_ENABLE, 0xff);
+ if (ret)
+ return ret;
+
+ /*
+ * The DT bindings say 0=auto, 1=internal, 2=external, while
+ * the register[0:1] values are 0=external, 1=internal,
+ * 2=auto.
+ */
+ clock_mode = dev_read_u8_default(dev, "clock-mode", 0);
+ ret = lp5562_led_reg_update(dev, REG_CONFIG, 2 - clock_mode, REG_CONFIG_CLK_MASK);
+
+ return ret;
+}
+
+static int lp5562_led_wrap_bind(struct udevice *dev)
+{
+ return led_bind_generic(dev, "lp5562-led");
+}
+
+static const struct udevice_id lp5562_led_ids[] = {
+ { .compatible = "ti,lp5562" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(lp5562_led_wrap) = {
+ .name = "lp5562-led-wrap",
+ .id = UCLASS_NOP,
+ .of_match = lp5562_led_ids,
+ .bind = lp5562_led_wrap_bind,
+ .probe = lp5562_led_wrap_probe,
+ .priv_auto = sizeof(struct lp5562_led_wrap_priv),
+};
diff --git a/drivers/led/led_pwm.c b/drivers/led/led_pwm.c
new file mode 100644
index 00000000000..15dd836509b
--- /dev/null
+++ b/drivers/led/led_pwm.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 VK
+ * Author: Ivan Vozvakhov <i.vozvakhov@vk.team>
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <led.h>
+#include <malloc.h>
+#include <pwm.h>
+
+#define LEDS_PWM_DRIVER_NAME "led_pwm"
+
+struct led_pwm_priv {
+ struct udevice *pwm;
+ uint period; /* period in ns */
+ uint duty; /* duty cycle in ns */
+ uint channel; /* pwm channel number */
+ bool active_low; /* pwm polarity */
+ bool enabled;
+};
+
+static int led_pwm_enable(struct udevice *dev)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = pwm_set_invert(priv->pwm, priv->channel, priv->active_low);
+ if (ret)
+ return ret;
+
+ ret = pwm_set_config(priv->pwm, priv->channel, priv->period, priv->duty);
+ if (ret)
+ return ret;
+
+ ret = pwm_set_enable(priv->pwm, priv->channel, true);
+ if (ret)
+ return ret;
+
+ priv->enabled = true;
+
+ return 0;
+}
+
+static int led_pwm_disable(struct udevice *dev)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = pwm_set_config(priv->pwm, priv->channel, priv->period, 0);
+ if (ret)
+ return ret;
+
+ ret = pwm_set_enable(priv->pwm, priv->channel, false);
+ if (ret)
+ return ret;
+
+ priv->enabled = false;
+
+ return 0;
+}
+
+static int led_pwm_set_state(struct udevice *dev, enum led_state_t state)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ switch (state) {
+ case LEDST_OFF:
+ ret = led_pwm_disable(dev);
+ break;
+ case LEDST_ON:
+ ret = led_pwm_enable(dev);
+ break;
+ case LEDST_TOGGLE:
+ ret = (priv->enabled) ? led_pwm_disable(dev) : led_pwm_enable(dev);
+ break;
+ default:
+ ret = -ENOSYS;
+ }
+
+ return ret;
+}
+
+static enum led_state_t led_pwm_get_state(struct udevice *dev)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+
+ return (priv->enabled) ? LEDST_ON : LEDST_OFF;
+}
+
+static int led_pwm_probe(struct udevice *dev)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+
+ return led_pwm_set_state(dev, (priv->enabled) ? LEDST_ON : LEDST_OFF);
+}
+
+static int led_pwm_of_to_plat(struct udevice *dev)
+{
+ struct led_pwm_priv *priv = dev_get_priv(dev);
+ struct ofnode_phandle_args args;
+ uint def_brightness, max_brightness;
+ int ret;
+
+ ret = dev_read_phandle_with_args(dev, "pwms", "#pwm-cells", 0, 0, &args);
+ if (ret)
+ return ret;
+
+ ret = uclass_get_device_by_ofnode(UCLASS_PWM, args.node, &priv->pwm);
+ if (ret)
+ return ret;
+
+ priv->channel = args.args[0];
+ priv->period = args.args[1];
+ priv->active_low = dev_read_bool(dev, "active-low");
+
+ def_brightness = dev_read_u32_default(dev, "u-boot,default-brightness", 0);
+ max_brightness = dev_read_u32_default(dev, "max-brightness", 255);
+ priv->enabled = !!def_brightness;
+
+ /*
+ * No need to handle pwm inverted case (active_low)
+ * because of pwm_set_invert function
+ */
+ if (def_brightness < max_brightness)
+ priv->duty = priv->period * def_brightness / max_brightness;
+ else
+ priv->duty = priv->period;
+
+ return 0;
+}
+
+static int led_pwm_bind(struct udevice *parent)
+{
+ return led_bind_generic(parent, LEDS_PWM_DRIVER_NAME);
+}
+
+static const struct led_ops led_pwm_ops = {
+ .set_state = led_pwm_set_state,
+ .get_state = led_pwm_get_state,
+};
+
+static const struct udevice_id led_pwm_ids[] = {
+ { .compatible = "pwm-leds" },
+ { }
+};
+
+U_BOOT_DRIVER(led_pwm) = {
+ .name = LEDS_PWM_DRIVER_NAME,
+ .id = UCLASS_LED,
+ .ops = &led_pwm_ops,
+ .priv_auto = sizeof(struct led_pwm_priv),
+ .probe = led_pwm_probe,
+ .of_to_plat = led_pwm_of_to_plat,
+};
+
+U_BOOT_DRIVER(led_pwm_wrap) = {
+ .name = LEDS_PWM_DRIVER_NAME "_wrap",
+ .id = UCLASS_NOP,
+ .of_match = led_pwm_ids,
+ .bind = led_pwm_bind,
+};
diff --git a/drivers/led/led_sw_blink.c b/drivers/led/led_sw_blink.c
new file mode 100644
index 00000000000..ee1546d02d4
--- /dev/null
+++ b/drivers/led/led_sw_blink.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Software blinking helpers
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+ */
+
+#include <cyclic.h>
+#include <dm.h>
+#include <led.h>
+#include <time.h>
+#include <stdlib.h>
+
+#define CYCLIC_NAME_PREFIX "led_sw_blink_"
+
+static void led_sw_blink(struct cyclic_info *c)
+{
+ struct led_sw_blink *sw_blink;
+ struct udevice *dev;
+ struct led_ops *ops;
+
+ sw_blink = container_of(c, struct led_sw_blink, cyclic);
+ dev = sw_blink->dev;
+ ops = led_get_ops(dev);
+
+ switch (sw_blink->state) {
+ case LED_SW_BLINK_ST_OFF:
+ sw_blink->state = LED_SW_BLINK_ST_ON;
+ ops->set_state(dev, LEDST_ON);
+ break;
+ case LED_SW_BLINK_ST_ON:
+ sw_blink->state = LED_SW_BLINK_ST_OFF;
+ ops->set_state(dev, LEDST_OFF);
+ break;
+ case LED_SW_BLINK_ST_NOT_READY:
+ /*
+ * led_set_period has been called, but
+ * led_set_state(LDST_BLINK) has not yet,
+ * so doing nothing
+ */
+ break;
+ default:
+ break;
+ }
+}
+
+int led_sw_set_period(struct udevice *dev, int period_ms)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+ struct led_sw_blink *sw_blink = uc_plat->sw_blink;
+ struct led_ops *ops = led_get_ops(dev);
+ int half_period_us;
+
+ half_period_us = period_ms * 1000 / 2;
+
+ if (!sw_blink) {
+ int len = sizeof(struct led_sw_blink) +
+ strlen(CYCLIC_NAME_PREFIX) +
+ strlen(uc_plat->label) + 1;
+
+ sw_blink = calloc(1, len);
+ if (!sw_blink)
+ return -ENOMEM;
+
+ sw_blink->dev = dev;
+ sw_blink->state = LED_SW_BLINK_ST_DISABLED;
+ strcpy((char *)sw_blink->cyclic_name, CYCLIC_NAME_PREFIX);
+ strcat((char *)sw_blink->cyclic_name, uc_plat->label);
+
+ uc_plat->sw_blink = sw_blink;
+ }
+
+ if (sw_blink->state == LED_SW_BLINK_ST_DISABLED) {
+ cyclic_register(&sw_blink->cyclic, led_sw_blink,
+ half_period_us, sw_blink->cyclic_name);
+ } else {
+ sw_blink->cyclic.delay_us = half_period_us;
+ sw_blink->cyclic.start_time_us = timer_get_us();
+ }
+
+ sw_blink->state = LED_SW_BLINK_ST_NOT_READY;
+ ops->set_state(dev, LEDST_OFF);
+
+ return 0;
+}
+
+bool led_sw_is_blinking(struct udevice *dev)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+ struct led_sw_blink *sw_blink = uc_plat->sw_blink;
+
+ if (!sw_blink)
+ return false;
+
+ return sw_blink->state > LED_SW_BLINK_ST_NOT_READY;
+}
+
+bool led_sw_on_state_change(struct udevice *dev, enum led_state_t state)
+{
+ struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
+ struct led_sw_blink *sw_blink = uc_plat->sw_blink;
+
+ if (!sw_blink || sw_blink->state == LED_SW_BLINK_ST_DISABLED)
+ return false;
+
+ if (state == LEDST_BLINK) {
+ struct led_ops *ops = led_get_ops(dev);
+
+ /*
+ * toggle LED initially and start blinking on next
+ * led_sw_blink() call.
+ */
+ switch (ops->get_state(dev)) {
+ case LEDST_ON:
+ ops->set_state(dev, LEDST_OFF);
+ sw_blink->state = LED_SW_BLINK_ST_OFF;
+ default:
+ ops->set_state(dev, LEDST_ON);
+ sw_blink->state = LED_SW_BLINK_ST_ON;
+ }
+
+ return true;
+ }
+
+ /* stop blinking */
+ uc_plat->sw_blink = NULL;
+ cyclic_unregister(&sw_blink->cyclic);
+ free(sw_blink);
+
+ return false;
+}