Hello, attached you will find the patch against the 2.6.23-rc6-mm1
Changed fetaures: 1. All baudrates possible (dynamic baudfactor calculation) 2. Added support of modem control and status lines. Still missing (due to lack of an usb sniffer): 1. Break control 2. Parity Werner --- linux-2.6.23-rc6-mm1/drivers/usb/serial/ch341.c 2007-09-21 09:56:56.000000000 +0200 +++ develop/drivers/usb/serial/ch341.c 2007-09-21 10:00:26.000000000 +0200 @@ -1,5 +1,9 @@ /* - * Copyright 2007, Frank A Kingswood <[EMAIL PROTECTED]> + * Copyright 2007, Frank A Kingswood <frank <at> kingswood-consulting.co.uk> + * + * Copyright 2007, Werner Cornelius <werner <at> cornelius-consult.de> + * for changes/extenions regarding universal baud rate capability and modem + * line control/status routines. * * ch341.c implements a serial port driver for the Winchiphead CH341. * @@ -21,327 +25,548 @@ #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 [] = { - { USB_DEVICE(0x4348, 0x5523) }, - { }, + { USB_DEVICE(0x4348, 0x5523) }, + { }, }; 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, - u16 value, u16 index) + u16 value, u16 index) { - int r; - dbg("ch341_control_out(%02x,%02x,%04x,%04x)", USB_DIR_OUT|0x40, - (int)request, (int)value, (int)index); + int r; + dbg("ch341_control_out(%02x,%02x,%04x,%04x)", USB_DIR_OUT|0x40, + (int)request, (int)value, (int)index); - r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - value, index, NULL, 0, DEFAULT_TIMEOUT); + r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); - return r; + return r; } static int ch341_control_in(struct usb_device *dev, - u8 request, u16 value, u16 index, - char *buf, unsigned bufsize) + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) { - int r; - dbg("ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)", USB_DIR_IN|0x40, - (int)request, (int)value, (int)index, buf, (int)bufsize); - - r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - value, index, buf, bufsize, DEFAULT_TIMEOUT); - return r; -} - -static int ch341_set_baudrate(struct usb_device *dev, - struct ch341_private *priv) -{ - short a, b; - int r; - - 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; - } + int r; + dbg("ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)", USB_DIR_IN|0x40, + (int)request, (int)value, (int)index, buf, (int)bufsize); + + r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + return r; +} + +int ch341_set_baudrate(struct usb_device *dev, struct ch341_private *priv) +{ + short a, b; + int r; + unsigned long factor; + short divisor; + + dbg("ch341_set_baudrate(%d)", priv->baud_rate); - r = ch341_control_out(dev, 0x9a, 0x1312, a); - if (!r) - r = ch341_control_out(dev, 0x9a, 0x0f2c, b); + if (!priv->baud_rate) + return -EINVAL; + + factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate); + divisor = CH341_BAUDBASE_DIVMAX; - return r; + 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); + + return r; } -static int ch341_set_handshake(struct usb_device *dev, - struct ch341_private *priv) +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) +int ch341_get_status(struct usb_device *dev, struct ch341_private *priv) { - char *buffer; - int r; - const unsigned size = 8; + char *buffer; + int r; + const unsigned size = 8; + unsigned long flags; - dbg("ch341_get_status()"); + dbg("ch341_get_status()"); - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; - r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size); - if ( r < 0) - goto out; + r = ch341_control_in(dev, 0x95, 0x0706, 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; +out: kfree(buffer); + return r; } /* -------------------------------------------------------------------------- */ -static int ch341_configure(struct usb_device *dev, struct ch341_private *priv) +int ch341_configure(struct usb_device *dev, struct ch341_private *priv) { - char *buffer; - int r; - const unsigned size = 8; - - dbg("ch341_configure()"); - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - /* expect two bytes 0x27 0x00 */ - r = ch341_control_in(dev, 0x5f, 0, 0, buffer, size); - if (r < 0) - goto out; - - r = ch341_control_out(dev, 0xa1, 0, 0); - if (r < 0) - goto out; - - r = ch341_set_baudrate(dev, priv); - if (r < 0) - goto out; - - /* expect two bytes 0x56 0x00 */ - r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size); - if (r < 0) - goto out; - - r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050); - if (r < 0) - goto out; - - /* expect 0xff 0xee */ - r = ch341_get_status(dev); - if (r < 0) - goto out; - - r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a); - if (r < 0) - goto out; - - r = ch341_set_baudrate(dev, priv); - if (r < 0) - goto out; - - r = ch341_set_handshake(dev, priv); - if (r < 0) - goto out; + char *buffer; + int r; + const unsigned size = 8; + + dbg("ch341_configure()"); + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* expect two bytes 0x27 0x00 */ + r = ch341_control_in(dev, 0x5f, 0, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0xa1, 0, 0); + if (r < 0) + goto out; + + r = ch341_set_baudrate(dev, priv); + if (r < 0) + goto out; + + /* expect two bytes 0x56 0x00 */ + r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050); + if (r < 0) + goto out; + + /* expect 0xff 0xee */ + r = ch341_get_status(dev, priv); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a); + if (r < 0) + goto out; + + r = ch341_set_baudrate(dev, priv); + if (r < 0) + goto out; + + r = ch341_set_handshake(dev, priv->line_control); + if (r < 0) + goto out; - /* expect 0x9f 0xee */ - r = ch341_get_status(dev); + /* expect 0x9f 0xee */ + r = ch341_get_status(dev, priv); -out: kfree(buffer); - return r; +out: kfree(buffer); + return r; } /* allocate private data */ static int ch341_attach(struct usb_serial *serial) { - struct ch341_private *priv; - int r; + struct ch341_private *priv; + int r; - dbg("ch341_attach()"); + dbg("ch341_attach()"); - /* private data */ - priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL); - if (!priv) - return -ENOMEM; + /* private data */ + priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; - priv->baud_rate = DEFAULT_BAUD_RATE; - priv->dtr = 1; - priv->rts = 1; + spin_lock_init(&priv->lock); + init_waitqueue_head(&priv->delta_msr_wait); + priv->baud_rate = DEFAULT_BAUD_RATE; + priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR; - r = ch341_configure(serial->dev, priv); - if (r < 0) - goto error; + r = ch341_configure(serial->dev, priv); + if (r < 0) + goto error; - usb_set_serial_port_data(serial->port[0], priv); - return 0; + usb_set_serial_port_data(serial->port[0], priv); + return 0; -error: kfree(priv); - return r; +error: kfree(priv); + return r; } -/* open this device, set default parameters */ -static int ch341_open(struct usb_serial_port *port, struct file *filp) +static void ch341_close(struct usb_serial_port *port, struct file *filp) { - struct usb_serial *serial = port->serial; - struct ch341_private *priv = usb_get_serial_port_data(serial->port[0]); - int r; - - dbg("ch341_open()"); - - priv->baud_rate = DEFAULT_BAUD_RATE; - priv->dtr = 1; - priv->rts = 1; + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int c_cflag; - r = ch341_configure(serial->dev, priv); - if (r) - goto out; + dbg("%s - port %d", __FUNCTION__, port->number); - r = ch341_set_handshake(serial->dev, priv); - if (r) - goto out; + /* shutdown our urbs */ + dbg("%s - shutting down urbs", __FUNCTION__); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->interrupt_in_urb); + + if (port->tty) { + c_cflag = port->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); +} - r = ch341_set_baudrate(serial->dev, priv); - if (r) - goto out; +/* open this device, set default parameters */ +static int ch341_open(struct usb_serial_port *port, struct file *filp) +{ + struct usb_serial *serial = port->serial; + struct ch341_private *priv = usb_get_serial_port_data(serial->port[0]); + int r; + + dbg("ch341_open()"); + + priv->baud_rate = DEFAULT_BAUD_RATE; + 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->line_control); + if (r) + goto out; + + r = ch341_set_baudrate(serial->dev, priv); + if (r) + goto out; + + dbg("%s - submitting interrupt urb", __FUNCTION__); + 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", __FUNCTION__, r); + ch341_close(port, NULL); + return -EPROTO; + } - r = usb_serial_generic_open(port, filp); + r = usb_serial_generic_open(port, filp); -out: return r; +out: return r; } /* Old_termios contains the original termios settings and * tty->termios contains the new setting to be used. */ static void ch341_set_termios(struct usb_serial_port *port, - struct ktermios *old_termios) + struct ktermios *old_termios) { - struct ch341_private *priv = usb_get_serial_port_data(port); - struct tty_struct *tty = port->tty; - unsigned baud_rate; + struct ch341_private *priv = usb_get_serial_port_data(port); + struct tty_struct *tty = port->tty; + unsigned baud_rate; + unsigned long flags; + + dbg("ch341_set_termios()"); + + if (!tty || !tty->termios) + return; + + baud_rate = tty_get_baud_rate(tty); + 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, 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] + */ +} - dbg("ch341_set_termios()"); +static int ch341_tiocmset(struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; - if (!tty || !tty->termios) + 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)", __FUNCTION__, 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", __FUNCTION__, + urb->status); return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, + urb->status); + goto exit; + } - baud_rate = tty_get_baud_rate(tty); + usb_serial_debug_data(debug, &port->dev, __FUNCTION__, + urb->actual_length, urb->transfer_buffer); - 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 (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", + __FUNCTION__, 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, + unsigned int cmd, unsigned long arg) +{ + dbg("%s (%d) cmd = 0x%04x", __FUNCTION__, port->number, cmd); + + switch (cmd) { + case TIOCMIWAIT: + dbg("%s (%d) TIOCMIWAIT", __FUNCTION__, port->number); + return wait_modem_info(port, arg); + + default: + dbg("%s not supported = 0x%04x", __FUNCTION__, cmd); + break; } - ch341_set_baudrate(port->serial->dev, priv); + return -ENOIOCTLCMD; +} + +static int ch341_tiocmget(struct usb_serial_port *port, struct file *file) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 mcr; + u8 status; + unsigned int result; + + dbg("%s (%d)", __FUNCTION__, 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", __FUNCTION__, result); - /* Unimplemented: - * (cflag & CSIZE) : data bits [5, 8] - * (cflag & PARENB) : parity {NONE, EVEN, ODD} - * (cflag & CSTOPB) : stop bits [1, 2] - */ + return result; } static struct usb_driver ch341_driver = { - .name = "ch341", - .probe = usb_serial_probe, - .disconnect = usb_serial_disconnect, - .id_table = id_table, - .no_dynamic_id = 1, + .name = "ch341", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .no_dynamic_id = 1, }; static struct usb_serial_driver ch341_device = { - .driver = { - .owner = THIS_MODULE, - .name = "ch341-uart", - }, - .id_table = id_table, - .usb_driver = &ch341_driver, - .num_interrupt_in = NUM_DONT_CARE, - .num_bulk_in = 1, - .num_bulk_out = 1, - .num_ports = 1, - .open = ch341_open, - .set_termios = ch341_set_termios, - .attach = ch341_attach, + .driver = { + .owner = THIS_MODULE, + .name = "ch341-uart", + }, + .id_table = id_table, + .usb_driver = &ch341_driver, + .num_interrupt_in = 1, //NUM_DONT_CARE, + .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, }; static int __init ch341_init(void) { - int retval; + int retval; - retval = usb_serial_register(&ch341_device); - if (retval) - return retval; - retval = usb_register(&ch341_driver); - if (retval) - usb_serial_deregister(&ch341_device); - return retval; + retval = usb_serial_register(&ch341_device); + if (retval) + return retval; + retval = usb_register(&ch341_driver); + if (retval) + usb_serial_deregister(&ch341_device); + return retval; } static void __exit ch341_exit(void) { - usb_deregister(&ch341_driver); - usb_serial_deregister(&ch341_device); + usb_deregister(&ch341_driver); + usb_serial_deregister(&ch341_device); } module_init(ch341_init); --- linux-2.6.23-rc6-mm1/Documentation/usb/usb-serial.txt 2007-09-21 09:56:55.000000000 +0200 +++ develop/Documentation/usb/usb-serial.txt 2007-09-21 10:03:18.000000000 +0200 @@ -437,6 +437,8 @@ 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 Am Donnerstag, 20. September 2007 02:03 schrieb Greg KH: > On Fri, Sep 14, 2007 at 02:38:55PM +0200, Werner Cornelius wrote: > > Hello, > > > > I know that there has been a patch for the Winchiphead CH340/41 USB to > > serial converter chips on the net, but they have been implemented with > > only a basic feature and limited baudrates due to the lack of any > > datasheets. > > > > I picked these patches up and added all common baudrates as well as the > > complete modem control and status handling to be able of using this > > adapter in nearly all applications. > > Care to just send me a patch against the -mm tree, for these new > features? The driver, with the proper attribution from the original > author, is already in that tree and will be going into the 2.6.24 > release. > > thanks, > > greg k-h - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/