[PATCH 2/3] gpio: Add FT232H CBUS GPIO driver

From: Anatolij Gustschin
Date: Thu Jul 06 2017 - 16:49:40 EST


Add driver for CBUS pins on FT232H. The driver supports setting
GPIO direction and getting/setting CBUS 0-3 pin value. The CBUS
pins have to be enabled by configuring I/O mode in the FTDI EEPROM.

Signed-off-by: Anatolij Gustschin <agust@xxxxxxx>
---
drivers/gpio/Kconfig | 11 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ftdi-cbus.c | 251 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 263 insertions(+)
create mode 100644 drivers/gpio/gpio-ftdi-cbus.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index f235eae..cca784a 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -929,6 +929,17 @@ config HTC_EGPIO
several HTC phones. It provides basic support for input
pins, output pins, and irqs.

+config GPIO_FTDI_CBUS
+ tristate "Support for CBUS GPIO pins on FTDI FT232H"
+ depends on MFD_FTDI_FT232H
+ select GPIO_GENERIC
+ help
+ This driver provides basic support for up to four CBUS GPIOs
+ on FT232H. It allows to configure CBUS pins as input or output
+ and to read and write CBUS pin state. You need to enable I/O-mode
+ for ACBUS 5/6/8/9 pins in FTDI EEPROM first. I/O-mode disabled GPIOs
+ will not be used in the driver.
+
config GPIO_JANZ_TTL
tristate "Janz VMOD-TTL Digital IO Module"
depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index a9fda6c..c2ab813 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
obj-$(CONFIG_GPIO_ETRAXFS) += gpio-etraxfs.o
obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o
obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o
+obj-$(CONFIG_GPIO_FTDI_CBUS) += gpio-ftdi-cbus.o
obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
diff --git a/drivers/gpio/gpio-ftdi-cbus.c b/drivers/gpio/gpio-ftdi-cbus.c
new file mode 100644
index 0000000..b0adfe3
--- /dev/null
+++ b/drivers/gpio/gpio-ftdi-cbus.c
@@ -0,0 +1,251 @@
+/*
+ * FTDI FT232H CBUS GPIO driver
+ *
+ * Copyright (C) 2017 DENX Software Engineering
+ * Anatolij Gustschin <agust@xxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/mfd/ftdi/ftdi.h>
+
+struct ftdi_cbus_gpio {
+ struct platform_device *pdev;
+ struct gpio_chip gpio;
+ const char *gpio_names[4];
+ u8 cbus_pin_offsets[4];
+ u8 cbus_mask;
+ u8 pinbuf[4];
+ u8 eeprom[FTDI_MAX_EEPROM_SIZE];
+};
+
+static const char *ftdi_acbus_names[5] = {
+ "ACBUS5", "ACBUS6", NULL, "ACBUS8", "ACBUS9"
+};
+
+static int ftdi_cbus_gpio_read_pins(struct ftdi_cbus_gpio *priv,
+ unsigned char *pins)
+{
+ struct gpio_chip *chip = &priv->gpio;
+ struct ctrl_desc desc;
+ int ret;
+
+ desc.dir_out = false;
+ desc.request = FTDI_SIO_READ_PINS_REQUEST;
+ desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN;
+ desc.value = 0;
+ desc.index = 1;
+ desc.data = &priv->pinbuf[0];
+ desc.size = 1;
+ desc.timeout = USB_CTRL_GET_TIMEOUT;
+
+ ret = ftdi_ctrl_xfer(priv->pdev, &desc);
+ if (ret < 0) {
+ dev_dbg(chip->parent, "failed to get pin values: %d\n", ret);
+ return ret;
+ }
+
+ *pins = priv->pinbuf[0];
+ return 0;
+}
+
+static inline void ftdi_cbus_init_gpio_data(struct ftdi_cbus_gpio *priv,
+ int gpio_num, int cbus_num)
+{
+ switch (cbus_num) {
+ case 5:
+ case 6:
+ priv->cbus_pin_offsets[gpio_num] = cbus_num - 5;
+ break;
+ case 8:
+ case 9:
+ priv->cbus_pin_offsets[gpio_num] = cbus_num - 6;
+ break;
+ default:
+ return;
+ }
+
+ priv->gpio_names[gpio_num] = ftdi_acbus_names[cbus_num - 5];
+}
+
+static int ftdi_read_eeprom(struct ftdi_cbus_gpio *priv)
+{
+ struct ctrl_desc desc;
+ unsigned int i;
+ int ret;
+
+ desc.dir_out = false;
+ desc.request = FTDI_SIO_READ_EEPROM_REQUEST;
+ desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN;
+ desc.value = 0;
+ desc.size = 2;
+ desc.timeout = USB_CTRL_GET_TIMEOUT;
+
+ for (i = 0; i < FTDI_MAX_EEPROM_SIZE / 2; i++) {
+ desc.index = i;
+ desc.data = &priv->eeprom[i * 2];
+
+ ret = ftdi_ctrl_xfer(priv->pdev, &desc);
+ if (ret < 0) {
+ dev_dbg(&priv->pdev->dev, "EEPROM read failed: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ print_hex_dump(KERN_DEBUG, "EEPROM: ", DUMP_PREFIX_OFFSET, 16, 1,
+ priv->eeprom, sizeof(priv->eeprom), 1);
+ return 0;
+}
+
+static int ftdi_cbus_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ftdi_cbus_gpio *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+ int ret;
+ u8 pins = 0;
+
+ ret = ftdi_cbus_gpio_read_pins(priv, &pins);
+ if (ret)
+ return ret;
+
+ offs = priv->cbus_pin_offsets[offset];
+
+ return !!(pins & BIT(offs));
+}
+
+static void ftdi_cbus_gpio_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct ftdi_cbus_gpio *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+ int ret;
+
+ offs = priv->cbus_pin_offsets[offset];
+
+ if (value)
+ priv->cbus_mask |= BIT(offs);
+ else
+ priv->cbus_mask &= ~BIT(offs);
+
+ ret = ftdi_set_bitmode(priv->pdev, priv->cbus_mask, BITMODE_CBUS);
+ if (ret < 0)
+ dev_dbg(chip->parent, "setting pin value failed: %d\n", ret);
+}
+
+static int ftdi_cbus_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ftdi_cbus_gpio *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+
+ offs = priv->cbus_pin_offsets[offset];
+ /* Direction bits are in the upper nibble */
+ priv->cbus_mask &= ~(BIT(offs) << 4);
+
+ return ftdi_set_bitmode(priv->pdev, priv->cbus_mask, BITMODE_CBUS);
+}
+
+static int ftdi_cbus_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct ftdi_cbus_gpio *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+
+ offs = priv->cbus_pin_offsets[offset];
+ priv->cbus_mask |= BIT(offs) << 4;
+
+ if (value)
+ priv->cbus_mask |= BIT(offs);
+ else
+ priv->cbus_mask &= ~BIT(offs);
+
+ return ftdi_set_bitmode(priv->pdev, priv->cbus_mask, BITMODE_CBUS);
+}
+
+static int ftdi_cbus_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ftdi_cbus_gpio *priv;
+ unsigned int i;
+ int ret, ngpio = 0;
+ u8 val;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->gpio.label = dev_name(&pdev->dev);
+ priv->gpio.parent = dev;
+ priv->gpio.owner = THIS_MODULE;
+ priv->gpio.names = priv->gpio_names;
+ priv->gpio.base = -1;
+ priv->gpio.ngpio = 0; /* will be set if I/O mode enabled in EEPROM */
+ priv->gpio.can_sleep = true;
+ priv->gpio.set = ftdi_cbus_gpio_set;
+ priv->gpio.get = ftdi_cbus_gpio_get;
+ priv->gpio.direction_input = ftdi_cbus_gpio_direction_input;
+ priv->gpio.direction_output = ftdi_cbus_gpio_direction_output;
+
+ ret = ftdi_read_eeprom(priv);
+ if (ret < 0)
+ return ret;
+
+ /* Check if I/O mode is enabled for supported pins 5, 6, 8, 9 */
+ for (i = 5; i < 10; i++) {
+ val = priv->eeprom[0x18 + i / 2] >> (i % 2 ? 4 : 0);
+ if ((val & 0x0f) == FTDI_232H_CBUS_IOMODE) {
+ dev_info(&priv->pdev->dev, "gpio-%d @ ACBUS%d\n",
+ priv->gpio.ngpio++, i);
+ ftdi_cbus_init_gpio_data(priv, ngpio++, i);
+ }
+ }
+
+ if (!priv->gpio.ngpio) {
+ dev_warn(dev, "I/O mode disabled in EEPROM\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = devm_gpiochip_add_data(dev, &priv->gpio, priv);
+ if (ret) {
+ dev_err(dev, "failed to add CBUS gpiochip: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "using %d CBUS pins\n", priv->gpio.ngpio);
+ return 0;
+}
+
+static int ftdi_cbus_gpio_remove(struct platform_device *pdev)
+{
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver ftdi_cbus_gpio_driver = {
+ .driver.name = "ftdi-cbus-gpio",
+ .probe = ftdi_cbus_gpio_probe,
+ .remove = ftdi_cbus_gpio_remove,
+};
+
+module_platform_driver(ftdi_cbus_gpio_driver);
+
+MODULE_ALIAS("platform:ftdi-cbus-gpio");
+MODULE_AUTHOR("Anatolij Gustschin <agust@xxxxxxx");
+MODULE_DESCRIPTION("FT232H CBUS GPIO interface driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4