2009-01-04 11:06:32

by Tollef Fog Heen

[permalink] [raw]
Subject: [PATCH] Winchiphead 340/1: full baud rate and status/control line support


Patch against current git (3c92ec8ae91ecf59d88c798301833d7cf83f2179)

From: Tollef Fog Heen <[email protected]>

* Implement support for all baud rates rather than just a hard coded set.
* Make it possible to control status and control lines

Signed-off-by: Tollef Fog Heen <[email protected]>
---
Documentation/usb/usb-serial.txt | 2
drivers/usb/serial/ch341.c | 413 +++++++++++++++++++++++++++++++--------
2 files changed, 340 insertions(+), 75 deletions(-)

diff --git a/Documentation/usb/usb-serial.txt b/Documentation/usb/usb-serial.txt
index ff2c1ff..201ec84 100644
--- a/Documentation/usb/usb-serial.txt
+++ b/Documentation/usb/usb-serial.txt
@@ -434,6 +434,8 @@ Winchiphead CH341 Driver
The manufacturer's website: http://www.winchiphead.com/.
For any questions or problems with this driver, please contact
[email protected].
+ Extensions for universal baudrate settings and modem status/control have
+ been added by werner <at> cornelius-consult.de.


Generic Serial driver
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index f61e3ca..c1739b9 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -1,5 +1,7 @@
/*
* Copyright 2007, Frank A Kingswood <[email protected]>
+ * Copyright 2007, Werner Cornelius <werner <at> cornelius-consult.de>
+ * Copyright 2008, 2009, Tollef Fog Heen <[email protected]>
*
* ch341.c implements a serial port driver for the Winchiphead CH341.
*
@@ -24,6 +26,36 @@
#define DEFAULT_BAUD_RATE 2400
#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,14 @@ 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 */
+ int delta_msr_cond;
+ 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 */
+ struct usb_serial *serial;
};

static int ch341_control_out(struct usb_device *dev, u8 request,
@@ -68,41 +105,46 @@ static int ch341_control_in(struct usb_device *dev,
}

static int ch341_set_baudrate(struct usb_device *dev,
+ struct tty_struct *tty,
struct ch341_private *priv)
{
short a, b;
int r;
+ unsigned long factor;
+ short divisor;
+ unsigned baud;
+ unsigned t1, t2;

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:
- return -EINVAL;
+
+ if (priv->baud_rate > CH341_BAUDBASE_FACTOR)
+ priv->baud_rate = CH341_BAUDBASE_FACTOR;
+
+ factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
+ divisor = CH341_BAUDBASE_DIVMAX;
+
+ while ((factor > 0xfff0) && divisor) {
+ factor >>= 3;
+ divisor--;
}

+ if (factor > 0xfff0) { /* Clamp */
+ factor = 0xfff0;
+ divisor = 0;
+ }
+
+ t1 = factor;
+ for (t2 = divisor; t2 <= CH341_BAUDBASE_DIVMAX; t2++)
+ t1 >>= 3;
+ baud = CH341_BAUDBASE_FACTOR / t1;
+
+ if (baud && tty)
+ tty_encode_baud_rate(tty, baud, baud);
+
+ 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 +152,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 +175,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.
- */
- r = 0;
+ /* 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;
@@ -166,7 +212,7 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
if (r < 0)
goto out;

- r = ch341_set_baudrate(dev, priv);
+ r = ch341_set_baudrate(dev, NULL, priv);
if (r < 0)
goto out;

@@ -180,7 +226,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;

@@ -188,16 +234,16 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
if (r < 0)
goto out;

- r = ch341_set_baudrate(dev, priv);
+ r = ch341_set_baudrate(dev, NULL, 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,21 +262,51 @@ 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;
+
+ priv->serial = serial;

r = ch341_configure(serial->dev, priv);
if (r < 0)
goto error;

usb_set_serial_port_data(serial->port[0], priv);
+
return 0;

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;
+
+ 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 (C_HUPCL(tty)) {
+ /* 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);
+ priv->delta_msr_cond = 1;
+}
+
+
/* open this device, set default parameters */
static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port,
struct file *filp)
@@ -242,21 +318,32 @@ 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;
+
+ if (tty && C_CLOCAL(tty))
+ priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;
+ else
+ priv->line_control = 0;

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;

- r = ch341_set_baudrate(serial->dev, priv);
+ r = ch341_set_baudrate(serial->dev, tty, priv);
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);
+ return -EPROTO;
+ }
r = usb_serial_generic_open(tty, port, filp);

