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