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 +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 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 + */ + +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("GPIO line mux driver"); +MODULE_LICENSE("GPL");