out: return r;
@@ -270,38 +357,203 @@ 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()");

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;
+ priv->baud_rate = 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, tty, 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_baudrate(port->serial->dev, priv);
+ 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]
*/
+ tty->termios->c_cflag &= ~(CSIZE | PARENB | CSTOPB);
+}
+
+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);
+ priv->delta_msr_cond = 1;
+ }
+
+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) {
+ priv->delta_msr_cond = 0;
+ wait_event_interruptible(priv->delta_msr_wait,
+ (priv->delta_msr_cond == 1));
+ /* 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 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);
+
+ return result;
+}
+
+static void ch341_shutdown(struct usb_serial *serial)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(serial->port[0]);
+
+ dbg("%s", __func__);

- /* 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);
+ kfree(priv);
}

static struct usb_driver ch341_driver = {
@@ -317,12 +569,23 @@ static struct usb_serial_driver ch341_device = {
.owner = THIS_MODULE,
.name = "ch341-uart",
},
- .id_table = id_table,
- .usb_driver = &ch341_driver,
- .num_ports = 1,
- .open = ch341_open,
- .set_termios = ch341_set_termios,
- .attach = ch341_attach,
+ .id_table = id_table,
+ .usb_driver = &ch341_driver,
+ /*
+ .num_interrupt_in = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ */
+ .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,
+ .shutdown = ch341_shutdown,
};

static int __init ch341_init(void)

--
Tollef Fog Heen
UNIX is user friendly, it's just picky about who its friends are


2009-01-04 11:06:45

by Tollef Fog Heen

[permalink] [raw]
Subject: Re: [PATCH] Winchiphead 340/1: full baud rate and status/control line support

]] Alan Cox

| Some niggles but this is a big improvement to the code.

Thanks. New patch incoming RSN.

| > static int ch341_set_baudrate(struct usb_device *dev,
| > + struct tty_struct *tty,
| > struct ch341_private *priv)
| > + baud = CH341_BAUDBASE_FACTOR / t1;
| > +
| > + if (baud && tty)
| > + tty_encode_baud_rate(tty, baud, baud);
|
| Can you really get a speed below 1 baud ?

Unsure what you mean here? Why would you ever end up with baudrates
below 1?

| > + control = priv->line_control;
| > + spin_unlock_irqrestore(&priv->lock, flags);
| > +
| > + return ch341_set_handshake(port->serial->dev, control);
| > +}
|
| This could race another set_handshake ? I'm not sure it matters and I'd
| suggest that stuff gets fixed after this is submitted separately

What's the easiest way to fix this?

--
Tollef Fog Heen
UNIX is user friendly, it's just picky about who its friends are

2009-01-04 12:17:04

by Alan

[permalink] [raw]
Subject: Re: [PATCH] Winchiphead 340/1: full baud rate and status/control line support

> + if (factor > 0xfff0) { /* Clamp */
> + factor = 0xfff0;
> + divisor = 0;
> + }
> +
> + t1 = factor;
> + for (t2 = divisor; t2 <= CH341_BAUDBASE_DIVMAX; t2++)
> + t1 >>= 3;
> + baud = CH341_BAUDBASE_FACTOR / t1;
> +

Same question as before: Can baud really become zero at this point ? Also
for that matter what guarantees that t1 cannot become zero in this
computation ?


> + if (baud && tty)
> + tty_encode_baud_rate(tty, baud, baud);

The check for B0 (hangup) was done earlier (see set_termios). So if you
can end up with a requested baud rate being turned into 0 you need to
bump it up to 1. If not you don't need the test.

2009-01-04 12:19:18

by Alan

[permalink] [raw]
Subject: Re: [PATCH] Winchiphead 340/1: full baud rate and status/control line support

> |
> | This could race another set_handshake ? I'm not sure it matters and I'd
> | suggest that stuff gets fixed after this is submitted separately
>
> What's the easiest way to fix this?

It can sleep so probably all the cases where handshake lines are updated
need to use a mutex to protect them. That will depend if any occur in the
IRQ paths in which case it gets a lot trickier.

2009-01-04 21:16:16

by Tollef Fog Heen

[permalink] [raw]
Subject: Re: [PATCH] Winchiphead 340/1: full baud rate and status/control line support

]] Alan Cox

