diff mbox

[06/13] USB: serial: ch341: fix initial line settings

Message ID 20161220091312.GB26724@localhost
State New
Headers show

Commit Message

Johan Hovold Dec. 20, 2016, 9:13 a.m. UTC
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

Comments

Russell Senior Dec. 20, 2016, 3:31 p.m. UTC | #1
On Tue, Dec 20, 2016 at 1:13 AM, Johan Hovold <johan@kernel.org> wrote:
> Perhaps you could give the attached vendor driver a quick spin just to

> confirm that? It's a rebased version against usb-next.


This is plain jane usb-next with your vendor patch, which I applied in
a local branch:

 00001-gfadd29d:

Dec 20 07:18:36 willard kernel: PKCS#7 signature not signed with a trusted key
Dec 20 07:18:36 willard kernel: ch341: module verification failed:
signature and/or required key missing - tainting kernel
Dec 20 07:18:36 willard kernel: usbcore: registered new interface driver ch341
Dec 20 07:18:36 willard kernel: usbserial: USB Serial support
registered for ch34x
Dec 20 07:18:44 willard kernel: usb 6-2: new full-speed USB device
number 12 using uhci_hcd
Dec 20 07:18:44 willard kernel: usb 6-2: New USB device found,
idVendor=1a86, idProduct=7523
Dec 20 07:18:44 willard kernel: usb 6-2: New USB device strings:
Mfr=0, Product=2, SerialNumber=0
Dec 20 07:18:44 willard kernel: usb 6-2: Product: USB2.0-Ser!
Dec 20 07:18:44 willard kernel: ch341 6-2:1.0: ch34x converter detected
Dec 20 07:18:44 willard kernel: ------------[ cut here ]------------
Dec 20 07:18:44 willard kernel: WARNING: CPU: 1 PID: 30168 at
/home/russell/src/usb-serial/drivers/usb/core/hcd.c:1584
usb_hcd_map_urb_for_dma+0x382/0x560
Dec 20 07:18:44 willard kernel: transfer buffer not dma capable
Dec 20 07:18:44 willard kernel: Modules linked in: ch341(E) ntfs msdos
pl2303 usbserial bnep arc4 iwldvm mac80211 btusb btrtl btbcm btintel
bluetooth snd_hda_codec_hdmi uvcvideo snd_hda_codec_conexant
snd_hda_codec_generic videobuf2_vmalloc videobuf2_memops
videobuf2_v4l2 videobuf2_core videodev iwlwifi snd_hda_inte
l coretemp media kvm_intel snd_hda_codec snd_hda_core kvm
thinkpad_acpi snd_hwdep mei_me mei nvram cfg80211 snd_pcm snd_seq_midi
snd_seq_midi_event snd_rawmidi snd_seq snd_seq_device
Dec 20 07:18:44 willard kernel:  snd_timer irqbypass snd joydev input_leds
Dec 20 07:18:44 willard kernel:  serio_raw
Dec 20 07:18:44 willard kernel:  lpc_ich soundcore autofs4 amdkfd
amd_iommu_v2 radeon i915 i2c_algo_bit ttm e1000e drm_kms_helper
psmouse syscopyarea firewire_ohci sysfillrect sysimgblt fb_sys_fops
ahci libahci firewire_core drm crc_itu_t ptp wmi video pps_core [last
unloaded: ch341]
Dec 20 07:18:44 willard kernel: CPU: 1 PID: 30168 Comm: kworker/1:0
Tainted: G            E   4.9.0-rc2-2016-12-16-00013-gc510871 #1
Dec 20 07:18:44 willard kernel: Hardware name: LENOVO 406227U/406227U,
BIOS 7VET66WW (2.16 ) 04/22/2009
Dec 20 07:18:44 willard kernel: Workqueue: usb_hub_wq hub_event
Dec 20 07:18:44 willard kernel:  ffffa51f89e4f3a8 ffffffffae3e8ec3
ffffa51f89e4f3f8 0000000000000000
Dec 20 07:18:44 willard kernel:  ffffa51f89e4f3e8 ffffffffae07f90b
0000063033791ae0 ffff8ae139fe23c0
Dec 20 07:18:44 willard kernel:  0000000000000000 0000000002400000
0000000000000002 ffff8ae11a80a800
Dec 20 07:18:44 willard kernel: Call Trace:
Dec 20 07:18:44 willard kernel:  [<ffffffffae3e8ec3>] dump_stack+0x63/0x90
Dec 20 07:18:44 willard kernel:  [<ffffffffae07f90b>] __warn+0xcb/0xf0
Dec 20 07:18:44 willard kernel:  [<ffffffffae07f98f>]
warn_slowpath_fmt+0x5f/0x80
Dec 20 07:18:44 willard kernel:  [<ffffffffae60f392>]
usb_hcd_map_urb_for_dma+0x382/0x560
Dec 20 07:18:44 willard kernel:  [<ffffffffae6106e2>]
usb_hcd_submit_urb+0x1d2/0xac0
Dec 20 07:18:44 willard kernel:  [<ffffffffae0da37a>] ? vprintk_emit+0x2ea/0x4d0
Dec 20 07:18:44 willard kernel:  [<ffffffffae61213b>] usb_submit_urb+0x2eb/0x570
Dec 20 07:18:44 willard kernel:  [<ffffffffae612abe>]
usb_start_wait_urb+0x6e/0x170
Dec 20 07:18:44 willard kernel:  [<ffffffffae612c9c>] usb_control_msg+0xdc/0x130
Dec 20 07:18:44 willard kernel:  [<ffffffffc0877fd7>]
ch34x_attach+0x1d7/0x2b0 [ch341]
Dec 20 07:18:44 willard kernel:  [<ffffffffc0930410>]
usb_serial_probe+0xf10/0x1170 [usbserial]
Dec 20 07:18:44 willard kernel:  [<ffffffffae3eac48>] ?
ida_simple_get+0x98/0x100
Dec 20 07:18:44 willard kernel:  [<ffffffffae2adffa>] ?
kernfs_activate+0x7a/0xe0
Dec 20 07:18:44 willard kernel:  [<ffffffffae55d580>] ?
__pm_runtime_set_status+0x1e0/0x2a0
Dec 20 07:18:44 willard kernel:  [<ffffffffae617453>]
usb_probe_interface+0x153/0x2f0
Dec 20 07:18:44 willard kernel:  [<ffffffffae5519b4>]
driver_probe_device+0x224/0x430
Dec 20 07:18:44 willard kernel:  [<ffffffffae551d3c>]
__device_attach_driver+0x8c/0x100
Dec 20 07:18:44 willard kernel:  [<ffffffffae551cb0>] ?
__driver_attach+0xf0/0xf0
Dec 20 07:18:44 willard kernel:  [<ffffffffae54f5c7>] bus_for_each_drv+0x67/0xb0
Dec 20 07:18:44 willard kernel:  [<ffffffffae55161d>] __device_attach+0xdd/0x160
Dec 20 07:18:44 willard kernel:  [<ffffffffae551df3>]
device_initial_probe+0x13/0x20
Dec 20 07:18:44 willard kernel:  [<ffffffffae550832>] bus_probe_device+0x92/0xa0
Dec 20 07:18:44 willard kernel:  [<ffffffffae54e3ad>] device_add+0x3fd/0x670
Dec 20 07:18:44 willard kernel:  [<ffffffffae615359>]
usb_set_configuration+0x529/0x8d0
Dec 20 07:18:44 willard kernel:  [<ffffffffae6204ae>] generic_probe+0x2e/0x80
Dec 20 07:18:44 willard kernel:  [<ffffffffae6172be>] usb_probe_device+0x2e/0x70
Dec 20 07:18:44 willard kernel:  [<ffffffffae5519b4>]
driver_probe_device+0x224/0x430
Dec 20 07:18:44 willard kernel:  [<ffffffffae551d3c>]
__device_attach_driver+0x8c/0x100
Dec 20 07:18:44 willard kernel:  [<ffffffffae551cb0>] ?
__driver_attach+0xf0/0xf0
Dec 20 07:18:44 willard kernel:  [<ffffffffae54f5c7>] bus_for_each_drv+0x67/0xb0
Dec 20 07:18:44 willard kernel:  [<ffffffffae55161d>] __device_attach+0xdd/0x160
Dec 20 07:18:44 willard kernel:  [<ffffffffae551df3>]
device_initial_probe+0x13/0x20
Dec 20 07:18:44 willard kernel:  [<ffffffffae550832>] bus_probe_device+0x92/0xa0
Dec 20 07:18:44 willard kernel:  [<ffffffffae54e3ad>] device_add+0x3fd/0x670
Dec 20 07:18:44 willard kernel:  [<ffffffffae516e00>] ? random_poll+0x80/0x80
Dec 20 07:18:44 willard kernel:  [<ffffffffae60a63f>] usb_new_device+0x26f/0x4b0
Dec 20 07:18:44 willard kernel:  [<ffffffffae60c71e>] hub_event+0xf8e/0x1540
Dec 20 07:18:44 willard kernel:  [<ffffffffae09a65b>]
process_one_work+0x16b/0x480
Dec 20 07:18:44 willard kernel:  [<ffffffffae09a9bb>] worker_thread+0x4b/0x500
Dec 20 07:18:44 willard kernel:  [<ffffffffae09a970>] ?
process_one_work+0x480/0x480
Dec 20 07:18:44 willard kernel:  [<ffffffffae09a970>] ?
process_one_work+0x480/0x480
Dec 20 07:18:44 willard kernel:  [<ffffffffae0a0c19>] kthread+0xd9/0xf0
Dec 20 07:18:44 willard kernel:  [<ffffffffae0a0b40>] ? kthread_park+0x60/0x60
Dec 20 07:18:44 willard kernel:  [<ffffffffae8274b5>] ret_from_fork+0x25/0x30
Dec 20 07:18:44 willard kernel: ---[ end trace a6d2a2beec5e73c1 ]---
Dec 20 07:18:44 willard kernel: usb 6-2: ch34x converter now attached to ttyUSB0

