Skip to content

Commit

Permalink
Merge pull request #20292 from maribu/drivers/periph/gpio_ll/switch_dir
Browse files Browse the repository at this point in the history
drivers/periph_gpio_ll: Implement API to switch direction
  • Loading branch information
benpicco authored Feb 5, 2024
2 parents 8bf6133 + 06a0537 commit 6deca8b
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 3 deletions.
1 change: 1 addition & 0 deletions cpu/atmega_common/Makefile.features
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions cpu/atmega_common/include/gpio_ll_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions cpu/sam0_common/Makefile.features
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions cpu/sam0_common/include/gpio_ll_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
78 changes: 76 additions & 2 deletions drivers/include/periph/gpio_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@
#ifndef PERIPH_GPIO_LL_H
#define PERIPH_GPIO_LL_H

#include <inttypes.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>

#include "architecture.h"
#include "periph_cpu.h"
#include "periph/gpio.h"
#include "periph_cpu.h"

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion drivers/periph_common/Makefile.dep
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions tests/periph/gpio_ll/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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");
Expand Down

0 comments on commit 6deca8b

Please sign in to comment.