Skip to content

Instantly share code, notes, and snippets.

@nocko
Created August 11, 2016 16:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nocko/6d0b9ec56d6b1b602e964e56f48c9a4b to your computer and use it in GitHub Desktop.
Save nocko/6d0b9ec56d6b1b602e964e56f48c9a4b to your computer and use it in GitHub Desktop.
ftdi-sio GPIO support for Linux >= 4.6
--- a/drivers/usb/serial/ftdi_sio.c.orig 2016-07-24 15:23:50.000000000 -0400
+++ b/drivers/usb/serial/ftdi_sio.c 2016-08-11 11:50:58.689444925 -0400
@@ -33,6 +33,9 @@
#include <linux/kernel.h>
#include <linux/errno.h>
+#ifdef CONFIG_GPIOLIB
+#include <linux/gpio.h>
+#endif
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
@@ -53,6 +56,7 @@
struct ftdi_private {
enum ftdi_chip_type chip_type;
+ struct usb_serial_port *port;
/* type of device, either SIO or FT8U232AM */
int baud_base; /* baud base clock for divisor setting */
int custom_divisor; /* custom_divisor kludge, this is for
@@ -77,6 +81,15 @@
unsigned int latency; /* latency setting in use */
unsigned short max_packet_size;
struct mutex cfg_lock; /* Avoid mess by parallel calls of config ioctl() and change_speed() */
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gc;
+ bool serial_open;
+ unsigned char open_gpios;
+ unsigned int bitmode;
+ unsigned char gpio_direction; /* data direction in bitbang
+ * mode, 0=in / 1=out */
+ unsigned char gpo_values; /* GPIO output values */
+#endif
};
/* struct ftdi_sio_quirk is used by devices requiring special attention. */
@@ -1037,6 +1050,7 @@
static int ftdi_sio_port_probe(struct usb_serial_port *port);
static int ftdi_sio_port_remove(struct usb_serial_port *port);
static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port);
+static void ftdi_close(struct usb_serial_port *port);
static void ftdi_dtr_rts(struct usb_serial_port *port, int on);
static void ftdi_process_read_urb(struct urb *urb);
static int ftdi_prepare_write_buffer(struct usb_serial_port *port,
@@ -1060,6 +1074,15 @@
static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base);
static __u32 ftdi_2232h_baud_to_divisor(int baud);
+#ifdef CONFIG_GPIOLIB
+static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio);
+static void ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val);
+static int ftdi_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio);
+static int ftdi_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val);
+static int ftdi_gpio_request(struct gpio_chip *chip, unsigned offset);
+static void ftdi_gpio_free(struct gpio_chip *chip, unsigned offset);
+#endif
+
static struct usb_serial_driver ftdi_sio_device = {
.driver = {
.owner = THIS_MODULE,
@@ -1074,6 +1097,7 @@
.port_probe = ftdi_sio_port_probe,
.port_remove = ftdi_sio_port_remove,
.open = ftdi_open,
+ .close = ftdi_close,
.dtr_rts = ftdi_dtr_rts,
.throttle = usb_serial_generic_throttle,
.unthrottle = usb_serial_generic_unthrottle,
@@ -1443,6 +1467,59 @@
return rv;
}
+#ifdef CONFIG_GPIOLIB
+static int write_bitmode(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ int rv;
+ int bitmode = priv->bitmode;
+ int gpio_direction = priv->gpio_direction;
+
+ dev_dbg(&port->dev, "%s: setting bitmode = %i, direction = %i\n",
+ __func__, bitmode, gpio_direction);
+
+ rv = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ FTDI_SIO_SET_BITMODE_REQUEST,
+ FTDI_SIO_SET_BITMODE_REQUEST_TYPE,
+ gpio_direction | bitmode, priv->interface,
+ NULL, 0, WDR_TIMEOUT);
+ if (rv < 0)
+ dev_err(&port->dev, "Unable to write bitmode: %i\n", rv);
+ return rv;
+}
+
+/*
+ * Returns the GPIO pin values, or negative error code.
+ */
+static int read_pins(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+ struct usb_device *udev = port->serial->dev;
+ unsigned char *buf;
+ unsigned char pin_states;
+ int rv;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ rv = usb_control_msg(udev,
+ usb_rcvctrlpipe(udev, 0),
+ FTDI_SIO_READ_PINS_REQUEST,
+ FTDI_SIO_READ_PINS_REQUEST_TYPE,
+ 0, priv->interface,
+ buf, 1, WDR_TIMEOUT);
+ if (rv < 0)
+ dev_err(&port->dev, "Unable to read pins: %i\n", rv);
+
+ pin_states = buf[0];
+ kfree(buf);
+ return (rv < 0) ? rv : pin_states;
+}
+#endif
+
static int get_serial_info(struct usb_serial_port *port,
struct serial_struct __user *retinfo)
{
@@ -1794,7 +1871,8 @@
struct ftdi_private *priv;
const struct ftdi_sio_quirk *quirk = usb_get_serial_data(port->serial);
-
+ int error;
+
priv = kzalloc(sizeof(struct ftdi_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -1814,6 +1892,57 @@
priv->latency = 16;
write_latency_timer(port);
create_sysfs_attrs(port);
+
+#ifdef CONFIG_GPIOLIB
+ priv->serial_open = false;
+ priv->gpo_values = 0;
+ priv->gpio_direction = 0;
+ priv->open_gpios = 0;
+ priv->port = port;
+ priv->gc.owner = THIS_MODULE;
+ priv->gc.direction_input = ftdi_gpio_dir_in;
+ priv->gc.direction_output = ftdi_gpio_dir_out;
+ priv->gc.get = ftdi_gpio_get;
+ priv->gc.set = ftdi_gpio_set;
+ priv->gc.request = ftdi_gpio_request;
+ priv->gc.free = ftdi_gpio_free;
+ priv->gc.can_sleep = true;
+ priv->gc.base = -1;
+ switch (priv->chip_type) {
+ case SIO:
+ case FT8U232AM:
+ priv->gc.ngpio = 0;
+ break;
+ case FT232BM:
+ case FT2232C:
+ case FT232RL:
+ case FT2232H:
+ case FT4232H:
+ case FT232H:
+ priv->gc.ngpio = 8;
+ break;
+ case FTX:
+ /* 8 on FT231X and FT240X, but only 4 on FT230X and
+ * FT234XD. We don't know of any way to distinguish
+ * these chips, so we use the higher value. */
+ priv->gc.ngpio = 8;
+ break;
+ }
+ priv->gc.parent = &port->dev;
+ priv->gc.label = dev_name(&port->dev);
+
+ dev_dbg(&port->dev, "ngpio=%d\n", priv->gc.ngpio);
+ if (!priv->gc.ngpio)
+ return 0;
+
+ error = gpiochip_add(&priv->gc);
+ if (error) {
+ dev_err(&port->dev, "ftdi_sio: Failed to register GPIOs\n");
+ /* We can still use it as a serial port. Just mark the
+ * GPIOs as unused for ftdi_sio_port_remove(). */
+ priv->gc.ngpio = 0;
+ }
+#endif
return 0;
}
@@ -1930,6 +2059,11 @@
{
struct ftdi_private *priv = usb_get_serial_port_data(port);
+#ifdef CONFIG_GPIOLIB
+ if (priv->gc.ngpio) {
+ gpiochip_remove(&priv->gc);
+ }
+#endif
remove_sysfs_attrs(port);
kfree(priv);
@@ -1941,6 +2075,19 @@
{
struct usb_device *dev = port->serial->dev;
struct ftdi_private *priv = usb_get_serial_port_data(port);
+ int error;
+
+#ifdef CONFIG_GPIOLIB
+ if (priv->open_gpios)
+ return -ENXIO;
+
+ mutex_lock(&priv->cfg_lock);
+ priv->bitmode = FTDI_SIO_SET_BITMODE_RESET;
+ error = write_bitmode(port);
+ mutex_unlock(&priv->cfg_lock);
+ if (error)
+ return -EIO;
+#endif
/* No error checking for this (will get errors later anyway) */
/* See ftdi_sio.h for description of what is reset */
@@ -1957,9 +2104,20 @@
if (tty)
ftdi_set_termios(tty, port, NULL);
+#ifdef CONFIG_GPIOLIB
+ priv->serial_open = true;
+#endif
return usb_serial_generic_open(tty, port);
}
+static void ftdi_close(struct usb_serial_port *port)
+{
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
+
+ priv->serial_open = false;
+ usb_serial_generic_close(port);
+}
+
static void ftdi_dtr_rts(struct usb_serial_port *port, int on)
{
struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -2500,6 +2658,108 @@
return -ENOIOCTLCMD;
}
+#ifdef CONFIG_GPIOLIB
+static inline struct ftdi_private *gc_to_ftdi_private(struct gpio_chip *gc)
+{
+ return container_of(gc, struct ftdi_private, gc);
+}
+
+static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+ int value = read_pins(priv->port);
+ dev_dbg(&port->dev, "%s - value=%d\n", __func__, value);
+ if (value < 0)
+ return value;
+ return (value & (1 << gpio)) ? 1 : 0;
+}
+
+static void ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+ struct tty_struct *tty = tty_port_tty_get(&port->port);
+ struct device *dev = &port->dev;
+ int sent;
+
+ mutex_lock(&priv->cfg_lock);
+ if (val)
+ priv->gpo_values |= 1 << gpio;
+ else
+ priv->gpo_values &= ~(1 << gpio);
+ dev_dbg(dev, "%s - gpo_values=%d\n", __func__, priv->gpo_values);
+ sent = port->serial->type->write(tty, port, &(priv->gpo_values), 1);
+ mutex_unlock(&priv->cfg_lock);
+ if (sent < 1)
+ dev_err(dev, "error %d setting outputs (ignored)\n", sent);
+}
+
+static int ftdi_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+ struct device *dev = &port->dev;
+ int error;
+
+ mutex_lock(&priv->cfg_lock);
+ priv->gpio_direction &= ~(1 << gpio);
+ dev_dbg(dev, "%s - direction=%d\n", __func__, priv->gpio_direction);
+ error = write_bitmode(priv->port);
+ mutex_unlock(&priv->cfg_lock);
+ dev_dbg(dev, "%s - error=%d\n", __func__, error);
+ return error ? -EIO : 0;
+}
+
+static int ftdi_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+ struct device *dev = &port->dev;
+ int error;
+
+ gc->set(gc, gpio, val);
+ mutex_lock(&priv->cfg_lock);
+ priv->gpio_direction |= (1 << gpio);
+ dev_dbg(dev, "%s - direction=%d\n", __func__, priv->gpio_direction);
+ error = write_bitmode(priv->port);
+ mutex_unlock(&priv->cfg_lock);
+ dev_dbg(dev, "%s - error=%d\n", __func__, error);
+ return error ? -EIO : 0;
+}
+
+static int ftdi_gpio_request(struct gpio_chip *gc, unsigned offset)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+ int error;
+
+ if (priv->serial_open)
+ return -ENXIO;
+
+ mutex_lock(&priv->cfg_lock);
+ priv->bitmode = FTDI_SIO_SET_BITMODE_BITBANG;
+ error = write_bitmode(priv->port);
+ if (!error)
+ priv->open_gpios |= 1 << offset;
+ dev_dbg(&port->dev, "%s - open_gpios=%d\n", __func__, priv->open_gpios);
+ mutex_unlock(&priv->cfg_lock);
+ dev_dbg(&port->dev, "%s - error=%d\n", __func__, error);
+ return error ? -EIO : 0;
+}
+
+static void ftdi_gpio_free(struct gpio_chip *gc, unsigned offset)
+{
+ struct ftdi_private *priv = gc_to_ftdi_private(gc);
+ struct usb_serial_port *port = priv->port;
+
+ mutex_lock(&priv->cfg_lock);
+ priv->open_gpios &= ~(1 << offset);
+ dev_dbg(&port->dev, "%s - open_gpios=%d\n", __func__, priv->open_gpios);
+ mutex_unlock(&priv->cfg_lock);
+}
+#endif
+
module_usb_serial_driver(serial_drivers, id_table_combined);
MODULE_AUTHOR(DRIVER_AUTHOR);
@attina
Copy link

attina commented Jan 9, 2019

How to enable GPIO support on Linux sysfs for FT4232 chip?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment