Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LED PWM driver #29401

Merged
merged 2 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/led/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 1 addition & 0 deletions drivers/led/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions drivers/led/Kconfig.pwm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2020 Seagate Technology LLC
# SPDX-License-Identifier: Apache-2.0

config LED_PWM
bool "PWM LED driver"
simonguinot marked this conversation as resolved.
Show resolved Hide resolved
depends on PWM
help
Enable driver for PWM LEDs.
164 changes: 164 additions & 0 deletions drivers/led/led_pwm.c
Original file line number Diff line number Diff line change
@@ -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 <drivers/led.h>
#include <drivers/pwm.h>
#include <device.h>
#include <zephyr.h>
#include <sys/math_extras.h>

#include <logging/log.h>
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;
simonguinot marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
simonguinot marked this conversation as resolved.
Show resolved Hide resolved
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)
9 changes: 9 additions & 0 deletions samples/drivers/led_pwm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
33 changes: 33 additions & 0 deletions samples/drivers/led_pwm/README.rst
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions samples/drivers/led_pwm/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CONFIG_LOG=y

CONFIG_PWM=y

CONFIG_LED=y
CONFIG_LED_PWM=y
8 changes: 8 additions & 0 deletions samples/drivers/led_pwm/sample.yaml
Original file line number Diff line number Diff line change
@@ -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
129 changes: 129 additions & 0 deletions samples/drivers/led_pwm/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (c) 2020 Seagate Technology LLC
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <device.h>
#include <errno.h>
#include <drivers/led.h>
#include <sys/util.h>
#include <zephyr.h>

#include <logging/log.h>
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);
}