Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 664d5df9 authored by Werner Cornelius's avatar Werner Cornelius Committed by Greg Kroah-Hartman
Browse files

USB: usb-serial ch341: support for DTR/RTS/CTS

Fixup of Werner Cornelius patch to the ch341 USB-serial driver, which adds:
- support all baudrates, not just a hard-coded set
- support for controlling DTR, RTS and CTS

Features still missing:
- character length other than 8 bits
- parity settings
- break control

I adapted his patch for the new usb_serial API introduced in 2.6.25-git8 by
Alan Cox on 22 July 2008. Non-compliance to the new API was a reason for
refusing a similar patch from Tollef Fog Heen.

Usage example by Tollef Fog Heen :
        TEMPer USB thermometer <http://err.no/src/TEMPer.c

>

Signed-off-by: default avatarWerner Cornelius <Werner.Cornelius@cornelius-consult.de>
Signed-off-by: default avatarBoris Hajduk <boris@hajduk.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent bcbbbfc1
Loading
Loading
Loading
Loading
+302 −72
Original line number Diff line number Diff line
/*
 * Copyright 2007, Frank A Kingswood <frank@kingswood-consulting.co.uk>
 * Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
 * Copyright 2009, Boris Hajduk <boris@hajduk.org>
 *
 * ch341.c implements a serial port driver for the Winchiphead CH341.
 *
@@ -21,9 +23,39 @@
#include <linux/usb/serial.h>
#include <linux/serial.h>

#define DEFAULT_BAUD_RATE 2400
#define DEFAULT_BAUD_RATE 9600
#define DEFAULT_TIMEOUT   1000

/* flags for IO-Bits */
#define CH341_BIT_RTS (1 << 6)
#define CH341_BIT_DTR (1 << 5)

/******************************/
/* interrupt pipe definitions */
/******************************/
/* always 4 interrupt bytes */
/* first irq byte normally 0x08 */
/* second irq byte base 0x7d + below */
/* third irq byte base 0x94 + below */
/* fourth irq byte normally 0xee */

/* second interrupt byte */
#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */

/* status returned in third interrupt answer byte, inverted in data
   from irq */
#define CH341_BIT_CTS 0x01
#define CH341_BIT_DSR 0x02
#define CH341_BIT_RI  0x04
#define CH341_BIT_DCD 0x08
#define CH341_BITS_MODEM_STAT 0x0f /* all bits */

/*******************************/
/* baudrate calculation factor */
/*******************************/
#define CH341_BAUDBASE_FACTOR 1532620800
#define CH341_BAUDBASE_DIVMAX 3

static int debug;

