summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/gpio/Kconfig9
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-line-mux.c126
4 files changed, 142 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9d..8056ef0ffbf0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10774,6 +10774,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 c33f9305ab97..2efa58f10781 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1994,6 +1994,15 @@ 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 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 2421a8fd3733..36fa7c52d240 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -90,6 +90,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..a4f384306218
--- /dev/null
+++ b/drivers/gpio/gpio-line-mux.c
@@ -0,0 +1,126 @@
+// 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/platform_device.h>
+
+#define MUX_SELECT_DELAY_US 100
+
+struct gpio_lmux {
+ struct gpio_chip gc;
+ struct mux_control *mux;
+ struct gpio_desc *muxed_gpio;
+
+ u32 num_gpio_mux_states;
+ unsigned int gpio_mux_states[] __counted_by(num_gpio_mux_states);
+};
+
+static int gpio_lmux_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_lmux *glm = gpiochip_get_data(gc);
+ int ret;
+
+ if (offset > gc->ngpio)
+ return -EINVAL;
+
+ ret = mux_control_select_delay(glm->mux, glm->gpio_mux_states[offset],
+ MUX_SELECT_DELAY_US);
+ if (ret < 0)
+ return ret;
+
+ ret = gpiod_get_raw_value_cansleep(glm->muxed_gpio);
+ mux_control_deselect(glm->mux);
+ return ret;
+}
+
+static int gpio_lmux_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ return -EOPNOTSUPP;
+}
+
+static int gpio_lmux_gpio_get_direction(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int gpio_lmux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_lmux *glm;
+ unsigned int ngpio;
+ size_t size;
+ int ret;
+
+ ngpio = device_property_count_u32(dev, "gpio-line-mux-states");
+ if (!ngpio)
+ return -EINVAL;
+
+ size = struct_size(glm, gpio_mux_states, ngpio);
+ glm = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!glm)
+ return -ENOMEM;
+
+ glm->gc.base = -1;
+ glm->gc.can_sleep = true;
+ glm->gc.fwnode = dev_fwnode(dev);
+ glm->gc.label = dev_name(dev);
+ 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->mux = devm_mux_control_get(dev, NULL);
+ if (IS_ERR(glm->mux))
+ return dev_err_probe(dev, PTR_ERR(glm->mux),
+ "could not get mux controller\n");
+
+ glm->muxed_gpio = devm_gpiod_get(dev, "muxed", GPIOD_IN);
+ if (IS_ERR(glm->muxed_gpio))
+ return dev_err_probe(dev, PTR_ERR(glm->muxed_gpio),
+ "could not get muxed-gpio\n");
+
+ glm->num_gpio_mux_states = ngpio;
+ ret = device_property_read_u32_array(dev, "gpio-line-mux-states",
+ &glm->gpio_mux_states[0], ngpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "could not get mux states\n");
+
+ ret = devm_gpiochip_add_data(dev, &glm->gc, glm);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add gpiochip\n");
+
+ 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");