diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index c30a9c403e6..cdbb2a95d88 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_sources_ifdef(CONFIG_HT16K33 ht16k33.c) +zephyr_sources_ifdef(CONFIG_LED_PWM led_pwm.c) zephyr_sources_ifdef(CONFIG_LP3943 lp3943.c) zephyr_sources_ifdef(CONFIG_LP503X lp503x.c) zephyr_sources_ifdef(CONFIG_LP5562 lp5562.c) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 4c43e3ed0ca..d147a5f17cb 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -31,5 +31,6 @@ source "drivers/led/Kconfig.lp3943" source "drivers/led/Kconfig.lp503x" source "drivers/led/Kconfig.lp5562" source "drivers/led/Kconfig.pca9633" +source "drivers/led/Kconfig.pwm" endif # LED diff --git a/drivers/led/Kconfig.pwm b/drivers/led/Kconfig.pwm new file mode 100644 index 00000000000..3a6c0067014 --- /dev/null +++ b/drivers/led/Kconfig.pwm @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Seagate Technology LLC +# SPDX-License-Identifier: Apache-2.0 + +config LED_PWM + bool "PWM LED driver" + depends on PWM + help + Enable driver for PWM LEDs. diff --git a/drivers/led/led_pwm.c b/drivers/led/led_pwm.c new file mode 100644 index 00000000000..beda5237410 --- /dev/null +++ b/drivers/led/led_pwm.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020 Seagate Technology LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT pwm_leds + +/** + * @file + * @brief PWM driven LEDs + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(led_pwm, CONFIG_LED_LOG_LEVEL); + +#define DEV_CFG(dev) ((const struct led_pwm_config *) ((dev)->config)) +#define DEV_DATA(dev) ((struct led_pwm_data *) ((dev)->data)) + +struct led_pwm { + char *pwm_label; + uint32_t channel; + uint32_t period; + pwm_flags_t flags; +}; + +struct led_pwm_config { + int num_leds; + const struct led_pwm *led; +}; + +struct led_pwm_data { + const struct device *pwm; +}; + +static int led_pwm_blink(const struct device *dev, uint32_t led, + uint32_t delay_on, uint32_t delay_off) +{ + const struct led_pwm_config *config = DEV_CFG(dev); + struct led_pwm_data *data = DEV_DATA(dev); + const struct led_pwm *led_pwm; + uint32_t period_usec, pulse_usec; + + if (led >= config->num_leds) { + return -EINVAL; + } + + /* + * Convert delays (in ms) into PWM period and pulse (in us) and check + * for overflows. + */ + if (u32_add_overflow(delay_on, delay_off, &period_usec) || + u32_mul_overflow(period_usec, 1000, &period_usec) || + u32_mul_overflow(delay_on, 1000, &pulse_usec)) { + return -EINVAL; + } + + led_pwm = &config->led[led]; + + return pwm_pin_set_usec(data[led].pwm, led_pwm->channel, + period_usec, pulse_usec, led_pwm->flags); +} + +static int led_pwm_set_brightness(const struct device *dev, + uint32_t led, uint8_t value) +{ + const struct led_pwm_config *config = DEV_CFG(dev); + struct led_pwm_data *data = DEV_DATA(dev); + const struct led_pwm *led_pwm; + uint32_t pulse; + + if (led >= config->num_leds || value > 100) { + return -EINVAL; + } + + led_pwm = &config->led[led]; + + pulse = led_pwm->period * value / 100; + + return pwm_pin_set_cycles(data[led].pwm, led_pwm->channel, + led_pwm->period, pulse, led_pwm->flags); +} + +static int led_pwm_on(const struct device *dev, uint32_t led) +{ + return led_pwm_set_brightness(dev, led, 100); +} + +static int led_pwm_off(const struct device *dev, uint32_t led) +{ + return led_pwm_set_brightness(dev, led, 0); +} + +static int led_pwm_init(const struct device *dev) +{ + const struct led_pwm_config *config = DEV_CFG(dev); + struct led_pwm_data *data = DEV_DATA(dev); + int i; + + if (!config->num_leds) { + LOG_ERR("%s: no LEDs found (DT child nodes missing)", + dev->name); + return -ENODEV; + } + + for (i = 0; i < config->num_leds; i++) { + const struct led_pwm *led = &config->led[i]; + + data[i].pwm = device_get_binding(led->pwm_label); + if (data->pwm == NULL) { + LOG_ERR("%s: device %s not found", + dev->name, led->pwm_label); + return -ENODEV; + } + } + + return 0; +} + +static const struct led_driver_api led_pwm_api = { + .on = led_pwm_on, + .off = led_pwm_off, + .blink = led_pwm_blink, + .set_brightness = led_pwm_set_brightness, +}; + +#define LED_PWM(led_node_id) \ +{ \ + .pwm_label = DT_PWMS_LABEL(led_node_id), \ + .channel = DT_PWMS_CHANNEL(led_node_id), \ + .period = DT_PHA_OR(led_node_id, pwms, period, 100), \ + .flags = DT_PHA_OR(led_node_id, pwms, flags, \ + PWM_POLARITY_NORMAL), \ +}, + +#define LED_PWM_DEVICE(id) \ + \ +const struct led_pwm led_pwm_##id[] = { \ + DT_INST_FOREACH_CHILD(id, LED_PWM) \ +}; \ + \ +const struct led_pwm_config led_pwm_config_##id = { \ + .num_leds = ARRAY_SIZE(led_pwm_##id), \ + .led = led_pwm_##id, \ +}; \ + \ +static struct led_pwm_data \ + led_pwm_data_##id[ARRAY_SIZE(led_pwm_##id)]; \ + \ +DEVICE_AND_API_INIT(led_pwm_##id, \ + DT_INST_PROP_OR(id, label, "LED_PWM_"#id), \ + &led_pwm_init, \ + &led_pwm_data_##id, \ + &led_pwm_config_##id, \ + POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \ + &led_pwm_api); + +DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)