xref: /linux/drivers/usb/serial/ch341.c (revision 5fd54ace4721fc5ce2bb5aef6318fcf17f421460)
1*5fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
26ce76104SFrank A Kingswood /*
36ce76104SFrank A Kingswood  * Copyright 2007, Frank A Kingswood <frank@kingswood-consulting.co.uk>
4664d5df9SWerner Cornelius  * Copyright 2007, Werner Cornelius <werner@cornelius-consult.de>
5664d5df9SWerner Cornelius  * Copyright 2009, Boris Hajduk <boris@hajduk.org>
66ce76104SFrank A Kingswood  *
76ce76104SFrank A Kingswood  * ch341.c implements a serial port driver for the Winchiphead CH341.
86ce76104SFrank A Kingswood  *
96ce76104SFrank A Kingswood  * The CH341 device can be used to implement an RS232 asynchronous
106ce76104SFrank A Kingswood  * serial port, an IEEE-1284 parallel printer port or a memory-like
116ce76104SFrank A Kingswood  * interface. In all cases the CH341 supports an I2C interface as well.
126ce76104SFrank A Kingswood  * This driver only supports the asynchronous serial interface.
136ce76104SFrank A Kingswood  *
146ce76104SFrank A Kingswood  * This program is free software; you can redistribute it and/or
156ce76104SFrank A Kingswood  * modify it under the terms of the GNU General Public License version
166ce76104SFrank A Kingswood  * 2 as published by the Free Software Foundation.
176ce76104SFrank A Kingswood  */
186ce76104SFrank A Kingswood 
196ce76104SFrank A Kingswood #include <linux/kernel.h>
206ce76104SFrank A Kingswood #include <linux/tty.h>
216ce76104SFrank A Kingswood #include <linux/module.h>
225a0e3ad6STejun Heo #include <linux/slab.h>
236ce76104SFrank A Kingswood #include <linux/usb.h>
246ce76104SFrank A Kingswood #include <linux/usb/serial.h>
256ce76104SFrank A Kingswood #include <linux/serial.h>
265be796f0SJohan Hovold #include <asm/unaligned.h>
276ce76104SFrank A Kingswood 
28664d5df9SWerner Cornelius #define DEFAULT_BAUD_RATE 9600
296ce76104SFrank A Kingswood #define DEFAULT_TIMEOUT   1000
306ce76104SFrank A Kingswood 
31664d5df9SWerner Cornelius /* flags for IO-Bits */
32664d5df9SWerner Cornelius #define CH341_BIT_RTS (1 << 6)
33664d5df9SWerner Cornelius #define CH341_BIT_DTR (1 << 5)
34664d5df9SWerner Cornelius 
35664d5df9SWerner Cornelius /******************************/
36664d5df9SWerner Cornelius /* interrupt pipe definitions */
37664d5df9SWerner Cornelius /******************************/
38664d5df9SWerner Cornelius /* always 4 interrupt bytes */
39664d5df9SWerner Cornelius /* first irq byte normally 0x08 */
40664d5df9SWerner Cornelius /* second irq byte base 0x7d + below */
41664d5df9SWerner Cornelius /* third irq byte base 0x94 + below */
42664d5df9SWerner Cornelius /* fourth irq byte normally 0xee */
43664d5df9SWerner Cornelius 
44664d5df9SWerner Cornelius /* second interrupt byte */
45664d5df9SWerner Cornelius #define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */
46664d5df9SWerner Cornelius 
47664d5df9SWerner Cornelius /* status returned in third interrupt answer byte, inverted in data
48664d5df9SWerner Cornelius    from irq */
49664d5df9SWerner Cornelius #define CH341_BIT_CTS 0x01
50664d5df9SWerner Cornelius #define CH341_BIT_DSR 0x02
51664d5df9SWerner Cornelius #define CH341_BIT_RI  0x04
52664d5df9SWerner Cornelius #define CH341_BIT_DCD 0x08
53664d5df9SWerner Cornelius #define CH341_BITS_MODEM_STAT 0x0f /* all bits */
54664d5df9SWerner Cornelius 
55664d5df9SWerner Cornelius /*******************************/
56664d5df9SWerner Cornelius /* baudrate calculation factor */
57664d5df9SWerner Cornelius /*******************************/
58664d5df9SWerner Cornelius #define CH341_BAUDBASE_FACTOR 1532620800
59664d5df9SWerner Cornelius #define CH341_BAUDBASE_DIVMAX 3
60664d5df9SWerner Cornelius 
61492896f0STim Small /* Break support - the information used to implement this was gleaned from
62492896f0STim Small  * the Net/FreeBSD uchcom.c driver by Takanori Watanabe.  Domo arigato.
63492896f0STim Small  */
64492896f0STim Small 
656fde8d29SAidan Thornton #define CH341_REQ_READ_VERSION 0x5F
66492896f0STim Small #define CH341_REQ_WRITE_REG    0x9A
67492896f0STim Small #define CH341_REQ_READ_REG     0x95
686fde8d29SAidan Thornton #define CH341_REQ_SERIAL_INIT  0xA1
696fde8d29SAidan Thornton #define CH341_REQ_MODEM_CTRL   0xA4
70492896f0STim Small 
716fde8d29SAidan Thornton #define CH341_REG_BREAK        0x05
726fde8d29SAidan Thornton #define CH341_REG_LCR          0x18
736fde8d29SAidan Thornton #define CH341_NBREAK_BITS      0x01
746fde8d29SAidan Thornton 
756fde8d29SAidan Thornton #define CH341_LCR_ENABLE_RX    0x80
766fde8d29SAidan Thornton #define CH341_LCR_ENABLE_TX    0x40
776fde8d29SAidan Thornton #define CH341_LCR_MARK_SPACE   0x20
786fde8d29SAidan Thornton #define CH341_LCR_PAR_EVEN     0x10
796fde8d29SAidan Thornton #define CH341_LCR_ENABLE_PAR   0x08
806fde8d29SAidan Thornton #define CH341_LCR_STOP_BITS_2  0x04
816fde8d29SAidan Thornton #define CH341_LCR_CS8          0x03
826fde8d29SAidan Thornton #define CH341_LCR_CS7          0x02
836fde8d29SAidan Thornton #define CH341_LCR_CS6          0x01
846fde8d29SAidan Thornton #define CH341_LCR_CS5          0x00
85492896f0STim Small 
867d40d7e8SNémeth Márton static const struct usb_device_id id_table[] = {
876ce76104SFrank A Kingswood 	{ USB_DEVICE(0x4348, 0x5523) },
8882078234SMichael F. Robbins 	{ USB_DEVICE(0x1a86, 0x7523) },
89d0781383Swangyanqing 	{ USB_DEVICE(0x1a86, 0x5523) },
906ce76104SFrank A Kingswood 	{ },
916ce76104SFrank A Kingswood };
926ce76104SFrank A Kingswood MODULE_DEVICE_TABLE(usb, id_table);
936ce76104SFrank A Kingswood 
946ce76104SFrank A Kingswood struct ch341_private {
95664d5df9SWerner Cornelius 	spinlock_t lock; /* access lock */
96664d5df9SWerner Cornelius 	unsigned baud_rate; /* set baud rate */
97beea33d4SJohan Hovold 	u8 mcr;
98e8024460SJohan Hovold 	u8 msr;
993cca8624SJohan Hovold 	u8 lcr;
1006ce76104SFrank A Kingswood };
1016ce76104SFrank A Kingswood 
102aa91def4SNicolas PLANEL static void ch341_set_termios(struct tty_struct *tty,
103aa91def4SNicolas PLANEL 			      struct usb_serial_port *port,
104aa91def4SNicolas PLANEL 			      struct ktermios *old_termios);
105aa91def4SNicolas PLANEL 
1066ce76104SFrank A Kingswood static int ch341_control_out(struct usb_device *dev, u8 request,
1076ce76104SFrank A Kingswood 			     u16 value, u16 index)
1086ce76104SFrank A Kingswood {
1096ce76104SFrank A Kingswood 	int r;
11079cbeeafSGreg Kroah-Hartman 
11191e0efcdSJohan Hovold 	dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x)\n", __func__,
11291e0efcdSJohan Hovold 		request, value, index);
1136ce76104SFrank A Kingswood 
1146ce76104SFrank A Kingswood 	r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
1156ce76104SFrank A Kingswood 			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
1166ce76104SFrank A Kingswood 			    value, index, NULL, 0, DEFAULT_TIMEOUT);
1172d5a9c72SJohan Hovold 	if (r < 0)
1182d5a9c72SJohan Hovold 		dev_err(&dev->dev, "failed to send control message: %d\n", r);
1196ce76104SFrank A Kingswood 
1206ce76104SFrank A Kingswood 	return r;
1216ce76104SFrank A Kingswood }
1226ce76104SFrank A Kingswood 
1236ce76104SFrank A Kingswood static int ch341_control_in(struct usb_device *dev,
1246ce76104SFrank A Kingswood 			    u8 request, u16 value, u16 index,
1256ce76104SFrank A Kingswood 			    char *buf, unsigned bufsize)
1266ce76104SFrank A Kingswood {
1276ce76104SFrank A Kingswood 	int r;
12879cbeeafSGreg Kroah-Hartman 
12991e0efcdSJohan Hovold 	dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x,%u)\n", __func__,
13091e0efcdSJohan Hovold 		request, value, index, bufsize);
1316ce76104SFrank A Kingswood 
1326ce76104SFrank A Kingswood 	r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
1336ce76104SFrank A Kingswood 			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
1346ce76104SFrank A Kingswood 			    value, index, buf, bufsize, DEFAULT_TIMEOUT);
1352d5a9c72SJohan Hovold 	if (r < bufsize) {
1362d5a9c72SJohan Hovold 		if (r >= 0) {
1372d5a9c72SJohan Hovold 			dev_err(&dev->dev,
1382d5a9c72SJohan Hovold 				"short control message received (%d < %u)\n",
1392d5a9c72SJohan Hovold 				r, bufsize);
1402d5a9c72SJohan Hovold 			r = -EIO;
1412d5a9c72SJohan Hovold 		}
1422d5a9c72SJohan Hovold 
1432d5a9c72SJohan Hovold 		dev_err(&dev->dev, "failed to receive control message: %d\n",
1442d5a9c72SJohan Hovold 			r);
1456ce76104SFrank A Kingswood 		return r;
1466ce76104SFrank A Kingswood 	}
1476ce76104SFrank A Kingswood 
1482d5a9c72SJohan Hovold 	return 0;
1492d5a9c72SJohan Hovold }
1502d5a9c72SJohan Hovold 
15155fa15b5SJohan Hovold static int ch341_set_baudrate_lcr(struct usb_device *dev,
15255fa15b5SJohan Hovold 				  struct ch341_private *priv, u8 lcr)
1536ce76104SFrank A Kingswood {
1544e46c410SAidan Thornton 	short a;
1556ce76104SFrank A Kingswood 	int r;
156664d5df9SWerner Cornelius 	unsigned long factor;
157664d5df9SWerner Cornelius 	short divisor;
1586ce76104SFrank A Kingswood 
159664d5df9SWerner Cornelius 	if (!priv->baud_rate)
1606ce76104SFrank A Kingswood 		return -EINVAL;
161664d5df9SWerner Cornelius 	factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate);
162664d5df9SWerner Cornelius 	divisor = CH341_BAUDBASE_DIVMAX;
163664d5df9SWerner Cornelius 
164664d5df9SWerner Cornelius 	while ((factor > 0xfff0) && divisor) {
165664d5df9SWerner Cornelius 		factor >>= 3;
166664d5df9SWerner Cornelius 		divisor--;
1676ce76104SFrank A Kingswood 	}
1686ce76104SFrank A Kingswood 
169664d5df9SWerner Cornelius 	if (factor > 0xfff0)
170664d5df9SWerner Cornelius 		return -EINVAL;
171664d5df9SWerner Cornelius 
172664d5df9SWerner Cornelius 	factor = 0x10000 - factor;
173664d5df9SWerner Cornelius 	a = (factor & 0xff00) | divisor;
174664d5df9SWerner Cornelius 
17555fa15b5SJohan Hovold 	/*
17655fa15b5SJohan Hovold 	 * CH341A buffers data until a full endpoint-size packet (32 bytes)
17755fa15b5SJohan Hovold 	 * has been received unless bit 7 is set.
17855fa15b5SJohan Hovold 	 */
17955fa15b5SJohan Hovold 	a |= BIT(7);
18055fa15b5SJohan Hovold 
18155fa15b5SJohan Hovold 	r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x1312, a);
18255fa15b5SJohan Hovold 	if (r)
18355fa15b5SJohan Hovold 		return r;
18455fa15b5SJohan Hovold 
18555fa15b5SJohan Hovold 	r = ch341_control_out(dev, CH341_REQ_WRITE_REG, 0x2518, lcr);
18655fa15b5SJohan Hovold 	if (r)
18755fa15b5SJohan Hovold 		return r;
1886ce76104SFrank A Kingswood 
1896ce76104SFrank A Kingswood 	return r;
1906ce76104SFrank A Kingswood }
1916ce76104SFrank A Kingswood 
192664d5df9SWerner Cornelius static int ch341_set_handshake(struct usb_device *dev, u8 control)
1936ce76104SFrank A Kingswood {
1946fde8d29SAidan Thornton 	return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0);
1956ce76104SFrank A Kingswood }
1966ce76104SFrank A Kingswood 
197664d5df9SWerner Cornelius static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv)
1986ce76104SFrank A Kingswood {
1992d5a9c72SJohan Hovold 	const unsigned int size = 2;
2006ce76104SFrank A Kingswood 	char *buffer;
2016ce76104SFrank A Kingswood 	int r;
202664d5df9SWerner Cornelius 	unsigned long flags;
2036ce76104SFrank A Kingswood 
2046ce76104SFrank A Kingswood 	buffer = kmalloc(size, GFP_KERNEL);
2056ce76104SFrank A Kingswood 	if (!buffer)
2066ce76104SFrank A Kingswood 		return -ENOMEM;
2076ce76104SFrank A Kingswood 
2086fde8d29SAidan Thornton 	r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size);
2096ce76104SFrank A Kingswood 	if (r < 0)
2106ce76104SFrank A Kingswood 		goto out;
2116ce76104SFrank A Kingswood 
212664d5df9SWerner Cornelius 	spin_lock_irqsave(&priv->lock, flags);
213e8024460SJohan Hovold 	priv->msr = (~(*buffer)) & CH341_BITS_MODEM_STAT;
214664d5df9SWerner Cornelius 	spin_unlock_irqrestore(&priv->lock, flags);
2156ce76104SFrank A Kingswood 
2166ce76104SFrank A Kingswood out:	kfree(buffer);
2176ce76104SFrank A Kingswood 	return r;
2186ce76104SFrank A Kingswood }
2196ce76104SFrank A Kingswood 
2206ce76104SFrank A Kingswood /* -------------------------------------------------------------------------- */
2216ce76104SFrank A Kingswood 
22293b6497dSAdrian Bunk static int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
2236ce76104SFrank A Kingswood {
2242d5a9c72SJohan Hovold 	const unsigned int size = 2;
2256ce76104SFrank A Kingswood 	char *buffer;
2266ce76104SFrank A Kingswood 	int r;
2276ce76104SFrank A Kingswood 
2286ce76104SFrank A Kingswood 	buffer = kmalloc(size, GFP_KERNEL);
2296ce76104SFrank A Kingswood 	if (!buffer)
2306ce76104SFrank A Kingswood 		return -ENOMEM;
2316ce76104SFrank A Kingswood 
2326ce76104SFrank A Kingswood 	/* expect two bytes 0x27 0x00 */
2336fde8d29SAidan Thornton 	r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size);
2346ce76104SFrank A Kingswood 	if (r < 0)
2356ce76104SFrank A Kingswood 		goto out;
236a98b6900SAidan Thornton 	dev_dbg(&dev->dev, "Chip version: 0x%02x\n", buffer[0]);
2376ce76104SFrank A Kingswood 
2386fde8d29SAidan Thornton 	r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0);
2396ce76104SFrank A Kingswood 	if (r < 0)
2406ce76104SFrank A Kingswood 		goto out;
2416ce76104SFrank A Kingswood 
24255fa15b5SJohan Hovold 	r = ch341_set_baudrate_lcr(dev, priv, priv->lcr);
2436ce76104SFrank A Kingswood 	if (r < 0)
2446ce76104SFrank A Kingswood 		goto out;
2456ce76104SFrank A Kingswood 
246beea33d4SJohan Hovold 	r = ch341_set_handshake(dev, priv->mcr);
2476ce76104SFrank A Kingswood 
2486ce76104SFrank A Kingswood out:	kfree(buffer);
2496ce76104SFrank A Kingswood 	return r;
2506ce76104SFrank A Kingswood }
2516ce76104SFrank A Kingswood 
252456c5be5SJohan Hovold static int ch341_port_probe(struct usb_serial_port *port)
2536ce76104SFrank A Kingswood {
2546ce76104SFrank A Kingswood 	struct ch341_private *priv;
2556ce76104SFrank A Kingswood 	int r;
2566ce76104SFrank A Kingswood 
2576ce76104SFrank A Kingswood 	priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL);
2586ce76104SFrank A Kingswood 	if (!priv)
2596ce76104SFrank A Kingswood 		return -ENOMEM;
2606ce76104SFrank A Kingswood 
261664d5df9SWerner Cornelius 	spin_lock_init(&priv->lock);
2626ce76104SFrank A Kingswood 	priv->baud_rate = DEFAULT_BAUD_RATE;
2637c61b0d5SJohan Hovold 	/*
2647c61b0d5SJohan Hovold 	 * Some CH340 devices appear unable to change the initial LCR
2657c61b0d5SJohan Hovold 	 * settings, so set a sane 8N1 default.
2667c61b0d5SJohan Hovold 	 */
2677c61b0d5SJohan Hovold 	priv->lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8;
2686ce76104SFrank A Kingswood 
269456c5be5SJohan Hovold 	r = ch341_configure(port->serial->dev, priv);
2706ce76104SFrank A Kingswood 	if (r < 0)
2716ce76104SFrank A Kingswood 		goto error;
2726ce76104SFrank A Kingswood 
273456c5be5SJohan Hovold 	usb_set_serial_port_data(port, priv);
2746ce76104SFrank A Kingswood 	return 0;
2756ce76104SFrank A Kingswood 
2766ce76104SFrank A Kingswood error:	kfree(priv);
2776ce76104SFrank A Kingswood 	return r;
2786ce76104SFrank A Kingswood }
2796ce76104SFrank A Kingswood 
280456c5be5SJohan Hovold static int ch341_port_remove(struct usb_serial_port *port)
281456c5be5SJohan Hovold {
282456c5be5SJohan Hovold 	struct ch341_private *priv;
283456c5be5SJohan Hovold 
284456c5be5SJohan Hovold 	priv = usb_get_serial_port_data(port);
285456c5be5SJohan Hovold 	kfree(priv);
286456c5be5SJohan Hovold 
287456c5be5SJohan Hovold 	return 0;
288456c5be5SJohan Hovold }
289456c5be5SJohan Hovold 
290335f8514SAlan Cox static int ch341_carrier_raised(struct usb_serial_port *port)
291335f8514SAlan Cox {
292335f8514SAlan Cox 	struct ch341_private *priv = usb_get_serial_port_data(port);
293e8024460SJohan Hovold 	if (priv->msr & CH341_BIT_DCD)
294335f8514SAlan Cox 		return 1;
295335f8514SAlan Cox 	return 0;
296335f8514SAlan Cox }
297335f8514SAlan Cox 
298335f8514SAlan Cox static void ch341_dtr_rts(struct usb_serial_port *port, int on)
299664d5df9SWerner Cornelius {
300664d5df9SWerner Cornelius 	struct ch341_private *priv = usb_get_serial_port_data(port);
301664d5df9SWerner Cornelius 	unsigned long flags;
302664d5df9SWerner Cornelius 
303335f8514SAlan Cox 	/* drop DTR and RTS */
304335f8514SAlan Cox 	spin_lock_irqsave(&priv->lock, flags);
305335f8514SAlan Cox 	if (on)
306beea33d4SJohan Hovold 		priv->mcr |= CH341_BIT_RTS | CH341_BIT_DTR;
307335f8514SAlan Cox 	else
308beea33d4SJohan Hovold 		priv->mcr &= ~(CH341_BIT_RTS | CH341_BIT_DTR);
309335f8514SAlan Cox 	spin_unlock_irqrestore(&priv->lock, flags);
310beea33d4SJohan Hovold 	ch341_set_handshake(port->serial->dev, priv->mcr);
311335f8514SAlan Cox }
312335f8514SAlan Cox 
313335f8514SAlan Cox static void ch341_close(struct usb_serial_port *port)
314335f8514SAlan Cox {
315f26788daSJohan Hovold 	usb_serial_generic_close(port);
316664d5df9SWerner Cornelius 	usb_kill_urb(port->interrupt_in_urb);
317664d5df9SWerner Cornelius }
318664d5df9SWerner Cornelius 
319664d5df9SWerner Cornelius 
3206ce76104SFrank A Kingswood /* open this device, set default parameters */
321a509a7e4SAlan Cox static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port)
3226ce76104SFrank A Kingswood {
323456c5be5SJohan Hovold 	struct ch341_private *priv = usb_get_serial_port_data(port);
3246ce76104SFrank A Kingswood 	int r;
3256ce76104SFrank A Kingswood 
326aa91def4SNicolas PLANEL 	if (tty)
327aa91def4SNicolas PLANEL 		ch341_set_termios(tty, port, NULL);
3286ce76104SFrank A Kingswood 
329d9a38a87SJohan Hovold 	dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__);
330664d5df9SWerner Cornelius 	r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
331664d5df9SWerner Cornelius 	if (r) {
332d9a38a87SJohan Hovold 		dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n",
333d9a38a87SJohan Hovold 			__func__, r);
334f2950b78SJohan Hovold 		return r;
335664d5df9SWerner Cornelius 	}
336664d5df9SWerner Cornelius 
337a0467a96SJohan Hovold 	r = ch341_get_status(port->serial->dev, priv);
338a0467a96SJohan Hovold 	if (r < 0) {
339a0467a96SJohan Hovold 		dev_err(&port->dev, "failed to read modem status: %d\n", r);
340a0467a96SJohan Hovold 		goto err_kill_interrupt_urb;
341a0467a96SJohan Hovold 	}
342a0467a96SJohan Hovold 
343a509a7e4SAlan Cox 	r = usb_serial_generic_open(tty, port);
344f2950b78SJohan Hovold 	if (r)
345f2950b78SJohan Hovold 		goto err_kill_interrupt_urb;
3466ce76104SFrank A Kingswood 
347f2950b78SJohan Hovold 	return 0;
348f2950b78SJohan Hovold 
349f2950b78SJohan Hovold err_kill_interrupt_urb:
350f2950b78SJohan Hovold 	usb_kill_urb(port->interrupt_in_urb);
351f2950b78SJohan Hovold 
352f2950b78SJohan Hovold 	return r;
3536ce76104SFrank A Kingswood }
3546ce76104SFrank A Kingswood 
3556ce76104SFrank A Kingswood /* Old_termios contains the original termios settings and
3566ce76104SFrank A Kingswood  * tty->termios contains the new setting to be used.
3576ce76104SFrank A Kingswood  */
35895da310eSAlan Cox static void ch341_set_termios(struct tty_struct *tty,
35995da310eSAlan Cox 		struct usb_serial_port *port, struct ktermios *old_termios)
3606ce76104SFrank A Kingswood {
3616ce76104SFrank A Kingswood 	struct ch341_private *priv = usb_get_serial_port_data(port);
3626ce76104SFrank A Kingswood 	unsigned baud_rate;
363664d5df9SWerner Cornelius 	unsigned long flags;
364448b6dc5SJohan Hovold 	u8 lcr;
3654e46c410SAidan Thornton 	int r;
3664e46c410SAidan Thornton 
3674e46c410SAidan Thornton 	/* redundant changes may cause the chip to lose bytes */
3684e46c410SAidan Thornton 	if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
3694e46c410SAidan Thornton 		return;
3706ce76104SFrank A Kingswood 
3716ce76104SFrank A Kingswood 	baud_rate = tty_get_baud_rate(tty);
3726ce76104SFrank A Kingswood 
373448b6dc5SJohan Hovold 	lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX;
374664d5df9SWerner Cornelius 
375ba781bdfSAidan Thornton 	switch (C_CSIZE(tty)) {
376ba781bdfSAidan Thornton 	case CS5:
377448b6dc5SJohan Hovold 		lcr |= CH341_LCR_CS5;
378ba781bdfSAidan Thornton 		break;
379ba781bdfSAidan Thornton 	case CS6:
380448b6dc5SJohan Hovold 		lcr |= CH341_LCR_CS6;
381ba781bdfSAidan Thornton 		break;
382ba781bdfSAidan Thornton 	case CS7:
383448b6dc5SJohan Hovold 		lcr |= CH341_LCR_CS7;
384ba781bdfSAidan Thornton 		break;
385ba781bdfSAidan Thornton 	case CS8:
386448b6dc5SJohan Hovold 		lcr |= CH341_LCR_CS8;
387ba781bdfSAidan Thornton 		break;
388ba781bdfSAidan Thornton 	}
389ba781bdfSAidan Thornton 
390ba781bdfSAidan Thornton 	if (C_PARENB(tty)) {
391448b6dc5SJohan Hovold 		lcr |= CH341_LCR_ENABLE_PAR;
392ba781bdfSAidan Thornton 		if (C_PARODD(tty) == 0)
393448b6dc5SJohan Hovold 			lcr |= CH341_LCR_PAR_EVEN;
394ba781bdfSAidan Thornton 		if (C_CMSPAR(tty))
395448b6dc5SJohan Hovold 			lcr |= CH341_LCR_MARK_SPACE;
396ba781bdfSAidan Thornton 	}
397ba781bdfSAidan Thornton 
398ba781bdfSAidan Thornton 	if (C_CSTOPB(tty))
399448b6dc5SJohan Hovold 		lcr |= CH341_LCR_STOP_BITS_2;
4004e46c410SAidan Thornton 
401664d5df9SWerner Cornelius 	if (baud_rate) {
402a20047f3SJohan Hovold 		priv->baud_rate = baud_rate;
403a20047f3SJohan Hovold 
404448b6dc5SJohan Hovold 		r = ch341_set_baudrate_lcr(port->serial->dev, priv, lcr);
4054e46c410SAidan Thornton 		if (r < 0 && old_termios) {
4064e46c410SAidan Thornton 			priv->baud_rate = tty_termios_baud_rate(old_termios);
4074e46c410SAidan Thornton 			tty_termios_copy_hw(&tty->termios, old_termios);
4083cca8624SJohan Hovold 		} else if (r == 0) {
409448b6dc5SJohan Hovold 			priv->lcr = lcr;
4104e46c410SAidan Thornton 		}
4116ce76104SFrank A Kingswood 	}
4126ce76104SFrank A Kingswood 
413030ee7aeSJohan Hovold 	spin_lock_irqsave(&priv->lock, flags);
414030ee7aeSJohan Hovold 	if (C_BAUD(tty) == B0)
415beea33d4SJohan Hovold 		priv->mcr &= ~(CH341_BIT_DTR | CH341_BIT_RTS);
416030ee7aeSJohan Hovold 	else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
417beea33d4SJohan Hovold 		priv->mcr |= (CH341_BIT_DTR | CH341_BIT_RTS);
418030ee7aeSJohan Hovold 	spin_unlock_irqrestore(&priv->lock, flags);
4196ce76104SFrank A Kingswood 
420beea33d4SJohan Hovold 	ch341_set_handshake(port->serial->dev, priv->mcr);
421664d5df9SWerner Cornelius }
42273f59308SAlan Cox 
423492896f0STim Small static void ch341_break_ctl(struct tty_struct *tty, int break_state)
424492896f0STim Small {
425492896f0STim Small 	const uint16_t ch341_break_reg =
4266fde8d29SAidan Thornton 			((uint16_t) CH341_REG_LCR << 8) | CH341_REG_BREAK;
427492896f0STim Small 	struct usb_serial_port *port = tty->driver_data;
428492896f0STim Small 	int r;
429492896f0STim Small 	uint16_t reg_contents;
430f2b5cc83SJohan Hovold 	uint8_t *break_reg;
431492896f0STim Small 
432f2b5cc83SJohan Hovold 	break_reg = kmalloc(2, GFP_KERNEL);
43310c642d0SJohan Hovold 	if (!break_reg)
434f2b5cc83SJohan Hovold 		return;
435f2b5cc83SJohan Hovold 
436492896f0STim Small 	r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG,
437f2b5cc83SJohan Hovold 			ch341_break_reg, 0, break_reg, 2);
438492896f0STim Small 	if (r < 0) {
4396a9b15feSJohan Hovold 		dev_err(&port->dev, "%s - USB control read error (%d)\n",
4406a9b15feSJohan Hovold 				__func__, r);
441f2b5cc83SJohan Hovold 		goto out;
442492896f0STim Small 	}
44379cbeeafSGreg Kroah-Hartman 	dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n",
444492896f0STim Small 		__func__, break_reg[0], break_reg[1]);
445492896f0STim Small 	if (break_state != 0) {
44679cbeeafSGreg Kroah-Hartman 		dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__);
4476fde8d29SAidan Thornton 		break_reg[0] &= ~CH341_NBREAK_BITS;
4486fde8d29SAidan Thornton 		break_reg[1] &= ~CH341_LCR_ENABLE_TX;
449492896f0STim Small 	} else {
45079cbeeafSGreg Kroah-Hartman 		dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__);
4516fde8d29SAidan Thornton 		break_reg[0] |= CH341_NBREAK_BITS;
4526fde8d29SAidan Thornton 		break_reg[1] |= CH341_LCR_ENABLE_TX;
453492896f0STim Small 	}
45479cbeeafSGreg Kroah-Hartman 	dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n",
455492896f0STim Small 		__func__, break_reg[0], break_reg[1]);
4565be796f0SJohan Hovold 	reg_contents = get_unaligned_le16(break_reg);
457492896f0STim Small 	r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG,
458492896f0STim Small 			ch341_break_reg, reg_contents);
459492896f0STim Small 	if (r < 0)
4606a9b15feSJohan Hovold 		dev_err(&port->dev, "%s - USB control write error (%d)\n",
4616a9b15feSJohan Hovold 				__func__, r);
462f2b5cc83SJohan Hovold out:
463f2b5cc83SJohan Hovold 	kfree(break_reg);
464492896f0STim Small }
465492896f0STim Small 
46620b9d177SAlan Cox static int ch341_tiocmset(struct tty_struct *tty,
467664d5df9SWerner Cornelius 			  unsigned int set, unsigned int clear)
468664d5df9SWerner Cornelius {
469664d5df9SWerner Cornelius 	struct usb_serial_port *port = tty->driver_data;
470664d5df9SWerner Cornelius 	struct ch341_private *priv = usb_get_serial_port_data(port);
471664d5df9SWerner Cornelius 	unsigned long flags;
472664d5df9SWerner Cornelius 	u8 control;
473664d5df9SWerner Cornelius 
474664d5df9SWerner Cornelius 	spin_lock_irqsave(&priv->lock, flags);
475664d5df9SWerner Cornelius 	if (set & TIOCM_RTS)
476beea33d4SJohan Hovold 		priv->mcr |= CH341_BIT_RTS;
477664d5df9SWerner Cornelius 	if (set & TIOCM_DTR)
478beea33d4SJohan Hovold 		priv->mcr |= CH341_BIT_DTR;
479664d5df9SWerner Cornelius 	if (clear & TIOCM_RTS)
480beea33d4SJohan Hovold 		priv->mcr &= ~CH341_BIT_RTS;
481664d5df9SWerner Cornelius 	if (clear & TIOCM_DTR)
482beea33d4SJohan Hovold 		priv->mcr &= ~CH341_BIT_DTR;
483beea33d4SJohan Hovold 	control = priv->mcr;
484664d5df9SWerner Cornelius 	spin_unlock_irqrestore(&priv->lock, flags);
485664d5df9SWerner Cornelius 
486664d5df9SWerner Cornelius 	return ch341_set_handshake(port->serial->dev, control);
487664d5df9SWerner Cornelius }
488664d5df9SWerner Cornelius 
489e8024460SJohan Hovold static void ch341_update_status(struct usb_serial_port *port,
490ac035628SJohan Hovold 					unsigned char *data, size_t len)
491ac035628SJohan Hovold {
492ac035628SJohan Hovold 	struct ch341_private *priv = usb_get_serial_port_data(port);
493b770081fSJohan Hovold 	struct tty_struct *tty;
494ac035628SJohan Hovold 	unsigned long flags;
495b770081fSJohan Hovold 	u8 status;
496b770081fSJohan Hovold 	u8 delta;
497ac035628SJohan Hovold 
498ac035628SJohan Hovold 	if (len < 4)
499ac035628SJohan Hovold 		return;
500ac035628SJohan Hovold 
501b770081fSJohan Hovold 	status = ~data[2] & CH341_BITS_MODEM_STAT;
502b770081fSJohan Hovold 
503ac035628SJohan Hovold 	spin_lock_irqsave(&priv->lock, flags);
504e8024460SJohan Hovold 	delta = status ^ priv->msr;
505e8024460SJohan Hovold 	priv->msr = status;
506ac035628SJohan Hovold 	spin_unlock_irqrestore(&priv->lock, flags);
507ac035628SJohan Hovold 
508fd74b0b1SJohan Hovold 	if (data[1] & CH341_MULT_STAT)
509fd74b0b1SJohan Hovold 		dev_dbg(&port->dev, "%s - multiple status change\n", __func__);
510fd74b0b1SJohan Hovold 
511d984fe91SJohan Hovold 	if (!delta)
512d984fe91SJohan Hovold 		return;
513d984fe91SJohan Hovold 
5145e409a26SJohan Hovold 	if (delta & CH341_BIT_CTS)
5155e409a26SJohan Hovold 		port->icount.cts++;
5165e409a26SJohan Hovold 	if (delta & CH341_BIT_DSR)
5175e409a26SJohan Hovold 		port->icount.dsr++;
5185e409a26SJohan Hovold 	if (delta & CH341_BIT_RI)
5195e409a26SJohan Hovold 		port->icount.rng++;
520b770081fSJohan Hovold 	if (delta & CH341_BIT_DCD) {
5215e409a26SJohan Hovold 		port->icount.dcd++;
522b770081fSJohan Hovold 		tty = tty_port_tty_get(&port->port);
523b770081fSJohan Hovold 		if (tty) {
524ac035628SJohan Hovold 			usb_serial_handle_dcd_change(port, tty,
525b770081fSJohan Hovold 						status & CH341_BIT_DCD);
526ac035628SJohan Hovold 			tty_kref_put(tty);
527ac035628SJohan Hovold 		}
528b770081fSJohan Hovold 	}
529ac035628SJohan Hovold 
530ac035628SJohan Hovold 	wake_up_interruptible(&port->port.delta_msr_wait);
531ac035628SJohan Hovold }
532ac035628SJohan Hovold 
533664d5df9SWerner Cornelius static void ch341_read_int_callback(struct urb *urb)
534664d5df9SWerner Cornelius {
535271ec2d2SJohan Hovold 	struct usb_serial_port *port = urb->context;
536664d5df9SWerner Cornelius 	unsigned char *data = urb->transfer_buffer;
537271ec2d2SJohan Hovold 	unsigned int len = urb->actual_length;
538664d5df9SWerner Cornelius 	int status;
539664d5df9SWerner Cornelius 
540664d5df9SWerner Cornelius 	switch (urb->status) {
541664d5df9SWerner Cornelius 	case 0:
542664d5df9SWerner Cornelius 		/* success */
543664d5df9SWerner Cornelius 		break;
544664d5df9SWerner Cornelius 	case -ECONNRESET:
545664d5df9SWerner Cornelius 	case -ENOENT:
546664d5df9SWerner Cornelius 	case -ESHUTDOWN:
547664d5df9SWerner Cornelius 		/* this urb is terminated, clean up */
548271ec2d2SJohan Hovold 		dev_dbg(&urb->dev->dev, "%s - urb shutting down: %d\n",
54979cbeeafSGreg Kroah-Hartman 			__func__, urb->status);
550664d5df9SWerner Cornelius 		return;
551664d5df9SWerner Cornelius 	default:
552271ec2d2SJohan Hovold 		dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n",
55379cbeeafSGreg Kroah-Hartman 			__func__, urb->status);
554664d5df9SWerner Cornelius 		goto exit;
555664d5df9SWerner Cornelius 	}
556664d5df9SWerner Cornelius 
557271ec2d2SJohan Hovold 	usb_serial_debug_data(&port->dev, __func__, len, data);
558e8024460SJohan Hovold 	ch341_update_status(port, data, len);
559664d5df9SWerner Cornelius exit:
560664d5df9SWerner Cornelius 	status = usb_submit_urb(urb, GFP_ATOMIC);
561271ec2d2SJohan Hovold 	if (status) {
562271ec2d2SJohan Hovold 		dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n",
563664d5df9SWerner Cornelius 			__func__, status);
564664d5df9SWerner Cornelius 	}
565271ec2d2SJohan Hovold }
566664d5df9SWerner Cornelius 
56760b33c13SAlan Cox static int ch341_tiocmget(struct tty_struct *tty)
568664d5df9SWerner Cornelius {
569664d5df9SWerner Cornelius 	struct usb_serial_port *port = tty->driver_data;
570664d5df9SWerner Cornelius 	struct ch341_private *priv = usb_get_serial_port_data(port);
571664d5df9SWerner Cornelius 	unsigned long flags;
572664d5df9SWerner Cornelius 	u8 mcr;
573664d5df9SWerner Cornelius 	u8 status;
574664d5df9SWerner Cornelius 	unsigned int result;
575664d5df9SWerner Cornelius 
576664d5df9SWerner Cornelius 	spin_lock_irqsave(&priv->lock, flags);
577beea33d4SJohan Hovold 	mcr = priv->mcr;
578e8024460SJohan Hovold 	status = priv->msr;
579664d5df9SWerner Cornelius 	spin_unlock_irqrestore(&priv->lock, flags);
580664d5df9SWerner Cornelius 
581664d5df9SWerner Cornelius 	result = ((mcr & CH341_BIT_DTR)		? TIOCM_DTR : 0)
582664d5df9SWerner Cornelius 		  | ((mcr & CH341_BIT_RTS)	? TIOCM_RTS : 0)
583664d5df9SWerner Cornelius 		  | ((status & CH341_BIT_CTS)	? TIOCM_CTS : 0)
584664d5df9SWerner Cornelius 		  | ((status & CH341_BIT_DSR)	? TIOCM_DSR : 0)
585664d5df9SWerner Cornelius 		  | ((status & CH341_BIT_RI)	? TIOCM_RI  : 0)
586664d5df9SWerner Cornelius 		  | ((status & CH341_BIT_DCD)	? TIOCM_CD  : 0);
587664d5df9SWerner Cornelius 
58879cbeeafSGreg Kroah-Hartman 	dev_dbg(&port->dev, "%s - result = %x\n", __func__, result);
589664d5df9SWerner Cornelius 
590664d5df9SWerner Cornelius 	return result;
5916ce76104SFrank A Kingswood }
5926ce76104SFrank A Kingswood 
593622b80cfSGreg Kroah-Hartman static int ch341_reset_resume(struct usb_serial *serial)
5941ded7ea4SMing Lei {
595ce5e2928SJohan Hovold 	struct usb_serial_port *port = serial->port[0];
596ce5e2928SJohan Hovold 	struct ch341_private *priv = usb_get_serial_port_data(port);
597ce5e2928SJohan Hovold 	int ret;
5981ded7ea4SMing Lei 
5991ded7ea4SMing Lei 	/* reconfigure ch341 serial port after bus-reset */
6002bfd1c96SGreg Kroah-Hartman 	ch341_configure(serial->dev, priv);
6011ded7ea4SMing Lei 
602ce5e2928SJohan Hovold 	if (tty_port_initialized(&port->port)) {
603ce5e2928SJohan Hovold 		ret = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
604ce5e2928SJohan Hovold 		if (ret) {
605ce5e2928SJohan Hovold 			dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
606ce5e2928SJohan Hovold 				ret);
607ce5e2928SJohan Hovold 			return ret;
608ce5e2928SJohan Hovold 		}
609a0467a96SJohan Hovold 
610a0467a96SJohan Hovold 		ret = ch341_get_status(port->serial->dev, priv);
611a0467a96SJohan Hovold 		if (ret < 0) {
612a0467a96SJohan Hovold 			dev_err(&port->dev, "failed to read modem status: %d\n",
613a0467a96SJohan Hovold 				ret);
614a0467a96SJohan Hovold 		}
615ce5e2928SJohan Hovold 	}
616ce5e2928SJohan Hovold 
617ce5e2928SJohan Hovold 	return usb_serial_generic_resume(serial);
6181ded7ea4SMing Lei }
6191ded7ea4SMing Lei 
6206ce76104SFrank A Kingswood static struct usb_serial_driver ch341_device = {
6216ce76104SFrank A Kingswood 	.driver = {
6226ce76104SFrank A Kingswood 		.owner	= THIS_MODULE,
6236ce76104SFrank A Kingswood 		.name	= "ch341-uart",
6246ce76104SFrank A Kingswood 	},
6256ce76104SFrank A Kingswood 	.id_table          = id_table,
6266ce76104SFrank A Kingswood 	.num_ports         = 1,
6276ce76104SFrank A Kingswood 	.open              = ch341_open,
628335f8514SAlan Cox 	.dtr_rts	   = ch341_dtr_rts,
629335f8514SAlan Cox 	.carrier_raised	   = ch341_carrier_raised,
630664d5df9SWerner Cornelius 	.close             = ch341_close,
6316ce76104SFrank A Kingswood 	.set_termios       = ch341_set_termios,
632492896f0STim Small 	.break_ctl         = ch341_break_ctl,
633664d5df9SWerner Cornelius 	.tiocmget          = ch341_tiocmget,
634664d5df9SWerner Cornelius 	.tiocmset          = ch341_tiocmset,
6355e409a26SJohan Hovold 	.tiocmiwait        = usb_serial_generic_tiocmiwait,
636664d5df9SWerner Cornelius 	.read_int_callback = ch341_read_int_callback,
637456c5be5SJohan Hovold 	.port_probe        = ch341_port_probe,
638456c5be5SJohan Hovold 	.port_remove       = ch341_port_remove,
6391c1eaba8SGreg Kroah-Hartman 	.reset_resume      = ch341_reset_resume,
6406ce76104SFrank A Kingswood };
6416ce76104SFrank A Kingswood 
64208a4f6bcSAlan Stern static struct usb_serial_driver * const serial_drivers[] = {
64308a4f6bcSAlan Stern 	&ch341_device, NULL
64408a4f6bcSAlan Stern };
64508a4f6bcSAlan Stern 
64668e24113SGreg Kroah-Hartman module_usb_serial_driver(serial_drivers, id_table);
6476ce76104SFrank A Kingswood 
6486ce76104SFrank A Kingswood MODULE_LICENSE("GPL");
649