| > + if (factor > 0xfff0) { /* Clamp */
| > + factor = 0xfff0;
| > + divisor = 0;
| > + }
| > +
| > + t1 = factor;
| > + for (t2 = divisor; t2 <= CH341_BAUDBASE_DIVMAX; t2++)
| > + t1 >>= 3;
| > + baud = CH341_BAUDBASE_FACTOR / t1;
| > +
|
| Same question as before: Can baud really become zero at this point ? Also
| for that matter what guarantees that t1 cannot become zero in this
| computation ?

Nothing, that code is completely wrong. I've rewritten it now. Thanks
for explaining.

| > + if (baud && tty)
| > + tty_encode_baud_rate(tty, baud, baud);
|
| The check for B0 (hangup) was done earlier (see set_termios). So if you
| can end up with a requested baud rate being turned into 0 you need to
| bump it up to 1. If not you don't need the test.

Indeed, fixed as well. New patch incoming in a few minutes.

I've also added the mutex protecting set_handshake, but I feel less
confident about whether that's the right approach, so feedback there is
also appreciated.

--
Tollef Fog Heen
UNIX is user friendly, it's just picky about who its friends are

2009-01-04 21:18:20

by Tollef Fog Heen

[permalink] [raw]
Subject: [PATCH] Winchiphead 340/1: full baud rate and status/control line support


Patch against current git (3c92ec8ae91ecf59d88c798301833d7cf83f2179)

From: Tollef Fog Heen <[email protected]>

* Implement support for all baud rates rather than just a hard coded set.
* Make it possible to control status and control lines
* Grab a bunch of #defines from FreeBSD to reduce the number of magic
numbers in the file

Signed-off-by: Tollef Fog Heen <[email protected]>
---

Documentation/usb/usb-serial.txt | 2
drivers/usb/serial/ch341.c | 501 ++++++++++++++++++++++++++++++++-------
2 files changed, 419 insertions(+), 84 deletions(-)

diff --git a/Documentation/usb/usb-serial.txt b/Documentation/usb/usb-serial.txt
index ff2c1ff..201ec84 100644
--- a/Documentation/usb/usb-serial.txt
+++ b/Documentation/usb/usb-serial.txt
@@ -434,6 +434,8 @@ Winchiphead CH341 Driver
The manufacturer's website: http://www.winchiphead.com/.
For any questions or problems with this driver, please contact
[email protected].
+ Extensions for universal baudrate settings and modem status/control have
+ been added by werner <at> cornelius-consult.de.


Generic Serial driver
diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c
index f61e3ca..a661941 100644
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -1,5 +1,7 @@
/*
* Copyright 2007, Frank A Kingswood <[email protected]>
+ * Copyright 2007, Werner Cornelius <werner <at> cornelius-consult.de>
+ * Copyright 2008, 2009, Tollef Fog Heen <[email protected]>
*
* ch341.c implements a serial port driver for the Winchiphead CH341.
*
@@ -11,6 +13,9 @@
* 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.
+ *
+ * Also see http://fxr.watson.org/fxr/source/dev/usb/uchcom.c (or
+ * freebsd:/src/sys/dev/usb/uchcom.c)
*/

#include <linux/kernel.h>
@@ -19,11 +24,81 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
+#include <linux/mutex.h>
#include <linux/serial.h>

#define DEFAULT_BAUD_RATE 2400
#define DEFAULT_TIMEOUT 1000

+/* flags for IO-Bits */
+#define CH341_BIT_RTS (1 << 6)
+#define CH341_BIT_DTR (1 << 5)
+
+#define CH341_REQ_GET_VERSION 0x5F
+#define CH341_REQ_READ_REG 0x95
+#define CH341_REQ_WRITE_REG 0x9A
+#define CH341_REQ_RESET 0xA1
+#define CH341_REQ_SET_DTRRTS 0xA4
+
+#define CH341_REG_STAT1 0x06
+#define CH341_REG_STAT2 0x07
+#define CH341_REG_BPS_PRE 0x12
+#define CH341_REG_BPS_DIV 0x13
+#define CH341_REG_BPS_MOD 0x14
+#define CH341_REG_BPS_PAD 0x0F
+#define CH341_REG_BREAK1 0x05
+#define CH341_REG_BREAK2 0x18
+#define CH341_REG_LCR1 0x18
+#define CH341_REG_LCR2 0x25
+
+#define CH341_VER_20 0x20
+
+#define CH341_BPS_MOD_BASE 20000000
+#define CH341_BPS_MOD_BASE_OFS 1100
+
+#define CH341_BRK1_MASK 0x01
+#define CH341_BRK2_MASK 0x40
+
+#define CH341_LCR1_MASK 0xAF
+#define CH341_LCR2_MASK 0x07
+#define CH341_LCR1_PARENB 0x80
+#define CH341_LCR2_PAREVEN 0x07
+#define CH341_LCR2_PARODD 0x06
+#define CH341_LCR2_PARMARK 0x05
+#define CH341_LCR2_PARSPACE 0x04
+
+#define CH341_INTR_STAT1 0x02
+#define CH341_INTR_STAT2 0x03
+#define CH341_INTR_LEAST 4
+
+#define CH341_REGISTERS(a,b) ((a << 8) | b)
+
+/******************************/
+/* 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 +109,15 @@ 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 */
+ int delta_msr_cond;
+ unsigned baud_rate; /* set baud rate */
+ struct mutex handshake_lock;
+ 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 */
+ struct usb_serial *serial;
};

