diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-10-18 14:59:04 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-10-18 14:59:04 +0200 |
commit | 89303c7ea770b6010943ef4ed73eb92bdc5a7ec8 (patch) | |
tree | 62c74ff248b2da8bce3e91ce148b1706406ebefa /drivers/usb | |
parent | ef1a2a62cc48c81c68599ff56cc0a97b954c87fe (diff) | |
parent | 17c42e34997ae172c794f84fefe47f00bec13f9a (diff) | |
download | linux-stable-89303c7ea770b6010943ef4ed73eb92bdc5a7ec8.tar.gz linux-stable-89303c7ea770b6010943ef4ed73eb92bdc5a7ec8.tar.bz2 linux-stable-89303c7ea770b6010943ef4ed73eb92bdc5a7ec8.zip |
Merge tag 'usb-serial-4.20-rc1' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial into usb-next
Johan writes:
USB-serial updates for v4.20-rc1
Here are the USB-serial updates for 4.20-rc1, including:
- support for CBUS GPIO on FTDI devices (FTX and FT232R)
- fix of a long-standing transfer-length bug
Included are also various clean ups.
All have been in linux-next with no reported issues.
Signed-off-by: Johan Hovold <johan@kernel.org>
* tag 'usb-serial-4.20-rc1' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial:
USB: serial: cypress_m8: remove set but not used variable 'iflag'
USB: serial: cypress_m8: fix interrupt-out transfer length
USB: serial: ftdi_sio: add support for FT232R CBUS gpios
USB: serial: ftdi_sio: fix gpio name collisions
USB: serial: ftdi_sio: implement GPIO support for FT-X devices
USB: serial: cypress_m8: fix spelling mistake "retreiving" -> "retrieving"
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/serial/cypress_m8.c | 7 | ||||
-rw-r--r-- | drivers/usb/serial/ftdi_sio.c | 391 | ||||
-rw-r--r-- | drivers/usb/serial/ftdi_sio.h | 28 |
3 files changed, 420 insertions, 6 deletions
diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index e0035c023120..ed51bc48eea6 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -378,7 +378,7 @@ static int cypress_serial_control(struct tty_struct *tty, retval = -ENOTTY; goto out; } - dev_dbg(dev, "%s - retreiving serial line settings\n", __func__); + dev_dbg(dev, "%s - retrieving serial line settings\n", __func__); do { retval = usb_control_msg(port->serial->dev, usb_rcvctrlpipe(port->serial->dev, 0), @@ -769,7 +769,7 @@ send: usb_fill_int_urb(port->interrupt_out_urb, port->serial->dev, usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress), - port->interrupt_out_buffer, port->interrupt_out_size, + port->interrupt_out_buffer, actual_size, cypress_write_int_callback, port, priv->write_urb_interval); result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC); if (result) { @@ -863,7 +863,7 @@ static void cypress_set_termios(struct tty_struct *tty, struct cypress_private *priv = usb_get_serial_port_data(port); struct device *dev = &port->dev; int data_bits, stop_bits, parity_type, parity_enable; - unsigned cflag, iflag; + unsigned int cflag; unsigned long flags; __u8 oldlines; int linechange = 0; @@ -899,7 +899,6 @@ static void cypress_set_termios(struct tty_struct *tty, tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS); cflag = tty->termios.c_cflag; - iflag = tty->termios.c_iflag; /* check if there are new settings */ if (old_termios) { diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index b5cef322826f..f1eb20acb3bb 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -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); diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index dcd0b6e05baf..a79a1325b4d9 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -35,7 +35,10 @@ #define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ #define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ #define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ -#define FTDI_SIO_GET_LATENCY_TIMER 10 /* Get the latency timer */ +#define FTDI_SIO_GET_LATENCY_TIMER 0x0a /* Get the latency timer */ +#define FTDI_SIO_SET_BITMODE 0x0b /* Set bitbang mode */ +#define FTDI_SIO_READ_PINS 0x0c /* Read immediate value of pins */ +#define FTDI_SIO_READ_EEPROM 0x90 /* Read EEPROM */ /* Interface indices for FT2232, FT2232H and FT4232H devices */ #define INTERFACE_A 1 @@ -433,6 +436,29 @@ enum ftdi_sio_baudrate { * 1 = active */ +/* FTDI_SIO_SET_BITMODE */ +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */ +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +/* FTDI_SIO_READ_PINS */ +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +/* + * FTDI_SIO_READ_EEPROM + * + * EEPROM format found in FTDI AN_201, "FT-X MTP memory Configuration", + * http://www.ftdichip.com/Support/Documents/AppNotes/AN_201_FT-X%20MTP%20Memory%20Configuration.pdf + */ +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa /* Descriptors returned by the device |