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