static int ch341_control_out(struct usb_device *dev, u8 request,
@@ -68,61 +149,71 @@ static int ch341_control_in(struct usb_device *dev,
}

static int ch341_set_baudrate(struct usb_device *dev,
+ struct tty_struct *tty,
struct ch341_private *priv)
{
short a, b;
int r;
+ unsigned long factor;
+ unsigned baud_rate;
+ 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:
- return -EINVAL;
+
+ if (priv->baud_rate > CH341_BAUDBASE_FACTOR)
+ priv->baud_rate = CH341_BAUDBASE_FACTOR;
+ else if (priv->baud_rate < 50) { /* Slowest we can go */
+ priv->baud_rate = 50;
+ }
+ baud_rate = priv->baud_rate;
+
+ if (tty)
+ tty_encode_baud_rate(tty, baud_rate, baud_rate);
+
+ if (baud_rate == 307200 ||
+ baud_rate == 921600) {
+ divisor = 7;
+ factor = (baud_rate == 307200 ? 0xD9 : 0xF3 );
+ } else {
+ factor = (CH341_BAUDBASE_FACTOR / baud_rate);
+ divisor = CH341_BAUDBASE_DIVMAX;
+ while ((factor > 0xfff0) && divisor) {
+ factor >>= 3;
+ divisor--;
+ }
}

- r = ch341_control_out(dev, 0x9a, 0x1312, a);
+ factor = 0x10000 - factor;
+ a = (factor & 0xff00) | divisor;
+ b = CH341_BPS_MOD_BASE / baud_rate + CH341_BPS_MOD_BASE_OFS;
+ b += b/2;
+ b /= 0x100;
+ b++;
+
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REGISTERS(CH341_REG_BPS_DIV,
+ CH341_REG_BPS_PRE),
+ a);
if (!r)
- r = ch341_control_out(dev, 0x9a, 0x0f2c, b);
-
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REGISTERS(CH341_REG_BPS_PAD,
+ CH341_REG_BPS_MOD),
+ b);
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, CH341_REQ_SET_DTRRTS, ~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()");

@@ -130,14 +221,21 @@ static int ch341_get_status(struct usb_device *dev)
if (!buffer)
return -ENOMEM;

- r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size);
+ r = ch341_control_in(dev, CH341_REQ_READ_REG,
+ CH341_REGISTERS(CH341_REG_STAT2, CH341_REG_STAT1),
+ 0, buffer, size);
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.
- */
- r = 0;
+ /* 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;
@@ -158,46 +256,52 @@ static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
return -ENOMEM;

/* expect two bytes 0x27 0x00 */
- r = ch341_control_in(dev, 0x5f, 0, 0, buffer, size);
+ r = ch341_control_in(dev, CH341_REQ_GET_VERSION, 0, 0, buffer, size);
if (r < 0)
goto out;

- r = ch341_control_out(dev, 0xa1, 0, 0);
+ r = ch341_control_out(dev, CH341_REQ_RESET, 0, 0);
if (r < 0)
goto out;

- r = ch341_set_baudrate(dev, priv);
+ r = ch341_set_baudrate(dev, NULL, priv);
if (r < 0)
goto out;

/* expect two bytes 0x56 0x00 */
- r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size);
+ r = ch341_control_in(dev, CH341_REQ_READ_REG,
+ CH341_REGISTERS(CH341_REG_LCR2, CH341_REG_LCR1),
+ 0, buffer, size);
if (r < 0)
goto out;

