From a194d48643df69e9ecf10b45852f0bc98d2863f7 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Mon, 29 Sep 2025 18:28:51 +0800 Subject: [PATCH 1/2] drivers: pwm: Enable PWM capture feature for TPM This implementation refers to the code from FTM, which supports to capture pulse and period. Signed-off-by: Felix Wang --- drivers/pwm/Kconfig.mcux_tpm | 12 +- drivers/pwm/pwm_mcux_tpm.c | 398 ++++++++++++++++++++++++++++++++++- 2 files changed, 399 insertions(+), 11 deletions(-) diff --git a/drivers/pwm/Kconfig.mcux_tpm b/drivers/pwm/Kconfig.mcux_tpm index 5cbd78fca709a..6032a62ecb123 100644 --- a/drivers/pwm/Kconfig.mcux_tpm +++ b/drivers/pwm/Kconfig.mcux_tpm @@ -1,4 +1,4 @@ -# Copyright 2020 NXP +# Copyright 2020, 2025 NXP # SPDX-License-Identifier: Apache-2.0 # MCUX TPM PWM @@ -11,3 +11,13 @@ config PWM_MCUX_TPM select PINCTRL help Enable the MCUX TPM PWM driver. + +config PWM_CAPTURE_MCUX_TPM_FILTER_VALUE + int "MCUX TPM PWM capture filter value" + depends on PWM_MCUX_TPM && PWM_CAPTURE + range 0 15 + default 0 + help + PWM capture filter value for channels. + The filter is disabled when the value is zero, otherwise + the filter is configured as (CHxFVAL * 4) clock cycles. diff --git a/drivers/pwm/pwm_mcux_tpm.c b/drivers/pwm/pwm_mcux_tpm.c index a9282ddd494c2..fee70588c7fa7 100644 --- a/drivers/pwm/pwm_mcux_tpm.c +++ b/drivers/pwm/pwm_mcux_tpm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,11 +27,14 @@ LOG_MODULE_REGISTER(pwm_mcux_tpm, CONFIG_PWM_LOG_LEVEL); -#if defined(TPM0) -#define MAX_CHANNELS ARRAY_SIZE(TPM0->CONTROLS) -#else -#define MAX_CHANNELS ARRAY_SIZE(TPM1->CONTROLS) -#endif +#define TPM_MAX_CHANNELS TPM_CnSC_COUNT +#define TPM_COMBINE_SHIFT (8U) + +/* PWM capture operates on channel pairs */ +#define TPM_MAX_CAPTURE_PAIRS (TPM_MAX_CHANNELS / 2U) +#define TPM_PAIR_FIRST_CH(pair) (pair * 2U) +#define TPM_PAIR_SECOND_CH(pair) (TPM_PAIR_FIRST_CH(pair) + 1) +#define TPM_WHICH_PAIR(ch) (ch / 2U) #define DEV_CFG(_dev) ((const struct mcux_tpm_config *)(_dev)->config) #define DEV_DATA(_dev) ((struct mcux_tpm_data *)(_dev)->data) @@ -45,13 +49,33 @@ struct mcux_tpm_config { uint8_t channel_count; tpm_pwm_mode_t mode; const struct pinctrl_dev_config *pincfg; +#ifdef CONFIG_PWM_CAPTURE + void (*irq_config_func)(const struct device *dev); +#endif /* CONFIG_PWM_CAPTURE */ +}; + +struct mcux_tpm_capture_data { + tpm_dual_edge_capture_param_t param; + pwm_capture_callback_handler_t callback; + void *user_data; + uint32_t first_edge_overflows; + uint32_t first_edge_cnt; /* Counter value after entering first edge interrupt */ + uint32_t first_edge_cnv; /* CnV value When first edge is captured */ + bool first_edge_overflow; + bool first_chan_captured; + bool pulse_capture; + bool continuous_capture; }; struct mcux_tpm_data { DEVICE_MMIO_NAMED_RAM(base); uint32_t clock_freq; uint32_t period_cycles; - tpm_chnl_pwm_signal_param_t channel[MAX_CHANNELS]; + tpm_chnl_pwm_signal_param_t channel[TPM_MAX_CHANNELS]; +#ifdef CONFIG_PWM_CAPTURE + uint32_t overflows; + struct mcux_tpm_capture_data capture[TPM_MAX_CAPTURE_PAIRS]; +#endif /* CONFIG_PWM_CAPTURE */ }; static int mcux_tpm_set_cycles(const struct device *dev, uint32_t channel, @@ -61,6 +85,10 @@ static int mcux_tpm_set_cycles(const struct device *dev, uint32_t channel, const struct mcux_tpm_config *config = dev->config; struct mcux_tpm_data *data = dev->data; TPM_Type *base = TPM_TYPE_BASE(dev, base); +#ifdef CONFIG_PWM_CAPTURE + uint32_t pair = TPM_WHICH_PAIR(channel); + uint32_t irqs; +#endif /* CONFIG_PWM_CAPTURE */ if (channel >= config->channel_count) { LOG_ERR("Invalid channel"); @@ -76,6 +104,14 @@ static int mcux_tpm_set_cycles(const struct device *dev, uint32_t channel, return -ENOTSUP; } +#ifdef CONFIG_PWM_CAPTURE + irqs = TPM_GetEnabledInterrupts(base); + if (irqs & BIT(TPM_PAIR_SECOND_CH(pair))) { + LOG_ERR("Cannot set PWM, capture in progress on pair %d", pair); + return -EBUSY; + } +#endif /* CONFIG_PWM_CAPTURE */ + LOG_DBG("pulse_cycles=%d, period_cycles=%d, flags=%d", pulse_cycles, period_cycles, flags); if (period_cycles != data->period_cycles) { @@ -138,6 +174,304 @@ static int mcux_tpm_set_cycles(const struct device *dev, uint32_t channel, return 0; } +#ifdef CONFIG_PWM_CAPTURE +static int mcux_tpm_configure_capture(const struct device *dev, + uint32_t channel, pwm_flags_t flags, + pwm_capture_callback_handler_t cb, + void *user_data) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + tpm_dual_edge_capture_param_t *param; + uint32_t pair = TPM_WHICH_PAIR(channel); + + if ((channel & 0x1U) == 0x1U) { + LOG_ERR("PWM capture only supported on even channels"); + return -ENOTSUP; + } + + if (pair >= ARRAY_SIZE(data->capture)) { + LOG_ERR("Invalid channel pair %d", pair); + return -EINVAL; + } + + if ((TPM_GetEnabledInterrupts(base) & BIT(TPM_PAIR_SECOND_CH(pair))) != 0) { + LOG_ERR("Capture already active on channel pair %d", pair); + return -EBUSY; + } + + if (!(flags & PWM_CAPTURE_TYPE_MASK)) { + LOG_ERR("No capture type specified"); + return -EINVAL; + } + + if ((flags & PWM_CAPTURE_TYPE_MASK) == PWM_CAPTURE_TYPE_BOTH) { + LOG_ERR("Cannot capture both period and pulse width"); + return -ENOTSUP; + } + + data->capture[pair].callback = cb; + data->capture[pair].user_data = user_data; + param = &data->capture[pair].param; + + if ((flags & PWM_CAPTURE_MODE_MASK) == PWM_CAPTURE_MODE_CONTINUOUS) { + data->capture[pair].continuous_capture = true; + } else { + data->capture[pair].continuous_capture = false; + } + + if (flags & PWM_CAPTURE_TYPE_PERIOD) { + data->capture[pair].pulse_capture = false; + + if (flags & PWM_POLARITY_INVERTED) { + param->currChanEdgeMode = kTPM_FallingEdge; + param->nextChanEdgeMode = kTPM_FallingEdge; + } else { + param->currChanEdgeMode = kTPM_RisingEdge; + param->nextChanEdgeMode = kTPM_RisingEdge; + } + } else { + data->capture[pair].pulse_capture = true; + + if (flags & PWM_POLARITY_INVERTED) { + param->currChanEdgeMode = kTPM_FallingEdge; + param->nextChanEdgeMode = kTPM_RisingEdge; + } else { + param->currChanEdgeMode = kTPM_RisingEdge; + param->nextChanEdgeMode = kTPM_FallingEdge; + } + } + + return 0; +} + +static int mcux_tpm_enable_capture(const struct device *dev, uint32_t channel) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + uint32_t pair = TPM_WHICH_PAIR(channel); + + if ((channel & 0x1U) == 0x1U) { + LOG_ERR("PWM capture only supported on even channels"); + return -ENOTSUP; + } + + if (pair >= ARRAY_SIZE(data->capture)) { + LOG_ERR("Invalid channel pair %d", pair); + return -EINVAL; + } + + if (!data->capture[pair].callback) { + LOG_ERR("PWM capture not configured"); + return -EINVAL; + } + + if ((TPM_GetEnabledInterrupts(base) & BIT(TPM_PAIR_SECOND_CH(pair))) != 0) { + LOG_ERR("Capture already active on channel pair %d", pair); + return -EBUSY; + } + + TPM_ClearStatusFlags(base, BIT(TPM_PAIR_FIRST_CH(pair)) | + BIT(TPM_PAIR_SECOND_CH(pair))); + + TPM_SetupDualEdgeCapture(base, pair, &data->capture[pair].param, + CONFIG_PWM_CAPTURE_MCUX_TPM_FILTER_VALUE); + + TPM_EnableInterrupts(base, BIT(TPM_PAIR_FIRST_CH(pair)) | + BIT(TPM_PAIR_SECOND_CH(pair))); + + return 0; +} + +static int mcux_tpm_disable_capture(const struct device *dev, uint32_t channel) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + uint32_t pair = TPM_WHICH_PAIR(channel); + + if ((channel & 0x1U) == 0x1U) { + LOG_ERR("PWM capture only supported on even channels"); + return -ENOTSUP; + } + + if (pair >= ARRAY_SIZE(data->capture)) { + LOG_ERR("Invalid channel pair %d", pair); + return -EINVAL; + } + + TPM_DisableInterrupts(base, BIT(TPM_PAIR_FIRST_CH(pair)) | + BIT(TPM_PAIR_SECOND_CH(pair))); + + /* Disable input capture combine mode */ + base->COMBINE &= ~(TPM_COMBINE_COMBINE0_MASK << ((TPM_COMBINE_SHIFT * pair))); + + return 0; +} + +static void mcux_tpm_capture_first_edge(const struct device *dev, uint32_t channel, uint32_t cnt, + bool overflow) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + struct mcux_tpm_capture_data *capture; + uint32_t pair = TPM_WHICH_PAIR(channel); + + __ASSERT_NO_MSG(pair < ARRAY_SIZE(data->capture)); + + capture = &data->capture[pair]; + capture->first_edge_cnv = TPM_GetChannelValue(base, channel); + capture->first_edge_cnt = cnt; + capture->first_edge_overflows = data->overflows; + capture->first_edge_overflow = overflow; + capture->first_chan_captured = true; + + /* Disable interrupt for first edge to prepare for second edge capture */ + TPM_DisableInterrupts(base, BIT(channel)); + TPM_ClearStatusFlags(base, BIT(channel)); + + LOG_DBG("pair = %d, 1st ovfs = %d, 1st cnt = %u, 1st cnv = %u, 1st ovf = %d", pair, + capture->first_edge_overflows, cnt, capture->first_edge_cnv, overflow); +} + +static void mcux_tpm_capture_second_edge(const struct device *dev, uint32_t channel, + uint32_t cnt, bool overflow) + +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + uint32_t second_edge_overflows = data->overflows; + uint32_t pair = TPM_WHICH_PAIR(channel); + struct mcux_tpm_capture_data *capture; + uint32_t overflows; + uint32_t first_cnv; + uint32_t second_cnv; + uint32_t cycles; + int status = 0; + + __ASSERT_NO_MSG(pair < ARRAY_SIZE(data->capture)); + capture = &data->capture[pair]; + first_cnv = capture->first_edge_cnv; + second_cnv = TPM_GetChannelValue(base, channel); + + if (unlikely(capture->first_edge_overflow && first_cnv > capture->first_edge_cnt)) { + /* Compensate for the overflow registered in the same IRQ */ + capture->first_edge_overflows--; + } + + if (unlikely(overflow && second_cnv > cnt)) { + /* Compensate for the overflow registered in the same IRQ */ + second_edge_overflows--; + } + + overflows = second_edge_overflows - capture->first_edge_overflows; + + /* Calculate cycles, check for overflows */ + if (overflows > 0) { + if (u32_mul_overflow(overflows, base->MOD, &cycles)) { + LOG_ERR("overflow while calculating cycles"); + status = -ERANGE; + } else { + cycles -= first_cnv; + if (u32_add_overflow(cycles, second_cnv, &cycles)) { + LOG_ERR("overflow while calculating cycles"); + cycles = 0; + status = -ERANGE; + } + } + } else { + cycles = second_cnv - first_cnv; + } + + LOG_DBG("pair = %d, 1st ovfs = %u, 2nd ovfs = %u, ovfs = %u, 1st cnv = %u, " + "2nd cnv = %u, cycles = %u, 2nd cnt = %u, 2nd ovf = %d", + pair, capture->first_edge_overflows, second_edge_overflows, + overflows, capture->first_edge_cnv, second_cnv, cycles, cnt, overflow); + + if (capture->pulse_capture) { + capture->callback(dev, pair, 0, cycles, status, + capture->user_data); + } else { + capture->callback(dev, pair, cycles, 0, status, + capture->user_data); + } + + /* Prepare for next capture */ + capture->first_chan_captured = false; + TPM_ClearStatusFlags(base, BIT(channel)); + if (capture->continuous_capture) { + if (capture->pulse_capture) { + /* Prepare for first edge of next pulse capture */ + TPM_EnableInterrupts(base, BIT(TPM_PAIR_FIRST_CH(pair))); + } else { + /* In continuous period capture mode, first edge of next period capture + * is second edge of this capture (this edge) + */ + capture->first_edge_cnv = second_cnv; + capture->first_edge_cnt = cnt; + capture->first_edge_overflows = second_edge_overflows; + capture->first_edge_overflow = (overflows > 0); + capture->first_chan_captured = true; + } + } else { + /* One-shot capture done */ + TPM_DisableInterrupts(base, BIT(TPM_PAIR_SECOND_CH(pair))); + } +} + +static bool mcux_tpm_handle_overflow(const struct device *dev) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + + if (TPM_GetStatusFlags(base) & kTPM_TimeOverflowFlag) { + TPM_ClearStatusFlags(base, kTPM_TimeOverflowFlag); + data->overflows++; + return true; + } + + return false; +} + +static void mcux_tpm_irq_handler(const struct device *dev, uint32_t chan_start, uint32_t chan_end) +{ + TPM_Type *base = TPM_TYPE_BASE(dev, base); + struct mcux_tpm_data *data = dev->data; + struct mcux_tpm_capture_data *capture; + bool overflow; + uint32_t flags; + uint32_t irqs; + uint32_t cnt; + uint32_t ch; + uint32_t first_chan; + uint32_t second_chan; + + flags = TPM_GetStatusFlags(base); + irqs = TPM_GetEnabledInterrupts(base); + cnt = base->CNT; + overflow = mcux_tpm_handle_overflow(dev); + + for (ch = chan_start; ch < chan_end; ch = ch + 2U) { + first_chan = ch; + second_chan = ch + 1U; + capture = &data->capture[TPM_WHICH_PAIR(first_chan)]; + + if ((flags & BIT(second_chan)) && (irqs & BIT(second_chan))) { + if (capture->first_chan_captured) { + mcux_tpm_capture_second_edge(dev, second_chan, cnt, overflow); + } else { + /* Missed first edge, clear second edge flag */ + TPM_ClearStatusFlags(base, BIT(second_chan)); + } + } else if ((flags & BIT(first_chan)) && (irqs & BIT(first_chan)) && + !capture->first_chan_captured) { + mcux_tpm_capture_first_edge(dev, first_chan, cnt, overflow); + } else { + /* No action required */ + } + } +} +#endif /* CONFIG_PWM_CAPTURE */ + static int mcux_tpm_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles) { @@ -217,18 +551,55 @@ static int mcux_tpm_init(const struct device *dev) TPM_Init(base, &tpm_config); +#ifdef CONFIG_PWM_CAPTURE + config->irq_config_func(dev); + TPM_EnableInterrupts(base, kTPM_TimeOverflowInterruptEnable); + data->period_cycles = 0xFFFFU; + TPM_SetTimerPeriod(base, data->period_cycles); + TPM_StartTimer(base, config->tpm_clock_source); +#endif /* CONFIG_PWM_CAPTURE */ + return 0; } static DEVICE_API(pwm, mcux_tpm_driver_api) = { .set_cycles = mcux_tpm_set_cycles, .get_cycles_per_sec = mcux_tpm_get_cycles_per_sec, +#ifdef CONFIG_PWM_CAPTURE + .configure_capture = mcux_tpm_configure_capture, + .enable_capture = mcux_tpm_enable_capture, + .disable_capture = mcux_tpm_disable_capture, +#endif /* CONFIG_PWM_CAPTURE */ }; #define TO_TPM_PRESCALE_DIVIDE(val) _DO_CONCAT(kTPM_Prescale_Divide_, val) -#define TPM_DEVICE(n) \ - PINCTRL_DT_INST_DEFINE(n); \ +#ifdef CONFIG_PWM_CAPTURE + +static void mcux_tpm_isr(const struct device *dev) +{ + const struct mcux_tpm_config *cfg = dev->config; + + mcux_tpm_irq_handler(dev, 0, cfg->channel_count); +} + +#define TPM_CONFIG_FUNC(n) \ +static void mcux_tpm_config_func_##n(const struct device *dev) \ +{ \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ + mcux_tpm_isr, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQN(n)); \ +} +#define TPM_CFG_CAPTURE_INIT(n) \ + .irq_config_func = mcux_tpm_config_func_##n +#define TPM_INIT_CFG(n) TPM_DECLARE_CFG(n, TPM_CFG_CAPTURE_INIT(n)) +#else /* !CONFIG_PWM_CAPTURE */ +#define TPM_CONFIG_FUNC(n) +#define TPM_CFG_CAPTURE_INIT +#define TPM_INIT_CFG(n) TPM_DECLARE_CFG(n, TPM_CFG_CAPTURE_INIT) +#endif /* !CONFIG_PWM_CAPTURE */ + +#define TPM_DECLARE_CFG(n, CAPTURE_INIT) \ static const struct mcux_tpm_config mcux_tpm_config_##n = { \ DEVICE_MMIO_NAMED_ROM_INIT(base, DT_DRV_INST(n)), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ @@ -240,12 +611,19 @@ static DEVICE_API(pwm, mcux_tpm_driver_api) = { DT_INST_REG_ADDR(n)), \ .mode = kTPM_EdgeAlignedPwm, \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ - }; \ + CAPTURE_INIT \ + } + +#define TPM_DEVICE(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static const struct mcux_tpm_config mcux_tpm_config_##n; \ static struct mcux_tpm_data mcux_tpm_data_##n; \ DEVICE_DT_INST_DEFINE(n, &mcux_tpm_init, NULL, \ &mcux_tpm_data_##n, \ &mcux_tpm_config_##n, \ POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \ - &mcux_tpm_driver_api); + &mcux_tpm_driver_api); \ + TPM_CONFIG_FUNC(n) \ + TPM_INIT_CFG(n); DT_INST_FOREACH_STATUS_OKAY(TPM_DEVICE) From 99ae548dedff0a369507d4ac67fe6386c7958ffb Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Tue, 30 Sep 2025 14:29:47 +0800 Subject: [PATCH 2/2] test: drivers: pwm: Enable test cases for frdm_mcxw71 Enable pwm_api and pwm_loopback test. All test cases passed. Signed-off-by: Felix Wang --- .../pwm/pwm_api/boards/frdm_mcxw71.overlay | 27 ++++++++++++++ .../pwm_loopback/boards/frdm_mcxw71.overlay | 36 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/drivers/pwm/pwm_api/boards/frdm_mcxw71.overlay create mode 100644 tests/drivers/pwm/pwm_loopback/boards/frdm_mcxw71.overlay diff --git a/tests/drivers/pwm/pwm_api/boards/frdm_mcxw71.overlay b/tests/drivers/pwm/pwm_api/boards/frdm_mcxw71.overlay new file mode 100644 index 0000000000000..cacd378b8f8e3 --- /dev/null +++ b/tests/drivers/pwm/pwm_api/boards/frdm_mcxw71.overlay @@ -0,0 +1,27 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +/ { + aliases { + pwm-test = &tpm1; + }; +}; + +&pinctrl { + tpm1_default: tpm1_default { + group0 { + pinmux = , + ; + drive-strength = "low"; + slew-rate = "slow"; + }; + }; +}; + +&tpm1 { + status = "okay"; + pinctrl-0 = <&tpm1_default>; + pinctrl-names = "default"; +}; diff --git a/tests/drivers/pwm/pwm_loopback/boards/frdm_mcxw71.overlay b/tests/drivers/pwm/pwm_loopback/boards/frdm_mcxw71.overlay new file mode 100644 index 0000000000000..ae6a6c829d1db --- /dev/null +++ b/tests/drivers/pwm/pwm_loopback/boards/frdm_mcxw71.overlay @@ -0,0 +1,36 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&pinctrl { + tpm0_default: tpm0_default { + group0 { + pinmux = , + , + , + ; + drive-strength = "low"; + slew-rate = "slow"; + }; + }; +}; + +/* To test this sample, connect + * PTA18(J2-9) ---> PTA21(J1-8) + */ + +/ { + pwm_loopback_0 { + compatible = "test-pwm-loopback"; + pwms = <&tpm0 3 0 PWM_POLARITY_NORMAL>, /* PTA18 J2 pin 9, out */ + <&tpm0 0 0 PWM_POLARITY_NORMAL>; /* PTA21 J1 pin 8, in */ + }; +}; + +&tpm0 { + status = "okay"; + pinctrl-0 = <&tpm0_default>; + pinctrl-names = "default"; +};