Skip to content

Commit c22f7a5

Browse files
committed
gpio: improve support for shared GPIOs
Merge series from Bartosz Golaszewski <brgl@bgdev.pl>: Problem statement: GPIOs are implemented as a strictly exclusive resource in the kernel but there are lots of platforms on which single pin is shared by multiple devices which don't communicate so need some way of properly sharing access to a GPIO. What we have now is the GPIOD_FLAGS_BIT_NONEXCLUSIVE flag which was introduced as a hack and doesn't do any locking or arbitration of access - it literally just hand the same GPIO descriptor to all interested users. The proposed solution is composed of three major parts: the high-level, shared GPIO proxy driver that arbitrates access to the shared pin and exposes a regular GPIO chip interface to consumers, a low-level shared GPIOLIB module that scans firmware nodes and creates auxiliary devices that attach to the proxy driver and finally a set of core GPIOLIB changes that plug the former into the GPIO lookup path. The changes are implemented in a way that allows to seamlessly compile out any code related to sharing GPIOs for systems that don't need it. The practical use-case for this are the powerdown GPIOs shared by speakers on Qualcomm db845c platform, however I have also extensively tested it using gpio-virtuser on arm64 qemu with various DT configurations.
2 parents 21e68bc + b871d9a commit c22f7a5

11 files changed

Lines changed: 1092 additions & 9 deletions

File tree

drivers/gpio/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
config GPIOLIB_LEGACY
77
def_bool y
88

9+
config HAVE_SHARED_GPIOS
10+
bool
11+
912
menuconfig GPIOLIB
1013
bool "GPIO Support"
1114
help
@@ -50,6 +53,11 @@ config OF_GPIO_MM_GPIOCHIP
5053
this symbol, but new drivers should use the generic gpio-regmap
5154
infrastructure instead.
5255

56+
config GPIO_SHARED
57+
def_bool y
58+
depends on HAVE_SHARED_GPIOS || COMPILE_TEST
59+
select AUXILIARY_BUS
60+
5361
config DEBUG_GPIO
5462
bool "Debug GPIO calls"
5563
depends on DEBUG_KERNEL
@@ -2017,6 +2025,15 @@ config GPIO_SIM
20172025
This enables the GPIO simulator - a configfs-based GPIO testing
20182026
driver.
20192027

2028+
config GPIO_SHARED_PROXY
2029+
tristate "Proxy driver for non-exclusive GPIOs"
2030+
default m
2031+
depends on GPIO_SHARED || COMPILE_TEST
2032+
select AUXILIARY_BUS
2033+
help
2034+
This enables the GPIO shared proxy driver - an abstraction layer
2035+
for GPIO pins that are shared by multiple devices.
2036+
20202037
endmenu
20212038

20222039
menu "GPIO Debugging utilities"

drivers/gpio/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o
1212
obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o
1313
gpiolib-acpi-y := gpiolib-acpi-core.o gpiolib-acpi-quirks.o
1414
obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o
15+
obj-$(CONFIG_GPIO_SHARED) += gpiolib-shared.o
1516

1617
# Device drivers. Generally keep list sorted alphabetically
1718
obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o
@@ -159,6 +160,7 @@ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
159160
obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
160161
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
161162
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
163+
obj-$(CONFIG_GPIO_SHARED_PROXY) += gpio-shared-proxy.o
162164
obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
163165
obj-$(CONFIG_GPIO_SIM) += gpio-sim.o
164166
obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o

