[PATCH] usb: cp210x: Added support for GPIO (CP2103/4/5)

From: Preston Fick
Date: Sat Apr 28 2012 - 12:55:43 EST


This fix contains several changes that allow toggling of GPIO on CP210x
devices that support it. Changes inclue:
* Added in part number support, necessary to see if the connected device
supports the GPIO functionality
* Added two IOCTLs and ioctl function to allow GET/SET of GPIO
* Modified cp210x_get/set_config functions to allow for both interface and
device requests (also modified all calling functions)
* Removed cp210x_set_config_single since the new cp210x_set_config allows for
variable length requests to be sent
* Added in new #defines for partnum support, new USB requests and changed
"Config request types" section to contain more correct definitions

Signed-off-by: Preston Fick <preston.fick@xxxxxxxxxx>
---
drivers/usb/serial/cp210x.c | 239 ++++++++++++++++++++++++++++++-------------
1 files changed, 170 insertions(+), 69 deletions(-)

diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c
index ec30f95..188eb78 100644
--- a/drivers/usb/serial/cp210x.c
+++ b/drivers/usb/serial/cp210x.c
@@ -35,6 +35,8 @@
*/
static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *);
static void cp210x_close(struct usb_serial_port *);
+static int cp210x_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg);
static void cp210x_get_termios(struct tty_struct *,
struct usb_serial_port *port);
static void cp210x_get_termios_port(struct usb_serial_port *port,
@@ -154,6 +156,7 @@ MODULE_DEVICE_TABLE(usb, id_table);

struct cp210x_port_private {
__u8 bInterfaceNumber;
+ __u8 bPartNumber;
};

static struct usb_driver cp210x_driver = {
@@ -174,6 +177,7 @@ static struct usb_serial_driver cp210x_device = {
.bulk_out_size = 256,
.open = cp210x_open,
.close = cp210x_close,
+ .ioctl = cp210x_ioctl,
.break_ctl = cp210x_break_ctl,
.set_termios = cp210x_set_termios,
.tiocmget = cp210x_tiocmget,
@@ -187,9 +191,22 @@ static struct usb_serial_driver * const serial_drivers[] = {
&cp210x_device, NULL
};

+/* Part number definitions */
+#define CP2101_PARTNUM 0x01
+#define CP2102_PARTNUM 0x02
+#define CP2103_PARTNUM 0x03
+#define CP2104_PARTNUM 0x04
+#define CP2105_PARTNUM 0x05
+
+/* IOCTLs */
+#define IOCTL_GPIOGET 0x8000
+#define IOCTL_GPIOSET 0x8001
+
/* Config request types */
-#define REQTYPE_HOST_TO_DEVICE 0x41
-#define REQTYPE_DEVICE_TO_HOST 0xc1
+#define REQTYPE_HOST_TO_INTERFACE 0x41
+#define REQTYPE_INTERFACE_TO_HOST 0xc1
+#define REQTYPE_HOST_TO_DEVICE 0x40
+#define REQTYPE_DEVICE_TO_HOST 0xc0

/* Config request codes */
#define CP210X_IFC_ENABLE 0x00
@@ -218,11 +235,17 @@ 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
#define UART_DISABLE 0x0000

+/* CP210X_VENDOR_SPECIFIC */
+#define CP210X_WRITE_LATCH 0x37E1
+#define CP210X_READ_LATCH 0x00C2
+#define CP210X_GET_PARTNUM 0x370B
+
/* CP210X_(SET|GET)_BAUDDIV */
#define BAUD_RATE_GEN_FREQ 0x384000

@@ -267,8 +290,8 @@ static struct usb_serial_driver * const serial_drivers[] = {
* 'data' is a pointer to a pre-allocated array of integers large
* enough to hold 'size' bytes (with 4 bytes to each integer)
*/
-static int cp210x_get_config(struct usb_serial_port *port, u8 request,
- unsigned int *data, int size)
+static int cp210x_get_config(struct usb_serial_port *port, u8 requestType,
+ u8 request, int value, unsigned int *data, int size)
{
struct usb_serial *serial = port->serial;
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
@@ -286,7 +309,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,

/* Issue the request, attempting to read 'size' bytes */
result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
- request, REQTYPE_DEVICE_TO_HOST, 0x0000,
+ request, requestType, value,
port_priv->bInterfaceNumber, buf, size,
USB_CTRL_GET_TIMEOUT);

@@ -315,45 +338,39 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,
* Values less than 16 bits wide are sent directly
* 'size' is specified in bytes.
*/
-static int cp210x_set_config(struct usb_serial_port *port, u8 request,
- unsigned int *data, int size)
+static int cp210x_set_config(struct usb_serial_port *port, u8 requestType,
+ u8 request, int value, unsigned int *data, int size)
{
struct usb_serial *serial = port->serial;
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
- __le32 *buf;
- int result, i, length;
+ __le32 *buf = NULL;
+ int result, i, length = 0;

- /* Number of integers required to contain the array */
- length = (((size - 1) | 3) + 1)/4;
-
- buf = kmalloc(length * sizeof(__le32), GFP_KERNEL);
- if (!buf) {
- dev_err(&port->dev, "%s - out of memory.\n",
- __func__);
- return -ENOMEM;
- }
+ if (size) {
+ /* Number of integers required to contain the array */
+ length = (((size - 1) | 3) + 1)/4;

- /* Array of integers into bytes */
- for (i = 0; i < length; i++)
- buf[i] = cpu_to_le32(data[i]);
+ buf = kmalloc(length * sizeof(__le32), GFP_KERNEL);
+ if (!buf) {
+ dev_err(&port->dev, "%s - out of memory.\n",
+ __func__);
+ return -ENOMEM;
+ }

- if (size > 2) {
- result = usb_control_msg(serial->dev,
- usb_sndctrlpipe(serial->dev, 0),
- request, REQTYPE_HOST_TO_DEVICE, 0x0000,
- port_priv->bInterfaceNumber, buf, size,
- USB_CTRL_SET_TIMEOUT);
- } else {
- result = usb_control_msg(serial->dev,
- usb_sndctrlpipe(serial->dev, 0),
- request, REQTYPE_HOST_TO_DEVICE, data[0],
- port_priv->bInterfaceNumber, NULL, 0,
- USB_CTRL_SET_TIMEOUT);
+ /* Array of integers into bytes */
+ for (i = 0; i < length; i++)
+ buf[i] = cpu_to_le32(data[i]);
}

+ result = usb_control_msg(serial->dev,
+ usb_sndctrlpipe(serial->dev, 0),
+ request, requestType, value,
+ port_priv->bInterfaceNumber, buf, size,
+ USB_CTRL_SET_TIMEOUT);
+
kfree(buf);

- if ((size > 2 && result != size) || result < 0) {
+ if (result != size) {
dbg("%s - Unable to send request, "
"request=0x%x size=%d result=%d",
__func__, request, size, result);
@@ -367,17 +384,6 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request,
}

/*
- * cp210x_set_config_single
- * Convenience function for calling cp210x_set_config on single data values
- * without requiring an integer pointer
- */
-static inline int cp210x_set_config_single(struct usb_serial_port *port,
- u8 request, unsigned int data)
-{
- return cp210x_set_config(port, request, &data, 2);
-}
-
-/*
* cp210x_quantise_baudrate
* Quantises the baud rate as per AN205 Table 1
*/
@@ -424,8 +430,8 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)

dbg("%s - port %d", __func__, port->number);

- result = cp210x_set_config_single(port, CP210X_IFC_ENABLE,
- UART_ENABLE);
+ result = cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_IFC_ENABLE, UART_ENABLE, NULL, 0);
if (result) {
dev_err(&port->dev, "%s - Unable to enable UART\n", __func__);
return result;
@@ -449,10 +455,79 @@ static void cp210x_close(struct usb_serial_port *port)

mutex_lock(&port->serial->disc_mutex);
if (!port->serial->disconnected)
- cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_IFC_ENABLE, UART_DISABLE, NULL, 0);
mutex_unlock(&port->serial->disc_mutex);
}

+static int cp210x_ioctl(struct tty_struct *tty,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int result = 0;
+ unsigned int latch_mask_setting_to_write = 0;
+
+ switch (cmd) {
+
+ case IOCTL_GPIOGET:
+ if ((port_priv->bPartNumber == CP2103_PARTNUM) ||
+ (port_priv->bPartNumber == CP2104_PARTNUM)) {
+ return cp210x_get_config(port, REQTYPE_DEVICE_TO_HOST,
+ CP210X_VENDOR_SPECIFIC,
+ CP210X_READ_LATCH,
+ (unsigned int *)arg, 1);
+ } else if (port_priv->bPartNumber == CP2105_PARTNUM) {
+ return cp210x_get_config(port,
+ REQTYPE_INTERFACE_TO_HOST,
+ CP210X_VENDOR_SPECIFIC,
+ CP210X_READ_LATCH,
+ (unsigned int *)arg, 1);
+ } else {
+ return -ENOTSUPP;
+ }
+ break;
+
+ case IOCTL_GPIOSET:
+ if ((port_priv->bPartNumber == CP2103_PARTNUM) ||
+ (port_priv->bPartNumber == CP2104_PARTNUM)) {
+ latch_mask_setting_to_write =
+ *(unsigned int *)arg & 0x000000FF;
+ latch_mask_setting_to_write |=
+ (*(unsigned int *)arg & 0x00FF0000) >> 8;
+ result = usb_control_msg(port->serial->dev,
+ usb_sndctrlpipe(port->serial->dev, 0),
+ CP210X_VENDOR_SPECIFIC,
+ REQTYPE_HOST_TO_DEVICE,
+ CP210X_WRITE_LATCH,
+ latch_mask_setting_to_write,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result != 0)
+ return -EPROTO;
+
+ return 0;
+ } else if (port_priv->bPartNumber == CP2105_PARTNUM) {
+ latch_mask_setting_to_write =
+ *(unsigned int *)arg & 0x000000FF;
+ latch_mask_setting_to_write |=
+ (*(unsigned int *)arg & 0x00FF0000) >> 8;
+ return cp210x_set_config(port,
+ REQTYPE_HOST_TO_INTERFACE,
+ CP210X_VENDOR_SPECIFIC,
+ CP210X_WRITE_LATCH,
+ &latch_mask_setting_to_write, 2);
+ } else {
+ return -ENOTSUPP;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return -ENOIOCTLCMD;
+}
+
/*
* cp210x_get_termios
* Reads the baud rate, data bits, parity, stop bits and flow control mode
@@ -490,14 +565,16 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,

dbg("%s - port %d", __func__, port->number);

- cp210x_get_config(port, CP210X_GET_BAUDRATE, &baud, 4);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_BAUDRATE, 0, &baud, 4);

dbg("%s - baud rate = %d", __func__, baud);
*baudp = baud;

cflag = *cflagp;

- cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_LINE_CTL, 0, &bits, 2);
cflag &= ~CSIZE;
switch (bits & BITS_DATA_MASK) {
case BITS_DATA_5:
@@ -522,14 +599,16 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,
cflag |= CS8;
bits &= ~BITS_DATA_MASK;
bits |= BITS_DATA_8;
- cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2);
break;
default:
dbg("%s - Unknown number of data bits, using 8", __func__);
cflag |= CS8;
bits &= ~BITS_DATA_MASK;
bits |= BITS_DATA_8;
- cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2);
break;
}

@@ -560,7 +639,8 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,
dbg("%s - Unknown parity mode, disabling parity", __func__);
cflag &= ~PARENB;
bits &= ~BITS_PARITY_MASK;
- cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2);
break;
}

@@ -573,7 +653,8 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,
dbg("%s - stop bits = 1.5 (not supported, using 1 stop bit)",
__func__);
bits &= ~BITS_STOP_MASK;
- cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2);
break;
case BITS_STOP_2:
dbg("%s - stop bits = 2", __func__);
@@ -583,11 +664,13 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,
dbg("%s - Unknown number of stop bits, using 1 stop bit",
__func__);
bits &= ~BITS_STOP_MASK;
- cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2);
break;
}

- cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_FLOW, 0, modem_ctl, 16);
if (modem_ctl[0] & 0x0008) {
dbg("%s - flow control = CRTSCTS", __func__);
cflag |= CRTSCTS;
@@ -640,8 +723,8 @@ static void cp210x_change_speed(struct tty_struct *tty,
baud = cp210x_quantise_baudrate(baud);

dbg("%s - setting baud rate to %u", __func__, baud);
- if (cp210x_set_config(port, CP210X_SET_BAUDRATE, &baud,
- sizeof(baud))) {
+ if (cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_BAUDRATE, 0, &baud, sizeof(baud))) {
dev_warn(&port->dev, "failed to set baud rate to %u\n", baud);
if (old_termios)
baud = old_termios->c_ospeed;
@@ -672,7 +755,8 @@ static void cp210x_set_termios(struct tty_struct *tty,

/* If the number of data bits is to be updated */
if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
- cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_LINE_CTL, 0, &bits, 2);
bits &= ~BITS_DATA_MASK;
switch (cflag & CSIZE) {
case CS5:
@@ -702,14 +786,16 @@ static void cp210x_set_termios(struct tty_struct *tty,
bits |= BITS_DATA_8;
break;
}
- if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+ if (cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2))
dbg("Number of data bits requested "
"not supported by device");
}

if ((cflag & (PARENB|PARODD|CMSPAR)) !=
(old_cflag & (PARENB|PARODD|CMSPAR))) {
- cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_LINE_CTL, 0, &bits, 2);
bits &= ~BITS_PARITY_MASK;
if (cflag & PARENB) {
if (cflag & CMSPAR) {
@@ -730,12 +816,14 @@ static void cp210x_set_termios(struct tty_struct *tty,
}
}
}
- if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+ if (cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2))
dbg("Parity mode not supported by device");
}

if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
- cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_LINE_CTL, 0, &bits, 2);
bits &= ~BITS_STOP_MASK;
if (cflag & CSTOPB) {
bits |= BITS_STOP_2;
@@ -744,13 +832,15 @@ static void cp210x_set_termios(struct tty_struct *tty,
bits |= BITS_STOP_1;
dbg("%s - stop bits = 1", __func__);
}
- if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+ if (cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_LINE_CTL, 0, &bits, 2))
dbg("Number of stop bits requested "
"not supported by device");
}

