From d30f4dcd0e1a44b9b546ab56cbda21438ef6d5f4 Mon Sep 17 00:00:00 2001 From: Kamil Gawor Date: Thu, 2 Jan 2020 10:57:54 +0100 Subject: [PATCH 1/4] driver: Add LED GPIO driver Add driver for controlling the LED devices attached to GPIO pins which are declared in devicetree under leds node and generated as static initializer. The driver takes care of configuring the pins to output state before using it. Signed-off-by: Kamil Gawor --- drivers/led/CMakeLists.txt | 1 + drivers/led/Kconfig | 1 + drivers/led/Kconfig.led_gpio | 18 ++++ drivers/led/led_gpio.c | 168 +++++++++++++++++++++++++++++++ dts/bindings/gpio/gpio-leds.yaml | 11 ++ include/drivers/led.h | 8 +- 6 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 drivers/led/Kconfig.led_gpio create mode 100644 drivers/led/led_gpio.c diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index 3c2f7c114d05c..cb07935c27330 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -4,5 +4,6 @@ zephyr_sources_ifdef(CONFIG_HT16K33 ht16k33.c) zephyr_sources_ifdef(CONFIG_LP3943 lp3943.c) zephyr_sources_ifdef(CONFIG_LP5562 lp5562.c) zephyr_sources_ifdef(CONFIG_PCA9633 pca9633.c) +zephyr_sources_ifdef(CONFIG_LED_GPIO led_gpio.c) zephyr_sources_ifdef(CONFIG_USERSPACE led_handlers.c) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 02a288b176719..0fe93eb54db8b 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -24,5 +24,6 @@ source "drivers/led/Kconfig.ht16k33" source "drivers/led/Kconfig.lp3943" source "drivers/led/Kconfig.lp5562" source "drivers/led/Kconfig.pca9633" +source "drivers/led/Kconfig.led_gpio" endif # LED diff --git a/drivers/led/Kconfig.led_gpio b/drivers/led/Kconfig.led_gpio new file mode 100644 index 0000000000000..44c164bde008d --- /dev/null +++ b/drivers/led/Kconfig.led_gpio @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config LED_GPIO + bool "LED GPIO driver" + depends on GPIO + help + This option enables support for the LEDs connected to GPIO + outputs. To be useful the particular board must have LEDs + and they must be connected to the GPIO lines. + +if LED_GPIO + +module = LED_GPIO +module-str = LED GPIO +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # LED_GPIO diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c new file mode 100644 index 0000000000000..b809ab457a13c --- /dev/null +++ b/drivers/led/led_gpio.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +LOG_MODULE_REGISTER(led_gpio, CONFIG_LED_GPIO_LOG_LEVEL); + +#define DT_DRV_COMPAT gpio_leds +#define LED_DATA(node_id) \ +{ \ + { \ + DT_GPIO_LABEL(node_id, gpios), \ + DT_GPIO_PIN(node_id, gpios), \ + DT_GPIO_FLAGS(node_id, gpios) \ + }, \ + DT_LABEL(node_id), \ + DT_PROP(node_id, id) \ +}, + +struct gpio_cfg { + const char *port; + gpio_pin_t pin; + gpio_dt_flags_t flags; +}; + +struct led_dts_cfg { + struct gpio_cfg gpio; + const char *label; + uint32_t id; +}; + +struct led_gpio_data { + struct device *gpio_dev; + const struct led_dts_cfg *cfg; + bool state; +}; + +struct led_gpio_cfg { + const struct led_dts_cfg *led; + uint32_t led_cnt; +}; + +static inline struct led_gpio_data *get_dev_data(struct device *dev) +{ + return dev->driver_data; +} + +static inline const struct led_gpio_cfg *get_dev_config(struct device *dev) +{ + return dev->config_info; +} + +static struct led_gpio_data *led_parameters_get(struct device *dev, + uint32_t led) +{ + const struct led_gpio_cfg *cfg = get_dev_config(dev); + struct led_gpio_data *data = get_dev_data(dev); + + for (size_t i = 0; i < cfg->led_cnt; i++) { + if (data[i].cfg->id == led) { + return &data[i]; + } + } + + return NULL; +} + +static int gpio_led_set_no_delay(struct led_gpio_data *data, bool state) +{ + int err; + + data->state = state; + + err = gpio_pin_set(data->gpio_dev, data->cfg->gpio.pin, + state); + if (err) { + LOG_DBG("%s turn %s error %d", + data->cfg->label, + state ? "on" : "off", + err); + } else { + LOG_DBG("%s turn %s", data->cfg->label, + state ? "on" : "off"); + } + + return err; +} + +static int gpio_led_set(struct led_gpio_data *data, bool state) +{ + return gpio_led_set_no_delay(data, state); +} + +static int gpio_led_on(struct device *dev, uint32_t led) +{ + struct led_gpio_data *data = led_parameters_get(dev, led); + + if (!data) { + return -EINVAL; + } + + return gpio_led_set(data, true); +} + +static int gpio_led_off(struct device *dev, uint32_t led) +{ + struct led_gpio_data *data = led_parameters_get(dev, led); + + if (!data) { + return -EINVAL; + } + + return gpio_led_set(data, false); +} + +static const struct led_driver_api led_api = { + .on = gpio_led_on, + .off = gpio_led_off, +}; + +static int gpio_led_init(struct device *dev) +{ + int err; + const struct led_gpio_cfg *cfg = get_dev_config(dev); + struct led_gpio_data *data = get_dev_data(dev); + + for (size_t i = 0; i < cfg->led_cnt; i++) { + data[i].gpio_dev = device_get_binding(cfg->led[i].gpio.port); + if (!data[i].gpio_dev) { + LOG_DBG("Failed to get GPIO device for port %s pin %d", + cfg->led[i].gpio.port, cfg->led[i].gpio.pin); + return -EINVAL; + } + + /* Set all leds pin as output */ + err = gpio_pin_configure(data[i].gpio_dev, + cfg->led[i].gpio.pin, + (GPIO_OUTPUT_INACTIVE | + cfg->led[i].gpio.flags)); + if (err) { + LOG_DBG("Failed to set GPIO port %s pin %d as output", + cfg->led[i].gpio.port, cfg->led[i].gpio.pin); + return err; + } + + + data[i].cfg = &cfg->led[i]; + } + + return 0; +} + +static const struct led_dts_cfg led_dts[] = {DT_INST_FOREACH_CHILD(0, + LED_DATA)}; +static struct led_gpio_data led_data[ARRAY_SIZE(led_dts)]; +static const struct led_gpio_cfg led_cfg = { + .led_cnt = ARRAY_SIZE(led_dts), + .led = led_dts +}; + +DEVICE_AND_API_INIT(gpio_led, DT_PROP(DT_DRV_INST(0), driver), + gpio_led_init, led_data, + &led_cfg, POST_KERNEL, CONFIG_LED_INIT_PRIORITY, + &led_api); diff --git a/dts/bindings/gpio/gpio-leds.yaml b/dts/bindings/gpio/gpio-leds.yaml index 1b3e6ce39e7a0..a5270efddaa04 100644 --- a/dts/bindings/gpio/gpio-leds.yaml +++ b/dts/bindings/gpio/gpio-leds.yaml @@ -5,6 +5,13 @@ description: GPIO LEDs parent node compatible: "gpio-leds" +properties: + driver: + required: false + type: string + description: GPIO LED driver name + default: "LED_GPIO_0" + child-binding: description: GPIO LED child node properties: @@ -15,3 +22,7 @@ child-binding: required: true type: string description: Human readable string describing the device (used as device_get_binding() argument) + id: + required: false + type: int + description: Id needed for the GPIO LED driver to identify an LED diff --git a/include/drivers/led.h b/include/drivers/led.h index d1cf12079837e..db8f2ecc75d7e 100644 --- a/include/drivers/led.h +++ b/include/drivers/led.h @@ -73,7 +73,7 @@ __subsystem struct led_driver_api { * This routine starts blinking an LED forever with the given time period * * @param dev LED device - * @param led LED channel/pin + * @param led LED channel/pin/id * @param delay_on Time period (in milliseconds) an LED should be ON * @param delay_off Time period (in milliseconds) an LED should be OFF * @return 0 on success, negative on error @@ -97,7 +97,7 @@ static inline int z_impl_led_blink(struct device *dev, uint32_t led, * Calling this function after led_blink() won't affect blinking. * * @param dev LED device - * @param led LED channel/pin + * @param led LED channel/pin/id * @param value Brightness value to set in percent * @return 0 on success, negative on error */ @@ -119,7 +119,7 @@ static inline int z_impl_led_set_brightness(struct device *dev, uint32_t led, * This routine turns on an LED * * @param dev LED device - * @param led LED channel/pin + * @param led LED channel/pin/id * @return 0 on success, negative on error */ __syscall int led_on(struct device *dev, uint32_t led); @@ -138,7 +138,7 @@ static inline int z_impl_led_on(struct device *dev, uint32_t led) * This routine turns off an LED * * @param dev LED device - * @param led LED channel/pin + * @param led LED channel/pin/id * @return 0 on success, negative on error */ __syscall int led_off(struct device *dev, uint32_t led); From cd065bd94b47975a90800827e85a422e3ce36ca6 Mon Sep 17 00:00:00 2001 From: Kamil Gawor Date: Thu, 2 Jan 2020 10:59:53 +0100 Subject: [PATCH 2/4] driver: gpio_led: Software LED blink Add software LED blink feature for each defined LED. Blink funcionality uses software timer. Signed-off-by: Kamil Gawor --- drivers/led/Kconfig.led_gpio | 6 +++ drivers/led/led_gpio.c | 96 ++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/drivers/led/Kconfig.led_gpio b/drivers/led/Kconfig.led_gpio index 44c164bde008d..81dc3a806380a 100644 --- a/drivers/led/Kconfig.led_gpio +++ b/drivers/led/Kconfig.led_gpio @@ -11,6 +11,12 @@ config LED_GPIO if LED_GPIO +config LED_SOFTWARE_BLINK + bool "Software blink" + help + Support for an LED software blink based on + system timers + module = LED_GPIO module-str = LED GPIO source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c index b809ab457a13c..32355cb5443bf 100644 --- a/drivers/led/led_gpio.c +++ b/drivers/led/led_gpio.c @@ -20,6 +20,8 @@ LOG_MODULE_REGISTER(led_gpio, CONFIG_LED_GPIO_LOG_LEVEL); DT_LABEL(node_id), \ DT_PROP(node_id, id) \ }, +#define LED_SW_BLINK BIT(0) +#define LED_BLINK_DISABLE BIT(1) struct gpio_cfg { const char *port; @@ -36,6 +38,13 @@ struct led_dts_cfg { struct led_gpio_data { struct device *gpio_dev; const struct led_dts_cfg *cfg; +#if defined(CONFIG_LED_SOFTWARE_BLINK) + struct k_timer timer; + atomic_t flags; + uint32_t blink_delay_on; + uint32_t blink_delay_off; + bool next_state; +#endif bool state; }; @@ -92,6 +101,18 @@ static int gpio_led_set_no_delay(struct led_gpio_data *data, bool state) static int gpio_led_set(struct led_gpio_data *data, bool state) { +#if defined(CONFIG_LED_SOFTWARE_BLINK) + if (atomic_test_bit(&data->flags, LED_SW_BLINK)) { + data->next_state = state; + + atomic_set_bit(&data->flags, LED_BLINK_DISABLE); + k_timer_start(&data->timer, K_NO_WAIT, K_NO_WAIT); + + return 0; + + } +#endif /* defined(CONFIG_LED_SOFTWARE_BLINK) */ + return gpio_led_set_no_delay(data, state); } @@ -117,7 +138,78 @@ static int gpio_led_off(struct device *dev, uint32_t led) return gpio_led_set(data, false); } +#if defined(CONFIG_LED_SOFTWARE_BLINK) +static void stop_sw_blink(struct led_gpio_data *data) +{ + k_timer_stop(&data->timer); + data->blink_delay_on = 0; + data->blink_delay_off = 0; + atomic_clear_bit(&data->flags, LED_SW_BLINK); +} + +static void led_timer_handler(struct k_timer *timer) +{ + struct led_gpio_data *data; + uint32_t delay; + bool state; + int err; + + data = CONTAINER_OF(timer, struct led_gpio_data, timer); + + if (atomic_test_and_clear_bit(&data->flags, LED_BLINK_DISABLE)) { + stop_sw_blink(data); + gpio_led_set_no_delay(data, data->next_state); + + return; + } + + if (data->state) { + delay = data->blink_delay_off; + state = 0; + } else { + delay = data->blink_delay_on; + state = 1; + } + + err = gpio_led_set_no_delay(data, state); + if (err) { + LOG_DBG("%s blink error: %d", data->cfg->label, err); + } + + k_timer_start(&data->timer, K_MSEC(delay), K_NO_WAIT); +} + +static int gpio_led_blink(struct device *dev, uint32_t led, + uint32_t delay_on, uint32_t delay_off) +{ + struct led_gpio_data *data = led_parameters_get(dev, led); + + if (!delay_on) { + return gpio_led_off(dev, led); + } + + if (!delay_off) { + return gpio_led_on(dev, led); + } + + data->blink_delay_on = delay_on; + data->blink_delay_off = delay_off; + + atomic_set_bit(&data->flags, LED_SW_BLINK); + + LOG_DBG("%s blink start, delay on: %d, delay off: %d", + data->cfg->label, delay_on, delay_off); + + k_timer_start(&data->timer, K_NO_WAIT, K_NO_WAIT); + + return 0; +} +#endif /* defined(CONFIG_LED_SOFTWARE_BLINK) */ + static const struct led_driver_api led_api = { +#if defined(CONFIG_LED_SOFTWARE_BLINK) + .blink = gpio_led_blink, +#endif .on = gpio_led_on, .off = gpio_led_off, }; @@ -149,6 +241,10 @@ static int gpio_led_init(struct device *dev) data[i].cfg = &cfg->led[i]; + +#if defined(CONFIG_LED_SOFTWARE_BLINK) + k_timer_init(&data[i].timer, led_timer_handler, NULL); +#endif } return 0; From 8c29e951022dd8720b0acc48a5e760b9af7384fd Mon Sep 17 00:00:00 2001 From: Kamil Gawor Date: Fri, 20 Dec 2019 15:20:19 +0100 Subject: [PATCH 3/4] board: LED id for nrf52840_pca10056 Add LED ID for nrf52840_pca10056 board. This id is required for gpio leds driver. Signed-off-by: Kamil Gawor --- boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts b/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts index 3659cd20f57f7..4d0541f63600d 100644 --- a/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts +++ b/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts @@ -27,18 +27,22 @@ led0: led_0 { gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; label = "Green LED 0"; + id = <1>; }; led1: led_1 { gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; label = "Green LED 1"; + id = <2>; }; led2: led_2 { gpios = <&gpio0 15 GPIO_ACTIVE_LOW>; label = "Green LED 2"; + id = <3>; }; led3: led_3 { gpios = <&gpio0 16 GPIO_ACTIVE_LOW>; label = "Green LED 3"; + id = <4>; }; }; From 9e7285488b6c970a65007b5da2bf70a15925e251 Mon Sep 17 00:00:00 2001 From: Kamil Gawor Date: Mon, 23 Dec 2019 11:40:03 +0100 Subject: [PATCH 4/4] samples: driver: Add LED GPIO sample This commit provides a new sample which demonstrate how to use the GPIO LED drivers. Signed-off-by: Kamil Gawor --- samples/drivers/led_gpio/CMakeLists.txt | 9 +++ samples/drivers/led_gpio/README.rst | 32 ++++++++++ samples/drivers/led_gpio/prj.conf | 4 ++ samples/drivers/led_gpio/sample.yaml | 9 +++ samples/drivers/led_gpio/src/main.c | 83 +++++++++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 samples/drivers/led_gpio/CMakeLists.txt create mode 100644 samples/drivers/led_gpio/README.rst create mode 100644 samples/drivers/led_gpio/prj.conf create mode 100644 samples/drivers/led_gpio/sample.yaml create mode 100644 samples/drivers/led_gpio/src/main.c diff --git a/samples/drivers/led_gpio/CMakeLists.txt b/samples/drivers/led_gpio/CMakeLists.txt new file mode 100644 index 0000000000000..60f3e9a301de6 --- /dev/null +++ b/samples/drivers/led_gpio/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(led_gpio) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/led_gpio/README.rst b/samples/drivers/led_gpio/README.rst new file mode 100644 index 0000000000000..f6592b72eb6e6 --- /dev/null +++ b/samples/drivers/led_gpio/README.rst @@ -0,0 +1,32 @@ +.. _gpio-led-sample: + +LED GPIO Sample +############### + +Overview +******** + +The sample assumes that the LEDs are connected to GPIO lines. +It is configured to work on boards that have defined the led0 and led1 +aliases and id properties for them in their board devicetree description file. +The LED id properties are required for the LED GPIO driver. +The LEDs are controlled, using the +following pattern: + +1. Turn on LEDs +#. Turn off LEDs +#. Blink the LEDs +#. Turn off LEDs + +Building and Running +******************** + +This sample can be built and flashed to a board as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/driver/led_gpio + :board: nrf52840_pca10056 + :goals: build flash + :compact: + +After flashing the image to the board, the user LEDs on the board should act according to the given pattern. diff --git a/samples/drivers/led_gpio/prj.conf b/samples/drivers/led_gpio/prj.conf new file mode 100644 index 0000000000000..e9474acce3a9c --- /dev/null +++ b/samples/drivers/led_gpio/prj.conf @@ -0,0 +1,4 @@ +CONFIG_GPIO=y +CONFIG_LED=y +CONFIG_LED_GPIO=y +CONFIG_LED_SOFTWARE_BLINK=y diff --git a/samples/drivers/led_gpio/sample.yaml b/samples/drivers/led_gpio/sample.yaml new file mode 100644 index 0000000000000..1fab0dadbc876 --- /dev/null +++ b/samples/drivers/led_gpio/sample.yaml @@ -0,0 +1,9 @@ +sample: + name: LED GPIO driver sample + description: Demonstration of the LED GPIO driver +tests: + sample.drivers.led.gpio: + tags: LED gpio + depends_on: gpio + harness: led + platform_whitelist: nrf52840_pca10056 diff --git a/samples/drivers/led_gpio/src/main.c b/samples/drivers/led_gpio/src/main.c new file mode 100644 index 0000000000000..1f1a1dd910209 --- /dev/null +++ b/samples/drivers/led_gpio/src/main.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define LED_DEV_NAME "LED_GPIO_0" +#define NUM_LEDS 2 +#define BLINK_DELAY_ON 500 +#define BLINK_DELAY_OFF 500 +#define DELAY_TIME 1000 + +static uint8_t led_id[NUM_LEDS] = {DT_PROP(DT_NODELABEL(led0), id), + DT_PROP(DT_NODELABEL(led1), id)}; + +void main(void) +{ + struct device *led_dev; + int i, ret; + + led_dev = device_get_binding(LED_DEV_NAME); + if (led_dev) { + printk("Found LED device %s\n", LED_DEV_NAME); + } else { + printk("LED device %s not found\n", LED_DEV_NAME); + return; + } + + printk("Testing leds"); + + while (1) { + /* Turn on LEDs one by one */ + for (i = 0; i < NUM_LEDS; i++) { + ret = led_on(led_dev, led_id[i]); + if (ret < 0) { + return; + } + + k_msleep(DELAY_TIME); + } + + /* Turn off LEDs one by one */ + for (i = 0; i < NUM_LEDS; i++) { + ret = led_off(led_dev, led_id[i]); + if (ret < 0) { + return; + } + + k_msleep(DELAY_TIME); + } + + /* Test the blinking of LEDs one by one */ + for (i = 0; i < NUM_LEDS; i++) { + ret = led_blink(led_dev, led_id[i], BLINK_DELAY_ON, + BLINK_DELAY_OFF); + if (ret < 0) { + return; + } + + k_msleep(DELAY_TIME); + } + + /* Wait a few blinking before turning off the LEDs */ + k_msleep(DELAY_TIME * 10); + + /* Turn off LEDs one by one */ + for (i = 0; i < NUM_LEDS; i++) { + ret = led_off(led_dev, led_id[i]); + if (ret < 0) { + return; + } + + k_msleep(DELAY_TIME); + } + } +}