1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * KLSI KL5KUSB105 chip RS232 converter driver |
4 | * |
5 | * Copyright (C) 2010 Johan Hovold <jhovold@gmail.com> |
6 | * Copyright (C) 2001 Utz-Uwe Haus <haus@uuhaus.de> |
7 | * |
8 | * All information about the device was acquired using SniffUSB ans snoopUSB |
9 | * on Windows98. |
10 | * It was written out of frustration with the PalmConnect USB Serial adapter |
11 | * sold by Palm Inc. |
12 | * Neither Palm, nor their contractor (MCCI) or their supplier (KLSI) provided |
13 | * information that was not already available. |
14 | * |
15 | * It seems that KLSI bought some silicon-design information from ScanLogic, |
16 | * whose SL11R processor is at the core of the KL5KUSB chipset from KLSI. |
17 | * KLSI has firmware available for their devices; it is probable that the |
18 | * firmware differs from that used by KLSI in their products. If you have an |
19 | * original KLSI device and can provide some information on it, I would be |
20 | * most interested in adding support for it here. If you have any information |
21 | * on the protocol used (or find errors in my reverse-engineered stuff), please |
22 | * let me know. |
23 | * |
24 | * The code was only tested with a PalmConnect USB adapter; if you |
25 | * are adventurous, try it with any KLSI-based device and let me know how it |
26 | * breaks so that I can fix it! |
27 | */ |
28 | |
29 | /* TODO: |
30 | * check modem line signals |
31 | * implement handshaking or decide that we do not support it |
32 | */ |
33 | |
34 | #include <linux/kernel.h> |
35 | #include <linux/errno.h> |
36 | #include <linux/slab.h> |
37 | #include <linux/tty.h> |
38 | #include <linux/tty_driver.h> |
39 | #include <linux/tty_flip.h> |
40 | #include <linux/module.h> |
41 | #include <linux/uaccess.h> |
42 | #include <asm/unaligned.h> |
43 | #include <linux/usb.h> |
44 | #include <linux/usb/serial.h> |
45 | #include "kl5kusb105.h" |
46 | |
47 | #define DRIVER_AUTHOR "Utz-Uwe Haus <haus@uuhaus.de>, Johan Hovold <jhovold@gmail.com>" |
48 | #define DRIVER_DESC "KLSI KL5KUSB105 chipset USB->Serial Converter driver" |
49 | |
50 | |
51 | /* |
52 | * Function prototypes |
53 | */ |
54 | static int klsi_105_port_probe(struct usb_serial_port *port); |
55 | static void klsi_105_port_remove(struct usb_serial_port *port); |
56 | static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port); |
57 | static void klsi_105_close(struct usb_serial_port *port); |
58 | static void klsi_105_set_termios(struct tty_struct *tty, |
59 | struct usb_serial_port *port, |
60 | const struct ktermios *old_termios); |
61 | static int klsi_105_tiocmget(struct tty_struct *tty); |
62 | static void klsi_105_process_read_urb(struct urb *urb); |
63 | static int klsi_105_prepare_write_buffer(struct usb_serial_port *port, |
64 | void *dest, size_t size); |
65 | |
66 | /* |
67 | * All of the device info needed for the KLSI converters. |
68 | */ |
69 | static const struct usb_device_id id_table[] = { |
70 | { USB_DEVICE(PALMCONNECT_VID, PALMCONNECT_PID) }, |
71 | { } /* Terminating entry */ |
72 | }; |
73 | |
74 | MODULE_DEVICE_TABLE(usb, id_table); |
75 | |
76 | static struct usb_serial_driver kl5kusb105d_device = { |
77 | .driver = { |
78 | .owner = THIS_MODULE, |
79 | .name = "kl5kusb105d" , |
80 | }, |
81 | .description = "KL5KUSB105D / PalmConnect" , |
82 | .id_table = id_table, |
83 | .num_ports = 1, |
84 | .bulk_out_size = 64, |
85 | .open = klsi_105_open, |
86 | .close = klsi_105_close, |
87 | .set_termios = klsi_105_set_termios, |
88 | .tiocmget = klsi_105_tiocmget, |
89 | .port_probe = klsi_105_port_probe, |
90 | .port_remove = klsi_105_port_remove, |
91 | .throttle = usb_serial_generic_throttle, |
92 | .unthrottle = usb_serial_generic_unthrottle, |
93 | .process_read_urb = klsi_105_process_read_urb, |
94 | .prepare_write_buffer = klsi_105_prepare_write_buffer, |
95 | }; |
96 | |
97 | static struct usb_serial_driver * const serial_drivers[] = { |
98 | &kl5kusb105d_device, NULL |
99 | }; |
100 | |
101 | struct klsi_105_port_settings { |
102 | u8 pktlen; /* always 5, it seems */ |
103 | u8 baudrate; |
104 | u8 databits; |
105 | u8 unknown1; |
106 | u8 unknown2; |
107 | }; |
108 | |
109 | struct klsi_105_private { |
110 | struct klsi_105_port_settings cfg; |
111 | unsigned long line_state; /* modem line settings */ |
112 | spinlock_t lock; |
113 | }; |
114 | |
115 | |
116 | /* |
117 | * Handle vendor specific USB requests |
118 | */ |
119 | |
120 | |
121 | #define KLSI_TIMEOUT 5000 /* default urb timeout */ |
122 | |
123 | static int klsi_105_chg_port_settings(struct usb_serial_port *port, |
124 | struct klsi_105_port_settings *settings) |
125 | { |
126 | int rc; |
127 | |
128 | rc = usb_control_msg_send(dev: port->serial->dev, |
129 | endpoint: 0, |
130 | KL5KUSB105A_SIO_SET_DATA, |
131 | USB_TYPE_VENDOR | USB_DIR_OUT | |
132 | USB_RECIP_INTERFACE, |
133 | value: 0, /* value */ |
134 | index: 0, /* index */ |
135 | data: settings, |
136 | size: sizeof(struct klsi_105_port_settings), |
137 | KLSI_TIMEOUT, |
138 | GFP_KERNEL); |
139 | if (rc) |
140 | dev_err(&port->dev, |
141 | "Change port settings failed (error = %d)\n" , rc); |
142 | |
143 | dev_dbg(&port->dev, |
144 | "pktlen %u, baudrate 0x%02x, databits %u, u1 %u, u2 %u\n" , |
145 | settings->pktlen, settings->baudrate, settings->databits, |
146 | settings->unknown1, settings->unknown2); |
147 | |
148 | return rc; |
149 | } |
150 | |
151 | /* |
152 | * Read line control via vendor command and return result through |
153 | * the state pointer. |
154 | */ |
155 | static int klsi_105_get_line_state(struct usb_serial_port *port, |
156 | unsigned long *state) |
157 | { |
158 | u16 status; |
159 | int rc; |
160 | |
161 | rc = usb_control_msg_recv(dev: port->serial->dev, endpoint: 0, |
162 | KL5KUSB105A_SIO_POLL, |
163 | USB_TYPE_VENDOR | USB_DIR_IN, |
164 | value: 0, /* value */ |
165 | index: 0, /* index */ |
166 | data: &status, size: sizeof(status), |
167 | timeout: 10000, |
168 | GFP_KERNEL); |
169 | if (rc) { |
170 | dev_err(&port->dev, "reading line status failed: %d\n" , rc); |
171 | return rc; |
172 | } |
173 | |
174 | le16_to_cpus(&status); |
175 | |
176 | dev_dbg(&port->dev, "read status %04x\n" , status); |
177 | |
178 | *state = ((status & KL5KUSB105A_DSR) ? TIOCM_DSR : 0) | |
179 | ((status & KL5KUSB105A_CTS) ? TIOCM_CTS : 0); |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | |
185 | /* |
186 | * Driver's tty interface functions |
187 | */ |
188 | |
189 | static int klsi_105_port_probe(struct usb_serial_port *port) |
190 | { |
191 | struct klsi_105_private *priv; |
192 | |
193 | priv = kmalloc(size: sizeof(*priv), GFP_KERNEL); |
194 | if (!priv) |
195 | return -ENOMEM; |
196 | |
197 | /* set initial values for control structures */ |
198 | priv->cfg.pktlen = 5; |
199 | priv->cfg.baudrate = kl5kusb105a_sio_b9600; |
200 | priv->cfg.databits = kl5kusb105a_dtb_8; |
201 | priv->cfg.unknown1 = 0; |
202 | priv->cfg.unknown2 = 1; |
203 | |
204 | priv->line_state = 0; |
205 | |
206 | spin_lock_init(&priv->lock); |
207 | |
208 | usb_set_serial_port_data(port, data: priv); |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static void klsi_105_port_remove(struct usb_serial_port *port) |
214 | { |
215 | struct klsi_105_private *priv; |
216 | |
217 | priv = usb_get_serial_port_data(port); |
218 | kfree(objp: priv); |
219 | } |
220 | |
221 | static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port) |
222 | { |
223 | struct klsi_105_private *priv = usb_get_serial_port_data(port); |
224 | int retval = 0; |
225 | int rc; |
226 | unsigned long line_state; |
227 | struct klsi_105_port_settings cfg; |
228 | unsigned long flags; |
229 | |
230 | /* Do a defined restart: |
231 | * Set up sane default baud rate and send the 'READ_ON' |
232 | * vendor command. |
233 | * FIXME: set modem line control (how?) |
234 | * Then read the modem line control and store values in |
235 | * priv->line_state. |
236 | */ |
237 | |
238 | cfg.pktlen = 5; |
239 | cfg.baudrate = kl5kusb105a_sio_b9600; |
240 | cfg.databits = kl5kusb105a_dtb_8; |
241 | cfg.unknown1 = 0; |
242 | cfg.unknown2 = 1; |
243 | klsi_105_chg_port_settings(port, settings: &cfg); |
244 | |
245 | spin_lock_irqsave(&priv->lock, flags); |
246 | priv->cfg.pktlen = cfg.pktlen; |
247 | priv->cfg.baudrate = cfg.baudrate; |
248 | priv->cfg.databits = cfg.databits; |
249 | priv->cfg.unknown1 = cfg.unknown1; |
250 | priv->cfg.unknown2 = cfg.unknown2; |
251 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
252 | |
253 | /* READ_ON and urb submission */ |
254 | rc = usb_serial_generic_open(tty, port); |
255 | if (rc) |
256 | return rc; |
257 | |
258 | rc = usb_control_msg(dev: port->serial->dev, |
259 | usb_sndctrlpipe(port->serial->dev, 0), |
260 | KL5KUSB105A_SIO_CONFIGURE, |
261 | USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE, |
262 | KL5KUSB105A_SIO_CONFIGURE_READ_ON, |
263 | index: 0, /* index */ |
264 | NULL, |
265 | size: 0, |
266 | KLSI_TIMEOUT); |
267 | if (rc < 0) { |
268 | dev_err(&port->dev, "Enabling read failed (error = %d)\n" , rc); |
269 | retval = rc; |
270 | goto err_generic_close; |
271 | } else |
272 | dev_dbg(&port->dev, "%s - enabled reading\n" , __func__); |
273 | |
274 | rc = klsi_105_get_line_state(port, state: &line_state); |
275 | if (rc < 0) { |
276 | retval = rc; |
277 | goto err_disable_read; |
278 | } |
279 | |
280 | spin_lock_irqsave(&priv->lock, flags); |
281 | priv->line_state = line_state; |
282 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
283 | dev_dbg(&port->dev, "%s - read line state 0x%lx\n" , __func__, |
284 | line_state); |
285 | |
286 | return 0; |
287 | |
288 | err_disable_read: |
289 | usb_control_msg(dev: port->serial->dev, |
290 | usb_sndctrlpipe(port->serial->dev, 0), |
291 | KL5KUSB105A_SIO_CONFIGURE, |
292 | USB_TYPE_VENDOR | USB_DIR_OUT, |
293 | KL5KUSB105A_SIO_CONFIGURE_READ_OFF, |
294 | index: 0, /* index */ |
295 | NULL, size: 0, |
296 | KLSI_TIMEOUT); |
297 | err_generic_close: |
298 | usb_serial_generic_close(port); |
299 | |
300 | return retval; |
301 | } |
302 | |
303 | static void klsi_105_close(struct usb_serial_port *port) |
304 | { |
305 | int rc; |
306 | |
307 | /* send READ_OFF */ |
308 | rc = usb_control_msg(dev: port->serial->dev, |
309 | usb_sndctrlpipe(port->serial->dev, 0), |
310 | KL5KUSB105A_SIO_CONFIGURE, |
311 | USB_TYPE_VENDOR | USB_DIR_OUT, |
312 | KL5KUSB105A_SIO_CONFIGURE_READ_OFF, |
313 | index: 0, /* index */ |
314 | NULL, size: 0, |
315 | KLSI_TIMEOUT); |
316 | if (rc < 0) |
317 | dev_err(&port->dev, "failed to disable read: %d\n" , rc); |
318 | |
319 | /* shutdown our bulk reads and writes */ |
320 | usb_serial_generic_close(port); |
321 | } |
322 | |
323 | /* We need to write a complete 64-byte data block and encode the |
324 | * number actually sent in the first double-byte, LSB-order. That |
325 | * leaves at most 62 bytes of payload. |
326 | */ |
327 | #define KLSI_HDR_LEN 2 |
328 | static int klsi_105_prepare_write_buffer(struct usb_serial_port *port, |
329 | void *dest, size_t size) |
330 | { |
331 | unsigned char *buf = dest; |
332 | int count; |
333 | |
334 | count = kfifo_out_locked(&port->write_fifo, buf + KLSI_HDR_LEN, size, |
335 | &port->lock); |
336 | put_unaligned_le16(val: count, p: buf); |
337 | |
338 | return count + KLSI_HDR_LEN; |
339 | } |
340 | |
341 | /* The data received is preceded by a length double-byte in LSB-first order. |
342 | */ |
343 | static void klsi_105_process_read_urb(struct urb *urb) |
344 | { |
345 | struct usb_serial_port *port = urb->context; |
346 | unsigned char *data = urb->transfer_buffer; |
347 | unsigned len; |
348 | |
349 | /* empty urbs seem to happen, we ignore them */ |
350 | if (!urb->actual_length) |
351 | return; |
352 | |
353 | if (urb->actual_length <= KLSI_HDR_LEN) { |
354 | dev_dbg(&port->dev, "%s - malformed packet\n" , __func__); |
355 | return; |
356 | } |
357 | |
358 | len = get_unaligned_le16(p: data); |
359 | if (len > urb->actual_length - KLSI_HDR_LEN) { |
360 | dev_dbg(&port->dev, "%s - packet length mismatch\n" , __func__); |
361 | len = urb->actual_length - KLSI_HDR_LEN; |
362 | } |
363 | |
364 | tty_insert_flip_string(port: &port->port, chars: data + KLSI_HDR_LEN, size: len); |
365 | tty_flip_buffer_push(port: &port->port); |
366 | } |
367 | |
368 | static void klsi_105_set_termios(struct tty_struct *tty, |
369 | struct usb_serial_port *port, |
370 | const struct ktermios *old_termios) |
371 | { |
372 | struct klsi_105_private *priv = usb_get_serial_port_data(port); |
373 | struct device *dev = &port->dev; |
374 | unsigned int iflag = tty->termios.c_iflag; |
375 | unsigned int old_iflag = old_termios->c_iflag; |
376 | unsigned int cflag = tty->termios.c_cflag; |
377 | unsigned int old_cflag = old_termios->c_cflag; |
378 | struct klsi_105_port_settings *cfg; |
379 | unsigned long flags; |
380 | speed_t baud; |
381 | |
382 | cfg = kmalloc(size: sizeof(*cfg), GFP_KERNEL); |
383 | if (!cfg) |
384 | return; |
385 | |
386 | /* lock while we are modifying the settings */ |
387 | spin_lock_irqsave(&priv->lock, flags); |
388 | |
389 | /* |
390 | * Update baud rate |
391 | */ |
392 | baud = tty_get_baud_rate(tty); |
393 | |
394 | switch (baud) { |
395 | case 0: /* handled below */ |
396 | break; |
397 | case 1200: |
398 | priv->cfg.baudrate = kl5kusb105a_sio_b1200; |
399 | break; |
400 | case 2400: |
401 | priv->cfg.baudrate = kl5kusb105a_sio_b2400; |
402 | break; |
403 | case 4800: |
404 | priv->cfg.baudrate = kl5kusb105a_sio_b4800; |
405 | break; |
406 | case 9600: |
407 | priv->cfg.baudrate = kl5kusb105a_sio_b9600; |
408 | break; |
409 | case 19200: |
410 | priv->cfg.baudrate = kl5kusb105a_sio_b19200; |
411 | break; |
412 | case 38400: |
413 | priv->cfg.baudrate = kl5kusb105a_sio_b38400; |
414 | break; |
415 | case 57600: |
416 | priv->cfg.baudrate = kl5kusb105a_sio_b57600; |
417 | break; |
418 | case 115200: |
419 | priv->cfg.baudrate = kl5kusb105a_sio_b115200; |
420 | break; |
421 | default: |
422 | dev_dbg(dev, "unsupported baudrate, using 9600\n" ); |
423 | priv->cfg.baudrate = kl5kusb105a_sio_b9600; |
424 | baud = 9600; |
425 | break; |
426 | } |
427 | |
428 | /* |
429 | * FIXME: implement B0 handling |
430 | * |
431 | * Maybe this should be simulated by sending read disable and read |
432 | * enable messages? |
433 | */ |
434 | |
435 | tty_encode_baud_rate(tty, ibaud: baud, obaud: baud); |
436 | |
437 | if ((cflag & CSIZE) != (old_cflag & CSIZE)) { |
438 | /* set the number of data bits */ |
439 | switch (cflag & CSIZE) { |
440 | case CS5: |
441 | dev_dbg(dev, "%s - 5 bits/byte not supported\n" , __func__); |
442 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
443 | goto err; |
444 | case CS6: |
445 | dev_dbg(dev, "%s - 6 bits/byte not supported\n" , __func__); |
446 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
447 | goto err; |
448 | case CS7: |
449 | priv->cfg.databits = kl5kusb105a_dtb_7; |
450 | break; |
451 | case CS8: |
452 | priv->cfg.databits = kl5kusb105a_dtb_8; |
453 | break; |
454 | default: |
455 | dev_err(dev, "CSIZE was not CS5-CS8, using default of 8\n" ); |
456 | priv->cfg.databits = kl5kusb105a_dtb_8; |
457 | break; |
458 | } |
459 | } |
460 | |
461 | /* |
462 | * Update line control register (LCR) |
463 | */ |
464 | if ((cflag & (PARENB|PARODD)) != (old_cflag & (PARENB|PARODD)) |
465 | || (cflag & CSTOPB) != (old_cflag & CSTOPB)) { |
466 | /* Not currently supported */ |
467 | tty->termios.c_cflag &= ~(PARENB|PARODD|CSTOPB); |
468 | } |
469 | /* |
470 | * Set flow control: well, I do not really now how to handle DTR/RTS. |
471 | * Just do what we have seen with SniffUSB on Win98. |
472 | */ |
473 | if ((iflag & IXOFF) != (old_iflag & IXOFF) |
474 | || (iflag & IXON) != (old_iflag & IXON) |
475 | || (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { |
476 | /* Not currently supported */ |
477 | tty->termios.c_cflag &= ~CRTSCTS; |
478 | } |
479 | memcpy(cfg, &priv->cfg, sizeof(*cfg)); |
480 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
481 | |
482 | /* now commit changes to device */ |
483 | klsi_105_chg_port_settings(port, settings: cfg); |
484 | err: |
485 | kfree(objp: cfg); |
486 | } |
487 | |
488 | static int klsi_105_tiocmget(struct tty_struct *tty) |
489 | { |
490 | struct usb_serial_port *port = tty->driver_data; |
491 | struct klsi_105_private *priv = usb_get_serial_port_data(port); |
492 | unsigned long flags; |
493 | int rc; |
494 | unsigned long line_state; |
495 | |
496 | rc = klsi_105_get_line_state(port, state: &line_state); |
497 | if (rc < 0) { |
498 | dev_err(&port->dev, |
499 | "Reading line control failed (error = %d)\n" , rc); |
500 | /* better return value? EAGAIN? */ |
501 | return rc; |
502 | } |
503 | |
504 | spin_lock_irqsave(&priv->lock, flags); |
505 | priv->line_state = line_state; |
506 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
507 | dev_dbg(&port->dev, "%s - read line state 0x%lx\n" , __func__, line_state); |
508 | return (int)line_state; |
509 | } |
510 | |
511 | module_usb_serial_driver(serial_drivers, id_table); |
512 | |
513 | MODULE_AUTHOR(DRIVER_AUTHOR); |
514 | MODULE_DESCRIPTION(DRIVER_DESC); |
515 | MODULE_LICENSE("GPL" ); |
516 | |