Commit 566fc7a7 authored by Aksel Skauge Mellbye's avatar Aksel Skauge Mellbye Committed by Benjamin Cabé
Browse files

drivers: pwm: silabs: Add LETIMER PWM driver for Series 2



Add PWM driver for the Low Energy Timer peripheral on Series 2.
The LETIMER runs at up to 32 kHz and has a 24-bit counter. It
only supports PWM output, it does not support input capture.

Signed-off-by: default avatarAksel Skauge Mellbye <aksel.mellbye@silabs.com>
parent 2a958aa5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -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"
+14 −0
Original line number Diff line number Diff line
# 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.
+174 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Silicon Laboratories Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT silabs_letimer_pwm

#include <errno.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_silabs.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>

#include <sl_hal_letimer.h>

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)
+2 −1
Original line number Diff line number Diff line
@@ -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)
Loading