diff --git a/boards/silabs/radio_boards/siwx917_rb4338a/siwx917_rb4338a.yaml b/boards/silabs/radio_boards/siwx917_rb4338a/siwx917_rb4338a.yaml index 12dd348c93c..2a57439c4fa 100644 --- a/boards/silabs/radio_boards/siwx917_rb4338a/siwx917_rb4338a.yaml +++ b/boards/silabs/radio_boards/siwx917_rb4338a/siwx917_rb4338a.yaml @@ -14,5 +14,6 @@ supported: - flash - gpio - i2c + - pwm - wifi vendor: silabs diff --git a/drivers/clock_control/clock_control_silabs_siwx91x.c b/drivers/clock_control/clock_control_silabs_siwx91x.c index 44f4b3909e6..3d89921b332 100644 --- a/drivers/clock_control/clock_control_silabs_siwx91x.c +++ b/drivers/clock_control/clock_control_silabs_siwx91x.c @@ -64,6 +64,10 @@ static int siwx91x_clock_on(const struct device *dev, clock_control_subsys_t sys RSI_PS_M4ssPeriPowerUp(M4SS_PWRGATE_ULP_EFUSE_PERI); RSI_CLK_PeripheralClkEnable(M4CLK, UDMA_CLK, ENABLE_STATIC_CLK); break; + case SIWX91X_CLK_PWM: + RSI_PS_M4ssPeriPowerUp(M4SS_PWRGATE_ULP_EFUSE_PERI); + RSI_CLK_PeripheralClkEnable(M4CLK, PWM_CLK, ENABLE_STATIC_CLK); + break; default: return -EINVAL; } @@ -121,6 +125,10 @@ static int siwx91x_clock_get_rate(const struct device *dev, clock_control_subsys case SIWX91X_CLK_UART2: *rate = RSI_CLK_GetBaseClock(M4_UART1); return 0; + case SIWX91X_CLK_PWM: + /* PWM peripheral operates at the system clock frequency */ + *rate = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; + return 0; default: /* For now, no other driver need clock rate */ return -EINVAL; diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index c194ac8485f..4c1b22e9c63 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -31,6 +31,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NPCX pwm_npcx.c) zephyr_library_sources_ifdef(CONFIG_PWM_XLNX_AXI_TIMER pwm_xlnx_axi_timer.c) zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_PWT pwm_mcux_pwt.c) zephyr_library_sources_ifdef(CONFIG_PWM_GECKO pwm_gecko.c) +zephyr_library_sources_ifdef(CONFIG_PWM_SILABS_SIWX91X pwm_silabs_siwx91x.c) zephyr_library_sources_ifdef(CONFIG_PWM_GD32 pwm_gd32.c) zephyr_library_sources_ifdef(CONFIG_PWM_RCAR pwm_rcar.c) zephyr_library_sources_ifdef(CONFIG_PWM_PCA9685 pwm_pca9685.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0dd835a8973..57a684ca2d1 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -84,6 +84,8 @@ source "drivers/pwm/Kconfig.mcux_pwt" source "drivers/pwm/Kconfig.gecko" +source "drivers/pwm/Kconfig.siwx91x" + source "drivers/pwm/Kconfig.gd32" source "drivers/pwm/Kconfig.rcar" diff --git a/drivers/pwm/Kconfig.siwx91x b/drivers/pwm/Kconfig.siwx91x new file mode 100644 index 00000000000..6cf16ade50d --- /dev/null +++ b/drivers/pwm/Kconfig.siwx91x @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +config PWM_SILABS_SIWX91X + bool "Silabs SiWx91x PWM driver" + default y + depends on DT_HAS_SILABS_SIWX91X_PWM_ENABLED + help + Enable the PWM driver for the Silabs SiWx91x SoC series. diff --git a/drivers/pwm/pwm_silabs_siwx91x.c b/drivers/pwm/pwm_silabs_siwx91x.c new file mode 100644 index 00000000000..520776a3717 --- /dev/null +++ b/drivers/pwm/pwm_silabs_siwx91x.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sl_si91x_pwm.h" + +#define DT_DRV_COMPAT silabs_siwx91x_pwm + +#define PWM_CHANNELS 4 +#define DEFAULT_VALUE 0xFF + +struct pwm_siwx91x_channel_config { + uint8_t duty_cycle; + uint32_t frequency; + bool is_chan_active; +}; + +struct pwm_siwx91x_config { + /* Pointer to the clock device structure */ + const struct device *clock_dev; + /* Clock control subsystem */ + clock_control_subsys_t clock_subsys; + /* Pointer to the pin control device configuration */ + const struct pinctrl_dev_config *pcfg; + /* Prescaler information of the channels */ + uint8_t ch_prescaler[PWM_CHANNELS]; + /* Common PWM polarity for all the channels */ + uint8_t pwm_polarity; +}; + +struct pwm_siwx91x_data { + struct pwm_siwx91x_channel_config pwm_channel_cfg[PWM_CHANNELS]; +}; + +/* Function to convert prescaler value to a programmable reg value */ +static int siwx91x_prescale_convert(uint8_t prescale) +{ + switch (prescale) { + case 1: + return SL_TIME_PERIOD_PRESCALE_1; + case 2: + return SL_TIME_PERIOD_PRESCALE_2; + case 4: + return SL_TIME_PERIOD_PRESCALE_4; + case 8: + return SL_TIME_PERIOD_PRESCALE_8; + case 16: + return SL_TIME_PERIOD_PRESCALE_16; + case 32: + return SL_TIME_PERIOD_PRESCALE_32; + case 64: + return SL_TIME_PERIOD_PRESCALE_64; + default: + return -EINVAL; + } +} + +/* Program PWM channel with the default configurations */ +static int siwx91x_default_channel_config(const struct device *dev, uint32_t channel) +{ + const struct pwm_siwx91x_config *config = dev->config; + int prescale_reg_value = siwx91x_prescale_convert(config->ch_prescaler[channel]); + int ret; + + if (prescale_reg_value < 0) { + return -EINVAL; + } + + ret = sl_si91x_pwm_set_output_mode(SL_MODE_INDEPENDENT, channel); + if (ret) { + return -EINVAL; + } + + ret = sl_si91x_pwm_set_base_timer_mode(SL_FREE_RUN_MODE, channel); + if (ret) { + return -EINVAL; + } + + ret = sl_si91x_pwm_control_base_timer(SL_BASE_TIMER_EACH_CHANNEL); + if (ret) { + return -EINVAL; + } + + ret = sl_si91x_pwm_control_period(SL_TIME_PERIOD_POSTSCALE_1_1, prescale_reg_value, + channel); + if (ret) { + return -EINVAL; + } + + return 0; +} + +static int pwm_siwx91x_set_cycles(const struct device *dev, uint32_t channel, + uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags) +{ + const struct pwm_siwx91x_config *config = dev->config; + struct pwm_siwx91x_data *data = dev->data; + uint32_t prev_period; + uint32_t duty_cycle; + int ret; + + if (channel >= ARRAY_SIZE(data->pwm_channel_cfg)) { + return -EINVAL; + } + + if (config->pwm_polarity != flags) { + /* Polarity mismatch */ + return -ENOTSUP; + } + + if (data->pwm_channel_cfg[channel].is_chan_active == false) { + /* Configure the channel with default parameters */ + ret = siwx91x_default_channel_config(dev, channel); + if (ret) { + return -EINVAL; + } + } + + ret = sl_si91x_pwm_get_time_period(channel, (uint16_t *)&prev_period); + if (ret) { + return -EINVAL; + } + + if (period_cycles != prev_period) { + ret = sl_si91x_pwm_set_time_period(channel, period_cycles, 0); + if (ret) { + /* Programmed value must be out of range (>65535) */ + return -EINVAL; + } + } + + /* Calculate the duty cycle */ + duty_cycle = pulse_cycles * 100; + duty_cycle /= period_cycles; + + if (duty_cycle != data->pwm_channel_cfg[channel].duty_cycle) { + ret = sl_si91x_pwm_set_duty_cycle(pulse_cycles, channel); + if (ret) { + return -EINVAL; + } + data->pwm_channel_cfg[channel].duty_cycle = duty_cycle; + } + + if (data->pwm_channel_cfg[channel].is_chan_active == false) { + /* Start PWM after configuring the channel for first time */ + ret = sl_si91x_pwm_start(channel); + if (ret) { + return -EINVAL; + } + data->pwm_channel_cfg[channel].is_chan_active = true; + } + + return 0; +} + +static int pwm_siwx91x_get_cycles_per_sec(const struct device *dev, uint32_t channel, + uint64_t *cycles) +{ + struct pwm_siwx91x_data *data = dev->data; + + if (channel >= ARRAY_SIZE(data->pwm_channel_cfg)) { + return -EINVAL; + } + + *cycles = (uint64_t)data->pwm_channel_cfg[channel].frequency; + + return 0; +} + +static int pwm_siwx91x_init(const struct device *dev) +{ + const struct pwm_siwx91x_config *config = dev->config; + struct pwm_siwx91x_data *data = dev->data; + bool polarity_inverted = (config->pwm_polarity == PWM_POLARITY_INVERTED); + uint32_t pwm_frequency; + int ret; + + ret = clock_control_on(config->clock_dev, config->clock_subsys); + if (ret) { + return ret; + } + + ret = clock_control_get_rate(config->clock_dev, config->clock_subsys, &pwm_frequency); + if (ret) { + return ret; + } + + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret) { + return ret; + } + + ARRAY_FOR_EACH(data->pwm_channel_cfg, i) { + data->pwm_channel_cfg[i].frequency = pwm_frequency / config->ch_prescaler[i]; + } + + ret = sl_si91x_pwm_set_output_polarity(polarity_inverted, !polarity_inverted); + if (ret) { + return -EINVAL; + } + + return 0; +} + +static DEVICE_API(pwm, pwm_siwx91x_driver_api) = { + .set_cycles = pwm_siwx91x_set_cycles, + .get_cycles_per_sec = pwm_siwx91x_get_cycles_per_sec, +}; + +#define SIWX91X_PWM_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + static struct pwm_siwx91x_data pwm_siwx91x_data_##inst; \ + static const struct pwm_siwx91x_config pwm_config_##inst = { \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_PHA(inst, clocks, clkid), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .ch_prescaler = DT_INST_PROP(inst, silabs_ch_prescaler), \ + .pwm_polarity = DT_INST_PROP(inst, silabs_pwm_polarity), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, &pwm_siwx91x_init, NULL, &pwm_siwx91x_data_##inst, \ + &pwm_config_##inst, PRE_KERNEL_1, CONFIG_PWM_INIT_PRIORITY, \ + &pwm_siwx91x_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SIWX91X_PWM_INIT) diff --git a/dts/arm/silabs/siwg917.dtsi b/dts/arm/silabs/siwg917.dtsi index dfa80e4cde3..6fa3e202f9c 100644 --- a/dts/arm/silabs/siwg917.dtsi +++ b/dts/arm/silabs/siwg917.dtsi @@ -261,6 +261,19 @@ dma-channels = <12>; status = "disabled"; }; + + pwm: pwm@47070000 { + compatible = "silabs,siwx91x-pwm"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x47070000 0x14C>; + interrupts = <48 0>; + interrupt-names = "pwm"; + clocks = <&clock0 SIWX91X_CLK_PWM>; + #pwm-cells = <2>; + silabs,ch_prescaler = <64 64 64 64>; + status = "disabled"; + }; }; }; diff --git a/dts/bindings/pwm/silabs,siwx91x-pwm.yaml b/dts/bindings/pwm/silabs,siwx91x-pwm.yaml new file mode 100644 index 00000000000..b8a01f2027b --- /dev/null +++ b/dts/bindings/pwm/silabs,siwx91x-pwm.yaml @@ -0,0 +1,39 @@ +description: | + Silabs siwx91x PWM Controller + + The siwx91x PWM controller is designed to generate PWM signals. The mapping + between PWM channels and GPIO pins is configured through pinctrl settings. + +compatible: "silabs,siwx91x-pwm" + +include: [base.yaml, pinctrl-device.yaml, pwm-controller.yaml] + +properties: + "#pwm-cells": + const: 2 + + silabs,ch_prescaler: + type: array + required: true + description: | + Contains the prescaler values for all the 4 channels + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + + silabs,pwm_polarity: + type: int + required: true + description: | + Common PWM polarity for all the channels + 0 - Normal polarity + 1 - Inverted polarity + +pwm-cells: + - channel + - period diff --git a/include/zephyr/dt-bindings/clock/silabs/siwx91x-clock.h b/include/zephyr/dt-bindings/clock/silabs/siwx91x-clock.h index 06a007e82eb..21042b215c9 100644 --- a/include/zephyr/dt-bindings/clock/silabs/siwx91x-clock.h +++ b/include/zephyr/dt-bindings/clock/silabs/siwx91x-clock.h @@ -12,5 +12,6 @@ #define SIWX91X_CLK_I2C0 5 #define SIWX91X_CLK_I2C1 6 #define SIWX91X_CLK_DMA0 7 +#define SIWX91X_CLK_PWM 9 #endif diff --git a/modules/hal_silabs/wiseconnect/CMakeLists.txt b/modules/hal_silabs/wiseconnect/CMakeLists.txt index a7f83f6d7c7..1bca4a528d8 100644 --- a/modules/hal_silabs/wiseconnect/CMakeLists.txt +++ b/modules/hal_silabs/wiseconnect/CMakeLists.txt @@ -22,6 +22,7 @@ zephyr_compile_definitions( zephyr_include_directories( ${SISDK_DIR}/platform/common/inc ${SISDK_DIR}/platform/common/config + ${WISECONNECT_DIR}/components/board/silabs/inc ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/config ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/chip/inc ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/rom_driver/inc @@ -40,11 +41,13 @@ zephyr_library_sources( ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/chip/src/rsi_deepsleep_soc.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/chip/src/system_si91x.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/peripheral_drivers/src/clock_update.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/peripheral_drivers/src/rsi_pwm.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/systemlevel/src/rsi_ipmu.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/systemlevel/src/rsi_pll.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/systemlevel/src/rsi_ulpss_clk.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/service/clock_manager/src/sl_si91x_clock_manager.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/unified_api/src/sl_si91x_driver_gpio.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/unified_api/src/sl_si91x_pwm.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/unified_peripheral_drivers/src/sl_si91x_peripheral_gpio.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/chip/src/iPMU_prog/iPMU_dotc/ipmu_apis.c ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/core/chip/src/iPMU_prog/iPMU_dotc/rsi_system_config_917.c diff --git a/tests/drivers/pwm/pwm_api/boards/siwx917_rb4338a.overlay b/tests/drivers/pwm/pwm_api/boards/siwx917_rb4338a.overlay new file mode 100644 index 00000000000..e2bf3f35472 --- /dev/null +++ b/tests/drivers/pwm/pwm_api/boards/siwx917_rb4338a.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + pwm-0 = &pwm; + }; +}; + +&pinctrl0 { + pwm_ch: pwm_ch { + group1 { + pinmux = ; /* GPIO_7 P20 */ + }; + }; +}; + +&pwm { + pinctrl-0 = <&pwm_ch>; + pinctrl-names = "default"; + pwm_channel1: pwm_channel1 { + pwms = <&pwm 0 1000000>; + }; + silabs,pwm_polarity = ; + status = "okay"; +}; diff --git a/west.yml b/west.yml index c45ad70d6bf..084f8503188 100644 --- a/west.yml +++ b/west.yml @@ -228,7 +228,7 @@ manifest: groups: - hal - name: hal_silabs - revision: 2ad0fd7d56a9142a0479f5745277fda8b21da0db + revision: 9d32354344f6c816410e2642c2f81677f8a60e96 path: modules/hal/silabs groups: - hal