diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index fc12517c461..9a559a7b2be 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -32,6 +32,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_LETIMER pwm_silabs_letimer.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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 2000ac4fd50..7bc9251c131 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -86,6 +86,8 @@ source "drivers/pwm/Kconfig.mcux_pwt" source "drivers/pwm/Kconfig.gecko" +source "drivers/pwm/Kconfig.silabs" + source "drivers/pwm/Kconfig.siwx91x" source "drivers/pwm/Kconfig.gd32" diff --git a/drivers/pwm/Kconfig.silabs b/drivers/pwm/Kconfig.silabs new file mode 100644 index 00000000000..0dc4acb6397 --- /dev/null +++ b/drivers/pwm/Kconfig.silabs @@ -0,0 +1,14 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +config PWM_SILABS_LETIMER + bool "Silabs LETIMER PWM driver" + default y + depends on DT_HAS_SILABS_LETIMER_PWM_ENABLED + select SILABS_SISDK_LETIMER + help + Enable the PWM driver for the LETIMER peripheral on Silabs Series 2 SoCs. + + The LETIMER peripheral has two channels which share PWM period configuration. The last + configured period will apply to both channels. The output may glitch when updating the + PWM pulse and period, as the new values will take effect immediately. diff --git a/drivers/pwm/pwm_silabs_letimer.c b/drivers/pwm/pwm_silabs_letimer.c new file mode 100644 index 00000000000..910e2b4c913 --- /dev/null +++ b/drivers/pwm/pwm_silabs_letimer.c @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT silabs_letimer_pwm + +#include +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(pwm_silabs_letimer, CONFIG_PWM_LOG_LEVEL); + +struct silabs_letimer_pwm_config { + const struct pinctrl_dev_config *pcfg; + const struct device *clock_dev; + const struct silabs_clock_control_cmu_config clock_cfg; + LETIMER_TypeDef *base; + int clock_div; + bool run_in_debug; +}; + +static bool silabs_letimer_channel_is_pwm(const struct silabs_letimer_pwm_config *config, + uint32_t channel) +{ + uint32_t mask = (channel == 0) ? _LETIMER_CTRL_UFOA0_MASK : _LETIMER_CTRL_UFOA1_MASK; + + return FIELD_GET(mask, config->base->CTRL) == _LETIMER_CTRL_UFOA0_PWM; +} + +static int silabs_letimer_pwm_set_cycles(const struct device *dev, uint32_t channel, + uint32_t period_cycles, uint32_t pulse_cycles, + pwm_flags_t flags) +{ + bool invert_polarity = (flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED; + const struct silabs_letimer_pwm_config *config = dev->config; + + if (period_cycles >= BIT(24) || pulse_cycles >= BIT(24)) { + return -ENOTSUP; + } + + /* The hardware is not capable of driving a constant active level. + * Convert it into a constant inactive level with opposite polarity. + */ + if (pulse_cycles > 0 && pulse_cycles == period_cycles) { + invert_polarity = !invert_polarity; + pulse_cycles = 0; + } + + if (invert_polarity) { + sys_set_bit((mem_addr_t)&config->base->CTRL, channel + _LETIMER_CTRL_OPOL0_SHIFT); + } else { + sys_clear_bit((mem_addr_t)&config->base->CTRL, channel + _LETIMER_CTRL_OPOL0_SHIFT); + } + + if (!silabs_letimer_channel_is_pwm(config, channel)) { + config->base->CTRL_SET = + (channel == 0) ? LETIMER_CTRL_UFOA0_PWM : LETIMER_CTRL_UFOA1_PWM; + } + + sl_hal_letimer_set_compare(config->base, channel, pulse_cycles); + sl_hal_letimer_set_top(config->base, period_cycles); + + if (!(config->base->STATUS & LETIMER_STATUS_RUNNING)) { + sl_hal_letimer_start(config->base); + } + + return 0; +} + +static int silabs_letimer_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel, + uint64_t *cycles) +{ + const struct silabs_letimer_pwm_config *config = dev->config; + uint32_t clock_rate; + int err; + + err = clock_control_get_rate(config->clock_dev, (clock_control_subsys_t)&config->clock_cfg, + &clock_rate); + if (err < 0) { + return err; + } + + *cycles = clock_rate / BIT(config->clock_div); + + return 0; +} + +static int silabs_letimer_pwm_pm_action(const struct device *dev, enum pm_device_action action) +{ + const struct silabs_letimer_pwm_config *config = dev->config; + int err; + + if (action == PM_DEVICE_ACTION_RESUME) { + err = clock_control_on(config->clock_dev, + (clock_control_subsys_t)&config->clock_cfg); + if (err < 0 && err != -EALREADY) { + return err; + } + + err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (err < 0 && err != -ENOENT) { + return err; + } + + sl_hal_letimer_enable(config->base); + } else if (IS_ENABLED(CONFIG_PM_DEVICE) && (action == PM_DEVICE_ACTION_SUSPEND)) { + sl_hal_letimer_disable(config->base); + + err = clock_control_off(config->clock_dev, + (clock_control_subsys_t)&config->clock_cfg); + if (err < 0) { + return err; + } + + err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP); + if (err < 0 && err != -ENOENT) { + return err; + } + } else { + return -ENOTSUP; + } + + return 0; +} + +static int silabs_letimer_pwm_init(const struct device *dev) +{ + sl_hal_letimer_config_t letimer_config = SL_HAL_LETIMER_CONFIG_DEFAULT; + const struct silabs_letimer_pwm_config *config = dev->config; + int err; + + err = clock_control_on(config->clock_dev, (clock_control_subsys_t)&config->clock_cfg); + if (err < 0 && err != -EALREADY) { + return err; + } + + letimer_config.prescaler = config->clock_div; + letimer_config.debug_run = config->run_in_debug; + letimer_config.enable_top = true; + sl_hal_letimer_init(config->base, &letimer_config); + + return pm_device_driver_init(dev, silabs_letimer_pwm_pm_action); +} + +static DEVICE_API(pwm, silabs_letimer_pwm_api) = { + .set_cycles = silabs_letimer_pwm_set_cycles, + .get_cycles_per_sec = silabs_letimer_pwm_get_cycles_per_sec, +}; + +#define LETIMER_PWM_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + PM_DEVICE_DT_INST_DEFINE(inst, silabs_letimer_pwm_pm_action); \ + \ + static const struct silabs_letimer_pwm_config letimer_pwm_config_##inst = { \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(inst))), \ + .clock_cfg = SILABS_DT_CLOCK_CFG(DT_INST_PARENT(inst)), \ + .base = (LETIMER_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(inst)), \ + .run_in_debug = DT_PROP(DT_INST_PARENT(inst), run_in_debug), \ + .clock_div = DT_ENUM_IDX(DT_INST_PARENT(inst), clock_div), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, &silabs_letimer_pwm_init, PM_DEVICE_DT_INST_GET(inst), NULL, \ + &letimer_pwm_config_##inst, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \ + &silabs_letimer_pwm_api); + +DT_INST_FOREACH_STATUS_OKAY(LETIMER_PWM_INIT) diff --git a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt index ebcecde52df..9f76a436c14 100644 --- a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt +++ b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt @@ -262,10 +262,11 @@ if(CONFIG_SOC_GECKO_GPIO) ) endif() +zephyr_library_sources_ifdef(CONFIG_SILABS_SISDK_LETIMER ${PERIPHERAL_DIR}/src/sl_hal_letimer.c) + zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LDMA ${EMDRV_DIR}/dmadrv/src/dmadrv.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_I2C ${EMLIB_DIR}/src/em_i2c.c) -zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LETIMER ${EMLIB_DIR}/src/em_letimer.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LEUART ${EMLIB_DIR}/src/em_leuart.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_MSC ${EMLIB_DIR}/src/em_msc.c) zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_LDMA ${EMLIB_DIR}/src/em_ldma.c) diff --git a/modules/hal_silabs/simplicity_sdk/Kconfig b/modules/hal_silabs/simplicity_sdk/Kconfig index 7d2c8fcfa4b..f89d48129cb 100644 --- a/modules/hal_silabs/simplicity_sdk/Kconfig +++ b/modules/hal_silabs/simplicity_sdk/Kconfig @@ -4,6 +4,9 @@ menu "SiSDK configuration" depends on HAS_SILABS_SISDK +config SILABS_SISDK_LETIMER + bool "Peripheral HAL for LETIMER" + config RAIL_PA_CURVE_HEADER string "RAIL PA custom curve header file" default "pa_curves_efr32.h" diff --git a/tests/drivers/pwm/pwm_api/boards/xg29_rb4412a.overlay b/tests/drivers/pwm/pwm_api/boards/xg29_rb4412a.overlay new file mode 100644 index 00000000000..26a7dd5c37c --- /dev/null +++ b/tests/drivers/pwm/pwm_api/boards/xg29_rb4412a.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + pwm-0 = &letimer0_pwm; + }; +}; + +&pinctrl { + letimer0_default: letimer0_default { + group1 { + pins = ; /* WPK EXP15 */ + drive-push-pull; + }; + }; +}; + +&letimer0 { + status = "okay"; + + letimer0_pwm: pwm { + pinctrl-0 = <&letimer0_default>; + pinctrl-names = "default"; + status = "okay"; + }; +}; diff --git a/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.conf b/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.conf new file mode 100644 index 00000000000..795414a504a --- /dev/null +++ b/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.conf @@ -0,0 +1 @@ +CONFIG_SKIP_EDGE_NUM=4 diff --git a/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.overlay b/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.overlay new file mode 100644 index 00000000000..aafbbc640d3 --- /dev/null +++ b/tests/drivers/pwm/pwm_gpio_loopback/boards/xg29_rb4412a.overlay @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + zephyr,user { + pwms = <&letimer0_pwm 0 PWM_MSEC(5) PWM_POLARITY_NORMAL>; + gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>; /* WPK EXP16 */ + }; +}; + +&pinctrl { + letimer0_default: letimer0_default { + group1 { + pins = ; /* WPK EXP15 */ + drive-push-pull; + }; + }; +}; + +&letimer0 { + status = "okay"; + + letimer0_pwm: pwm { + pinctrl-0 = <&letimer0_default>; + pinctrl-names = "default"; + status = "okay"; + }; +}; diff --git a/tests/drivers/pwm/pwm_gpio_loopback/testcase.yaml b/tests/drivers/pwm/pwm_gpio_loopback/testcase.yaml index 7173c8c2720..02d2198f68a 100644 --- a/tests/drivers/pwm/pwm_gpio_loopback/testcase.yaml +++ b/tests/drivers/pwm/pwm_gpio_loopback/testcase.yaml @@ -21,3 +21,7 @@ tests: - nrf54h20dk/nrf54h20/cpuapp - nrf54l15dk/nrf54l15/cpuapp - ophelia4ev/nrf54l15/cpuapp + + drivers.pwm.gpio_loopback.silabs: + platform_allow: + - xg29_rb4412a