static struct usb_device_id id_table [] = {
@@ -34,9 +66,12 @@ static struct usb_device_id id_table [] = {
MODULE_DEVICE_TABLE(usb, id_table);

struct ch341_private {
	unsigned baud_rate;
	u8 dtr;
	u8 rts;
	spinlock_t lock; /* access lock */
	wait_queue_head_t delta_msr_wait; /* wait queue for modem status */
	unsigned baud_rate; /* set baud rate */
	u8 line_control; /* set line control value RTS/DTR */
	u8 line_status; /* active status of modem control inputs */
	u8 multi_status_change; /* status changed multiple since last call */
};

static int ch341_control_out(struct usb_device *dev, u8 request,
@@ -72,37 +107,28 @@ static int ch341_set_baudrate(struct usb_device *dev,
{
	short a, b;
	int r;
	unsigned long factor;
	short divisor;

	dbg("ch341_set_baudrate(%d)", priv->baud_rate);
	switch (priv->baud_rate) {
	case 2400:
		a = 0xd901;
		b = 0x0038;
		break;
	case 4800:
		a = 0x6402;
		b = 0x001f;
		break;
	case 9600:
		a = 0xb202;
		b = 0x0013;
		break;
	case 19200:
		a = 0xd902;
		b = 0x000d;
		break;
	case 38400:
		a = 0x6403;
		b = 0x000a;
		break;
	case 115200:
		a = 0xcc03;
		b = 0x0008;
		break;
	default:

	if (!priv->baud_rate)
		return -EINVAL;
	factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
	divisor = CH341_BAUDBASE_DIVMAX;

	while ((factor > 0xfff0) && divisor) {
		factor >>= 3;
		divisor--;
	}

	if (factor > 0xfff0)
		return -EINVAL;

	factor = 0x10000 - factor;
	a = (factor & 0xff00) | divisor;
	b = factor & 0xff;

	r = ch341_control_out(dev, 0x9a, 0x1312, a);
	if (!r)
		r = ch341_control_out(dev, 0x9a, 0x0f2c, b);
@@ -110,19 +136,18 @@ static int ch341_set_baudrate(struct usb_device *dev,
	return r;
}

static int ch341_set_handshake(struct usb_device *dev,
			       struct ch341_private *priv)
static int ch341_set_handshake(struct usb_device *dev, u8 control)
{
	dbg("ch341_set_handshake(%d,%d)", priv->dtr, priv->rts);
	return ch341_control_out(dev, 0xa4,
		~((priv->dtr?1<<5:0)|(priv->rts?1<<6:0)), 0);
	dbg("ch341_set_handshake(0x%02x)", control);
	return ch341_control_out(dev, 0xa4, ~control, 0);
}

static int ch341_get_status(struct usb_device *dev)
static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
{
	char *buffer;
	int r;
	const unsigned size = 8;
	unsigned long flags;

	dbg("ch341_get_status()");

@@ -134,10 +159,15 @@ static int ch341_get_status(struct usb_device *dev)
	if (r < 0)
		goto out;

	/* Not having the datasheet for the CH341, we ignore the bytes returned
	 * from the device. Return error if the device did not respond in time.
	 */
	/* setup the private status if available */
	if (r == 2) {
		r = 0;
		spin_lock_irqsave(&priv->lock, flags);
		priv->line_status = (~(*buffer)) & CH341_BITS_MODEM_STAT;
		priv->multi_status_change = 0;
		spin_unlock_irqrestore(&priv->lock, flags);
	} else
		r = -EPROTO;

out:	kfree(buffer);
	return r;
@@ -180,7 +210,7 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
		goto out;

	/* expect 0xff 0xee */
	r = ch341_get_status(dev);
	r = ch341_get_status(dev, priv);
	if (r < 0)
		goto out;

@@ -192,12 +222,12 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
	if (r < 0)
		goto out;

	r = ch341_set_handshake(dev, priv);
	r = ch341_set_handshake(dev, priv->line_control);
	if (r < 0)
		goto out;

	/* expect 0x9f 0xee */
	r = ch341_get_status(dev);
	r = ch341_get_status(dev, priv);

out:	kfree(buffer);
	return r;
@@ -216,9 +246,10 @@ static int ch341_attach(struct usb_serial *serial)
	if (!priv)
		return -ENOMEM;

	spin_lock_init(&priv->lock);
	init_waitqueue_head(&priv->delta_msr_wait);
	priv->baud_rate = DEFAULT_BAUD_RATE;
	priv->dtr = 1;
	priv->rts = 1;
	priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;

	r = ch341_configure(serial->dev, priv);
	if (r < 0)
@@ -231,6 +262,35 @@ error: kfree(priv);
	return r;
}

static void ch341_close(struct tty_struct *tty, struct usb_serial_port *port,
				struct file *filp)
{
	struct ch341_private *priv = usb_get_serial_port_data(port);
	unsigned long flags;
	unsigned int c_cflag;

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

	/* shutdown our urbs */
	dbg("%s - shutting down urbs", __func__);
	usb_kill_urb(port->write_urb);
	usb_kill_urb(port->read_urb);
	usb_kill_urb(port->interrupt_in_urb);

	if (tty) {
		c_cflag = tty->termios->c_cflag;
		if (c_cflag & HUPCL) {
			/* drop DTR and RTS */
			spin_lock_irqsave(&priv->lock, flags);
			priv->line_control = 0;
			spin_unlock_irqrestore(&priv->lock, flags);
			ch341_set_handshake(port->serial->dev, 0);
		}
	}
	wake_up_interruptible(&priv->delta_msr_wait);
}


/* open this device, set default parameters */
static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port,
				struct file *filp)
@@ -242,14 +302,13 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port,
	dbg("ch341_open()");

	priv->baud_rate = DEFAULT_BAUD_RATE;
	priv->dtr = 1;
	priv->rts = 1;
	priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;

	r = ch341_configure(serial->dev, priv);
	if (r)
		goto out;

	r = ch341_set_handshake(serial->dev, priv);
	r = ch341_set_handshake(serial->dev, priv->line_control);
	if (r)
		goto out;

@@ -257,6 +316,16 @@ static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port,
	if (r)
		goto out;

	dbg("%s - submitting interrupt urb", __func__);
	port->interrupt_in_urb->dev = serial->dev;
	r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
	if (r) {
		dev_err(&port->dev, "%s - failed submitting interrupt urb,"
			" error %d\n", __func__, r);
		ch341_close(tty, port, NULL);
		return -EPROTO;
	}

	r = usb_serial_generic_open(tty, port, filp);

out:	return r;
@@ -270,38 +339,194 @@ static void ch341_set_termios(struct tty_struct *tty,
{
	struct ch341_private *priv = usb_get_serial_port_data(port);
	unsigned baud_rate;
	unsigned long flags;

	dbg("ch341_set_termios()");

	if (!tty || !tty->termios)
		return;

	baud_rate = tty_get_baud_rate(tty);

	switch (baud_rate) {
	case 2400:
	case 4800:
	case 9600:
	case 19200:
	case 38400:
	case 115200:
	priv->baud_rate = baud_rate;
		break;
	default:
		dbg("Rate %d not supported, using %d",
			baud_rate, DEFAULT_BAUD_RATE);
		priv->baud_rate = DEFAULT_BAUD_RATE;
	}

	if (baud_rate) {
		spin_lock_irqsave(&priv->lock, flags);
		priv->line_control |= (CH341_BIT_DTR | CH341_BIT_RTS);
		spin_unlock_irqrestore(&priv->lock, flags);
		ch341_set_baudrate(port->serial->dev, priv);
	} else {
		spin_lock_irqsave(&priv->lock, flags);
		priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
		spin_unlock_irqrestore(&priv->lock, flags);
	}

	ch341_set_handshake(port->serial->dev, priv->line_control);

	/* Unimplemented:
	 * (cflag & CSIZE) : data bits [5, 8]
	 * (cflag & PARENB) : parity {NONE, EVEN, ODD}
	 * (cflag & CSTOPB) : stop bits [1, 2]
	 */
}

static int ch341_tiocmset(struct tty_struct *tty, struct file *file,
			  unsigned int set, unsigned int clear)
{
	struct usb_serial_port *port = tty->driver_data;
	struct ch341_private *priv = usb_get_serial_port_data(port);
	unsigned long flags;
	u8 control;

	spin_lock_irqsave(&priv->lock, flags);
	if (set & TIOCM_RTS)
		priv->line_control |= CH341_BIT_RTS;
	if (set & TIOCM_DTR)
		priv->line_control |= CH341_BIT_DTR;
	if (clear & TIOCM_RTS)
		priv->line_control &= ~CH341_BIT_RTS;
	if (clear & TIOCM_DTR)
		priv->line_control &= ~CH341_BIT_DTR;
	control = priv->line_control;
	spin_unlock_irqrestore(&priv->lock, flags);

	return ch341_set_handshake(port->serial->dev, control);
}

static void ch341_read_int_callback(struct urb *urb)
{
	struct usb_serial_port *port = (struct usb_serial_port *) urb->context;
	unsigned char *data = urb->transfer_buffer;
	unsigned int actual_length = urb->actual_length;
	int status;

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

	switch (urb->status) {
	case 0:
		/* success */
		break;
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		/* this urb is terminated, clean up */
		dbg("%s - urb shutting down with status: %d", __func__,
		    urb->status);
		return;
	default:
		dbg("%s - nonzero urb status received: %d", __func__,
		    urb->status);
		goto exit;
	}

	usb_serial_debug_data(debug, &port->dev, __func__,
			      urb->actual_length, urb->transfer_buffer);

	if (actual_length >= 4) {
		struct ch341_private *priv = usb_get_serial_port_data(port);
		unsigned long flags;

		spin_lock_irqsave(&priv->lock, flags);
		priv->line_status = (~(data[2])) & CH341_BITS_MODEM_STAT;
		if ((data[1] & CH341_MULT_STAT))
			priv->multi_status_change = 1;
		spin_unlock_irqrestore(&priv->lock, flags);
		wake_up_interruptible(&priv->delta_msr_wait);
	}

exit:
	status = usb_submit_urb(urb, GFP_ATOMIC);
	if (status)
		dev_err(&urb->dev->dev,
			"%s - usb_submit_urb failed with result %d\n",
			__func__, status);
}

static int wait_modem_info(struct usb_serial_port *port, unsigned int arg)
{
	struct ch341_private *priv = usb_get_serial_port_data(port);
	unsigned long flags;
	u8 prevstatus;
	u8 status;
	u8 changed;
	u8 multi_change = 0;

	spin_lock_irqsave(&priv->lock, flags);
	prevstatus = priv->line_status;
	priv->multi_status_change = 0;
	spin_unlock_irqrestore(&priv->lock, flags);

	while (!multi_change) {
		interruptible_sleep_on(&priv->delta_msr_wait);
		/* see if a signal did it */
		if (signal_pending(current))
			return -ERESTARTSYS;

		spin_lock_irqsave(&priv->lock, flags);
		status = priv->line_status;
		multi_change = priv->multi_status_change;
		spin_unlock_irqrestore(&priv->lock, flags);

		changed = prevstatus ^ status;

		if (((arg & TIOCM_RNG) && (changed & CH341_BIT_RI)) ||
		    ((arg & TIOCM_DSR) && (changed & CH341_BIT_DSR)) ||
		    ((arg & TIOCM_CD)  && (changed & CH341_BIT_DCD)) ||
		    ((arg & TIOCM_CTS) && (changed & CH341_BIT_CTS))) {
			return 0;
		}
		prevstatus = status;
	}

	return 0;
}

/*static int ch341_ioctl(struct usb_serial_port *port, struct file *file,*/
static int ch341_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", __func__, port->number, cmd);

	switch (cmd) {
	case TIOCMIWAIT:
		dbg("%s (%d) TIOCMIWAIT", __func__,  port->number);
		return wait_modem_info(port, arg);

	default:
		dbg("%s not supported = 0x%04x", __func__, cmd);
		break;
	}

	return -ENOIOCTLCMD;
}

static int ch341_tiocmget(struct tty_struct *tty, struct file *file)
{
	struct usb_serial_port *port = tty->driver_data;
	struct ch341_private *priv = usb_get_serial_port_data(port);
	unsigned long flags;
	u8 mcr;
	u8 status;
	unsigned int result;

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

	spin_lock_irqsave(&priv->lock, flags);
	mcr = priv->line_control;
	status = priv->line_status;
	spin_unlock_irqrestore(&priv->lock, flags);

	result = ((mcr & CH341_BIT_DTR)		? TIOCM_DTR : 0)
		  | ((mcr & CH341_BIT_RTS)	? TIOCM_RTS : 0)
		  | ((status & CH341_BIT_CTS)	? TIOCM_CTS : 0)
		  | ((status & CH341_BIT_DSR)	? TIOCM_DSR : 0)
		  | ((status & CH341_BIT_RI)	? TIOCM_RI  : 0)
		  | ((status & CH341_BIT_DCD)	? TIOCM_CD  : 0);

	dbg("%s - result = %x", __func__, result);

	 /* Copy back the old hardware settings */
	 tty_termios_copy_hw(tty->termios, old_termios);
	 /* And re-encode with the new baud */
	 tty_encode_baud_rate(tty, baud_rate, baud_rate);
	return result;
}

static struct usb_driver ch341_driver = {
@@ -321,7 +546,12 @@ static struct usb_serial_driver ch341_device = {
	.usb_driver        = &ch341_driver,
	.num_ports         = 1,
	.open              = ch341_open,
	.close             = ch341_close,
	.ioctl             = ch341_ioctl,
	.set_termios       = ch341_set_termios,
	.tiocmget          = ch341_tiocmget,
	.tiocmset          = ch341_tiocmset,
	.read_int_callback = ch341_read_int_callback,
	.attach            = ch341_attach,
};