]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
USB: serial: cp210x: Adding GPIO support for CP2105
authorMartyn Welch <martyn.welch@collabora.co.uk>
Thu, 20 Oct 2016 14:13:54 +0000 (15:13 +0100)
committerJohan Hovold <johan@kernel.org>
Mon, 24 Oct 2016 10:00:19 +0000 (12:00 +0200)
This patch adds support for the GPIO found on the CP2105. Unlike the GPIO
provided by some of the other devices supported by the cp210x driver, the
GPIO on the CP2015 is muxed on pins otherwise used for serial control
lines. The GPIO have been configured in 2 separate banks as the choice to
configure the pins for GPIO is made separately for pins shared with each
of the 2 serial ports this device provides, though the choice is made for
all pins associated with that port in one go. The choice of whether to use
the pins for GPIO or serial is made by adding configuration to a one-time
programable PROM in the chip and can not be changed at runtime. The device
defaults to GPIO.

This device supports either push-pull or open-drain modes, it doesn't
provide an explicit input mode, though the state of the GPIO can be read
when used in open-drain mode. Like with pin use, the mode is configured in
the one-time programable PROM and can't be changed at runtime.

Signed-off-by: Martyn Welch <martyn.welch@collabora.co.uk>
Signed-off-by: Johan Hovold <johan@kernel.org>
drivers/usb/serial/cp210x.c

index f5ee4ba4b33bdc538001b999746b1dd95a284b2e..213ed3868fedc56e282e0b5824589f30e59ae57c 100644 (file)
@@ -23,6 +23,9 @@
 #include <linux/usb.h>
 #include <linux/uaccess.h>
 #include <linux/usb/serial.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
 
 #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver"
 
@@ -44,6 +47,9 @@ static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int);
 static int cp210x_tiocmset_port(struct usb_serial_port *port,
                unsigned int, unsigned int);
 static void cp210x_break_ctl(struct tty_struct *, int);
+static int cp210x_attach(struct usb_serial *);
+static void cp210x_disconnect(struct usb_serial *);
+static void cp210x_release(struct usb_serial *);
 static int cp210x_port_probe(struct usb_serial_port *);
 static int cp210x_port_remove(struct usb_serial_port *);
 static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
