Proposed patch for cp210x: GPIO and USB descriptor control

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

A couple of years ago, Silicon Labs granted my company access to API 
documentation for their cp210x "runtime" and "manufacturing" DLLs (provided 
we release our work via the GPL, which we did).  The former allows USB host 
manipulation of cp210c GPIO pins; the latter allows setting cp210x port 
configuration and USB descriptors.  With this information I was able to add 
support for these features in the cp210x driver, exposed via ioctl.  We use 
this driver regularly with good results.

SiLabs wanted to push the changes to the Linux community itself, but doesn't 
seem to have ever done so.  I'd like to make them available now.  However, 
I'm no kernel developer and have only passing familiarity with the relevant 
kernel interfaces.  I'm hoping that someone might be willing to 'mentor' me 
through the process of getting this code suitably crafted and organized for 
possible inclusion.

The patch for our driver is included below, relative to cp210x from Linus' 
tree @ v2.6.34-rc7.

All the best,
Steve

--- cp210x.c-2.6.34-rc7	2010-05-13 16:48:48.000000000 -0600
+++ cp210x.c.tmi	2010-05-13 17:36:10.000000000 -0600
@@ -11,6 +11,9 @@
  * thanks to Karl Hiramoto karl@xxxxxxxxxxxxx RTSCTS hardware flow
  * control thanks to Munir Nassar nassarmu@xxxxxxxxxxxxx
  *
+ * R. Steve McKown, Titanium Mirror, Inc., rsmckown@xxxxxxxxxx
+ * Added port configuration, usb descriptor and gpio management.
+ *
  */
 
 #include <linux/kernel.h>
@@ -50,6 +53,8 @@
 static void cp210x_break_ctl(struct tty_struct *, int);
 static int cp210x_startup(struct usb_serial *);
 static void cp210x_disconnect(struct usb_serial *);
+static int cp210x_ioctl(struct tty_struct *, struct file *,
+		unsigned int, unsigned long);
 static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
 static int cp210x_carrier_raised(struct usb_serial_port *p);
 
@@ -140,6 +145,7 @@
 	.num_ports		= 1,
 	.open			= cp210x_open,
 	.close			= cp210x_close,
+	.ioctl			= cp210x_ioctl,
 	.break_ctl		= cp210x_break_ctl,
 	.set_termios		= cp210x_set_termios,
 	.tiocmget 		= cp210x_tiocmget,
@@ -221,6 +227,339 @@
 #define CONTROL_WRITE_DTR	0x0100
 #define CONTROL_WRITE_RTS	0x0200
 
