zephyr/drivers/clock_control/clock_control_pwm.c
Andriy Gelman 0d1fa268bb drivers: clock_control: Add PWM clock device
Adds a clock control device for a PWM node, allowing the PWM
to be controlled using the clock control API.

It is a similar idea to the device driver in linux:
linux/Documentation/devicetree/bindings/clock/pwm-clock.yaml

Signed-off-by: Andriy Gelman <andriy.gelman@gmail.com>
2023-11-20 09:18:44 +01:00

160 lines
4.9 KiB
C

/*
* Copyright (c) 2023 Andriy Gelman <andriy.gelman@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT pwm_clock
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/kernel.h>
#define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(clock_control_pwm);
BUILD_ASSERT(CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY > CONFIG_PWM_INIT_PRIORITY,
"PWM must have a higher priority than PWM clock control");
#define NUM_PWM_CLOCKS 1
struct clock_control_pwm_config {
const struct pwm_dt_spec pwm_dt;
const uint16_t pwm_on_delay;
};
struct clock_control_pwm_data {
uint32_t clock_frequency;
bool is_enabled;
};
static int clock_control_pwm_on(const struct device *dev, clock_control_subsys_t sys)
{
struct clock_control_pwm_data *data = dev->data;
const struct clock_control_pwm_config *config = dev->config;
const struct pwm_dt_spec *spec;
int id = (int)sys;
int ret;
if (id >= NUM_PWM_CLOCKS) {
return -EINVAL;
}
spec = &config->pwm_dt;
if (data->clock_frequency == 0) {
ret = pwm_set_dt(spec, spec->period, spec->period / 2);
} else {
uint64_t cycles_per_sec;
uint32_t period_cycles;
ret = pwm_get_cycles_per_sec(spec->dev, spec->channel, &cycles_per_sec);
if (ret) {
return ret;
}
if (cycles_per_sec % data->clock_frequency > 0) {
LOG_WRN("Target clock frequency cannot be expressed in PWM clock ticks");
}
period_cycles = cycles_per_sec / data->clock_frequency;
ret = pwm_set_cycles(spec->dev, spec->channel, period_cycles, period_cycles / 2,
spec->flags);
}
if (ret) {
return ret;
}
k_busy_wait(config->pwm_on_delay);
data->is_enabled = true;
return 0;
}
static int clock_control_pwm_get_rate(const struct device *dev, clock_control_subsys_t sys,
uint32_t *rate)
{
struct clock_control_pwm_data *data = dev->data;
const struct clock_control_pwm_config *config = dev->config;
int id = (int)sys;
if (id >= NUM_PWM_CLOCKS) {
return -EINVAL;
}
if (data->clock_frequency > 0) {
*rate = data->clock_frequency;
} else {
*rate = NSEC_PER_SEC / config->pwm_dt.period;
}
return 0;
}
static int clock_control_pwm_set_rate(const struct device *dev, clock_control_subsys_rate_t sys,
clock_control_subsys_rate_t rate)
{
struct clock_control_pwm_data *data = dev->data;
uint32_t rate_hz = (uint32_t)rate;
int id = (int)sys;
if (id >= NUM_PWM_CLOCKS) {
return -EINVAL;
}
if (data->clock_frequency == rate_hz && data->is_enabled) {
return -EALREADY;
}
data->clock_frequency = rate_hz;
return clock_control_pwm_on(dev, sys);
}
static int clock_control_pwm_init(const struct device *dev)
{
const struct clock_control_pwm_config *config = dev->config;
if (!device_is_ready(config->pwm_dt.dev)) {
return -ENODEV;
}
return 0;
}
static struct clock_control_driver_api clock_control_pwm_api = {
.on = clock_control_pwm_on,
.get_rate = clock_control_pwm_get_rate,
.set_rate = clock_control_pwm_set_rate,
};
#define PWM_CLOCK_INIT(i) \
\
BUILD_ASSERT(DT_INST_PROP_LEN(i, pwms) <= 1, \
"One PWM per clock control node is supported"); \
\
BUILD_ASSERT(DT_INST_PROP(i, pwm_on_delay) <= UINT16_MAX, \
"Maximum pwm-on-delay is 65535 usec"); \
\
static const struct clock_control_pwm_config clock_control_pwm_config_##i = { \
.pwm_dt = PWM_DT_SPEC_INST_GET(i), \
.pwm_on_delay = DT_INST_PROP(i, pwm_on_delay), \
}; \
\
static struct clock_control_pwm_data clock_control_pwm_data_##i = { \
.clock_frequency = DT_INST_PROP_OR(i, clock_frequency, 0), \
}; \
\
DEVICE_DT_INST_DEFINE(i, clock_control_pwm_init, NULL, &clock_control_pwm_data_##i, \
&clock_control_pwm_config_##i, POST_KERNEL, \
CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY, &clock_control_pwm_api);
DT_INST_FOREACH_STATUS_OKAY(PWM_CLOCK_INIT)