- r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050);
+ r = ch341_control_out(dev, CH341_REQ_WRITE_REG,
+ CH341_REGISTERS(CH341_REG_LCR2, CH341_REG_LCR1),
+ 0x0050);
if (r < 0)
goto out;

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

- r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a);
+ r = ch341_control_out(dev, CH341_REQ_RESET, 0x501f, 0xd90a);
if (r < 0)
goto out;

- r = ch341_set_baudrate(dev, priv);
+ r = ch341_set_baudrate(dev, NULL, priv);
if (r < 0)
goto out;

- r = ch341_set_handshake(dev, priv);
+ mutex_lock(&priv->handshake_lock);
+ r = ch341_set_handshake(dev, priv->line_control);
+ mutex_unlock(&priv->handshake_lock);
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,21 +320,54 @@ 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;
+ mutex_init(&priv-> handshake_lock);
+
+ priv->serial = serial;

r = ch341_configure(serial->dev, priv);
if (r < 0)
goto error;

usb_set_serial_port_data(serial->port[0], priv);
+
return 0;

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;
+
+ 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 (C_HUPCL(tty)) {
+ /* drop DTR and RTS */
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->line_control = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ mutex_lock(&priv->handshake_lock);
+ ch341_set_handshake(port->serial->dev, 0);
+ mutex_unlock(&priv->handshake_lock);
+ }
+ wake_up_interruptible(&priv->delta_msr_wait);
+ priv->delta_msr_cond = 1;
+}
+
+
/* open this device, set default parameters */
static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port,
struct file *filp)
@@ -242,21 +379,34 @@ 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;
+
+ if (tty && C_CLOCAL(tty))
+ priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR;
+ else
+ priv->line_control = 0;

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

- r = ch341_set_handshake(serial->dev, priv);
+ mutex_lock(&priv->handshake_lock);
+ r = ch341_set_handshake(serial->dev, priv->line_control);
+ mutex_unlock(&priv->handshake_lock);
if (r)
goto out;

- r = ch341_set_baudrate(serial->dev, priv);
+ r = ch341_set_baudrate(serial->dev, tty, priv);
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);
+ return -EPROTO;
+ }
r = usb_serial_generic_open(tty, port, filp);

out: return r;
@@ -270,38 +420,210 @@ 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()");

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;
+ priv->baud_rate = 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, tty, 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_baudrate(port->serial->dev, priv);
+ mutex_lock(&priv->handshake_lock);
+ ch341_set_handshake(port->serial->dev, priv->line_control);
+ mutex_unlock(&priv->handshake_lock);

/* Unimplemented:
* (cflag & CSIZE) : data bits [5, 8]
* (cflag & PARENB) : parity {NONE, EVEN, ODD}
* (cflag & CSTOPB) : stop bits [1, 2]
*/
+ tty->termios->c_cflag &= ~(CSIZE | PARENB | CSTOPB);
+}
+
+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;
+ int r;
+
+ 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);
+
+ mutex_lock(&priv->handshake_lock);
+ r = ch341_set_handshake(port->serial->dev, control);
+ mutex_unlock(&priv->handshake_lock);
+
+ return r;
+}
+
+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);
+ priv->delta_msr_cond = 1;
+ }
+
+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) {
+ priv->delta_msr_cond = 0;
+ wait_event_interruptible(priv->delta_msr_wait,
+ (priv->delta_msr_cond == 1));
+ /* 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 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);
+
+ return result;
+}
+
+static void ch341_shutdown(struct usb_serial *serial)
+{
+ struct ch341_private *priv = usb_get_serial_port_data(serial->port[0]);
+
+ dbg("%s", __func__);

- /* 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);
+ kfree(priv);
}

static struct usb_driver ch341_driver = {
@@ -317,12 +639,23 @@ static struct usb_serial_driver ch341_device = {
.owner = THIS_MODULE,
.name = "ch341-uart",
},
- .id_table = id_table,
- .usb_driver = &ch341_driver,
- .num_ports = 1,
- .open = ch341_open,
- .set_termios = ch341_set_termios,
- .attach = ch341_attach,
+ .id_table = id_table,
+ .usb_driver = &ch341_driver,
+ /*
+ .num_interrupt_in = 1,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+ */
+ .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,
+ .shutdown = ch341_shutdown,
};

static int __init ch341_init(void)

--
Tollef Fog Heen
UNIX is user friendly, it's just picky about who its friends are