and, it didn't work.  Got 0xff's out on the pl2303, got nothing
visible when characters sent in the other direction after setting
115200 8N1 on both sides.  And, fwiw, I've been rmmod ch341 ; insmod
$path/ch341.ko, where $path is the kernel module tree for the commit
build, all from a 00013-gc510871 base kernel without rebooting.

Not sure what the complaint about keys is about, I had not seen that before.
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johan Hovold Dec. 20, 2016, 4:07 p.m. UTC | #2
On Tue, Dec 20, 2016 at 04:38:19AM -0800, Russell Senior wrote:
> > 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")

> 

> 00019-g3baa1ef:

> 

> Dec 20 04:30:50 willard kernel: usbcore: registered new interface driver ch341

> Dec 20 04:30:50 willard kernel: usbserial: USB Serial support

> registered for ch341-uart

> Dec 20 04:31:14 willard kernel: usb 6-2: new full-speed USB device

> number 11 using uhci_hcd

> Dec 20 04:31:14 willard kernel: usb 6-2: New USB device found,

> idVendor=1a86, idProduct=7523

> Dec 20 04:31:14 willard kernel: usb 6-2: New USB device strings:

> Mfr=0, Product=2, SerialNumber=0

> Dec 20 04:31:14 willard kernel: usb 6-2: Product: USB2.0-Ser!

> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: ch341-uart converter detected

> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: [00] = 00


> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: [17] = 00

> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: [18] = c3


Ok, so the device only returns the value of the LCR when read together
with 0x25. Otherwise all zero.

> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: write 0x31 0xb282


> Dec 20 04:31:14 willard kernel: ch341 6-2:1.0: [18] = f1


And as before it it looks to be updated correctly through a direct
write, even though tx_en and rx_en is always set.

My guess is this is some CH341 clone that mimics the behaviour of some
version of the chip, especially as the vendor does not seem to bother to
support it (unless it's a new version?).

I think sticking to using the init command for all devices, except when
detecting a device that fails to read the divisor registers and then
fall back to direct writes might be the way forward. That way, we'd
have some minimal support for these devices at least.

Perhaps we should determine what else is working or broken first,
though.

Russel, could you test if break-signalling works, and if the
modem-control signals (DTR/RTS) are asserted at open? Do you get any
interrupts when the modem-status changes (e.g. remote end hangs up --
there should be debug messages)?

Any other suggestions?

Thanks,
Johan
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Russell Senior Dec. 20, 2016, 8:05 p.m. UTC | #3
On Tue, Dec 20, 2016 at 7:52 AM, Johan Hovold <johan@kernel.org> wrote:
> On Tue, Dec 20, 2016 at 07:31:55AM -0800, Russell Senior wrote:


>> Not sure what the complaint about keys is about, I had not seen that

>> before.

>

> Related to module signing, not sure why you only see it with the vendor

> driver. Did you apply it to my usb-next branch or Greg's?


Yours.
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Russell Senior Dec. 20, 2016, 8:09 p.m. UTC | #4
On Tue, Dec 20, 2016 at 8:07 AM, Johan Hovold <johan@kernel.org> wrote:

> Perhaps we should determine what else is working or broken first,

> though.

>

> Russel, could you test if break-signalling works, and if the

> modem-control signals (DTR/RTS) are asserted at open? Do you get any

> interrupts when the modem-status changes (e.g. remote end hangs up --

> there should be debug messages)?


Which version(s) would you like tested?
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johan Hovold Dec. 20, 2016, 8:49 p.m. UTC | #5
On Tue, Dec 20, 2016 at 12:09:55PM -0800, Russell Senior wrote:
> On Tue, Dec 20, 2016 at 8:07 AM, Johan Hovold <johan@kernel.org> wrote:

> 

> > Perhaps we should determine what else is working or broken first,

> > though.

> >

> > Russel, could you test if break-signalling works, and if the

> > modem-control signals (DTR/RTS) are asserted at open? Do you get any

> > interrupts when the modem-status changes (e.g. remote end hangs up --

> > there should be debug messages)?

> 

> Which version(s) would you like tested?


You can use c51087107e88 ("USB: serial: ch341: rename modem-status
register").

Thanks,
Johan
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johan Hovold Jan. 9, 2017, 1:51 p.m. UTC | #6
On Tue, Dec 20, 2016 at 09:49:00PM +0100, Johan Hovold wrote:
> On Tue, Dec 20, 2016 at 12:09:55PM -0800, Russell Senior wrote:

> > On Tue, Dec 20, 2016 at 8:07 AM, Johan Hovold <johan@kernel.org> wrote:

> > 

> > > Perhaps we should determine what else is working or broken first,

> > > though.

> > >

> > > Russel, could you test if break-signalling works, and if the

> > > modem-control signals (DTR/RTS) are asserted at open? Do you get any

> > > interrupts when the modem-status changes (e.g. remote end hangs up --

> > > there should be debug messages)?

> > 

> > Which version(s) would you like tested?

> 

> You can use c51087107e88 ("USB: serial: ch341: rename modem-status

> register").


As you noticed, I decided to revert to using direct register writes to
avoid having the modem-control lines being toggled on open/set_termios.
So with a trivial patch changing the initial LCR settings your device
should now be supported.

It would still be interesting to know whether the features mentioned
above works. Testing that using the series I posted on Friday would be
preferred (but not required).

Thanks,
Johan
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johan Hovold Jan. 12, 2017, 2:49 p.m. UTC | #7
On Mon, Jan 09, 2017 at 02:51:59PM +0100, Johan Hovold wrote:
> On Tue, Dec 20, 2016 at 09:49:00PM +0100, Johan Hovold wrote:

> > On Tue, Dec 20, 2016 at 12:09:55PM -0800, Russell Senior wrote:

> > > On Tue, Dec 20, 2016 at 8:07 AM, Johan Hovold <johan@kernel.org> wrote:

> > > 

> > > > Perhaps we should determine what else is working or broken first,

> > > > though.

> > > >

> > > > Russel, could you test if break-signalling works, and if the

> > > > modem-control signals (DTR/RTS) are asserted at open? Do you get any

> > > > interrupts when the modem-status changes (e.g. remote end hangs up --

> > > > there should be debug messages)?

> > > 

> > > Which version(s) would you like tested?

> > 

> > You can use c51087107e88 ("USB: serial: ch341: rename modem-status

> > register").

> 

> As you noticed, I decided to revert to using direct register writes to

> avoid having the modem-control lines being toggled on open/set_termios.

> So with a trivial patch changing the initial LCR settings your device

> should now be supported.


I received one of these quirky devices today and discovered that the
partial revert to using direct register writes should be sufficient for
basic support for these devices.

Changing baud rate and line settings (e.g. 6E2) works with my fixes for
4.10-rc4.

> It would still be interesting to know whether the features mentioned

> above works. Testing that using the series I posted on Friday would be

> preferred (but not required).


I did a quick test of this as well, and it seems break control is not
supported and no interrupts are received on modem-status changes. DTR
and RTS appear to work at least.

Johan
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

From 6d2bca3cda09fca9c0a2a2c84dbf64c6090b3da5 Mon Sep 17 00:00:00 2001
From: Johan Hovold <johan@kernel.org>
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 <frank@kingswood-consulting.co.uk>
  * Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
  * Copyright 2009, Boris Hajduk <boris@hajduk.org>
+ * Copyright 2016, WCH Tech Group <tech@winchiphead.com>
  *
- * 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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
 #include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
 #include <linux/module.h>
-#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
-#include <linux/serial.h>
-#include <asm/unaligned.h>
-
-#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 <tech@winchiphead.com>"
+
+#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