@@ -208,6 +214,16 @@ static const struct usb_device_id id_table[] = {
 
 MODULE_DEVICE_TABLE(usb, id_table);
 
+struct cp210x_serial_private {
+#ifdef CONFIG_GPIOLIB
+       struct gpio_chip        gc;
+       u8                      config;
+       u8                      gpio_mode;
+       u8                      gpio_registered;
+#endif
+       u8                      partnum;
+};
+
 struct cp210x_port_private {
        __u8                    bInterfaceNumber;
        bool                    has_swapped_line_ctl;
@@ -229,6 +245,9 @@ static struct usb_serial_driver cp210x_device = {
        .tx_empty               = cp210x_tx_empty,
        .tiocmget               = cp210x_tiocmget,
        .tiocmset               = cp210x_tiocmset,
+       .attach                 = cp210x_attach,
+       .disconnect             = cp210x_disconnect,
+       .release                = cp210x_release,
        .port_probe             = cp210x_port_probe,
        .port_remove            = cp210x_port_remove,
        .dtr_rts                = cp210x_dtr_rts
@@ -271,6 +290,7 @@ static struct usb_serial_driver * const serial_drivers[] = {
 #define CP210X_SET_CHARS       0x19
 #define CP210X_GET_BAUDRATE    0x1D
 #define CP210X_SET_BAUDRATE    0x1E
+#define CP210X_VENDOR_SPECIFIC 0xFF
 
 /* CP210X_IFC_ENABLE */
 #define UART_ENABLE            0x0001
@@ -313,6 +333,21 @@ static struct usb_serial_driver * const serial_drivers[] = {
 #define CONTROL_WRITE_DTR      0x0100
 #define CONTROL_WRITE_RTS      0x0200
 
+/* CP210X_VENDOR_SPECIFIC values */
+#define CP210X_READ_LATCH      0x00C2
+#define CP210X_GET_PARTNUM     0x370B
+#define CP210X_GET_PORTCONFIG  0x370C
+#define CP210X_GET_DEVICEMODE  0x3711
+#define CP210X_WRITE_LATCH     0x37E1
+
+/* Part number definitions */
+#define CP210X_PARTNUM_CP2101  0x01
+#define CP210X_PARTNUM_CP2102  0x02
+#define CP210X_PARTNUM_CP2103  0x03
+#define CP210X_PARTNUM_CP2104  0x04
+#define CP210X_PARTNUM_CP2105  0x05
+#define CP210X_PARTNUM_CP2108  0x08
+
 /* CP210X_GET_COMM_STATUS returns these 0x13 bytes */
 struct cp210x_comm_status {
        __le32   ulErrors;
@@ -368,6 +403,60 @@ struct cp210x_flow_ctl {
 #define CP210X_SERIAL_RTS_ACTIVE       1
 #define CP210X_SERIAL_RTS_FLOW_CTL     2
 
+/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */
+struct cp210x_pin_mode {
+       u8      eci;
+       u8      sci;
+} __packed;
+
+#define CP210X_PIN_MODE_MODEM          0
+#define CP210X_PIN_MODE_GPIO           BIT(0)
+
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xf bytes.
+ * Structure needs padding due to unused/unspecified bytes.
+ */
+struct cp210x_config {
+       __le16  gpio_mode;
+       u8      __pad0[2];
+       __le16  reset_state;
+       u8      __pad1[4];
+       __le16  suspend_state;
+       u8      sci_cfg;
+       u8      eci_cfg;
+       u8      device_cfg;
+} __packed;
+
+/* GPIO modes */
+#define CP210X_SCI_GPIO_MODE_OFFSET    9
+#define CP210X_SCI_GPIO_MODE_MASK      GENMASK(11, 9)
+
+#define CP210X_ECI_GPIO_MODE_OFFSET    2
+#define CP210X_ECI_GPIO_MODE_MASK      GENMASK(3, 2)
+
+/* CP2105 port configuration values */
+#define CP2105_GPIO0_TXLED_MODE                BIT(0)
+#define CP2105_GPIO1_RXLED_MODE                BIT(1)
+#define CP2105_GPIO1_RS485_MODE                BIT(2)
+
+/* CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x2 bytes. */
+struct cp210x_gpio_write {
+       u8      mask;
+       u8      state;
+} __packed;
+
+/*
+ * Helper to get interface number when we only have struct usb_serial.
+ */
+static u8 cp210x_interface_num(struct usb_serial *serial)
+{
+       struct usb_host_interface *cur_altsetting;
+
+       cur_altsetting = serial->interface->cur_altsetting;
+
+       return cur_altsetting->desc.bInterfaceNumber;
+}
+
 /*
  * Reads a variable-sized block of CP210X_ registers, identified by req.
  * Returns data into buf in native USB byte order.
@@ -463,6 +552,40 @@ static int cp210x_read_u8_reg(struct usb_serial_port *port, u8 req, u8 *val)
        return cp210x_read_reg_block(port, req, val, sizeof(*val));
 }
 
+/*
+ * Reads a variable-sized vendor block of CP210X_ registers, identified by val.
+ * Returns data into buf in native USB byte order.
+ */
+static int cp210x_read_vendor_block(struct usb_serial *serial, u8 type, u16 val,
+                                   void *buf, int bufsize)
+{
+       void *dmabuf;
+       int result;
+
+       dmabuf = kmalloc(bufsize, GFP_KERNEL);
+       if (!dmabuf)
+               return -ENOMEM;
+
+       result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+                                CP210X_VENDOR_SPECIFIC, type, val,
+                                cp210x_interface_num(serial), dmabuf, bufsize,
+                                USB_CTRL_GET_TIMEOUT);
+       if (result == bufsize) {
+               memcpy(buf, dmabuf, bufsize);
+               result = 0;
+       } else {
+               dev_err(&serial->interface->dev,
+                       "failed to get vendor val 0x%04x size %d: %d\n", val,
+                       bufsize, result);
+               if (result >= 0)
+                       result = -EIO;
+       }
+
+       kfree(dmabuf);
+
+       return result;
+}
+
 /*
  * Writes any 16-bit CP210X_ register (req) whose value is passed
  * entirely in the wValue field of the USB request.
@@ -532,6 +655,42 @@ static int cp210x_write_u32_reg(struct usb_serial_port *port, u8 req, u32 val)
        return cp210x_write_reg_block(port, req, &le32_val, sizeof(le32_val));
 }
 
+#ifdef CONFIG_GPIOLIB
+/*
+ * Writes a variable-sized vendor block of CP210X_ registers, identified by val.
+ * Data in buf must be in native USB byte order.
+ */
+static int cp210x_write_vendor_block(struct usb_serial *serial, u8 type,
+                                    u16 val, void *buf, int bufsize)
+{
+       void *dmabuf;
+       int result;
+
+       dmabuf = kmemdup(buf, bufsize, GFP_KERNEL);
+       if (!dmabuf)
+               return -ENOMEM;
+
+       result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+                                CP210X_VENDOR_SPECIFIC, type, val,
+                                cp210x_interface_num(serial), dmabuf, bufsize,
+                                USB_CTRL_SET_TIMEOUT);
+
+       kfree(dmabuf);
+
+       if (result == bufsize) {
+               result = 0;
+       } else {
+               dev_err(&serial->interface->dev,
+                       "failed to set vendor val 0x%04x size %d: %d\n", val,
+                       bufsize, result);
+               if (result >= 0)
+                       result = -EIO;
+       }
+
+       return result;
+}
+#endif
+
 /*
  * Detect CP2108 GET_LINE_CTL bug and activate workaround.
  * Write a known good value 0x800, read it back.
@@ -1098,10 +1257,188 @@ static void cp210x_break_ctl(struct tty_struct *tty, int break_state)
        cp210x_write_u16_reg(port, CP210X_SET_BREAK, state);
 }
 
+#ifdef CONFIG_GPIOLIB
+static int cp210x_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+       struct usb_serial *serial = gpiochip_get_data(gc);
+       struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+       switch (offset) {
+       case 0:
+               if (priv->config & CP2105_GPIO0_TXLED_MODE)
+                       return -ENODEV;
+               break;
+       case 1:
+               if (priv->config & (CP2105_GPIO1_RXLED_MODE |
+                                   CP2105_GPIO1_RS485_MODE))
+                       return -ENODEV;
+               break;
+       }
+
+       return 0;
+}
+
+static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+       struct usb_serial *serial = gpiochip_get_data(gc);
+       int result;
+       u8 buf;
+
+       result = cp210x_read_vendor_block(serial, REQTYPE_INTERFACE_TO_HOST,
+                                         CP210X_READ_LATCH, &buf, sizeof(buf));
+       if (result < 0)
+               return result;
+
+       return !!(buf & BIT(gpio));
+}
+
+static void cp210x_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
+{
+       struct usb_serial *serial = gpiochip_get_data(gc);
+       struct cp210x_gpio_write buf;
+
+       if (value == 1)
+               buf.state = BIT(gpio);
+       else
+               buf.state = 0;
+
+       buf.mask = BIT(gpio);
+
+       cp210x_write_vendor_block(serial, REQTYPE_HOST_TO_INTERFACE,
+                                 CP210X_WRITE_LATCH, &buf, sizeof(buf));
+}
+
+static int cp210x_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio)
+{
+       /* Hardware does not support an input mode */
+       return 0;
+}
+
+static int cp210x_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
+{
+       /* Hardware does not support an input mode */
+       return -ENOTSUPP;
+}
+
+static int cp210x_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
+                                       int value)
+{
+       return 0;
+}
+
+static int cp210x_gpio_set_single_ended(struct gpio_chip *gc, unsigned int gpio,
+                                       enum single_ended_mode mode)
+{
+       struct usb_serial *serial = gpiochip_get_data(gc);
+       struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+       /* Succeed only if in correct mode (this can't be set at runtime) */
+       if ((mode == LINE_MODE_PUSH_PULL) && (priv->gpio_mode & BIT(gpio)))
+               return 0;
+
+       if ((mode == LINE_MODE_OPEN_DRAIN) && !(priv->gpio_mode & BIT(gpio)))
+               return 0;
+
+       return -ENOTSUPP;
+}
+
+/*
+ * This function is for configuring GPIO using shared pins, where other signals
+ * are made unavailable by configuring the use of GPIO. This is believed to be
+ * only applicable to the cp2105 at this point, the other devices supported by
+ * this driver that provide GPIO do so in a way that does not impact other
+ * signals and are thus expected to have very different initialisation.
+ */
+static int cp2105_shared_gpio_init(struct usb_serial *serial)
+{
+       struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+       struct cp210x_pin_mode mode;
+       struct cp210x_config config;
+       u8 intf_num = cp210x_interface_num(serial);
+       int result;
+
+       result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+                                         CP210X_GET_DEVICEMODE, &mode,
+                                         sizeof(mode));
+       if (result < 0)
+               return result;
+
+       result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+                                         CP210X_GET_PORTCONFIG, &config,
+                                         sizeof(config));
+       if (result < 0)
+               return result;
+
+       /*  2 banks of GPIO - One for the pins taken from each serial port */
+       if (intf_num == 0) {
+               if (mode.eci == CP210X_PIN_MODE_MODEM)
+                       return 0;
+
+               priv->config = config.eci_cfg;
+               priv->gpio_mode = (u8)((le16_to_cpu(config.gpio_mode) &
+                                               CP210X_ECI_GPIO_MODE_MASK) >>
+                                               CP210X_ECI_GPIO_MODE_OFFSET);
+               priv->gc.ngpio = 2;
+       } else if (intf_num == 1) {
+               if (mode.sci == CP210X_PIN_MODE_MODEM)
+                       return 0;
+
+               priv->config = config.sci_cfg;
+               priv->gpio_mode = (u8)((le16_to_cpu(config.gpio_mode) &
+                                               CP210X_SCI_GPIO_MODE_MASK) >>
+                                               CP210X_SCI_GPIO_MODE_OFFSET);
+               priv->gc.ngpio = 3;
+       } else {
+               return -ENODEV;
+       }
+
+       priv->gc.label = "cp210x";
+       priv->gc.request = cp210x_gpio_request;
+       priv->gc.get_direction = cp210x_gpio_direction_get;
+       priv->gc.direction_input = cp210x_gpio_direction_input;
+       priv->gc.direction_output = cp210x_gpio_direction_output;
+       priv->gc.get = cp210x_gpio_get;
+       priv->gc.set = cp210x_gpio_set;
+       priv->gc.set_single_ended = cp210x_gpio_set_single_ended;
+       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, serial);
+       if (!result)
+               priv->gpio_registered = 1;
+
+       return result;
+}
+
+static void cp210x_gpio_remove(struct usb_serial *serial)
+{
+       struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+       if (priv->gpio_registered) {
+               gpiochip_remove(&priv->gc);
+               priv->gpio_registered = 0;
+       }
+}
+
+#else
+
+static int cp2105_shared_gpio_init(struct usb_serial *serial)
+{
+       return 0;
+}
+
+static void cp210x_gpio_remove(struct usb_serial *serial)
+{
+       /* Nothing to do */
+}
+
+#endif
+
 static int cp210x_port_probe(struct usb_serial_port *port)
 {
        struct usb_serial *serial = port->serial;
-       struct usb_host_interface *cur_altsetting;
        struct cp210x_port_private *port_priv;
        int ret;
 
@@ -1109,8 +1446,7 @@ static int cp210x_port_probe(struct usb_serial_port *port)
        if (!port_priv)
                return -ENOMEM;
 
-       cur_altsetting = serial->interface->cur_altsetting;
-       port_priv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber;
+       port_priv->bInterfaceNumber = cp210x_interface_num(serial);
 
        usb_set_serial_port_data(port, port_priv);
 
@@ -1133,6 +1469,52 @@ static int cp210x_port_remove(struct usb_serial_port *port)
        return 0;
 }
 
+static int cp210x_attach(struct usb_serial *serial)
+{
+       int result;
+       struct cp210x_serial_private *priv;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+                                         CP210X_GET_PARTNUM, &priv->partnum,
+                                         sizeof(priv->partnum));
+       if (result < 0)
+               goto err_free_priv;
+
+       usb_set_serial_data(serial, priv);
+
+       if (priv->partnum == CP210X_PARTNUM_CP2105) {
+               result = cp2105_shared_gpio_init(serial);
+               if (result < 0) {
+                       dev_err(&serial->interface->dev,
+                               "GPIO initialisation failed, continuing without GPIO support\n");
+               }
+       }
+
+       return 0;
+err_free_priv:
+       kfree(priv);
+
+       return result;
+}
+
+static void cp210x_disconnect(struct usb_serial *serial)
+{
+       cp210x_gpio_remove(serial);
+}
+
+static void cp210x_release(struct usb_serial *serial)
+{
+       struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+       cp210x_gpio_remove(serial);
+
+       kfree(priv);
+}
+
 module_usb_serial_driver(serial_drivers, id_table);
 
 MODULE_DESCRIPTION(DRIVER_DESC);