xref: /linux/drivers/usb/serial/ch341.c (revision 6ce76104781a10554129791dc62c3104424f6d48)
1*6ce76104SFrank A Kingswood /*
2*6ce76104SFrank A Kingswood  * Copyright 2007, Frank A Kingswood <frank@kingswood-consulting.co.uk>
3*6ce76104SFrank A Kingswood  *
4*6ce76104SFrank A Kingswood  * ch341.c implements a serial port driver for the Winchiphead CH341.
5*6ce76104SFrank A Kingswood  *
6*6ce76104SFrank A Kingswood  * The CH341 device can be used to implement an RS232 asynchronous
7*6ce76104SFrank A Kingswood  * serial port, an IEEE-1284 parallel printer port or a memory-like
8*6ce76104SFrank A Kingswood  * interface. In all cases the CH341 supports an I2C interface as well.
9*6ce76104SFrank A Kingswood  * This driver only supports the asynchronous serial interface.
10*6ce76104SFrank A Kingswood  *
11*6ce76104SFrank A Kingswood  * This program is free software; you can redistribute it and/or
12*6ce76104SFrank A Kingswood  * modify it under the terms of the GNU General Public License version
13*6ce76104SFrank A Kingswood  * 2 as published by the Free Software Foundation.
14*6ce76104SFrank A Kingswood  */
15*6ce76104SFrank A Kingswood 
16*6ce76104SFrank A Kingswood #include <linux/kernel.h>
17*6ce76104SFrank A Kingswood #include <linux/init.h>
18*6ce76104SFrank A Kingswood #include <linux/tty.h>
19*6ce76104SFrank A Kingswood #include <linux/module.h>
20*6ce76104SFrank A Kingswood #include <linux/usb.h>
21*6ce76104SFrank A Kingswood #include <linux/usb/serial.h>
22*6ce76104SFrank A Kingswood #include <linux/serial.h>
23*6ce76104SFrank A Kingswood 
24*6ce76104SFrank A Kingswood #define DEFAULT_BAUD_RATE 2400
25*6ce76104SFrank A Kingswood #define DEFAULT_TIMEOUT   1000
26*6ce76104SFrank A Kingswood 
27*6ce76104SFrank A Kingswood static int debug;
28*6ce76104SFrank A Kingswood 
29*6ce76104SFrank A Kingswood static struct usb_device_id id_table [] = {
30*6ce76104SFrank A Kingswood 	{ USB_DEVICE(0x4348, 0x5523) },
31*6ce76104SFrank A Kingswood 	{ },
32*6ce76104SFrank A Kingswood };
33*6ce76104SFrank A Kingswood MODULE_DEVICE_TABLE(usb, id_table);
34*6ce76104SFrank A Kingswood 
35*6ce76104SFrank A Kingswood struct ch341_private {
36*6ce76104SFrank A Kingswood 	unsigned baud_rate;
37*6ce76104SFrank A Kingswood 	u8 dtr;
38*6ce76104SFrank A Kingswood 	u8 rts;
39*6ce76104SFrank A Kingswood };
40*6ce76104SFrank A Kingswood 
41*6ce76104SFrank A Kingswood static int ch341_control_out(struct usb_device *dev, u8 request,
42*6ce76104SFrank A Kingswood 			     u16 value, u16 index)
43*6ce76104SFrank A Kingswood {
44*6ce76104SFrank A Kingswood 	int r;
45*6ce76104SFrank A Kingswood 	dbg("ch341_control_out(%02x,%02x,%04x,%04x)", USB_DIR_OUT|0x40,
46*6ce76104SFrank A Kingswood 		(int)request, (int)value, (int)index);
47*6ce76104SFrank A Kingswood 
48*6ce76104SFrank A Kingswood 	r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
49*6ce76104SFrank A Kingswood 			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
50*6ce76104SFrank A Kingswood 			    value, index, NULL, 0, DEFAULT_TIMEOUT);
51*6ce76104SFrank A Kingswood 
52*6ce76104SFrank A Kingswood 	return r;
53*6ce76104SFrank A Kingswood }
54*6ce76104SFrank A Kingswood 
55*6ce76104SFrank A Kingswood static int ch341_control_in(struct usb_device *dev,
56*6ce76104SFrank A Kingswood 			    u8 request, u16 value, u16 index,
57*6ce76104SFrank A Kingswood 			    char *buf, unsigned bufsize)
58*6ce76104SFrank A Kingswood {
59*6ce76104SFrank A Kingswood 	int r;
60*6ce76104SFrank A Kingswood 	dbg("ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)", USB_DIR_IN|0x40,
61*6ce76104SFrank A Kingswood 		(int)request, (int)value, (int)index, buf, (int)bufsize);
62*6ce76104SFrank A Kingswood 
63*6ce76104SFrank A Kingswood 	r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
64*6ce76104SFrank A Kingswood 			    USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
65*6ce76104SFrank A Kingswood 			    value, index, buf, bufsize, DEFAULT_TIMEOUT);
66*6ce76104SFrank A Kingswood 	return r;
67*6ce76104SFrank A Kingswood }
68*6ce76104SFrank A Kingswood 
69*6ce76104SFrank A Kingswood int ch341_set_baudrate(struct usb_device *dev, struct ch341_private *priv)
70*6ce76104SFrank A Kingswood {
71*6ce76104SFrank A Kingswood 	short a, b;
72*6ce76104SFrank A Kingswood 	int r;
73*6ce76104SFrank A Kingswood 
74*6ce76104SFrank A Kingswood 	dbg("ch341_set_baudrate(%d)", priv->baud_rate);
75*6ce76104SFrank A Kingswood 	switch (priv->baud_rate) {
76*6ce76104SFrank A Kingswood 	case 2400:
77*6ce76104SFrank A Kingswood 		a = 0xd901;
78*6ce76104SFrank A Kingswood 		b = 0x0038;
79*6ce76104SFrank A Kingswood 		break;
80*6ce76104SFrank A Kingswood 	case 4800:
81*6ce76104SFrank A Kingswood 		a = 0x6402;
82*6ce76104SFrank A Kingswood 		b = 0x001f;
83*6ce76104SFrank A Kingswood 		break;
84*6ce76104SFrank A Kingswood 	case 9600:
85*6ce76104SFrank A Kingswood 		a = 0xb202;
86*6ce76104SFrank A Kingswood 		b = 0x0013;
87*6ce76104SFrank A Kingswood 		break;
88*6ce76104SFrank A Kingswood 	case 19200:
89*6ce76104SFrank A Kingswood 		a = 0xd902;
90*6ce76104SFrank A Kingswood 		b = 0x000d;
91*6ce76104SFrank A Kingswood 		break;
92*6ce76104SFrank A Kingswood 	case 38400:
93*6ce76104SFrank A Kingswood 		a = 0x6403;
94*6ce76104SFrank A Kingswood 		b = 0x000a;
95*6ce76104SFrank A Kingswood 		break;
96*6ce76104SFrank A Kingswood 	case 115200:
97*6ce76104SFrank A Kingswood 		a = 0xcc03;
98*6ce76104SFrank A Kingswood 		b = 0x0008;
99*6ce76104SFrank A Kingswood 		break;
100*6ce76104SFrank A Kingswood 	default:
101*6ce76104SFrank A Kingswood 		return -EINVAL;
102*6ce76104SFrank A Kingswood 	}
103*6ce76104SFrank A Kingswood 
104*6ce76104SFrank A Kingswood 	r = ch341_control_out(dev, 0x9a, 0x1312, a);
105*6ce76104SFrank A Kingswood 	if (!r)
106*6ce76104SFrank A Kingswood 		r = ch341_control_out(dev, 0x9a, 0x0f2c, b);
107*6ce76104SFrank A Kingswood 
108*6ce76104SFrank A Kingswood 	return r;
109*6ce76104SFrank A Kingswood }
110*6ce76104SFrank A Kingswood 
111*6ce76104SFrank A Kingswood int ch341_set_handshake(struct usb_device *dev, struct ch341_private *priv)
112*6ce76104SFrank A Kingswood {
113*6ce76104SFrank A Kingswood 	dbg("ch341_set_handshake(%d,%d)", priv->dtr, priv->rts);
114*6ce76104SFrank A Kingswood 	return ch341_control_out(dev, 0xa4,
115*6ce76104SFrank A Kingswood 		~((priv->dtr?1<<5:0)|(priv->rts?1<<6:0)), 0);
116*6ce76104SFrank A Kingswood }
117*6ce76104SFrank A Kingswood 
118*6ce76104SFrank A Kingswood int ch341_get_status(struct usb_device *dev)
119*6ce76104SFrank A Kingswood {
120*6ce76104SFrank A Kingswood 	char *buffer;
121*6ce76104SFrank A Kingswood 	int r;
122*6ce76104SFrank A Kingswood 	const unsigned size = 8;
123*6ce76104SFrank A Kingswood 
124*6ce76104SFrank A Kingswood 	dbg("ch341_get_status()");
125*6ce76104SFrank A Kingswood 
126*6ce76104SFrank A Kingswood 	buffer = kmalloc(size, GFP_KERNEL);
127*6ce76104SFrank A Kingswood 	if (!buffer)
128*6ce76104SFrank A Kingswood 		return -ENOMEM;
129*6ce76104SFrank A Kingswood 
130*6ce76104SFrank A Kingswood 	r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size);
131*6ce76104SFrank A Kingswood 	if ( r < 0)
132*6ce76104SFrank A Kingswood 		goto out;
133*6ce76104SFrank A Kingswood 
134*6ce76104SFrank A Kingswood 	/* Not having the datasheet for the CH341, we ignore the bytes returned
135*6ce76104SFrank A Kingswood 	 * from the device. Return error if the device did not respond in time.
136*6ce76104SFrank A Kingswood 	 */
137*6ce76104SFrank A Kingswood 	r = 0;
138*6ce76104SFrank A Kingswood 
139*6ce76104SFrank A Kingswood out:	kfree(buffer);
140*6ce76104SFrank A Kingswood 	return r;
141*6ce76104SFrank A Kingswood }
142*6ce76104SFrank A Kingswood 
143*6ce76104SFrank A Kingswood /* -------------------------------------------------------------------------- */
144*6ce76104SFrank A Kingswood 
145*6ce76104SFrank A Kingswood int ch341_configure(struct usb_device *dev, struct ch341_private *priv)
146*6ce76104SFrank A Kingswood {
147*6ce76104SFrank A Kingswood 	char *buffer;
148*6ce76104SFrank A Kingswood 	int r;
149*6ce76104SFrank A Kingswood 	const unsigned size = 8;
150*6ce76104SFrank A Kingswood 
151*6ce76104SFrank A Kingswood 	dbg("ch341_configure()");
152*6ce76104SFrank A Kingswood 
153*6ce76104SFrank A Kingswood 	buffer = kmalloc(size, GFP_KERNEL);
154*6ce76104SFrank A Kingswood 	if (!buffer)
155*6ce76104SFrank A Kingswood 		return -ENOMEM;
156*6ce76104SFrank A Kingswood 
157*6ce76104SFrank A Kingswood 	/* expect two bytes 0x27 0x00 */
158*6ce76104SFrank A Kingswood 	r = ch341_control_in(dev, 0x5f, 0, 0, buffer, size);
159*6ce76104SFrank A Kingswood 	if (r < 0)
160*6ce76104SFrank A Kingswood 		goto out;
161*6ce76104SFrank A Kingswood 
162*6ce76104SFrank A Kingswood 	r = ch341_control_out(dev, 0xa1, 0, 0);
163*6ce76104SFrank A Kingswood 	if (r < 0)
164*6ce76104SFrank A Kingswood 		goto out;
165*6ce76104SFrank A Kingswood 
166*6ce76104SFrank A Kingswood 	r = ch341_set_baudrate(dev, priv);
167*6ce76104SFrank A Kingswood 	if (r < 0)
168*6ce76104SFrank A Kingswood 		goto out;
169*6ce76104SFrank A Kingswood 
170*6ce76104SFrank A Kingswood 	/* expect two bytes 0x56 0x00 */
171*6ce76104SFrank A Kingswood 	r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size);
172*6ce76104SFrank A Kingswood 	if (r < 0)
173*6ce76104SFrank A Kingswood 		goto out;
174*6ce76104SFrank A Kingswood 
175*6ce76104SFrank A Kingswood 	r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050);
176*6ce76104SFrank A Kingswood 	if (r < 0)
177*6ce76104SFrank A Kingswood 		goto out;
178*6ce76104SFrank A Kingswood 
179*6ce76104SFrank A Kingswood 	/* expect 0xff 0xee */
180*6ce76104SFrank A Kingswood 	r = ch341_get_status(dev);
181*6ce76104SFrank A Kingswood 	if (r < 0)
182*6ce76104SFrank A Kingswood 		goto out;
183*6ce76104SFrank A Kingswood 
184*6ce76104SFrank A Kingswood 	r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a);
185*6ce76104SFrank A Kingswood 	if (r < 0)
186*6ce76104SFrank A Kingswood 		goto out;
187*6ce76104SFrank A Kingswood 
188*6ce76104SFrank A Kingswood 	r = ch341_set_baudrate(dev, priv);
189*6ce76104SFrank A Kingswood 	if (r < 0)
190*6ce76104SFrank A Kingswood 		goto out;
191*6ce76104SFrank A Kingswood 
192*6ce76104SFrank A Kingswood 	r = ch341_set_handshake(dev, priv);
193*6ce76104SFrank A Kingswood 	if (r < 0)
194*6ce76104SFrank A Kingswood 		goto out;
195*6ce76104SFrank A Kingswood 
196*6ce76104SFrank A Kingswood 	/* expect 0x9f 0xee */
197*6ce76104SFrank A Kingswood 	r = ch341_get_status(dev);
198*6ce76104SFrank A Kingswood 
199*6ce76104SFrank A Kingswood out:	kfree(buffer);
200*6ce76104SFrank A Kingswood 	return r;
201*6ce76104SFrank A Kingswood }
202*6ce76104SFrank A Kingswood 
203*6ce76104SFrank A Kingswood /* allocate private data */
204*6ce76104SFrank A Kingswood static int ch341_attach(struct usb_serial *serial)
205*6ce76104SFrank A Kingswood {
206*6ce76104SFrank A Kingswood 	struct ch341_private *priv;
207*6ce76104SFrank A Kingswood 	int r;
208*6ce76104SFrank A Kingswood 
209*6ce76104SFrank A Kingswood 	dbg("ch341_attach()");
210*6ce76104SFrank A Kingswood 
211*6ce76104SFrank A Kingswood 	/* private data */
212*6ce76104SFrank A Kingswood 	priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL);
213*6ce76104SFrank A Kingswood 	if (!priv)
214*6ce76104SFrank A Kingswood 		return -ENOMEM;
215*6ce76104SFrank A Kingswood 
216*6ce76104SFrank A Kingswood 	priv->baud_rate = DEFAULT_BAUD_RATE;
217*6ce76104SFrank A Kingswood 	priv->dtr = 1;
218*6ce76104SFrank A Kingswood 	priv->rts = 1;
219*6ce76104SFrank A Kingswood 
220*6ce76104SFrank A Kingswood 	r = ch341_configure(serial->dev, priv);
221*6ce76104SFrank A Kingswood 	if (r < 0)
222*6ce76104SFrank A Kingswood 		goto error;
223*6ce76104SFrank A Kingswood 
224*6ce76104SFrank A Kingswood 	usb_set_serial_port_data(serial->port[0], priv);
225*6ce76104SFrank A Kingswood 	return 0;
226*6ce76104SFrank A Kingswood 
227*6ce76104SFrank A Kingswood error:	kfree(priv);
228*6ce76104SFrank A Kingswood 	return r;
229*6ce76104SFrank A Kingswood }
230*6ce76104SFrank A Kingswood 
231*6ce76104SFrank A Kingswood /* open this device, set default parameters */
232*6ce76104SFrank A Kingswood static int ch341_open(struct usb_serial_port *port, struct file *filp)
233*6ce76104SFrank A Kingswood {
234*6ce76104SFrank A Kingswood 	struct usb_serial *serial = port->serial;
235*6ce76104SFrank A Kingswood 	struct ch341_private *priv = usb_get_serial_port_data(serial->port[0]);
236*6ce76104SFrank A Kingswood 	int r;
237*6ce76104SFrank A Kingswood 
238*6ce76104SFrank A Kingswood 	dbg("ch341_open()");
239*6ce76104SFrank A Kingswood 
240*6ce76104SFrank A Kingswood 	priv->baud_rate = DEFAULT_BAUD_RATE;
241*6ce76104SFrank A Kingswood 	priv->dtr = 1;
242*6ce76104SFrank A Kingswood 	priv->rts = 1;
243*6ce76104SFrank A Kingswood 
244*6ce76104SFrank A Kingswood 	r = ch341_configure(serial->dev, priv);
245*6ce76104SFrank A Kingswood 	if (r)
246*6ce76104SFrank A Kingswood 		goto out;
247*6ce76104SFrank A Kingswood 
248*6ce76104SFrank A Kingswood 	r = ch341_set_handshake(serial->dev, priv);
249*6ce76104SFrank A Kingswood 	if (r)
250*6ce76104SFrank A Kingswood 		goto out;
251*6ce76104SFrank A Kingswood 
252*6ce76104SFrank A Kingswood 	r = ch341_set_baudrate(serial->dev, priv);
253*6ce76104SFrank A Kingswood 	if (r)
254*6ce76104SFrank A Kingswood 		goto out;
255*6ce76104SFrank A Kingswood 
256*6ce76104SFrank A Kingswood 	r = usb_serial_generic_open(port, filp);
257*6ce76104SFrank A Kingswood 
258*6ce76104SFrank A Kingswood out:	return r;
259*6ce76104SFrank A Kingswood }
260*6ce76104SFrank A Kingswood 
261*6ce76104SFrank A Kingswood /* Old_termios contains the original termios settings and
262*6ce76104SFrank A Kingswood  * tty->termios contains the new setting to be used.
263*6ce76104SFrank A Kingswood  */
264*6ce76104SFrank A Kingswood static void ch341_set_termios(struct usb_serial_port *port,
265*6ce76104SFrank A Kingswood 			      struct ktermios *old_termios)
266*6ce76104SFrank A Kingswood {
267*6ce76104SFrank A Kingswood 	struct ch341_private *priv = usb_get_serial_port_data(port);
268*6ce76104SFrank A Kingswood 	struct tty_struct *tty = port->tty;
269*6ce76104SFrank A Kingswood 	unsigned baud_rate;
270*6ce76104SFrank A Kingswood 
271*6ce76104SFrank A Kingswood 	dbg("ch341_set_termios()");
272*6ce76104SFrank A Kingswood 
273*6ce76104SFrank A Kingswood 	if (!tty || !tty->termios)
274*6ce76104SFrank A Kingswood 		return;
275*6ce76104SFrank A Kingswood 
276*6ce76104SFrank A Kingswood 	baud_rate = tty_get_baud_rate(tty);
277*6ce76104SFrank A Kingswood 
278*6ce76104SFrank A Kingswood 	switch (baud_rate) {
279*6ce76104SFrank A Kingswood 	case 2400:
280*6ce76104SFrank A Kingswood 	case 4800:
281*6ce76104SFrank A Kingswood 	case 9600:
282*6ce76104SFrank A Kingswood 	case 19200:
283*6ce76104SFrank A Kingswood 	case 38400:
284*6ce76104SFrank A Kingswood 	case 115200:
285*6ce76104SFrank A Kingswood 		priv->baud_rate = baud_rate;
286*6ce76104SFrank A Kingswood 		break;
287*6ce76104SFrank A Kingswood 	default:
288*6ce76104SFrank A Kingswood 		dbg("Rate %d not supported, using %d",
289*6ce76104SFrank A Kingswood 			baud_rate, DEFAULT_BAUD_RATE);
290*6ce76104SFrank A Kingswood 		priv->baud_rate = DEFAULT_BAUD_RATE;
291*6ce76104SFrank A Kingswood 	}
292*6ce76104SFrank A Kingswood 
293*6ce76104SFrank A Kingswood 	ch341_set_baudrate(port->serial->dev, priv);
294*6ce76104SFrank A Kingswood 
295*6ce76104SFrank A Kingswood 	/* Unimplemented:
296*6ce76104SFrank A Kingswood 	 * (cflag & CSIZE) : data bits [5, 8]
297*6ce76104SFrank A Kingswood 	 * (cflag & PARENB) : parity {NONE, EVEN, ODD}
298*6ce76104SFrank A Kingswood 	 * (cflag & CSTOPB) : stop bits [1, 2]
299*6ce76104SFrank A Kingswood 	 */
300*6ce76104SFrank A Kingswood }
301*6ce76104SFrank A Kingswood 
302*6ce76104SFrank A Kingswood static struct usb_driver ch341_driver = {
303*6ce76104SFrank A Kingswood 	.name		= "ch341",
304*6ce76104SFrank A Kingswood 	.probe		= usb_serial_probe,
305*6ce76104SFrank A Kingswood 	.disconnect	= usb_serial_disconnect,
306*6ce76104SFrank A Kingswood 	.id_table	= id_table,
307*6ce76104SFrank A Kingswood 	.no_dynamic_id	= 1,
308*6ce76104SFrank A Kingswood };
309*6ce76104SFrank A Kingswood 
310*6ce76104SFrank A Kingswood static struct usb_serial_driver ch341_device = {
311*6ce76104SFrank A Kingswood 	.driver = {
312*6ce76104SFrank A Kingswood 		.owner	= THIS_MODULE,
313*6ce76104SFrank A Kingswood 		.name	= "ch341-uart",
314*6ce76104SFrank A Kingswood 	},
315*6ce76104SFrank A Kingswood 	.id_table         = id_table,
316*6ce76104SFrank A Kingswood 	.usb_driver       = &ch341_driver,
317*6ce76104SFrank A Kingswood 	.num_interrupt_in = NUM_DONT_CARE,
318*6ce76104SFrank A Kingswood 	.num_bulk_in      = 1,
319*6ce76104SFrank A Kingswood 	.num_bulk_out     = 1,
320*6ce76104SFrank A Kingswood 	.num_ports        = 1,
321*6ce76104SFrank A Kingswood 	.open             = ch341_open,
322*6ce76104SFrank A Kingswood 	.set_termios      = ch341_set_termios,
323*6ce76104SFrank A Kingswood 	.attach           = ch341_attach,
324*6ce76104SFrank A Kingswood };
325*6ce76104SFrank A Kingswood 
326*6ce76104SFrank A Kingswood static int __init ch341_init(void)
327*6ce76104SFrank A Kingswood {
328*6ce76104SFrank A Kingswood 	int retval;
329*6ce76104SFrank A Kingswood 
330*6ce76104SFrank A Kingswood 	retval = usb_serial_register(&ch341_device);
331*6ce76104SFrank A Kingswood 	if (retval)
332*6ce76104SFrank A Kingswood 		return retval;
333*6ce76104SFrank A Kingswood 	retval = usb_register(&ch341_driver);
334*6ce76104SFrank A Kingswood 	if (retval)
335*6ce76104SFrank A Kingswood 		usb_serial_deregister(&ch341_device);
336*6ce76104SFrank A Kingswood 	return retval;
337*6ce76104SFrank A Kingswood }
338*6ce76104SFrank A Kingswood 
339*6ce76104SFrank A Kingswood static void __exit ch341_exit(void)
340*6ce76104SFrank A Kingswood {
341*6ce76104SFrank A Kingswood 	usb_deregister(&ch341_driver);
342*6ce76104SFrank A Kingswood 	usb_serial_deregister(&ch341_device);
343*6ce76104SFrank A Kingswood }
344*6ce76104SFrank A Kingswood 
345*6ce76104SFrank A Kingswood module_init(ch341_init);
346*6ce76104SFrank A Kingswood module_exit(ch341_exit);
347*6ce76104SFrank A Kingswood MODULE_LICENSE("GPL");
348*6ce76104SFrank A Kingswood 
349*6ce76104SFrank A Kingswood module_param(debug, bool, S_IRUGO | S_IWUSR);
350*6ce76104SFrank A Kingswood MODULE_PARM_DESC(debug, "Debug enabled or not");
351*6ce76104SFrank A Kingswood 
352*6ce76104SFrank A Kingswood /* EOF ch341.c */
353