Hi,
I posted back in February regarding the Silicon Labs CP2101 USB Serial
converter.
After spending some time figuring out how it works, I have managed to
write a simple driver which uses some of the basic functionality of the
chip.
The attached patch provides cp2101.c, and patches Kconfig and Makefile
The code is still unfinished, but functional. I have yet to get flow
control working, which is a little difficult since the pins are not
connected on my serial converter. Also, a few comments in the code
wouldn't go a miss.

Any feedback would be greatly appreciated.

-- 
Craig Shelley
EMail: [EMAIL PROTECTED]
Jabber: [EMAIL PROTECTED]
diff -Nru a/drivers/usb/serial/cp2101.c b/drivers/usb/serial/cp2101.c
--- a/drivers/usb/serial/cp2101.c	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/usb/serial/cp2101.c	2005-03-18 00:29:43.837267216 +0000
@@ -0,0 +1,501 @@
+/*
+ * Silicon Laboratories CP2101 USB to RS232 serial adaptor driver
+ *
+ * Copyright (C) 2005 Craig Shelley ([EMAIL PROTECTED])
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License version
+ *	2 as published by the Free Software Foundation.
+ *
+ */
+
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <asm/uaccess.h>
+#include "usb-serial.h"
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.01"
+#define DRIVER_DESC "Silicon Laboratories CP2101 USB to RS232 serial adaptor driver"
+
+/*
+ * Function Prototypes
+ */
+int cp2101_get_config(struct usb_serial_port*, u8);
+int cp2101_set_config(struct usb_serial_port*, u8, u16);
+int cp2101_open(struct usb_serial_port*, struct file*);
+static void cp2101_cleanup(struct usb_serial_port*);
+void cp2101_close(struct usb_serial_port*, struct file*);
+void cp2101_get_termios(struct usb_serial_port*);
+static void cp2101_set_termios(struct usb_serial_port*, struct termios*);
+static void cp2101_break_ctl(struct usb_serial_port*, int);
+void cp2101_shutdown(struct usb_serial*);
+
+
+
+static int debug;
+
+static struct usb_device_id id_table [] = {
+	{USB_DEVICE(0x10c4, 0xea60) },
+	{ } /* Terminating Entry*/
+};
+
+MODULE_DEVICE_TABLE (usb, id_table);
+
+static struct usb_driver cp2101_driver = {
+	.owner 		= THIS_MODULE,
+	.name 		= "cp2101",
+	.probe 		= usb_serial_probe,
+	.disconnect 	= usb_serial_disconnect,
+	.id_table 	= id_table,
+};
+
+struct usb_serial_device_type cp2101_device = {
+	.owner 			= THIS_MODULE,
+	.name 			= "CP-2101",
+	.id_table 		= id_table,
+	.num_interrupt_in	= 0,
+	.num_bulk_in 		= 0,
+	.num_bulk_out 		= 0,
+	.num_ports 		= 1,
+	.open =			cp2101_open,
+	.close =		cp2101_close,
+//	.ioctl =		cp2101_ioctl,
+	.break_ctl =		cp2101_break_ctl,
+	.set_termios =		cp2101_set_termios,
+	.shutdown =		cp2101_shutdown,
+};
+
+#define REQTYPE_HOST_TO_DEVICE	0x41
+#define REQTYPE_DEVICE_TO_HOST	0xc1
+
+#define CP2101_UART 		0x00
+#define CP2101_BAUDRATE		0x01
+#define CP2101_BITS		0x03
+#define CP2101_BREAK		0x05
+#define CP2101_DTRRTS		0x07
+
+#define CP2101_CONFIG		0x13	/* ??? */
+#define REQ_GET_CONFIG		0x14	/* ??? */
+
+#define UART_ENABLE		0x0001
+#define UART_DISABLE		0x0000
+#define BAUD_RATE_GEN_FREQ	0x384000
+
+#define BITS_DATA_MASK		0X0f00
+#define BITS_DATA_6		0X0600
+#define BITS_DATA_7		0X0700
+#define BITS_DATA_8		0X0800
+#define BITS_DATA_9		0X0900
+
+#define BITS_PARITY_MASK	0x00f0
+#define BITS_PARITY_NONE	0x0000
+#define BITS_PARITY_ODD		0x0010
+#define BITS_PARITY_EVEN	0x0020
+#define BITS_PARITY_MARK	0x0030
+#define BITS_PARITY_SPACE	0x0040
+
+#define BITS_STOP_MASK		0x000f
+#define BITS_STOP_1		0x0000
+#define BITS_STOP_1_5		0x0001
+#define BITS_STOP_2		0x0002
+#define BREAK_ON		0x0000
+#define BREAK_OFF		0x0001
+
+
+int cp2101_get_config(struct usb_serial_port* port, u8 request)
+{
+	struct usb_serial *serial = port->serial;
+	unsigned char buf[4];
+	unsigned int value;
+	int result, i;
+
+	/*Even number required for get*/
+	request++;
+	
+	result = usb_control_msg (serial->dev, usb_rcvctrlpipe (serial->dev, 0),
+				  request, REQTYPE_DEVICE_TO_HOST, 0x0000, 
+				  0, buf, 4, 300);
+	
+	if (result < 0)
+	{
+		dev_err(&port->dev, "%s - Unable to send config request,  request=0x%x result=%d\n", __FUNCTION__, request, result);
+		return result;
+	}
+
+	value = 0;
+	
+	for (i=0; i<4 && i<result; i++)
+		value |= (buf[i] << (i * 8));
+		
+	dbg("%s - request=0x%x result=%d value=0x%x", __FUNCTION__, request, result, value);
+
+	return value;
+}
+
+int cp2101_set_config(struct usb_serial_port* port, u8 request, u16 value)
+{
+	struct usb_serial *serial = port->serial;
+	int result;
+	result = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0),
+				  request, REQTYPE_HOST_TO_DEVICE, value, 
+				  0, NULL, 0, 300);
+	
+	if (result <0)
+	{
+		dev_err(&port->dev, "%s - Unable to send config request,  request=0x%x value=0x%x result=%d\n", __FUNCTION__, request, value, result);
+		return 1;
+	}
+	
+	dbg("%s - request=0x%x value=0x%x result=%d", __FUNCTION__, request, value, result);
+
+	return 0;
+}
+
+int cp2101_open (struct usb_serial_port *port, struct file *filp)
+{
+	struct usb_serial *serial = port->serial;
+	int result = 0;
+
+	
+	dbg("%s - port %d", __FUNCTION__, port->number);
+	
+	if (cp2101_set_config(port, CP2101_UART, UART_ENABLE))
+	{
+		dev_err(&port->dev, "%s - Unable to enable UART\n", __FUNCTION__);
+		return -EPROTO;
+	}
+	
+	cp2101_get_termios(port);
+
+	/* Start reading from the device */
+	usb_fill_bulk_urb (port->read_urb, serial->dev,
+			usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress),
+			port->read_urb->transfer_buffer,
+			port->read_urb->transfer_buffer_length,
+			serial->type->read_bulk_callback,
+			port);
+	result = usb_submit_urb(port->read_urb, GFP_KERNEL);
+	if (result)
+		dev_err(&port->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result);
+
+
+
+	return result;
+}
+
+static void cp2101_cleanup (struct usb_serial_port *port)
+{
+	struct usb_serial *serial = port->serial;
+
+	dbg("%s - port %d", __FUNCTION__, port->number);
+
+	if (serial->dev) {
+
+		/* shutdown any bulk reads that might be going on */
+		if (serial->num_bulk_out)
+			usb_kill_urb(port->write_urb);
+		if (serial->num_bulk_in)
+			usb_kill_urb(port->read_urb);
+	}
+}
+
+void cp2101_close (struct usb_serial_port *port, struct file * filp)
+{
+	dbg("%s - port %d", __FUNCTION__, port->number);
+	cp2101_set_config(port, CP2101_UART, UART_DISABLE);
+	
+	/* shutdown our urbs */
+	dbg("%s - shutting down urbs", __FUNCTION__);
+	usb_kill_urb(port->write_urb);
+	usb_kill_urb(port->read_urb);
+}
+
+void cp2101_get_termios (struct usb_serial_port *port)
+{
+	unsigned int cflag;
+	int baud;
+	int bits;
+
+	dbg("%s -  port %d", __FUNCTION__, port->number);
+
+	if ((!port->tty) || (!port->tty->termios)) {
+		dbg("%s - no tty structures", __FUNCTION__);
+		return;
+	}
+
+	cflag = port->tty->termios->c_cflag;
+	cflag &= ~(CBAUD);
+
+	baud = cp2101_get_config(port, CP2101_BAUDRATE);
+	if (baud)
+	{
+		baud = BAUD_RATE_GEN_FREQ / baud;
+		dbg("%s - Device is configured with baud rate of %d baud", __FUNCTION__, baud);
+	}
+
+	switch (baud)
+	{
+		/* The baud rates which are commented out below appear to be supported by the device
+		 * but are non-standard
+		 */
+		case 600:	cflag |= B600;	   break;
+		case 1200:	cflag |= B1200;	   break;
+		case 1800:	cflag |= B1800;	   break;
+		case 2400:	cflag |= B2400;	   break;
+		case 4800:	cflag |= B4800;	   break;
+		/*case 7200:	cflag |= B7200;	   break;*/
+		case 9600:	cflag |= B9600;    break;
+		/*case 14400:	cflag |= B14400;   break;*/
+		case 19200:	cflag |= B19200;   break;
+		/*case 28800:	cflag |= B28800;   break;*/
+		case 38400:	cflag |= B38400;   break;
+		/*case 55854:	cflag |= B55054;   break;*/
+		case 57600:	cflag |= B57600;   break;
+		case 115200:	cflag |= B115200;  break;
+		/*case 127117:	cflag |= B127117;  break;*/
+		case 230400:	cflag |= B230400;  break;
+		case 460800:	cflag |= B460800;  break;
+		case 921600:	cflag |= B921600;  break;
+		/*case 3686400:	cflag |= B3686400; break;*/
+		default:
+			dbg("%s - Baud rate is non-standard, using 9600 baud", __FUNCTION__);
+			cflag |= B9600;
+			baud = 9600;
+			break;
+	}
+
+
+	bits = cp2101_get_config(port, CP2101_BITS);
+	
+	switch(bits & BITS_DATA_MASK)
+	{
+		case BITS_DATA_6:	cflag |= CS6;	break;
+		case BITS_DATA_7:	cflag |= CS7;	break;
+		case BITS_DATA_8:	cflag |= CS8;	break;
+		default:
+			dbg("%s - Non-standard number of data bits, using 8", __FUNCTION__);
+			cflag |= CS8;
+			bits &= ~BITS_DATA_MASK;
+			bits |= BITS_DATA_8;
+			break;
+	}
+	
+	switch(bits & BITS_PARITY_MASK)
+	{
+		case BITS_PARITY_NONE:					break;
+		case BITS_PARITY_ODD:	cflag |= (PARENB|PARODD);	break;
+		case BITS_PARITY_EVEN:	cflag |= PARENB;		break;
+		default:
+			dbg("%s - Non-standard parity mode, disabling parity", __FUNCTION__);
+			bits &= ~BITS_PARITY_MASK;
+			break;
+
+	}
+				
+	switch(bits & BITS_STOP_MASK)
+	{
+		case BITS_STOP_1:				break;
+		case BITS_STOP_2:	cflag |= CSTOPB;	break;
+		default:
+			dbg("%s - Non-standard number of stop bits, using 1 stop bit", __FUNCTION__);
+			bits &= ~BITS_STOP_MASK;
+			break;
+	}
+
+	cp2101_set_config(port, CP2101_BAUDRATE, (BAUD_RATE_GEN_FREQ / baud));
+	cp2101_set_config(port, CP2101_BITS, bits);
+	
+	port->tty->termios->c_cflag = cflag;
+}
+
+static void cp2101_set_termios (struct usb_serial_port *port, struct termios *old_termios)
+{
+	unsigned int cflag, old_cflag=0;
+	int baud=0;
+	int bits=0;
+	
+	dbg("%s -  port %d", __FUNCTION__, port->number);
+
+	if ((!port->tty) || (!port->tty->termios)) 
+	{
+		dbg("%s - no tty structures", __FUNCTION__);
+		return;
+	}
+
+	cflag = port->tty->termios->c_cflag;
+
+	/* check that they really want us to change something */
+	if (old_termios) 
+	{
+		if ((cflag == old_termios->c_cflag) &&
+		    (RELEVANT_IFLAG(port->tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) 
+		{
+		    dbg("%s - nothing to change...", __FUNCTION__);
+		    return;
+		}
+
+		old_cflag = old_termios->c_cflag;
+	}
+
+	if ((cflag & CBAUD) != (old_cflag & CBAUD))
+	{
+		switch (cflag & CBAUD) 
+		{
+			/* The baud rates which are commented out below appear to be supported by the device
+			 * but are non-standard
+			 */
+			case B0:	baud = 0;	break;
+			case B600:	baud = 600;	break;
+			case B1200:	baud = 1200;	break;
+			case B1800:	baud = 1800;	break;
+			case B2400:	baud = 2400;	break;
+			case B4800:	baud = 4800;	break;
+			/*case B7200:	baud = 7200;	break;*/
+			case B9600:	baud = 9600;	break;
+			/*ase B14400:	baud = 14400;	break;*/
+			case B19200:	baud = 19200;	break;
+			/*case B28800:	baud = 28800;	break;*/
+			case B38400:	baud = 38400;	break;
+			/*case B55854:	baud = 55054;	break;*/
+			case B57600:	baud = 57600;	break;
+			case B115200:	baud = 115200;	break;
+			/*case B127117:	baud = 127117;	break;*/
+			case B230400:	baud = 230400;	break;
+			case B460800:	baud = 460800;	break;
+			case B921600:	baud = 921600;	break;
+			/*case B3686400:	baud = 3686400;	break;*/
+			default:
+					dev_err(&port->dev, "cp2101 driver does not support the baudrate requested\n");
+					break;
+		}
+			
+		if (baud) 
+			cp2101_set_config(port, CP2101_BAUDRATE, (BAUD_RATE_GEN_FREQ / baud));
+
+
+	}
+
+	if ((cflag & (CSIZE|PARENB|PARODD)) != (old_cflag & (CSIZE|PARENB|PARODD)))
+	{
+		switch (cflag & CSIZE) 
+		{
+			case CS6:	bits |= BITS_DATA_6;	break;
+			case CS7:	bits |= BITS_DATA_7;	break;
+			case CS8:	bits |= BITS_DATA_8;	break;
+			/*case CS9:	bits |= BITS_DATA_9;	break;*/
+			default:
+				dev_err(&port->dev, "cp2101 driver does not support the number of bits required, using 8 bit mode\n");
+				bits |= BITS_DATA_8;
+				break;
+
+		}
+		dbg("%s - data bits = %d", __FUNCTION__, bits);
+
+		if (cflag & PARENB)
+		{
+			if (cflag & PARODD)
+			{
+				bits |= BITS_PARITY_ODD;
+				dbg("%s - parity = ODD", __FUNCTION__);
+			}
+			else
+			{
+				bits |= BITS_PARITY_EVEN;
+				dbg("%s - parity = EVEN", __FUNCTION__);
+			}
+				
+		}
+
+		if (cflag & CSTOPB) 
+		{
+			bits |= BITS_STOP_2;
+			dbg("%s - stop bits = 2", __FUNCTION__);
+		} else {
+			bits |= BITS_STOP_1;
+			dbg("%s - stop bits = 1", __FUNCTION__);
+		}
+		
+		cp2101_set_config(port, CP2101_BITS, bits);
+	}
+}
+
+static void cp2101_break_ctl (struct usb_serial_port *port, int break_state)
+{
+	u16 state;
+
+	dbg("%s - port %d", __FUNCTION__, port->number);
+
+	if (break_state == 0)
+		state = BREAK_OFF;
+	else
+		state = BREAK_ON;
+	dbg("%s - turning break %s", __FUNCTION__, state==BREAK_OFF ? "off" : "on");
+	cp2101_set_config(port, CP2101_BREAK, state);
+	
+}
+
+
+
+void cp2101_shutdown (struct usb_serial *serial)
+{
+	int i;
+
+	dbg("%s", __FUNCTION__);
+
+	/* stop reads and writes on all ports */
+	for (i=0; i < serial->num_ports; ++i) {
+		cp2101_cleanup(serial->port[i]);
+	}
+}
+
+
+static int __init cp2101_init (void)
+{
+	int retval;
+	
+	retval = usb_serial_register(&cp2101_device);
+	if (retval)
+		return retval; /*Failed to register*/
+	
+	retval = usb_register(&cp2101_driver);
+	if (retval)
+	{
+		/*Failed to register*/
+		usb_serial_deregister(&cp2101_device);
+		return retval;
+	}
+	
+	/*Success*/
+	info(DRIVER_DESC " " DRIVER_VERSION);
+	return 0;
+}
+
+
+static void __exit cp2101_exit (void)
+{
+	usb_deregister (&cp2101_driver);
+	usb_serial_deregister (&cp2101_device);
+}
+
+
+module_init(cp2101_init);
+module_exit(cp2101_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Enable verbose debugging messages");
+
diff -Nru a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
--- a/drivers/usb/serial/Kconfig	2005-03-18 00:39:25.905779352 +0000
+++ b/drivers/usb/serial/Kconfig	2005-03-18 00:40:08.939237272 +0000
@@ -413,6 +413,15 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called omninet.
 
+config USB_SERIAL_CP2101
+	tristate "USB CP2101 UART Bridge Controller (EXPERIMENTAL)"
+	depends on USB_SERIAL && EXPERIMENTAL
+	help
+	  Say Y here if you want to use a CP2101 based USB to RS232 converter.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cp2101.
+
 config USB_EZUSB
 	bool
 	depends on USB_SERIAL_KEYSPAN_PDA || USB_SERIAL_XIRCOM || USB_SERIAL_KEYSPAN || USB_SERIAL_WHITEHEAT
diff -Nru a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
--- a/drivers/usb/serial/Makefile	2005-03-18 00:37:33.292899112 +0000
+++ b/drivers/usb/serial/Makefile	2005-01-22 09:47:40.997971000 +0000
@@ -33,4 +33,5 @@
 obj-$(CONFIG_USB_SERIAL_VISOR)			+= visor.o
 obj-$(CONFIG_USB_SERIAL_WHITEHEAT)		+= whiteheat.o
 obj-$(CONFIG_USB_SERIAL_XIRCOM)			+= keyspan_pda.o
+obj-$(CONFIG_USB_SERIAL_CP2101)			+= cp2101.o
 

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to