From 94c4666cb36aa3706c6e96eb23ba55523e1c3f71 Mon Sep 17 00:00:00 2001 From: Francois Ramu Date: Fri, 18 Mar 2022 11:30:17 +0100 Subject: [PATCH 1/5] include: drivers: pwm: extend the pwm flags to be soc specific Add 8 bits to the pwm_flags_t so that upper byte is reserved for non-standard but soc specific dts flags and keeping the low Byte for standard flags. Some of SoC like STM32 mcus can then define their own flags in their dt-bindings/pwm. Signed-off-by: Francois Ramu --- include/drivers/pwm.h | 6 +++++- include/dt-bindings/pwm/pwm.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/drivers/pwm.h b/include/drivers/pwm.h index 82d144ed11c14..6400bda4603d2 100644 --- a/include/drivers/pwm.h +++ b/include/drivers/pwm.h @@ -64,8 +64,12 @@ extern "C" { /** * @brief Provides a type to hold PWM configuration flags. + * + * The lower 8 bits are used for standard flags. + * The upper 8 bits are reserved for SoC specific flags. */ -typedef uint8_t pwm_flags_t; + +typedef uint16_t pwm_flags_t; /** * @typedef pwm_pin_set_t diff --git a/include/dt-bindings/pwm/pwm.h b/include/dt-bindings/pwm/pwm.h index 92f3157a5ed07..d879654cff99b 100644 --- a/include/dt-bindings/pwm/pwm.h +++ b/include/dt-bindings/pwm/pwm.h @@ -11,6 +11,7 @@ * The `PWM_POLARITY_*` flags are used with pwm_pin_set_cycles(), * pwm_pin_set_usec(), pwm_pin_set_nsec() or pwm_pin_configure_capture() to * specify the polarity of a PWM pin. + * The flags are on the lower 8bits of the pwm_flags_t * @{ */ /** PWM pin normal polarity (active-high pulse). */ From 75634f1984ee2e9f71bdeaa3859c13644de5ffbe Mon Sep 17 00:00:00 2001 From: Francois Ramu Date: Wed, 26 Jan 2022 10:30:49 +0100 Subject: [PATCH 2/5] dts: pwm : flag to enable a complementary output of a stm32 pwm channel Depending on the stm32 soc and the timer instance, several channels can enable the complementary output for the PWM signal This flag completes the PWM_POLARITY for a PWM channel in the upper byte of the pwm_flags_t. Signed-off-by: Francois Ramu --- include/dt-bindings/pwm/stm32_pwm.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 include/dt-bindings/pwm/stm32_pwm.h diff --git a/include/dt-bindings/pwm/stm32_pwm.h b/include/dt-bindings/pwm/stm32_pwm.h new file mode 100644 index 0000000000000..e342253f2f553 --- /dev/null +++ b/include/dt-bindings/pwm/stm32_pwm.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_PWM_STM32_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_PWM_STM32_H_ + +/** + * @name custom PWM complementary flags for output pins + * This flag can be used with any of the `pwm_pin_set_*` API calls to indicate + * that the PWM signal has to be routed to the complementary output channel. + * This feature is only available on certain SoC families, refer to the + * binding's documentation for more details. + * The custom flags are on the upper 8bits of the pwm_flags_t + * @{ + */ +/** PWM complementary output pin is enabled */ +#define PWM_STM32_COMPLEMENTARY (1U << 8) + +/** @cond INTERNAL_HIDDEN */ +#define PWM_STM32_COMPLEMENTARY_MASK 0x100 +/** @endcond */ +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_PWM_STM32_H_ */ From 28f73eaf951bdfc80aecc43eb61d5d9150570906 Mon Sep 17 00:00:00 2001 From: Francois Ramu Date: Mon, 14 Feb 2022 12:54:05 +0100 Subject: [PATCH 3/5] dts: bindings : explains the flag for stm32 complementary channel Depending on the stm32 mcus and the timer instance, several channels can enable the complementary output for the PWM signal. Example of a DTS <&pwm1 2 4 (PWM_POLARITY_NORMAL | PWM_COMPLEMENTARY)>; Note that the timer channel must support the complementary output on that channel : usually channels 1-3 + channel 4 on stm32g4x. Signed-off-by: Francois Ramu --- dts/bindings/pwm/st,stm32-pwm.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dts/bindings/pwm/st,stm32-pwm.yaml b/dts/bindings/pwm/st,stm32-pwm.yaml index 5ad0300a53226..c5210485d6b9f 100644 --- a/dts/bindings/pwm/st,stm32-pwm.yaml +++ b/dts/bindings/pwm/st,stm32-pwm.yaml @@ -26,9 +26,15 @@ properties: "#pwm-cells": const: 3 + description: | + Number of items to expect in a PWM + - channel of the timer used for PWM + - period to set in ns + - flags : combination of standard flags like PWM_POLARITY_NORMAL + or specific flags like PWM_STM32_COMPLEMENTARY. As an example for channel2: + <&pwm1 2 100 (PWM_POLARITY_NORMAL | PWM_STM32_COMPLEMENTARY)>; pwm-cells: - channel -# period in terms of nanoseconds - period - flags From 22684542c41f92063b85641a67066e1d376ac7cd Mon Sep 17 00:00:00 2001 From: Francois Ramu Date: Tue, 25 Jan 2022 10:49:28 +0100 Subject: [PATCH 4/5] drivers: pwm: stm32 can initialize the PWM complementary output If the dts defines the PWM complementary output, then the OCN must be init in place of the OC state and polarity. This is an exclusive setting for this pin. The channel in LL_TIM_OC_SetPolarity can be the complementary one. Signed-off-by: Francois Ramu --- drivers/pwm/pwm_stm32.c | 84 ++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/drivers/pwm/pwm_stm32.c b/drivers/pwm/pwm_stm32.c index 85e7911dff7e4..70f4b19d032fc 100644 --- a/drivers/pwm/pwm_stm32.c +++ b/drivers/pwm/pwm_stm32.c @@ -19,6 +19,7 @@ #include #include +#include #include LOG_MODULE_REGISTER(pwm_stm32, CONFIG_PWM_LOG_LEVEL); @@ -40,6 +41,10 @@ struct pwm_stm32_capture_data { bool capture_pulse; bool continuous; }; + +/* first capture is always nonsense, second is nonsense when polarity changed */ +#define SKIPPED_PWM_CAPTURES 2u + #endif /*CONFIG_PWM_CAPTURE*/ /** PWM data. */ @@ -66,34 +71,15 @@ struct pwm_stm32_config { #endif /* CONFIG_PWM_CAPTURE */ }; -/** Series F3, F7, G0, G4, H7, L4, MP1 and WB have up to 6 channels, others up - * to 4. - */ -#if defined(CONFIG_SOC_SERIES_STM32F3X) || \ - defined(CONFIG_SOC_SERIES_STM32F7X) || \ - defined(CONFIG_SOC_SERIES_STM32G0X) || \ - defined(CONFIG_SOC_SERIES_STM32G4X) || \ - defined(CONFIG_SOC_SERIES_STM32H7X) || \ - defined(CONFIG_SOC_SERIES_STM32L4X) || \ - defined(CONFIG_SOC_SERIES_STM32MP1X) || \ - defined(CONFIG_SOC_SERIES_STM32WBX) +/** Maximum number of timer channels : some stm32 soc have 6 else only 4 */ +#if defined(LL_TIM_CHANNEL_CH6) #define TIMER_HAS_6CH 1 -#else -#define TIMER_HAS_6CH 0 -#endif - -/** Maximum number of timer channels. */ -#if TIMER_HAS_6CH #define TIMER_MAX_CH 6u #else +#define TIMER_HAS_6CH 0 #define TIMER_MAX_CH 4u #endif -/* first capture is always nonsense, second is nonsense when polarity changed */ -#ifdef CONFIG_PWM_CAPTURE -#define SKIPPED_PWM_CAPTURES 2u -#endif /* CONFIG_PWM_CAPTURE */ - /** Channel to LL mapping. */ static const uint32_t ch2ll[TIMER_MAX_CH] = { LL_TIM_CHANNEL_CH1, LL_TIM_CHANNEL_CH2, @@ -103,6 +89,21 @@ static const uint32_t ch2ll[TIMER_MAX_CH] = { #endif }; + +/** Some stm32 mcus have complementary channels : 3 or 4 */ +static const uint32_t ch2ll_n[] = { +#if defined(LL_TIM_CHANNEL_CH1N) + LL_TIM_CHANNEL_CH1N, + LL_TIM_CHANNEL_CH2N, + LL_TIM_CHANNEL_CH3N, +#if defined(LL_TIM_CHANNEL_CH4N) +/** stm32g4x and stm32u5x have 4 complementary channels */ + LL_TIM_CHANNEL_CH4N, +#endif /* LL_TIM_CHANNEL_CH4N */ +#endif /* LL_TIM_CHANNEL_CH1N */ +}; +/** Maximum number of complemented timer channels is ARRAY_SIZE(ch2ll_n)*/ + /** Channel to compare set function mapping. */ static void (*const set_timer_compare[TIMER_MAX_CH])(TIM_TypeDef *, uint32_t) = { @@ -227,6 +228,7 @@ static int pwm_stm32_pin_set(const struct device *dev, uint32_t pwm, const struct pwm_stm32_config *cfg = dev->config; uint32_t channel; + uint32_t current_channel; /* complementary output if used */ if (pwm < 1u || pwm > TIMER_MAX_CH) { LOG_ERR("Invalid channel (%d)", pwm); @@ -259,20 +261,47 @@ static int pwm_stm32_pin_set(const struct device *dev, uint32_t pwm, channel = ch2ll[pwm - 1u]; + /* in LL_TIM_CC_DisableChannel and LL_TIM_CC_IsEnabledChannel, + * the channel param could be the complementary one + */ + if ((flags & PWM_STM32_COMPLEMENTARY_MASK) == PWM_STM32_COMPLEMENTARY) { + if (pwm > ARRAY_SIZE(ch2ll_n)) { + /* setting a flag on a channel that has not this capability */ + LOG_ERR("Channel %d has NO complementary output", pwm); + return -EINVAL; + } + current_channel = ch2ll_n[pwm - 1u]; + } else { + current_channel = channel; + } + if (period_cycles == 0u) { - LL_TIM_CC_DisableChannel(cfg->timer, channel); + LL_TIM_CC_DisableChannel(cfg->timer, current_channel); return 0; } - if (!LL_TIM_CC_IsEnabledChannel(cfg->timer, channel)) { + if (!LL_TIM_CC_IsEnabledChannel(cfg->timer, current_channel)) { LL_TIM_OC_InitTypeDef oc_init; LL_TIM_OC_StructInit(&oc_init); oc_init.OCMode = LL_TIM_OCMODE_PWM1; + +#if defined(LL_TIM_CHANNEL_CH1N) + /* the flags holds the PWM_STM32_COMPLEMENTARY information */ + if ((flags & PWM_STM32_COMPLEMENTARY_MASK) == PWM_STM32_COMPLEMENTARY) { + oc_init.OCNState = LL_TIM_OCSTATE_ENABLE; + oc_init.OCNPolarity = get_polarity(flags); + } else { + oc_init.OCState = LL_TIM_OCSTATE_ENABLE; + oc_init.OCPolarity = get_polarity(flags); + } +#else /* LL_TIM_CHANNEL_CH1N */ + oc_init.OCState = LL_TIM_OCSTATE_ENABLE; - oc_init.CompareValue = pulse_cycles; oc_init.OCPolarity = get_polarity(flags); +#endif /* LL_TIM_CHANNEL_CH1N */ + oc_init.CompareValue = pulse_cycles; #ifdef CONFIG_PWM_CAPTURE if (IS_TIM_SLAVE_INSTANCE(cfg->timer)) { @@ -283,17 +312,20 @@ static int pwm_stm32_pin_set(const struct device *dev, uint32_t pwm, } #endif /* CONFIG_PWM_CAPTURE */ + /* in LL_TIM_OC_Init, the channel is always the non-complementary */ if (LL_TIM_OC_Init(cfg->timer, channel, &oc_init) != SUCCESS) { LOG_ERR("Could not initialize timer channel output"); return -EIO; } LL_TIM_EnableARRPreload(cfg->timer); + /* in LL_TIM_OC_EnablePreload, the channel is always the non-complementary */ LL_TIM_OC_EnablePreload(cfg->timer, channel); LL_TIM_SetAutoReload(cfg->timer, period_cycles - 1u); LL_TIM_GenerateEvent_UPDATE(cfg->timer); } else { - LL_TIM_OC_SetPolarity(cfg->timer, channel, get_polarity(flags)); + /* in LL_TIM_OC_SetPolarity, the channel could be the complementary one */ + LL_TIM_OC_SetPolarity(cfg->timer, current_channel, get_polarity(flags)); set_timer_compare[pwm - 1u](cfg->timer, pulse_cycles); LL_TIM_SetAutoReload(cfg->timer, period_cycles - 1u); } From 1498c36db4178c116f5ea80fa777be0aa720ff7f Mon Sep 17 00:00:00 2001 From: Francois Ramu Date: Tue, 22 Mar 2022 11:06:52 +0100 Subject: [PATCH 5/5] samples: blinky pwm running on the nucleo_l4r5zi Adding an overlay to configure the nucleo_l4r5zi for testing the pwm blinky application on red led (PB14 on nucleo board) Each has a specific pwm output from different timers/channels Signed-off-by: Francois Ramu --- .../blinky_pwm/boards/nucleo_l4r5zi.overlay | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 samples/basic/blinky_pwm/boards/nucleo_l4r5zi.overlay diff --git a/samples/basic/blinky_pwm/boards/nucleo_l4r5zi.overlay b/samples/basic/blinky_pwm/boards/nucleo_l4r5zi.overlay new file mode 100644 index 0000000000000..7e40a6480089b --- /dev/null +++ b/samples/basic/blinky_pwm/boards/nucleo_l4r5zi.overlay @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 STMicroelectronics + */ + +#include +#include + +/ { + pwmleds { + compatible = "pwm-leds"; + + red_pwm_led: red_pwm_led { + pwms = <&pwm1 2 4 (PWM_POLARITY_NORMAL | PWM_STM32_COMPLEMENTARY)>; + }; + }; + + aliases { + pwm-led0 = &red_pwm_led; + }; +}; + +&timers1 { + status = "okay"; + + pwm1: pwm { + status = "okay"; + pinctrl-0 = <&tim1_ch2n_pb14>; + pinctrl-names = "default"; + }; +};