|
@@ -39,6 +39,7 @@
|
|
|
#include <linux/uaccess.h>
|
|
|
#include <linux/usb.h>
|
|
|
#include <linux/serial.h>
|
|
|
+#include <linux/gpio/driver.h>
|
|
|
#include <linux/usb/serial.h>
|
|
|
#include "ftdi_sio.h"
|
|
|
#include "ftdi_sio_ids.h"
|
|
@@ -72,6 +73,15 @@ struct ftdi_private {
|
|
|
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;
|
|
|
+ struct mutex gpio_lock; /* protects GPIO state */
|
|
|
+ bool gpio_registered; /* is the gpiochip in kernel registered */
|
|
|
+ bool gpio_used; /* true if the user requested a gpio */
|
|
|
+ u8 gpio_altfunc; /* which pins are in gpio mode */
|
|
|
+ u8 gpio_output; /* pin directions cache */
|
|
|
+ u8 gpio_value; /* pin value for outputs */
|
|
|
+#endif
|
|
|
};
|
|
|
|
|
|
/* struct ftdi_sio_quirk is used by devices requiring special attention. */
|
|
@@ -1766,6 +1776,375 @@ static void remove_sysfs_attrs(struct usb_serial_port *port)
|
|
|
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_GPIOLIB
|
|
|
+
|
|
|
+static int ftdi_set_bitmode(struct usb_serial_port *port, u8 mode)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ int result;
|
|
|
+ u16 val;
|
|
|
+
|
|
|
+ val = (mode << 8) | (priv->gpio_output << 4) | priv->gpio_value;
|
|
|
+ result = usb_control_msg(serial->dev,
|
|
|
+ usb_sndctrlpipe(serial->dev, 0),
|
|
|
+ FTDI_SIO_SET_BITMODE_REQUEST,
|
|
|
+ FTDI_SIO_SET_BITMODE_REQUEST_TYPE, val,
|
|
|
+ priv->interface, NULL, 0, WDR_TIMEOUT);
|
|
|
+ if (result < 0) {
|
|
|
+ dev_err(&serial->interface->dev,
|
|
|
+ "bitmode request failed for value 0x%04x: %d\n",
|
|
|
+ val, result);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_set_cbus_pins(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_CBUS);
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_exit_cbus_mode(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+
|
|
|
+ priv->gpio_output = 0;
|
|
|
+ priv->gpio_value = 0;
|
|
|
+ return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_RESET);
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (priv->gpio_altfunc & BIT(offset))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ mutex_lock(&priv->gpio_lock);
|
|
|
+ if (!priv->gpio_used) {
|
|
|
+ /* Set default pin states, as we cannot get them from device */
|
|
|
+ priv->gpio_output = 0x00;
|
|
|
+ priv->gpio_value = 0x00;
|
|
|
+ result = ftdi_set_cbus_pins(port);
|
|
|
+ if (result) {
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ priv->gpio_used = true;
|
|
|
+ }
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_read_cbus_pins(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ unsigned char *buf;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ buf = kmalloc(1, GFP_KERNEL);
|
|
|
+ if (!buf)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ result = usb_control_msg(serial->dev,
|
|
|
+ usb_rcvctrlpipe(serial->dev, 0),
|
|
|
+ FTDI_SIO_READ_PINS_REQUEST,
|
|
|
+ FTDI_SIO_READ_PINS_REQUEST_TYPE, 0,
|
|
|
+ priv->interface, buf, 1, WDR_TIMEOUT);
|
|
|
+ if (result < 1) {
|
|
|
+ if (result >= 0)
|
|
|
+ result = -EIO;
|
|
|
+ } else {
|
|
|
+ result = buf[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(buf);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ int result;
|
|
|
+
|
|
|
+ result = ftdi_read_cbus_pins(port);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ return !!(result & BIT(gpio));
|
|
|
+}
|
|
|
+
|
|
|
+static void ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+
|
|
|
+ mutex_lock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ if (value)
|
|
|
+ priv->gpio_value |= BIT(gpio);
|
|
|
+ else
|
|
|
+ priv->gpio_value &= ~BIT(gpio);
|
|
|
+
|
|
|
+ ftdi_set_cbus_pins(port);
|
|
|
+
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask,
|
|
|
+ unsigned long *bits)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ int result;
|
|
|
+
|
|
|
+ result = ftdi_read_cbus_pins(port);
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ *bits = result & *mask;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ftdi_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
|
|
|
+ unsigned long *bits)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+
|
|
|
+ mutex_lock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ priv->gpio_value &= ~(*mask);
|
|
|
+ priv->gpio_value |= *bits & *mask;
|
|
|
+ ftdi_set_cbus_pins(port);
|
|
|
+
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+
|
|
|
+ return !(priv->gpio_output & BIT(gpio));
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ int result;
|
|
|
+
|
|
|
+ mutex_lock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ priv->gpio_output &= ~BIT(gpio);
|
|
|
+ result = ftdi_set_cbus_pins(port);
|
|
|
+
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
|
|
|
+ int value)
|
|
|
+{
|
|
|
+ struct usb_serial_port *port = gpiochip_get_data(gc);
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ int result;
|
|
|
+
|
|
|
+ mutex_lock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ priv->gpio_output |= BIT(gpio);
|
|
|
+ if (value)
|
|
|
+ priv->gpio_value |= BIT(gpio);
|
|
|
+ else
|
|
|
+ priv->gpio_value &= ~BIT(gpio);
|
|
|
+
|
|
|
+ result = ftdi_set_cbus_pins(port);
|
|
|
+
|
|
|
+ mutex_unlock(&priv->gpio_lock);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr,
|
|
|
+ u16 nbytes)
|
|
|
+{
|
|
|
+ int read = 0;
|
|
|
+
|
|
|
+ if (addr % 2 != 0)
|
|
|
+ return -EINVAL;
|
|
|
+ if (nbytes % 2 != 0)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Read EEPROM two bytes at a time */
|
|
|
+ while (read < nbytes) {
|
|
|
+ int rv;
|
|
|
+
|
|
|
+ rv = usb_control_msg(serial->dev,
|
|
|
+ usb_rcvctrlpipe(serial->dev, 0),
|
|
|
+ FTDI_SIO_READ_EEPROM_REQUEST,
|
|
|
+ FTDI_SIO_READ_EEPROM_REQUEST_TYPE,
|
|
|
+ 0, (addr + read) / 2, dst + read, 2,
|
|
|
+ WDR_TIMEOUT);
|
|
|
+ if (rv < 2) {
|
|
|
+ if (rv >= 0)
|
|
|
+ return -EIO;
|
|
|
+ else
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ read += rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_init_ft232r(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ u16 cbus_config;
|
|
|
+ u8 *buf;
|
|
|
+ int ret;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ buf = kmalloc(2, GFP_KERNEL);
|
|
|
+ if (!buf)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ret = ftdi_read_eeprom(port->serial, buf, 0x14, 2);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out_free;
|
|
|
+
|
|
|
+ cbus_config = le16_to_cpup((__le16 *)buf);
|
|
|
+ dev_dbg(&port->dev, "cbus_config = 0x%04x\n", cbus_config);
|
|
|
+
|
|
|
+ priv->gc.ngpio = 4;
|
|
|
+
|
|
|
+ priv->gpio_altfunc = 0xff;
|
|
|
+ for (i = 0; i < priv->gc.ngpio; ++i) {
|
|
|
+ if ((cbus_config & 0xf) == FTDI_FT232R_CBUS_MUX_GPIO)
|
|
|
+ priv->gpio_altfunc &= ~BIT(i);
|
|
|
+ cbus_config >>= 4;
|
|
|
+ }
|
|
|
+out_free:
|
|
|
+ kfree(buf);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_init_ftx(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ const u16 cbus_cfg_addr = 0x1a;
|
|
|
+ const u16 cbus_cfg_size = 4;
|
|
|
+ u8 *cbus_cfg_buf;
|
|
|
+ int result;
|
|
|
+ u8 i;
|
|
|
+
|
|
|
+ cbus_cfg_buf = kmalloc(cbus_cfg_size, GFP_KERNEL);
|
|
|
+ if (!cbus_cfg_buf)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ result = ftdi_read_eeprom(serial, cbus_cfg_buf,
|
|
|
+ cbus_cfg_addr, cbus_cfg_size);
|
|
|
+ if (result < 0)
|
|
|
+ goto out_free;
|
|
|
+
|
|
|
+ /* FIXME: FT234XD alone has 1 GPIO, but how to recognize this IC? */
|
|
|
+ priv->gc.ngpio = 4;
|
|
|
+
|
|
|
+ /* Determine which pins are configured for CBUS bitbanging */
|
|
|
+ priv->gpio_altfunc = 0xff;
|
|
|
+ for (i = 0; i < priv->gc.ngpio; ++i) {
|
|
|
+ if (cbus_cfg_buf[i] == FTDI_FTX_CBUS_MUX_GPIO)
|
|
|
+ priv->gpio_altfunc &= ~BIT(i);
|
|
|
+ }
|
|
|
+
|
|
|
+out_free:
|
|
|
+ kfree(cbus_cfg_buf);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int ftdi_gpio_init(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ int result;
|
|
|
+
|
|
|
+ switch (priv->chip_type) {
|
|
|
+ case FT232RL:
|
|
|
+ result = ftdi_gpio_init_ft232r(port);
|
|
|
+ break;
|
|
|
+ case FTX:
|
|
|
+ result = ftdi_gpio_init_ftx(port);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result < 0)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ mutex_init(&priv->gpio_lock);
|
|
|
+
|
|
|
+ priv->gc.label = "ftdi-cbus";
|
|
|
+ priv->gc.request = ftdi_gpio_request;
|
|
|
+ priv->gc.get_direction = ftdi_gpio_direction_get;
|
|
|
+ priv->gc.direction_input = ftdi_gpio_direction_input;
|
|
|
+ priv->gc.direction_output = ftdi_gpio_direction_output;
|
|
|
+ priv->gc.get = ftdi_gpio_get;
|
|
|
+ priv->gc.set = ftdi_gpio_set;
|
|
|
+ priv->gc.get_multiple = ftdi_gpio_get_multiple;
|
|
|
+ priv->gc.set_multiple = ftdi_gpio_set_multiple;
|
|
|
+ priv->gc.owner = THIS_MODULE;
|
|
|
+ priv->gc.parent = &serial->interface->dev;
|
|
|
+ priv->gc.base = -1;
|
|
|
+ priv->gc.can_sleep = true;
|
|
|
+
|
|
|
+ result = gpiochip_add_data(&priv->gc, port);
|
|
|
+ if (!result)
|
|
|
+ priv->gpio_registered = true;
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static void ftdi_gpio_remove(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
+
|
|
|
+ if (priv->gpio_registered) {
|
|
|
+ gpiochip_remove(&priv->gc);
|
|
|
+ priv->gpio_registered = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (priv->gpio_used) {
|
|
|
+ /* Exiting CBUS-mode does not reset pin states. */
|
|
|
+ ftdi_exit_cbus_mode(port);
|
|
|
+ priv->gpio_used = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#else
|
|
|
+
|
|
|
+static int ftdi_gpio_init(struct usb_serial_port *port)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ftdi_gpio_remove(struct usb_serial_port *port) { }
|
|
|
+
|
|
|
+#endif /* CONFIG_GPIOLIB */
|
|
|
+
|
|
|
/*
|
|
|
* ***************************************************************************
|
|
|
* FTDI driver specific functions
|
|
@@ -1794,7 +2173,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
|
|
|
{
|
|
|
struct ftdi_private *priv;
|
|
|
const struct ftdi_sio_quirk *quirk = usb_get_serial_data(port->serial);
|
|
|
-
|
|
|
+ int result;
|
|
|
|
|
|
priv = kzalloc(sizeof(struct ftdi_private), GFP_KERNEL);
|
|
|
if (!priv)
|
|
@@ -1813,6 +2192,14 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
|
|
|
priv->latency = 16;
|
|
|
write_latency_timer(port);
|
|
|
create_sysfs_attrs(port);
|
|
|
+
|
|
|
+ result = ftdi_gpio_init(port);
|
|
|
+ if (result < 0) {
|
|
|
+ dev_err(&port->serial->interface->dev,
|
|
|
+ "GPIO initialisation failed: %d\n",
|
|
|
+ result);
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1930,6 +2317,8 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port)
|
|
|
{
|
|
|
struct ftdi_private *priv = usb_get_serial_port_data(port);
|
|
|
|
|
|
+ ftdi_gpio_remove(port);
|
|
|
+
|
|
|
remove_sysfs_attrs(port);
|
|
|
|
|
|
kfree(priv);
|