diff --git a/arch/arm/configs/imx6_vtpctp_defconfig b/arch/arm/configs/imx6_vtpctp_defconfig
index ee990c6b819f..094eaad275f5 100644
--- a/arch/arm/configs/imx6_vtpctp_defconfig
+++ b/arch/arm/configs/imx6_vtpctp_defconfig
@@ -209,6 +209,7 @@ CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_SIZE=65536
# CONFIG_ENCSW is not set
+CONFIG_DIO_SPI_DRV=m
CONFIG_EEPROM_AT24=y
CONFIG_EEPROM_AT25=y
CONFIG_SCSI=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 716732cea2c6..5c95bc004752 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -594,6 +594,15 @@ config DIN_SPI_DRV
Expose digial inputs using sysfs interface, e.g.
/sys/bus/spi/devices/spi0.0/din
+config DIO_SPI_DRV
+ tristate "SPI and SYSFS based digital input/output driver"
+ depends on SYSFS && GPIOLIB && SPI
+ help
+ This option enables the SPI and SYSFS based digital
+ input/output driver for VTPCT/EC devices.
+ To compile this driver as a module, choose M here: the
+ module will be called dio_drv.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 1554044bedd5..62ae525a5ed1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -70,3 +70,4 @@ obj-$(CONFIG_TPS6594_PFSM) += tps6594-pfsm.o
obj-$(CONFIG_TI_SN74LV165A) += ti_sn74lv165a.o
obj-$(CONFIG_DOUT_DRV) += dout_drv.o
obj-$(CONFIG_DIN_SPI_DRV) += din_spi_drv.o
+obj-$(CONFIG_DIO_SPI_DRV) += dio_drv.o
diff --git a/drivers/misc/dio_drv.c b/drivers/misc/dio_drv.c
new file mode 100644
index 000000000000..6ff434761833
--- /dev/null
+++ b/drivers/misc/dio_drv.c
@@ -0,0 +1,201 @@
+//------------------------------------------------------------------------------
+/// Copyright (c) 2019-2022 WAGO GmbH & Co. KG
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, version 2.
+///
+/// This program is distributed in the hope that it will be useful, but
+/// WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+/// General Public License for more details.
+///
+/// You should have received a copy of the GNU General Public License
+/// along with this program. If not, see .
+//------------------------------------------------------------------------------
+
+///------------------------------------------------------------------------------
+/// \file dio_drv.c
+///
+/// \brief Driver for "X3" (4 digital I/O) on TouchPanel devices
+///
+/// \author Ralf Gliese, elrest Automationssysteme GmbH
+///------------------------------------------------------------------------------
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct dio_data {
+ unsigned char dma_buffer[2];
+ int gpio_load;
+ int gpio_reset;
+ struct mutex lock;
+ unsigned char tx_data;
+};
+
+static unsigned char reverse_low_nibble(unsigned char input)
+{
+ static const unsigned char lookup[] = {
+ 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
+ 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf
+ };
+ return lookup[input & 0xf] | (input & 0xf0);
+};
+
+/* SPI Write/Read cycle */
+static int dio_spi_exchange(struct spi_device *spi, int data)
+{
+ struct dio_data *dio = spi_get_drvdata(spi);
+ struct spi_message msg;
+ struct spi_transfer xfer;
+ int ret;
+
+ if (data >= 0)
+ dio->tx_data = reverse_low_nibble(data);
+
+ dio->dma_buffer[0] = 0;
+ dio->dma_buffer[1] = dio->tx_data;
+
+ /* Latch input data */
+ gpio_set_value(dio->gpio_load, 0);
+ udelay(10);
+ gpio_set_value(dio->gpio_load, 1);
+
+ /* Initialize the SPI message and transfer data structures */
+ spi_message_init(&msg);
+ memset(&xfer, 0, sizeof xfer);
+ xfer.tx_buf = xfer.rx_buf = dio->dma_buffer;
+ xfer.len = sizeof dio->dma_buffer;
+ spi_message_add_tail(&xfer, &msg);
+
+ /* Send the message and wait for completion */
+ ret = spi_sync(spi, &msg);
+ if (ret != 0) {
+ dev_err(&spi->dev, "SPI transmission failed\n");
+ return 0;
+ }
+ dev_dbg(&spi->dev, "sent %02x %02x, received %02x %02x\n",
+ dio->tx_data, dio->tx_data,
+ dio->dma_buffer[0], dio->dma_buffer[1]);
+ return reverse_low_nibble(dio->dma_buffer[0]) | dio->dma_buffer[1] << 8;
+}
+
+static ssize_t dio_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct dio_data *dio = spi_get_drvdata(spi);
+ int ret;
+
+ ret = mutex_lock_interruptible(&dio->lock);
+ if (ret)
+ return ret;
+ ret = dio_spi_exchange(spi, -1);
+ mutex_unlock(&dio->lock);
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t dio_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct dio_data *dio = spi_get_drvdata(spi);
+ unsigned long data;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &data);
+ if (ret < 0)
+ return ret;
+ if (data > 0xf)
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&dio->lock);
+ if (ret)
+ return ret;
+ dio_spi_exchange(spi, data);
+ mutex_unlock(&dio->lock);
+ return count;
+}
+
+static DEVICE_ATTR_RW(dio);
+
+static int alloc_gpio(struct device *dev, const char *name)
+{
+ int ret, gpio = of_get_named_gpio(dev->of_node, name, 0);
+
+ if (gpio < 0) {
+ dev_err(dev, "GPIO '%s' not found in device tree\n", name);
+ return gpio;
+ }
+ ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH, name);
+ if (ret < 0) {
+ dev_err(dev, "Cannot request GPIO '%s' %d\n", name, gpio);
+ return ret;
+ }
+ dev_dbg(dev, "GPIO '%s' has Number %d\n", name, gpio);
+ return gpio;
+}
+
+static int dio_drv_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct dio_data *dio;
+
+ dio = devm_kzalloc(dev, sizeof *dio, GFP_KERNEL);
+ if (!dio)
+ return -ENOMEM;
+
+ dio->gpio_load = alloc_gpio(dev, "gpio-load");
+ if (dio->gpio_load < 0)
+ return dio->gpio_load;
+
+ dio->gpio_reset = alloc_gpio(dev, "gpio-reset");
+ if (dio->gpio_reset < 0)
+ return dio->gpio_reset;
+
+ mutex_init(&dio->lock);
+ spi_set_drvdata(spi, dio);
+
+ /* Initialize and enable output lines */
+ dio_spi_exchange(spi, 0);
+ gpio_set_value(dio->gpio_reset, 0);
+
+ return device_create_file(dev, &dev_attr_dio);
+}
+
+static void dio_drv_remove(struct spi_device *spi)
+{
+ struct dio_data *dio = spi_get_drvdata(spi);
+
+ gpio_set_value(dio->gpio_reset, 1);
+ device_remove_file(&spi->dev, &dev_attr_dio);
+}
+
+static const struct spi_device_id dio_spi_ids[] = {
+ { "dio_spi", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, dio_spi_ids);
+
+static struct spi_driver dio_driver = {
+ .driver = {
+ .name = "dio_spi",
+ .owner = THIS_MODULE,
+ },
+ .id_table = dio_spi_ids,
+ .probe = dio_drv_probe,
+ .remove = dio_drv_remove,
+};
+
+module_spi_driver(dio_driver);
+
+MODULE_AUTHOR("elrest");
+MODULE_DESCRIPTION("dio_spi driver");
+MODULE_LICENSE("GPL");