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 <simon.guinot@seagate.com>
This commit is contained in:
Simon Guinot 2020-10-19 19:50:33 +02:00 committed by Maureen Helm
commit 525a588db4
4 changed files with 174 additions and 0 deletions

View file

@ -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)

View file

@ -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
drivers/led/Kconfig.pwm Normal file
View file

@ -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.

164
drivers/led/led_pwm.c Normal file
View file

@ -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;
}
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)