Skip to content

drivers: pwm: add NEORV32 PWM controller driver #88559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion boards/others/neorv32/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For more information about the NEORV32, see the following websites:
- `The NEORV32 RISC-V Processor Datasheet`_
- `The NEORV32 RISC-V Processor User Guide`_

The currently supported version is NEORV32 v1.11.2.
The currently supported version is NEORV32 v1.11.3.

Supported Board Targets
=======================
Expand Down Expand Up @@ -97,6 +97,13 @@ supporting the GPIOs, support can be enabled by setting the ``status`` property
devicetree node to ``okay``. The number of supported GPIOs can be set via the ``ngpios`` devicetree
property.

Pulse-Width Modulation
======================

The NEORV32 PWM controller is supported but disabled by default. For NEORV32 SoC implementations
supporting PWM, support can be enabled by setting the ``status`` property of the ``pwm`` devicetree
node to ``okay``.

True Random-Number Generator
============================

Expand Down
26 changes: 26 additions & 0 deletions boards/others/neorv32/neorv32_neorv32_minimalboot.dts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
led1 = &led1;
led2 = &led2;
led3 = &led3;
pwm-led0 = &pwm_led0;
pwm-led1 = &pwm_led1;
pwm-led2 = &pwm_led2;
};

chosen {
Expand Down Expand Up @@ -51,6 +54,25 @@
label = "LED_3";
};
};

pwmleds {
compatible = "pwm-leds";

pwm_led0: pwm_led0 {
pwms = <&pwm 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_0";
};

pwm_led1: pwm_led1 {
pwms = <&pwm 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_1";
};

pwm_led2: pwm_led2 {
pwms = <&pwm 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_2";
};
};
};

&cpu0 {
Expand Down Expand Up @@ -88,3 +110,7 @@
status = "okay";
ngpios = <4>;
};

&pwm {
status = "okay";
};
1 change: 1 addition & 0 deletions boards/others/neorv32/neorv32_neorv32_minimalboot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ ram: 64
flash: 64
supported:
- gpio
- pwm
26 changes: 26 additions & 0 deletions boards/others/neorv32/neorv32_neorv32_up5kdemo.dts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
led1 = &led1;
led2 = &led2;
led3 = &led3;
pwm-led0 = &pwm_led0;
pwm-led1 = &pwm_led1;
pwm-led2 = &pwm_led2;
};

chosen {
Expand Down Expand Up @@ -51,6 +54,25 @@
label = "LED_3";
};
};

pwmleds {
compatible = "pwm-leds";

pwm_led0: pwm_led0 {
pwms = <&pwm 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_0";
};

pwm_led1: pwm_led1 {
pwms = <&pwm 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_1";
};

pwm_led2: pwm_led2 {
pwms = <&pwm 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
label = "PWM_LED_2";
};
};
};

&cpu0 {
Expand Down Expand Up @@ -88,3 +110,7 @@
status = "okay";
ngpios = <32>;
};

&pwm {
status = "okay";
};
1 change: 1 addition & 0 deletions boards/others/neorv32/neorv32_neorv32_up5kdemo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ ram: 64
flash: 64
supported:
- gpio
- pwm
2 changes: 1 addition & 1 deletion doc/releases/migration-guide-4.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Boards
* The DT binding :dtcompatible:`zephyr,native-posix-cpu` has been deprecated in favor of
:dtcompatible:`zephyr,native-sim-cpu`.

* Zephyr now supports version 1.11.2 of the :zephyr:board:`neorv32`. NEORV32 processor (SoC)
* Zephyr now supports version 1.11.3 of the :zephyr:board:`neorv32`. NEORV32 processor (SoC)
implementations need to be updated to this version to be compatible with Zephyr v4.2.0.

* The :zephyr:board:`neorv32` now targets NEORV32 processor (SoC) templates via board variants. The
Expand Down
1 change: 1 addition & 0 deletions drivers/pwm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RA pwm_renesas_ra.c)
zephyr_library_sources_ifdef(CONFIG_PWM_INFINEON_CAT1 pwm_ifx_cat1.c)
zephyr_library_sources_ifdef(CONFIG_PWM_FAKE pwm_fake.c)
zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NEORV32 pwm_neorv32.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)
2 changes: 2 additions & 0 deletions drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@ source "drivers/pwm/Kconfig.fake"

source "drivers/pwm/Kconfig.renesas_rz"

source "drivers/pwm/Kconfig.neorv32"

endif # PWM
12 changes: 12 additions & 0 deletions drivers/pwm/Kconfig.neorv32
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# NEORV32 PWM configuration options

# Copyright (c) 2025 Henrik Brix Andersen <[email protected]>
# SPDX-License-Identifier: Apache-2.0

config PWM_NEORV32
bool "NEORV32 PWM driver"
default y
depends on DT_HAS_NEORV32_PWM_ENABLED
depends on SYSCON
help
Enable NEORV32 PWM driver.
161 changes: 161 additions & 0 deletions drivers/pwm/pwm_neorv32.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2025 Henrik Brix Andersen <[email protected]>
*
* 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)
27 changes: 27 additions & 0 deletions dts/bindings/pwm/neorv32,pwm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) 2025 Henrik Brix Andersen <[email protected]>
# SPDX-License-Identifier: Apache-2.0

description: NEORV32 PWM

compatible: "neorv32,pwm"

include: [pwm-controller.yaml, base.yaml]

properties:
reg:
required: true

syscon:
type: phandle
required: true
description: |
phandle to syscon (NEORV32 SYSINFO) node.

"#pwm-cells":
const: 3

pwm-cells:
- channel
# period in terms of nanoseconds
- period
- flags
9 changes: 9 additions & 0 deletions dts/riscv/neorv32.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <skeleton.dtsi>
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>

/ {
chosen {
Expand Down Expand Up @@ -79,6 +80,14 @@
reg = <0xffe00000 0x10000>;
};

pwm: pwm@fff00000 {
compatible = "neorv32,pwm";
status = "disabled";
reg = <0xfff00000 0x10000>;
syscon = <&sysinfo>;
#pwm-cells = <3>;
};

clint: clint@fff40000 {
compatible = "neorv32,clint", "sifive,clint0";
status = "disabled";
Expand Down
2 changes: 1 addition & 1 deletion soc/neorv32/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if SOC_NEORV32

config SOC_NEORV32_VERSION
hex
default 0x01110200
default 0x01110300
help
The targeted NEORV32 version as BCD-coded number. The format is
identical to that of the NEORV32 Machine implementation ID (mimpid)
Expand Down
Loading