diff --git a/cpu/stm32/Makefile.features b/cpu/stm32/Makefile.features index ce010bf481..472f4a4417 100644 --- a/cpu/stm32/Makefile.features +++ b/cpu/stm32/Makefile.features @@ -25,6 +25,7 @@ FEATURES_PROVIDED += periph_uart_nonblocking ifneq (f1,$(CPU_FAM)) FEATURES_PROVIDED += periph_gpio_ll_open_drain_pull_up + FEATURES_PROVIDED += periph_gpio_ll_switch_dir endif ifneq (,$(filter $(CPU_FAM),c0 f0 f1 f3 g0 g4 l0 l1 l4 l5 u5 wb wl)) diff --git a/cpu/stm32/include/gpio_ll_arch.h b/cpu/stm32/include/gpio_ll_arch.h index 56f3647408..429dfee918 100644 --- a/cpu/stm32/include/gpio_ll_arch.h +++ b/cpu/stm32/include/gpio_ll_arch.h @@ -142,6 +142,31 @@ static inline void gpio_ll_write(gpio_port_t port, uword_t value) p->ODR = value; } +#ifdef MODULE_PERIPH_GPIO_LL_SWITCH_DIR +static inline uword_t gpio_ll_prepare_switch_dir(uword_t mask) +{ + /* implementation too large to always inline */ + extern uword_t gpio_ll_prepare_switch_dir_impl(uword_t mask); + return gpio_ll_prepare_switch_dir_impl(mask); +} + +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t pins) +{ + GPIO_TypeDef *p = (GPIO_TypeDef *)port; + unsigned irq_state = irq_disable(); + p->MODER |= pins; + irq_restore(irq_state); +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t pins) +{ + GPIO_TypeDef *p = (GPIO_TypeDef *)port; + unsigned irq_state = irq_disable(); + p->MODER &= ~pins; + irq_restore(irq_state); +} +#endif + static inline gpio_port_t gpio_get_port(gpio_t pin) { return pin & 0xfffffff0LU; diff --git a/cpu/stm32/include/periph/cpu_gpio_ll.h b/cpu/stm32/include/periph/cpu_gpio_ll.h index 454bfc2123..793f0e750f 100644 --- a/cpu/stm32/include/periph/cpu_gpio_ll.h +++ b/cpu/stm32/include/periph/cpu_gpio_ll.h @@ -31,6 +31,11 @@ extern "C" { * public view on type */ #ifndef DOXYGEN +#if !defined(CPU_FAM_STM32F1) +/* For the STM32F1 GPIO peripheral, the gpio_ll_switch_dir is not supported */ +# define HAVE_GPIO_LL_PREPARE_SWITCH_DIR +#endif + #define HAVE_GPIO_PULL_STRENGTH_T typedef enum { GPIO_PULL_WEAKEST = 0, diff --git a/cpu/stm32/periph/gpio_ll.c b/cpu/stm32/periph/gpio_ll.c index 66a7af11f3..ae2a8c8413 100644 --- a/cpu/stm32/periph/gpio_ll.c +++ b/cpu/stm32/periph/gpio_ll.c @@ -361,6 +361,50 @@ int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf) return 0; } +uword_t gpio_ll_prepare_switch_dir_impl(uword_t mask) +{ + /* Mask contains a bitmask containing the pins needed to change + * the direction of. E.g. for pins 0 to 3 it looks like: + * + * 3 2 1 0 + * +----+----+----+----+ + * | P3 | P2 | P1 | P0 | + * +----+----+----+----+ + * + * We need to update the GPIOX->MODER register, which for pins 0 to 3 + * looks like this: + * + * 7 6 5 4 3 2 1 0 + * +---------+---------+---------+---------+ + * | MODE3 | MODE2 | MODE1 | MODE0 | + * +---------+---------+---------+---------+ + * + * Where each mode field will have the value `0b00` for input or `0b01` + * for output (the others two values are for alternate function mode and + * analog mode, which are both not relevant here). So, we need a way to + * efficiently set and clear every second bit. Specifically, a bitmask + * that looks like this is our goal: + * + * 7 6 5 4 3 2 1 0 + * +----+----+----+----+----+----+----+----+ + * | 0 | P3 | 0 | P2 | 0 | P1 | 0 | P0 | + * +----+----+----+----+----+----+----+----+ + * + * This is what below bit magic magic does (but for 16 pins instead of + * 4). + */ + uword_t output = mask & 0xFFFF; + output |= output << 8; + output &= 0x00FF00FF; + output |= output << 4; + output &= 0x0F0F0F0F; + output |= output << 2; + output &= 0x33333333; + output |= output << 1; + output &= 0x55555555; + return output; +} + gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin) { gpio_conf_t result = { 0 };