[PATCH 1/2] gpio: shared-proxy: track direction instead of reading it back

From: Shawn Guo

Date: Mon Sep 14 2026 - 21:45:11 EST


On boards where more than one consumer shares a Qualcomm SPMI PMIC GPIO,
all but the first consumer fail to configure the line as output:

reg-fixed-voltage regulator-wcn-core-vm-1p35: setup of GPIO (default) failed: -1
reg-fixed-voltage regulator-wcn-core-vm-1p35: error -EPERM: can't get GPIO

The first consumer takes the usecnt == 1 branch and really does set the
underlying line to output. Later consumers instead read the direction
back with gpiod_get_direction() and refuse with -EPERM if it reports
input. pmic_gpio_get_direction() derives the direction from the pad's
input buffer (pad->input_enabled), which pmic_gpio_direction_output()
never clears, so a pad whose input buffer is enabled at power-up reports
input forever and the proxy rejects every consumer after the first.

The readback is not needed: the proxy is the only entity configuring the
line, so it already knows which direction it asked for. Record that in
struct gpio_shared_desc and compare against the recorded value. Keep a
readback in the get_direction() callback for the case where no proxy has
configured the line yet, and reset the recorded direction once the last
user goes away so that the next requester establishes it again.

Assisted-by: LLM
Fixes: e992d54c6f97 ("gpio: shared-proxy: implement the shared GPIO proxy driver")
Signed-off-by: Shawn Guo <shengchao.guo@xxxxxxxxxxxxxxxx>
---
drivers/gpio/gpio-shared-proxy.c | 43 +++++++++++++++++++-------------
drivers/gpio/gpiolib-shared.c | 1 +
drivers/gpio/gpiolib-shared.h | 1 +
3 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
index bc69b8729d19..a8f0d08c6d9f 100644
--- a/drivers/gpio/gpio-shared-proxy.c
+++ b/drivers/gpio/gpio-shared-proxy.c
@@ -116,6 +116,8 @@ static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)
}

proxy->shared_desc->usecnt--;
+ if (!shared_desc->usecnt)
+ shared_desc->dir = -1;

dev_dbg(proxy->dev, "Shared GPIO freed, number of users: %u\n",
proxy->shared_desc->usecnt);
@@ -155,22 +157,24 @@ static int gpio_shared_proxy_direction_input(struct gpio_chip *gc,
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
struct gpio_desc *desc = shared_desc->desc;
- int dir;
+ int ret;

guard(mutex)(&shared_desc->mutex);

- if (shared_desc->usecnt == 1) {
+ if (shared_desc->usecnt == 1 || shared_desc->dir < 0) {
dev_dbg(proxy->dev,
- "Only one user of this shared GPIO, allowing to set direction to input\n");
+ "Setting the direction of the shared GPIO to input\n");

- return gpiod_direction_input(desc);
- }
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ return ret;

- dir = gpiod_get_direction(desc);
- if (dir < 0)
- return dir;
+ shared_desc->dir = GPIO_LINE_DIRECTION_IN;
+
+ return 0;
+ }

- if (dir == GPIO_LINE_DIRECTION_OUT) {
+ if (shared_desc->dir == GPIO_LINE_DIRECTION_OUT) {
dev_dbg(proxy->dev,
"Shared GPIO's direction already set to output, refusing to change\n");
return -EPERM;
@@ -185,19 +189,20 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
struct gpio_desc *desc = shared_desc->desc;
- int ret, dir;
+ int ret;

guard(mutex)(&shared_desc->mutex);

- if (shared_desc->usecnt == 1) {
+ if (shared_desc->usecnt == 1 || shared_desc->dir < 0) {
dev_dbg(proxy->dev,
- "Only one user of this shared GPIO, allowing to set direction to output with value '%s'\n",
+ "Setting the direction of the shared GPIO to output with value '%s'\n",
str_high_low(value));

ret = gpiod_direction_output(desc, value);
if (ret)
return ret;

+ shared_desc->dir = GPIO_LINE_DIRECTION_OUT;
shared_desc->def_val = value;
shared_desc->votecnt = 0;
proxy->voted_change = false;
@@ -205,11 +210,7 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
return 0;
}

- dir = gpiod_get_direction(desc);
- if (dir < 0)
- return dir;
-
- if (dir == GPIO_LINE_DIRECTION_IN) {
+ if (shared_desc->dir == GPIO_LINE_DIRECTION_IN) {
dev_dbg(proxy->dev,
"Shared GPIO's direction already set to input, refusing to change\n");
return -EPERM;
@@ -240,8 +241,14 @@ static int gpio_shared_proxy_get_direction(struct gpio_chip *gc,
unsigned int offset)
{
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
+ struct gpio_shared_desc *shared_desc = proxy->shared_desc;
+
+ guard(mutex)(&shared_desc->mutex);
+
+ if (shared_desc->dir < 0)
+ return gpiod_get_direction(shared_desc->desc);

- return gpiod_get_direction(proxy->shared_desc->desc);
+ return shared_desc->dir;
}

static int gpio_shared_proxy_to_irq(struct gpio_chip *gc, unsigned int offset)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 5f9623e40b0f..8267b5089244 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -661,6 +661,7 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry)
}

shared_desc->desc = &gdev->descs[entry->offset];
+ shared_desc->dir = -1;
mutex_init(&shared_desc->mutex);

return shared_desc;
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index 618756f6c6aa..0ef3bc7a7916 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -43,6 +43,7 @@ struct gpio_shared_desc {
unsigned int usecnt;
unsigned int votecnt;
int def_val;
+ int dir; /* GPIO_LINE_DIRECTION_* as configured by the proxies, -1 if unset */
struct mutex mutex; /* serializes all proxy operations on this descriptor */
};

--
2.43.0