Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

161 lines
4.6 KiB

/*
* Copyright (c) 2025 Henrik Brix Andersen <henrik@brixandersen.dk>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT neorv32_pwm
#include <zephyr/device.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/syscon.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <soc.h>
LOG_MODULE_REGISTER(pwm_neorv32, CONFIG_PWM_LOG_LEVEL);
/* NEORV32 PWM CHANNEL_CFG[0..15] register bits */
#define NEORV32_PWM_CFG_EN BIT(31)
#define NEORV32_PWM_CFG_PRSC GENMASK(30, 28)
#define NEORV32_PWM_CFG_POL BIT(27)
#define NEORV32_PWM_CFG_CDIV GENMASK(17, 8)
#define NEORV32_PWM_CFG_DUTY GENMASK(7, 0)
/* Maximum number of PWMs supported */
#define NEORV32_PWM_CHANNELS 16
struct neorv32_pwm_config {
const struct device *syscon;
mm_reg_t base;
};
static inline void neorv32_pwm_write_channel_cfg(const struct device *dev, uint8_t channel,
uint32_t cfg)
{
const struct neorv32_pwm_config *config = dev->config;
__ASSERT_NO_MSG(channel < NEORV32_PWM_CHANNELS);
sys_write32(cfg, config->base + (channel * sizeof(uint32_t)));
}
static int neorv32_pwm_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags)
{
static const uint16_t cdiv_max = FIELD_GET(NEORV32_PWM_CFG_CDIV, NEORV32_PWM_CFG_CDIV);
static const uint16_t steps = FIELD_GET(NEORV32_PWM_CFG_DUTY, NEORV32_PWM_CFG_DUTY) + 1U;
static const uint16_t prsc_tbl[] = {2, 4, 8, 64, 128, 1024, 2048, 4096};
uint32_t cfg = 0U;
uint8_t duty = 0U;
uint16_t cdiv;
uint8_t prsc;
if (channel >= NEORV32_PWM_CHANNELS) {
LOG_ERR("invalid PWM channel %u", channel);
return -EINVAL;
}
if (pulse_cycles == 0U) {
/* Constant inactive level */
if ((flags & PWM_POLARITY_INVERTED) != 0U) {
cfg |= NEORV32_PWM_CFG_POL;
}
} else if (pulse_cycles == period_cycles) {
/* Constant active level */
if ((flags & PWM_POLARITY_INVERTED) == 0U) {
cfg |= NEORV32_PWM_CFG_POL;
}
} else {
/* PWM enabled */
if ((flags & PWM_POLARITY_INVERTED) != 0U) {
cfg |= NEORV32_PWM_CFG_POL;
}
for (prsc = 0; prsc < ARRAY_SIZE(prsc_tbl); prsc++) {
if (period_cycles / prsc_tbl[prsc] <= steps * (1U + cdiv_max)) {
break;
}
}
cdiv = DIV_ROUND_CLOSEST(DIV_ROUND_CLOSEST(period_cycles, prsc_tbl[prsc]), steps) -
1U;
duty = CLAMP(DIV_ROUND_CLOSEST((uint64_t)(pulse_cycles * steps), period_cycles),
1U, steps - 1U);
cfg |= NEORV32_PWM_CFG_EN | FIELD_PREP(NEORV32_PWM_CFG_PRSC, prsc) |
FIELD_PREP(NEORV32_PWM_CFG_CDIV, cdiv) |
FIELD_PREP(NEORV32_PWM_CFG_DUTY, duty);
}
neorv32_pwm_write_channel_cfg(dev, channel, cfg);
return 0;
}
static int neorv32_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel,
uint64_t *cycles)
{
const struct neorv32_pwm_config *config = dev->config;
uint32_t clk;
int err;
if (channel >= NEORV32_PWM_CHANNELS) {
LOG_ERR("invalid PWM channel %u", channel);
return -EINVAL;
}
err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_CLK, &clk);
if (err < 0) {
LOG_ERR("failed to determine clock rate (err %d)", err);
return -EIO;
}
*cycles = clk;
return 0;
}
static int neorv32_pwm_init(const struct device *dev)
{
const struct neorv32_pwm_config *config = dev->config;
uint32_t features;
int err;
if (!device_is_ready(config->syscon)) {
LOG_ERR("syscon device not ready");
return -EINVAL;
}
err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_SOC, &features);
if (err < 0) {
LOG_ERR("failed to determine implemented features (err %d)", err);
return -EIO;
}
if ((features & NEORV32_SYSINFO_SOC_IO_PWM) == 0) {
LOG_ERR("neorv32 pwm not supported");
return -ENODEV;
}
return 0;
}
static DEVICE_API(pwm, neorv32_pwm_driver_api) = {
.set_cycles = neorv32_pwm_set_cycles,
.get_cycles_per_sec = neorv32_pwm_get_cycles_per_sec,
};
#define NEORV32_PWM_INIT(n) \
static const struct neorv32_pwm_config neorv32_pwm_##n##_config = { \
.syscon = DEVICE_DT_GET(DT_INST_PHANDLE(n, syscon)), \
.base = DT_INST_REG_ADDR(n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, neorv32_pwm_init, NULL, NULL, &neorv32_pwm_##n##_config, \
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &neorv32_pwm_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NEORV32_PWM_INIT)