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