From 8fc2d2d9b44b5bfe796a49c63aaf990ef7069e8d Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 26 Mar 2026 20:03:55 +0530 Subject: [PATCH 1/5] UPSTREAM-PEND: drivers: syscon: use uint32_t for register offsets Use uint32_t to allow larger offsets in place of the existing uint16_t configuration. PR: 106755 Signed-off-by: Amneesh Singh --- Backport notes: - Dropped syscon_shell.c changes since it is not present on 4.3.0 --- drivers/syscon/syscon.c | 4 ++-- drivers/syscon/syscon_bflb_efuse.c | 2 +- drivers/syscon/syscon_common.h | 2 +- include/zephyr/drivers/syscon.h | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/syscon/syscon.c b/drivers/syscon/syscon.c index 1d2ce4b12630..f655993ba478 100644 --- a/drivers/syscon/syscon.c +++ b/drivers/syscon/syscon.c @@ -33,7 +33,7 @@ static int syscon_generic_get_base(const struct device *dev, uintptr_t *addr) return 0; } -static int syscon_generic_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) +static int syscon_generic_read_reg(const struct device *dev, uint32_t reg, uint32_t *val) { const struct syscon_generic_config *config = dev->config; struct syscon_generic_data *data = dev->data; @@ -66,7 +66,7 @@ static int syscon_generic_read_reg(const struct device *dev, uint16_t reg, uint3 return 0; } -static int syscon_generic_write_reg(const struct device *dev, uint16_t reg, uint32_t val) +static int syscon_generic_write_reg(const struct device *dev, uint32_t reg, uint32_t val) { const struct syscon_generic_config *config = dev->config; struct syscon_generic_data *data = dev->data; diff --git a/drivers/syscon/syscon_bflb_efuse.c b/drivers/syscon/syscon_bflb_efuse.c index e8d368dbef51..2ff60952731c 100644 --- a/drivers/syscon/syscon_bflb_efuse.c +++ b/drivers/syscon/syscon_bflb_efuse.c @@ -175,7 +175,7 @@ static void efuse_bflb_cache(const struct device *dev) irq_unlock(key); } -static int efuse_bflb_read(const struct device *dev, uint16_t reg, uint32_t *val) +static int efuse_bflb_read(const struct device *dev, uint32_t reg, uint32_t *val) { struct efuse_bflb_data *data = dev->data; diff --git a/drivers/syscon/syscon_common.h b/drivers/syscon/syscon_common.h index 4cc23d21388c..28dfdf83f98a 100644 --- a/drivers/syscon/syscon_common.h +++ b/drivers/syscon/syscon_common.h @@ -22,7 +22,7 @@ extern "C" { * @retval 0 if the register read is valid. * @retval -EINVAL if the read is invalid. */ -static inline int syscon_sanitize_reg(uint16_t *reg, size_t reg_size, uint8_t reg_width) +static inline int syscon_sanitize_reg(uint32_t *reg, size_t reg_size, uint8_t reg_width) { /* Avoid unaligned readings */ *reg = ROUND_DOWN(*reg, reg_width); diff --git a/include/zephyr/drivers/syscon.h b/include/zephyr/drivers/syscon.h index 888db3763e2f..50e3cd554823 100644 --- a/include/zephyr/drivers/syscon.h +++ b/include/zephyr/drivers/syscon.h @@ -41,14 +41,14 @@ typedef int (*syscon_api_get_base)(const struct device *dev, uintptr_t *addr); * * @see syscon_read_reg */ -typedef int (*syscon_api_read_reg)(const struct device *dev, uint16_t reg, uint32_t *val); +typedef int (*syscon_api_read_reg)(const struct device *dev, uint32_t reg, uint32_t *val); /** * API template to write a single register. * * @see syscon_write_reg */ -typedef int (*syscon_api_write_reg)(const struct device *dev, uint16_t reg, uint32_t val); +typedef int (*syscon_api_write_reg)(const struct device *dev, uint32_t reg, uint32_t val); /** * API template to get the size of the syscon register. @@ -102,9 +102,9 @@ static inline int z_impl_syscon_get_base(const struct device *dev, uintptr_t *ad * @retval 0 on success. * @retval -ENOSYS If the API or function isn't implemented. */ -__syscall int syscon_read_reg(const struct device *dev, uint16_t reg, uint32_t *val); +__syscall int syscon_read_reg(const struct device *dev, uint32_t reg, uint32_t *val); -static inline int z_impl_syscon_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) +static inline int z_impl_syscon_read_reg(const struct device *dev, uint32_t reg, uint32_t *val) { const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api; @@ -128,9 +128,9 @@ static inline int z_impl_syscon_read_reg(const struct device *dev, uint16_t reg, * @retval 0 on success. * @retval -ENOSYS If the API or function isn't implemented. */ -__syscall int syscon_write_reg(const struct device *dev, uint16_t reg, uint32_t val); +__syscall int syscon_write_reg(const struct device *dev, uint32_t reg, uint32_t val); -static inline int z_impl_syscon_write_reg(const struct device *dev, uint16_t reg, uint32_t val) +static inline int z_impl_syscon_write_reg(const struct device *dev, uint32_t reg, uint32_t val) { const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api; From 1cb5c42d3386c093801f1ccc12df7c7719d2a372 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 26 Mar 2026 20:01:51 +0530 Subject: [PATCH 2/5] UPSTREAM-PEND: dts: ti,control-module: add epwm-tbclk space-specifier Add a specifier space for configuring timebase clock for TI's Enhanced PWM via syscon. PR: 88757 Signed-off-by: Amneesh Singh --- dts/bindings/syscon/ti,control-module.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dts/bindings/syscon/ti,control-module.yaml b/dts/bindings/syscon/ti,control-module.yaml index b2725f513862..c9c8af2b11de 100644 --- a/dts/bindings/syscon/ti,control-module.yaml +++ b/dts/bindings/syscon/ti,control-module.yaml @@ -18,3 +18,13 @@ properties: Example: ti,unlock-offsets = <0x1008 /* Partition 1 */ 0x5008>; /* Partition 2 */ + + "#epwm-tbclk-cells": + type: int + const: 2 + description: | + Number of items to expect in a time-base clock specifier for TI Enhanced PWM. + +epwm-tbclk-cells: + - offset + - bit From a23e4b4277a55adf00fb0235df2f8b70d044aba6 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 26 Mar 2026 20:45:49 +0530 Subject: [PATCH 3/5] UPSTREAM-PEND: drivers: pwm: introduce ti_am3352_ehrpwm This is the driver for EPWM/EHRPWM module of TI SoCs. It only uses Action Qualifier submodule currently. PR: 88757 Signed-off-by: Amneesh Singh --- Backport notes: - Fixed conflicts in Kconfig and CMakeLists.txt --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.ti_am3352_ehrpwm | 12 + drivers/pwm/pwm_ti_am3352_ehrpwm.c | 433 +++++++++++++++++++++++++ dts/bindings/pwm/ti,am3352-ehrpwm.yaml | 37 +++ 5 files changed, 485 insertions(+) create mode 100644 drivers/pwm/Kconfig.ti_am3352_ehrpwm create mode 100644 drivers/pwm/pwm_ti_am3352_ehrpwm.c create mode 100644 dts/bindings/pwm/ti,am3352-ehrpwm.yaml diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 0dcd3a8e86dd..2f03d2025cbd 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -70,3 +70,4 @@ zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c) zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c) zephyr_library_sources_ifdef(CONFIG_PWM_REALTEK_RTS5912 pwm_realtek_rts5912.c) zephyr_library_sources_ifdef(CONFIG_PWM_TI_AM3352_ECAP pwm_ti_am3352_ecap.c) +zephyr_library_sources_ifdef(CONFIG_PWM_TI_AM3352_EHRPWM pwm_ti_am3352_ehrpwm.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 49ea5958a3e7..03655e96afd5 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -144,4 +144,6 @@ source "drivers/pwm/Kconfig.ambiq_timer" source "drivers/pwm/Kconfig.ti_am3352_ecap" +source "drivers/pwm/Kconfig.ti_am3352_ehrpwm" + endif # PWM diff --git a/drivers/pwm/Kconfig.ti_am3352_ehrpwm b/drivers/pwm/Kconfig.ti_am3352_ehrpwm new file mode 100644 index 000000000000..6dbfe830b2a6 --- /dev/null +++ b/drivers/pwm/Kconfig.ti_am3352_ehrpwm @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +config PWM_TI_AM3352_EHRPWM + bool "TI EHRPWM based PWM controller" + default y + depends on DT_HAS_TI_AM3352_EHRPWM_ENABLED + depends on CLOCK_CONTROL + select PINCTRL + select SYSCON + help + Enable EHRPWM controller for TI SoCs diff --git a/drivers/pwm/pwm_ti_am3352_ehrpwm.c b/drivers/pwm/pwm_ti_am3352_ehrpwm.c new file mode 100644 index 000000000000..920134c840cc --- /dev/null +++ b/drivers/pwm/pwm_ti_am3352_ehrpwm.c @@ -0,0 +1,433 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 Texas Instruments + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#ifdef CONFIG_CLOCK_CONTROL_TISCI +#include +#endif + +LOG_MODULE_REGISTER(ti_ehrpwm); + +#define DT_DRV_COMPAT ti_am3352_ehrpwm + +#define TI_EHRPWM_PERIOD_CYCLES_MAX (0xFFFF) +#define TI_EHRPWM_NUM_CHANNELS (2) + +struct ti_ehrpwm_regs { + volatile uint16_t TBCTL; /**< Time-Base Control Register, offset: 0x00 */ + uint8_t RESERVED_1[0x8]; /**< Reserved, offset: 0x04 - 0x0A */ + volatile uint16_t TBPRD; /**< Time-Base Period Register, offest: 0x0A */ + uint8_t RESERVED_2[0x6]; /**< Reserved, offset: 0x0E - 0x12 */ + volatile uint16_t CMPA; /**< Counter-Compare A Register, offest: 0x12 */ + volatile uint16_t CMPB; /**< Counter-Compare B Register, offest: 0x14 */ + volatile uint16_t AQCTLA; /**< AQ Control Register for Output A, offset: 0x16 */ + volatile uint16_t AQCTLB; /**< AQ Control Register for Output B, offset: 0x18 */ + volatile uint16_t AQSFRC; /**< AQ Software Force Register, offset: 0x1A */ + volatile uint16_t AQCSFRC /**< AQ Software Continuous Force Register, offset: 0x1C */; +}; + +/* Time Based Control Register */ +#define TI_EHRPWM_TBCTL_CLKDIV GENMASK(12, 10) +#define TI_EHRPWM_TBCTL_CLKDIV_MAX (7) +#define TI_EHRPWM_TBCTL_HSPCLKDIV GENMASK(9, 7) +#define TI_EHRPWM_TBCTL_HSPCLKDIV_MAX (7) +#define TI_EHRPWM_TBCTL_PRDLD BIT(3) +#define TI_EHRPWM_TBCTL_CTRMODE GENMASK(1, 0) +#define TI_EHRPWM_TBCTL_CTRMODE_UP_ONLY (0) +#define TI_EHRPWM_TBCTL_CTRMODE_UP_DOWN (2) + +/* Action Qualifier Control Register */ +#define TI_EHRPWM_AQCTL_CBD GENMASK(11, 10) +#define TI_EHRPWM_AQCTL_CBU GENMASK(9, 8) +#define TI_EHRPWM_AQCTL_CAD GENMASK(7, 6) +#define TI_EHRPWM_AQCTL_CAU GENMASK(5, 4) +#define TI_EHRPWM_AQCTL_PRD GENMASK(3, 2) +#define TI_EHRPWM_AQCTL_ZRO GENMASK(1, 0) + +/* Action Qualifier Software Force Register */ +#define TI_EHRPWM_AQSFRC_RLDCSF GENMASK(7, 6) + +/* Action Qualifier Continuous Software Force Register */ +#define TI_EHRPWM_AQCSFRC_CSFB GENMASK(3, 2) +#define TI_EHRPWM_AQCSFRC_CSFA GENMASK(1, 0) +#define TI_EHRPWM_AQCSFRC_CSF_LOW (1) + +/* Action Qualifier Control Register */ +#define TI_EHRPWM_AQCTL_ZRO GENMASK(1, 0) +#define TI_EHRPWM_AQCTL_PRD GENMASK(3, 2) +#define TI_EHRPWM_AQCTL_CAU GENMASK(5, 4) +#define TI_EHRPWM_AQCTL_CBU GENMASK(9, 8) +#define TI_EHRPWM_AQCTL_FLD_CLR (1) +#define TI_EHRPWM_AQCTL_FLD_SET (2) + +#define DEV_CFG(dev) ((const struct ti_ehrpwm_cfg *)(dev)->config) +#define DEV_DATA(dev) ((struct ti_ehrpwm_data *)(dev)->data) +#define DEV_REGS(dev) ((struct ti_ehrpwm_regs *)DEVICE_MMIO_GET(dev)) + +struct ti_ehrpwm_cfg { + DEVICE_MMIO_ROM; + const struct device *clock_dev; + clock_control_subsys_t clock_subsys; + const struct device *tbclk_syscon; + uint32_t tbclk_offset; + uint8_t tbclk_bit; + clock_control_subsys_t tbclk_subsys; + const struct pinctrl_dev_config *pcfg; +}; + +struct ti_ehrpwm_data { + DEVICE_MMIO_RAM; + uint32_t period_cycles[TI_EHRPWM_NUM_CHANNELS]; + uint32_t prescale_div[TI_EHRPWM_NUM_CHANNELS]; + bool symmetric; + bool enabled; +}; + +static int ti_ehrpwm_configure_tbctl(const struct device *dev, uint32_t channel, + uint32_t period_cycles) +{ + struct ti_ehrpwm_regs *regs = DEV_REGS(dev); + struct ti_ehrpwm_data *data = DEV_DATA(dev); + uint16_t prescale_div; + uint16_t tbctl; + + /* already configured */ + if (data->period_cycles[channel] == period_cycles) { + return 0; + } + + tbctl = regs->TBCTL; + + /* configure shadow loading on period register (=0h) */ + tbctl &= ~TI_EHRPWM_TBCTL_PRDLD; + + /* configure counter mode */ + tbctl &= ~TI_EHRPWM_TBCTL_CTRMODE; + if (data->symmetric) { + tbctl |= FIELD_PREP(TI_EHRPWM_TBCTL_CTRMODE, TI_EHRPWM_TBCTL_CTRMODE_UP_DOWN); + } else { + tbctl |= FIELD_PREP(TI_EHRPWM_TBCTL_CTRMODE, TI_EHRPWM_TBCTL_CTRMODE_UP_ONLY); + } + + /* find the minimum prescaler that will allow configuring period_cycles */ + for (uint16_t clkdiv = 0; clkdiv <= TI_EHRPWM_TBCTL_CLKDIV_MAX; clkdiv++) { + for (uint16_t hspclkdiv = 0; hspclkdiv <= TI_EHRPWM_TBCTL_HSPCLKDIV_MAX; + hspclkdiv++) { + + prescale_div = (1 << clkdiv) * (hspclkdiv ? (hspclkdiv * 2) : 1); + + if (prescale_div > period_cycles / TI_EHRPWM_PERIOD_CYCLES_MAX) { + tbctl &= ~(TI_EHRPWM_TBCTL_CLKDIV | TI_EHRPWM_TBCTL_HSPCLKDIV); + tbctl |= FIELD_PREP(TI_EHRPWM_TBCTL_HSPCLKDIV, hspclkdiv) | + FIELD_PREP(TI_EHRPWM_TBCTL_CLKDIV, clkdiv); + + /* write tbctl */ + regs->TBCTL = tbctl; + + /* save period_cycles */ + data->period_cycles[channel] = period_cycles; + data->prescale_div[channel] = prescale_div; + + /* early return */ + return 0; + } + } + } + + /* period is too long for configuration */ + return -1; +} + +static int ti_ehrpwm_configure_aq(const struct device *dev, uint32_t channel, bool polarity) +{ + struct ti_ehrpwm_data *data = DEV_DATA(dev); + struct ti_ehrpwm_regs *regs = DEV_REGS(dev); + uint16_t aqctl_upmask; + uint16_t aqctl_downmask; + uint16_t aqctl; + + if (channel == 0) { + aqctl = regs->AQCTLA; + aqctl_upmask = TI_EHRPWM_AQCTL_CAU; + aqctl_downmask = TI_EHRPWM_AQCTL_CAD; + } else { + aqctl = regs->AQCTLB; + aqctl_upmask = TI_EHRPWM_AQCTL_CBU; + aqctl_downmask = TI_EHRPWM_AQCTL_CBD; + } + + aqctl &= ~(TI_EHRPWM_AQCTL_ZRO | TI_EHRPWM_AQCTL_PRD | aqctl_upmask | aqctl_downmask); + if (polarity == PWM_POLARITY_NORMAL) { + /* active-high */ + aqctl |= FIELD_PREP(aqctl_upmask, TI_EHRPWM_AQCTL_FLD_CLR); + + aqctl &= ~TI_EHRPWM_AQCTL_PRD; + + if (data->symmetric) { + aqctl &= ~TI_EHRPWM_AQCTL_ZRO; + aqctl |= FIELD_PREP(aqctl_downmask, TI_EHRPWM_AQCTL_FLD_SET); + } else { + aqctl |= FIELD_PREP(TI_EHRPWM_AQCTL_ZRO, TI_EHRPWM_AQCTL_FLD_SET); + aqctl &= ~aqctl_downmask; + } + } else { + /* active-low */ + aqctl |= FIELD_PREP(aqctl_upmask, TI_EHRPWM_AQCTL_FLD_SET); + + aqctl &= ~TI_EHRPWM_AQCTL_ZRO; + + if (data->symmetric) { + aqctl &= ~TI_EHRPWM_AQCTL_PRD; + aqctl |= FIELD_PREP(aqctl_downmask, TI_EHRPWM_AQCTL_FLD_CLR); + } else { + aqctl |= FIELD_PREP(TI_EHRPWM_AQCTL_PRD, TI_EHRPWM_AQCTL_FLD_CLR); + aqctl &= ~aqctl_downmask; + } + } + + if (channel == 0) { + regs->AQCTLA = aqctl; + } else { + regs->AQCTLB = aqctl; + } + + return 0; +} + +static int ti_ehrpwm_enable(const struct device *dev, uint32_t channel) +{ + const struct ti_ehrpwm_cfg *cfg = DEV_CFG(dev); + struct ti_ehrpwm_data *data = DEV_DATA(dev); + struct ti_ehrpwm_regs *regs = DEV_REGS(dev); + uint32_t tbclk_reg; + uint16_t aqcsfrc; + int err; + + /* already enabled */ + if (data->enabled == true) { + return 0; + } + + /* disable forced action qualifier */ + aqcsfrc = regs->AQCSFRC; + if (channel == 0) { + aqcsfrc &= ~TI_EHRPWM_AQCSFRC_CSFA; + } else { + aqcsfrc &= ~TI_EHRPWM_AQCSFRC_CSFB; + } + + /* configure shadow register */ + regs->AQSFRC &= ~TI_EHRPWM_AQSFRC_RLDCSF; + regs->AQCSFRC = aqcsfrc; + + /* enable TBCLK */ + err = syscon_read_reg(cfg->tbclk_syscon, cfg->tbclk_offset, &tbclk_reg); + if (err != 0) { + LOG_ERR("failed to read timebase clock register"); + return err; + } + tbclk_reg |= BIT(cfg->tbclk_bit); + err = syscon_write_reg(cfg->tbclk_syscon, cfg->tbclk_offset, tbclk_reg); + if (err != 0) { + LOG_ERR("failed to write timebase clock register"); + return err; + } + data->enabled = true; + + return 0; +} + +static int ti_ehrpwm_disable(const struct device *dev, uint32_t channel) +{ + const struct ti_ehrpwm_cfg *cfg = DEV_CFG(dev); + struct ti_ehrpwm_data *data = DEV_DATA(dev); + struct ti_ehrpwm_regs *regs = DEV_REGS(dev); + uint16_t aqcsfrc; + uint32_t tbclk_reg; + int err; + + /* already disabled */ + if (data->enabled == false) { + return 0; + } + + /* force continuous low on aq submodule */ + aqcsfrc = regs->AQCSFRC; + if (channel == 0) { + aqcsfrc &= ~TI_EHRPWM_AQCSFRC_CSFA; + aqcsfrc |= FIELD_PREP(TI_EHRPWM_AQCSFRC_CSFA, TI_EHRPWM_AQCSFRC_CSF_LOW); + } else { + aqcsfrc &= ~TI_EHRPWM_AQCSFRC_CSFB; + aqcsfrc |= FIELD_PREP(TI_EHRPWM_AQCSFRC_CSFB, TI_EHRPWM_AQCSFRC_CSF_LOW); + } + + /* configure shadow register */ + regs->AQSFRC &= ~TI_EHRPWM_AQSFRC_RLDCSF; + regs->AQCSFRC = aqcsfrc; + + /* configure active register */ + regs->AQSFRC |= TI_EHRPWM_AQSFRC_RLDCSF; + regs->AQCSFRC = aqcsfrc; + + /* disable TBCLK */ + err = syscon_read_reg(cfg->tbclk_syscon, cfg->tbclk_offset, &tbclk_reg); + if (err != 0) { + LOG_ERR("failed to read timebase clock register"); + return err; + } + tbclk_reg &= ~BIT(cfg->tbclk_bit); + err = syscon_write_reg(cfg->tbclk_syscon, cfg->tbclk_offset, tbclk_reg); + if (err != 0) { + LOG_ERR("failed to write timebase clock register"); + return err; + } + + data->period_cycles[channel] = 0; + data->enabled = false; + + return 0; +} + +static int ti_ehrpwm_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_cycles, + uint32_t pulse_cycles, pwm_flags_t flags) +{ + struct ti_ehrpwm_regs *regs = DEV_REGS(dev); + struct ti_ehrpwm_data *data = DEV_DATA(dev); + int err = 0; + + if (channel >= TI_EHRPWM_NUM_CHANNELS) { + LOG_ERR("invalid channel number %u", channel); + return -EINVAL; + } + + /* there is a common period register, so the period should be same for all channels */ + for (uint32_t i = 0; i < TI_EHRPWM_NUM_CHANNELS; i++) { + if (i != channel && data->period_cycles[i] != 0 && + data->period_cycles[i] != period_cycles) { + LOG_ERR("period value must be same as other channels"); + return -EINVAL; + } + } + + /* force constant low */ + if (period_cycles == 0) { + err = ti_ehrpwm_disable(dev, channel); + if (err != 0) { + LOG_ERR("failed to disable ehrpwm module"); + return err; + } + + /* early return after disable */ + return 0; + } + + err = ti_ehrpwm_enable(dev, channel); + if (err != 0) { + return err; + } + + /* configure action qualifier */ + err = ti_ehrpwm_configure_aq(dev, channel, flags & PWM_POLARITY_MASK); + if (err != 0) { + LOG_ERR("failed to configure action qualifier"); + return err; + } + + /* configure tbctl and prescaler */ + err = ti_ehrpwm_configure_tbctl(dev, channel, period_cycles); + if (err != 0) { + LOG_ERR("failed to configure clock prescaler values"); + return err; + } + + /* update cycles */ + period_cycles /= data->prescale_div[channel]; + pulse_cycles /= data->prescale_div[channel]; + + if (data->symmetric) { + period_cycles /= 2; + pulse_cycles /= 2; + } + + /* write period cycles */ + regs->TBPRD = period_cycles; + + /* write duty cycles */ + if (channel == 0) { + regs->CMPA = pulse_cycles; + } else { + regs->CMPB = pulse_cycles; + } + + return 0; +} + +static int ti_ehrpwm_get_cycles_per_sec(const struct device *dev, uint32_t channel, + uint64_t *cycles) +{ + ARG_UNUSED(channel); + const struct ti_ehrpwm_cfg *cfg = DEV_CFG(dev); + + return clock_control_get_rate(cfg->clock_dev, cfg->clock_subsys, (uint32_t *)cycles); +} + +static int ti_ehrpwm_init(const struct device *dev) +{ + const struct ti_ehrpwm_cfg *cfg = DEV_CFG(dev); + int ret; + + DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); + + ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("Fail to configure pinctrl\n"); + return ret; + } + + return 0; +} + +static DEVICE_API(pwm, ti_ehrpwm_api) = { + .set_cycles = ti_ehrpwm_set_cycles, + .get_cycles_per_sec = ti_ehrpwm_get_cycles_per_sec, +}; + +#define TI_EHRPWM_DEFINE_CLK_SUBSYS(n) \ + COND_CODE_1(CONFIG_CLOCK_CONTROL_TISCI, ( \ + static struct tisci_clock_config tisci_fclk_##n = \ + TISCI_GET_CLOCK_DETAILS_BY_INST(n); \ + static const clock_control_subsys_t ti_ehrpwm_clk_subsys_##n = \ + &tisci_fclk_##n; \ + ), (COND_CODE_1(CONFIG_CLOCK_CONTROL_ARM_SCMI, \ + (static const clock_control_subsys_t ti_ehrpwm_clk_subsys_##n = \ + (clock_control_subsys_t)DT_INST_PHA(n, clocks, name); \ + ), (BUILD_ASSERT(0, "Unsupported clock controller");)))) + +#define TI_EHRPWM_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + TI_EHRPWM_DEFINE_CLK_SUBSYS(n); \ + static struct ti_ehrpwm_cfg ti_ehrpwm_config_##n = { \ + DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = ti_ehrpwm_clk_subsys_##n, \ + .tbclk_syscon = DEVICE_DT_GET(DT_INST_PHANDLE(n, tbclk)), \ + .tbclk_offset = DT_INST_PHA(n, tbclk, offset), \ + .tbclk_bit = DT_INST_PHA(n, tbclk, bit), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + }; \ + \ + static struct ti_ehrpwm_data ti_ehrpwm_data_##n = { \ + .symmetric = DT_INST_PROP(n, symmetric), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, ti_ehrpwm_init, NULL, &ti_ehrpwm_data_##n, &ti_ehrpwm_config_##n, \ + POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &ti_ehrpwm_api); + +DT_INST_FOREACH_STATUS_OKAY(TI_EHRPWM_INIT) diff --git a/dts/bindings/pwm/ti,am3352-ehrpwm.yaml b/dts/bindings/pwm/ti,am3352-ehrpwm.yaml new file mode 100644 index 000000000000..75c8e5c962e4 --- /dev/null +++ b/dts/bindings/pwm/ti,am3352-ehrpwm.yaml @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +description: TI SOC EHRPWM based PWM controller + +include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml] + +compatible: "ti,am3352-ehrpwm" + +properties: + reg: + required: true + + clocks: + required: true + + symmetric: + type: boolean + description: Generate a symmetric PWM by using up-down count mode + + tbclk: + type: phandle-array + specifier-space: epwm-tbclk + description: | + Offset and bit configuration for selecting the time-base clock + Format: <&syscon_phandle offset bit> + - syscon_phandle: reference to syscon device + - offset: register offset within syscon for clock selection + - value: bit within the register that controls the timebase clock + + "#pwm-cells": + const: 3 + +pwm-cells: + - channel + - period + - flags From f1ae5f1fefeb7709573f516d4e1f9885188d0602 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 26 Mar 2026 20:14:39 +0530 Subject: [PATCH 4/5] UPSTREAM-PEND: am243x_evm: am2434: r5f0_0: add support for TI EPWM Add DT nodes for TI's Enhanced PWM. This requires addition of a syscon node as well to configure the timebase clock. PR: 88757 Signed-off-by: Amneesh Singh --- Backport notes: - Resolved device tree conflicts --- .../am243x_evm_am2434_r5f0_0-pinctrl.dtsi | 8 ++ .../am243x_evm/am243x_evm_am2434_r5f0_0.dts | 6 ++ dts/arm/ti/am64x_r5.dtsi | 45 ++++++++ dts/vendor/ti/am64x_main.dtsi | 100 ++++++++++++++++++ 4 files changed, 159 insertions(+) diff --git a/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0-pinctrl.dtsi b/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0-pinctrl.dtsi index 578e04be3b1e..69b7d9d81b5d 100644 --- a/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0-pinctrl.dtsi +++ b/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0-pinctrl.dtsi @@ -160,6 +160,14 @@ main_ecap0_in: ecap0_in_default { pinmux = ; /* (D18) ECAP0_IN_APWM_OUT */ }; + + main_epwm0_a: epwm0_a_default { + pinmux = ; /* (U20) EHRPWM0_A */ + }; + + main_epwm0_b: epwm0_b_default { + pinmux = ; /* (U18) EHRPWM0_A */ + }; }; &mcu_pinctrl { diff --git a/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0.dts b/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0.dts index 848053a5f23c..7b4292ab088e 100644 --- a/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0.dts +++ b/boards/ti/am243x_evm/am243x_evm_am2434_r5f0_0.dts @@ -291,3 +291,9 @@ pinctrl-names = "default"; status = "okay"; }; + +&main_epwm0 { + pinctrl-0 = <&main_epwm0_a &main_epwm0_b>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/dts/arm/ti/am64x_r5.dtsi b/dts/arm/ti/am64x_r5.dtsi index 374fc2e94d6b..9902f463569e 100644 --- a/dts/arm/ti/am64x_r5.dtsi +++ b/dts/arm/ti/am64x_r5.dtsi @@ -262,3 +262,48 @@ interrupts = <0 142 IRQ_TYPE_EDGE IRQ_DEFAULT_PRIORITY>; interrupt-parent = <&vim>; }; + +&main_epwm0 { + interrupts = <0 108 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm1 { + interrupts = <0 110 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm2 { + interrupts = <0 112 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm3 { + interrupts = <0 114 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm4 { + interrupts = <0 116 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm5 { + interrupts = <0 118 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm6 { + interrupts = <0 146 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm7 { + interrupts = <0 148 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; + +&main_epwm8 { + interrupts = <0 150 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; +}; diff --git a/dts/vendor/ti/am64x_main.dtsi b/dts/vendor/ti/am64x_main.dtsi index 75246da90d84..5e9b5b297502 100644 --- a/dts/vendor/ti/am64x_main.dtsi +++ b/dts/vendor/ti/am64x_main.dtsi @@ -47,6 +47,16 @@ }; }; + main_mmr_cfg0: syscon@43000000 { + compatible = "ti,control-module", "syscon"; + reg = <0x43000000 DT_SIZE_K(128)>; + ranges = <0x00 0x43000000 DT_SIZE_K(128)>; + ti,unlock-offsets = <0x5008>; + #address-cells = <1>; + #size-cells = <1>; + #epwm-tbclk-cells = <2>; + }; + main_timer0: timer@2400000 { compatible = "ti,am654-timer"; reg = <0x2400000 DT_SIZE_K(1)>; @@ -483,4 +493,94 @@ clocks = <&k3_clks 53 0>; status = "disabled"; }; + + main_epwm0: pwm@23000000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23000000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 0>; + #pwm-cells = <3>; + power-domains = <&epwm0_pd>; + clocks = <&k3_clks 86 0>; + status = "disabled"; + }; + + main_epwm1: pwm@23010000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23010000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 1>; + #pwm-cells = <3>; + power-domains = <&epwm1_pd>; + clocks = <&k3_clks 87 0>; + status = "disabled"; + }; + + main_epwm2: pwm@23020000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23020000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 2>; + #pwm-cells = <3>; + power-domains = <&epwm2_pd>; + clocks = <&k3_clks 88 0>; + status = "disabled"; + }; + + main_epwm3: pwm@23030000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23030000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 3>; + #pwm-cells = <3>; + power-domains = <&epwm3_pd>; + clocks = <&k3_clks 89 0>; + status = "disabled"; + }; + + main_epwm4: pwm@23040000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23040000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 4>; + #pwm-cells = <3>; + power-domains = <&epwm4_pd>; + clocks = <&k3_clks 90 0>; + status = "disabled"; + }; + + main_epwm5: pwm@23050000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23050000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 5>; + #pwm-cells = <3>; + power-domains = <&epwm5_pd>; + clocks = <&k3_clks 91 0>; + status = "disabled"; + }; + + main_epwm6: pwm@23060000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23060000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 6>; + #pwm-cells = <3>; + power-domains = <&epwm6_pd>; + clocks = <&k3_clks 92 0>; + status = "disabled"; + }; + + main_epwm7: pwm@23070000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23070000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 7>; + #pwm-cells = <3>; + power-domains = <&epwm7_pd>; + clocks = <&k3_clks 93 0>; + status = "disabled"; + }; + + main_epwm8: pwm@23080000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23080000 0x100>; + tbclk = <&main_mmr_cfg0 0x4130 8>; + #pwm-cells = <3>; + power-domains = <&epwm8_pd>; + clocks = <&k3_clks 94 0>; + status = "disabled"; + }; }; From cd4bb4899d89176efdde044cd073e7c8e0748501 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 26 Mar 2026 20:17:41 +0530 Subject: [PATCH 5/5] UPSTREAM-PEND: am62l_evm: am62l3: a53: add support for TI EPWM Add DT nodes for TI's Enhanced PWM. This requires addition of a syscon node as well to configure the timebase clock. PR: 88757 Signed-off-by: Amneesh Singh --- Backport notes: - Resolved device tree conflicts --- .../am62l_evm_am62l3_a53-pinctrl.dtsi | 8 ++++ boards/ti/am62l_evm/am62l_evm_am62l3_a53.dts | 6 +++ dts/arm64/ti/am62l3_a53.dtsi | 15 +++++++ dts/vendor/ti/am62l-main.dtsi | 39 +++++++++++++++++++ dts/vendor/ti/am62l_power_domains.dtsi | 18 +++++++++ 5 files changed, 86 insertions(+) diff --git a/boards/ti/am62l_evm/am62l_evm_am62l3_a53-pinctrl.dtsi b/boards/ti/am62l_evm/am62l_evm_am62l3_a53-pinctrl.dtsi index d273ea0793f3..be2e7b062750 100644 --- a/boards/ti/am62l_evm/am62l_evm_am62l3_a53-pinctrl.dtsi +++ b/boards/ti/am62l_evm/am62l_evm_am62l3_a53-pinctrl.dtsi @@ -165,6 +165,14 @@ pinmux = ; /* (L22) ECAP0_IN_APWM_OUT */ }; + main_epwm0_a: epwm0_a_default { + pinmux = ; /* (G22) EHRPWM0_A */ + }; + + main_epwm0_b: epwm0_b_default { + pinmux = ; /* (F22) EHRPWM0_B */ + }; + main_gpio0_3_led: main_gpio0_led_default { pinmux = ; /* (D6) MMC1_SDWP */ }; diff --git a/boards/ti/am62l_evm/am62l_evm_am62l3_a53.dts b/boards/ti/am62l_evm/am62l_evm_am62l3_a53.dts index 1d2a5ab97c46..ebe33e28194d 100644 --- a/boards/ti/am62l_evm/am62l_evm_am62l3_a53.dts +++ b/boards/ti/am62l_evm/am62l_evm_am62l3_a53.dts @@ -160,6 +160,12 @@ status = "okay"; }; +&main_epwm0 { + pinctrl-0 = <&main_epwm0_a &main_epwm0_b>; + pinctrl-names = "default"; + status = "okay"; +}; + &main_rtc0 { status = "okay"; }; diff --git a/dts/arm64/ti/am62l3_a53.dtsi b/dts/arm64/ti/am62l3_a53.dtsi index b70f934cf6b5..60092c6b683a 100644 --- a/dts/arm64/ti/am62l3_a53.dtsi +++ b/dts/arm64/ti/am62l3_a53.dtsi @@ -202,6 +202,21 @@ interrupt-parent = <&gic>; }; +&main_epwm0 { + interrupts = ; + interrupt-parent = <&gic>; +}; + +&main_epwm1 { + interrupts = ; + interrupt-parent = <&gic>; +}; + +&main_epwm2 { + interrupts = ; + interrupt-parent = <&gic>; +}; + &main_timer0 { interrupts = ; interrupt-parent = <&gic>; diff --git a/dts/vendor/ti/am62l-main.dtsi b/dts/vendor/ti/am62l-main.dtsi index 0f9269d636bb..3c49750bd0b9 100644 --- a/dts/vendor/ti/am62l-main.dtsi +++ b/dts/vendor/ti/am62l-main.dtsi @@ -9,6 +9,15 @@ #include "am62l_power_domains.dtsi" / { + main_mmr_cfg3: syscon@9180000 { + compatible = "ti,control-module", "syscon"; + reg = <0x9180000 DT_SIZE_K(512)>; + ranges = <0x00 0x9180000 DT_SIZE_K(512)>; + #address-cells = <1>; + #size-cells = <1>; + #epwm-tbclk-cells = <2>; + }; + main_uart0: serial@2800000 { compatible = "ns16550"; reg = <0x02800000 0x100>; @@ -416,6 +425,36 @@ status = "disabled"; }; + main_epwm0: pwm@23000000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23000000 0x100>; + tbclk = <&main_mmr_cfg3 0x69100 0>; + #pwm-cells = <3>; + power-domains = <&main_epwm0_pd>; + clocks = <&scmi_clk 164>; + status = "disabled"; + }; + + main_epwm1: pwm@23010000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23010000 0x100>; + tbclk = <&main_mmr_cfg3 0x69100 1>; + #pwm-cells = <3>; + power-domains = <&main_epwm1_pd>; + clocks = <&scmi_clk 165>; + status = "disabled"; + }; + + main_epwm2: pwm@23020000 { + compatible = "ti,am3352-ehrpwm"; + reg = <0x23020000 0x100>; + tbclk = <&main_mmr_cfg3 0x69100 2>; + #pwm-cells = <3>; + power-domains = <&main_epwm2_pd>; + clocks = <&scmi_clk 166>; + status = "disabled"; + }; + main_rtc0: rtc@2b1f0000 { compatible = "ti,k3-rtc-counter"; reg = <0x2b1f0000 0x64>; diff --git a/dts/vendor/ti/am62l_power_domains.dtsi b/dts/vendor/ti/am62l_power_domains.dtsi index ba8e8860518f..e77292ebdce9 100644 --- a/dts/vendor/ti/am62l_power_domains.dtsi +++ b/dts/vendor/ti/am62l_power_domains.dtsi @@ -68,6 +68,24 @@ #power-domain-cells = <0>; }; + main_epwm0_pd: power-domain@28 { + compatible = "arm,scmi-power-domain"; + reg = <40>; + #power-domain-cells = <0>; + }; + + main_epwm1_pd: power-domain@29 { + compatible = "arm,scmi-power-domain"; + reg = <41>; + #power-domain-cells = <0>; + }; + + main_epwm2_pd: power-domain@2a { + compatible = "arm,scmi-power-domain"; + reg = <42>; + #power-domain-cells = <0>; + }; + main_spi3_pd: power-domain@4b { compatible = "arm,scmi-power-domain"; reg = <75>;