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>; }; }; 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..81dc3a806380a --- /dev/null +++ b/drivers/led/Kconfig.led_gpio @@ -0,0 +1,24 @@ +# 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 + +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" + +endif # LED_GPIO diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c new file mode 100644 index 0000000000000..32355cb5443bf --- /dev/null +++ b/drivers/led/led_gpio.c @@ -0,0 +1,264 @@ +/* + * 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) \ +}, +#define LED_SW_BLINK BIT(0) +#define LED_BLINK_DISABLE BIT(1) + +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; +#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; +}; + +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) +{ +#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); +} + +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); +} + +#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, +}; + +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]; + +#if defined(CONFIG_LED_SOFTWARE_BLINK) + k_timer_init(&data[i].timer, led_timer_handler, NULL); +#endif + } + + 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); 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); + } + } +}