if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
- cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_FLOW, 0, modem_ctl, 16);
dbg("%s - read modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x",
__func__, modem_ctl[0], modem_ctl[1],
modem_ctl[2], modem_ctl[3]);
@@ -770,7 +860,8 @@ static void cp210x_set_termios(struct tty_struct *tty,
dbg("%s - write modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x",
__func__, modem_ctl[0], modem_ctl[1],
modem_ctl[2], modem_ctl[3]);
- cp210x_set_config(port, CP210X_SET_FLOW, modem_ctl, 16);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_FLOW, 0, modem_ctl, 16);
}

}
@@ -808,7 +899,8 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,

dbg("%s - control = 0x%.4x", __func__, control);

- return cp210x_set_config(port, CP210X_SET_MHS, &control, 2);
+ return cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_MHS, 0, &control, 2);
}

static void cp210x_dtr_rts(struct usb_serial_port *p, int on)
@@ -827,7 +919,8 @@ static int cp210x_tiocmget (struct tty_struct *tty)

dbg("%s - port %d", __func__, port->number);

- cp210x_get_config(port, CP210X_GET_MDMSTS, &control, 1);
+ cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
+ CP210X_GET_MDMSTS, 0, &control, 1);

result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0)
|((control & CONTROL_RTS) ? TIOCM_RTS : 0)
@@ -853,13 +946,15 @@ static void cp210x_break_ctl (struct tty_struct *tty, int break_state)
state = BREAK_ON;
dbg("%s - turning break %s", __func__,
state == BREAK_OFF ? "off" : "on");
- cp210x_set_config(port, CP210X_SET_BREAK, &state, 2);
+ cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
+ CP210X_SET_BREAK, 0, &state, 2);
}

static int cp210x_startup(struct usb_serial *serial)
{
struct cp210x_port_private *port_priv;
int i;
+ unsigned int partNum;

/* cp210x buffers behave strangely unless device is reset */
usb_reset_device(serial->dev);
@@ -874,6 +969,12 @@ static int cp210x_startup(struct usb_serial *serial)
serial->interface->cur_altsetting->desc.bInterfaceNumber;

usb_set_serial_port_data(serial->port[i], port_priv);
+
+ /* Get the 1-byte part number of the cp210x device */
+ cp210x_get_config(serial->port[i],
+ REQTYPE_DEVICE_TO_HOST, CP210X_VENDOR_SPECIFIC,
+ CP210X_GET_PARTNUM, &partNum, 1);
+ port_priv->bPartNumber = partNum & 0xFF;
}

return 0;
--
1.7.5.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/