From 067cfc1c2ea382b0820d995f476e2a26713a3200 Mon Sep 17 00:00:00 2001 From: Ivan Vozvakhov Date: Sat, 12 Mar 2022 13:03:14 +0300 Subject: [PATCH] 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 --- MAINTAINERS | 6 + doc/device-tree-bindings/leds/leds-pwm.txt | 47 +++++ drivers/led/Kconfig | 6 + drivers/led/Makefile | 1 + drivers/led/led_pwm.c | 192 +++++++++++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 doc/device-tree-bindings/leds/leds-pwm.txt create mode 100644 drivers/led/led_pwm.c diff --git a/MAINTAINERS b/MAINTAINERS index 6142504284..7a9e3156f4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -899,6 +899,12 @@ F: doc/README.kwbimage F: doc/kwboot.1 F: tools/kwb* +LED +M: Ivan Vozvakhov +S: Supported +F: doc/device-tree-bindings/leds/leds-pwm.txt +F: drivers/led/led_pwm.c + LOGGING M: Simon Glass S: Maintained diff --git a/doc/device-tree-bindings/leds/leds-pwm.txt b/doc/device-tree-bindings/leds/leds-pwm.txt new file mode 100644 index 0000000000..186e8a848f --- /dev/null +++ b/doc/device-tree-bindings/leds/leds-pwm.txt @@ -0,0 +1,47 @@ +LEDs connected to PWM (Linux compatible) + +Required properties: +- compatible : should be "pwm-leds". + +Each LED is represented as a sub-node of the pwm-leds device. Each +node's name represents the name of the corresponding LED. + +LED sub-node properties: +- pwms : (required) LED pwm channel, see "pwms property" in + doc/device-tree-bindings/pwm/pwm.txt +- label : (optional) LED label, see "label property" in + doc/device-tree-bindings/led/common.txt +- max-brightness : (optional, unsigned, default 255) Maximum brightness possible + for the LED +- active-low : (optional, boolean, default false) For PWMs where the LED is + wired to supply rather than ground +- u-boot,default-brightness : (optional, unsigned, default 0) Initial state + of pwm-leds + +Example: + +leds { + compatible = "pwm-leds"; + status = "okay"; + + blue { + label = "led-blue"; + pwms = <&pwm1 0 100000 0>; + max-brightness = <255>; + u-boot,default-brightness = <127>; + }; + + green { + label = "led-green"; + pwms = <&pwm2 0 100000 0>; + max-brightness = <255>; + u-boot,default-brightness = <127>; + }; + + red { + label = "led-red"; + pwms = <&pwm3 0 100000 0>; + max-brightness = <255>; + u-boot,default-brightness = <127>; + }; +} 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, +};