From 067cfc1c2ea382b0820d995f476e2a26713a3200 Mon Sep 17 00:00:00 2001 From: Ivan Vozvakhov Date: Sat, 12 Mar 2022 13:03:14 +0300 Subject: led: led_pwm: Add a driver for LEDs connected to PWM Add a driver which allows to use of LEDs connected to PWM (Linux compatible). MAINTAINERS: add i.vozvakhov as a maintainer of leds-pwm C(required during new functionality adding). Signed-off-by: Ivan Vozvakhov --- drivers/led/Kconfig | 6 ++ drivers/led/Makefile | 1 + drivers/led/led_pwm.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 drivers/led/led_pwm.c (limited to 'drivers/led') diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 430d0760ba..418ed215c5 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -49,6 +49,12 @@ config LED_CORTINA This option enables support for LEDs connected to the Cortina Access CAxxxx SOCs. +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 LED blinking" diff --git a/drivers/led/Makefile b/drivers/led/Makefile index 2aa2c2173a..49ae91961d 100644 --- a/drivers/led/Makefile +++ b/drivers/led/Makefile @@ -8,5 +8,6 @@ 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_$(SPL_)LED_GPIO) += led_gpio.o obj-$(CONFIG_LED_CORTINA) += led_cortina.o diff --git a/drivers/led/led_pwm.c b/drivers/led/led_pwm.c new file mode 100644 index 0000000000..4e50272258 --- /dev/null +++ b/drivers/led/led_pwm.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 VK + * Author: Ivan Vozvakhov + */ + +#include +#include +#include +#include +#include +#include +#include + +#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); + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + + /* Ignore the top-level LED node */ + if (!uc_plat->label) + return 0; + + return led_pwm_set_state(dev, (priv->enabled) ? LEDST_ON : LEDST_OFF); +} + +static int led_pwm_of_to_plat(struct udevice *dev) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct led_pwm_priv *priv = dev_get_priv(dev); + struct ofnode_phandle_args args; + uint def_brightness, max_brightness; + int ret; + + /* Ignore the top-level LED node */ + if (!uc_plat->label) + return 0; + + 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 iverted 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) +{ + struct udevice *dev; + ofnode node; + int ret; + + dev_for_each_subnode(node, parent) { + struct led_uc_plat *uc_plat; + const char *label; + + label = ofnode_read_string(node, "label"); + if (!label) + label = ofnode_get_name(node); + + ret = device_bind_driver_to_node(parent, LEDS_PWM_DRIVER_NAME, + ofnode_get_name(node), + node, &dev); + if (ret) + return ret; + + uc_plat = dev_get_uclass_plat(dev); + uc_plat->label = label; + } + return 0; +} + +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, + .of_match = led_pwm_ids, + .ops = &led_pwm_ops, + .priv_auto = sizeof(struct led_pwm_priv), + .bind = led_pwm_bind, + .probe = led_pwm_probe, + .of_to_plat = led_pwm_of_to_plat, +}; -- cgit v1.2.3