drivers/gpio/gpio-shared-proxy.c

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (C) 2025 Linaro Ltd.
4+
*/
5+
6+
#include <linux/auxiliary_bus.h>
7+
#include <linux/cleanup.h>
8+
#include <linux/device.h>
9+
#include <linux/err.h>
10+
#include <linux/gpio/consumer.h>
11+
#include <linux/gpio/driver.h>
12+
#include <linux/mod_devicetable.h>
13+
#include <linux/module.h>
14+
#include <linux/string_choices.h>
15+
#include <linux/types.h>
16+
17+
#include "gpiolib-shared.h"
18+
19+
struct gpio_shared_proxy_data {
20+
struct gpio_chip gc;
21+
struct gpio_shared_desc *shared_desc;
22+
struct device *dev;
23+
bool voted_high;
24+
};
25+
26+
static int
27+
gpio_shared_proxy_set_unlocked(struct gpio_shared_proxy_data *proxy,
28+
int (*set_func)(struct gpio_desc *desc, int value),
29+
int value)
30+
{
31+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
32+
struct gpio_desc *desc = shared_desc->desc;
33+
int ret = 0;
34+
35+
gpio_shared_lockdep_assert(shared_desc);
36+
37+
if (value) {
38+
/* User wants to set value to high. */
39+
if (proxy->voted_high)
40+
/* Already voted for high, nothing to do. */
41+
goto out;
42+
43+
/* Haven't voted for high yet. */
44+
if (!shared_desc->highcnt) {
45+
/*
46+
* Current value is low, need to actually set value
47+
* to high.
48+
*/
49+
ret = set_func(desc, 1);
50+
if (ret)
51+
goto out;
52+
}
53+
54+
shared_desc->highcnt++;
55+
proxy->voted_high = true;
56+
57+
goto out;
58+
}
59+
60+
/* Desired value is low. */
61+
if (!proxy->voted_high)
62+
/* We didn't vote for high, nothing to do. */
63+
goto out;
64+
65+
/* We previously voted for high. */
66+
if (shared_desc->highcnt == 1) {
67+
/* This is the last remaining vote for high, set value to low. */
68+
ret = set_func(desc, 0);
69+
if (ret)
70+
goto out;
71+
}
72+
73+
shared_desc->highcnt--;
74+
proxy->voted_high = false;
75+
76+
out:
77+
if (shared_desc->highcnt)
78+
dev_dbg(proxy->dev,
79+
"Voted for value '%s', effective value is 'high', number of votes for 'high': %u\n",
80+
str_high_low(value), shared_desc->highcnt);
81+
else
82+
dev_dbg(proxy->dev, "Voted for value 'low', effective value is 'low'\n");
83+
84+
return ret;
85+
}
86+
87+
static int gpio_shared_proxy_request(struct gpio_chip *gc, unsigned int offset)
88+
{
89+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
90+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
91+
92+
guard(gpio_shared_desc_lock)(shared_desc);
93+
94+
proxy->shared_desc->usecnt++;
95+
96+
dev_dbg(proxy->dev, "Shared GPIO requested, number of users: %u\n",
97+
proxy->shared_desc->usecnt);
98+
99+
return 0;
100+
}
101+
102+
static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)
103+
{
104+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
105+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
106+
107+
guard(gpio_shared_desc_lock)(shared_desc);
108+
109+
proxy->shared_desc->usecnt--;
110+
111+
dev_dbg(proxy->dev, "Shared GPIO freed, number of users: %u\n",
112+
proxy->shared_desc->usecnt);
113+
}
114+
115+
static int gpio_shared_proxy_set_config(struct gpio_chip *gc,
116+
unsigned int offset, unsigned long cfg)
117+
{
118+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
119+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
120+
struct gpio_desc *desc = shared_desc->desc;
121+
int ret;
122+
123+
guard(gpio_shared_desc_lock)(shared_desc);
124+
125+
if (shared_desc->usecnt > 1) {
126+
if (shared_desc->cfg != cfg) {
127+
dev_dbg(proxy->dev,
128+
"Shared GPIO's configuration already set, accepting changes but users may conflict!!\n");
129+
} else {
130+
dev_dbg(proxy->dev, "Equal config requested, nothing to do\n");
131+
return 0;
132+
}
133+
}
134+
135+
ret = gpiod_set_config(desc, cfg);
136+
if (ret && ret != -ENOTSUPP)
137+
return ret;
138+
139+
shared_desc->cfg = cfg;
140+
return 0;
141+
}
142+
143+
static int gpio_shared_proxy_direction_input(struct gpio_chip *gc,
144+
unsigned int offset)
145+
{
146+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
147+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
148+
struct gpio_desc *desc = shared_desc->desc;
149+
int dir;
150+
151+
guard(gpio_shared_desc_lock)(shared_desc);
152+
153+
if (shared_desc->usecnt == 1) {
154+
dev_dbg(proxy->dev,
155+
"Only one user of this shared GPIO, allowing to set direction to input\n");
156+
157+
return gpiod_direction_input(desc);
158+
}
159+
160+
dir = gpiod_get_direction(desc);
161+
if (dir < 0)
162+
return dir;
163+
164+
if (dir == GPIO_LINE_DIRECTION_OUT) {
165+
dev_dbg(proxy->dev,
166+
"Shared GPIO's direction already set to output, refusing to change\n");
167+
return -EPERM;
168+
}
169+
170+
return 0;
171+
}
172+
173+
static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
174+
unsigned int offset, int value)
175+
{
176+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
177+
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
178+
struct gpio_desc *desc = shared_desc->desc;
179+
int ret, dir;
180+
181+
guard(gpio_shared_desc_lock)(shared_desc);
182+
183+
if (shared_desc->usecnt == 1) {
184+
dev_dbg(proxy->dev,
185+
"Only one user of this shared GPIO, allowing to set direction to output with value '%s'\n",
186+
str_high_low(value));
187+
188+
ret = gpiod_direction_output(desc, value);
189+
if (ret)
190+
return ret;
191+
192+
if (value) {
193+
proxy->voted_high = true;
194+
shared_desc->highcnt = 1;
195+
} else {
196+
proxy->voted_high = false;
197+
shared_desc->highcnt = 0;
198+
}
199+
200+
return 0;
201+
}
202+
203+
dir = gpiod_get_direction(desc);
204+
if (dir < 0)
205+
return dir;
206+
207+
if (dir == GPIO_LINE_DIRECTION_IN) {
208+
dev_dbg(proxy->dev,
209+
"Shared GPIO's direction already set to input, refusing to change\n");
210+
return -EPERM;
211+
}
212+
213+
return gpio_shared_proxy_set_unlocked(proxy, gpiod_direction_output, value);
214+
}
215+
216+
static int gpio_shared_proxy_get(struct gpio_chip *gc, unsigned int offset)
217+
{
218+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
219+
220+
return gpiod_get_value(proxy->shared_desc->desc);
221+
}
222+
223+
static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
224+
unsigned int offset)
225+
{
226+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
227+
228+
return gpiod_get_value_cansleep(proxy->shared_desc->desc);
229+
}
230+
231+
static int gpio_shared_proxy_do_set(struct gpio_shared_proxy_data *proxy,
232+
int (*set_func)(struct gpio_desc *desc, int value),
233+
int value)
234+
{
235+
guard(gpio_shared_desc_lock)(proxy->shared_desc);
236+
237+
return gpio_shared_proxy_set_unlocked(proxy, set_func, value);
238+
}
239+
240+
static int gpio_shared_proxy_set(struct gpio_chip *gc, unsigned int offset,
241+
int value)
242+
{
243+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
244+
245+
return gpio_shared_proxy_do_set(proxy, gpiod_set_value, value);
246+
}
247+
248+
static int gpio_shared_proxy_set_cansleep(struct gpio_chip *gc,
249+
unsigned int offset, int value)
250+
{
251+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
252+
253+
return gpio_shared_proxy_do_set(proxy, gpiod_set_value_cansleep, value);
254+
}
255+
256+
static int gpio_shared_proxy_get_direction(struct gpio_chip *gc,
257+
unsigned int offset)
258+
{
259+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
260+
261+
return gpiod_get_direction(proxy->shared_desc->desc);
262+
}
263+
264+
static int gpio_shared_proxy_to_irq(struct gpio_chip *gc, unsigned int offset)
265+
{
266+
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
267+
268+
return gpiod_to_irq(proxy->shared_desc->desc);
269+
}
270+
271+
static int gpio_shared_proxy_probe(struct auxiliary_device *adev,
272+
const struct auxiliary_device_id *id)
273+
{
274+
struct gpio_shared_proxy_data *proxy;
275+
struct gpio_shared_desc *shared_desc;
276+
struct device *dev = &adev->dev;
277+
struct gpio_chip *gc;
278+
279+
shared_desc = devm_gpiod_shared_get(dev);
280+
if (IS_ERR(shared_desc))
281+
return PTR_ERR(shared_desc);
282+
283+
proxy = devm_kzalloc(dev, sizeof(*proxy), GFP_KERNEL);
284+
if (!proxy)
285+
return -ENOMEM;
286+
287+
proxy->shared_desc = shared_desc;
288+
proxy->dev = dev;
289+
290+
gc = &proxy->gc;
291+
gc->base = -1;
292+
gc->ngpio = 1;
293+
gc->label = dev_name(dev);
294+
gc->parent = dev;
295+
gc->owner = THIS_MODULE;
296+
gc->can_sleep = shared_desc->can_sleep;
297+
298+
gc->request = gpio_shared_proxy_request;
299+
gc->free = gpio_shared_proxy_free;
300+
gc->set_config = gpio_shared_proxy_set_config;
301+
gc->direction_input = gpio_shared_proxy_direction_input;
302+
gc->direction_output = gpio_shared_proxy_direction_output;
303+
if (gc->can_sleep) {
304+
gc->set = gpio_shared_proxy_set_cansleep;
305+
gc->get = gpio_shared_proxy_get_cansleep;
306+
} else {
307+
gc->set = gpio_shared_proxy_set;
308+
gc->get = gpio_shared_proxy_get;
309+
}
310+
gc->get_direction = gpio_shared_proxy_get_direction;
311+
gc->to_irq = gpio_shared_proxy_to_irq;
312+
313+
return devm_gpiochip_add_data(dev, &proxy->gc, proxy);
314+
}
315+
316+
static const struct auxiliary_device_id gpio_shared_proxy_id_table[] = {
317+
{ .name = "gpiolib_shared.proxy" },
318+
{},
319+
};
320+
MODULE_DEVICE_TABLE(auxiliary, gpio_shared_proxy_id_table);
321+
322+
static struct auxiliary_driver gpio_shared_proxy_driver = {
323+
.driver = {
324+
.name = "gpio-shared-proxy",
325+
},
326+
.probe = gpio_shared_proxy_probe,
327+
.id_table = gpio_shared_proxy_id_table,
328+
};
329+
module_auxiliary_driver(gpio_shared_proxy_driver);
330+
331+
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
332+
MODULE_DESCRIPTION("Shared GPIO mux driver.");
333+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)