diff --git a/boards/ti/lp_am13e230/lp_am13e230.dts b/boards/ti/lp_am13e230/lp_am13e230.dts index 1661269d0b30..8fddd2ca3b4e 100644 --- a/boards/ti/lp_am13e230/lp_am13e230.dts +++ b/boards/ti/lp_am13e230/lp_am13e230.dts @@ -10,6 +10,8 @@ #include #include #include +#include +#include / { model = "TI AM13E230X Launchpad"; @@ -132,3 +134,44 @@ pinctrl-0 = <&uc4_tx_sda_pico_pa0 &uc4_rx_scl_sclk_pa1>; pinctrl-names = "default"; }; + +&gpio_pb4 { + input-enable; + bias-pull-down; +}; + +&outputxbar8_pb3 { + bias-pull-down; +}; + +&xbar1 { + status = "okay"; +}; + +&xbar2 { + status = "okay"; +}; + +&ecap0 { + status = "okay"; + interrupts = <29 0>; + + pinctrl-0 = <&outputxbar8_pb3>; + pinctrl-names = "default"; + + /* ECAP0 -> OUTPUTXBAR8 (idx 7) -> G1SEL bit 4 -> PB3 */ + mux-states = <&xbar2 AM13E2_XBAR_OUT_CTRL(7, 1, 4) 1>; + mux-state-names = "output"; +}; + +&ecap1 { + status = "okay"; + interrupts = <30 0>; + + pinctrl-0 = <&gpio_pb4>; + pinctrl-names = "default"; + + /* PB4 (GPIO36) -> INPUTXBAR1 -> ECAP1 ECCTL0.INPUTSEL (raw=1) */ + mux-states = <&xbar1 0 AM13E2_XBAR_IN_STATE(36, 1)>; + mux-state-names = "input"; +}; diff --git a/boards/ti/lp_am13e230/lp_am13e230.yaml b/boards/ti/lp_am13e230/lp_am13e230.yaml index 476d0d4d591b..86e737289612 100644 --- a/boards/ti/lp_am13e230/lp_am13e230.yaml +++ b/boards/ti/lp_am13e230/lp_am13e230.yaml @@ -12,4 +12,5 @@ supported: - uart - i2c - spi + - pwm vendor: ti diff --git a/drivers/pwm/Kconfig.ti_am3352_ecap b/drivers/pwm/Kconfig.ti_am3352_ecap index 59454408adc9..6e47b8fedc73 100644 --- a/drivers/pwm/Kconfig.ti_am3352_ecap +++ b/drivers/pwm/Kconfig.ti_am3352_ecap @@ -9,3 +9,15 @@ config PWM_TI_AM3352_ECAP select PINCTRL help Enable ECAP controller for TI SoCs + +if PWM_TI_AM3352_ECAP +config PWM_TI_AM3352_ECAP_VARIANT_AM13 + bool "TI ECAP AM13E variant" + depends on SOC_SERIES_AM13E + default y + select MUX + select SYSCON + help + Enable AM13E ECAP support : mux-states xbar routing + and sysctl for setting PWREN bits. +endif diff --git a/drivers/pwm/pwm_ti_am3352_ecap.c b/drivers/pwm/pwm_ti_am3352_ecap.c index 6c70239e1c7c..f26c39e98c47 100644 --- a/drivers/pwm/pwm_ti_am3352_ecap.c +++ b/drivers/pwm/pwm_ti_am3352_ecap.c @@ -14,15 +14,34 @@ #include #endif +#ifdef CONFIG_CLOCK_CONTROL_MSPM0 +#include +#endif + +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 +#include +#include +#include + +#define TI_ECAP_SYSCTL_PWREN_KEY (0x26U << 24) +#endif + LOG_MODULE_REGISTER(ti_ecap); #define DT_DRV_COMPAT ti_am3352_ecap - struct ti_ecap_regs { - uint8_t RESERVED_1[0x10]; /**< Reserved, offset: 0x00 - 0x10 */ - volatile uint32_t CAP3; /**< Capture-3 Register, offset: 0x10 */ - volatile uint32_t CAP4; /**< Capture-4 Register, offset: 0x14 */ - uint8_t RESERVED_2[0x10]; /**< Reserved, offset: 0x18 - 0x28 */ + volatile uint32_t TSCTR; /**< Time-Stamp Counter, offset: 0x00 */ + volatile uint32_t CTRPHS; /**< Counter Phase Offset, offset: 0x04 */ + volatile uint32_t CAP1; /**< Capture-1 / APWM period, offset: 0x08 */ + volatile uint32_t CAP2; /**< Capture-2 / APWM compare, offset: 0x0C */ + volatile uint32_t CAP3; /**< Capture-3 / APWM shadow period, offset: 0x10 */ + volatile uint32_t CAP4; /**< Capture-4 / APWM shadow compare, offset: 0x14 */ + uint8_t RESERVED_2[0xC]; /**< Reserved, offset: 0x18 - 0x24 */ +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 + volatile uint32_t ECCTL0; /**< Input mux select, AM13E-only, offset: 0x24 */ +#else + uint8_t RESERVED_ECCTL0[0x4]; /**< Reserved, offset: 0x24 - 0x28 */ +#endif volatile uint32_t ECCTL; /**< ECAP Control Register, offset: 0x28 */ volatile uint32_t ECINT_EN_FLG; /**< ECAP Interrupt Enable & Flag Register, offset: 0x2C */ volatile uint32_t ECINT_CLR_FRC; /**< ECAP Interrupt Clear & Force Register, offset: 0x30 */ @@ -58,6 +77,11 @@ struct ti_ecap_regs { #define TI_ECAP_ECINT_CLR_CEVT4 BIT(4) #define TI_ECAP_ECINT_CLR_INT BIT(0) +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 +/* ECAP Input Mux Select Register (ECCTL0, AM13E-only) */ +#define TI_ECAP_ECCTL0_INPUTSEL_MASK GENMASK(6, 0) +#endif + #define DEV_CFG(dev) ((const struct ti_ecap_cfg *)(dev)->config) #define DEV_DATA(dev) ((struct ti_ecap_data *)(dev)->data) #define DEV_REGS(dev) ((struct ti_ecap_regs *)DEVICE_MMIO_GET(dev)) @@ -70,6 +94,13 @@ struct ti_ecap_capture_data { bool continuous; }; +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 +struct ti_ecap_mux_entry { + const struct device *dev; + const struct mux_state *state; +}; +#endif + struct ti_ecap_cfg { DEVICE_MMIO_ROM; void (*irq_config_func)(); @@ -77,6 +108,16 @@ struct ti_ecap_cfg { clock_control_subsys_t clock_subsys; uint32_t clock_frequency; const struct pinctrl_dev_config *pcfg; +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 + const struct ti_ecap_mux_entry *mux_entries; + uint8_t mux_entries_count; + bool has_input_xbar; + uint8_t input_xbar_channel; + const struct device *pwren_syscon; + uint32_t pwren_offset; + uint8_t pwren_bit; + bool has_pwren; +#endif }; struct ti_ecap_data { @@ -94,8 +135,9 @@ static int ti_ecap_set_cycles(const struct device *dev, uint32_t channel, uint32 ecctl = regs->ECCTL; - /* enable apwm and counter */ - ecctl |= (TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAP_APWM); + /* Stop the counter while reconfiguring APWM. */ + ecctl &= ~TI_ECAP_ECCTL_TSCNTSTP; + ecctl |= TI_ECAP_ECCTL_CAP_APWM; /* polarity */ if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) { @@ -104,12 +146,15 @@ static int ti_ecap_set_cycles(const struct device *dev, uint32_t channel, uint32 ecctl |= TI_ECAP_ECCTL_APWMPOL; } - /* update shadow period and shadow cmp registers in apwm mode */ + regs->ECCTL = ecctl; + + regs->CAP1 = period_cycles; + regs->CAP2 = pulse_cycles; regs->CAP3 = period_cycles; regs->CAP4 = pulse_cycles; - /* update ecctl */ - regs->ECCTL = ecctl; + regs->TSCTR = 0; + regs->ECCTL |= TI_ECAP_ECCTL_TSCNTSTP; return 0; } @@ -189,8 +234,10 @@ static int ti_ecap_configure_capture(const struct device *dev, uint32_t channel, /* disable interrupts */ regs->ECINT_EN_FLG &= ~(TI_ECAP_ECINT_EN_CEVT4 | TI_ECAP_ECINT_EN_CNTOVF); - /* clear event flags */ - regs->ECINT_CLR_FRC |= + /* ECCLR is write-1-to-clear; ECFRC sits in the upper half of this + * 32-bit view — never RMW or a stale force bit can be rewritten. + */ + regs->ECINT_CLR_FRC = (TI_ECAP_ECINT_CLR_CNTOVF | TI_ECAP_ECINT_CLR_CEVT4 | TI_ECAP_ECINT_CLR_INT); ecctl = regs->ECCTL; @@ -236,11 +283,48 @@ static int ti_ecap_configure_capture(const struct device *dev, uint32_t channel, } #endif /* CONFIG_PWM_CAPTURE */ +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 +static int ti_ecap_pwren_enable(const struct ti_ecap_cfg *cfg) +{ + uint32_t bit; + uint32_t mask; + int ret; + + if (!cfg->has_pwren) { + return 0; + } + + if (!device_is_ready(cfg->pwren_syscon)) { + LOG_ERR("SYSCTL (pwren) device not ready"); + return -ENODEV; + } + + bit = BIT(cfg->pwren_bit); + mask = bit | TI_ECAP_SYSCTL_PWREN_KEY; + ret = syscon_update_bits(cfg->pwren_syscon, cfg->pwren_offset, mask, mask); + if (ret < 0) { + LOG_ERR("failed to set SYSCTL PWREN bit %u: %d", cfg->pwren_bit, ret); + return ret; + } + + msp_delay_peripheral_startup(); + + return 0; +} +#endif /* CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 */ + static int ti_ecap_init(const struct device *dev) { const struct ti_ecap_cfg *cfg = DEV_CFG(dev); int ret; +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 + ret = ti_ecap_pwren_enable(cfg); + if (ret < 0) { + return ret; + } +#endif + DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); @@ -249,6 +333,30 @@ static int ti_ecap_init(const struct device *dev) return ret; } +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 + for (uint8_t i = 0; i < cfg->mux_entries_count; i++) { + const struct ti_ecap_mux_entry *entry = &cfg->mux_entries[i]; + + if (!device_is_ready(entry->dev)) { + LOG_ERR("xbar device not ready"); + return -ENODEV; + } + + ret = mux_state_apply(entry->dev, entry->state); + if (ret < 0) { + LOG_ERR("failed to apply mux state %d: %d", i, ret); + return ret; + } + } + + if (cfg->has_input_xbar) { + struct ti_ecap_regs *regs = DEV_REGS(dev); + + regs->ECCTL0 = (regs->ECCTL0 & ~TI_ECAP_ECCTL0_INPUTSEL_MASK) | + FIELD_PREP(TI_ECAP_ECCTL0_INPUTSEL_MASK, cfg->input_xbar_channel); + } +#endif /* CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 */ + cfg->irq_config_func(); return 0; @@ -268,8 +376,7 @@ static void ti_ecap_isr(const struct device *dev) cpt->callback(dev, 0, period, pulse, -ERANGE, cpt->user_data); } - /* clear event */ - regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CNTOVF; + regs->ECINT_CLR_FRC = TI_ECAP_ECINT_CLR_CNTOVF | TI_ECAP_ECINT_CLR_INT; } else if (ecint_en_flg & TI_ECAP_ECINT_EN_CEVT4_FLG) { uint32_t cap3 = regs->CAP3; uint32_t cap4 = regs->CAP4; @@ -286,12 +393,10 @@ static void ti_ecap_isr(const struct device *dev) cpt->callback(dev, 0, period, pulse, 0, cpt->user_data); } - /* clear event */ - regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CEVT4; + regs->ECINT_CLR_FRC = TI_ECAP_ECINT_CLR_CEVT4 | TI_ECAP_ECINT_CLR_INT; + } else { + regs->ECINT_CLR_FRC = TI_ECAP_ECINT_CLR_INT; } - - /* clear global interrupt */ - regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_INT; } static DEVICE_API(pwm, ti_ecap_api) = { @@ -313,24 +418,77 @@ static DEVICE_API(pwm, ti_ecap_api) = { ), (COND_CODE_1(CONFIG_CLOCK_CONTROL_ARM_SCMI, \ (static const clock_control_subsys_t ti_ecap_clk_subsys_##n = \ (clock_control_subsys_t)DT_INST_PHA(n, clocks, name); \ - ), (BUILD_ASSERT(0, "Unsupported clock controller");)))) + ), (COND_CODE_1(CONFIG_CLOCK_CONTROL_MSPM0, \ + (static const struct mspm0_sys_clock ti_ecap_mspm0_sys_clock_##n = \ + MSPM0_CLOCK_SUBSYS_FN(n); \ + static const clock_control_subsys_t ti_ecap_clk_subsys_##n = \ + (clock_control_subsys_t)&ti_ecap_mspm0_sys_clock_##n; \ + ), (BUILD_ASSERT(0, "Unsupported clock controller");)))))) + +#ifdef CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 +/* One entry per "mux-states" phandle-array element: applies the crossbar + * routing (Input or Output XBAR) named by that entry through the mux + * subsystem. + */ +#define TI_ECAP_MUX_ENTRY(node_id, prop, idx) \ + { \ + .dev = MUX_STATE_DT_DEV_GET_BY_IDX(node_id, idx), \ + .state = MUX_STATE_DT_GET_BY_IDX(node_id, idx), \ + } + +#define TI_ECAP_MUX_ENTRIES_DEFINE(n) \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(n, mux_states), \ + (MUX_STATE_DT_INST_SPEC_DEFINE_ALL(n); \ + static const struct ti_ecap_mux_entry ti_ecap_mux_entries_##n[] = { \ + DT_INST_FOREACH_PROP_ELEM_SEP(n, mux_states, TI_ECAP_MUX_ENTRY, \ + (,))};)) + +/* If a "mux-states" entry is named "input", its control cell is the Input + * XBAR channel that feeds this eCAP's own ECCTL0.INPUTSEL field - a + * separate mux internal to the eCAP, indexing into its own 128-entry table + * (INPUTXBAR1-16 occupy indices 0-15), not the crossbar's own addressing. + */ +#define TI_ECAP_MUX_ENTRIES_INIT(n) \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, mux_states), \ + (.mux_entries = ti_ecap_mux_entries_##n, \ + .mux_entries_count = ARRAY_SIZE(ti_ecap_mux_entries_##n),), ()) \ + COND_CODE_1(DT_INST_PROP_HAS_NAME(n, mux_states, input), \ + (.has_input_xbar = true, \ + .input_xbar_channel = DT_INST_PHA_BY_NAME(n, mux_states, input, channel),), ()) + +#define TI_ECAP_PWREN_INIT(n) \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, ti_pwren), \ + (.has_pwren = true, \ + .pwren_syscon = DEVICE_DT_GET(DT_INST_PHANDLE(n, ti_pwren)), \ + .pwren_offset = DT_INST_PHA(n, ti_pwren, offset), \ + .pwren_bit = DT_INST_PHA(n, ti_pwren, bit),), ()) +#else /* !CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 */ +#define TI_ECAP_MUX_ENTRIES_DEFINE(n) +#define TI_ECAP_MUX_ENTRIES_INIT(n) +#define TI_ECAP_PWREN_INIT(n) +#endif /* CONFIG_PWM_TI_AM3352_ECAP_VARIANT_AM13 */ #define TI_ECAP_INIT(n) \ - TI_ECAP_DEFINE_CLK_SUBSYS(n); \ + TI_ECAP_DEFINE_CLK_SUBSYS(n); \ PINCTRL_DT_INST_DEFINE(n); \ + TI_ECAP_MUX_ENTRIES_DEFINE(n); \ static void ti_ecap_irq_config_func_##n(void) \ { \ - IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ti_ecap_isr, \ - DEVICE_DT_INST_GET(n), DT_INST_IRQ(n, flags)); \ + IRQ_CONNECT( \ + DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ti_ecap_isr, \ + DEVICE_DT_INST_GET(n), \ + COND_CODE_1(DT_INST_IRQ_HAS_CELL(n, flags), \ + (DT_INST_IRQ(n, flags)), (0))); \ irq_enable(DT_INST_IRQN(n)); \ } \ static struct ti_ecap_cfg ti_ecap_config_##n = { \ DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ - .clock_subsys = ti_ecap_clk_subsys_##n, \ + .clock_subsys = ti_ecap_clk_subsys_##n, \ .irq_config_func = ti_ecap_irq_config_func_##n, \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ - }; \ + TI_ECAP_MUX_ENTRIES_INIT(n) \ + TI_ECAP_PWREN_INIT(n)}; \ \ static struct ti_ecap_data ti_ecap_data_##n; \ \ diff --git a/dts/arm/ti/am13/am13e.dtsi b/dts/arm/ti/am13/am13e.dtsi index a955b44aaaea..f813e3cb5c6b 100644 --- a/dts/arm/ti/am13/am13e.dtsi +++ b/dts/arm/ti/am13/am13e.dtsi @@ -7,6 +7,7 @@ #include #include #include +#include #include / { @@ -35,6 +36,28 @@ #mux-state-cells = <2>; status = "disabled"; }; + + ecap0: ecap@40440000 { + compatible = "ti,am3352-ecap"; + reg = <0x40440000 0x40>; + clocks = <&ckm MSPM0_CLOCK_MCLK>; + ti,pwren = <&sysctl MSPM_SYSCTL_PWREN_MCPERIPH + MSPM_SYSCTL_PWREN_ECAP0_BIT>; + + #pwm-cells = <3>; + status = "disabled"; + }; + + ecap1: ecap@40441000 { + compatible = "ti,am3352-ecap"; + reg = <0x40441000 0x40>; + clocks = <&ckm MSPM0_CLOCK_MCLK>; + ti,pwren = <&sysctl MSPM_SYSCTL_PWREN_MCPERIPH + MSPM_SYSCTL_PWREN_ECAP1_BIT>; + + #pwm-cells = <3>; + status = "disabled"; + }; }; cpus { diff --git a/dts/bindings/pwm/ti,am3352-ecap.yaml b/dts/bindings/pwm/ti,am3352-ecap.yaml index cf1ede443cf6..aea5cece3fbd 100644 --- a/dts/bindings/pwm/ti,am3352-ecap.yaml +++ b/dts/bindings/pwm/ti,am3352-ecap.yaml @@ -1,9 +1,35 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 Texas Instruments # SPDX-License-Identifier: Apache-2.0 -description: TI SOC ECAP based PWM controller +description: | + TI SOC ECAP based PWM controller -include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml] + On SoCs where the eCAP's pins are reachable only through a crossbar + (e.g. AM13E), reference the crossbar via "mux-states" instead of adding + SoC-specific pinmux logic to this driver: + + &ecap0 { + pinctrl-0 = <&my_apwm_pin>; + pinctrl-names = "default"; + + mux-states = <&xbar_out AM13E2_XBAR_OUT_CTRL(idx, group, bit) 1>; + mux-state-names = "output"; + }; + + &ecap1 { + pinctrl-0 = <&my_capture_pin>; + pinctrl-names = "default"; + + mux-states = <&xbar_in CHANNEL AM13E2_XBAR_IN_STATE(GPIO_NUMBER, 1)>; + mux-state-names = "input"; + }; + + A "mux-states" entry named "input" additionally feeds this eCAP's own + ECCTL0.INPUTSEL field (AM13E-only) with that entry's crossbar channel, + since ECCTL0 is a mux internal to the eCAP, separate from the crossbar + itself. + +include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml, mux-consumer.yaml] compatible: "ti,am3352-ecap" @@ -21,6 +47,13 @@ properties: "#pwm-cells": const: 3 + ti,pwren: + type: phandle-array + specifier-space: pwren + description: | + Power-enable this peripheral via SYSCTL.PWREN_*. + Format: <&sysctl offset bit>. + pwm-cells: - channel - period diff --git a/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.conf b/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.conf new file mode 100644 index 000000000000..2badf72d832e --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.conf @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +# AM13e MPU map is still incomplete for userspace ztests; run capture +# cases in supervisor mode (same approach as other boards here). +CONFIG_TEST_USERSPACE=n diff --git a/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.overlay b/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.overlay new file mode 100644 index 000000000000..9ca804215169 --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/boards/lp_am13e230.overlay @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 Texas Instruments Incorporated + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + pwm_loopback_0 { + compatible = "test-pwm-loopback"; + /* + * Generate on eCAP0 APWM (PB3 / OUTPUTXBAR8), capture on + * eCAP1 (PB4 / INPUTXBAR1). Connect PB3 to PB4 on the board. + */ + pwms = <&ecap0 0 0 PWM_POLARITY_NORMAL>, + <&ecap1 0 0 PWM_POLARITY_NORMAL>; + }; +};