|
@@ -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);
|