From a62fe1d73fba22574bb09bd8933cc6844bda9abd Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 17 Apr 2025 15:35:56 +0530 Subject: [PATCH 1/2] drivers: clock_control: introduce syscon_ti This is a syscon backed gate-clock driver which uses its parent syscon node to write to MMRs corresponding to the clk-id to enable or disable a clock. Signed-off-by: Amneesh Singh --- drivers/clock_control/CMakeLists.txt | 1 + drivers/clock_control/Kconfig | 2 + drivers/clock_control/Kconfig.ti_syscon | 10 ++ .../clock_control/clock_control_ti_syscon.c | 132 ++++++++++++++++++ dts/bindings/clock/ti,am62-epwm-tbclk.yaml | 8 ++ dts/bindings/clock/ti,am64-epwm-tbclk.yaml | 8 ++ dts/bindings/clock/ti,am654-ehrpwm-tbclk.yaml | 18 +++ 7 files changed, 179 insertions(+) create mode 100644 drivers/clock_control/Kconfig.ti_syscon create mode 100644 drivers/clock_control/clock_control_ti_syscon.c create mode 100644 dts/bindings/clock/ti,am62-epwm-tbclk.yaml create mode 100644 dts/bindings/clock/ti,am64-epwm-tbclk.yaml create mode 100644 dts/bindings/clock/ti,am654-ehrpwm-tbclk.yaml diff --git a/drivers/clock_control/CMakeLists.txt b/drivers/clock_control/CMakeLists.txt index 2e8cf83005347..ce1a3da59ad73 100644 --- a/drivers/clock_control/CMakeLists.txt +++ b/drivers/clock_control/CMakeLists.txt @@ -109,3 +109,4 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AST10X0 clock_control_ast10x0. zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MAX32 clock_control_max32.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_AUXPLL clock_control_nrf_auxpll.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_WCH_RCC clock_control_wch_rcc.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_TI_SYSCON clock_control_ti_syscon.c) diff --git a/drivers/clock_control/Kconfig b/drivers/clock_control/Kconfig index 229a45792e8d9..710c81cf4fea9 100644 --- a/drivers/clock_control/Kconfig +++ b/drivers/clock_control/Kconfig @@ -110,4 +110,6 @@ source "drivers/clock_control/Kconfig.wch_rcc" source "drivers/clock_control/Kconfig.it51xxx" +source "drivers/clock_control/Kconfig.ti_syscon" + endif # CLOCK_CONTROL diff --git a/drivers/clock_control/Kconfig.ti_syscon b/drivers/clock_control/Kconfig.ti_syscon new file mode 100644 index 0000000000000..6baf5ab4d116a --- /dev/null +++ b/drivers/clock_control/Kconfig.ti_syscon @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +config CLOCK_CONTROL_TI_SYSCON + bool "TI Syscon backed gate-clock driver" + default y + depends on DT_HAS_TI_AM654_EHRPWM_TBCLK_ENABLED || DT_HAS_TI_AM64_EPWM_TBCLK_ENABLED || DT_HAS_TI_AM62_EPWM_TBCLK_ENABLED + select SYSCON + help + Enable driver for TI Syscon backed gate-clock controller. diff --git a/drivers/clock_control/clock_control_ti_syscon.c b/drivers/clock_control/clock_control_ti_syscon.c new file mode 100644 index 0000000000000..a144101829dd3 --- /dev/null +++ b/drivers/clock_control/clock_control_ti_syscon.c @@ -0,0 +1,132 @@ +/* + * Copyright 2025 Texas Instruments + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_AM654_COMPAT ti_am654_ehrpwm_tbclk +#define DT_AM64_COMPAT ti_am64_epwm_tbclk +#define DT_AM62_COMPAT ti_am62_epwm_tbclk + +#include +#include +#include + +LOG_MODULE_REGISTER(ti_syscon_gate_clk, CONFIG_CLOCK_CONTROL_LOG_LEVEL); + +#define DEV_CFG(dev) ((struct ti_syscon_gate_clk_cfg *)(dev)->config) + +struct ti_syscon_gate_clk_id_data { + uint32_t offset; + uint32_t bit; +}; + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_AM64_COMPAT) +static const struct ti_syscon_gate_clk_id_data am64_clk_ids[] = { + {0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}, {0, 8}, +}; +#endif + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_AM654_COMPAT) +static const struct ti_syscon_gate_clk_id_data am654_clk_ids[] = { + {0x0, 0}, {0x4, 0}, {0x8, 0}, {0xc, 0}, {0x10, 0}, {0x14, 0}, +}; +#endif + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_AM62_COMPAT) +static const struct ti_syscon_gate_clk_id_data am62_clk_ids[] = { + {0, 0}, + {0, 1}, + {0, 2}, +}; +#endif + +struct ti_syscon_gate_clk_cfg { + mm_reg_t reg; + const struct device *syscon; + const struct ti_syscon_gate_clk_id_data *clk_ids; + const uint32_t num_clk_ids; +}; + +static int ti_syscon_gate_clk_enable(const struct device *dev, clock_control_subsys_t sub_system, + bool enable) +{ + const struct ti_syscon_gate_clk_cfg *cfg = DEV_CFG(dev); + uint32_t clk_id = (sub_system ? (uint32_t)sub_system : 0); + uint32_t reg; + uint32_t bit; + uint32_t val; + uint32_t rb; + int err; + + if (clk_id >= cfg->num_clk_ids) { + LOG_ERR("invalid clk id"); + return -EINVAL; + } + + reg = cfg->reg + cfg->clk_ids[clk_id].offset; + bit = cfg->clk_ids[clk_id].bit; + + err = syscon_read_reg(cfg->syscon, reg, &val); + if (err < 0) { + LOG_ERR("failed to read syscon register"); + return err; + } + + if (enable) { + val |= BIT(bit); + } else { + val &= ~BIT(bit); + } + + err = syscon_write_reg(cfg->syscon, reg, val); + if (err < 0) { + LOG_ERR("failed to write syscon register"); + return err; + } + + err = syscon_read_reg(cfg->syscon, reg, &rb); + if (err < 0) { + LOG_ERR("failed to read syscon register"); + return err; + } + + if (rb != val) { + LOG_ERR("readback does not match written value"); + return -EIO; + } + + return 0; +} + +static int ti_syscon_gate_clk_on(const struct device *dev, clock_control_subsys_t sub_system) +{ + return ti_syscon_gate_clk_enable(dev, sub_system, true); +} + +static int ti_syscon_gate_clk_off(const struct device *dev, clock_control_subsys_t sub_system) +{ + return ti_syscon_gate_clk_enable(dev, sub_system, false); +} + +static DEVICE_API(clock_control, ti_syscon_gate_clk_driver_api) = { + .on = ti_syscon_gate_clk_on, + .off = ti_syscon_gate_clk_off, +}; + +#define TI_SYSCON_GATE_CLK_INIT(node, clks) \ + static const struct ti_syscon_gate_clk_cfg ti_syscon_gate_clk_config_##node = { \ + .reg = DT_REG_ADDR(node), \ + .syscon = DEVICE_DT_GET(DT_PARENT(node)), \ + .clk_ids = clks, \ + .num_clk_ids = ARRAY_SIZE(clks), \ + }; \ + \ + DEVICE_DT_DEFINE(node, NULL, NULL, NULL, &ti_syscon_gate_clk_config_##node, POST_KERNEL, \ + CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &ti_syscon_gate_clk_driver_api); + +/* add more compats as required */ + +DT_FOREACH_STATUS_OKAY_VARGS(DT_AM654_COMPAT, TI_SYSCON_GATE_CLK_INIT, am654_clk_ids) +DT_FOREACH_STATUS_OKAY_VARGS(DT_AM64_COMPAT, TI_SYSCON_GATE_CLK_INIT, am64_clk_ids) +DT_FOREACH_STATUS_OKAY_VARGS(DT_AM62_COMPAT, TI_SYSCON_GATE_CLK_INIT, am62_clk_ids) diff --git a/dts/bindings/clock/ti,am62-epwm-tbclk.yaml b/dts/bindings/clock/ti,am62-epwm-tbclk.yaml new file mode 100644 index 0000000000000..0b09e2c5f9148 --- /dev/null +++ b/dts/bindings/clock/ti,am62-epwm-tbclk.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Texas Instrumetns +# SPDX-License-Identifier: Apache-2.0 + +description: Syscon backed gate-clock driver + +compatible: "ti,am62-epwm-tbclk" + +include: ["ti,am654-ehrpwm-tbclk.yaml"] diff --git a/dts/bindings/clock/ti,am64-epwm-tbclk.yaml b/dts/bindings/clock/ti,am64-epwm-tbclk.yaml new file mode 100644 index 0000000000000..d1abda398faa3 --- /dev/null +++ b/dts/bindings/clock/ti,am64-epwm-tbclk.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Texas Instrumetns +# SPDX-License-Identifier: Apache-2.0 + +description: Syscon backed gate-clock driver + +compatible: "ti,am64-epwm-tbclk" + +include: ["ti,am654-ehrpwm-tbclk.yaml"] diff --git a/dts/bindings/clock/ti,am654-ehrpwm-tbclk.yaml b/dts/bindings/clock/ti,am654-ehrpwm-tbclk.yaml new file mode 100644 index 0000000000000..3880b8100cd3b --- /dev/null +++ b/dts/bindings/clock/ti,am654-ehrpwm-tbclk.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2025 Texas Instrumetns +# SPDX-License-Identifier: Apache-2.0 + +description: Syscon backed gate-clock driver + +compatible: "ti,am654-ehrpwm-tbclk" + +include: [base.yaml, clock-controller.yaml] + +properties: + reg: + required: true + + "#clock-cells": + const: 1 + +clock-cells: + - clk-id From b2826d8310f65ba2031c8f7857de2783408b463c Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Thu, 17 Apr 2025 15:39:52 +0530 Subject: [PATCH 2/2] drivers: pwm: introduce ti_am3352_ehrpwm This is the driver for EPWM/EHRPWM module of TI SoCs. It only uses Action Qualifier submodule currently. Signed-off-by: Amneesh Singh --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.ti_am3352_ehrpwm | 9 + drivers/pwm/pwm_ti_am3352_ehrpwm.c | 424 +++++++++++++++++++++++++ dts/bindings/pwm/ti,am3352-ehrpwm.yaml | 33 ++ 5 files changed, 469 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 4c1b22e9c6301..f5eda4d803b9f 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -54,3 +54,4 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.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) +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 57a684ca2d160..7220013f06be5 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -122,4 +122,6 @@ source "drivers/pwm/Kconfig.fake" source "drivers/pwm/Kconfig.renesas_rz" +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 0000000000000..adae245990653 --- /dev/null +++ b/drivers/pwm/Kconfig.ti_am3352_ehrpwm @@ -0,0 +1,9 @@ +# Copyright (c) 2025 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 + 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 0000000000000..fdde925ced675 --- /dev/null +++ b/drivers/pwm/pwm_ti_am3352_ehrpwm.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +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; + clock_control_subsys_t tbclk_subsys; + uint32_t clock_frequency; + 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); + 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 = clock_control_on(cfg->tbclk, cfg->tbclk_subsys); + if (err != 0) { + LOG_ERR("failed to enable tbclk"); + 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; + 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 = clock_control_off(cfg->tbclk, cfg->tbclk_subsys); + if (err != 0) { + LOG_ERR("failed to disable tbclk"); + 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); + + if (cfg->clock_dev != NULL) { + return clock_control_get_rate(cfg->clock_dev, cfg->clock_subsys, + (uint32_t *)cycles); + } + + if (cfg->clock_frequency != 0U) { + *cycles = cfg->clock_frequency; + return 0; + } + + return -ENOTSUP; +} + +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_CLK_CONFIG(n) \ + COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, fck), ( \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, fck)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME( \ + n, fck, clk_id), \ + .clock_frequency = 0 \ + ), ( \ + .clock_dev = NULL, \ + .clock_subsys = NULL, \ + .clock_frequency = DT_INST_PROP_OR(n, clock_frequency, 0) \ + ) \ + ) + +#define TI_EHRPWM_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static struct ti_ehrpwm_cfg ti_ehrpwm_config_##n = { \ + DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ + TI_EHRPWM_CLK_CONFIG(n), \ + .tbclk = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, tbclk)), \ + .tbclk_subsys = \ + (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(n, tbclk, clk_id), \ + .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 0000000000000..7fe4c0823dc07 --- /dev/null +++ b/dts/bindings/pwm/ti,am3352-ehrpwm.yaml @@ -0,0 +1,33 @@ +# Copyright (c) 2025 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 + + clock-frequency: + type: int + description: Optional peripheral frequency to use if the clock is absent + + "#pwm-cells": + const: 3 + +pwm-cells: + - channel + - period + - flags