diff --git a/cpu/atmega_common/Makefile.features b/cpu/atmega_common/Makefile.features index a7255a01106d..952f58126ec1 100644 --- a/cpu/atmega_common/Makefile.features +++ b/cpu/atmega_common/Makefile.features @@ -12,6 +12,7 @@ FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_irq_unmask +FEATURES_PROVIDED += periph_gpio_ll_switch_dir FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_rtc_ms FEATURES_PROVIDED += periph_rtt_overflow diff --git a/cpu/atmega_common/include/gpio_ll_arch.h b/cpu/atmega_common/include/gpio_ll_arch.h index 89f0121b6872..9f8cdf1a4ff5 100644 --- a/cpu/atmega_common/include/gpio_ll_arch.h +++ b/cpu/atmega_common/include/gpio_ll_arch.h @@ -184,6 +184,22 @@ static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask, return result; } +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs) +{ + unsigned irq_state = irq_disable(); + atmega_gpio_port_t *p = (void *)port; + p->ddr |= outputs; + irq_restore(irq_state); +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs) +{ + unsigned irq_state = irq_disable(); + atmega_gpio_port_t *p = (void *)port; + p->ddr &= ~(inputs); + irq_restore(irq_state); +} + static inline gpio_port_t gpio_port_pack_addr(void *addr) { return (gpio_port_t)addr; diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index 8915115c6bc3..184942584b19 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -21,6 +21,7 @@ FEATURES_PROVIDED += periph_gpio_ll_irq FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_irq_unmask +FEATURES_PROVIDED += periph_gpio_ll_switch_dir FEATURES_PROVIDED += periph_i2c_reconfigure FEATURES_PROVIDED += periph_rtt_overflow FEATURES_PROVIDED += periph_rtt_set_counter diff --git a/cpu/sam0_common/include/gpio_ll_arch.h b/cpu/sam0_common/include/gpio_ll_arch.h index 8f72b1997cd2..02b25c7c1e1f 100644 --- a/cpu/sam0_common/include/gpio_ll_arch.h +++ b/cpu/sam0_common/include/gpio_ll_arch.h @@ -106,6 +106,18 @@ static inline void gpio_ll_write(gpio_port_t port, uword_t mask) p->OUT.reg = mask; } +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs) +{ + PortGroup *p = (PortGroup *)port; + p->DIRSET.reg = outputs; +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs) +{ + PortGroup *p = (PortGroup *)port; + p->DIRCLR.reg = inputs; +} + static inline gpio_port_t gpio_get_port(gpio_t pin) { return (gpio_port_t)(pin & ~(0x1f)); diff --git a/drivers/include/periph/gpio_ll.h b/drivers/include/periph/gpio_ll.h index cd7c21f91810..ec2c708dacd0 100644 --- a/drivers/include/periph/gpio_ll.h +++ b/drivers/include/periph/gpio_ll.h @@ -69,13 +69,13 @@ #ifndef PERIPH_GPIO_LL_H #define PERIPH_GPIO_LL_H -#include +#include #include #include #include "architecture.h" -#include "periph_cpu.h" #include "periph/gpio.h" +#include "periph_cpu.h" #ifdef __cplusplus extern "C" { @@ -706,6 +706,47 @@ static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask, } #endif +/** + * @brief Turn GPIO pins specified by the bitmask @p outputs to outputs + * + * @param[in] port GPIO port to modify + * @param[in] outputs Bitmask specifying the GPIO pins to set in output + * mode + * @pre The feature `gpio_ll_switch_dir` is available + * @pre Each affected GPIO pin is either configured as input or as + * push-pull output. + * + * @note This is a makeshift solution to implement bit-banging of + * bidirectional protocols on less sophisticated GPIO peripherals + * that do not support open drain mode. + * @warning Use open drain mode instead, if supported. + */ +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs); + +/** + * @brief Turn GPIO pins specified by the bitmask @p inputs to inputs + * + * @param[in] port GPIO port to modify + * @param[in] inputs Bitmask specifying the GPIO pins to set in input + * mode + * @pre The feature `gpio_ll_switch_dir` is available + * @pre Each affected GPIO pin is either configured as input or as + * push-pull output. + * + * @warning The state of the output register may be intermixed with the + * input configuration. Specifically, on AVR the output register + * enables/disables the internal pull up, on SAM0 MCUs the output + * register controls the pull resistor direction (if the pull + * resistor is enabled). Hence, the bits in the output + * register of the pins switched to input should be restored + * just after this call. + * @note This is a makeshift solution to implement bit-banging of + * bidirectional protocols on less sophisticated GPIO peripherals + * that do not support open drain mode. + * @warning Use open drain mode instead, if supported. + */ +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs); + /** * @brief Perform a masked write operation on the I/O register of the port * @@ -786,6 +827,39 @@ static inline gpio_port_t gpio_port_pack_addr(void *addr); */ static inline void * gpio_port_unpack_addr(gpio_port_t port); +#ifndef DOXYGEN +#if !MODULE_PERIPH_GPIO_LL_SWITCH_DIR +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs) +{ + (void)port; + (void)outputs; + /* Hack: If this function is only used guarded by some + * + * if (IS_USED(MODULE_PERIPH_GPIO_LL_SWITCH_DIR)) { + * ... + * } + * + * as intended, all calls to the following fake function will be optimized + * due to the elimination of dead branches. If used incorrectly, a linking + * failure will be the result. The error message will not be ideal, but a + * compile time error is much better than a runtime error. + */ + extern void gpio_ll_switch_dir_output_used_but_feature_gpio_ll_switch_dir_is_not_provided(void); + gpio_ll_switch_dir_output_used_but_feature_gpio_ll_switch_dir_is_not_provided(); +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs) +{ + (void)port; + (void)inputs; + /* Same hack as above */ + extern void gpio_ll_switch_dir_input_used_but_feature_gpio_ll_switch_dir_is_not_provided(void); + gpio_ll_switch_dir_input_used_but_feature_gpio_ll_switch_dir_is_not_provided(); +} + +#endif /* !MODULE_PERIPH_GPIO_LL_SWITCH_DIR */ +#endif /* !DOXYGEN */ + #ifdef __cplusplus } #endif diff --git a/drivers/periph_common/Makefile.dep b/drivers/periph_common/Makefile.dep index cf0a84f9fff1..9d164a994485 100644 --- a/drivers/periph_common/Makefile.dep +++ b/drivers/periph_common/Makefile.dep @@ -1,14 +1,15 @@ # Always use hardware features, if available ifneq (,$(filter periph_gpio_ll%,$(USEMODULE))) FEATURES_OPTIONAL += periph_gpio_ll_disconnect - FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high FEATURES_OPTIONAL += periph_gpio_ll_input_pull_down FEATURES_OPTIONAL += periph_gpio_ll_input_pull_keep FEATURES_OPTIONAL += periph_gpio_ll_input_pull_up + FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_low FEATURES_OPTIONAL += periph_gpio_ll_irq_unmask FEATURES_OPTIONAL += periph_gpio_ll_open_drain FEATURES_OPTIONAL += periph_gpio_ll_open_drain_pull_up FEATURES_OPTIONAL += periph_gpio_ll_open_source FEATURES_OPTIONAL += periph_gpio_ll_open_source_pull_down + FEATURES_OPTIONAL += periph_gpio_ll_switch_dir endif diff --git a/tests/periph/gpio_ll/main.c b/tests/periph/gpio_ll/main.c index 36d05cd2fcc4..d6b0603d6205 100644 --- a/tests/periph/gpio_ll/main.c +++ b/tests/periph/gpio_ll/main.c @@ -862,6 +862,74 @@ static void test_irq(void) test_irq_level(); } +static void test_switch_dir(void) +{ + bool test_passed; + puts_optional("\n" + "Testing Switching Direction\n" + "===========================\n"); + + uword_t mask_out = 1U << PIN_OUT_0; + uword_t mask_in = 1U << PIN_IN_0; + + /* floating input must be supported by every MCU */ + expect(0 == gpio_ll_init(port_in, PIN_IN_0, gpio_ll_in)); + expect(0 == gpio_ll_init(port_out, PIN_OUT_0, gpio_ll_in)); + gpio_conf_t conf = gpio_ll_query_conf(port_out, PIN_OUT_0); + expect(conf.state == GPIO_INPUT); + gpio_conf_t conf_orig = conf; + + /* capture output state before switching from input mode to output mode, so + * that it can be restored when switching back to input mode */ + uword_t out_state = gpio_ll_read_output(port_out); + + /* now, switch to output mode and verify the switch */ + gpio_ll_switch_dir_output(port_out, mask_out); + conf = gpio_ll_query_conf(port_out, PIN_OUT_0); + test_passed = (conf.state == GPIO_OUTPUT_PUSH_PULL); + printf_optional("Input pin can be switched to output (push-pull) mode: %s\n", + noyes[test_passed]); + expect(test_passed); + + gpio_ll_clear(port_out, mask_out); + test_passed = (0 == (gpio_ll_read(port_in) & mask_in)); + gpio_ll_set(port_out, mask_out); + test_passed = test_passed && (gpio_ll_read(port_in) & mask_in); + printf_optional("Pin behaves as output after switched to output mode: %s\n", + noyes[test_passed]); + expect(test_passed); + + /* switch back to input mode */ + gpio_ll_switch_dir_input(port_out, mask_out); + /* restore out state from before the switch */ + gpio_ll_write(port_out, out_state); + /* verify we are back at the old config */ + conf = gpio_ll_query_conf(port_out, PIN_OUT_0); + test_passed = (conf.bits == conf_orig.bits); + printf_optional("Returning back to input had no side effects on config: %s\n", + noyes[test_passed]); + if (!test_passed) { + puts_optional("Before:"); + print_conf(conf_orig); + puts_optional("After:"); + print_conf(conf); + expect(0); + } + + /* Finally: check if input behaves like a proper input. For that, we + * configure the pin it is connected to (PORT_IN.PIN_IN_0) as output and + * see if we can read that from the pin used to switch back and force */ + expect(0 == gpio_ll_init(port_in, PIN_IN_0, gpio_ll_out)); + + gpio_ll_clear(port_in, mask_in); + test_passed = (0 == (gpio_ll_read(port_out) & mask_out)); + gpio_ll_set(port_in, mask_in); + test_passed = test_passed && (gpio_ll_read(port_out) & mask_out); + printf_optional("Pin behaves as input after switched back to input mode: %s\n", + noyes[test_passed]); + expect(test_passed); +} + int main(void) { print_details(); @@ -872,6 +940,10 @@ int main(void) test_irq(); } + if (IS_USED(MODULE_PERIPH_GPIO_LL_SWITCH_DIR)) { + test_switch_dir(); + } + /* if no expect() didn't blow up until now, the test is passed */ puts("\n\nTEST SUCCEEDED");