This driver implements a GPIO multiplexer based on latches connected to other GPIOs. A set of data GPIOs is connected to the data input of multiple latches. The clock input of each latch is driven by another set of GPIOs. With two 8-bit latches 10 GPIOs can be multiplexed into 16 GPIOs. GPOs might be a better term as in fact the multiplexed pins are output only. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- drivers/gpio/Kconfig | 6 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-latch.c | 195 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 drivers/gpio/gpio-latch.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ab75fe4ed9..dd2b56d256 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -205,6 +205,12 @@ config GPIO_ZYNQ help Say yes here to support Xilinx Zynq GPIO controller. +config GPIO_LATCH + tristate "GPIO latch driver" + help + Say yes here to enable a driver for GPIO multiplexers based on latches + connected to other GPIOs. + endmenu endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fcb6a232e0..90ab0a8b28 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o obj-$(CONFIG_GPIO_STARFIVE) += gpio-starfive-vic.o obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o +obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o diff --git a/drivers/gpio/gpio-latch.c b/drivers/gpio/gpio-latch.c new file mode 100644 index 0000000000..2a89f22401 --- /dev/null +++ b/drivers/gpio/gpio-latch.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GPIO latch driver + * + * Copyright (C) 2022 Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> + * + * This driver implements a GPIO (or better GPO as there is no input) + * multiplexer based on latches like this: + * + * CLK0 ----------------------. ,--------. + * CLK1 -------------------. `--------|> #0 | + * | | | + * OUT0 ----------------+--|-----------|D0 Q0|-----|< + * OUT1 --------------+-|--|-----------|D1 Q1|-----|< + * OUT2 ------------+-|-|--|-----------|D2 Q2|-----|< + * OUT3 ----------+-|-|-|--|-----------|D3 Q3|-----|< + * OUT4 --------+-|-|-|-|--|-----------|D4 Q4|-----|< + * OUT5 ------+-|-|-|-|-|--|-----------|D5 Q5|-----|< + * OUT6 ----+-|-|-|-|-|-|--|-----------|D6 Q6|-----|< + * OUT7 --+-|-|-|-|-|-|-|--|-----------|D7 Q7|-----|< + * | | | | | | | | | `--------' + * | | | | | | | | | + * | | | | | | | | | ,--------. + * | | | | | | | | `-----------|> #1 | + * | | | | | | | | | | + * | | | | | | | `--------------|D0 Q0|-----|< + * | | | | | | `----------------|D1 Q1|-----|< + * | | | | | `------------------|D2 Q2|-----|< + * | | | | `--------------------|D3 Q3|-----|< + * | | | `----------------------|D4 Q4|-----|< + * | | `------------------------|D5 Q5|-----|< + * | `--------------------------|D6 Q6|-----|< + * `----------------------------|D7 Q7|-----|< + * `--------' + * + * The above is just an example. The actual number of number of latches and + * the number of inputs per latch is derived from the number of GPIOs given + * in the corresponding device tree properties. + */ + +#include <common.h> +#include <errno.h> +#include <io.h> +#include <of.h> +#include <gpio.h> +#include <init.h> +#include <of_gpio.h> +#include <linux/bitmap.h> + +struct gpio_latch_priv { + struct gpio_chip gc; + int *clk_gpios; + int *latched_gpios; + int n_latched_gpios; + unsigned int setup_duration_ns; + unsigned int clock_duration_ns; + unsigned long *shadow; +}; + +static int gpio_latch_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return 0; +} + +static void gpio_latch_set(struct gpio_chip *gc, unsigned int offset, int val) +{ + struct gpio_latch_priv *priv = container_of(gc, struct gpio_latch_priv, gc); + int latch = offset / priv->n_latched_gpios; + int i; + + assign_bit(offset, priv->shadow, val); + + for (i = 0; i < priv->n_latched_gpios; i++) + gpio_set_value(priv->latched_gpios[i], + test_bit(latch * priv->n_latched_gpios + i, priv->shadow)); + + ndelay(priv->setup_duration_ns); + gpio_set_value(priv->clk_gpios[latch], 1); + ndelay(priv->clock_duration_ns); + gpio_set_value(priv->clk_gpios[latch], 0); +} +static int gpio_latch_direction_output(struct gpio_chip *gc, unsigned gpio, int val) +{ + gpio_latch_set(gc, gpio, val); + + return 0; +} + +#define DURATION_NS_MAX 5000 + +static struct gpio_ops gpio_latch_gpio_ops = { + .direction_output = gpio_latch_direction_output, + .set = gpio_latch_set, + .get_direction = gpio_latch_get_direction, +}; + +static int gpio_latch_probe(struct device_d *dev) +{ + struct gpio_latch_priv *priv; + int n_latches, i, ret; + struct device_node *np = dev->device_node; + enum of_gpio_flags flags; + + priv = xzalloc(sizeof(*priv)); + + n_latches = of_gpio_named_count(np, "clk-gpios"); + if (n_latches < 0) { + dev_err(dev, "invalid or missing clk-gpios"); + ret = -EINVAL; + goto err_gpio; + } + + priv->n_latched_gpios = of_gpio_named_count(np, "latched-gpios"); + if (priv->n_latched_gpios < 0) { + dev_err(dev, "invalid or missing latched-gpios"); + ret = -EINVAL; + goto err_gpio; + } + + priv->clk_gpios = xzalloc(sizeof(int) * n_latches); + priv->latched_gpios = xzalloc(sizeof(int) * priv->n_latched_gpios); + + for (i = 0; i < n_latches; i++) { + priv->clk_gpios[i] = of_get_named_gpio_flags(np, "clk-gpios", + i, &flags); + ret = gpio_request_one(priv->clk_gpios[i], + flags & OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0, + dev_name(dev)); + if (ret) { + dev_err(dev, "Cannot request gpio %d: %s\n", priv->clk_gpios[i], + strerror(-ret)); + goto err_gpio; + } + + gpio_direction_output(priv->clk_gpios[i], 0); + } + + for (i = 0; i < priv->n_latched_gpios; i++) { + priv->latched_gpios[i] = of_get_named_gpio_flags(np, "latched-gpios", + i, &flags); + ret = gpio_request_one(priv->latched_gpios[i], + flags & OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0, + dev_name(dev)); + if (ret) { + dev_err(dev, "Cannot request gpio %d: %s\n", priv->latched_gpios[i], + strerror(-ret)); + goto err_gpio; + } + } + + priv->shadow = bitmap_zalloc(n_latches * priv->n_latched_gpios); + + of_property_read_u32(np, "setup-duration-ns", &priv->setup_duration_ns); + if (priv->setup_duration_ns > DURATION_NS_MAX) { + dev_warn(dev, "setup-duration-ns too high, limit to %d\n", + DURATION_NS_MAX); + priv->setup_duration_ns = DURATION_NS_MAX; + } + + of_property_read_u32(np, "clock-duration-ns", &priv->clock_duration_ns); + if (priv->clock_duration_ns > DURATION_NS_MAX) { + dev_warn(dev, "clock-duration-ns too high, limit to %d\n", + DURATION_NS_MAX); + priv->clock_duration_ns = DURATION_NS_MAX; + } + + priv->gc.ops = &gpio_latch_gpio_ops; + priv->gc.ngpio = n_latches * priv->n_latched_gpios; + priv->gc.base = -1; + priv->gc.dev = dev; + + return gpiochip_add(&priv->gc); + +err_gpio: + return ret; +} + +static const struct of_device_id gpio_latch_ids[] = { + { + .compatible = "gpio-latch", + }, { + /* sentinel */ + } +}; + +static struct driver_d gpio_latch_driver = { + .name = "gpio-latch", + .probe = gpio_latch_probe, + .of_compatible = DRV_OF_COMPAT(gpio_latch_ids), +}; +device_platform_driver(gpio_latch_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("GPIO latch driver"); -- 2.30.2