Add a new driver which provides a 1-to-many mapping for a single real
GPIO using a multiplexer. Each virtual GPIO corresponds to a multiplexer
state which, if set for the multiplexer, connects the real GPIO to the
corresponding virtual GPIO.
For now, this doesn't support advanced features like IRQs, just normal
IN and OUT functionality of GPIOs.
This can help in various usecases. One practical case is the special
hardware design of the Realtek-based XS1930-10 switch from Zyxel. It
features two SFP+ ports/cages whose signals are wired to directly to the
switch SoC. Although Realtek SoCs are short on GPIOs, there are usually
enough the fit the SFP signals without any hacks.
However, Zyxel did some weird design and connected RX_LOS, MOD_ABS and
TX_FAULT of one SFP cage onto a single GPIO line controlled by a
multiplexer (the same for the other SFP cage). The single multiplexer
controls the lines for both SFP and depending on the state, the
designated 'signal GPIO lines' are connected to one of the three SFP
signals.
Because the SFP core/driver doesn't support multiplexer but needs single
GPIOs for each of the signals, this driver fills the gap between both.
It registers a gpio_chip, provides multiple virtual GPIOs and sets the
backing multiplexer accordingly.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
---
MAINTAINERS | 6 ++
drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-line-mux.c | 194 +++++++++++++++++++++++++++++++++++
4 files changed, 211 insertions(+)
create mode 100644 drivers/gpio/gpio-line-mux.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 46126ce2f968..4d75253fe451 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10647,6 +10647,12 @@ S: Maintained
F: Documentation/devicetree/bindings/leds/irled/gpio-ir-tx.yaml
F: drivers/media/rc/gpio-ir-tx.c
+GPIO LINE MUX
+M: Jonas Jelonek <jelonek.jonas@gmail.com>
+S: Maintained
+F: Documentation/devicetree/bindings/gpio/gpio-line-mux.yaml
+F: drivers/gpio/gpio-line-mux.c
+
GPIO MOCKUP DRIVER
M: Bamvor Jian Zhang <bamv2005@gmail.com>
L: linux-gpio@vger.kernel.org
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index ce237398fa00..93695b86a955 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1986,6 +1986,16 @@ config GPIO_LATCH
Say yes here to enable a driver for GPIO multiplexers based on latches
connected to other GPIOs.
+config GPIO_LINE_MUX
+ tristate "GPIO line mux driver"
+ depends on OF_GPIO
+ select GPIO_AGGREGATOR
+ select MULTIPLEXER
+ help
+ Say Y here to support the GPIO line mux, which can provide virtual
+ GPIOs backed by a shared real GPIO and a multiplexer in a 1-to-many
+ fashion.
+
config GPIO_MOCKUP
tristate "GPIO Testing Driver (DEPRECATED)"
select IRQ_SIM
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index ee260a0809d3..6caee52b0356 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o
obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o
obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o
+obj-$(CONFIG_GPIO_LINE_MUX) += gpio-line-mux.o
obj-$(CONFIG_GPIO_LJCA) += gpio-ljca.o
obj-$(CONFIG_GPIO_LOGICVC) += gpio-logicvc.o
obj-$(CONFIG_GPIO_LOONGSON1) += gpio-loongson1.o
diff --git a/drivers/gpio/gpio-line-mux.c b/drivers/gpio/gpio-line-mux.c
new file mode 100644
index 000000000000..a367e8f585c6
--- /dev/null
+++ b/drivers/gpio/gpio-line-mux.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO line mux which acts as virtual gpiochip and provides a 1-to-many
+ * mapping between virtual GPIOs and a real GPIO + multiplexer.
+ *
+ * Copyright (c) 2025 Jonas Jelonek <jelonek.jonas@gmail.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/mux/consumer.h>
+#include <linux/mux/driver.h>
+#include <linux/platform_device.h>
+
+struct gpio_lmux {
+ struct gpio_chip gc;
+ struct mux_control *mux;
+ struct device *dev;
+
+ struct mutex lock;
+
+ struct gpio_desc *shared_gpio;
+ /* dynamically sized, must be last */
+ unsigned int gpio_mux_states[];
+};
+
+DEFINE_GUARD(gpio_lmux, struct gpio_lmux *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
+
+static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
+ int ret;
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ guard(gpio_lmux)(glm);
+
+ ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
+ if (ret < 0)
+ return ret;
+
+ ret = gpiod_get_raw_value_cansleep(glm->shared_gpio);
+ mux_control_deselect(glm->mux);
+ return ret;
+}
+
+static int gpio_lmux_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
+ int ret;
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ guard(gpio_lmux)(glm);
+
+ ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
+ if (ret < 0)
+ return ret;
+
+ gpiod_set_raw_value_cansleep(glm->shared_gpio, value);
+ mux_control_deselect(glm->mux);
+ return 0;
+}
+
+static int gpio_lmux_gpio_get_direction(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ guard(gpio_lmux)(glm);
+
+ return gpiod_get_direction(glm->shared_gpio);
+}
+
+static int gpio_lmux_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ guard(gpio_lmux)(glm);
+
+ return gpiod_direction_input(glm->shared_gpio);
+}
+
+static int gpio_lmux_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ guard(gpio_lmux)(glm);
+
+ return gpiod_direction_output_raw(glm->shared_gpio, value);
+}
+
+static int gpio_lmux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_lmux *glm;
+ unsigned int ngpio, size;
+ int ret;
+
+ ngpio = device_property_count_u32(dev, "gpio-line-mux-states");
+ if (!ngpio)
+ return -EINVAL;
+
+ size = sizeof(*glm) + (sizeof(unsigned int) * ngpio);
+ glm = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!glm)
+ return -ENOMEM;
+
+ mutex_init(&glm->lock);
+
+ glm->dev = dev;
+ glm->gc.base = -1;
+ glm->gc.can_sleep = true;
+ glm->gc.fwnode = dev_fwnode(dev);
+ glm->gc.label = "gpio-line-mux";
+ glm->gc.ngpio = ngpio;
+ glm->gc.owner = THIS_MODULE;
+ glm->gc.parent = dev;
+
+ glm->gc.get = gpio_lmux_gpio_get;
+ glm->gc.set = gpio_lmux_gpio_set;
+ glm->gc.get_direction = gpio_lmux_gpio_get_direction;
+ glm->gc.direction_input = gpio_lmux_gpio_direction_input;
+ glm->gc.direction_output = gpio_lmux_gpio_direction_output;
+
+ glm->mux = devm_mux_control_get(dev, NULL);
+ if (IS_ERR(glm->mux)) {
+ if (PTR_ERR(glm->mux) == -EPROBE_DEFER) {
+ dev_err(dev, "mux-controller not ready, deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_err(dev, "could not get mux-controller\n");
+ return PTR_ERR(glm->mux);
+ }
+
+ glm->shared_gpio = devm_gpiod_get(dev, "shared", GPIOD_ASIS);
+ if (IS_ERR(glm->shared_gpio)) {
+ dev_err(dev, "could not get shared-gpio\n");
+ return PTR_ERR(glm->shared_gpio);
+ }
+
+ ret = device_property_read_u32_array(dev, "gpio-line-mux-states",
+ &glm->gpio_mux_states[0], ngpio);
+ if (ret) {
+ dev_err(dev, "could not get mux states\n");
+ return ret;
+ }
+
+ ret = devm_gpiochip_add_data(dev, &glm->gc, glm);
+ if (ret) {
+ dev_err(dev, "failed to add gpiochip: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "providing %u virtual GPIOs for real GPIO %u\n", ngpio,
+ desc_to_gpio(glm->shared_gpio));
+ return 0;
+}
+
+static const struct of_device_id gpio_lmux_of_match[] = {
+ { .compatible = "gpio-line-mux" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_lmux_of_match);
+
+static struct platform_driver gpio_lmux_driver = {
+ .driver = {
+ .name = "gpio-line-mux",
+ .of_match_table = gpio_lmux_of_match,
+ },
+ .probe = gpio_lmux_probe,
+};
+module_platform_driver(gpio_lmux_driver);
+
+MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
+MODULE_DESCRIPTION("GPIO line mux driver");
+MODULE_LICENSE("GPL");
--
2.48.1
Hi Jonas,
Linus mentioned gpio forwarder in the previous iteration, this caught my
attention. So I had a look to your series.
On 10/27/25 12:17 AM, Jonas Jelonek wrote:
> Add a new driver which provides a 1-to-many mapping for a single real
> GPIO using a multiplexer. Each virtual GPIO corresponds to a multiplexer
> state which, if set for the multiplexer, connects the real GPIO to the
> corresponding virtual GPIO.
>
> For now, this doesn't support advanced features like IRQs, just normal
> IN and OUT functionality of GPIOs.
>
> This can help in various usecases. One practical case is the special
> hardware design of the Realtek-based XS1930-10 switch from Zyxel. It
> features two SFP+ ports/cages whose signals are wired to directly to the
> switch SoC. Although Realtek SoCs are short on GPIOs, there are usually
> enough the fit the SFP signals without any hacks.
>
> However, Zyxel did some weird design and connected RX_LOS, MOD_ABS and
> TX_FAULT of one SFP cage onto a single GPIO line controlled by a
> multiplexer (the same for the other SFP cage). The single multiplexer
> controls the lines for both SFP and depending on the state, the
> designated 'signal GPIO lines' are connected to one of the three SFP
> signals.
>
> Because the SFP core/driver doesn't support multiplexer but needs single
> GPIOs for each of the signals, this driver fills the gap between both.
> It registers a gpio_chip, provides multiple virtual GPIOs and sets the
> backing multiplexer accordingly.
>
> Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
> ---
> MAINTAINERS | 6 ++
> drivers/gpio/Kconfig | 10 ++
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-line-mux.c | 194 +++++++++++++++++++++++++++++++++++
> 4 files changed, 211 insertions(+)
> create mode 100644 drivers/gpio/gpio-line-mux.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 46126ce2f968..4d75253fe451 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10647,6 +10647,12 @@ S: Maintained
> F: Documentation/devicetree/bindings/leds/irled/gpio-ir-tx.yaml
> F: drivers/media/rc/gpio-ir-tx.c
>
> +GPIO LINE MUX
> +M: Jonas Jelonek <jelonek.jonas@gmail.com>
> +S: Maintained
> +F: Documentation/devicetree/bindings/gpio/gpio-line-mux.yaml
> +F: drivers/gpio/gpio-line-mux.c
> +
> GPIO MOCKUP DRIVER
> M: Bamvor Jian Zhang <bamv2005@gmail.com>
> L: linux-gpio@vger.kernel.org
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index ce237398fa00..93695b86a955 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1986,6 +1986,16 @@ config GPIO_LATCH
> Say yes here to enable a driver for GPIO multiplexers based on latches
> connected to other GPIOs.
>
> +config GPIO_LINE_MUX
> + tristate "GPIO line mux driver"
> + depends on OF_GPIO
> + select GPIO_AGGREGATOR
You don't need GPIO_AGGREGATOR.
> + select MULTIPLEXER
> + help
> + Say Y here to support the GPIO line mux, which can provide virtual
> + GPIOs backed by a shared real GPIO and a multiplexer in a 1-to-many
> + fashion.
> +
> config GPIO_MOCKUP
> tristate "GPIO Testing Driver (DEPRECATED)"
> select IRQ_SIM
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index ee260a0809d3..6caee52b0356 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -89,6 +89,7 @@ obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o
> obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
> obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o
> obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o
> +obj-$(CONFIG_GPIO_LINE_MUX) += gpio-line-mux.o
> obj-$(CONFIG_GPIO_LJCA) += gpio-ljca.o
> obj-$(CONFIG_GPIO_LOGICVC) += gpio-logicvc.o
> obj-$(CONFIG_GPIO_LOONGSON1) += gpio-loongson1.o
> diff --git a/drivers/gpio/gpio-line-mux.c b/drivers/gpio/gpio-line-mux.c
> new file mode 100644
> index 000000000000..a367e8f585c6
> --- /dev/null
> +++ b/drivers/gpio/gpio-line-mux.c
> @@ -0,0 +1,194 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * GPIO line mux which acts as virtual gpiochip and provides a 1-to-many
> + * mapping between virtual GPIOs and a real GPIO + multiplexer.
> + *
> + * Copyright (c) 2025 Jonas Jelonek <jelonek.jonas@gmail.com>
> + */
> +
> +#include <linux/gpio/consumer.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/mux/consumer.h>
> +#include <linux/mux/driver.h>
> +#include <linux/platform_device.h>
> +
> +struct gpio_lmux {
> + struct gpio_chip gc;
> + struct mux_control *mux;
> + struct device *dev;
not used
> +
> + struct mutex lock;
> +
> + struct gpio_desc *shared_gpio;
> + /* dynamically sized, must be last */
> + unsigned int gpio_mux_states[];
> +};
> +
> +DEFINE_GUARD(gpio_lmux, struct gpio_lmux *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
> +
> +static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
> + int ret;
> +
> + if (offset > gc->ngpio)
> + return -EINVAL;
> +
> + guard(gpio_lmux)(glm);
> +
> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
> + if (ret < 0)
> + return ret;
> +
> + ret = gpiod_get_raw_value_cansleep(glm->shared_gpio);
Why ignoring ACTIVE_LOW status ?
And cansleep depends on your shared_gpio line, maybe it is not the case.
> + mux_control_deselect(glm->mux);
> + return ret;
> +}
> +
[...]
> +
> +static int gpio_lmux_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct gpio_lmux *glm;
> + unsigned int ngpio, size;
> + int ret;
nitpick: reverse xmas tree
> +
> + ngpio = device_property_count_u32(dev, "gpio-line-mux-states");
> + if (!ngpio)
> + return -EINVAL;
> +
> + size = sizeof(*glm) + (sizeof(unsigned int) * ngpio);
use struct_size() macro
> + glm = devm_kzalloc(dev, size, GFP_KERNEL);
> + if (!glm)
> + return -ENOMEM;
> +
> + mutex_init(&glm->lock);
> +
> + glm->dev = dev;
> + glm->gc.base = -1;
> + glm->gc.can_sleep = true;
depends on your shared_gpio line. Use gpiod_cansleep() like in the
gpio-aggregator driver to know if your shared_gpio can sleep.
> + glm->gc.fwnode = dev_fwnode(dev);
> + glm->gc.label = "gpio-line-mux";
dev_name() ?
> + glm->gc.ngpio = ngpio;
> + glm->gc.owner = THIS_MODULE;
> + glm->gc.parent = dev;
> +
> + glm->gc.get = gpio_lmux_gpio_get;
> + glm->gc.set = gpio_lmux_gpio_set;
> + glm->gc.get_direction = gpio_lmux_gpio_get_direction;
> + glm->gc.direction_input = gpio_lmux_gpio_direction_input;
> + glm->gc.direction_output = gpio_lmux_gpio_direction_output;
> +
> + glm->mux = devm_mux_control_get(dev, NULL);
> + if (IS_ERR(glm->mux)) {
> + if (PTR_ERR(glm->mux) == -EPROBE_DEFER) {
> + dev_err(dev, "mux-controller not ready, deferring probe\n");
> + return -EPROBE_DEFER;
> + }
> +
> + dev_err(dev, "could not get mux-controller\n");
> + return PTR_ERR(glm->mux);
> + }
You can replace the if statement by:
if (IS_ERR(glm->mux))
return dev_err_probe(dev, PTR_ERR(glm->mux), "could not ...");
> +
> + glm->shared_gpio = devm_gpiod_get(dev, "shared", GPIOD_ASIS);
> + if (IS_ERR(glm->shared_gpio)) {
> + dev_err(dev, "could not get shared-gpio\n");
> + return PTR_ERR(glm->shared_gpio);
> + }
ditto
> +
> + ret = device_property_read_u32_array(dev, "gpio-line-mux-states",
> + &glm->gpio_mux_states[0], ngpio);
> + if (ret) {
> + dev_err(dev, "could not get mux states\n");
> + return ret;
> + }
ditto
> +
> + ret = devm_gpiochip_add_data(dev, &glm->gc, glm);
> + if (ret) {
> + dev_err(dev, "failed to add gpiochip: %d\n", ret);
> + return ret;
> + }
ditto
> +
> + dev_info(dev, "providing %u virtual GPIOs for real GPIO %u\n", ngpio,
> + desc_to_gpio(glm->shared_gpio));
No logs if device probes successfully
> + return 0;
> +}
> +
> +static const struct of_device_id gpio_lmux_of_match[] = {
> + { .compatible = "gpio-line-mux" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, gpio_lmux_of_match);
> +
> +static struct platform_driver gpio_lmux_driver = {
> + .driver = {
> + .name = "gpio-line-mux",
> + .of_match_table = gpio_lmux_of_match,
> + },
> + .probe = gpio_lmux_probe,
> +};
> +module_platform_driver(gpio_lmux_driver);
> +
> +MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
> +MODULE_DESCRIPTION("GPIO line mux driver");
> +MODULE_LICENSE("GPL");
The advantage of the forwarder is that it handles if the shared GPIO is
sleeping or not.
But I think the forwarder shall have ngpio, not 1. You will have to add
ngpio times the same GPIO desc. Also unsupported operations shall be unset.
So I don't really know if it shall be used in this case.
Best Regards,
Thomas
Hi Thomas,
On 28.10.25 10:45, Thomas Richard wrote:
> On 10/27/25 12:17 AM, Jonas Jelonek wrote:
>> +
>> + struct mutex lock;
>> +
>> + struct gpio_desc *shared_gpio;
>> + /* dynamically sized, must be last */
>> + unsigned int gpio_mux_states[];
>> +};
>> +
>> +DEFINE_GUARD(gpio_lmux, struct gpio_lmux *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
>> +
>> +static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
>> +{
>> + struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
>> + int ret;
>> +
>> + if (offset > gc->ngpio)
>> + return -EINVAL;
>> +
>> + guard(gpio_lmux)(glm);
>> +
>> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
>> + if (ret < 0)
>> + return ret;
>> +
>> + ret = gpiod_get_raw_value_cansleep(glm->shared_gpio);
> Why ignoring ACTIVE_LOW status ?
I think this would be rather error-prone and doesn't make sense to me. The
consumer of this driver should decide about whether it uses ACTIVE_HIGH or
ACTIVE_LOW for each one of the virtual GPIOs separately. This should then be
applied as if this was a real GPIO. Following the ACTIVE_* that is given in
the 'shared-gpio' property then would interfere again.
> [...]
>
Thanks for all the suggested simplifications, I'll incorporate them.
> The advantage of the forwarder is that it handles if the shared GPIO is
> sleeping or not.
> But I think the forwarder shall have ngpio, not 1. You will have to add
> ngpio times the same GPIO desc. Also unsupported operations shall be unset.
> So I don't really know if it shall be used in this case.
I agree. As Peter mentioned, I need to use "can_sleep" anyway because of the mux.
So there's not really an argument left to use the forwarder.
> Best Regards,
>
> Thomas
Best regards,
Jonas
Hi! 2025-10-28 at 10:45, Thomas Richard wrote: > On 10/27/25 12:17 AM, Jonas Jelonek wrote: >> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]); >> + if (ret < 0) >> + return ret; >> + >> + ret = gpiod_get_raw_value_cansleep(glm->shared_gpio); > > Why ignoring ACTIVE_LOW status ? > And cansleep depends on your shared_gpio line, maybe it is not the case. > >> + mux_control_deselect(glm->mux); *snip* >> + glm->gc.can_sleep = true; > > depends on your shared_gpio line. Does it? In this case, the gpio will always need to be able to sleep, since mux_control_select() may sleep. Or, what am I missing? > Use gpiod_cansleep() like in the > gpio-aggregator driver to know if your shared_gpio can sleep. Cheers, Peter
Hi Peter, On 10/28/25 11:09 AM, Peter Rosin wrote: > Hi! > > 2025-10-28 at 10:45, Thomas Richard wrote: >> On 10/27/25 12:17 AM, Jonas Jelonek wrote: >>> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]); >>> + if (ret < 0) >>> + return ret; >>> + >>> + ret = gpiod_get_raw_value_cansleep(glm->shared_gpio); >> >> Why ignoring ACTIVE_LOW status ? >> And cansleep depends on your shared_gpio line, maybe it is not the case. >> >>> + mux_control_deselect(glm->mux); > > *snip* > >>> + glm->gc.can_sleep = true; >> >> depends on your shared_gpio line. > > Does it? In this case, the gpio will always need to be able to > sleep, since mux_control_select() may sleep. Or, what am I > missing? Oh yes you're right, I forgot the mux part. Best Regards, Thomas
Hi!
2025-10-27 at 00:17, Jonas Jelonek wrote:
> Add a new driver which provides a 1-to-many mapping for a single real
> GPIO using a multiplexer. Each virtual GPIO corresponds to a multiplexer
> state which, if set for the multiplexer, connects the real GPIO to the
> corresponding virtual GPIO.
>
> For now, this doesn't support advanced features like IRQs, just normal
> IN and OUT functionality of GPIOs.
>
> This can help in various usecases. One practical case is the special
> hardware design of the Realtek-based XS1930-10 switch from Zyxel. It
> features two SFP+ ports/cages whose signals are wired to directly to the
> switch SoC. Although Realtek SoCs are short on GPIOs, there are usually
> enough the fit the SFP signals without any hacks.
>
> However, Zyxel did some weird design and connected RX_LOS, MOD_ABS and
> TX_FAULT of one SFP cage onto a single GPIO line controlled by a
> multiplexer (the same for the other SFP cage). The single multiplexer
> controls the lines for both SFP and depending on the state, the
> designated 'signal GPIO lines' are connected to one of the three SFP
> signals.
>
> Because the SFP core/driver doesn't support multiplexer but needs single
> GPIOs for each of the signals, this driver fills the gap between both.
> It registers a gpio_chip, provides multiple virtual GPIOs and sets the
> backing multiplexer accordingly.
>
> Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
*snip*
> +static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
> + int ret;
> +
> + if (offset > gc->ngpio)
> + return -EINVAL;
> +
> + guard(gpio_lmux)(glm);
> +
> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
Consider using mux_control_select_delay() here, with some suitable
delay, to allow the mux to settle before reading the gpio line.
> + if (ret < 0)
> + return ret;
> +
> + ret = gpiod_get_raw_value_cansleep(glm->shared_gpio);
> + mux_control_deselect(glm->mux);
> + return ret;
> +}
> +
> +static int gpio_lmux_gpio_set(struct gpio_chip *gc, unsigned int offset,
> + int value)
> +{
> + struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
> + int ret;
> +
> + if (offset > gc->ngpio)
> + return -EINVAL;
> +
> + guard(gpio_lmux)(glm);
> +
> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
> + if (ret < 0)
> + return ret;
> +
> + gpiod_set_raw_value_cansleep(glm->shared_gpio, value);
> + mux_control_deselect(glm->mux);
This .set implementation is completely broken. It you want to
set a gpio to outout high/low, you presumably want the gpio to
stay that way for at least some period of time, while whatever
else happens that relies on the gpio to be in that state. But in
this case only the mux select/deselect is protecting that gpio
state, which is bound to be inadequate for anything real.
Sure, you can probably build something trivial and see that the
gpio can be manipulated, but the second something else touches
the mux, the intended state of an output gpio line is
(potentially) clobbered.
I notice that in your target application, the sfp driver, all
uses of gpios via the mux are inputs. Input is a much easier
problem. At least as long as you do not require IRQ, if you
need IRQs you face similar problems where the mux needs to be
locked in its position for whatever period of time you can
expect IRQs.
TL;DR, this .set implementation needs to be removed, there is
simply no reasonable way to implement a muxed gpio .set in a
pure software driver. You need some hardware to preserve the
state if/when the mux is manipulated.
Cheers,
Peter
> + return 0;
> +}
Hi Peter,
On 27.10.25 08:51, Peter Rosin wrote:
>> +static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
>> +{
>> + struct gpio_lmux *glm = (struct gpio_lmux *)gpiochip_get_data(gc);
>> + int ret;
>> +
>> + if (offset > gc->ngpio)
>> + return -EINVAL;
>> +
>> + guard(gpio_lmux)(glm);
>> +
>> + ret = mux_control_select(glm->mux, glm->gpio_mux_states[offset]);
> Consider using mux_control_select_delay() here, with some suitable
> delay, to allow the mux to settle before reading the gpio line.
Thanks, will fix this.
> This .set implementation is completely broken. It you want to
> set a gpio to outout high/low, you presumably want the gpio to
> stay that way for at least some period of time, while whatever
> else happens that relies on the gpio to be in that state. But in
> this case only the mux select/deselect is protecting that gpio
> state, which is bound to be inadequate for anything real.
>
> Sure, you can probably build something trivial and see that the
> gpio can be manipulated, but the second something else touches
> the mux, the intended state of an output gpio line is
> (potentially) clobbered.
>
> I notice that in your target application, the sfp driver, all
> uses of gpios via the mux are inputs. Input is a much easier
> problem. At least as long as you do not require IRQ, if you
> need IRQs you face similar problems where the mux needs to be
> locked in its position for whatever period of time you can
> expect IRQs.
Yes, that why I left IRQs out here, but somehow I didn't properly
transfer that I have similar issues with output functionality.
>
> TL;DR, this .set implementation needs to be removed, there is
> simply no reasonable way to implement a muxed gpio .set in a
> pure software driver. You need some hardware to preserve the
> state if/when the mux is manipulated.
I see, thanks for pointing it out! I'll drop this in the next iteration
then and make this driver input-only.
> Cheers,
> Peter
>
Best,
Jonas
© 2016 - 2026 Red Hat, Inc.