From 38f554ef4f99281fc9d546da8d13eefccc03d30c Mon Sep 17 00:00:00 2001 From: Purdea Andrei Date: Sun, 22 Jan 2023 06:23:26 +0200 Subject: [PATCH] drivers: gpio_rpi_pico.c: add support for single-ended IO Note: RP2040 can support single-ended IO, by setting the GPIO_OUT register to a constant value, and then changing the GPIO_OE register instead, when the output has to change. To do this, the output-setting functions need to know which pins have been configured as single-ended, and for that reason the data structure has been extended to include this information. Another change is the PR, is that setting of the pull-ups/pull-downs now applies to both inputs and outputs as well. Previous solution was wrong, because if the user wanted to set up an input with a pull resistor enabled, and then reconfigure it to an output without any pulls, then the pulls remained in place for the output. Now pulls are correctly set based on the gpio flags for outputs too, and this is especially useful for single-ended outputs too. Signed-off-by: Purdea Andrei --- drivers/gpio/gpio_rpi_pico.c | 75 ++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpio_rpi_pico.c b/drivers/gpio/gpio_rpi_pico.c index e6c9cbcabaf..41ed8e64ecb 100644 --- a/drivers/gpio/gpio_rpi_pico.c +++ b/drivers/gpio/gpio_rpi_pico.c @@ -29,32 +29,52 @@ struct gpio_rpi_data { struct gpio_driver_data common; sys_slist_t callbacks; uint32_t int_enabled_mask; + uint32_t single_ended_mask; + uint32_t open_drain_mask; }; static int gpio_rpi_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { - if (flags & GPIO_SINGLE_ENDED) { - return -ENOTSUP; - } + struct gpio_rpi_data *data = dev->data; + + gpio_set_pulls(pin, + (flags & GPIO_PULL_UP) != 0U, + (flags & GPIO_PULL_DOWN) != 0U); /* Avoid gpio_init, since that also clears previously set direction/high/low */ gpio_set_function(pin, GPIO_FUNC_SIO); if (flags & GPIO_OUTPUT) { - gpio_set_dir(pin, GPIO_OUT); + if (flags & GPIO_SINGLE_ENDED) { + data->single_ended_mask |= BIT(pin); - if (flags & GPIO_OUTPUT_INIT_HIGH) { - gpio_put(pin, 1); - } else if (flags & GPIO_OUTPUT_INIT_LOW) { - gpio_put(pin, 0); + /* Setting the initial state of output data, and output enable. + * The output data will not change from here on, only output + * enable will. If none of the GPIO_OUTPUT_INIT_* flags have + * been set then fall back to the non-agressive input mode. + */ + if (flags & GPIO_LINE_OPEN_DRAIN) { + data->open_drain_mask |= BIT(pin); + gpio_put(pin, 0); + gpio_set_dir(pin, flags & GPIO_OUTPUT_INIT_LOW); + } else { + data->open_drain_mask &= ~(BIT(pin)); + gpio_put(pin, 1); + gpio_set_dir(pin, flags & GPIO_OUTPUT_INIT_HIGH); + } + } else { + data->single_ended_mask &= ~(BIT(pin)); + if (flags & GPIO_OUTPUT_INIT_HIGH) { + gpio_put(pin, 1); + } else if (flags & GPIO_OUTPUT_INIT_LOW) { + gpio_put(pin, 0); + } + gpio_set_dir(pin, GPIO_OUT); } } else if (flags & GPIO_INPUT) { gpio_set_dir(pin, GPIO_IN); - gpio_set_pulls(pin, - (flags & GPIO_PULL_UP) != 0U, - (flags & GPIO_PULL_DOWN) != 0U); } return 0; @@ -69,28 +89,53 @@ static int gpio_rpi_port_get_raw(const struct device *dev, uint32_t *value) static int gpio_rpi_port_set_masked_raw(const struct device *port, uint32_t mask, uint32_t value) { - gpio_put_masked(mask, value); + struct gpio_rpi_data *data = port->data; + /* First handle push-pull pins: */ + gpio_put_masked(mask & ~data->single_ended_mask, value); + /* Then handle open-drain pins: */ + gpio_set_dir_masked(mask & data->single_ended_mask & data->open_drain_mask, ~value); + /* Then handle open-source pins: */ + gpio_set_dir_masked(mask & data->single_ended_mask & ~data->open_drain_mask, value); return 0; } static int gpio_rpi_port_set_bits_raw(const struct device *port, uint32_t pins) { - gpio_set_mask(pins); + struct gpio_rpi_data *data = port->data; + /* First handle push-pull pins: */ + gpio_set_mask(pins & ~data->single_ended_mask); + /* Then handle open-drain pins: */ + gpio_set_dir_in_masked(pins & data->single_ended_mask & data->open_drain_mask); + /* Then handle open-source pins: */ + gpio_set_dir_out_masked(pins & data->single_ended_mask & ~data->open_drain_mask); return 0; } static int gpio_rpi_port_clear_bits_raw(const struct device *port, uint32_t pins) { - gpio_clr_mask(pins); + struct gpio_rpi_data *data = port->data; + /* First handle push-pull pins: */ + gpio_clr_mask(pins & ~data->single_ended_mask); + /* Then handle open-drain pins: */ + gpio_set_dir_out_masked(pins & data->single_ended_mask & data->open_drain_mask); + /* Then handle open-source pins: */ + gpio_set_dir_in_masked(pins & data->single_ended_mask & ~data->open_drain_mask); return 0; } static int gpio_rpi_port_toggle_bits(const struct device *port, uint32_t pins) { - gpio_xor_mask(pins); + struct gpio_rpi_data *data = port->data; + /* First handle push-pull pins: */ + gpio_xor_mask(pins & ~data->single_ended_mask); + /* Then handle single-ended pins: */ + /* (unfortunately there's no pico-sdk api call that can be used for this, + * but it's possible by accessing the registers directly) + */ + sio_hw->gpio_oe_togl = (pins & data->single_ended_mask); return 0; }