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 <andrei@purdea.ro>
This commit is contained in:
Purdea Andrei 2023-01-22 06:23:26 +02:00 committed by Christopher Friedt
parent eead89e7f2
commit 38f554ef4f

View File

@ -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;
}