From 04d6e53d9de2882609ccda723a99b5d2ba77c51c Mon Sep 17 00:00:00 2001 From: Simon Guinot Date: Mon, 19 Oct 2020 19:50:33 +0200 Subject: [PATCH 1/2] drivers: led: add driver for PWM LEDs This driver supports the PWM driven LEDs. The devices are created from the DT nodes with a compatible property matching "pwm-leds". For each child node a LED is created and its "pwms" phandle's node is used to retrieve the PWM configuration: channel, period and flags. If some of this properties are missing (it is the case for some PWM controllers), then reasonable default values are used. This driver implements the following LED API methods: - led_on - led_off - led_blink - led_set_brightness Signed-off-by: Simon Guinot --- drivers/led/CMakeLists.txt | 1 + drivers/led/Kconfig | 1 + drivers/led/Kconfig.pwm | 8 ++ drivers/led/led_pwm.c | 164 +++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 drivers/led/Kconfig.pwm create mode 100644 drivers/led/led_pwm.c diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index c30a9c403e66bc..cdbb2a95d887b1 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 4c43e3ed0caf2f..d147a5f17cbb12 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 00000000000000..3a6c0067014907 --- /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 00000000000000..beda5237410c54 --- /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) From 09712f384cdd67cc620a3eedcbc47b0e0ec45ad1 Mon Sep 17 00:00:00 2001 From: Simon Guinot Date: Tue, 20 Oct 2020 23:56:14 +0200 Subject: [PATCH 2/2] samples: drivers: add test sample for the PWM LED driver This sample allows to test the led-pwm driver. The first "pwm-leds" compatible device instance found in DT is used. For each LEDs attached to this device (child nodes) the same test pattern (described below) is executed. The LED API functions are used to control the LEDs. Test pattern: For each PWM LEDs (one after the other): - turn on - turn off - increase the brightness gradually up to the maximum level - blink (0.1 sec on, 0.1 sec off) - blink (1 sec on, 1 sec off) - turn off Signed-off-by: Simon Guinot --- samples/drivers/led_pwm/CMakeLists.txt | 9 ++ samples/drivers/led_pwm/README.rst | 33 +++++++ samples/drivers/led_pwm/prj.conf | 6 ++ samples/drivers/led_pwm/sample.yaml | 8 ++ samples/drivers/led_pwm/src/main.c | 129 +++++++++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 samples/drivers/led_pwm/CMakeLists.txt create mode 100644 samples/drivers/led_pwm/README.rst create mode 100644 samples/drivers/led_pwm/prj.conf create mode 100644 samples/drivers/led_pwm/sample.yaml create mode 100644 samples/drivers/led_pwm/src/main.c diff --git a/samples/drivers/led_pwm/CMakeLists.txt b/samples/drivers/led_pwm/CMakeLists.txt new file mode 100644 index 00000000000000..909b20d9278434 --- /dev/null +++ b/samples/drivers/led_pwm/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(led_pwm) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/led_pwm/README.rst b/samples/drivers/led_pwm/README.rst new file mode 100644 index 00000000000000..3749ca8e0b2e22 --- /dev/null +++ b/samples/drivers/led_pwm/README.rst @@ -0,0 +1,33 @@ +.. _led_pwm: + +LED PWM sample application +########################## + +Overview +******** + +This sample allows to test the led-pwm driver. The first "pwm-leds" compatible +device instance found in DT is used. For each LEDs attached to this device +(child nodes) the same test pattern (described below) is executed. The LED API +functions are used to control the LEDs. + +Test pattern +============ + +For each PWM LEDs (one after the other): + +- turn on +- turn off +- increase the brightness gradually up to the maximum level +- blink (0.1 sec on, 0.1 sec off) +- blink (1 sec on, 1 sec off) +- turn off + +Building and Running +******************** + +This sample can be built and executed on all the boards with PWM LEDs connected. +The LEDs must be correctly described in the DTS: the compatible property of the +device node must match "pwm-leds". And for each LED, a child node must be +defined and the PWM configuration must be provided through a "pwms" phandle's +node. diff --git a/samples/drivers/led_pwm/prj.conf b/samples/drivers/led_pwm/prj.conf new file mode 100644 index 00000000000000..2444040168a32a --- /dev/null +++ b/samples/drivers/led_pwm/prj.conf @@ -0,0 +1,6 @@ +CONFIG_LOG=y + +CONFIG_PWM=y + +CONFIG_LED=y +CONFIG_LED_PWM=y diff --git a/samples/drivers/led_pwm/sample.yaml b/samples/drivers/led_pwm/sample.yaml new file mode 100644 index 00000000000000..18c74bced0a4c2 --- /dev/null +++ b/samples/drivers/led_pwm/sample.yaml @@ -0,0 +1,8 @@ +sample: + description: Demonstration of the PWM LED driver + name: PWM LED sample +tests: + sample.drivers.led.led_pwm: + filter: dt_compat_enabled("pwm-leds") + tags: LED + depends_on: pwm diff --git a/samples/drivers/led_pwm/src/main.c b/samples/drivers/led_pwm/src/main.c new file mode 100644 index 00000000000000..71e3e6e4d05492 --- /dev/null +++ b/samples/drivers/led_pwm/src/main.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 Seagate Technology LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL); + +#if DT_NODE_HAS_STATUS(DT_INST(0, pwm_leds), okay) +#define LED_PWM_NODE_ID DT_INST(0, pwm_leds) +#define LED_PWM_DEV_NAME DT_INST_PROP_OR(0, label, "LED_PWM_0") +#else +#error "No LED PWM device found" +#endif + +#define LED_PWM_LABEL(led_node_id) DT_PROP_OR(led_node_id, label, NULL), + +const char *led_label[] = { + DT_FOREACH_CHILD(LED_PWM_NODE_ID, LED_PWM_LABEL) +}; + +const int num_leds = ARRAY_SIZE(led_label); + +#define MAX_BRIGHTNESS 100 + +#define FADE_DELAY_MS 10 +#define FADE_DELAY K_MSEC(FADE_DELAY_MS) + +/** + * @brief Run tests on a single LED using the LED API syscalls. + * + * @param led_pwm LED PWM device. + * @param led Number of the LED to test. + */ +static void run_led_test(const struct device *led_pwm, uint8_t led) +{ + int err; + uint16_t level; + + LOG_INF("Testing LED %d (%s)", led, led_label[led] ? : "no label"); + + /* Turn LED on. */ + err = led_on(led_pwm, led); + LOG_INF(" Turning on"); + if (err < 0) { + LOG_ERR("err=%d", err); + return; + } + k_sleep(K_MSEC(1000)); + + /* Turn LED off. */ + err = led_off(led_pwm, led); + LOG_INF(" Turning off"); + if (err < 0) { + LOG_ERR("err=%d", err); + return; + } + k_sleep(K_MSEC(1000)); + + /* Increase LED brightness gradually up to the maximum level. */ + LOG_INF(" Increasing brightness gradually"); + for (level = 0; level <= MAX_BRIGHTNESS; level++) { + err = led_set_brightness(led_pwm, led, level); + if (err < 0) { + LOG_ERR("err=%d brightness=%d\n", err, level); + return; + } + k_sleep(FADE_DELAY); + } + k_sleep(K_MSEC(1000)); + + /* Set LED blinking (on: 0.1 sec, off: 0.1 sec) */ + LOG_INF(" Blinking (on: 0.1 sec, off: 0.1 sec)"); + err = led_blink(led_pwm, led, 100, 100); + if (err < 0) { + LOG_ERR("err=%d", err); + return; + } + k_sleep(K_MSEC(5000)); + + /* Enable LED blinking (on: 1 sec, off: 1 sec) */ + LOG_INF(" Blinking (on: 1 sec, off: 1 sec)"); + err = led_blink(led_pwm, led, 1000, 1000); + if (err < 0) { + LOG_ERR("err=%d", err); + return; + } + k_sleep(K_MSEC(5000)); + + /* Turn LED off. */ + LOG_INF(" Turning off"); + err = led_off(led_pwm, led); + if (err < 0) { + LOG_ERR("err=%d", err); + return; + } +} + +void main(void) +{ + const struct device *led_pwm; + uint8_t led; + + led_pwm = device_get_binding(LED_PWM_DEV_NAME); + if (led_pwm) { + LOG_INF("Found device %s", LED_PWM_DEV_NAME); + } else { + LOG_ERR("Device %s not found", LED_PWM_DEV_NAME); + return; + } + + if (!num_leds) { + LOG_ERR("No LEDs found for %s", LED_PWM_DEV_NAME); + return; + } + + do { + for (led = 0; led < num_leds; led++) { + run_led_test(led_pwm, led); + } + } while (true); +}