1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * USB ConnectTech WhiteHEAT driver |
4 | * |
5 | * Copyright (C) 2002 |
6 | * Connect Tech Inc. |
7 | * |
8 | * Copyright (C) 1999 - 2001 |
9 | * Greg Kroah-Hartman (greg@kroah.com) |
10 | * |
11 | * See Documentation/usb/usb-serial.rst for more information on using this |
12 | * driver |
13 | */ |
14 | |
15 | #include <linux/kernel.h> |
16 | #include <linux/errno.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/tty.h> |
19 | #include <linux/tty_driver.h> |
20 | #include <linux/tty_flip.h> |
21 | #include <linux/module.h> |
22 | #include <linux/spinlock.h> |
23 | #include <linux/mutex.h> |
24 | #include <linux/uaccess.h> |
25 | #include <asm/termbits.h> |
26 | #include <linux/usb.h> |
27 | #include <linux/serial_reg.h> |
28 | #include <linux/serial.h> |
29 | #include <linux/usb/serial.h> |
30 | #include <linux/usb/ezusb.h> |
31 | #include "whiteheat.h" /* WhiteHEAT specific commands */ |
32 | |
33 | /* |
34 | * Version Information |
35 | */ |
36 | #define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Stuart MacDonald <stuartm@connecttech.com>" |
37 | #define DRIVER_DESC "USB ConnectTech WhiteHEAT driver" |
38 | |
39 | #define CONNECT_TECH_VENDOR_ID 0x0710 |
40 | #define CONNECT_TECH_FAKE_WHITE_HEAT_ID 0x0001 |
41 | #define CONNECT_TECH_WHITE_HEAT_ID 0x8001 |
42 | |
43 | /* |
44 | ID tables for whiteheat are unusual, because we want to different |
45 | things for different versions of the device. Eventually, this |
46 | will be doable from a single table. But, for now, we define two |
47 | separate ID tables, and then a third table that combines them |
48 | just for the purpose of exporting the autoloading information. |
49 | */ |
50 | static const struct usb_device_id id_table_std[] = { |
51 | { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, |
52 | { } /* Terminating entry */ |
53 | }; |
54 | |
55 | static const struct usb_device_id id_table_prerenumeration[] = { |
56 | { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, |
57 | { } /* Terminating entry */ |
58 | }; |
59 | |
60 | static const struct usb_device_id id_table_combined[] = { |
61 | { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, |
62 | { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, |
63 | { } /* Terminating entry */ |
64 | }; |
65 | |
66 | MODULE_DEVICE_TABLE(usb, id_table_combined); |
67 | |
68 | |
69 | /* function prototypes for the Connect Tech WhiteHEAT prerenumeration device */ |
70 | static int whiteheat_firmware_download(struct usb_serial *serial, |
71 | const struct usb_device_id *id); |
72 | static int whiteheat_firmware_attach(struct usb_serial *serial); |
73 | |
74 | /* function prototypes for the Connect Tech WhiteHEAT serial converter */ |
75 | static int whiteheat_attach(struct usb_serial *serial); |
76 | static void whiteheat_release(struct usb_serial *serial); |
77 | static int whiteheat_port_probe(struct usb_serial_port *port); |
78 | static void whiteheat_port_remove(struct usb_serial_port *port); |
79 | static int whiteheat_open(struct tty_struct *tty, |
80 | struct usb_serial_port *port); |
81 | static void whiteheat_close(struct usb_serial_port *port); |
82 | static void whiteheat_get_serial(struct tty_struct *tty, |
83 | struct serial_struct *ss); |
84 | static void whiteheat_set_termios(struct tty_struct *tty, |
85 | struct usb_serial_port *port, |
86 | const struct ktermios *old_termios); |
87 | static int whiteheat_tiocmget(struct tty_struct *tty); |
88 | static int whiteheat_tiocmset(struct tty_struct *tty, |
89 | unsigned int set, unsigned int clear); |
90 | static int whiteheat_break_ctl(struct tty_struct *tty, int break_state); |
91 | |
92 | static struct usb_serial_driver whiteheat_fake_device = { |
93 | .driver = { |
94 | .owner = THIS_MODULE, |
95 | .name = "whiteheatnofirm" , |
96 | }, |
97 | .description = "Connect Tech - WhiteHEAT - (prerenumeration)" , |
98 | .id_table = id_table_prerenumeration, |
99 | .num_ports = 1, |
100 | .probe = whiteheat_firmware_download, |
101 | .attach = whiteheat_firmware_attach, |
102 | }; |
103 | |
104 | static struct usb_serial_driver whiteheat_device = { |
105 | .driver = { |
106 | .owner = THIS_MODULE, |
107 | .name = "whiteheat" , |
108 | }, |
109 | .description = "Connect Tech - WhiteHEAT" , |
110 | .id_table = id_table_std, |
111 | .num_ports = 4, |
112 | .num_bulk_in = 5, |
113 | .num_bulk_out = 5, |
114 | .attach = whiteheat_attach, |
115 | .release = whiteheat_release, |
116 | .port_probe = whiteheat_port_probe, |
117 | .port_remove = whiteheat_port_remove, |
118 | .open = whiteheat_open, |
119 | .close = whiteheat_close, |
120 | .get_serial = whiteheat_get_serial, |
121 | .set_termios = whiteheat_set_termios, |
122 | .break_ctl = whiteheat_break_ctl, |
123 | .tiocmget = whiteheat_tiocmget, |
124 | .tiocmset = whiteheat_tiocmset, |
125 | .throttle = usb_serial_generic_throttle, |
126 | .unthrottle = usb_serial_generic_unthrottle, |
127 | }; |
128 | |
129 | static struct usb_serial_driver * const serial_drivers[] = { |
130 | &whiteheat_fake_device, &whiteheat_device, NULL |
131 | }; |
132 | |
133 | struct whiteheat_command_private { |
134 | struct mutex mutex; |
135 | __u8 port_running; |
136 | __u8 command_finished; |
137 | wait_queue_head_t wait_command; /* for handling sleeping whilst |
138 | waiting for a command to |
139 | finish */ |
140 | __u8 result_buffer[64]; |
141 | }; |
142 | |
143 | struct whiteheat_private { |
144 | __u8 mcr; /* FIXME: no locking on mcr */ |
145 | }; |
146 | |
147 | |
148 | /* local function prototypes */ |
149 | static int start_command_port(struct usb_serial *serial); |
150 | static void stop_command_port(struct usb_serial *serial); |
151 | static void command_port_write_callback(struct urb *urb); |
152 | static void command_port_read_callback(struct urb *urb); |
153 | |
154 | static int firm_send_command(struct usb_serial_port *port, __u8 command, |
155 | __u8 *data, __u8 datasize); |
156 | static int firm_open(struct usb_serial_port *port); |
157 | static int firm_close(struct usb_serial_port *port); |
158 | static void firm_setup_port(struct tty_struct *tty); |
159 | static int firm_set_rts(struct usb_serial_port *port, __u8 onoff); |
160 | static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff); |
161 | static int firm_set_break(struct usb_serial_port *port, __u8 onoff); |
162 | static int firm_purge(struct usb_serial_port *port, __u8 rxtx); |
163 | static int firm_get_dtr_rts(struct usb_serial_port *port); |
164 | static int firm_report_tx_done(struct usb_serial_port *port); |
165 | |
166 | |
167 | #define COMMAND_PORT 4 |
168 | #define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */ |
169 | #define COMMAND_TIMEOUT_MS 2000 |
170 | |
171 | |
172 | /***************************************************************************** |
173 | * Connect Tech's White Heat prerenumeration driver functions |
174 | *****************************************************************************/ |
175 | |
176 | /* steps to download the firmware to the WhiteHEAT device: |
177 | - hold the reset (by writing to the reset bit of the CPUCS register) |
178 | - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD |
179 | - release the reset (by writing to the CPUCS register) |
180 | - download the WH.HEX file for all addresses greater than 0x1b3f using |
181 | VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD |
182 | - hold the reset |
183 | - download the WH.HEX file for all addresses less than 0x1b40 using |
184 | VENDOR_REQUEST_ANCHOR_LOAD |
185 | - release the reset |
186 | - device renumerated itself and comes up as new device id with all |
187 | firmware download completed. |
188 | */ |
189 | static int whiteheat_firmware_download(struct usb_serial *serial, |
190 | const struct usb_device_id *id) |
191 | { |
192 | int response; |
193 | |
194 | response = ezusb_fx1_ihex_firmware_download(dev: serial->dev, firmware_path: "whiteheat_loader.fw" ); |
195 | if (response >= 0) { |
196 | response = ezusb_fx1_ihex_firmware_download(dev: serial->dev, firmware_path: "whiteheat.fw" ); |
197 | if (response >= 0) |
198 | return 0; |
199 | } |
200 | return -ENOENT; |
201 | } |
202 | |
203 | |
204 | static int whiteheat_firmware_attach(struct usb_serial *serial) |
205 | { |
206 | /* We want this device to fail to have a driver assigned to it */ |
207 | return 1; |
208 | } |
209 | |
210 | |
211 | /***************************************************************************** |
212 | * Connect Tech's White Heat serial driver functions |
213 | *****************************************************************************/ |
214 | |
215 | static int whiteheat_attach(struct usb_serial *serial) |
216 | { |
217 | struct usb_serial_port *command_port; |
218 | struct whiteheat_command_private *command_info; |
219 | struct whiteheat_hw_info *hw_info; |
220 | int pipe; |
221 | int ret; |
222 | int alen; |
223 | __u8 *command; |
224 | __u8 *result; |
225 | |
226 | command_port = serial->port[COMMAND_PORT]; |
227 | |
228 | pipe = usb_sndbulkpipe(serial->dev, |
229 | command_port->bulk_out_endpointAddress); |
230 | command = kmalloc(size: 2, GFP_KERNEL); |
231 | if (!command) |
232 | goto no_command_buffer; |
233 | command[0] = WHITEHEAT_GET_HW_INFO; |
234 | command[1] = 0; |
235 | |
236 | result = kmalloc(size: sizeof(*hw_info) + 1, GFP_KERNEL); |
237 | if (!result) |
238 | goto no_result_buffer; |
239 | /* |
240 | * When the module is reloaded the firmware is still there and |
241 | * the endpoints are still in the usb core unchanged. This is the |
242 | * unlinking bug in disguise. Same for the call below. |
243 | */ |
244 | usb_clear_halt(dev: serial->dev, pipe); |
245 | ret = usb_bulk_msg(usb_dev: serial->dev, pipe, data: command, len: 2, |
246 | actual_length: &alen, COMMAND_TIMEOUT_MS); |
247 | if (ret) { |
248 | dev_err(&serial->dev->dev, "%s: Couldn't send command [%d]\n" , |
249 | serial->type->description, ret); |
250 | goto no_firmware; |
251 | } else if (alen != 2) { |
252 | dev_err(&serial->dev->dev, "%s: Send command incomplete [%d]\n" , |
253 | serial->type->description, alen); |
254 | goto no_firmware; |
255 | } |
256 | |
257 | pipe = usb_rcvbulkpipe(serial->dev, |
258 | command_port->bulk_in_endpointAddress); |
259 | /* See the comment on the usb_clear_halt() above */ |
260 | usb_clear_halt(dev: serial->dev, pipe); |
261 | ret = usb_bulk_msg(usb_dev: serial->dev, pipe, data: result, |
262 | len: sizeof(*hw_info) + 1, actual_length: &alen, COMMAND_TIMEOUT_MS); |
263 | if (ret) { |
264 | dev_err(&serial->dev->dev, "%s: Couldn't get results [%d]\n" , |
265 | serial->type->description, ret); |
266 | goto no_firmware; |
267 | } else if (alen != sizeof(*hw_info) + 1) { |
268 | dev_err(&serial->dev->dev, "%s: Get results incomplete [%d]\n" , |
269 | serial->type->description, alen); |
270 | goto no_firmware; |
271 | } else if (result[0] != command[0]) { |
272 | dev_err(&serial->dev->dev, "%s: Command failed [%d]\n" , |
273 | serial->type->description, result[0]); |
274 | goto no_firmware; |
275 | } |
276 | |
277 | hw_info = (struct whiteheat_hw_info *)&result[1]; |
278 | |
279 | dev_info(&serial->dev->dev, "%s: Firmware v%d.%02d\n" , |
280 | serial->type->description, |
281 | hw_info->sw_major_rev, hw_info->sw_minor_rev); |
282 | |
283 | command_info = kmalloc(size: sizeof(struct whiteheat_command_private), |
284 | GFP_KERNEL); |
285 | if (!command_info) |
286 | goto no_command_private; |
287 | |
288 | mutex_init(&command_info->mutex); |
289 | command_info->port_running = 0; |
290 | init_waitqueue_head(&command_info->wait_command); |
291 | usb_set_serial_port_data(port: command_port, data: command_info); |
292 | command_port->write_urb->complete = command_port_write_callback; |
293 | command_port->read_urb->complete = command_port_read_callback; |
294 | kfree(objp: result); |
295 | kfree(objp: command); |
296 | |
297 | return 0; |
298 | |
299 | no_firmware: |
300 | /* Firmware likely not running */ |
301 | dev_err(&serial->dev->dev, |
302 | "%s: Unable to retrieve firmware version, try replugging\n" , |
303 | serial->type->description); |
304 | dev_err(&serial->dev->dev, |
305 | "%s: If the firmware is not running (status led not blinking)\n" , |
306 | serial->type->description); |
307 | dev_err(&serial->dev->dev, |
308 | "%s: please contact support@connecttech.com\n" , |
309 | serial->type->description); |
310 | kfree(objp: result); |
311 | kfree(objp: command); |
312 | return -ENODEV; |
313 | |
314 | no_command_private: |
315 | kfree(objp: result); |
316 | no_result_buffer: |
317 | kfree(objp: command); |
318 | no_command_buffer: |
319 | return -ENOMEM; |
320 | } |
321 | |
322 | static void whiteheat_release(struct usb_serial *serial) |
323 | { |
324 | struct usb_serial_port *command_port; |
325 | |
326 | /* free up our private data for our command port */ |
327 | command_port = serial->port[COMMAND_PORT]; |
328 | kfree(objp: usb_get_serial_port_data(port: command_port)); |
329 | } |
330 | |
331 | static int whiteheat_port_probe(struct usb_serial_port *port) |
332 | { |
333 | struct whiteheat_private *info; |
334 | |
335 | info = kzalloc(size: sizeof(*info), GFP_KERNEL); |
336 | if (!info) |
337 | return -ENOMEM; |
338 | |
339 | usb_set_serial_port_data(port, data: info); |
340 | |
341 | return 0; |
342 | } |
343 | |
344 | static void whiteheat_port_remove(struct usb_serial_port *port) |
345 | { |
346 | struct whiteheat_private *info; |
347 | |
348 | info = usb_get_serial_port_data(port); |
349 | kfree(objp: info); |
350 | } |
351 | |
352 | static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port) |
353 | { |
354 | int retval; |
355 | |
356 | retval = start_command_port(serial: port->serial); |
357 | if (retval) |
358 | goto exit; |
359 | |
360 | /* send an open port command */ |
361 | retval = firm_open(port); |
362 | if (retval) { |
363 | stop_command_port(serial: port->serial); |
364 | goto exit; |
365 | } |
366 | |
367 | retval = firm_purge(port, WHITEHEAT_PURGE_RX | WHITEHEAT_PURGE_TX); |
368 | if (retval) { |
369 | firm_close(port); |
370 | stop_command_port(serial: port->serial); |
371 | goto exit; |
372 | } |
373 | |
374 | if (tty) |
375 | firm_setup_port(tty); |
376 | |
377 | /* Work around HCD bugs */ |
378 | usb_clear_halt(dev: port->serial->dev, pipe: port->read_urb->pipe); |
379 | usb_clear_halt(dev: port->serial->dev, pipe: port->write_urb->pipe); |
380 | |
381 | retval = usb_serial_generic_open(tty, port); |
382 | if (retval) { |
383 | firm_close(port); |
384 | stop_command_port(serial: port->serial); |
385 | goto exit; |
386 | } |
387 | exit: |
388 | return retval; |
389 | } |
390 | |
391 | |
392 | static void whiteheat_close(struct usb_serial_port *port) |
393 | { |
394 | firm_report_tx_done(port); |
395 | firm_close(port); |
396 | |
397 | usb_serial_generic_close(port); |
398 | |
399 | stop_command_port(serial: port->serial); |
400 | } |
401 | |
402 | static int whiteheat_tiocmget(struct tty_struct *tty) |
403 | { |
404 | struct usb_serial_port *port = tty->driver_data; |
405 | struct whiteheat_private *info = usb_get_serial_port_data(port); |
406 | unsigned int modem_signals = 0; |
407 | |
408 | firm_get_dtr_rts(port); |
409 | if (info->mcr & UART_MCR_DTR) |
410 | modem_signals |= TIOCM_DTR; |
411 | if (info->mcr & UART_MCR_RTS) |
412 | modem_signals |= TIOCM_RTS; |
413 | |
414 | return modem_signals; |
415 | } |
416 | |
417 | static int whiteheat_tiocmset(struct tty_struct *tty, |
418 | unsigned int set, unsigned int clear) |
419 | { |
420 | struct usb_serial_port *port = tty->driver_data; |
421 | struct whiteheat_private *info = usb_get_serial_port_data(port); |
422 | |
423 | if (set & TIOCM_RTS) |
424 | info->mcr |= UART_MCR_RTS; |
425 | if (set & TIOCM_DTR) |
426 | info->mcr |= UART_MCR_DTR; |
427 | |
428 | if (clear & TIOCM_RTS) |
429 | info->mcr &= ~UART_MCR_RTS; |
430 | if (clear & TIOCM_DTR) |
431 | info->mcr &= ~UART_MCR_DTR; |
432 | |
433 | firm_set_dtr(port, onoff: info->mcr & UART_MCR_DTR); |
434 | firm_set_rts(port, onoff: info->mcr & UART_MCR_RTS); |
435 | return 0; |
436 | } |
437 | |
438 | |
439 | static void whiteheat_get_serial(struct tty_struct *tty, struct serial_struct *ss) |
440 | { |
441 | ss->baud_base = 460800; |
442 | } |
443 | |
444 | |
445 | static void whiteheat_set_termios(struct tty_struct *tty, |
446 | struct usb_serial_port *port, |
447 | const struct ktermios *old_termios) |
448 | { |
449 | firm_setup_port(tty); |
450 | } |
451 | |
452 | static int whiteheat_break_ctl(struct tty_struct *tty, int break_state) |
453 | { |
454 | struct usb_serial_port *port = tty->driver_data; |
455 | |
456 | return firm_set_break(port, onoff: break_state); |
457 | } |
458 | |
459 | |
460 | /***************************************************************************** |
461 | * Connect Tech's White Heat callback routines |
462 | *****************************************************************************/ |
463 | static void command_port_write_callback(struct urb *urb) |
464 | { |
465 | int status = urb->status; |
466 | |
467 | if (status) { |
468 | dev_dbg(&urb->dev->dev, "nonzero urb status: %d\n" , status); |
469 | return; |
470 | } |
471 | } |
472 | |
473 | |
474 | static void command_port_read_callback(struct urb *urb) |
475 | { |
476 | struct usb_serial_port *command_port = urb->context; |
477 | struct whiteheat_command_private *command_info; |
478 | int status = urb->status; |
479 | unsigned char *data = urb->transfer_buffer; |
480 | int result; |
481 | |
482 | command_info = usb_get_serial_port_data(port: command_port); |
483 | if (!command_info) { |
484 | dev_dbg(&urb->dev->dev, "%s - command_info is NULL, exiting.\n" , __func__); |
485 | return; |
486 | } |
487 | if (!urb->actual_length) { |
488 | dev_dbg(&urb->dev->dev, "%s - empty response, exiting.\n" , __func__); |
489 | return; |
490 | } |
491 | if (status) { |
492 | dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n" , __func__, status); |
493 | if (status != -ENOENT) |
494 | command_info->command_finished = WHITEHEAT_CMD_FAILURE; |
495 | wake_up(&command_info->wait_command); |
496 | return; |
497 | } |
498 | |
499 | usb_serial_debug_data(dev: &command_port->dev, function: __func__, size: urb->actual_length, data); |
500 | |
501 | if (data[0] == WHITEHEAT_CMD_COMPLETE) { |
502 | command_info->command_finished = WHITEHEAT_CMD_COMPLETE; |
503 | wake_up(&command_info->wait_command); |
504 | } else if (data[0] == WHITEHEAT_CMD_FAILURE) { |
505 | command_info->command_finished = WHITEHEAT_CMD_FAILURE; |
506 | wake_up(&command_info->wait_command); |
507 | } else if (data[0] == WHITEHEAT_EVENT) { |
508 | /* These are unsolicited reports from the firmware, hence no |
509 | waiting command to wakeup */ |
510 | dev_dbg(&urb->dev->dev, "%s - event received\n" , __func__); |
511 | } else if ((data[0] == WHITEHEAT_GET_DTR_RTS) && |
512 | (urb->actual_length - 1 <= sizeof(command_info->result_buffer))) { |
513 | memcpy(command_info->result_buffer, &data[1], |
514 | urb->actual_length - 1); |
515 | command_info->command_finished = WHITEHEAT_CMD_COMPLETE; |
516 | wake_up(&command_info->wait_command); |
517 | } else |
518 | dev_dbg(&urb->dev->dev, "%s - bad reply from firmware\n" , __func__); |
519 | |
520 | /* Continue trying to always read */ |
521 | result = usb_submit_urb(urb: command_port->read_urb, GFP_ATOMIC); |
522 | if (result) |
523 | dev_dbg(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n" , |
524 | __func__, result); |
525 | } |
526 | |
527 | |
528 | /***************************************************************************** |
529 | * Connect Tech's White Heat firmware interface |
530 | *****************************************************************************/ |
531 | static int firm_send_command(struct usb_serial_port *port, __u8 command, |
532 | __u8 *data, __u8 datasize) |
533 | { |
534 | struct usb_serial_port *command_port; |
535 | struct whiteheat_command_private *command_info; |
536 | struct whiteheat_private *info; |
537 | struct device *dev = &port->dev; |
538 | __u8 *transfer_buffer; |
539 | int retval = 0; |
540 | int t; |
541 | |
542 | dev_dbg(dev, "%s - command %d\n" , __func__, command); |
543 | |
544 | command_port = port->serial->port[COMMAND_PORT]; |
545 | command_info = usb_get_serial_port_data(port: command_port); |
546 | |
547 | if (command_port->bulk_out_size < datasize + 1) |
548 | return -EIO; |
549 | |
550 | mutex_lock(&command_info->mutex); |
551 | command_info->command_finished = false; |
552 | |
553 | transfer_buffer = (__u8 *)command_port->write_urb->transfer_buffer; |
554 | transfer_buffer[0] = command; |
555 | memcpy(&transfer_buffer[1], data, datasize); |
556 | command_port->write_urb->transfer_buffer_length = datasize + 1; |
557 | retval = usb_submit_urb(urb: command_port->write_urb, GFP_NOIO); |
558 | if (retval) { |
559 | dev_dbg(dev, "%s - submit urb failed\n" , __func__); |
560 | goto exit; |
561 | } |
562 | |
563 | /* wait for the command to complete */ |
564 | t = wait_event_timeout(command_info->wait_command, |
565 | (bool)command_info->command_finished, COMMAND_TIMEOUT); |
566 | if (!t) |
567 | usb_kill_urb(urb: command_port->write_urb); |
568 | |
569 | if (command_info->command_finished == false) { |
570 | dev_dbg(dev, "%s - command timed out.\n" , __func__); |
571 | retval = -ETIMEDOUT; |
572 | goto exit; |
573 | } |
574 | |
575 | if (command_info->command_finished == WHITEHEAT_CMD_FAILURE) { |
576 | dev_dbg(dev, "%s - command failed.\n" , __func__); |
577 | retval = -EIO; |
578 | goto exit; |
579 | } |
580 | |
581 | if (command_info->command_finished == WHITEHEAT_CMD_COMPLETE) { |
582 | dev_dbg(dev, "%s - command completed.\n" , __func__); |
583 | switch (command) { |
584 | case WHITEHEAT_GET_DTR_RTS: |
585 | info = usb_get_serial_port_data(port); |
586 | info->mcr = command_info->result_buffer[0]; |
587 | break; |
588 | } |
589 | } |
590 | exit: |
591 | mutex_unlock(lock: &command_info->mutex); |
592 | return retval; |
593 | } |
594 | |
595 | |
596 | static int firm_open(struct usb_serial_port *port) |
597 | { |
598 | struct whiteheat_simple open_command; |
599 | |
600 | open_command.port = port->port_number + 1; |
601 | return firm_send_command(port, WHITEHEAT_OPEN, |
602 | data: (__u8 *)&open_command, datasize: sizeof(open_command)); |
603 | } |
604 | |
605 | |
606 | static int firm_close(struct usb_serial_port *port) |
607 | { |
608 | struct whiteheat_simple close_command; |
609 | |
610 | close_command.port = port->port_number + 1; |
611 | return firm_send_command(port, WHITEHEAT_CLOSE, |
612 | data: (__u8 *)&close_command, datasize: sizeof(close_command)); |
613 | } |
614 | |
615 | |
616 | static void firm_setup_port(struct tty_struct *tty) |
617 | { |
618 | struct usb_serial_port *port = tty->driver_data; |
619 | struct device *dev = &port->dev; |
620 | struct whiteheat_port_settings port_settings; |
621 | unsigned int cflag = tty->termios.c_cflag; |
622 | speed_t baud; |
623 | |
624 | port_settings.port = port->port_number + 1; |
625 | |
626 | port_settings.bits = tty_get_char_size(cflag); |
627 | dev_dbg(dev, "%s - data bits = %d\n" , __func__, port_settings.bits); |
628 | |
629 | /* determine the parity */ |
630 | if (cflag & PARENB) |
631 | if (cflag & CMSPAR) |
632 | if (cflag & PARODD) |
633 | port_settings.parity = WHITEHEAT_PAR_MARK; |
634 | else |
635 | port_settings.parity = WHITEHEAT_PAR_SPACE; |
636 | else |
637 | if (cflag & PARODD) |
638 | port_settings.parity = WHITEHEAT_PAR_ODD; |
639 | else |
640 | port_settings.parity = WHITEHEAT_PAR_EVEN; |
641 | else |
642 | port_settings.parity = WHITEHEAT_PAR_NONE; |
643 | dev_dbg(dev, "%s - parity = %c\n" , __func__, port_settings.parity); |
644 | |
645 | /* figure out the stop bits requested */ |
646 | if (cflag & CSTOPB) |
647 | port_settings.stop = 2; |
648 | else |
649 | port_settings.stop = 1; |
650 | dev_dbg(dev, "%s - stop bits = %d\n" , __func__, port_settings.stop); |
651 | |
652 | /* figure out the flow control settings */ |
653 | if (cflag & CRTSCTS) |
654 | port_settings.hflow = (WHITEHEAT_HFLOW_CTS | |
655 | WHITEHEAT_HFLOW_RTS); |
656 | else |
657 | port_settings.hflow = WHITEHEAT_HFLOW_NONE; |
658 | dev_dbg(dev, "%s - hardware flow control = %s %s %s %s\n" , __func__, |
659 | (port_settings.hflow & WHITEHEAT_HFLOW_CTS) ? "CTS" : "" , |
660 | (port_settings.hflow & WHITEHEAT_HFLOW_RTS) ? "RTS" : "" , |
661 | (port_settings.hflow & WHITEHEAT_HFLOW_DSR) ? "DSR" : "" , |
662 | (port_settings.hflow & WHITEHEAT_HFLOW_DTR) ? "DTR" : "" ); |
663 | |
664 | /* determine software flow control */ |
665 | if (I_IXOFF(tty)) |
666 | port_settings.sflow = WHITEHEAT_SFLOW_RXTX; |
667 | else |
668 | port_settings.sflow = WHITEHEAT_SFLOW_NONE; |
669 | dev_dbg(dev, "%s - software flow control = %c\n" , __func__, port_settings.sflow); |
670 | |
671 | port_settings.xon = START_CHAR(tty); |
672 | port_settings.xoff = STOP_CHAR(tty); |
673 | dev_dbg(dev, "%s - XON = %2x, XOFF = %2x\n" , __func__, port_settings.xon, port_settings.xoff); |
674 | |
675 | /* get the baud rate wanted */ |
676 | baud = tty_get_baud_rate(tty); |
677 | port_settings.baud = cpu_to_le32(baud); |
678 | dev_dbg(dev, "%s - baud rate = %u\n" , __func__, baud); |
679 | |
680 | /* fixme: should set validated settings */ |
681 | tty_encode_baud_rate(tty, ibaud: baud, obaud: baud); |
682 | |
683 | /* handle any settings that aren't specified in the tty structure */ |
684 | port_settings.lloop = 0; |
685 | |
686 | /* now send the message to the device */ |
687 | firm_send_command(port, WHITEHEAT_SETUP_PORT, |
688 | data: (__u8 *)&port_settings, datasize: sizeof(port_settings)); |
689 | } |
690 | |
691 | |
692 | static int firm_set_rts(struct usb_serial_port *port, __u8 onoff) |
693 | { |
694 | struct whiteheat_set_rdb rts_command; |
695 | |
696 | rts_command.port = port->port_number + 1; |
697 | rts_command.state = onoff; |
698 | return firm_send_command(port, WHITEHEAT_SET_RTS, |
699 | data: (__u8 *)&rts_command, datasize: sizeof(rts_command)); |
700 | } |
701 | |
702 | |
703 | static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff) |
704 | { |
705 | struct whiteheat_set_rdb dtr_command; |
706 | |
707 | dtr_command.port = port->port_number + 1; |
708 | dtr_command.state = onoff; |
709 | return firm_send_command(port, WHITEHEAT_SET_DTR, |
710 | data: (__u8 *)&dtr_command, datasize: sizeof(dtr_command)); |
711 | } |
712 | |
713 | |
714 | static int firm_set_break(struct usb_serial_port *port, __u8 onoff) |
715 | { |
716 | struct whiteheat_set_rdb break_command; |
717 | |
718 | break_command.port = port->port_number + 1; |
719 | break_command.state = onoff; |
720 | return firm_send_command(port, WHITEHEAT_SET_BREAK, |
721 | data: (__u8 *)&break_command, datasize: sizeof(break_command)); |
722 | } |
723 | |
724 | |
725 | static int firm_purge(struct usb_serial_port *port, __u8 rxtx) |
726 | { |
727 | struct whiteheat_purge purge_command; |
728 | |
729 | purge_command.port = port->port_number + 1; |
730 | purge_command.what = rxtx; |
731 | return firm_send_command(port, WHITEHEAT_PURGE, |
732 | data: (__u8 *)&purge_command, datasize: sizeof(purge_command)); |
733 | } |
734 | |
735 | |
736 | static int firm_get_dtr_rts(struct usb_serial_port *port) |
737 | { |
738 | struct whiteheat_simple get_dr_command; |
739 | |
740 | get_dr_command.port = port->port_number + 1; |
741 | return firm_send_command(port, WHITEHEAT_GET_DTR_RTS, |
742 | data: (__u8 *)&get_dr_command, datasize: sizeof(get_dr_command)); |
743 | } |
744 | |
745 | |
746 | static int firm_report_tx_done(struct usb_serial_port *port) |
747 | { |
748 | struct whiteheat_simple close_command; |
749 | |
750 | close_command.port = port->port_number + 1; |
751 | return firm_send_command(port, WHITEHEAT_REPORT_TX_DONE, |
752 | data: (__u8 *)&close_command, datasize: sizeof(close_command)); |
753 | } |
754 | |
755 | |
756 | /***************************************************************************** |
757 | * Connect Tech's White Heat utility functions |
758 | *****************************************************************************/ |
759 | static int start_command_port(struct usb_serial *serial) |
760 | { |
761 | struct usb_serial_port *command_port; |
762 | struct whiteheat_command_private *command_info; |
763 | int retval = 0; |
764 | |
765 | command_port = serial->port[COMMAND_PORT]; |
766 | command_info = usb_get_serial_port_data(port: command_port); |
767 | mutex_lock(&command_info->mutex); |
768 | if (!command_info->port_running) { |
769 | /* Work around HCD bugs */ |
770 | usb_clear_halt(dev: serial->dev, pipe: command_port->read_urb->pipe); |
771 | |
772 | retval = usb_submit_urb(urb: command_port->read_urb, GFP_KERNEL); |
773 | if (retval) { |
774 | dev_err(&serial->dev->dev, |
775 | "%s - failed submitting read urb, error %d\n" , |
776 | __func__, retval); |
777 | goto exit; |
778 | } |
779 | } |
780 | command_info->port_running++; |
781 | |
782 | exit: |
783 | mutex_unlock(lock: &command_info->mutex); |
784 | return retval; |
785 | } |
786 | |
787 | |
788 | static void stop_command_port(struct usb_serial *serial) |
789 | { |
790 | struct usb_serial_port *command_port; |
791 | struct whiteheat_command_private *command_info; |
792 | |
793 | command_port = serial->port[COMMAND_PORT]; |
794 | command_info = usb_get_serial_port_data(port: command_port); |
795 | mutex_lock(&command_info->mutex); |
796 | command_info->port_running--; |
797 | if (!command_info->port_running) |
798 | usb_kill_urb(urb: command_port->read_urb); |
799 | mutex_unlock(lock: &command_info->mutex); |
800 | } |
801 | |
802 | module_usb_serial_driver(serial_drivers, id_table_combined); |
803 | |
804 | MODULE_AUTHOR(DRIVER_AUTHOR); |
805 | MODULE_DESCRIPTION(DRIVER_DESC); |
806 | MODULE_LICENSE("GPL" ); |
807 | |
808 | MODULE_FIRMWARE("whiteheat.fw" ); |
809 | MODULE_FIRMWARE("whiteheat_loader.fw" ); |
810 | |