Instead of passing target states, use actions for device PM control. Actions represent better the meaning of the callback argument. Furthermore, they are more future proof as they can be suitable for other PM actions that have no direct mapping to a state. If we compare with Linux, we could have a multi-stage suspend/resume. Such scenario would not have a good mapping when using target states. Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
183 lines
4.2 KiB
C
183 lines
4.2 KiB
C
/*
|
|
* 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))
|
|
|
|
struct led_pwm {
|
|
const struct device *dev;
|
|
uint32_t channel;
|
|
uint32_t period;
|
|
pwm_flags_t flags;
|
|
};
|
|
|
|
struct led_pwm_config {
|
|
int num_leds;
|
|
const struct led_pwm *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_CFG(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(led_pwm->dev, 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);
|
|
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(led_pwm->dev, 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);
|
|
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];
|
|
|
|
if (!device_is_ready(led->dev)) {
|
|
LOG_ERR("%s: pwm device not ready", dev->name);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
static int led_pwm_pm_control(const struct device *dev,
|
|
enum pm_device_action action)
|
|
{
|
|
const struct led_pwm_config *config = DEV_CFG(dev);
|
|
|
|
/* switch all underlying PWM devices to the new state */
|
|
for (size_t i = 0; i < config->num_leds; i++) {
|
|
int err;
|
|
enum pm_device_state state;
|
|
const struct led_pwm *led_pwm = &config->led[i];
|
|
|
|
LOG_DBG("Switching PWM %p to state %" PRIu32, led_pwm->dev, state);
|
|
|
|
/* NOTE: temporary solution, deserves proper fix */
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
state = PM_DEVICE_STATE_ACTIVE;
|
|
break;
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
state = PM_DEVICE_STATE_SUSPENDED;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
err = pm_device_state_set(led_pwm->dev, state);
|
|
if (err) {
|
|
LOG_ERR("Cannot switch PWM %p power state", led_pwm->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(led_node_id) \
|
|
{ \
|
|
.dev = DEVICE_DT_GET(DT_PWMS_CTLR(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) \
|
|
\
|
|
static const struct led_pwm led_pwm_##id[] = { \
|
|
DT_INST_FOREACH_CHILD(id, LED_PWM) \
|
|
}; \
|
|
\
|
|
static const struct led_pwm_config led_pwm_config_##id = { \
|
|
.num_leds = ARRAY_SIZE(led_pwm_##id), \
|
|
.led = led_pwm_##id, \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(id, &led_pwm_init, led_pwm_pm_control, \
|
|
NULL, &led_pwm_config_##id, POST_KERNEL, \
|
|
CONFIG_LED_INIT_PRIORITY, &led_pwm_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)
|