]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/usb/serial/cp210x.c
Merge tag 'usb-serial-4.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[karo-tx-linux.git] / drivers / usb / serial / cp210x.c
index 243ac5ebe46a02560d5cbe3ca8fd97fe4e5eb721..fff718352e0cda323d4d5d30817a92af36e9de16 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"
 
@@ -33,7 +36,7 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *);
 static void cp210x_close(struct usb_serial_port *);
 static void cp210x_get_termios(struct tty_struct *, struct usb_serial_port *);
 static void cp210x_get_termios_port(struct usb_serial_port *port,
-       unsigned int *cflagp, unsigned int *baudp);
+       tcflag_t *cflagp, unsigned int *baudp);
 static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *,
                                                        struct ktermios *);
 static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *,
@@ -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);
@@ -209,6 +215,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;
+       bool                    gpio_registered;
+#endif
+       u8                      partnum;
+};
+
 struct cp210x_port_private {
        __u8                    bInterfaceNumber;
        bool                    has_swapped_line_ctl;
@@ -230,6 +246,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
@@ -272,6 +291,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
@@ -314,6 +334,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;
@@ -369,6 +404,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.
@@ -402,7 +491,7 @@ static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req,
                dev_err(&port->dev, "failed get req 0x%x size %d status: %d\n",
                                req, bufsize, result);
                if (result >= 0)
-                       result = -EPROTO;
+                       result = -EIO;
 
                /*
                 * FIXME Some callers don't bother to check for error,
@@ -464,6 +553,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.
@@ -515,7 +638,7 @@ static int cp210x_write_reg_block(struct usb_serial_port *port, u8 req,
                dev_err(&port->dev, "failed set req 0x%x size %d status: %d\n",
                                req, bufsize, result);
                if (result >= 0)
-                       result = -EPROTO;
+                       result = -EIO;
        }
 
        return result;
@@ -533,6 +656,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.
@@ -683,7 +842,7 @@ static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port,
        } else {
                dev_err(&port->dev, "failed to get comm status: %d\n", result);
                if (result >= 0)
-                       result = -EPROTO;
+                       result = -EIO;
        }
 
        kfree(sts);
@@ -719,7 +878,7 @@ static void cp210x_get_termios(struct tty_struct *tty,
                        &tty->termios.c_cflag, &baud);
                tty_encode_baud_rate(tty, baud, baud);
        } else {
-               unsigned int cflag;
+               tcflag_t cflag;
                cflag = 0;
                cp210x_get_termios_port(port, &cflag, &baud);
        }
@@ -730,10 +889,10 @@ static void cp210x_get_termios(struct tty_struct *tty,
  * This is the heart of cp210x_get_termios which always uses a &usb_serial_port.
  */
 static void cp210x_get_termios_port(struct usb_serial_port *port,
-       unsigned int *cflagp, unsigned int *baudp)
+       tcflag_t *cflagp, unsigned int *baudp)
 {
        struct device *dev = &port->dev;
-       unsigned int cflag;
+       tcflag_t cflag;
        struct cp210x_flow_ctl flow_ctl;
        u32 baud;
        u16 bits;
@@ -930,16 +1089,9 @@ static void cp210x_set_termios(struct tty_struct *tty,
                        dev_dbg(dev, "%s - data bits = 7\n", __func__);
                        break;
                case CS8:
-                       bits |= BITS_DATA_8;
-                       dev_dbg(dev, "%s - data bits = 8\n", __func__);
-                       break;
-               /*case CS9:
-                       bits |= BITS_DATA_9;
-                       dev_dbg(dev, "%s - data bits = 9\n", __func__);
-                       break;*/
                default:
-                       dev_dbg(dev, "cp210x driver does not support the number of bits requested, using 8 bit mode\n");
                        bits |= BITS_DATA_8;
+                       dev_dbg(dev, "%s - data bits = 8\n", __func__);
                        break;
                }
                if (cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits))
@@ -1108,10 +1260,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 = true;
+
+       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 = false;
+       }
+}
+
+#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;
 
@@ -1119,8 +1449,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);
 
@@ -1143,6 +1472,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);