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