blob: 90db66bf32d9f98e4595fc1e7e864a5f138d1435 [file] [log] [blame]
/*
* Copyright (c) 2020 Seagate Technology LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT pwm_leds
/**
* @file
* @brief PWM driven LEDs
*/
#include <zephyr/drivers/led.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/math_extras.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(led_pwm, CONFIG_LED_LOG_LEVEL);
struct led_pwm_config {
int num_leds;
const struct pwm_dt_spec *led;
};
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->config;
const struct pwm_dt_spec *dt_led;
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;
}
dt_led = &config->led[led];
return pwm_set_dt(dt_led, PWM_USEC(period_usec), PWM_USEC(pulse_usec));
}
static int led_pwm_set_brightness(const struct device *dev,
uint32_t led, uint8_t value)
{
const struct led_pwm_config *config = dev->config;
const struct pwm_dt_spec *dt_led;
if (led >= config->num_leds || value > 100) {
return -EINVAL;
}
dt_led = &config->led[led];
return pwm_set_pulse_dt(&config->led[led],
dt_led->period * value / 100);
}
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->config;
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 pwm_dt_spec *led = &config->led[i];
if (!device_is_ready(led->dev)) {
LOG_ERR("%s: pwm device not ready", led->dev->name);
return -ENODEV;
}
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int led_pwm_pm_action(const struct device *dev,
enum pm_device_action action)
{
const struct led_pwm_config *config = dev->config;
/* switch all underlying PWM devices to the new state */
for (size_t i = 0; i < config->num_leds; i++) {
int err;
const struct pwm_dt_spec *led = &config->led[i];
LOG_DBG("PWM %p running pm action %" PRIu32, led->dev, action);
err = pm_device_action_run(led->dev, action);
if (err && (err != -EALREADY)) {
LOG_ERR("Cannot switch PWM %p power state", led->dev);
}
}
return 0;
}
#endif /* CONFIG_PM_DEVICE */
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_DEVICE(id) \
\
static const struct pwm_dt_spec led_pwm_##id[] = { \
DT_INST_FOREACH_CHILD_SEP(id, PWM_DT_SPEC_GET, (,)) \
}; \
\
static const struct led_pwm_config led_pwm_config_##id = { \
.num_leds = ARRAY_SIZE(led_pwm_##id), \
.led = led_pwm_##id, \
}; \
\
PM_DEVICE_DT_INST_DEFINE(id, led_pwm_pm_action); \
\
DEVICE_DT_INST_DEFINE(id, &led_pwm_init, \
PM_DEVICE_DT_INST_GET(id), NULL, \
&led_pwm_config_##id, POST_KERNEL, \
CONFIG_LED_INIT_PRIORITY, &led_pwm_api);
DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)