From patchwork Tue Dec 20 09:13:12 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Hovold X-Patchwork-Id: 88564 Delivered-To: patch@linaro.org Received: by 10.140.20.101 with SMTP id 92csp1618740qgi; Tue, 20 Dec 2016 01:13:19 -0800 (PST) X-Received: by 10.84.191.131 with SMTP id a3mr40848445pld.62.1482225199170; Tue, 20 Dec 2016 01:13:19 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h63si21416599pgc.109.2016.12.20.01.13.18; Tue, 20 Dec 2016 01:13:19 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of stable-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com; spf=pass (google.com: best guess record for domain of stable-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=stable-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757623AbcLTJNR (ORCPT + 3 others); Tue, 20 Dec 2016 04:13:17 -0500 Received: from mail-lf0-f67.google.com ([209.85.215.67]:36056 "EHLO mail-lf0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755328AbcLTJNL (ORCPT ); Tue, 20 Dec 2016 04:13:11 -0500 Received: by mail-lf0-f67.google.com with SMTP id o20so9742042lfg.3; Tue, 20 Dec 2016 01:13:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=7tncN3h1jKSFwIq8pSZ3A7rQYqHXCKSY9M8bt/UaaSM=; b=qR/l07tR3o8vL9aU5z3KZR9xCMyoT79vyUyphw2KC4b8y9xbrVuEfeqaiSYf92yVSC eJa9T0F154D/ceZeElWJ4YgEMh7VIXM1mZ6H3rPfMEQjK6wvnijj4y1awLNOgCOHYRQb yZvwSf0rEg9f4I6w4NlCR5VEGhDGBWbkxVkMG1+9nlWn6fcCznf2NRzQTnv2/L4FGJzj xD04FXZ0q6ck59WXcQX6769kk81w1PLRBhXpyXIeCKfJUqRTalIdVha/Vlj9x2bPx9qG qs/M10soKUY2Wvcx7sf57OS5wSFnWsornxAc7DAGEOL8UPN3ux4vgyFuyFlMoKAxiof7 oe/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:from:to:cc:subject:message-id :references:mime-version:content-disposition:in-reply-to:user-agent; bh=7tncN3h1jKSFwIq8pSZ3A7rQYqHXCKSY9M8bt/UaaSM=; b=d8JUQsycse7X/KrQ/86ewGO60nml/2dXJqNQpM0Ux29McDqtxZTIBnRt3my2+yK/NU VhBUtDfyXW0kdndEGBwZ466r28Vsa6l/pYxIE4NRYCAS+NQ8nsFJGlsoYqmL2CF99CBw 66Gy5n3JsIXGaczdRCkHylDBa3hCxg8ba8QUpU3n8cG3H01KEBj75GkHmM9Z8Ug9PH/F VQzy1yT+jJxjOmLFjWFh3xGuwi0WqYhLDYNn+OfxsUnIyEjPLcEkl1fQkThxaHHLOCZZ 0JU9E99YfeejOPeYsv3+WQZ2VtH9l9A6MwWs8T9Pk/rAeFg3dwJqPTHLPOmNgLaQCfwu pTMw== X-Gm-Message-State: AIkVDXK5/b0GM8XJYL7pbep23/P4e1uEuJEpP/lqWu8GpZyWQjmIpVmoGT/WfOn0CfUdEw== X-Received: by 10.46.75.18 with SMTP id y18mr9829061lja.68.1482225188303; Tue, 20 Dec 2016 01:13:08 -0800 (PST) Received: from xi.terra (c-04aadb54.07-184-6d6c6d4.cust.bredbandsbolaget.se. [84.219.170.4]) by smtp.gmail.com with ESMTPSA id o196sm4525317lff.18.2016.12.20.01.13.07 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 20 Dec 2016 01:13:07 -0800 (PST) Received: from johan by xi.terra with local (Exim 4.87) (envelope-from ) id 1cJGTw-0003VD-Uu; Tue, 20 Dec 2016 10:13:13 +0100 Date: Tue, 20 Dec 2016 10:13:12 +0100 From: Johan Hovold To: Russell Senior Cc: Johan Hovold , Aidan Thornton , Linux USB Mailing List , Grigori Goronzy , Karl Palsson , Eddi De Pieri , stable Subject: Re: [PATCH 06/13] USB: serial: ch341: fix initial line settings Message-ID: <20161220091312.GB26724@localhost> References: <20161214152810.14682-7-johan@kernel.org> <20161216144614.GB21690@localhost> <20161216161350.GE21690@localhost> <20161216173001.GG21690@localhost> <20161219105801.GJ21690@localhost> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.5.24 (2015-08-30) Sender: stable-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: stable@vger.kernel.org On Mon, Dec 19, 2016 at 02:12:05PM -0800, Russell Senior wrote: > >> Apart from the two additional tests mentioned above, can you also > >> provide a log from when connecting the device using the following commit > >> that I just pushed to the ch341 branch: > >> > >> f341ee36198d ("dbg: ch341: add register dumps to probe") > >> > >> which provides dumps of the register settings during initialisation. > >> (Make sure ch341 dynamic debugging is not enabled to avoid cluttering > >> the log.) > > > > I'll send this in a followup, need to rebuild. > > 00018-gf341ee3: > > Dec 19 13:51:13 willard kernel: usbcore: registered new interface driver ch341 > Dec 19 13:51:13 willard kernel: usbserial: USB Serial support > registered for ch341-uart > Dec 19 13:51:23 willard kernel: usb 6-2: new full-speed USB device > number 10 using uhci_hcd > Dec 19 13:51:23 willard kernel: usb 6-2: New USB device found, > idVendor=1a86, idProduct=7523 > Dec 19 13:51:23 willard kernel: usb 6-2: New USB device strings: > Mfr=0, Product=2, SerialNumber=0 > Dec 19 13:51:23 willard kernel: usb 6-2: Product: USB2.0-Ser! > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: ch341-uart converter detected > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [00] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [01] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [02] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [03] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [04] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [05] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [06] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [07] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [08] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [09] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0a] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0b] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0c] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0d] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0e] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [0f] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [10] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [11] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [12] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [13] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [14] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [15] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [16] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [17] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [18] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [19] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1a] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1b] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1c] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1d] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1e] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [1f] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [20] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [21] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [22] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [23] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [24] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [25] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [26] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [27] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [28] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [29] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2a] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2b] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2c] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2d] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2e] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: [2f] = 00 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: init 0 0 > Dec 19 13:51:23 willard kernel: ch341 6-2:1.0: write 0x31 0xb282 Ok, so no direct register read works (as they do on CH340G and CH341A) and instead always return zero. Except, when LCR is read back together with register 0x25: > Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00, > [0x1213] = 00 00 (pre version) Note that the 0x31 LCR is read back as 0xf1, that is, with the tx and rx enable bits set. My CH341A behaves similarly and always has rx enabled. > Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00, > [0x1213] = 00 00 (post init-0) > Dec 19 13:51:23 willard kernel: usb 6-2: ch341_set_baudrate_lcr - > speed = 9600, lcr = c3, a = b202 > Dec 19 13:51:23 willard kernel: usb 6-2: ch341_dbg - [0x2518] = f1 00, > [0x1213] = 00 00 (post init - lcr reset) And the init command does not seem to have any effect on either LCR or the divisors as previous tests also show. I'm not sure what device we're dealing with here, but it seems it would not be supported by the vendor (whose version of this driver also uses the init-command). Perhaps you could give the attached vendor driver a quick spin just to confirm that? It's a rebased version against usb-next. I've also pushed a commit that tries to dump the registers differently (reading together with register 0x25): 3baa1eff4245 ("dbg: ch341: dump registers differently") Could you provide a log from when connecting the device with this commit as well? Thanks, Johan >From 6d2bca3cda09fca9c0a2a2c84dbf64c6090b3da5 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 21 Nov 2016 16:28:22 +0100 Subject: [PATCH] tmp: ch341: replace with vendor driver --- drivers/usb/serial/ch341.c | 1238 ++++++++++++++++++++++++++++---------------- 1 file changed, 803 insertions(+), 435 deletions(-) diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c index 2597b83a8ae2..b193b78c27bb 100644 --- a/drivers/usb/serial/ch341.c +++ b/drivers/usb/serial/ch341.c @@ -1,11 +1,15 @@ /* + * Winchiphead CH34x USB to serial adaptor driver + * * Copyright 2007, Frank A Kingswood * Copyright 2007, Werner Cornelius * Copyright 2009, Boris Hajduk + * Copyright 2016, WCH Tech Group * - * ch341.c implements a serial port driver for the Winchiphead CH341. + * ch34x.c implements a serial port driver for the Winchiphead CH340 and + * CH341. * - * The CH341 device can be used to implement an RS232 asynchronous + * The CH34x device can be used to implement an RS232 asynchronous * serial port, an IEEE-1284 parallel printer port or a memory-like * interface. In all cases the CH341 supports an I2C interface as well. * This driver only supports the asynchronous serial interface. @@ -16,134 +20,270 @@ */ #include +#include +#include +#include #include +#include +#include +#include #include -#include +#include +#include +#include #include #include -#include -#include - -#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 - -/* Break support - the information used to implement this was gleaned from - * the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato. - */ -#define CH341_REQ_READ_VERSION 0x5F -#define CH341_REQ_WRITE_REG 0x9A -#define CH341_REQ_READ_REG 0x95 -#define CH341_REQ_SERIAL_INIT 0xA1 -#define CH341_REQ_MODEM_CTRL 0xA4 - -#define CH341_REG_BREAK 0x05 -#define CH341_REG_LCR 0x18 -#define CH341_NBREAK_BITS 0x01 - -#define CH341_LCR_ENABLE_RX 0x80 -#define CH341_LCR_ENABLE_TX 0x40 -#define CH341_LCR_MARK_SPACE 0x20 -#define CH341_LCR_PAR_EVEN 0x10 -#define CH341_LCR_ENABLE_PAR 0x08 -#define CH341_LCR_STOP_BITS_2 0x04 -#define CH341_LCR_CS8 0x03 -#define CH341_LCR_CS7 0x02 -#define CH341_LCR_CS6 0x01 -#define CH341_LCR_CS5 0x00 - -static const struct usb_device_id id_table[] = { - { USB_DEVICE(0x4348, 0x5523) }, - { USB_DEVICE(0x1a86, 0x7523) }, - { USB_DEVICE(0x1a86, 0x5523) }, - { }, +#define DRIVER_DESC "WCH CH34x USB to serial adaptor driver" +#define DRIVER_AUTHOR "WCH Tech Group " + +#define CH34x_VENDOR_ID 0x1A86 +#define CH340_PRODUCT_ID 0x7523 +#define CH341_PRODUCT_ID 0x5523 + +#define CH34x_CLOSING_WAIT (30 * HZ) + +#define CH34x_BUF_SIZE 1024 +#define CH34x_TMP_BUF_SIZE 1024 + +#define VENDOR_WRITE_TYPE 0x40 +#define VENDOR_READ_TYPE 0xC0 + +#define VENDOR_READ 0x95 +#define VENDOR_WRITE 0x9A +#define VENDOR_SERIAL_INIT 0xA1 +#define VENDOR_MODEM_OUT 0xA4 +#define VENDOR_VERSION 0x5F + +/* for command 0xA4 */ +#define UART_CTS 0x01 +#define UART_DSR 0x02 +#define UART_RING 0x04 +#define UART_DCD 0x08 +#define CONTROL_OUT 0x10 +#define CONTROL_DTR 0x20 +#define CONTROL_RTS 0x40 + +/* uart state */ +#define UART_STATE 0x00 +#define UART_OVERRUN_ERROR 0x01 +#define UART_BREAK_ERROR +#define UART_PARITY_ERROR 0x02 +#define UART_FRAME_ERROR 0x06 +#define UART_RECV_ERROR 0x02 +#define UART_STATE_TRANSIENT_MASK 0x07 + +/* port state */ +#define PORTA_STATE 0x01 +#define PORTB_STATE 0x02 +#define PORTC_STATE 0x03 + +/* Baud Rate */ +#define CH34x_BAUDRATE_FACTOR 1532620800 +#define CH34x_BAUDRATE_DIVMAX 3 + +static DECLARE_WAIT_QUEUE_HEAD(wq); +static int wait_flag; + +struct ch34x_buf { + unsigned int buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; + +struct ch34x_private { + spinlock_t lock; /* access lock */ + struct ch34x_buf *buf; + int write_urb_in_use; + unsigned int baud_rate; + wait_queue_head_t delta_msr_wait; + u8 line_control; /* set line control value RTS/DTR */ + u8 line_status; /* active status of modem control inputs */ + u8 termios_initialized; }; -MODULE_DEVICE_TABLE(usb, id_table); -struct ch341_private { - spinlock_t lock; /* access lock */ - unsigned baud_rate; /* set baud rate */ - u8 line_control; /* set line control value RTS/DTR */ - u8 line_status; /* active status of modem control inputs */ +static struct usb_device_id id_table[] = { + { USB_DEVICE(CH34x_VENDOR_ID, CH340_PRODUCT_ID) }, + { USB_DEVICE(CH34x_VENDOR_ID, CH341_PRODUCT_ID) }, + { } }; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct ch34x_buf *ch34x_buf_alloc(unsigned int size) +{ + struct ch34x_buf *pb; + + if (size == 0) + return NULL; + + pb = kmalloc(sizeof(struct ch34x_buf), GFP_KERNEL); + if (pb == NULL) + return NULL; + + pb->buf_buf = kmalloc(size, GFP_KERNEL); + if (pb->buf_buf == NULL) { + kfree(pb); + return NULL; + } + + pb->buf_size = size; + pb->buf_get = pb->buf_put = pb->buf_buf; + + return pb; +} + +static void ch34x_buf_free(struct ch34x_buf *pb) +{ + if (pb) { + kfree(pb->buf_buf); + kfree(pb); + } +} + +static void ch34x_buf_clear(struct ch34x_buf *pb) +{ + if (pb != NULL) + pb->buf_get = pb->buf_put; +} + +static unsigned int ch34x_buf_data_avail(struct ch34x_buf *pb) +{ + if (pb == NULL) + return 0; + + return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size); +} + +static unsigned int ch34x_buf_space_avail(struct ch34x_buf *pb) +{ + if (pb == NULL) + return 0; + + return ((pb->buf_size + pb->buf_get - pb->buf_put - 1) + % pb->buf_size); +} + +static unsigned int ch34x_buf_put(struct ch34x_buf *pb, + const char *buf, unsigned int count) +{ + unsigned int len; + + if (pb == NULL) + return 0; + + len = ch34x_buf_space_avail(pb); + if (count > len) + count = len; + else if (count == 0) + return 0; + + len = pb->buf_buf + pb->buf_size - pb->buf_put; + if (count > len) { + memcpy(pb->buf_put, buf, len); + memcpy(pb->buf_buf, buf+len, count - len); + pb->buf_put = pb->buf_buf + count - len; + } else { + memcpy(pb->buf_put, buf, count); + if (count < len) + pb->buf_put += count; + else if (count == len) + pb->buf_put = pb->buf_buf; + } -static void ch341_set_termios(struct tty_struct *tty, - struct usb_serial_port *port, - struct ktermios *old_termios); + return count; +} -static int ch341_control_out(struct usb_device *dev, u8 request, - u16 value, u16 index) +static unsigned int ch34x_buf_get(struct ch34x_buf *pb, + char *buf, unsigned int count) { - int r; + unsigned int len; + + if (pb == NULL) + return 0; + + len = ch34x_buf_data_avail(pb); + if (count > len) + count = len; + else if (count == 0) + return 0; + + len = pb->buf_buf + pb->buf_size - pb->buf_get; + if (count > len) { + memcpy(buf, pb->buf_get, len); + memcpy(buf + len, pb->buf_buf, count - len); + pb->buf_get = pb->buf_buf + count - len; + } else { + memcpy(buf, pb->buf_get, count); + if (count < len) + pb->buf_get += count; + else if (count == len) + pb->buf_get = pb->buf_buf; + } + + return count; +} - dev_dbg(&dev->dev, "ch341_control_out(%02x,%02x,%04x,%04x)\n", - USB_DIR_OUT|0x40, (int)request, (int)value, (int)index); +static int ch34x_vendor_read(__u8 request, + __u16 value, + __u16 index, + struct usb_serial *serial, + unsigned char *buf, + __u16 len) +{ + int retval; - 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); + retval = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + request, + VENDOR_READ_TYPE, + value, index, buf, len, 1000); - return r; + return retval; } -static int ch341_control_in(struct usb_device *dev, - u8 request, u16 value, u16 index, - char *buf, unsigned bufsize) +static int ch34x_vendor_write(__u8 request, + __u16 value, + __u16 index, + struct usb_serial *serial, + unsigned char *buf, + __u16 len) { - int r; + int retval; - dev_dbg(&dev->dev, "ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)\n", - USB_DIR_IN|0x40, (int)request, (int)value, (int)index, buf, - (int)bufsize); + retval = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, + VENDOR_WRITE_TYPE, + value, index, buf, len, 1000); - 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; + return retval; } -static int ch341_init_set_baudrate(struct usb_device *dev, - struct ch341_private *priv, unsigned ctrl) +static int set_control_lines(struct usb_serial_port *port, + struct usb_serial *serial, u8 value) { - short a; - int r; - unsigned long factor; - short divisor; + int retval; + + retval = ch34x_vendor_write(VENDOR_MODEM_OUT, (unsigned short)value, + 0x0000, serial, NULL, 0x00); + dev_dbg(&port->dev, "%s - value=%d, retval=%d", + __func__, value, retval); + + return retval; +} - if (!priv->baud_rate) +static int ch34x_get_baud_rate(unsigned int baud_rate, + unsigned char *a, unsigned char *b) +{ + unsigned long factor = 0; + short divisor = 0; + + if (!baud_rate) return -EINVAL; - factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate); - divisor = CH341_BAUDBASE_DIVMAX; + + factor = CH34x_BAUDRATE_FACTOR / baud_rate; + divisor = CH34x_BAUDRATE_DIVMAX; while ((factor > 0xfff0) && divisor) { factor >>= 3; @@ -154,464 +294,692 @@ static int ch341_init_set_baudrate(struct usb_device *dev, return -EINVAL; factor = 0x10000 - factor; - a = (factor & 0xff00) | divisor; - - /* 0x9c is "enable SFR_UART Control register and timer" */ - r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, - 0x9c | (ctrl << 8), a | 0x80); + *a = (factor & 0xff00) >> 8; + *b = divisor; - return r; + return 0; } -static int ch341_set_handshake(struct usb_device *dev, u8 control) +static void ch34x_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) { - return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0); -} + struct usb_serial *serial = port->serial; + struct ch34x_private *priv = usb_get_serial_port_data(port); + struct ktermios *termios = &tty->termios; -static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv) -{ - char *buffer; - int r; - const unsigned size = 8; + unsigned int baud_rate; + unsigned int cflag; unsigned long flags; + u8 control; - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; + unsigned char divisor = 0; + unsigned char reg_count = 0; + unsigned char factor = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; - r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size); - if (r < 0) - goto out; + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); - /* 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; - spin_unlock_irqrestore(&priv->lock, flags); - } else - r = -EPROTO; + spin_lock_irqsave(&priv->lock, flags); + if (!priv->termios_initialized) { + *termios = tty_std_termios; + termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + termios->c_ispeed = 9600; + termios->c_ospeed = 9600; + priv->termios_initialized = 1; + } + spin_unlock_irqrestore(&priv->lock, flags); -out: kfree(buffer); - return r; -} + /* + * The ch34x is reported to lose bytes if you change serial setting + * even to the same vaules as before. Thus we actually need to filter + * in this specific case. + */ + if (!tty_termios_hw_change(termios, old_termios)) + return; -/* -------------------------------------------------------------------------- */ + cflag = termios->c_cflag; -static int ch341_configure(struct usb_device *dev, struct ch341_private *priv) -{ - char *buffer; - int r; - const unsigned size = 8; - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - /* expect two bytes 0x27 0x00 */ - r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size); - if (r < 0) - goto out; - dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]); - - r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0); - if (r < 0) - goto out; - - /* expect two bytes 0x56 0x00 */ - r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x2518, 0, buffer, size); - if (r < 0) - goto out; - - r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, 0x0050); - if (r < 0) - goto out; - - /* expect 0xff 0xee */ - r = ch341_get_status(dev, priv); - if (r < 0) - goto out; - - r = ch341_init_set_baudrate(dev, priv, 0); - 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, priv); - -out: kfree(buffer); - return r; -} + dev_dbg(&port->dev, "%s(%d) cflag = 0x%x\n", + __func__, port->port_number, cflag); -static int ch341_port_probe(struct usb_serial_port *port) -{ - struct ch341_private *priv; - int r; + /* Get the byte size */ + switch (cflag & CSIZE) { + case CS5: + reg_value |= 0x00; + break; + case CS6: + reg_value |= 0x01; + break; + case CS7: + reg_value |= 0x02; + break; + case CS8: + reg_value |= 0x03; + break; + default: + reg_value |= 0x03; + break; + } + dev_dbg(&port->dev, "%s - data bits = %d", __func__, reg_value + 0x05); - priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL); - if (!priv) - return -ENOMEM; + if (cflag & CSTOPB) { + reg_value |= 0x04; + dev_dbg(&port->dev, "%s - stop bits = 2", __func__); + } else + dev_dbg(&port->dev, "%s - stop bits = 1", __func__); + + if (cflag & PARENB) { + if (cflag & PARODD) { + reg_value |= 0x08 | 0x00; + dev_dbg(&port->dev, "%s - parity = odd", __func__); + } else { + reg_value |= 0x08 | 0x10; + dev_dbg(&port->dev, "%s - parity = even", __func__); + } + } else + dev_dbg(&port->dev, "%s - parity = none", __func__); - spin_lock_init(&priv->lock); - priv->baud_rate = DEFAULT_BAUD_RATE; - priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR; + baud_rate = tty_get_baud_rate(tty); + dev_dbg(&port->dev, "%s = baud_rate = %d", __func__, baud_rate); + ch34x_get_baud_rate(baud_rate, &factor, &divisor); + dev_dbg(&port->dev, "----->>>> baud_rate = %d, factor:0x%x, divisor:0x%x", + baud_rate, factor, divisor); + + /* enable SFR_UART RX and TX */ + reg_value |= 0xc0; + /* enable SFR_UART Control register and timer */ + reg_count |= 0x9c; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + index |= 0x80 | divisor; + index |= (unsigned short)factor << 8; + ch34x_vendor_write(VENDOR_SERIAL_INIT, value, index, serial, NULL, 0); + + /* change control lines if we are switching to or from B0 */ + spin_lock_irqsave(&priv->lock, flags); + control = priv->line_control; + if ((cflag & CBAUD) == B0) + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + else + priv->line_control |= (CONTROL_DTR | CONTROL_RTS); - r = ch341_configure(port->serial->dev, priv); - if (r < 0) - goto error; + if (control != priv->line_control) { + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + set_control_lines(port, serial, control); + } else + spin_unlock_irqrestore(&priv->lock, flags); - usb_set_serial_port_data(port, priv); - return 0; + if (cflag & CRTSCTS) + ch34x_vendor_write(VENDOR_WRITE, 0x2727, 0x0101, + serial, NULL, 0); -error: kfree(priv); - return r; + /* FIXME: Need to read back resulting baud rate */ + if (baud_rate) + tty_encode_baud_rate(tty, baud_rate, baud_rate); } -static int ch341_port_remove(struct usb_serial_port *port) +static int ch34x_tiocmget(struct tty_struct *tty) { - struct ch341_private *priv; + struct usb_serial_port *port = tty->driver_data; + struct ch34x_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int mcr; + unsigned int retval; - priv = usb_get_serial_port_data(port); - kfree(priv); + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); + if (!usb_get_intfdata(port->serial->interface)) + return -ENODEV; - return 0; -} + spin_lock_irqsave(&priv->lock, flags); + mcr = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); -static int ch341_carrier_raised(struct usb_serial_port *port) -{ - struct ch341_private *priv = usb_get_serial_port_data(port); - if (priv->line_status & CH341_BIT_DCD) - return 1; - return 0; + retval = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) | + ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) | + ((mcr & UART_CTS) ? TIOCM_CTS : 0) | + ((mcr & UART_DSR) ? TIOCM_DSR : 0) | + ((mcr & UART_RING) ? TIOCM_RI : 0) | + ((mcr & UART_DCD) ? TIOCM_CD : 0); + + dev_dbg(&port->dev, "%s-retval = 0x%x", __func__, retval); + + return retval; } -static void ch341_dtr_rts(struct usb_serial_port *port, int on) +static void ch34x_close(struct usb_serial_port *port) { - struct ch341_private *priv = usb_get_serial_port_data(port); + struct tty_struct *tty = port->port.tty; + struct ch34x_private *priv = usb_get_serial_port_data(port); unsigned long flags; + unsigned int c_cflag; - /* drop DTR and RTS */ + /* clear out any remaining data in the buffer */ spin_lock_irqsave(&priv->lock, flags); - if (on) - priv->line_control |= CH341_BIT_RTS | CH341_BIT_DTR; - else - priv->line_control &= ~(CH341_BIT_RTS | CH341_BIT_DTR); + ch34x_buf_clear(priv->buf); spin_unlock_irqrestore(&priv->lock, flags); - ch341_set_handshake(port->serial->dev, priv->line_control); -} -static void ch341_close(struct usb_serial_port *port) -{ - usb_serial_generic_close(port); + /* kill our urbs */ usb_kill_urb(port->interrupt_in_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->write_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); + set_control_lines(port, port->serial, 0); + } + } } - -/* open this device, set default parameters */ -static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port) +static int ch34x_open(struct tty_struct *tty, + struct usb_serial_port *port) { + struct ktermios tmp_termios; struct usb_serial *serial = port->serial; - struct ch341_private *priv = usb_get_serial_port_data(port); - int r; + int retval; + + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); - r = ch341_configure(serial->dev, priv); - if (r) - goto out; + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); if (tty) - ch341_set_termios(tty, port, NULL); - - dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__); - r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); - if (r) { - dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n", - __func__, r); - goto out; + ch34x_set_termios(tty, port, &tmp_termios); + + dev_dbg(&port->dev, "%s - submit read urb", __func__); + port->read_urb->dev = serial->dev; + retval = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, "%s - failed submit read urb,error %d\n", + __func__, retval); + ch34x_close(port); + goto err_out; } - r = usb_serial_generic_open(tty, port); + dev_dbg(&port->dev, "%s - submit interrupt urb", __func__); + port->interrupt_in_urb->dev = serial->dev; + retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, "%s - failed submit interrupt urb,error %d\n", + __func__, retval); + ch34x_close(port); + goto err_out; + } -out: return r; +err_out: + return retval; } -/* Old_termios contains the original termios settings and - * tty->termios contains the new setting to be used. - */ -static void ch341_set_termios(struct tty_struct *tty, - struct usb_serial_port *port, struct ktermios *old_termios) +static int ch34x_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) { - struct ch341_private *priv = usb_get_serial_port_data(port); - unsigned baud_rate; + struct usb_serial_port *port = tty->driver_data; + struct ch34x_private *priv = usb_get_serial_port_data(port); unsigned long flags; - unsigned char ctrl; - int r; + u8 control; - /* redundant changes may cause the chip to lose bytes */ - if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) - return; + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); - baud_rate = tty_get_baud_rate(tty); + if (!usb_get_intfdata(port->serial->interface)) + return -ENODEV; - priv->baud_rate = baud_rate; - ctrl = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX; + spin_lock_irqsave(&priv->lock, flags); + if (set & TIOCM_RTS) + priv->line_control |= CONTROL_RTS; + if (set & TIOCM_DTR) + priv->line_control |= CONTROL_DTR; + if (clear & TIOCM_RTS) + priv->line_control &= ~CONTROL_RTS; + if (clear & TIOCM_DTR) + priv->line_control &= ~CONTROL_DTR; + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); - switch (C_CSIZE(tty)) { - case CS5: - ctrl |= CH341_LCR_CS5; - break; - case CS6: - ctrl |= CH341_LCR_CS6; - break; - case CS7: - ctrl |= CH341_LCR_CS7; - break; - case CS8: - ctrl |= CH341_LCR_CS8; - break; - } + return set_control_lines(port, port->serial, control); +} - if (C_PARENB(tty)) { - ctrl |= CH341_LCR_ENABLE_PAR; - if (C_PARODD(tty) == 0) - ctrl |= CH341_LCR_PAR_EVEN; - if (C_CMSPAR(tty)) - ctrl |= CH341_LCR_MARK_SPACE; - } +static int wait_modem_info(struct usb_serial_port *port, + unsigned int arg) +{ + struct ch34x_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int prevstatus; + unsigned int status; + unsigned int changed; - if (C_CSTOPB(tty)) - ctrl |= CH341_LCR_STOP_BITS_2; + dev_dbg(&port->dev, "%s -port:%d", __func__, port->port_number); + + spin_lock_irqsave(&priv->lock, flags); + prevstatus = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + while (1) { + wait_event_interruptible(wq, wait_flag != 0); + wait_flag = 0; + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; - if (baud_rate) { - spin_lock_irqsave(&priv->lock, flags); - priv->line_control |= (CH341_BIT_DTR | CH341_BIT_RTS); - spin_unlock_irqrestore(&priv->lock, flags); - r = ch341_init_set_baudrate(port->serial->dev, priv, ctrl); - if (r < 0 && old_termios) { - priv->baud_rate = tty_termios_baud_rate(old_termios); - tty_termios_copy_hw(&tty->termios, old_termios); - } - } else { spin_lock_irqsave(&priv->lock, flags); - priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS); + status = priv->line_status; spin_unlock_irqrestore(&priv->lock, flags); - } - ch341_set_handshake(port->serial->dev, priv->line_control); + changed = prevstatus ^ status; + + if (((arg & TIOCM_RNG) && (changed & UART_RING)) || + ((arg & TIOCM_DSR) && (changed & UART_DSR)) || + ((arg & TIOCM_CD) && (changed & UART_DCD)) || + ((arg & TIOCM_CTS) && (changed & UART_CTS))) + return 0; + prevstatus = status; + } } -static void ch341_break_ctl(struct tty_struct *tty, int break_state) +static int ch34x_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) { - const uint16_t ch341_break_reg = - ((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK; struct usb_serial_port *port = tty->driver_data; - int r; - uint16_t reg_contents; - uint8_t *break_reg; - break_reg = kmalloc(2, GFP_KERNEL); - if (!break_reg) + dev_dbg(&port->dev, "%s - port:%d, cmd=0x%04x", + __func__, port->port_number, cmd); + + switch (cmd) { + case TIOCMIWAIT: + dev_dbg(&port->dev, "%s - port:%d TIOCMIWAIT", + __func__, port->port_number); + return wait_modem_info(port, arg); + default: + dev_dbg(&port->dev, "%s not supported=0x%04x", __func__, cmd); + break; + } + return -ENOIOCTLCMD; +} + +static void ch34x_send(struct usb_serial_port *port) +{ + int count; + int retval; + struct ch34x_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); + + spin_lock_irqsave(&priv->lock, flags); + if (priv->write_urb_in_use) { + spin_unlock_irqrestore(&priv->lock, flags); return; + } - r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG, - ch341_break_reg, 0, break_reg, 2); - if (r < 0) { - dev_err(&port->dev, "%s - USB control read error (%d)\n", - __func__, r); - goto out; + count = ch34x_buf_get(priv->buf, port->write_urb->transfer_buffer, + port->bulk_out_size); + if (count == 0) { + spin_unlock_irqrestore(&priv->lock, flags); + return; } - dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n", - __func__, break_reg[0], break_reg[1]); - if (break_state != 0) { - dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__); - break_reg[0] &= ~CH341_NBREAK_BITS; - break_reg[1] &= ~CH341_LCR_ENABLE_TX; - } else { - dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__); - break_reg[0] |= CH341_NBREAK_BITS; - break_reg[1] |= CH341_LCR_ENABLE_TX; + + priv->write_urb_in_use = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + usb_serial_debug_data(&port->dev, __func__, count, + port->write_urb->transfer_buffer); + + port->write_urb->transfer_buffer_length = count; + port->write_urb->dev = port->serial->dev; + retval = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (retval) { + dev_err(&port->dev, "%s - failed submitting write urb,error %d\n" + , __func__, retval); + priv->write_urb_in_use = 0; } - dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n", - __func__, break_reg[0], break_reg[1]); - reg_contents = get_unaligned_le16(break_reg); - r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, - ch341_break_reg, reg_contents); - if (r < 0) - dev_err(&port->dev, "%s - USB control write error (%d)\n", - __func__, r); -out: - kfree(break_reg); + + usb_serial_port_softint(port); } -static int ch341_tiocmset(struct tty_struct *tty, - unsigned int set, unsigned int clear) +static int ch34x_write(struct tty_struct *tty, + struct usb_serial_port *port, + const unsigned char *buf, + int count) { - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); + struct ch34x_private *priv = usb_get_serial_port_data(port); unsigned long flags; - u8 control; + + dev_dbg(&port->dev, "%s - port:%d, %d bytes", + __func__, port->port_number, count); + + if (!count) + return count; 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; + count = ch34x_buf_put(priv->buf, buf, count); spin_unlock_irqrestore(&priv->lock, flags); - return ch341_set_handshake(port->serial->dev, control); + ch34x_send(port); + + return count; } -static void ch341_update_line_status(struct usb_serial_port *port, - unsigned char *data, size_t len) +static int ch34x_write_room(struct tty_struct *tty) { - struct ch341_private *priv = usb_get_serial_port_data(port); - struct tty_struct *tty; + struct usb_serial_port *port = tty->driver_data; + struct ch34x_private *priv = usb_get_serial_port_data(port); + int room = 0; unsigned long flags; - u8 status; - u8 delta; - if (len < 4) - return; + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); - status = ~data[2] & CH341_BITS_MODEM_STAT; + spin_lock_irqsave(&priv->lock, flags); + room = ch34x_buf_space_avail(priv->buf); + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s - room:%d", __func__, room); + return room; +} + +static int ch34x_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ch34x_private *priv = usb_get_serial_port_data(port); + int chars = 0; + unsigned long flags; + + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); spin_lock_irqsave(&priv->lock, flags); - delta = status ^ priv->line_status; - priv->line_status = status; + chars = ch34x_buf_data_avail(priv->buf); spin_unlock_irqrestore(&priv->lock, flags); - if (data[1] & CH341_MULT_STAT) - dev_dbg(&port->dev, "%s - multiple status change\n", __func__); + dev_dbg(&port->dev, "%s - chars:%d", __func__, chars); - if (!delta) - return; + return chars; +} - if (delta & CH341_BIT_CTS) - port->icount.cts++; - if (delta & CH341_BIT_DSR) - port->icount.dsr++; - if (delta & CH341_BIT_RI) - port->icount.rng++; - if (delta & CH341_BIT_DCD) { - port->icount.dcd++; - tty = tty_port_tty_get(&port->port); - if (tty) { - usb_serial_handle_dcd_change(port, tty, - status & CH341_BIT_DCD); - tty_kref_put(tty); +static int ch34x_attach(struct usb_serial *serial) +{ + struct ch34x_private *priv; + int i; + char buf[8]; + + dev_dbg(&serial->interface->dev, "%s", __func__); + + for (i = 0; i < serial->num_ports; ++i) { + priv = kzalloc(sizeof(struct ch34x_private), GFP_KERNEL); + if (!priv) + goto cleanup; + spin_lock_init(&priv->lock); + priv->buf = ch34x_buf_alloc(CH34x_BUF_SIZE); + if (priv->buf == NULL) { + kfree(priv); + goto cleanup; } + init_waitqueue_head(&priv->delta_msr_wait); + usb_set_serial_port_data(serial->port[i], priv); + } + + ch34x_vendor_read(VENDOR_VERSION, 0x0000, 0x0000, + serial, buf, 0x02); + ch34x_vendor_write(VENDOR_SERIAL_INIT, 0x0000, 0x0000, + serial, NULL, 0x00); + ch34x_vendor_write(VENDOR_WRITE, 0x1312, 0xD982, + serial, NULL, 0x00); + ch34x_vendor_write(VENDOR_WRITE, 0x0F2C, 0x0004, + serial, NULL, 0x00); + ch34x_vendor_read(VENDOR_READ, 0x2518, 0x0000, + serial, buf, 0x02); + ch34x_vendor_write(VENDOR_WRITE, 0x2727, 0x0000, + serial, NULL, 0x00); + ch34x_vendor_write(VENDOR_MODEM_OUT, 0x009F, 0x0000, + serial, NULL, 0x00); + + return 0; + +cleanup: + for (--i; i >= 0; --i) { + priv = usb_get_serial_port_data(serial->port[i]); + ch34x_buf_free(priv->buf); + kfree(priv); + usb_set_serial_port_data(serial->port[i], NULL); } - wake_up_interruptible(&port->port.delta_msr_wait); + return -ENOMEM; } -static void ch341_read_int_callback(struct urb *urb) +static void ch34x_update_line_status(struct usb_serial_port *port, + unsigned char *data, unsigned int actual_length) { - struct usb_serial_port *port = urb->context; + struct ch34x_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 length = UART_STATE + 0x04; + + if (actual_length < length) + return; + + /* Save off the uart status for others to look up */ + spin_lock_irqsave(&priv->lock, flags); + priv->line_status = data[UART_STATE]; + priv->line_control = data[PORTB_STATE]; + spin_unlock_irqrestore(&priv->lock, flags); + wait_flag = 1; + wake_up_interruptible(&priv->delta_msr_wait); +} + +static void ch34x_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 len = urb->actual_length; - int status; + unsigned int actual_length = urb->actual_length; + int status = urb->status; + int retval; + + dev_dbg(&port->dev, "%s port:%d", __func__, port->port_number); - switch (urb->status) { + switch (status) { + /* success */ case 0: - /* success */ break; case -ECONNRESET: case -ENOENT: + /* this urb is terminated, clean up */ case -ESHUTDOWN: - /* this urb is terminated, clean up */ - dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n", - __func__, urb->status); + dev_dbg(&port->dev, "%s - urb shutting down with status:%d", + __func__, status); return; default: - dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", - __func__, urb->status); + dev_dbg(&port->dev, "%s - nonzero urb status received:%d", + __func__, status); goto exit; } - usb_serial_debug_data(&port->dev, __func__, len, data); - ch341_update_line_status(port, data, len); + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, urb->transfer_buffer); + ch34x_update_line_status(port, data, actual_length); + exit: - status = usb_submit_urb(urb, GFP_ATOMIC); - if (status) { - dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n", - __func__, status); - } + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); } -static int ch341_tiocmget(struct tty_struct *tty) +static void ch34x_read_bulk_callback(struct urb *urb) { - struct usb_serial_port *port = tty->driver_data; - struct ch341_private *priv = usb_get_serial_port_data(port); + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct ch34x_private *priv = usb_get_serial_port_data(port); + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; unsigned long flags; - u8 mcr; - u8 status; - unsigned int result; + int i; + int retval; + int status = urb->status; + u8 line_status; + char tty_flag; + + dev_dbg(&urb->dev->dev, "%s - port:%d", __func__, port->port_number); + + if (status) { + dev_dbg(&urb->dev->dev, "%s - urb status=%d", __func__, status); + if (status == -EPROTO) { + /* + * CH34x mysteriously fails with -EPROTO + * reschedule the read + */ + dev_err(&urb->dev->dev, "%s - caught -EPROTO, \ + resubmitting the urb", __func__); + urb->dev = port->serial->dev; + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) { + dev_err(&urb->dev->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, retval); + return; + } + } + + dev_dbg(&urb->dev->dev, "%s - unable to \ + handle the error", __func__); + return; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + /* get tty_flag from status */ + tty_flag = TTY_NORMAL; spin_lock_irqsave(&priv->lock, flags); - mcr = priv->line_control; - status = priv->line_status; + line_status = priv->line_status; + priv->line_status &= ~UART_STATE_TRANSIENT_MASK; spin_unlock_irqrestore(&priv->lock, flags); + wait_flag = 1; + wake_up_interruptible(&priv->delta_msr_wait); + + /* + * break takes precedence over parity, + * which takes precedence over framing errors. + */ + if (line_status & UART_PARITY_ERROR) + tty_flag = TTY_PARITY; + else if (line_status & UART_OVERRUN_ERROR) + tty_flag = TTY_OVERRUN; + else if (line_status & UART_FRAME_ERROR) + tty_flag = TTY_FRAME; + dev_dbg(&urb->dev->dev, "%s - tty_flag=%d", __func__, tty_flag); + + tty = port->port.tty; + + if (tty && urb->actual_length) { + tty_buffer_request_room(tty->port, urb->actual_length + 1); + + /* overrun is special, not associated with a char */ + if (line_status & UART_OVERRUN_ERROR) + tty_insert_flip_char(tty->port, 0, TTY_OVERRUN); + + for (i = 0; i < urb->actual_length; ++i) + tty_insert_flip_char(tty->port, data[i], tty_flag); + + tty_flip_buffer_push(tty->port); + } - 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); - - dev_dbg(&port->dev, "%s - result = %x\n", __func__, result); + /* schedule the next read _if_ we are still open */ - return result; + urb->dev = port->serial->dev; + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "%s - fialed resubmitting read urb, error %d\n", + __func__, retval); } -static int ch341_reset_resume(struct usb_serial *serial) +static void ch34x_write_bulk_callback(struct urb *urb) { - struct ch341_private *priv; + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct ch34x_private *priv = usb_get_serial_port_data(port); + int retval; + int status = urb->status; - priv = usb_get_serial_port_data(serial->port[0]); + dev_dbg(&port->dev, "%s - port:%d", __func__, port->port_number); - /* reconfigure ch341 serial port after bus-reset */ - ch341_configure(serial->dev, priv); + switch (status) { + /* success */ + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting \ + down with status:%d", __func__, status); + priv->write_urb_in_use = 0; + return; + default: + /* error in the urb, so we have to resubmit it */ + dev_dbg(&port->dev, "%s - Overflow in write", __func__); + dev_dbg(&port->dev, "%s - nonzero write bulk \ + status received:%d", __func__, status); + port->write_urb->transfer_buffer_length = 1; + port->write_urb->dev = port->serial->dev; + retval = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "%s - failed resubmitting write urv, error:%d\n", + __func__, retval); + else + return; + } + priv->write_urb_in_use = 0; - return 0; + /* send any buffered data */ + ch34x_send(port); } -static struct usb_serial_driver ch341_device = { +static struct usb_serial_driver ch34x_device = { .driver = { .owner = THIS_MODULE, - .name = "ch341-uart", + .name = "ch34x", }, - .id_table = id_table, - .num_ports = 1, - .open = ch341_open, - .dtr_rts = ch341_dtr_rts, - .carrier_raised = ch341_carrier_raised, - .close = ch341_close, - .set_termios = ch341_set_termios, - .break_ctl = ch341_break_ctl, - .tiocmget = ch341_tiocmget, - .tiocmset = ch341_tiocmset, - .tiocmiwait = usb_serial_generic_tiocmiwait, - .read_int_callback = ch341_read_int_callback, - .port_probe = ch341_port_probe, - .port_remove = ch341_port_remove, - .reset_resume = ch341_reset_resume, + .id_table = id_table, + .num_ports = 1, + .open = ch34x_open, + .close = ch34x_close, + .write = ch34x_write, + .ioctl = ch34x_ioctl, + .set_termios = ch34x_set_termios, + .tiocmget = ch34x_tiocmget, + .tiocmset = ch34x_tiocmset, + .read_bulk_callback = ch34x_read_bulk_callback, + .read_int_callback = ch34x_read_int_callback, + .write_bulk_callback = ch34x_write_bulk_callback, + .write_room = ch34x_write_room, + .chars_in_buffer = ch34x_chars_in_buffer, + .attach = ch34x_attach, }; -static struct usb_serial_driver * const serial_drivers[] = { - &ch341_device, NULL +static struct usb_serial_driver *const serial_driver[] = { + &ch34x_device, NULL }; -module_usb_serial_driver(serial_drivers, id_table); +static int __init ch34x_init(void) +{ + return usb_serial_register_drivers(serial_driver, + KBUILD_MODNAME, id_table); +} +static void __exit ch34x_exit(void) +{ + usb_serial_deregister_drivers(serial_driver); +} + +module_init(ch34x_init); +module_exit(ch34x_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_LICENSE("GPL"); + -- 2.10.2