+/* CP2103 ioctls */
+#define IOCTL_GPIOGET		0x8000	/* Get gpio bits */
+#define IOCTL_GPIOSET		0x8001	/* Set gpio bits */
+#define IOCTL_GPIOBIC		0x8002	/* Clear specific gpio bit(s) */
+#define IOCTL_GPIOBIS		0x8003	/* Set specific gpio bit(s) */
+
+/* CP210x ioctls principally used during initial device configuration */
+#define IOCTL_DEVICERESET	0x8004	/* Reset the cp210x */
+#define IOCTL_PORTCONFGET	0x8005	/* Get port configuration */
+#define IOCTL_PORTCONFSET	0x8006	/* Set port configuration */
+#define IOCTL_SETVID		0x8007	/* Set vendor id */
+#define IOCTL_SETPID		0x8008	/* Set product id */
+#define IOCTL_SETMFG		0x8009	/* Set manufacturer string */
+#define IOCTL_SETPRODUCT	0x800a	/* Set product string */
+#define IOCTL_SETSERIAL		0x800b	/* Set serial number string */
+#define IOCTL_SETDEVVER		0x800c	/* set device version id */
+/* FIXME: where is IOCTL_SETMFG? */
+
+/* CP2103 GPIO */
+#define GPIO_0			0x01
+#define GPIO_1			0x02
+#define GPIO_2			0x04
+#define GPIO_3			0x08
+#define GPIO_MASK		(GPIO_0|GPIO_1|GPIO_2|GPIO_3)
+
+/* GetDeviceVersion() return codes */
+#define CP210x_CP2101_VERSION	0x01
+#define	CP210x_CP2102_VERSION	0x02
+#define	CP210x_CP2103_VERSION	0x03
+
+/* Return codes */
+#define	CP210x_SUCCESS			0x00
+#define	CP210x_DEVICE_NOT_FOUND		0xFF
+#define	CP210x_INVALID_HANDLE		0x01
+#define	CP210x_INVALID_PARAMETER	0x02
+#define	CP210x_DEVICE_IO_FAILED		0x03
+#define	CP210x_FUNCTION_NOT_SUPPORTED	0x04
+#define	CP210x_GLOBAL_DATA_ERROR	0x05
+#define	CP210x_FILE_ERROR		0x06
+#define	CP210x_COMMAND_FAILED		0x08
+#define	CP210x_INVALID_ACCESS_TYPE	0x09
+
+/* USB descriptor sizes */
+#define	CP210x_MAX_DEVICE_STRLEN	256
+#define	CP210x_MAX_PRODUCT_STRLEN	126
+#define	CP210x_MAX_SERIAL_STRLEN	63
+#define	CP210x_MAX_MAXPOWER		250
+
+/* Used to pass variable sized buffers between user and kernel space (ioctls) 
*/
+typedef struct {
+	char *buf;
+	size_t len;
+} cp210x_buffer_t;
+
+/* Port config definitions */
+typedef struct {
+	uint16_t mode;		/* Push-pull = 1, Open-drain = 0 */
+	uint16_t lowPower;
+	uint16_t latch;		/* Logic high = 1, Logic low = 0 */
+} cp210x_port_state_t;
+
+typedef struct {
+	cp210x_port_state_t reset;
+	cp210x_port_state_t suspend;
+	uint8_t enhancedFxn;
+} cp210x_port_config_t;
+
+#define PORT_CONFIG_LEN	13	/* Because sizeof() will pad to full words */
+
+/*
+ * cp210x_buf_from_user
+ * Copy a buffer from user space, returning the number of bytes copied
+ * from ubuf.buf into kbuf.  klen is the size of the buffer at kbuf.
+ */
+static size_t copy_buf_from_user(char *kbuf, unsigned long ubuf, size_t klen)
+{
+	cp210x_buffer_t t;
+
+	if (!kbuf || !ubuf || !klen ||
+			copy_from_user(&t, (cp210x_buffer_t *)ubuf, sizeof(t)))
+		return 0;
+	if (t.len < klen)
+		klen = t.len;
+	if (!t.buf || !t.len || copy_from_user(kbuf, t.buf, klen))
+		return 0;
+	return klen;
+}
+
+/*
+ * cp210x_ctlmsg
+ * A generic usb control message interface.
+ * Returns the actual size of the data read or written within the message, 0
+ * if no data were read or written, or a negative value to indicate an error.
+ */
+static int cp210x_ctlmsg(struct usb_serial_port *port, u8 request,
+		u8 requestype, u16 value, u16 index, void *data, u16 size)
+{
+	struct usb_device *dev = port->serial->dev;
+	u8 *tbuf;
+	int ret;
+
+	if (!(tbuf = kmalloc(size, GFP_KERNEL)))
+		return -ENOMEM;
+	if (requestype & 0x80) {
+		ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
+				requestype, value, index, tbuf, size, 300);
+		if (ret > 0 && size)
+			memcpy(data, tbuf, size);
+	} else {
+		if (size)
+			memcpy(tbuf, data, size);
+		ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
+				requestype, value, index, tbuf, size, 300);
+	}
+	kfree(tbuf);
+	if (ret < 0 && ret != -EPIPE) {
+		dev_printk(KERN_DEBUG, &dev->dev, "cp210x: control failed cmd rqt %u "
+				"rq %u len %u ret %d\n", requestype, request, size, ret);
+	}
+	return ret;
+}
+
+static int cp210x_reset(struct usb_serial_port *port)
+{
+	dbg("%s", __FUNCTION__);
+
+#if 1
+    /* Is this better than usb_device_reset?  It may be.  Once a client 
issues
+	 * the reset ioctl, it must disconnect and reconnect, since the USB
+	 * connections are torn down.  We also ignore the error return, since
+	 * the part resets and doesn't send one...
+	 */
+	cp210x_ctlmsg(port, 0xff, 0x40, 0x0008, 0x00, 0, 0);
+#else
+	usb_reset_device(port->serial->dev);
+#endif
+	return 0;
+}
+
+static int cp210x_get_partnum(struct usb_serial_port *port)
+{
+	static u8 _partnum = 0;
+	int ret = CP210x_SUCCESS;
+	if (!_partnum) {
+		u8 addr = port->serial->dev->actconfig->interface[0]->
+			cur_altsetting->endpoint[0].desc.bEndpointAddress &
+			USB_ENDPOINT_NUMBER_MASK;
+
+		if (addr == 0x03 || addr == 0x02) /* The part is a cp2101 */
+			_partnum = 0x01;
+		else if (addr == 0x01) {
+			/* Must query part to determine part number */
+			if (cp210x_ctlmsg(port, 0xff, 0xc0, 0x370b, 0x00,
+					&_partnum, 1) != 1)
+				ret = CP210x_DEVICE_IO_FAILED;
+		} else
+			ret = CP210x_DEVICE_IO_FAILED;
+	}
+	dbg("%s - partnum %u err %d", __FUNCTION__, _partnum, ret);
+	return (ret == CP210x_SUCCESS) ? _partnum : 0;
+}
+
+static inline int cp210x_setu16(struct usb_serial_port *port, int cmd,
+		unsigned int value)
+{
+	return cp210x_ctlmsg(port, 0xff, 0x40, 0x3700 | (cmd & 0xff),
+			value, 0, 0);
+}
+
+/* Populates usbstr with: (len) + (0x03) + unicode(str).  Each char in str
+ * takes up two bytes in unicode format, so the resulting len(usbstr) is
+ * 2 * len(str) + 2.
+ * Returns the resulting length of the string in usbstr.
+ * This function can accept overlapping usbstr and str as long as the overlap
+ * does not cause data written to usbstr to overwrite data not yet read from
+ * str.
+ */
+static int make_usb_string(char *usbstr, size_t usblen, char *src,
+		size_t srclen)
+{
+	int len = 0;
+
+	if (usbstr && usblen >= 2 && src && *src && srclen) {
+		char *p;
+
+		/* The usb string format uses first byte as length */
+		if (usblen > 256)
+			usblen = 256;
+
+		p = usbstr + 1;
+		*p++ = 0x03;
+		len = 2;
+		while (srclen && len < usblen) {
+			*p++ = *src++;
+			*p++ = 0;
+			len += 2;
+			srclen--;
+		}
+		*usbstr = (char)len;
+	}
+	return len;
+}
+
+/*
+ * cp210x_setstr
+ *
+ * Set a USB string descriptor using proprietary cp210x control messages.
+ * Return the number of characters actually written.
+ */
+static int cp210x_setstr(struct usb_serial_port *port, int cmd, char *usbstr)
+{
+	unsigned len = usbstr[0];
+	int ret = cp210x_ctlmsg(port, 0xff, 0x40, 0x3700 | (cmd & 0xff), 0,
+			usbstr, len);
+	dbg("%s - cmd 0x%02x len %d ret %d", __FUNCTION__, cmd, len, ret);
+	return ret;
+}
+
+/* Set all gpio simultaneously */
+static int cp210x_gpioset(struct usb_serial_port *port, u8 gpio)
+{
+	dbg("%s - port %d, gpio = 0x%.2x", __FUNCTION__, port->number, gpio);
+
+	return cp210x_ctlmsg(port, 0xff, 0x40, 0x37e1,
+			((uint16_t)gpio << 8) | GPIO_MASK, 0, 0);
+}
+
+/* Set select gpio bits */
+static int cp210x_gpiosetb(struct usb_serial_port *port, u8 set, u8 clear)
+{
+	u16 gpio = 0;
+
+	/* The bitmask is in the LSB, the values in the MSB */
+	if (set & GPIO_0)
+		gpio |= (GPIO_0 << 8)|GPIO_0;
+	if (set & GPIO_1)
+		gpio |= (GPIO_1 << 8)|GPIO_1;
+	if (set & GPIO_2)
+		gpio |= (GPIO_2 << 8)|GPIO_2;
+	if (set & GPIO_3)
+		gpio |= (GPIO_3 << 8)|GPIO_3;
+	if (clear & GPIO_0)
+		gpio = (gpio & ~(GPIO_0 << 8))|GPIO_0;
+	if (clear & GPIO_1)
+		gpio = (gpio & ~(GPIO_1 << 8))|GPIO_1;
+	if (clear & GPIO_2)
+		gpio = (gpio & ~(GPIO_2 << 8))|GPIO_2;
+	if (clear & GPIO_3)
+		gpio = (gpio & ~(GPIO_3 << 8))|GPIO_3;
+
+	dbg("%s - port %d, gpiob = 0x%.4x", __FUNCTION__, port->number, gpio);
+
+	/* FIXME: how about REQTYPE_HOST_TO_DEVICE instead of 0x40? */
+	return cp210x_ctlmsg(port, 0xff, 0x40, 0x37e1, gpio, 0, 0);
+}
+
+static int cp210x_gpioget(struct usb_serial_port *port, u8 *gpio)
+{
+	int ret;
+
+	dbg("%s - port %d", __FUNCTION__, port->number);
+
+	/* FIXME: how about REQTYPE_DEVICE_TO_HOST instead of 0xc0? */
+	ret = cp210x_ctlmsg(port, 0xff, 0xc0, 0x00c2, 0, gpio, 1);
+
+	dbg("%s - gpio = 0x%.2x (%d)", __FUNCTION__, *gpio, ret);
+
+	return (ret == 1) ? 0 : -1;
+}
+
+static int cp210x_portconfset(struct usb_serial_port *port,
+		cp210x_port_config_t *config)
+{
+	cp210x_port_config_t lconfig;
+	int ret;
+
+	dbg("%s", __FUNCTION__);
+
+	memcpy(&lconfig, config, sizeof(lconfig));
+
+	/* apparently not implemented yet */
+	lconfig.suspend.lowPower = 0;
+	lconfig.reset.lowPower = 0;
+
+	/* Words from cp2103 are MSB */
+	lconfig.reset.mode = cpu_to_be16(config->reset.mode);
+	/* lconfig.reset.lowPower = cpu_to_be16(config->reset.lowPower); */
+	lconfig.reset.latch = cpu_to_be16(config->reset.latch);
+	lconfig.suspend.mode = cpu_to_be16(config->suspend.mode);
+	/* lconfig.suspend.lowPower = cpu_to_be16(config->suspend.lowPower); */
+	lconfig.suspend.latch = cpu_to_be16(config->suspend.latch);
+
+	ret = cp210x_ctlmsg(port, 0xff, 0x40, 0x370c, 0, &lconfig,
+			PORT_CONFIG_LEN);
+	if (ret == PORT_CONFIG_LEN)
+		return 0;
+	else if (ret >= 0)
+		return -1;
+	else
+		return ret;
+}
+
+static int cp210x_portconfget(struct usb_serial_port *port,
+		cp210x_port_config_t *config)
+{
+	int ret;
+
+	dbg("%s", __FUNCTION__);
+
+	ret = cp210x_ctlmsg(port, 0xff, 0xc0, 0x370c, 0, config,
+			PORT_CONFIG_LEN);
+	if (ret == PORT_CONFIG_LEN) {
+		/* Words from cp2103 are MSB */
+		config->reset.mode = be16_to_cpu(config->reset.mode);
+		config->reset.lowPower = be16_to_cpu(config->reset.lowPower);
+		config->reset.latch = be16_to_cpu(config->reset.latch);
+		config->suspend.mode = be16_to_cpu(config->suspend.mode);
+		config->suspend.lowPower =
+				be16_to_cpu(config->suspend.lowPower);
+		config->suspend.latch = be16_to_cpu(config->suspend.latch);
+
+		/* apparently not implemented yet */
+		config->reset.lowPower = 0;
+		config->suspend.lowPower = 0;
+
+		return 0;
+	} else if (ret >= 0)
+		return -1;
+	else
+		return ret;
+
+}
+
 /*
  * cp210x_get_config
  * Reads from the CP210x configuration registers
@@ -431,6 +770,176 @@
 	mutex_unlock(&port->serial->disc_mutex);
 }
 
+static int cp210x_ioctl(struct tty_struct *tty, struct file *file,
+		unsigned int cmd, unsigned long arg)
+{
+	struct usb_serial_port *port = tty->driver_data;
+
+	dbg("%s (%d) cmd = 0x%04x", __FUNCTION__, port->number, cmd);
+
+	switch (cmd) {
+		
+	case TIOCMGET:
+	{
+		int result = cp210x_tiocmget(tty, file);
+		if (copy_to_user(&arg, &result, sizeof(int)))
+			return -EFAULT; 
+		return 0;
+	}
+	break;
+
+	case TIOCMSET:
+	case TIOCMBIS:
+
+	case TIOCMBIC:
+	{
+		int val = 0;
+
+		if (copy_from_user(&val, &arg, sizeof(int)))
+			return -EFAULT;
+
+		/* this looks wrong: TIOCMSET isn't going to work right */
+		if (cp210x_tiocmset(tty, file, cmd==TIOCMBIC ? 0 : val,
+					cmd==TIOCMBIC ? val : 0))
+			return -EFAULT;
+		return 0;
+	}
+	break;
+
+	case IOCTL_GPIOGET:
+	if (cp210x_get_partnum(port) == CP210x_CP2103_VERSION) {
+		u8 gpio = 0;
+		if (!cp210x_gpioget(port, &gpio) && !copy_to_user((u8*)arg,
+					&gpio, sizeof(gpio)))
+			return 0;
+	}
+	return -EFAULT;
+	break;
+
+	case IOCTL_GPIOSET:
+	if (cp210x_get_partnum(port) == CP210x_CP2103_VERSION &&
+			!cp210x_gpioset(port, arg))
+		return 0;
+	return -EFAULT;
+	break;
+
+	case IOCTL_GPIOBIC:
+	case IOCTL_GPIOBIS:
+	if (cp210x_get_partnum(port) == CP210x_CP2103_VERSION &&
+			!cp210x_gpiosetb(port, (cmd==IOCTL_GPIOBIC) ?  0 : arg,
+			(cmd==IOCTL_GPIOBIC) ? arg : 0))
+		return 0;
+	return -EFAULT;
+	break;
+
+	case IOCTL_DEVICERESET:
+	return cp210x_reset(port);
+	break;
+
+	case IOCTL_PORTCONFGET:
+	{
+		cp210x_port_config_t config;
+		if (!cp210x_portconfget(port, &config) && !copy_to_user(
+					(cp210x_port_config_t*)arg, &config,
+					sizeof(config)))
+			return 0;
+	}
+	return -EFAULT;
+	break;
+
+	case IOCTL_PORTCONFSET:
+	{
+	cp210x_port_config_t config;
+	if (!copy_from_user(&config, (cp210x_port_config_t*)arg,
+			sizeof(config)) &&
+			!cp210x_portconfset(port, &config))
+		return 0;
+	return -EFAULT;
+	}
+	break;
+
+	case IOCTL_SETVID:
+	{
+		unsigned int vid;
+		if (!copy_from_user(&vid, (unsigned int *)arg,
+				sizeof(unsigned int)) &&
+				!cp210x_setu16(port, 0x01, vid))
+			return 0;
+		return -EFAULT;
+	}
+	break;
+
+	case IOCTL_SETPID:
+	{
+		unsigned int pid;
+		if (!copy_from_user(&pid, (unsigned int *)arg,
+				sizeof(unsigned int)) &&
+				!cp210x_setu16(port, 0x02, pid))
+			return 0;
+		return -EFAULT;
+	}
+	break;
+
+	case IOCTL_SETMFG:
+#if 0 /* Silicon Labs apparently doesn't provide for setting of mfg desc */
+	{
+		char usbstr[CP210x_MAX_MFG_STRLEN * 2 + 2];
+		size_t len = copy_buf_from_user(usbstr + sizeof(usbstr) -
+				CP210x_MAX_MFG_STRLEN, arg,
+				CP210x_MAX_MFG_STRLEN);
+		len = make_usb_string(usbstr, sizeof(usbstr), str, len);
+		if (len && cp210x_setstr(port, 0x00, usbstr) == len)
+			return 0;
+		return -EFAULT;
+	}
+#endif
+	break;
+
+	case IOCTL_SETPRODUCT:
+	{
+		char usbstr[CP210x_MAX_PRODUCT_STRLEN * 2 + 2];
+		char *str = usbstr + sizeof(usbstr) - CP210x_MAX_PRODUCT_STRLEN;
+		size_t len = copy_buf_from_user(str, arg,
+				CP210x_MAX_PRODUCT_STRLEN);
+		len = make_usb_string(usbstr, sizeof(usbstr), str, len);
+		if (len && cp210x_setstr(port, 0x03, usbstr) == len)
+			return 0;
+		return -EFAULT;
+	}
+	break;
+
+	case IOCTL_SETSERIAL:
+	{
+		char usbstr[CP210x_MAX_SERIAL_STRLEN * 2 + 2];
+		char *str = usbstr + sizeof(usbstr) - CP210x_MAX_SERIAL_STRLEN;
+		size_t len = copy_buf_from_user(str, arg,
+				CP210x_MAX_SERIAL_STRLEN);
+		len = make_usb_string(usbstr, sizeof(usbstr), str, len);
+		if (len && cp210x_setstr(port, 0x04, usbstr) == len)
+			return 0;
+		return -EFAULT;
+	}
+	break;
+
+	case IOCTL_SETDEVVER:
+	{
+		unsigned int ver;
+		if (!copy_from_user(&ver, (unsigned int *)arg,
+				sizeof(unsigned int)) &&
+				!cp210x_setu16(port, 0x07, ver))
+			return 0;
+		return -EFAULT;
+	}
+	break;
+
+	default:
+	dbg("%s not supported = 0x%04x", __FUNCTION__, cmd);
+	break;
+	}
+
+	return -ENOIOCTLCMD;
+}
+
 /*
  * cp210x_get_termios
  * Reads the baud rate, data bits, parity, stop bits and flow control mode

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

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux