diff --git a/drivers/pwm/pwm_stm32.c b/drivers/pwm/pwm_stm32.c index 44f021a874d..ca989facd5d 100644 --- a/drivers/pwm/pwm_stm32.c +++ b/drivers/pwm/pwm_stm32.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2016 Linaro Limited. * Copyright (c) 2020 Teslabs Engineering S.L. + * Copyright (c) 2023 Nobleo Technology * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +34,22 @@ LOG_MODULE_REGISTER(pwm_stm32, CONFIG_PWM_LOG_LEVEL); #endif #ifdef CONFIG_PWM_CAPTURE + +/** + * @brief Capture state when in 4-channel support mode + */ +enum capture_state { + CAPTURE_STATE_IDLE = 0, + CAPTURE_STATE_WAIT_FOR_UPDATE_EVENT = 1, + CAPTURE_STATE_WAIT_FOR_PULSE_START = 2, + CAPTURE_STATE_WAIT_FOR_PERIOD_END = 3 +}; + +/** Return the complimentary channel number + * that is used to capture the end of the pulse. + */ +static const uint32_t complimentary_channel[] = {0, 2, 1, 4, 3}; + struct pwm_stm32_capture_data { pwm_capture_callback_handler_t callback; void *user_data; @@ -43,9 +60,16 @@ struct pwm_stm32_capture_data { bool capture_period; bool capture_pulse; bool continuous; + uint8_t channel; + + /* only used when four_channel_capture_support */ + enum capture_state state; }; -/* first capture is always nonsense, second is nonsense when polarity changed */ +/* When PWM capture is done by resetting the counter with UIF then the + * first capture is always nonsense, second is nonsense when polarity changed + * This is not the case when using four-channel-support. + */ #define SKIPPED_PWM_CAPTURES 2u #endif /*CONFIG_PWM_CAPTURE*/ @@ -70,6 +94,7 @@ struct pwm_stm32_config { const struct pinctrl_dev_config *pcfg; #ifdef CONFIG_PWM_CAPTURE void (*irq_config_func)(const struct device *dev); + const bool four_channel_capture_support; #endif /* CONFIG_PWM_CAPTURE */ }; @@ -115,6 +140,51 @@ static void (*const set_timer_compare[TIMER_MAX_CH])(TIM_TypeDef *, #endif }; +/** Channel to capture get function mapping. */ +#if !defined(CONFIG_SOC_SERIES_STM32F1X) && \ + !defined(CONFIG_SOC_SERIES_STM32F4X) && \ + !defined(CONFIG_SOC_SERIES_STM32G4X) && \ + !defined(CONFIG_SOC_SERIES_STM32MP1X) +static uint32_t __maybe_unused (*const get_channel_capture[])(const TIM_TypeDef *) = { +#else +static uint32_t __maybe_unused (*const get_channel_capture[])(TIM_TypeDef *) = { +#endif + LL_TIM_IC_GetCaptureCH1, LL_TIM_IC_GetCaptureCH2, + LL_TIM_IC_GetCaptureCH3, LL_TIM_IC_GetCaptureCH4 +}; + + +/** Channel to enable capture interrupt mapping. */ +static void __maybe_unused (*const enable_capture_interrupt[])(TIM_TypeDef *) = { + LL_TIM_EnableIT_CC1, LL_TIM_EnableIT_CC2, + LL_TIM_EnableIT_CC3, LL_TIM_EnableIT_CC4 +}; + +/** Channel to disable capture interrupt mapping. */ +static void __maybe_unused (*const disable_capture_interrupt[])(TIM_TypeDef *) = { + LL_TIM_DisableIT_CC1, LL_TIM_DisableIT_CC2, + LL_TIM_DisableIT_CC3, LL_TIM_DisableIT_CC4 +}; + +/** Channel to is capture active flag mapping. */ +#if !defined(CONFIG_SOC_SERIES_STM32F1X) && \ + !defined(CONFIG_SOC_SERIES_STM32F4X) && \ + !defined(CONFIG_SOC_SERIES_STM32G4X) && \ + !defined(CONFIG_SOC_SERIES_STM32MP1X) +static uint32_t __maybe_unused (*const is_capture_active[])(const TIM_TypeDef *) = { +#else +static uint32_t __maybe_unused (*const is_capture_active[])(TIM_TypeDef *) = { +#endif + LL_TIM_IsActiveFlag_CC1, LL_TIM_IsActiveFlag_CC2, + LL_TIM_IsActiveFlag_CC3, LL_TIM_IsActiveFlag_CC4 +}; + +/** Channel to clearing capture flag mapping. */ +static void __maybe_unused (*const clear_capture_interrupt[])(TIM_TypeDef *) = { + LL_TIM_ClearFlag_CC1, LL_TIM_ClearFlag_CC2, + LL_TIM_ClearFlag_CC3, LL_TIM_ClearFlag_CC4 +}; + /** * Obtain LL polarity from PWM flags. * @@ -270,12 +340,10 @@ static int pwm_stm32_set_cycles(const struct device *dev, uint32_t channel, } #ifdef CONFIG_PWM_CAPTURE - if ((channel == 1u) || (channel == 2u)) { - if (LL_TIM_IsEnabledIT_CC1(cfg->timer) || - LL_TIM_IsEnabledIT_CC2(cfg->timer)) { - LOG_ERR("Cannot set PWM output, capture in progress"); - return -EBUSY; - } + if (LL_TIM_IsEnabledIT_CC1(cfg->timer) || LL_TIM_IsEnabledIT_CC2(cfg->timer) || + LL_TIM_IsEnabledIT_CC3(cfg->timer) || LL_TIM_IsEnabledIT_CC4(cfg->timer)) { + LOG_ERR("Cannot set PWM output, capture in progress"); + return -EBUSY; } #endif /* CONFIG_PWM_CAPTURE */ @@ -391,8 +459,8 @@ static int pwm_stm32_set_cycles(const struct device *dev, uint32_t channel, } #ifdef CONFIG_PWM_CAPTURE -static int init_capture_channel(const struct device *dev, uint32_t channel, - pwm_flags_t flags, uint32_t ll_channel) +static int init_capture_channels(const struct device *dev, uint32_t channel, + pwm_flags_t flags) { const struct pwm_stm32_config *cfg = dev->config; bool is_inverted = (flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED; @@ -402,30 +470,21 @@ static int init_capture_channel(const struct device *dev, uint32_t channel, ic.ICPrescaler = TIM_ICPSC_DIV1; ic.ICFilter = LL_TIM_IC_FILTER_FDIV1; - if (ll_channel == LL_TIM_CHANNEL_CH1) { - if (channel == 1u) { - ic.ICActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI; - ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_FALLING - : LL_TIM_IC_POLARITY_RISING; - } else { - ic.ICActiveInput = LL_TIM_ACTIVEINPUT_INDIRECTTI; - ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_RISING - : LL_TIM_IC_POLARITY_FALLING; - } - } else { - if (channel == 1u) { - ic.ICActiveInput = LL_TIM_ACTIVEINPUT_INDIRECTTI; - ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_RISING - : LL_TIM_IC_POLARITY_FALLING; - } else { - ic.ICActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI; - ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_FALLING - : LL_TIM_IC_POLARITY_RISING; - } + /* Setup main channel */ + ic.ICActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI; + ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_FALLING : LL_TIM_IC_POLARITY_RISING; + + if (LL_TIM_IC_Init(cfg->timer, ch2ll[channel - 1], &ic) != SUCCESS) { + LOG_ERR("Could not initialize main channel for PWM capture"); + return -EIO; } - if (LL_TIM_IC_Init(cfg->timer, ll_channel, &ic) != SUCCESS) { - LOG_ERR("Could not initialize channel for PWM capture"); + /* Setup complimentary channel */ + ic.ICActiveInput = LL_TIM_ACTIVEINPUT_INDIRECTTI; + ic.ICPolarity = is_inverted ? LL_TIM_IC_POLARITY_RISING : LL_TIM_IC_POLARITY_FALLING; + + if (LL_TIM_IC_Init(cfg->timer, ch2ll[complimentary_channel[channel] - 1], &ic) != SUCCESS) { + LOG_ERR("Could not initialize complimentary channel for PWM capture"); return -EIO; } @@ -437,12 +496,17 @@ static int pwm_stm32_configure_capture(const struct device *dev, pwm_capture_callback_handler_t cb, void *user_data) { - /* - * Capture is implemented using the slave mode controller. - * This allows for high accuracy, but only CH1 and CH2 are supported. - * Alternatively all channels could be supported with ISR based resets. - * This is currently not implemented! + * Capture is implemented in two different ways, depending on the + * four-channel-capture-support setting in the node. + * - Two Channel Support: + * Only two channels (1 and 2) are available for capture. It uses + * the slave mode controller to reset the counter on each edge. + * - Four Channel Support: + * All four channels are available for capture. Instead of the + * slave mode controller it uses the ISR to reset the counter. + * This is slightly less accurate, but still within acceptable + * bounds. */ const struct pwm_stm32_config *cfg = dev->config; @@ -450,14 +514,21 @@ static int pwm_stm32_configure_capture(const struct device *dev, struct pwm_stm32_capture_data *cpt = &data->capture; int ret; - if ((channel != 1u) && (channel != 2u)) { - LOG_ERR("PWM capture only supported on first two channels"); - return -ENOTSUP; + if (!cfg->four_channel_capture_support) { + if ((channel != 1u) && (channel != 2u)) { + LOG_ERR("PWM capture only supported on first two channels"); + return -ENOTSUP; + } + } else { + if ((channel < 1u) || (channel > 4u)) { + LOG_ERR("PWM capture only exists on channels 1, 2, 3 and 4."); + return -ENOTSUP; + } } - if (LL_TIM_IsEnabledIT_CC1(cfg->timer) - || LL_TIM_IsEnabledIT_CC2(cfg->timer)) { - LOG_ERR("PWM Capture already in progress"); + if (LL_TIM_IsEnabledIT_CC1(cfg->timer) || LL_TIM_IsEnabledIT_CC2(cfg->timer) || + LL_TIM_IsEnabledIT_CC3(cfg->timer) || LL_TIM_IsEnabledIT_CC4(cfg->timer)) { + LOG_ERR("PWM capture already in progress"); return -EBUSY; } @@ -466,7 +537,8 @@ static int pwm_stm32_configure_capture(const struct device *dev, return -EINVAL; } - if (!IS_TIM_SLAVE_INSTANCE(cfg->timer)) { + if (!cfg->four_channel_capture_support && !IS_TIM_SLAVE_INSTANCE(cfg->timer)) { + /* slave mode is only used when not in four channel mode */ LOG_ERR("Timer does not support slave mode for PWM capture"); return -ENOTSUP; } @@ -480,23 +552,20 @@ static int pwm_stm32_configure_capture(const struct device *dev, /* Prevents faulty behavior while making changes */ LL_TIM_SetSlaveMode(cfg->timer, LL_TIM_SLAVEMODE_DISABLED); - ret = init_capture_channel(dev, channel, flags, LL_TIM_CHANNEL_CH1); + ret = init_capture_channels(dev, channel, flags); if (ret < 0) { return ret; } - ret = init_capture_channel(dev, channel, flags, LL_TIM_CHANNEL_CH2); - if (ret < 0) { - return ret; + if (!cfg->four_channel_capture_support) { + if (channel == 1u) { + LL_TIM_SetTriggerInput(cfg->timer, LL_TIM_TS_TI1FP1); + } else { + LL_TIM_SetTriggerInput(cfg->timer, LL_TIM_TS_TI2FP2); + } + LL_TIM_SetSlaveMode(cfg->timer, LL_TIM_SLAVEMODE_RESET); } - if (channel == 1u) { - LL_TIM_SetTriggerInput(cfg->timer, LL_TIM_TS_TI1FP1); - } else { - LL_TIM_SetTriggerInput(cfg->timer, LL_TIM_TS_TI2FP2); - } - LL_TIM_SetSlaveMode(cfg->timer, LL_TIM_SLAVEMODE_RESET); - LL_TIM_EnableARRPreload(cfg->timer); if (!IS_TIM_32B_COUNTER_INSTANCE(cfg->timer)) { LL_TIM_SetAutoReload(cfg->timer, 0xffffu); @@ -512,14 +581,22 @@ static int pwm_stm32_enable_capture(const struct device *dev, uint32_t channel) { const struct pwm_stm32_config *cfg = dev->config; struct pwm_stm32_data *data = dev->data; + struct pwm_stm32_capture_data *cpt = &data->capture; - if ((channel != 1u) && (channel != 2u)) { - LOG_ERR("PWM capture only supported on first two channels"); - return -EINVAL; + if (!cfg->four_channel_capture_support) { + if ((channel != 1u) && (channel != 2u)) { + LOG_ERR("PWM capture only supported on first two channels"); + return -ENOTSUP; + } + } else { + if ((channel < 1u) || (channel > 4u)) { + LOG_ERR("PWM capture only exists on channels 1, 2, 3 and 4."); + return -ENOTSUP; + } } - if (LL_TIM_IsEnabledIT_CC1(cfg->timer) - || LL_TIM_IsEnabledIT_CC2(cfg->timer)) { + if (LL_TIM_IsEnabledIT_CC1(cfg->timer) || LL_TIM_IsEnabledIT_CC2(cfg->timer) || + LL_TIM_IsEnabledIT_CC3(cfg->timer) || LL_TIM_IsEnabledIT_CC4(cfg->timer)) { LOG_ERR("PWM capture already active"); return -EBUSY; } @@ -529,21 +606,22 @@ static int pwm_stm32_enable_capture(const struct device *dev, uint32_t channel) return -EINVAL; } - data->capture.skip_irq = SKIPPED_PWM_CAPTURES; + cpt->channel = channel; + cpt->state = CAPTURE_STATE_WAIT_FOR_PULSE_START; + data->capture.skip_irq = cfg->four_channel_capture_support ? 0 : SKIPPED_PWM_CAPTURES; data->capture.overflows = 0u; - LL_TIM_ClearFlag_CC1(cfg->timer); - LL_TIM_ClearFlag_CC2(cfg->timer); + + clear_capture_interrupt[channel - 1](cfg->timer); LL_TIM_ClearFlag_UPDATE(cfg->timer); LL_TIM_SetUpdateSource(cfg->timer, LL_TIM_UPDATESOURCE_COUNTER); - if (channel == 1u) { - LL_TIM_EnableIT_CC1(cfg->timer); - } else { - LL_TIM_EnableIT_CC2(cfg->timer); - } + + enable_capture_interrupt[channel - 1](cfg->timer); + + LL_TIM_CC_EnableChannel(cfg->timer, ch2ll[channel - 1]); + LL_TIM_CC_EnableChannel(cfg->timer, ch2ll[complimentary_channel[channel] - 1]); LL_TIM_EnableIT_UPDATE(cfg->timer); - LL_TIM_CC_EnableChannel(cfg->timer, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(cfg->timer, LL_TIM_CHANNEL_CH2); + LL_TIM_GenerateEvent_UPDATE(cfg->timer); return 0; } @@ -552,91 +630,130 @@ static int pwm_stm32_disable_capture(const struct device *dev, uint32_t channel) { const struct pwm_stm32_config *cfg = dev->config; - if ((channel != 1u) && (channel != 2u)) { - LOG_ERR("PWM capture only supported on first two channels"); - return -EINVAL; + if (!cfg->four_channel_capture_support) { + if ((channel != 1u) && (channel != 2u)) { + LOG_ERR("PWM capture only supported on first two channels"); + return -ENOTSUP; + } + } else { + if ((channel < 1u) || (channel > 4u)) { + LOG_ERR("PWM capture only exists on channels 1, 2, 3 and 4."); + return -ENOTSUP; + } } LL_TIM_SetUpdateSource(cfg->timer, LL_TIM_UPDATESOURCE_REGULAR); - if (channel == 1u) { - LL_TIM_DisableIT_CC1(cfg->timer); - } else { - LL_TIM_DisableIT_CC2(cfg->timer); - } + + disable_capture_interrupt[channel - 1](cfg->timer); + LL_TIM_DisableIT_UPDATE(cfg->timer); - LL_TIM_CC_DisableChannel(cfg->timer, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_DisableChannel(cfg->timer, LL_TIM_CHANNEL_CH2); + LL_TIM_CC_DisableChannel(cfg->timer, ch2ll[channel - 1]); + LL_TIM_CC_DisableChannel(cfg->timer, ch2ll[complimentary_channel[channel] - 1]); return 0; } -static void get_pwm_capture(const struct device *dev, uint32_t channel) -{ - const struct pwm_stm32_config *cfg = dev->config; - struct pwm_stm32_data *data = dev->data; - struct pwm_stm32_capture_data *cpt = &data->capture; - - if (channel == 1u) { - cpt->period = LL_TIM_IC_GetCaptureCH1(cfg->timer); - cpt->pulse = LL_TIM_IC_GetCaptureCH2(cfg->timer); - } else { - cpt->period = LL_TIM_IC_GetCaptureCH2(cfg->timer); - cpt->pulse = LL_TIM_IC_GetCaptureCH1(cfg->timer); - } -} - static void pwm_stm32_isr(const struct device *dev) { const struct pwm_stm32_config *cfg = dev->config; struct pwm_stm32_data *data = dev->data; struct pwm_stm32_capture_data *cpt = &data->capture; int status = 0; - uint32_t in_ch = LL_TIM_IsEnabledIT_CC1(cfg->timer) ? 1u : 2u; - if (cpt->skip_irq == 0u) { - if (LL_TIM_IsActiveFlag_UPDATE(cfg->timer)) { - LL_TIM_ClearFlag_UPDATE(cfg->timer); - cpt->overflows++; - } - - if (LL_TIM_IsActiveFlag_CC1(cfg->timer) - || LL_TIM_IsActiveFlag_CC2(cfg->timer)) { - LL_TIM_ClearFlag_CC1(cfg->timer); - LL_TIM_ClearFlag_CC2(cfg->timer); - - get_pwm_capture(dev, in_ch); - - if (cpt->overflows) { - LOG_ERR("counter overflow during PWM capture"); - status = -ERANGE; - } - - if (!cpt->continuous) { - pwm_stm32_disable_capture(dev, in_ch); - } else { - cpt->overflows = 0u; - } - - if (cpt->callback != NULL) { - cpt->callback(dev, in_ch, - cpt->capture_period ? cpt->period : 0u, - cpt->capture_pulse ? cpt->pulse : 0u, - status, cpt->user_data); - } - } - } else { + if (cpt->skip_irq != 0u) { if (LL_TIM_IsActiveFlag_UPDATE(cfg->timer)) { LL_TIM_ClearFlag_UPDATE(cfg->timer); } if (LL_TIM_IsActiveFlag_CC1(cfg->timer) - || LL_TIM_IsActiveFlag_CC2(cfg->timer)) { + || LL_TIM_IsActiveFlag_CC2(cfg->timer) + || LL_TIM_IsActiveFlag_CC3(cfg->timer) + || LL_TIM_IsActiveFlag_CC4(cfg->timer)) { LL_TIM_ClearFlag_CC1(cfg->timer); LL_TIM_ClearFlag_CC2(cfg->timer); + LL_TIM_ClearFlag_CC3(cfg->timer); + LL_TIM_ClearFlag_CC4(cfg->timer); cpt->skip_irq--; } + + return; + } + + if (LL_TIM_IsActiveFlag_UPDATE(cfg->timer)) { + LL_TIM_ClearFlag_UPDATE(cfg->timer); + if (cfg->four_channel_capture_support && + cpt->state == CAPTURE_STATE_WAIT_FOR_UPDATE_EVENT) { + /* Special handling of UPDATE event in case it's triggered */ + cpt->state = CAPTURE_STATE_WAIT_FOR_PERIOD_END; + } else { + cpt->overflows++; + } + } + + if (!cfg->four_channel_capture_support) { + if (is_capture_active[cpt->channel - 1](cfg->timer) || + is_capture_active[complimentary_channel[cpt->channel] - 1](cfg->timer)) { + clear_capture_interrupt[cpt->channel - 1](cfg->timer); + clear_capture_interrupt + [complimentary_channel[cpt->channel] - 1](cfg->timer); + + cpt->period = get_channel_capture[cpt->channel - 1](cfg->timer); + cpt->pulse = get_channel_capture + [complimentary_channel[cpt->channel] - 1](cfg->timer); + } + } else { + if (cpt->state == CAPTURE_STATE_WAIT_FOR_PULSE_START && + is_capture_active[cpt->channel - 1](cfg->timer)) { + /* Reset the counter manually instead of automatically by HW + * This sets the pulse-start at 0 and makes the pulse-end + * and period related to that number. Sure we loose some + * accuracy but it's within acceptable range. + * + * This is done through an UPDATE event to also reset + * the prescalar. This could look like an overflow event + * and might therefore require special handling. + */ + cpt->state = CAPTURE_STATE_WAIT_FOR_UPDATE_EVENT; + LL_TIM_GenerateEvent_UPDATE(cfg->timer); + + } else if ((cpt->state == CAPTURE_STATE_WAIT_FOR_UPDATE_EVENT || + cpt->state == CAPTURE_STATE_WAIT_FOR_PERIOD_END) && + is_capture_active[cpt->channel - 1](cfg->timer)) { + cpt->state = CAPTURE_STATE_IDLE; + /* The end of the period. Both capture channels should now contain + * the timer value when the pulse and period ended respectively. + */ + cpt->pulse = get_channel_capture[complimentary_channel[cpt->channel] - 1] + (cfg->timer); + cpt->period = get_channel_capture[cpt->channel - 1](cfg->timer); + } + + clear_capture_interrupt[cpt->channel - 1](cfg->timer); + + if (cpt->state != CAPTURE_STATE_IDLE) { + /* Still waiting for a complete capture */ + return; + } + + if (cpt->overflows) { + LOG_ERR("counter overflow during PWM capture"); + status = -ERANGE; + } + } + + if (!cpt->continuous) { + pwm_stm32_disable_capture(dev, cpt->channel); + } else { + cpt->overflows = 0u; + cpt->state = CAPTURE_STATE_WAIT_FOR_PULSE_START; + } + + if (cpt->callback != NULL) { + cpt->callback(dev, cpt->channel, cpt->capture_period ? cpt->period : 0u, + cpt->capture_pulse ? cpt->pulse : 0u, status, cpt->user_data); } } + #endif /* CONFIG_PWM_CAPTURE */ static int pwm_stm32_get_cycles_per_sec(const struct device *dev, @@ -755,8 +872,9 @@ static void pwm_stm32_irq_config_func_##index(const struct device *dev) \ (IRQ_CONNECT_AND_ENABLE_DEFAULT(index)) \ ); \ } -#define CAPTURE_INIT(index) \ - .irq_config_func = pwm_stm32_irq_config_func_##index +#define CAPTURE_INIT(index) \ + .irq_config_func = pwm_stm32_irq_config_func_##index, \ + .four_channel_capture_support = DT_INST_PROP(index, four_channel_capture_support) #else #define IRQ_CONFIG_FUNC(index) #define CAPTURE_INIT(index) diff --git a/dts/bindings/pwm/st,stm32-pwm.yaml b/dts/bindings/pwm/st,stm32-pwm.yaml index 26d758ec7af..cf61c257971 100644 --- a/dts/bindings/pwm/st,stm32-pwm.yaml +++ b/dts/bindings/pwm/st,stm32-pwm.yaml @@ -11,6 +11,14 @@ properties: pinctrl-names: required: true + four-channel-capture-support: + type: boolean + description: | + Add support to capture on four channels. This is less accurate than + the default 2 channel support because the counter is reset by + interrupt instead of slave-mode controller. This option can also + be used as alternative for timers that does not support slave mode. + "#pwm-cells": const: 3 description: |