diff options
| -rw-r--r-- | drivers/usb/serial/ftdi_sio.c | 1964 | ||||
| -rw-r--r-- | drivers/usb/serial/ftdi_sio.h | 190 |
2 files changed, 1651 insertions, 503 deletions
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index f5307a47b09b..c470203c26d6 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -4,6 +4,8 @@ * Copyright (C) 1999 - 2001 * Greg Kroah-Hartman (greg@kroah.com) * Bill Ryder (bryder@sgi.com) + * Copyright (C) 2002 + * Kuba Ober (kuba@mareimbrium.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -13,49 +15,156 @@ * See Documentation/usb/usb-serial.txt for more information on using this driver * * See http://ftdi-usb-sio.sourceforge.net for upto date testing info - * and extra documentation + * and extra documentation + * + * (27/Jun/2003) Ian Abbott + * Reworked the urb handling logic. We have no more pool, but dynamically + * allocate the urb and the transfer buffer on the fly. In testing this + * does not incure any measurable overhead. This also relies on the fact + * that we have proper reference counting logic for urbs. I nicked this + * from Greg KH's Visor driver. + * + * (23/Jun/2003) Ian Abbott + * Reduced flip buffer pushes and corrected a data length test in + * ftdi_read_bulk_callback. + * Defererence pointers after any paranoid checks, not before. + * + * (21/Jun/2003) Erik Nygren + * Added support for Home Electronics Tira-1 IR tranceiver using FT232BM chip. + * See <http://www.home-electro.com/tira1.htm>. Only operates properly + * at 100000 and RTS-CTS, so set custom divisor mode on startup. + * Also force the Tira-1 and USB-UIRT to only use their custom baud rates. + * + * (18/Jun/2003) Ian Abbott + * Added Device ID of the USB relais from Rudolf Gugler (backported from + * Philipp Gühring's patch for 2.5.x kernel). + * Moved read transfer buffer reallocation into startup function. + * Free existing write urb and transfer buffer in startup function. + * Only use urbs in write urb pool that were successfully allocated. + * Moved some constant macros out of functions. + * Minor whitespace and comment changes. + * + * (12/Jun/2003) David Norwood + * Added support for USB-UIRT IR tranceiver using 8U232AM chip. + * See <http://home.earthlink.net/~jrhees/USBUIRT/index.htm>. Only + * operates properly at 312500, so set custom divisor mode on startup. + * + * (12/Jun/2003) Ian Abbott + * Added Sealevel SeaLINK+ 210x, 220x, 240x, 280x vid/pids from Tuan Hoang + * - I've eliminated some that don't seem to exist! + * Added Home Electronics Tira-1 IR transceiver pid from Chris Horn + * Some whitespace/coding-style cleanups + * + * (11/Jun/2003) Ian Abbott + * Fixed unsafe spinlock usage in ftdi_write + * + * (24/Feb/2003) Richard Shooter + * Increase read buffer size to improve read speeds at higher baud rates + * (specifically tested with up to 1Mb/sec at 1.5M baud) + * + * (23/Feb/2003) John Wilkins + * Added Xon/xoff flow control (activating support in the ftdi device) + * Added vid/pid for Videonetworks/Homechoice (UK ISP) + * + * (23/Feb/2003) Bill Ryder + * Added matrix orb device vid/pids from Wayne Wylupski + * + * (19/Feb/2003) Ian Abbott + * For TIOCSSERIAL, set alt_speed to 0 when ASYNC_SPD_MASK value has + * changed to something other than ASYNC_SPD_HI, ASYNC_SPD_VHI, + * ASYNC_SPD_SHI or ASYNC_SPD_WARP. Also, unless ASYNC_SPD_CUST is in + * force, don't bother changing baud rate when custom_divisor has changed. + * + * (18/Feb/2003) Ian Abbott + * Fixed TIOCMGET handling to include state of DTR and RTS, the state + * of which are now saved by set_dtr() and set_rts(). + * Fixed improper storage class for buf in set_dtr() and set_rts(). + * Added FT232BM chip type and support for its extra baud rates (compared + * to FT8U232AM). + * Took account of special case divisor values for highest baud rates of + * FT8U232AM and FT232BM. + * For TIOCSSERIAL, forced alt_speed to 0 when ASYNC_SPD_CUST kludge used, + * as previous alt_speed setting is now stale. + * Moved startup code common between the startup routines for the + * different chip types into a common subroutine. + * + * (17/Feb/2003) Bill Ryder + * Added write urb buffer pool on a per device basis + * Added more checking for open file on callbacks (fixed OOPS) + * Added CrystalFontz 632 and 634 PIDs + * (thanx to CrystalFontz for the sample devices - they flushed out + * some driver bugs) + * Minor debugging message changes + * Added throttle, unthrottle and chars_in_buffer functions + * Fixed FTDI_SIO (the original device) bug + * Fixed some shutdown handling + * + * * + * + * (07/Jun/2002) Kuba Ober + * Changed FTDI_SIO_BASE_BAUD_TO_DIVISOR macro into ftdi_baud_to_divisor + * function. It was getting too complex. + * Fix the divisor calculation logic which was setting divisor of 0.125 + * instead of 0.5 for fractional parts of divisor equal to 5/8, 6/8, 7/8. + * Also make it bump up the divisor to next integer in case of 7/8 - it's + * a better approximation. + * + * (25/Jul/2002) Bill Ryder inserted Dmitri's TIOCMIWAIT patch + * Not tested by me but it doesn't break anything I use. + * + * (04/Jan/2002) Kuba Ober + * Implemented 38400 baudrate kludge, where it can be substituted with other + * values. That's the only way to set custom baudrates. + * Implemented TIOCSSERIAL, TIOCGSERIAL ioctl's so that setserial is happy. + * FIXME: both baudrate things should eventually go to usbserial.c as other + * devices may need that functionality too. Actually, it can probably be + * merged in serial.c somehow - too many drivers repeat this code over + * and over. + * Fixed baudrate forgetfulness - open() used to reset baudrate to 9600 every time. + * Divisors for baudrates are calculated by a macro. + * Small code cleanups. Ugly whitespace changes for Plato's sake only ;-]. + * * (04/Nov/2001) Bill Ryder - * Fixed bug in read_bulk_callback where incorrect urb buffer was used. - * cleaned up write offset calculation - * added write_room since default values can be incorrect for sio - * changed write_bulk_callback to use same queue_task as other drivers - * (the previous version caused panics) - * Removed port iteration code since the device only has one I/O port and it - * was wrong anyway. + * Fixed bug in read_bulk_callback where incorrect urb buffer was used. + * Cleaned up write offset calculation + * Added write_room since default values can be incorrect for sio + * Changed write_bulk_callback to use same queue_task as other drivers + * (the previous version caused panics) + * Removed port iteration code since the device only has one I/O port and it + * was wrong anyway. * * (31/May/2001) gkh - * switched from using spinlock to a semaphore, which fixes lots of problems. + * Switched from using spinlock to a semaphore, which fixes lots of problems. * * (23/May/2001) Bill Ryder - * Added runtime debug patch (thanx Tyson D Sawyer). - * Cleaned up comments for 8U232 - * Added parity, framing and overrun error handling - * Added receive break handling. + * Added runtime debug patch (thanx Tyson D Sawyer). + * Cleaned up comments for 8U232 + * Added parity, framing and overrun error handling + * Added receive break handling. * * (04/08/2001) gb * Identify version on module load. * * (18/March/2001) Bill Ryder - * (Not released) - * Added send break handling. (requires kernel patch too) - * Fixed 8U232AM hardware RTS/CTS etc status reporting. - * Added flipbuf fix copied from generic device + * (Not released) + * Added send break handling. (requires kernel patch too) + * Fixed 8U232AM hardware RTS/CTS etc status reporting. + * Added flipbuf fix copied from generic device * * (12/3/2000) Bill Ryder - * Added support for 8U232AM device. - * Moved PID and VIDs into header file only. - * Turned on low-latency for the tty (device will do high baudrates) - * Added shutdown routine to close files when device removed. - * More debug and error message cleanups. - * + * Added support for 8U232AM device. + * Moved PID and VIDs into header file only. + * Turned on low-latency for the tty (device will do high baudrates) + * Added shutdown routine to close files when device removed. + * More debug and error message cleanups. * * (11/13/2000) Bill Ryder - * Added spinlock protected open code and close code. - * Multiple opens work (sort of - see webpage mentioned above). - * Cleaned up comments. Removed multiple PID/VID definitions. - * Factorised cts/dtr code - * Made use of __FUNCTION__ in dbg's + * Added spinlock protected open code and close code. + * Multiple opens work (sort of - see webpage mentioned above). + * Cleaned up comments. Removed multiple PID/VID definitions. + * Factorised cts/dtr code + * Made use of __FUNCTION__ in dbg's * * (11/01/2000) Adam J. Richter * usb_device_id table support @@ -72,16 +181,16 @@ * driver is a loadable module now. * * (04/04/2000) Bill Ryder - * Fixed bugs in TCGET/TCSET ioctls (by removing them - they are + * Fixed bugs in TCGET/TCSET ioctls (by removing them - they are * handled elsewhere in the tty io driver chain). * * (03/30/2000) Bill Ryder - * Implemented lots of ioctls - * Fixed a race condition in write - * Changed some dbg's to errs + * Implemented lots of ioctls + * Fixed a race condition in write + * Changed some dbg's to errs * * (03/26/2000) gkh - * Split driver up into device specific pieces. + * Split driver up into device specific pieces. * */ @@ -90,7 +199,6 @@ /* to talk to the device */ /* Thanx to gkh and the rest of the usb dev group for all code I have assimilated :-) */ - #include <linux/config.h> #include <linux/kernel.h> #include <linux/errno.h> @@ -103,6 +211,7 @@ #include <linux/spinlock.h> #include <asm/uaccess.h> #include <linux/usb.h> +#include <linux/serial.h> #ifdef CONFIG_USB_SERIAL_DEBUG static int debug = 1; #else @@ -112,47 +221,244 @@ #include "usb-serial.h" #include "ftdi_sio.h" - /* * Version Information */ -#define DRIVER_VERSION "v1.2.0" -#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Bill Ryder <bryder@sgi.com>" -#define DRIVER_DESC "USB FTDI RS232 Converters Driver" +#define DRIVER_VERSION "v1.4.0" +#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Bill Ryder <bryder@sgi.com>, Kuba Ober <kuba@mareimbrium.org>" +#define DRIVER_DESC "USB FTDI Serial Converters Driver" static struct usb_device_id id_table_sio [] = { { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) }, { } /* Terminating entry */ }; -/* THe 8U232AM has the same API as the sio except for: - - it can support MUCH higher baudrates (921600 at 48MHz/230400 - at 12MHz so .. it's baudrate setting codes are different - - it has a two byte status code. - - it returns characters very 16ms (the FTDI does it every 40ms) - */ +/* + * The 8U232AM has the same API as the sio except for: + * - it can support MUCH higher baudrates; up to: + * o 921600 for RS232 and 2000000 for RS422/485 at 48MHz + * o 230400 at 12MHz + * so .. 8U232AM's baudrate setting codes are different + * - it has a two byte status code. + * - it returns characters every 16ms (the FTDI does it every 40ms) + * + * the bcdDevice value is used to differentiate FT232BM and FT245BM from + * the earlier FT8U232AM and FT8U232BM. For now, include all known VID/PID + * combinations in both tables. + * FIXME: perhaps bcdDevice can also identify 12MHz devices, but I don't know + * if those ever went into mass production. [Ian Abbott] + */ + - static struct usb_device_id id_table_8U232AM [] = { - { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) }, - { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, - { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_RELAIS_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_XF_634_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_XF_632_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_VNHCPCUSB_D_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_0_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_5_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_6_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2101_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2102_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2103_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2104_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2201_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2201_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2202_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2202_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2203_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2203_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_5_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_6_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_7_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_8_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_5_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_6_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_7_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_8_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_1_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_2_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_3_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_5_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_6_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_7_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0, 0x3ff) }, { } /* Terminating entry */ }; -static struct usb_device_id id_table_combined [] = { +static struct usb_device_id id_table_FT232BM [] = { + { USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_RELAIS_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_XF_634_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_XF_632_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_VNHCPCUSB_D_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_0_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_5_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_MTXORB_VID, FTDI_MTXORB_6_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2101_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2102_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2103_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2104_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2201_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2201_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2202_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2202_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2203_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2203_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2401_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2402_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2403_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_5_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_6_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_7_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2801_8_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_5_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_6_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_7_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2802_8_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_1_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_2_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_3_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_5_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_6_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_7_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0x400, 0xffff) }, + { } /* Terminating entry */ +}; + + +static struct usb_device_id id_table_USB_UIRT [] = { + { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) }, + { } /* Terminating entry */ +}; + + +static struct usb_device_id id_table_HE_TIRA1 [] = { + { USB_DEVICE_VER(FTDI_VID, FTDI_HE_TIRA1_PID, 0x400, 0xffff) }, + { } /* Terminating entry */ +}; + + +static __devinitdata struct usb_device_id id_table_combined [] = { { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) }, { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) }, { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_634_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_632_PID) }, { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_0_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_1_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_2_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_3_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_4_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_5_PID) }, + { USB_DEVICE(FTDI_MTXORB_VID, FTDI_MTXORB_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2101_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2102_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2103_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2104_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_HE_TIRA1_PID, 0x400, 0xffff) }, + { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, id_table_combined); static struct usb_driver ftdi_driver = { - .owner = THIS_MODULE, .name = "ftdi_sio", .probe = usb_serial_probe, .disconnect = usb_serial_disconnect, @@ -160,31 +466,63 @@ static struct usb_driver ftdi_driver = { }; +/* Constants for read urb and write urb */ +#define BUFSZ 512 +#define PKTSZ 64 + struct ftdi_private { - enum ftdi_type ftdi_type; - __u16 last_set_data_urb_value ; /* the last data state set - needed for doing a break */ - int write_offset; + ftdi_chip_type_t chip_type; + /* type of the device, either SIO or FT8U232AM */ + int baud_base; /* baud base clock for divisor setting */ + int custom_divisor; /* custom_divisor kludge, this is for baud_base (different from what goes to the chip!) */ + __u16 last_set_data_urb_value ; + /* the last data state set - needed for doing a break */ + int write_offset; /* This is the offset in the usb data block to write the serial data - + * it is different between devices + */ + int flags; /* some ASYNC_xxxx flags are supported */ + unsigned long last_dtr_rts; /* saved modem control outputs */ + wait_queue_head_t delta_msr_wait; /* Used for TIOCMIWAIT */ + char prev_status, diff_status; /* Used for TIOCMIWAIT */ + + int force_baud; /* if non-zero, force the baud rate to this value */ + int force_rtscts; /* if non-zero, force RTS-CTS to always be enabled */ }; + +/* Used for TIOCMIWAIT */ +#define FTDI_STATUS_B0_MASK (FTDI_RS0_CTS | FTDI_RS0_DSR | FTDI_RS0_RI | FTDI_RS0_RLSD) +#define FTDI_STATUS_B1_MASK (FTDI_RS_BI) +/* End TIOCMIWAIT */ + +#define FTDI_IMPL_ASYNC_FLAGS = ( ASYNC_SPD_HI | ASYNC_SPD_VHI \ + ASYNC_SPD_CUST | ASYNC_SPD_SHI | ASYNC_SPD_WARP ) + /* function prototypes for a FTDI serial converter */ -static int ftdi_sio_startup (struct usb_serial *serial); +static int ftdi_SIO_startup (struct usb_serial *serial); static int ftdi_8U232AM_startup (struct usb_serial *serial); -static void ftdi_sio_shutdown (struct usb_serial *serial); -static int ftdi_sio_open (struct usb_serial_port *port, struct file *filp); -static void ftdi_sio_close (struct usb_serial_port *port, struct file *filp); -static int ftdi_sio_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); -static int ftdi_sio_write_room (struct usb_serial_port *port); -static void ftdi_sio_write_bulk_callback (struct urb *urb, struct pt_regs *regs); -static void ftdi_sio_read_bulk_callback (struct urb *urb, struct pt_regs *regs); -static void ftdi_sio_set_termios (struct usb_serial_port *port, struct termios * old); -static int ftdi_sio_tiocmget (struct usb_serial_port *port, struct file *file); -static int ftdi_sio_tiocmset (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear); -static int ftdi_sio_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg); -static void ftdi_sio_break_ctl (struct usb_serial_port *port, int break_state ); - -/* Should rename most ftdi_sio's to ftdi_ now since there are two devices - which share common code */ - -static struct usb_serial_device_type ftdi_sio_device = { +static int ftdi_FT232BM_startup (struct usb_serial *serial); +static int ftdi_USB_UIRT_startup (struct usb_serial *serial); +static int ftdi_HE_TIRA1_startup (struct usb_serial *serial); +static void ftdi_shutdown (struct usb_serial *serial); +static int ftdi_open (struct usb_serial_port *port, struct file *filp); +static void ftdi_close (struct usb_serial_port *port, struct file *filp); +static int ftdi_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); +static int ftdi_write_room (struct usb_serial_port *port); +static int ftdi_chars_in_buffer (struct usb_serial_port *port); +static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs); +static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs); +static void ftdi_set_termios (struct usb_serial_port *port, struct termios * old); +static int ftdi_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg); +static void ftdi_break_ctl (struct usb_serial_port *port, int break_state ); +static void ftdi_throttle (struct usb_serial_port *port); +static void ftdi_unthrottle (struct usb_serial_port *port); + +static unsigned short int ftdi_232am_baud_base_to_divisor (int baud, int base); +static unsigned short int ftdi_232am_baud_to_divisor (int baud); +static __u32 ftdi_232bm_baud_base_to_divisor (int baud, int base); +static __u32 ftdi_232bm_baud_to_divisor (int baud); + +static struct usb_serial_device_type ftdi_SIO_device = { .owner = THIS_MODULE, .name = "FTDI SIO", .id_table = id_table_sio, @@ -192,77 +530,206 @@ static struct usb_serial_device_type ftdi_sio_device = { .num_bulk_in = 1, .num_bulk_out = 1, .num_ports = 1, - .open = ftdi_sio_open, - .close = ftdi_sio_close, - .write = ftdi_sio_write, - .write_room = ftdi_sio_write_room, - .read_bulk_callback = ftdi_sio_read_bulk_callback, - .write_bulk_callback = ftdi_sio_write_bulk_callback, - .ioctl = ftdi_sio_ioctl, - .set_termios = ftdi_sio_set_termios, - .break_ctl = ftdi_sio_break_ctl, - .tiocmget = ftdi_sio_tiocmget, - .tiocmset = ftdi_sio_tiocmset, - .attach = ftdi_sio_startup, - .shutdown = ftdi_sio_shutdown, + .open = ftdi_open, + .close = ftdi_close, + .throttle = ftdi_throttle, + .unthrottle = ftdi_unthrottle, + .write = ftdi_write, + .write_room = ftdi_write_room, + .chars_in_buffer = ftdi_chars_in_buffer, + .read_bulk_callback = ftdi_read_bulk_callback, + .write_bulk_callback = ftdi_write_bulk_callback, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .attach = ftdi_SIO_startup, + .shutdown = ftdi_shutdown, }; static struct usb_serial_device_type ftdi_8U232AM_device = { .owner = THIS_MODULE, - .name = "FTDI 8U232AM", + .name = "FTDI 8U232AM Compatible", .id_table = id_table_8U232AM, .num_interrupt_in = 0, .num_bulk_in = 1, .num_bulk_out = 1, .num_ports = 1, - .open = ftdi_sio_open, - .close = ftdi_sio_close, - .write = ftdi_sio_write, - .write_room = ftdi_sio_write_room, - .read_bulk_callback = ftdi_sio_read_bulk_callback, - .write_bulk_callback = ftdi_sio_write_bulk_callback, - .ioctl = ftdi_sio_ioctl, - .set_termios = ftdi_sio_set_termios, - .break_ctl = ftdi_sio_break_ctl, - .tiocmget = ftdi_sio_tiocmget, - .tiocmset = ftdi_sio_tiocmset, + .open = ftdi_open, + .close = ftdi_close, + .throttle = ftdi_throttle, + .unthrottle = ftdi_unthrottle, + .write = ftdi_write, + .write_room = ftdi_write_room, + .chars_in_buffer = ftdi_chars_in_buffer, + .read_bulk_callback = ftdi_read_bulk_callback, + .write_bulk_callback = ftdi_write_bulk_callback, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, .attach = ftdi_8U232AM_startup, - .shutdown = ftdi_sio_shutdown, + .shutdown = ftdi_shutdown, }; +static struct usb_serial_device_type ftdi_FT232BM_device = { + .owner = THIS_MODULE, + .name = "FTDI FT232BM Compatible", + .id_table = id_table_FT232BM, + .num_interrupt_in = 0, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_ports = 1, + .open = ftdi_open, + .close = ftdi_close, + .throttle = ftdi_throttle, + .unthrottle = ftdi_unthrottle, + .write = ftdi_write, + .write_room = ftdi_write_room, + .chars_in_buffer = ftdi_chars_in_buffer, + .read_bulk_callback = ftdi_read_bulk_callback, + .write_bulk_callback = ftdi_write_bulk_callback, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .attach = ftdi_FT232BM_startup, + .shutdown = ftdi_shutdown, +}; + +static struct usb_serial_device_type ftdi_USB_UIRT_device = { + .owner = THIS_MODULE, + .name = "USB-UIRT Infrared Receiver/Transmitter", + .id_table = id_table_USB_UIRT, + .num_interrupt_in = 0, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_ports = 1, + .open = ftdi_open, + .close = ftdi_close, + .throttle = ftdi_throttle, + .unthrottle = ftdi_unthrottle, + .write = ftdi_write, + .write_room = ftdi_write_room, + .chars_in_buffer = ftdi_chars_in_buffer, + .read_bulk_callback = ftdi_read_bulk_callback, + .write_bulk_callback = ftdi_write_bulk_callback, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .attach = ftdi_USB_UIRT_startup, + .shutdown = ftdi_shutdown, +}; + +/* The TIRA1 is based on a FT232BM which requires a fixed baud rate of 100000 + * and which requires RTS-CTS to be enabled. */ +static struct usb_serial_device_type ftdi_HE_TIRA1_device = { + .owner = THIS_MODULE, + .name = "Home-Electronics TIRA-1 IR Transceiver", + .id_table = id_table_HE_TIRA1, + .num_interrupt_in = 0, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_ports = 1, + .open = ftdi_open, + .close = ftdi_close, + .throttle = ftdi_throttle, + .unthrottle = ftdi_unthrottle, + .write = ftdi_write, + .write_room = ftdi_write_room, + .chars_in_buffer = ftdi_chars_in_buffer, + .read_bulk_callback = ftdi_read_bulk_callback, + .write_bulk_callback = ftdi_write_bulk_callback, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .attach = ftdi_HE_TIRA1_startup, + .shutdown = ftdi_shutdown, +}; + + + +#define WDR_TIMEOUT (HZ * 5 ) /* default urb timeout */ + +/* High and low are for DTR, RTS etc etc */ +#define HIGH 1 +#define LOW 0 /* * *************************************************************************** - * FTDI SIO Serial Converter specific driver functions + * Utlity functions * *************************************************************************** */ -#define WDR_TIMEOUT (HZ * 5 ) /* default urb timeout */ +static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base) +{ + unsigned short int divisor; + int divisor3 = base / 2 / baud; // divisor shifted 3 bits to the left + if ((divisor3 & 0x7) == 7) divisor3 ++; // round x.7/8 up to x+1 + divisor = divisor3 >> 3; + divisor3 &= 0x7; + if (divisor3 == 1) divisor |= 0xc000; else // 0.125 + if (divisor3 >= 4) divisor |= 0x4000; else // 0.5 + if (divisor3 != 0) divisor |= 0x8000; // 0.25 + if (divisor == 1) divisor = 0; /* special case for maximum baud rate */ + return divisor; +} -/* utility functions to set and unset dtr and rts */ -#define HIGH 1 -#define LOW 0 -static int set_rts(struct usb_device *dev, - unsigned int pipe, - int high_or_low) +static unsigned short int ftdi_232am_baud_to_divisor(int baud) { - static char buf[1]; - unsigned ftdi_high_or_low = (high_or_low? FTDI_SIO_SET_RTS_HIGH : - FTDI_SIO_SET_RTS_LOW); - return(usb_control_msg(dev, pipe, + return(ftdi_232am_baud_base_to_divisor(baud, 48000000)); +} + +static __u32 ftdi_232bm_baud_base_to_divisor(int baud, int base) +{ + static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + __u32 divisor; + int divisor3 = base / 2 / baud; // divisor shifted 3 bits to the left + divisor = divisor3 >> 3; + divisor |= (__u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) divisor = 0; else // 1.0 + if (divisor == 0x4001) divisor = 1; // 1.5 + return divisor; +} + +static __u32 ftdi_232bm_baud_to_divisor(int baud) +{ + return(ftdi_232bm_baud_base_to_divisor(baud, 48000000)); +} + +static int set_rts(struct usb_serial_port *port, int high_or_low) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + char buf[1]; + unsigned ftdi_high_or_low; + if (high_or_low) { + ftdi_high_or_low = FTDI_SIO_SET_RTS_HIGH; + priv->last_dtr_rts |= TIOCM_RTS; + } else { + ftdi_high_or_low = FTDI_SIO_SET_RTS_LOW; + priv->last_dtr_rts &= ~TIOCM_RTS; + } + return(usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), FTDI_SIO_SET_MODEM_CTRL_REQUEST, FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, ftdi_high_or_low, 0, buf, 0, WDR_TIMEOUT)); } -static int set_dtr(struct usb_device *dev, - unsigned int pipe, - int high_or_low) + + +static int set_dtr(struct usb_serial_port *port, int high_or_low) { - static char buf[1]; - unsigned ftdi_high_or_low = (high_or_low? FTDI_SIO_SET_DTR_HIGH : - FTDI_SIO_SET_DTR_LOW); - return(usb_control_msg(dev, pipe, + struct ftdi_private *priv = usb_get_serial_port_data(port); + char buf[1]; + unsigned ftdi_high_or_low; + if (high_or_low) { + ftdi_high_or_low = FTDI_SIO_SET_DTR_HIGH; + priv->last_dtr_rts |= TIOCM_DTR; + } else { + ftdi_high_or_low = FTDI_SIO_SET_DTR_LOW; + priv->last_dtr_rts &= ~TIOCM_DTR; + } + return(usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), FTDI_SIO_SET_MODEM_CTRL_REQUEST, FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, ftdi_high_or_low, 0, @@ -270,71 +737,409 @@ static int set_dtr(struct usb_device *dev, } +static __u32 get_ftdi_divisor(struct usb_serial_port * port); -static int ftdi_sio_startup (struct usb_serial *serial) + +static int change_speed(struct usb_serial_port *port) { - struct ftdi_private *priv; - + char buf[1]; + __u16 urb_value; + __u16 urb_index; + __u32 urb_index_value; + + urb_index_value = get_ftdi_divisor(port); + urb_value = (__u16)urb_index_value; + urb_index = (__u16)(urb_index_value >> 16); - priv = kmalloc(sizeof(struct ftdi_private), GFP_KERNEL); - if (!priv){ - err("%s- kmalloc(%Zd) failed.", __FUNCTION__, sizeof(struct ftdi_private)); - return -ENOMEM; + return (usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_BAUDRATE_REQUEST, + FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + urb_value, urb_index, + buf, 0, 100) < 0); +} + + +static __u32 get_ftdi_divisor(struct usb_serial_port * port) +{ /* get_ftdi_divisor */ + struct ftdi_private *priv = usb_get_serial_port_data(port); + __u32 div_value = 0; + int div_okay = 1; + char *chip_name = ""; + int baud; + + /* + * The logic involved in setting the baudrate can be cleanly split in 3 steps. + * Obtaining the actual baud rate is a little tricky since unix traditionally + * somehow ignored the possibility to set non-standard baud rates. + * 1. Standard baud rates are set in tty->termios->c_cflag + * 2. If these are not enough, you can set any speed using alt_speed as follows: + * - set tty->termios->c_cflag speed to B38400 + * - set your real speed in tty->alt_speed; it gets ignored when + * alt_speed==0, (or) + * - call TIOCSSERIAL ioctl with (struct serial_struct) set as follows: + * flags & ASYNC_SPD_MASK == ASYNC_SPD_[HI, VHI, SHI, WARP], this just + * sets alt_speed to (HI: 57600, VHI: 115200, SHI: 230400, WARP: 460800) + * ** Steps 1, 2 are done courtesy of tty_get_baud_rate + * 3. You can also set baud rate by setting custom divisor as follows + * - set tty->termios->c_cflag speed to B38400 + * - call TIOCSSERIAL ioctl with (struct serial_struct) set as follows: + * o flags & ASYNC_SPD_MASK == ASYNC_SPD_CUST + * o custom_divisor set to baud_base / your_new_baudrate + * ** Step 3 is done courtesy of code borrowed from serial.c - I should really + * spend some time and separate+move this common code to serial.c, it is + * replicated in nearly every serial driver you see. + */ + + /* 1. Get the baud rate from the tty settings, this observes alt_speed hack */ + + baud = tty_get_baud_rate(port->tty); + dbg("%s - tty_get_baud_rate reports speed %d", __FUNCTION__, baud); + + /* 2. Observe async-compatible custom_divisor hack, update baudrate if needed */ + + if (baud == 38400 && + ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && + (priv->custom_divisor)) { + baud = priv->baud_base / priv->custom_divisor; + dbg("%s - custom divisor %d sets baud rate to %d", __FUNCTION__, priv->custom_divisor, baud); } - priv->ftdi_type = sio; - priv->write_offset = 1; - usb_set_serial_port_data(serial->port, priv); + /* 3. Convert baudrate to device-specific divisor */ + + if (!baud) baud = 9600; + switch(priv->chip_type) { + case SIO: /* SIO chip */ + chip_name = "SIO"; + switch(baud) { + case 300: div_value = ftdi_sio_b300; break; + case 600: div_value = ftdi_sio_b600; break; + case 1200: div_value = ftdi_sio_b1200; break; + case 2400: div_value = ftdi_sio_b2400; break; + case 4800: div_value = ftdi_sio_b4800; break; + case 9600: div_value = ftdi_sio_b9600; break; + case 19200: div_value = ftdi_sio_b19200; break; + case 38400: div_value = ftdi_sio_b38400; break; + case 57600: div_value = ftdi_sio_b57600; break; + case 115200: div_value = ftdi_sio_b115200; break; + } /* baud */ + if (div_value == 0) { + dbg("%s - Baudrate (%d) requested is not supported", __FUNCTION__, baud); + div_value = ftdi_sio_b9600; + div_okay = 0; + } + break; + case FT8U232AM: /* 8U232AM chip */ + chip_name = "FT8U232AM"; + if (baud <= 3000000) { + div_value = ftdi_232am_baud_to_divisor(baud); + } else { + dbg("%s - Baud rate too high!", __FUNCTION__); + div_value = ftdi_232am_baud_to_divisor(9600); + div_okay = 0; + } + break; + case FT232BM: /* FT232BM chip */ + chip_name = "FT232BM"; + if (baud <= 3000000) { + div_value = ftdi_232bm_baud_to_divisor(baud); + } else { + dbg("%s - Baud rate too high!", __FUNCTION__); + div_value = ftdi_232bm_baud_to_divisor(9600); + div_okay = 0; + } + break; + } /* priv->chip_type */ - return (0); + if (div_okay) { + dbg("%s - Baud rate set to %d (divisor 0x%lX) on chip %s", + __FUNCTION__, baud, (unsigned long)div_value, chip_name); + } + + return(div_value); } -static int ftdi_8U232AM_startup (struct usb_serial *serial) +static int get_serial_info(struct usb_serial_port * port, struct serial_struct * retinfo) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = priv->flags; + tmp.baud_base = priv->baud_base; + tmp.custom_divisor = priv->custom_divisor; + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} /* get_serial_info */ + + +static int set_serial_info(struct usb_serial_port * port, struct serial_struct * newinfo) +{ /* set_serial_info */ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct serial_struct new_serial; + struct ftdi_private old_priv; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + old_priv = * priv; + + /* Do error checking and permission checking */ + + if (!capable(CAP_SYS_ADMIN)) { + if (((new_serial.flags & ~ASYNC_USR_MASK) != + (priv->flags & ~ASYNC_USR_MASK))) + return -EPERM; + priv->flags = ((priv->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + priv->custom_divisor = new_serial.custom_divisor; + goto check_and_exit; + } + + if ((new_serial.baud_base != priv->baud_base) || + (new_serial.baud_base < 9600)) + return -EINVAL; + + /* Make the changes - these are privileged changes! */ + + priv->flags = ((priv->flags & ~ASYNC_FLAGS) | + (new_serial.flags & ASYNC_FLAGS)); + priv->custom_divisor = new_serial.custom_divisor; + + port->tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + +check_and_exit: + if ((old_priv.flags & ASYNC_SPD_MASK) != + (priv->flags & ASYNC_SPD_MASK)) { + if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + port->tty->alt_speed = 57600; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + port->tty->alt_speed = 115200; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + port->tty->alt_speed = 230400; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + port->tty->alt_speed = 460800; + else + port->tty->alt_speed = 0; + } + if (((old_priv.flags & ASYNC_SPD_MASK) != + (priv->flags & ASYNC_SPD_MASK)) || + (((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && + (old_priv.custom_divisor != priv->custom_divisor))) { + change_speed(port); + } + + return (0); + +} /* set_serial_info */ + +/* + * *************************************************************************** + * FTDI driver specific functions + * *************************************************************************** + */ + +/* Common startup subroutine */ +/* Called from ftdi_SIO_startup, etc. */ +static int ftdi_common_startup (struct usb_serial *serial) { + struct usb_serial_port *port = &serial->port[0]; struct ftdi_private *priv; - + + dbg("%s",__FUNCTION__); priv = kmalloc(sizeof(struct ftdi_private), GFP_KERNEL); if (!priv){ err("%s- kmalloc(%Zd) failed.", __FUNCTION__, sizeof(struct ftdi_private)); return -ENOMEM; } + memset(priv, 0, sizeof(*priv)); - priv->ftdi_type = F8U232AM; - priv->write_offset = 0; - usb_set_serial_port_data(serial->port, priv); + init_waitqueue_head(&priv->delta_msr_wait); + /* This will push the characters through immediately rather + than queue a task to deliver them */ + priv->flags = ASYNC_LOW_LATENCY; + + /* Increase the size of read buffers */ + if (port->bulk_in_buffer) { + kfree (port->bulk_in_buffer); + } + port->bulk_in_buffer = kmalloc (BUFSZ, GFP_KERNEL); + if (!port->bulk_in_buffer) { + kfree (priv); + return -ENOMEM; + } + if (port->read_urb) { + port->read_urb->transfer_buffer = port->bulk_in_buffer; + port->read_urb->transfer_buffer_length = BUFSZ; + } + + /* Free port's existing write urb and transfer buffer. */ + if (port->write_urb) { + usb_free_urb (port->write_urb); + port->write_urb = NULL; + } + if (port->bulk_out_buffer) { + kfree (port->bulk_out_buffer); + port->bulk_out_buffer = NULL; + } + usb_set_serial_port_data(serial->port, priv); + return (0); } -static void ftdi_sio_shutdown (struct usb_serial *serial) + +/* Startup for the SIO chip */ +/* Called from usbserial:serial_probe */ +static int ftdi_SIO_startup (struct usb_serial *serial) { - void *priv; + struct ftdi_private *priv; + int err; - dbg("%s", __FUNCTION__); + dbg("%s",__FUNCTION__); - priv = usb_get_serial_port_data(&serial->port[0]); - if (priv){ - kfree(priv); - usb_set_serial_port_data(&serial->port[0], NULL); + err = ftdi_common_startup(serial); + if (err){ + return (err); } + + priv = usb_get_serial_port_data(serial->port); + priv->chip_type = SIO; + priv->baud_base = 12000000 / 16; + priv->write_offset = 1; + + return (0); } +/* Startup for the 8U232AM chip */ +/* Called from usbserial:serial_probe */ +static int ftdi_8U232AM_startup (struct usb_serial *serial) +{ /* ftdi_8U232AM_startup */ + struct ftdi_private *priv; + int err; + + dbg("%s",__FUNCTION__); + err = ftdi_common_startup(serial); + if (err){ + return (err); + } + + priv = usb_get_serial_port_data(serial->port); + priv->chip_type = FT8U232AM; + priv->baud_base = 48000000 / 2; /* Would be / 16, but FTDI supports 0.125, 0.25 and 0.5 divisor fractions! */ + + return (0); +} /* ftdi_8U232AM_startup */ + +/* Startup for the FT232BM chip */ +/* Called from usbserial:serial_probe */ +static int ftdi_FT232BM_startup (struct usb_serial *serial) +{ /* ftdi_FT232BM_startup */ + struct ftdi_private *priv; + int err; + + dbg("%s",__FUNCTION__); + err = ftdi_common_startup(serial); + if (err){ + return (err); + } + + priv = usb_get_serial_port_data(serial->port); + priv->chip_type = FT232BM; + priv->baud_base = 48000000 / 2; /* Would be / 16, but FT232BM supports multiple of 0.125 divisor fractions! */ + + return (0); +} /* ftdi_FT232BM_startup */ + +/* Startup for the USB-UIRT device, which requires hardwired baudrate (38400 gets mapped to 312500) */ +/* Called from usbserial:serial_probe */ +static int ftdi_USB_UIRT_startup (struct usb_serial *serial) +{ /* ftdi_USB_UIRT_startup */ + struct ftdi_private *priv; + int err; + + dbg("%s",__FUNCTION__); + err = ftdi_8U232AM_startup(serial); + if (err){ + return (err); + } + + priv = usb_get_serial_port_data(serial->port); + priv->flags |= ASYNC_SPD_CUST; + priv->custom_divisor = 77; + priv->force_baud = B38400; + + return (0); +} /* ftdi_USB_UIRT_startup */ + +/* Startup for the HE-TIRA1 device, which requires hardwired + * baudrate (38400 gets mapped to 100000) */ +static int ftdi_HE_TIRA1_startup (struct usb_serial *serial) +{ /* ftdi_HE_TIRA1_startup */ + struct ftdi_private *priv; + int err; + + dbg("%s",__FUNCTION__); + err = ftdi_FT232BM_startup(serial); + if (err){ + return (err); + } + + priv = usb_get_serial_port_data(serial->port); + priv->flags |= ASYNC_SPD_CUST; + priv->custom_divisor = 240; + priv->force_baud = B38400; + priv->force_rtscts = 1; + + return (0); +} /* ftdi_HE_TIRA1_startup */ + + +/* ftdi_shutdown is called from usbserial:usb_serial_disconnect + * it is called when the usb device is disconnected + * + * usbserial:usb_serial_disconnect + * calls __serial_close for each open of the port + * shutdown is called then (ie ftdi_shutdown) + */ + + +static void ftdi_shutdown (struct usb_serial *serial) +{ /* ftdi_shutdown */ + + struct usb_serial_port *port = serial->port; + struct ftdi_private *priv = usb_get_serial_port_data(port); + + dbg("%s", __FUNCTION__); + + /* all open ports are closed at this point + * (by usbserial.c:__serial_close, which calls ftdi_close) + */ + + if (priv) { + kfree(priv); + usb_set_serial_port_data(port, NULL); + } +} /* ftdi_shutdown */ -static int ftdi_sio_open (struct usb_serial_port *port, struct file *filp) -{ /* ftdi_sio_open */ +static int ftdi_open (struct usb_serial_port *port, struct file *filp) +{ /* ftdi_open */ struct termios tmp_termios; struct usb_serial *serial = port->serial; + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result = 0; char buf[1]; /* Needed for the usb_control_msg I think */ dbg("%s", __FUNCTION__); - /* This will push the characters through immediately rather - than queue a task to deliver them */ - port->tty->low_latency = 1; + + port->tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0; /* No error checking for this (will get errors later anyway) */ /* See ftdi_sio.h for description of what is reset */ @@ -343,19 +1148,20 @@ static int ftdi_sio_open (struct usb_serial_port *port, struct file *filp) FTDI_SIO_RESET_SIO, 0, buf, 0, WDR_TIMEOUT); - /* Setup termios defaults. According to tty_io.c the - settings are driver specific */ - port->tty->termios->c_cflag = - B9600 | CS8 | CREAD | HUPCL | CLOCAL; + /* Termios defaults are set by usb_serial_init. We don't change + port->tty->termios - this would loose speed settings, etc. + This is same behaviour as serial.c/rs_open() - Kuba */ - /* ftdi_sio_set_termios will send usb control messages */ - ftdi_sio_set_termios(port, &tmp_termios); + /* ftdi_set_termios will send usb control messages */ + ftdi_set_termios(port, &tmp_termios); + /* FIXME: Flow control might be enabled, so it should be checked - + we have no control of defaults! */ /* Turn on RTS and DTR since we are not flow controlling by default */ - if (set_dtr(serial->dev, usb_sndctrlpipe(serial->dev, 0),HIGH) < 0) { + if (set_dtr(port, HIGH) < 0) { err("%s Error from DTR HIGH urb", __FUNCTION__); } - if (set_rts(serial->dev, usb_sndctrlpipe(serial->dev, 0),HIGH) < 0){ + if (set_rts(port, HIGH) < 0){ err("%s Error from RTS HIGH urb", __FUNCTION__); } @@ -363,23 +1169,37 @@ static int ftdi_sio_open (struct usb_serial_port *port, struct file *filp) usb_fill_bulk_urb(port->read_urb, serial->dev, usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, - ftdi_sio_read_bulk_callback, port); + ftdi_read_bulk_callback, port); result = usb_submit_urb(port->read_urb, GFP_KERNEL); if (result) err("%s - failed submitting read urb, error %d", __FUNCTION__, result); + return result; -} /* ftdi_sio_open */ +} /* ftdi_open */ + -static void ftdi_sio_close (struct usb_serial_port *port, struct file *filp) -{ /* ftdi_sio_close */ - struct usb_serial *serial = port->serial; /* Checked in usbserial.c */ +/* + * usbserial:__serial_close only calls ftdi_close if the point is open + * + * This only gets called when it is the last close + * + * + */ + +static void ftdi_close (struct usb_serial_port *port, struct file *filp) +{ /* ftdi_close */ + struct usb_serial *serial; unsigned int c_cflag = port->tty->termios->c_cflag; char buf[1]; dbg("%s", __FUNCTION__); + serial = get_usb_serial ( port, __FUNCTION__); + if (!serial) + return; + if (serial->dev) { if (c_cflag & HUPCL){ /* Disable flow control */ @@ -392,38 +1212,46 @@ static void ftdi_sio_close (struct usb_serial_port *port, struct file *filp) } /* drop DTR */ - if (set_dtr(serial->dev, usb_sndctrlpipe(serial->dev, 0), LOW) < 0){ + if (set_dtr(port, LOW) < 0){ err("Error from DTR LOW urb"); } /* drop RTS */ - if (set_rts(serial->dev, usb_sndctrlpipe(serial->dev, 0),LOW) < 0) { + if (set_rts(port, LOW) < 0) { err("Error from RTS LOW urb"); } + /* shutdown our bulk read */ + if (port->read_urb) { + usb_unlink_urb (port->read_urb); + } + /* unlink the running write urbs */ + + } /* Note change no line is hupcl is off */ + } /* if (serial->dev) */ - /* shutdown our bulk reads and writes */ - /* ***CHECK*** behaviour when there is nothing queued */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->read_urb); - } -} /* ftdi_sio_close */ + +} /* ftdi_close */ -/* The ftdi_sio requires the first byte to have: +/* The SIO requires the first byte to have: * B0 1 * B1 0 * B2..7 length of message excluding byte 0 + * + * The new devices do not require this byte */ -static int ftdi_sio_write (struct usb_serial_port *port, int from_user, +static int ftdi_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) -{ /* ftdi_sio_write */ - struct usb_serial *serial = port->serial; +{ /* ftdi_write */ + struct usb_serial *serial = get_usb_serial ( port, __FUNCTION__); struct ftdi_private *priv = usb_get_serial_port_data(port); - unsigned char *first_byte = port->write_urb->transfer_buffer; - int data_offset ; - int result; - + struct urb *urb; + unsigned char *buffer; + int data_offset ; /* will be 1 for the SIO and 0 otherwise */ + int status; + int transfer_size; + dbg("%s port %d, %d bytes", __FUNCTION__, port->number, count); if (count == 0) { @@ -434,123 +1262,209 @@ static int ftdi_sio_write (struct usb_serial_port *port, int from_user, data_offset = priv->write_offset; dbg("data_offset set to %d",data_offset); - if (port->write_urb->status == -EINPROGRESS) { - dbg("%s - already writing", __FUNCTION__); - return (0); - } + /* Determine total transfer size */ + transfer_size = count; + if (data_offset > 0) { + /* Original sio needs control bytes too... */ + transfer_size += (data_offset * + ((count + (PKTSZ - 1 - data_offset)) / + (PKTSZ - data_offset))); + } - count += data_offset; - count = (count > port->bulk_out_size) ? port->bulk_out_size : count; + buffer = kmalloc (transfer_size, GFP_ATOMIC); + if (!buffer) { + err("%s ran out of kernel memory for urb ...", __FUNCTION__); + return -ENOMEM; + } - /* Copy in the data to send */ - if (from_user) { - if (copy_from_user(port->write_urb->transfer_buffer + data_offset, - buf, count - data_offset )){ - return -EFAULT; + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + err("%s - no more free urbs", __FUNCTION__); + kfree (buffer); + return -ENOMEM; + } + + /* Copy data */ + if (data_offset > 0) { + /* Original sio requires control byte at start of each packet. */ + int user_pktsz = PKTSZ - data_offset; + int todo = count; + unsigned char *first_byte = buffer; + const unsigned char *current_position = buf; + + while (todo > 0) { + if (user_pktsz > todo) { + user_pktsz = todo; + } + /* Write the control byte at the front of the packet*/ + *first_byte = 1 | ((user_pktsz) << 2); + /* Copy data for packet */ + if (from_user) { + if (copy_from_user (first_byte + data_offset, + current_position, user_pktsz)){ + kfree (buffer); + usb_free_urb (urb); + return -EFAULT; + } + } else { + memcpy (first_byte + data_offset, + current_position, user_pktsz); + } + first_byte += user_pktsz + data_offset; + current_position += user_pktsz; + todo -= user_pktsz; } } else { - memcpy(port->write_urb->transfer_buffer + data_offset, - buf, count - data_offset ); - } - - first_byte = port->write_urb->transfer_buffer; - if (data_offset > 0){ - /* Write the control byte at the front of the packet*/ - *first_byte = 1 | ((count-data_offset) << 2) ; + /* No control byte required. */ + /* Copy in the data to send */ + if (from_user) { + if (copy_from_user (buffer, buf, count)) { + kfree (buffer); + usb_free_urb (urb); + return -EFAULT; + } + } else { + memcpy (buffer, buf, count); + } } - dbg("%s Bytes: %d, First Byte: 0x%02x", __FUNCTION__,count, first_byte[0]); - usb_serial_debug_data (__FILE__, __FUNCTION__, count, first_byte); - - /* send the data out the bulk port */ - usb_fill_bulk_urb(port->write_urb, serial->dev, + usb_serial_debug_data (__FILE__, __FUNCTION__, transfer_size, buffer); + + /* fill the buffer and send it */ + usb_fill_bulk_urb(urb, serial->dev, usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress), - port->write_urb->transfer_buffer, count, - ftdi_sio_write_bulk_callback, port); - - result = usb_submit_urb(port->write_urb, GFP_ATOMIC); - if (result) { - err("%s - failed submitting write urb, error %d", __FUNCTION__, result); - return 0; + buffer, transfer_size, + ftdi_write_bulk_callback, port); + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + err("%s - failed submitting write urb, error %d", __FUNCTION__, status); + count = status; } - dbg("%s write returning: %d", __FUNCTION__, count - data_offset); - return (count - data_offset); -} /* ftdi_sio_write */ + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb (urb); + + dbg("%s write returning: %d", __FUNCTION__, count); + return count; +} /* ftdi_write */ + -static void ftdi_sio_write_bulk_callback (struct urb *urb, struct pt_regs *regs) +/* This function may get called when the device is closed */ + +static void ftdi_write_bulk_callback (struct urb *urb, struct pt_regs *regs) { struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - struct usb_serial *serial; + struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); dbg("%s", __FUNCTION__); - if (port_paranoia_check (port, "ftdi_sio_write_bulk_callback")) { - return; - } - - serial = port->serial; - if (serial_paranoia_check (serial, "ftdi_sio_write_bulk_callback")) { + if (port_paranoia_check (port, __FUNCTION__)) return; - } if (urb->status) { dbg("nonzero write bulk status received: %d", urb->status); return; } - schedule_work(&port->work); -} /* ftdi_sio_write_bulk_callback */ + if (!serial) { + dbg("%s - bad serial pointer, exiting", __FUNCTION__); + return; + } + /* Have to check for validity of queueing up the tasks */ + dbg("%s - port->open_count = %d", __FUNCTION__, port->open_count); -static int ftdi_sio_write_room( struct usb_serial_port *port ) + if (port->open_count > 0){ + schedule_work(&port->work); + } + + return; +} /* ftdi_write_bulk_callback */ + + +static int ftdi_write_room( struct usb_serial_port *port ) { - struct ftdi_private *priv = usb_get_serial_port_data(port); - int room; + dbg("%s - port %d", __FUNCTION__, port->number); - if ( port->write_urb->status == -EINPROGRESS) { - /* There is a race here with the _write routines but it won't hurt */ - room = 0; - } else { - room = port->bulk_out_size - priv->write_offset; - } - return(room); -} /* ftdi_sio_write_room */ + /* + * We really can take anything the user throws at us + * but let's pick a nice big number to tell the tty + * layer that we have lots of free space + */ + return 2048; +} /* ftdi_write_room */ + + +static int ftdi_chars_in_buffer (struct usb_serial_port *port) +{ /* ftdi_chars_in_buffer */ + dbg("%s - port %d", __FUNCTION__, port->number); + + /* + * We can't really account for how much data we + * have sent out, but hasn't made it through to the + * device, so just tell the tty layer that everything + * is flushed. + */ + return 0; +} /* ftdi_chars_in_buffer */ -static void ftdi_sio_read_bulk_callback (struct urb *urb, struct pt_regs *regs) -{ /* ftdi_sio_serial_buld_callback */ + +static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs) +{ /* ftdi_read_bulk_callback */ struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial *serial; - struct tty_struct *tty = port->tty ; + struct tty_struct *tty; + struct ftdi_private *priv; char error_flag; unsigned char *data = urb->transfer_buffer; - const int data_offset = 2; int i; int result; + int need_flip; + int packet_offset; - dbg("%s - port %d", __FUNCTION__, port->number); + if (urb->number_of_packets > 0) { + err("%s transfer_buffer_length %d actual_length %d number of packets %d",__FUNCTION__, + urb->transfer_buffer_length, urb->actual_length, urb->number_of_packets ); + err("%s transfer_flags %x ", __FUNCTION__,urb->transfer_flags ); + } + + dbg("%s", __FUNCTION__); - if (port_paranoia_check (port, "ftdi_sio_read_bulk_callback")) { + if (port_paranoia_check (port, __FUNCTION__)) { return; } + if (port->open_count <= 0) + return; - serial = port->serial; - if (serial_paranoia_check (serial, "ftdi_sio_read_bulk_callback")) { + serial = get_usb_serial(port,__FUNCTION__); + if (!serial){ + dbg("%s - bad serial pointer - exiting",__FUNCTION__); + return; + } + + tty = port->tty; + if (!tty) { + dbg("%s - bad tty pointer - exiting",__FUNCTION__); return; } + priv = usb_get_serial_port_data(port); + if (urb->status) { /* This will happen at close every time so it is a dbg not an err */ - dbg("nonzero read bulk status received: %d", urb->status); + dbg("(this is ok on close) nonzero read bulk status received: %d", urb->status); return; } + /* The first two bytes of every read packet are status */ if (urb->actual_length > 2) { usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); } else { - dbg("Just status 0o%03o0o%03o",data[0],data[1]); + dbg("Status only: %03oo %03oo",data[0],data[1]); } @@ -559,129 +1473,101 @@ static void ftdi_sio_read_bulk_callback (struct urb *urb, struct pt_regs *regs) /* See acm.c - you do a tty_hangup - eg tty_hangup(tty) */ /* if CD is dropped and the line is not CLOCAL then we should hangup */ - /* Handle errors and break */ - error_flag = TTY_NORMAL; - /* Although the device uses a bitmask and hence can have multiple */ - /* errors on a packet - the order here sets the priority the */ - /* error is returned to the tty layer */ - - if ( data[1] & FTDI_RS_OE ) { - error_flag = TTY_OVERRUN; - dbg("OVERRRUN error"); - } - if ( data[1] & FTDI_RS_BI ) { - error_flag = TTY_BREAK; - dbg("BREAK received"); - } - if ( data[1] & FTDI_RS_PE ) { - error_flag = TTY_PARITY; - dbg("PARITY error"); - } - if ( data[1] & FTDI_RS_FE ) { - error_flag = TTY_FRAME; - dbg("FRAMING error"); - } - if (urb->actual_length > data_offset) { - - for (i = data_offset ; i < urb->actual_length ; ++i) { - /* have to make sure we don't overflow the buffer - with tty_insert_flip_char's */ - if(tty->flip.count >= TTY_FLIPBUF_SIZE) { - tty_flip_buffer_push(tty); + need_flip = 0; + for (packet_offset=0; packet_offset < urb->actual_length; packet_offset += PKTSZ) { + /* Compare new line status to the old one, signal if different */ + if (priv != NULL) { + char new_status = data[packet_offset+0] & FTDI_STATUS_B0_MASK; + if (new_status != priv->prev_status) { + priv->diff_status |= new_status ^ priv->prev_status; + wake_up_interruptible(&priv->delta_msr_wait); + priv->prev_status = new_status; } - /* Note that the error flag is duplicated for - every character received since we don't know - which character it applied to */ - tty_insert_flip_char(tty, data[i], error_flag); } - tty_flip_buffer_push(tty); + /* Handle errors and break */ + error_flag = TTY_NORMAL; + /* Although the device uses a bitmask and hence can have multiple */ + /* errors on a packet - the order here sets the priority the */ + /* error is returned to the tty layer */ - } + if ( data[packet_offset+1] & FTDI_RS_OE ) { + error_flag = TTY_OVERRUN; + dbg("OVERRRUN error"); + } + if ( data[packet_offset+1] & FTDI_RS_BI ) { + error_flag = TTY_BREAK; + dbg("BREAK received"); + } + if ( data[packet_offset+1] & FTDI_RS_PE ) { + error_flag = TTY_PARITY; + dbg("PARITY error"); + } + if ( data[packet_offset+1] & FTDI_RS_FE ) { + error_flag = TTY_FRAME; + dbg("FRAMING error"); + } + if (urb->actual_length > packet_offset + 2) { + for (i = 2; (i < PKTSZ) && ((i+packet_offset) < urb->actual_length); ++i) { + /* have to make sure we don't overflow the buffer + with tty_insert_flip_char's */ + if(tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + /* Note that the error flag is duplicated for + every character received since we don't know + which character it applied to */ + tty_insert_flip_char(tty, data[packet_offset+i], error_flag); + } + need_flip = 1; + } #ifdef NOT_CORRECT_BUT_KEEPING_IT_FOR_NOW - /* if a parity error is detected you get status packets forever - until a character is sent without a parity error. - This doesn't work well since the application receives a never - ending stream of bad data - even though new data hasn't been sent. - Therefore I (bill) have taken this out. - However - this might make sense for framing errors and so on - so I am leaving the code in for now. - */ - else { - if (error_flag != TTY_NORMAL){ - dbg("error_flag is not normal"); + /* if a parity error is detected you get status packets forever + until a character is sent without a parity error. + This doesn't work well since the application receives a never + ending stream of bad data - even though new data hasn't been sent. + Therefore I (bill) have taken this out. + However - this might make sense for framing errors and so on + so I am leaving the code in for now. + */ + else { + if (error_flag != TTY_NORMAL){ + dbg("error_flag is not normal"); /* In this case it is just status - if that is an error send a bad character */ if(tty->flip.count >= TTY_FLIPBUF_SIZE) { tty_flip_buffer_push(tty); } tty_insert_flip_char(tty, 0xff, error_flag); - tty_flip_buffer_push(tty); + need_flip = 1; + } } - } #endif + } /* "for(packet_offset=0..." */ - /* Continue trying to always read */ - usb_fill_bulk_urb(port->read_urb, serial->dev, - usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), - port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, - ftdi_sio_read_bulk_callback, port); + /* Low latency */ + if (need_flip) { + tty_flip_buffer_push(tty); + } - result = usb_submit_urb(port->read_urb, GFP_ATOMIC); - if (result) - err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result); + /* if the port is closed stop trying to read */ + if (port->open_count > 0){ + /* Continue trying to always read */ + usb_fill_bulk_urb(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, + ftdi_read_bulk_callback, port); + + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result); + } return; -} /* ftdi_sio_serial_read_bulk_callback */ +} /* ftdi_read_bulk_callback */ -static __u16 translate_baudrate_to_ftdi(unsigned int cflag, enum ftdi_type ftdi_type) -{ /* translate_baudrate_to_ftdi */ - - __u16 urb_value = ftdi_sio_b9600; - - if (ftdi_type == sio){ - switch(cflag & CBAUD){ - case B0: break; /* ignored by this */ - case B300: urb_value = ftdi_sio_b300; dbg("Set to 300"); break; - case B600: urb_value = ftdi_sio_b600; dbg("Set to 600") ; break; - case B1200: urb_value = ftdi_sio_b1200; dbg("Set to 1200") ; break; - case B2400: urb_value = ftdi_sio_b2400; dbg("Set to 2400") ; break; - case B4800: urb_value = ftdi_sio_b4800; dbg("Set to 4800") ; break; - case B9600: urb_value = ftdi_sio_b9600; dbg("Set to 9600") ; break; - case B19200: urb_value = ftdi_sio_b19200; dbg("Set to 19200") ; break; - case B38400: urb_value = ftdi_sio_b38400; dbg("Set to 38400") ; break; - case B57600: urb_value = ftdi_sio_b57600; dbg("Set to 57600") ; break; - case B115200: urb_value = ftdi_sio_b115200; dbg("Set to 115200") ; break; - default: dbg("%s - FTDI_SIO does not support the baudrate (%d) requested", - __FUNCTION__, (cflag & CBAUD)); - break; - } - } else { /* it is 8U232AM */ - switch(cflag & CBAUD){ - case B0: break; /* ignored by this */ - case B300: urb_value = ftdi_8U232AM_48MHz_b300; dbg("Set to 300"); break; - case B600: urb_value = ftdi_8U232AM_48MHz_b600; dbg("Set to 600") ; break; - case B1200: urb_value = ftdi_8U232AM_48MHz_b1200; dbg("Set to 1200") ; break; - case B2400: urb_value = ftdi_8U232AM_48MHz_b2400; dbg("Set to 2400") ; break; - case B4800: urb_value = ftdi_8U232AM_48MHz_b4800; dbg("Set to 4800") ; break; - case B9600: urb_value = ftdi_8U232AM_48MHz_b9600; dbg("Set to 9600") ; break; - case B19200: urb_value = ftdi_8U232AM_48MHz_b19200; dbg("Set to 19200") ; break; - case B38400: urb_value = ftdi_8U232AM_48MHz_b38400; dbg("Set to 38400") ; break; - case B57600: urb_value = ftdi_8U232AM_48MHz_b57600; dbg("Set to 57600") ; break; - case B115200: urb_value = ftdi_8U232AM_48MHz_b115200; dbg("Set to 115200") ; break; - case B230400: urb_value = ftdi_8U232AM_48MHz_b230400; dbg("Set to 230400") ; break; - case B460800: urb_value = ftdi_8U232AM_48MHz_b460800; dbg("Set to 460800") ; break; - case B921600: urb_value = ftdi_8U232AM_48MHz_b921600; dbg("Set to 921600") ; break; - default: dbg("%s - The baudrate (%d) requested is not implemented", - __FUNCTION__, (cflag & CBAUD)); - break; - } - } - return(urb_value); -} - -static void ftdi_sio_break_ctl( struct usb_serial_port *port, int break_state ) +static void ftdi_break_ctl( struct usb_serial_port *port, int break_state ) { struct usb_serial *serial = port->serial; struct ftdi_private *priv = usb_get_serial_port_data(port); @@ -704,32 +1590,48 @@ static void ftdi_sio_break_ctl( struct usb_serial_port *port, int break_state ) FTDI_SIO_SET_DATA_REQUEST_TYPE, urb_value , 0, buf, 0, WDR_TIMEOUT) < 0) { - err("%s - FAILED to enable/disable break state (state was %d)", - __FUNCTION__, break_state); + err("%s FAILED to enable/disable break state (state was %d)", __FUNCTION__,break_state); } - dbg("%s - break state is %d - urb is %d", __FUNCTION__, break_state, urb_value); + dbg("%s break state is %d - urb is %d", __FUNCTION__,break_state, urb_value); } +/* old_termios contains the original termios settings and tty->termios contains + * the new setting to be used + * WARNING: set_termios calls this with old_termios in kernel space + */ -/* As I understand this - old_termios contains the original termios settings */ -/* and tty->termios contains the new setting to be used */ -/* */ -/* WARNING: set_termios calls this with old_termios in kernel space */ - -static void ftdi_sio_set_termios (struct usb_serial_port *port, struct termios *old_termios) -{ /* ftdi_sio_set_termios */ +static void ftdi_set_termios (struct usb_serial_port *port, struct termios *old_termios) +{ /* ftdi_termios */ struct usb_serial *serial = port->serial; unsigned int cflag = port->tty->termios->c_cflag; struct ftdi_private *priv = usb_get_serial_port_data(port); __u16 urb_value; /* will hold the new flags */ char buf[1]; /* Perhaps I should dynamically alloc this? */ + // Added for xon/xoff support + unsigned int iflag = port->tty->termios->c_iflag; + unsigned char vstop; + unsigned char vstart; dbg("%s", __FUNCTION__); + /* Force baud rate if this device requires it, unless it is set to B0. */ + if (priv->force_baud && ((port->tty->termios->c_cflag & CBAUD) != B0)) { + dbg("%s: forcing baud rate for this device", __FUNCTION__); + port->tty->termios->c_cflag &= ~CBAUD; + port->tty->termios->c_cflag |= priv->force_baud; + } + + /* Force RTS-CTS if this device requires it. */ + if (priv->force_rtscts) { + dbg("%s: forcing rtscts for this device", __FUNCTION__); + port->tty->termios->c_cflag |= CRTSCTS; + } + + cflag = port->tty->termios->c_cflag; /* FIXME -For this cut I don't care if the line is really changing or not - so just do the change regardless - should be able to @@ -771,8 +1673,6 @@ static void ftdi_sio_set_termios (struct usb_serial_port *port, struct termios * } /* Now do the baudrate */ - urb_value = translate_baudrate_to_ftdi((cflag & CBAUD), priv->ftdi_type); - if ((cflag & CBAUD) == B0 ) { /* Disable flow control */ if (usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), @@ -783,24 +1683,20 @@ static void ftdi_sio_set_termios (struct usb_serial_port *port, struct termios * err("%s error from disable flowcontrol urb", __FUNCTION__); } /* Drop RTS and DTR */ - if (set_dtr(serial->dev, usb_sndctrlpipe(serial->dev, 0),LOW) < 0){ + if (set_dtr(port, LOW) < 0){ err("%s Error from DTR LOW urb", __FUNCTION__); } - if (set_rts(serial->dev, usb_sndctrlpipe(serial->dev, 0),LOW) < 0){ + if (set_rts(port, LOW) < 0){ err("%s Error from RTS LOW urb", __FUNCTION__); } } else { /* set the baudrate determined before */ - if (usb_control_msg(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - FTDI_SIO_SET_BAUDRATE_REQUEST, - FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, - urb_value, 0, - buf, 0, 100) < 0) { + if (change_speed(port)) { err("%s urb failed to set baurdrate", __FUNCTION__); } } + /* Set flow control */ /* Note device also supports DTR/CD (ugh) and Xon/Xoff in hardware */ if (cflag & CRTSCTS) { @@ -815,146 +1711,290 @@ static void ftdi_sio_set_termios (struct usb_serial_port *port, struct termios * } } else { - /* CHECKME Assuming XON/XOFF handled by tty stack - not by device */ - dbg("%s Turning off hardware flow control", __FUNCTION__); - if (usb_control_msg(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - FTDI_SIO_SET_FLOW_CTRL_REQUEST, - FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, - 0, 0, - buf, 0, WDR_TIMEOUT) < 0) { - err("urb failed to clear flow control"); - } + /* + * Xon/Xoff code + * + * Check the IXOFF status in the iflag component of the termios structure + * if IXOFF is not set, the pre-xon/xoff code is executed. + */ + if (iflag & IXOFF) { + dbg("%s request to enable xonxoff iflag=%04x",__FUNCTION__,iflag); + // Try to enable the XON/XOFF on the ftdi_sio + // Set the vstart and vstop -- could have been done up above where + // a lot of other dereferencing is done but that would be very + // inefficient as vstart and vstop are not always needed + vstart=port->tty->termios->c_cc[VSTART]; + vstop=port->tty->termios->c_cc[VSTOP]; + urb_value=(vstop << 8) | (vstart); + + if (usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + urb_value , FTDI_SIO_XON_XOFF_HS, + buf, 0, WDR_TIMEOUT) < 0) { + err("urb failed to set to xon/xoff flow control"); + } + } else { + /* else clause to only run if cfag ! CRTSCTS and iflag ! XOFF */ + /* CHECKME Assuming XON/XOFF handled by tty stack - not by device */ + dbg("%s Turning off hardware flow control", __FUNCTION__); + if (usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + 0, 0, + buf, 0, WDR_TIMEOUT) < 0) { + err("urb failed to clear flow control"); + } + } } return; -} /* ftdi_sio_set_termios */ +} /* ftdi_termios */ -static int ftdi_sio_tiocmget (struct usb_serial_port *port, struct file *file) + +static int ftdi_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg) { struct usb_serial *serial = port->serial; struct ftdi_private *priv = usb_get_serial_port_data(port); - char *buf = NULL; - int ret = -EINVAL; - int size; + + __u16 urb_value=0; /* Will hold the new flags */ + char buf[2]; + int ret, mask; - dbg("%s", __FUNCTION__); + dbg("%s cmd 0x%04x", __FUNCTION__, cmd); - buf = kmalloc(2, GFP_KERNEL); - if (!buf) - goto exit; + /* Based on code from acm.c and others */ + switch (cmd) { - if (priv->ftdi_type == sio) { - size = 1; - } else { - /* the 8U232AM returns a two byte value (the sio is a 1 byte value) - in the same - format as the data returned from the in point */ - size = 2; - } - ret = usb_control_msg(serial->dev, - usb_rcvctrlpipe(serial->dev, 0), - FTDI_SIO_GET_MODEM_STATUS_REQUEST, - FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE, - 0, 0, buf, size, WDR_TIMEOUT); - if (ret < 0) { - err("%s Could not get modem status of device - err: %d", - __FUNCTION__, ret); - goto exit; - } - - ret = (buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) | - (buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) | - (buf[0] & FTDI_SIO_RI_MASK ? TIOCM_RI : 0) | - (buf[0] & FTDI_SIO_RLSD_MASK ? TIOCM_CD : 0); - -exit: - kfree(buf); - return ret; -} + case TIOCMGET: + dbg("%s TIOCMGET", __FUNCTION__); + switch (priv->chip_type) { + case SIO: + /* Request the status from the device */ + if ((ret = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + FTDI_SIO_GET_MODEM_STATUS_REQUEST, + FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE, + 0, 0, + buf, 1, WDR_TIMEOUT)) < 0 ) { + err("%s Could not get modem status of device - err: %d", __FUNCTION__, + ret); + return(ret); + } + break; + case FT8U232AM: + case FT232BM: + /* the 8U232AM returns a two byte value (the sio is a 1 byte value) - in the same + format as the data returned from the in point */ + if ((ret = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + FTDI_SIO_GET_MODEM_STATUS_REQUEST, + FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE, + 0, 0, + buf, 2, WDR_TIMEOUT)) < 0 ) { + err("%s Could not get modem status of device - err: %d", __FUNCTION__, + ret); + return(ret); + } + break; + default: + return -EFAULT; + break; + } -static int ftdi_sio_tiocmset (struct usb_serial_port *port, struct file *file, - unsigned int set, unsigned int clear) -{ - struct usb_serial *serial = port->serial; - int ret = 0; - - dbg("%s", __FUNCTION__); + return put_user((buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) | + (buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) | + (buf[0] & FTDI_SIO_RI_MASK ? TIOCM_RI : 0) | + (buf[0] & FTDI_SIO_RLSD_MASK ? TIOCM_CD : 0) | + priv->last_dtr_rts, + (unsigned long *) arg); + break; - if (set & TIOCM_RTS) - if ((ret = set_rts(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - HIGH)) < 0) { - err("Urb to set RTS failed"); - goto exit; + case TIOCMSET: /* Turns on and off the lines as specified by the mask */ + dbg("%s TIOCMSET", __FUNCTION__); + if (get_user(mask, (unsigned long *) arg)) + return -EFAULT; + urb_value = ((mask & TIOCM_DTR) ? HIGH : LOW); + if ((ret = set_dtr(port, urb_value)) < 0){ + err("Error from DTR set urb (TIOCMSET)"); + return(ret); } - - if (set & TIOCM_DTR) - if ((ret = set_dtr(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - HIGH)) < 0) { - err("Urb to set DTR failed"); - goto exit; + urb_value = ((mask & TIOCM_RTS) ? HIGH : LOW); + if ((ret = set_rts(port, urb_value)) < 0){ + err("Error from RTS set urb (TIOCMSET)"); + return(ret); } - - if (clear & TIOCM_RTS) - if ((ret = set_rts(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - LOW)) < 0) { - err("Urb to unset RTS failed"); - goto exit; + return(0); + break; + + case TIOCMBIS: /* turns on (Sets) the lines as specified by the mask */ + dbg("%s TIOCMBIS", __FUNCTION__); + if (get_user(mask, (unsigned long *) arg)) + return -EFAULT; + if (mask & TIOCM_DTR){ + if ((ret = set_dtr(port, HIGH)) < 0) { + err("Urb to set DTR failed"); + return(ret); + } + } + if (mask & TIOCM_RTS) { + if ((ret = set_rts(port, HIGH)) < 0){ + err("Urb to set RTS failed"); + return(ret); + } } + return(0); + break; - if (clear & TIOCM_DTR) - if ((ret = set_dtr(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - LOW)) < 0) { - err("Urb to unset DTR failed"); - goto exit; + case TIOCMBIC: /* turns off (Clears) the lines as specified by the mask */ + dbg("%s TIOCMBIC", __FUNCTION__); + if (get_user(mask, (unsigned long *) arg)) + return -EFAULT; + if (mask & TIOCM_DTR){ + if ((ret = set_dtr(port, LOW)) < 0){ + err("Urb to unset DTR failed"); + return(ret); + } + } + if (mask & TIOCM_RTS) { + if ((ret = set_rts(port, LOW)) < 0){ + err("Urb to unset RTS failed"); + return(ret); + } } + return(0); + break; -exit: - return ret; -} + /* + * I had originally implemented TCSET{A,S}{,F,W} and + * TCGET{A,S} here separately, however when testing I + * found that the higher layers actually do the termios + * conversions themselves and pass the call onto + * ftdi_sio_set_termios. + * + */ + + case TIOCGSERIAL: /* gets serial port data */ + return get_serial_info(port, (struct serial_struct *) arg); + + case TIOCSSERIAL: /* sets serial port data */ + return set_serial_info(port, (struct serial_struct *) arg); + + /* + * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change + * - mask passed in arg for lines of interest + * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) + * Caller should use TIOCGICOUNT to see which one it was. + * + * This code is borrowed from linux/drivers/char/serial.c + */ + case TIOCMIWAIT: + while (priv != NULL) { + interruptible_sleep_on(&priv->delta_msr_wait); + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + else { + char diff = priv->diff_status; + + if (diff == 0) { + return -EIO; /* no change => error */ + } -static int ftdi_sio_ioctl (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg) -{ - dbg("%s cmd 0x%04x", __FUNCTION__, cmd); + /* Consume all events */ + priv->diff_status = 0; - switch (cmd) { + /* Return 0 if caller wanted to know about these bits */ + if ( ((arg & TIOCM_RNG) && (diff & FTDI_RS0_RI)) || + ((arg & TIOCM_DSR) && (diff & FTDI_RS0_DSR)) || + ((arg & TIOCM_CD) && (diff & FTDI_RS0_RLSD)) || + ((arg & TIOCM_CTS) && (diff & FTDI_RS0_CTS)) ) { + return 0; + } + /* + * Otherwise caller can't care less about what happened, + * and so we continue to wait for more events. + */ + } + } + return(0); + break; default: - /* This is not an error - turns out the higher layers will do - * some ioctls itself - */ - dbg("%s arg not supported - it was 0x%04x", __FUNCTION__,cmd); - return(-ENOIOCTLCMD); break; + } - return 0; -} /* ftdi_sio_ioctl */ -static int __init ftdi_sio_init (void) + /* This is not necessarily an error - turns out the higher layers will do + * some ioctls itself (see comment above) + */ + dbg("%s arg not supported - it was 0x%04x - check /usr/include/asm/ioctls.h", __FUNCTION__, cmd); + + return(-ENOIOCTLCMD); +} /* ftdi_ioctl */ + + +static void ftdi_throttle (struct usb_serial_port *port) +{ + dbg("%s - port %d", __FUNCTION__, port->number); + usb_unlink_urb (port->read_urb); +} + + +static void ftdi_unthrottle (struct usb_serial_port *port) { + int result; + struct usb_serial *serial = port->serial; + + dbg("%s - port %d", __FUNCTION__, port->number); + + port->read_urb->dev = serial->dev; + + usb_fill_bulk_urb(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, + ftdi_read_bulk_callback, port); + + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + err("%s - failed submitting read urb, error %d", __FUNCTION__, result); +} + +static int __init ftdi_init (void) +{ + dbg("%s", __FUNCTION__); - usb_serial_register (&ftdi_sio_device); + usb_serial_register (&ftdi_SIO_device); usb_serial_register (&ftdi_8U232AM_device); + usb_serial_register (&ftdi_FT232BM_device); + usb_serial_register (&ftdi_USB_UIRT_device); + usb_serial_register (&ftdi_HE_TIRA1_device); usb_register (&ftdi_driver); + info(DRIVER_VERSION ":" DRIVER_DESC); return 0; } -static void __exit ftdi_sio_exit (void) +static void __exit ftdi_exit (void) { + dbg("%s", __FUNCTION__); + usb_deregister (&ftdi_driver); - usb_serial_deregister (&ftdi_sio_device); + usb_serial_deregister (&ftdi_HE_TIRA1_device); + usb_serial_deregister (&ftdi_USB_UIRT_device); + usb_serial_deregister (&ftdi_FT232BM_device); usb_serial_deregister (&ftdi_8U232AM_device); + usb_serial_deregister (&ftdi_SIO_device); + } -module_init(ftdi_sio_init); -module_exit(ftdi_sio_exit); +module_init(ftdi_init); +module_exit(ftdi_exit); MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index b03067d36920..9da692ce1c7f 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -14,11 +14,12 @@ * of the protocol required to talk to the device and ongoing assistence * during development. * - * Bill Ryder - bryder@sgi.com of Silicon Graphics, Inc.- wrote the + * Bill Ryder - bryder@sgi.com formerly of Silicon Graphics, Inc.- wrote the * FTDI_SIO implementation. * * Philipp Gühring - pg@futureware.at - added the Device ID of the USB relais * from Rudolf Gugler + * */ #define FTDI_VID 0x0403 /* Vendor Id */ @@ -28,6 +29,93 @@ #define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */ #define FTDI_NF_RIC_PID 0x0001 /* Product Id */ + +/* www.crystalfontz.com devices - thanx for providing free devices for evaluation ! */ +/* they use the ftdi chipset for the USB interface and the vendor id is the same */ +#define FTDI_XF_634_PID 0xFC09 /* Four line device */ +#define FTDI_XF_632_PID 0xFC08 /* Two line device */ + +/* Video Networks Limited / Homechoice in the UK use an ftdi-based device for their 1Mb */ +/* broadband internet service. The following PID is exhibited by the usb device supplied */ +/* (the VID is the standard ftdi vid (FTDI_VID) */ +#define FTDI_VNHCPCUSB_D_PID 0xfe38 /* Product Id */ + +/* + * The following are the values for the Matrix Orbital LCD displays, + * which are the FT232BM ( similar to the 8U232AM ) + */ +#define FTDI_MTXORB_VID FTDI_VID /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_0_PID 0xFA00 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_1_PID 0xFA01 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_2_PID 0xFA02 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_3_PID 0xFA03 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_4_PID 0xFA04 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_5_PID 0xFA05 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_6_PID 0xFA06 /* Matrix Orbital Product Id */ + +/* + * The following are the values for the Sealevel SeaLINK+ adapters. + * (Original list sent by Tuan Hoang. Ian Abbott renamed the macros and + * removed some PIDs that don't seem to match any existing products.) + */ +#define SEALEVEL_VID 0x0c52 /* Sealevel Vendor ID */ +#define SEALEVEL_2101_PID 0x2101 /* SeaLINK+232 (2101/2105) */ +#define SEALEVEL_2102_PID 0x2102 /* SeaLINK+485 (2102) */ +#define SEALEVEL_2103_PID 0x2103 /* SeaLINK+232I (2103) */ +#define SEALEVEL_2104_PID 0x2104 /* SeaLINK+485I (2104) */ +#define SEALEVEL_2201_1_PID 0x2211 /* SeaPORT+2/232 (2201) Port 1 */ +#define SEALEVEL_2201_2_PID 0x2221 /* SeaPORT+2/232 (2201) Port 2 */ +#define SEALEVEL_2202_1_PID 0x2212 /* SeaPORT+2/485 (2202) Port 1 */ +#define SEALEVEL_2202_2_PID 0x2222 /* SeaPORT+2/485 (2202) Port 2 */ +#define SEALEVEL_2203_1_PID 0x2213 /* SeaPORT+2 (2203) Port 1 */ +#define SEALEVEL_2203_2_PID 0x2223 /* SeaPORT+2 (2203) Port 2 */ +#define SEALEVEL_2401_1_PID 0x2411 /* SeaPORT+4/232 (2401) Port 1 */ +#define SEALEVEL_2401_2_PID 0x2421 /* SeaPORT+4/232 (2401) Port 2 */ +#define SEALEVEL_2401_3_PID 0x2431 /* SeaPORT+4/232 (2401) Port 3 */ +#define SEALEVEL_2401_4_PID 0x2441 /* SeaPORT+4/232 (2401) Port 4 */ +#define SEALEVEL_2402_1_PID 0x2412 /* SeaPORT+4/485 (2402) Port 1 */ +#define SEALEVEL_2402_2_PID 0x2422 /* SeaPORT+4/485 (2402) Port 2 */ +#define SEALEVEL_2402_3_PID 0x2432 /* SeaPORT+4/485 (2402) Port 3 */ +#define SEALEVEL_2402_4_PID 0x2442 /* SeaPORT+4/485 (2402) Port 4 */ +#define SEALEVEL_2403_1_PID 0x2413 /* SeaPORT+4 (2403) Port 1 */ +#define SEALEVEL_2403_2_PID 0x2423 /* SeaPORT+4 (2403) Port 2 */ +#define SEALEVEL_2403_3_PID 0x2433 /* SeaPORT+4 (2403) Port 3 */ +#define SEALEVEL_2403_4_PID 0x2443 /* SeaPORT+4 (2403) Port 4 */ +#define SEALEVEL_2801_1_PID 0X2811 /* SeaLINK+8/232 (2801) Port 1 */ +#define SEALEVEL_2801_2_PID 0X2821 /* SeaLINK+8/232 (2801) Port 2 */ +#define SEALEVEL_2801_3_PID 0X2831 /* SeaLINK+8/232 (2801) Port 3 */ +#define SEALEVEL_2801_4_PID 0X2841 /* SeaLINK+8/232 (2801) Port 4 */ +#define SEALEVEL_2801_5_PID 0X2851 /* SeaLINK+8/232 (2801) Port 5 */ +#define SEALEVEL_2801_6_PID 0X2861 /* SeaLINK+8/232 (2801) Port 6 */ +#define SEALEVEL_2801_7_PID 0X2871 /* SeaLINK+8/232 (2801) Port 7 */ +#define SEALEVEL_2801_8_PID 0X2881 /* SeaLINK+8/232 (2801) Port 8 */ +#define SEALEVEL_2802_1_PID 0X2812 /* SeaLINK+8/485 (2802) Port 1 */ +#define SEALEVEL_2802_2_PID 0X2822 /* SeaLINK+8/485 (2802) Port 2 */ +#define SEALEVEL_2802_3_PID 0X2832 /* SeaLINK+8/485 (2802) Port 3 */ +#define SEALEVEL_2802_4_PID 0X2842 /* SeaLINK+8/485 (2802) Port 4 */ +#define SEALEVEL_2802_5_PID 0X2852 /* SeaLINK+8/485 (2802) Port 5 */ +#define SEALEVEL_2802_6_PID 0X2862 /* SeaLINK+8/485 (2802) Port 6 */ +#define SEALEVEL_2802_7_PID 0X2872 /* SeaLINK+8/485 (2802) Port 7 */ +#define SEALEVEL_2802_8_PID 0X2882 /* SeaLINK+8/485 (2802) Port 8 */ +#define SEALEVEL_2803_1_PID 0X2813 /* SeaLINK+8 (2803) Port 1 */ +#define SEALEVEL_2803_2_PID 0X2823 /* SeaLINK+8 (2803) Port 2 */ +#define SEALEVEL_2803_3_PID 0X2833 /* SeaLINK+8 (2803) Port 3 */ +#define SEALEVEL_2803_4_PID 0X2843 /* SeaLINK+8 (2803) Port 4 */ +#define SEALEVEL_2803_5_PID 0X2853 /* SeaLINK+8 (2803) Port 5 */ +#define SEALEVEL_2803_6_PID 0X2863 /* SeaLINK+8 (2803) Port 6 */ +#define SEALEVEL_2803_7_PID 0X2873 /* SeaLINK+8 (2803) Port 7 */ +#define SEALEVEL_2803_8_PID 0X2883 /* SeaLINK+8 (2803) Port 8 */ + +/* + * Home Electronics (www.home-electro.com) USB gadgets + */ +#define FTDI_HE_TIRA1_PID 0xFA78 /* Tira-1 IR tranceiver */ + +/* USB-UIRT - An infrared receiver and transmitter using the 8U232AM chip */ +/* http://home.earthlink.net/~jrhees/USBUIRT/index.htm */ +#define FTDI_USB_UIRT_PID 0xF850 /* Product Id */ + +/* Commands */ #define FTDI_SIO_RESET 0 /* Reset the port */ #define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ #define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ @@ -84,19 +172,61 @@ /* * BmRequestType: 0100 0000B * bRequest: FTDI_SIO_SET_BAUDRATE - * wValue: BaudRate value - see below + * wValue: BaudDivisor value - see below * wIndex: Port * wLength: 0 * Data: None + * The BaudDivisor values are calculated as follows: + * - BaseClock is either 12000000 or 48000000 depending on the device. FIXME: I wish + * I knew how to detect old chips to select proper base clock! + * - BaudDivisor is a fixed point number encoded in a funny way. + * (--WRONG WAY OF THINKING--) + * BaudDivisor is a fixed point number encoded with following bit weighs: + * (-2)(-1)(13..0). It is a radical with a denominator of 4, so values + * end with 0.0 (00...), 0.25 (10...), 0.5 (01...), and 0.75 (11...). + * (--THE REALITY--) + * The both-bits-set has quite different meaning from 0.75 - the chip designers + * have decided it to mean 0.125 instead of 0.75. + * This info looked up in FTDI application note "FT8U232 DEVICES \ Data Rates + * and Flow Control Consideration for USB to RS232". + * - BaudDivisor = (BaseClock / 16) / BaudRate, where the (=) operation should + * automagically re-encode the resulting value to take fractions into consideration. + * As all values are integers, some bit twiddling is in order: + * BaudDivisor = (BaseClock / 16 / BaudRate) | + * (((BaseClock / 2 / BaudRate) & 4) ? 0x4000 // 0.5 + * : ((BaseClock / 2 / BaudRate) & 2) ? 0x8000 // 0.25 + * : ((BaseClock / 2 / BaudRate) & 1) ? 0xc000 // 0.125 + * : 0) + * + * For the FT232BM, a 17th divisor bit was introduced to encode the multiples + * of 0.125 missing from the FT8U232AM. Bits 16 to 14 are coded as follows + * (the first four codes are the same as for the FT8U232AM, where bit 16 is + * always 0): + * 000 - add .000 to divisor + * 001 - add .500 to divisor + * 010 - add .250 to divisor + * 011 - add .125 to divisor + * 100 - add .375 to divisor + * 101 - add .625 to divisor + * 110 - add .750 to divisor + * 111 - add .875 to divisor + * Bits 15 to 0 of the 17-bit divisor are placed in the urb value. Bit 16 is + * placed in bit 0 of the urb index. + * + * Note that there are a couple of special cases to support the highest baud + * rates. If the calculated divisor value is 1, this needs to be replaced with + * 0. Additionally for the FT232BM, if the calculated divisor value is 0x4001 + * (1.5), this needs to be replaced with 0x0001 (1) (but this divisor value is + * not supported by the FT8U232AM). */ -enum ftdi_type { - sio = 1, - F8U232AM = 2, -}; +typedef enum { + SIO = 1, + FT8U232AM = 2, + FT232BM = 3, +} ftdi_chip_type_t; - -enum { +typedef enum { ftdi_sio_b300 = 0, ftdi_sio_b600 = 1, ftdi_sio_b1200 = 2, @@ -107,39 +237,12 @@ enum { ftdi_sio_b38400 = 7, ftdi_sio_b57600 = 8, ftdi_sio_b115200 = 9 -}; - - -enum { - ftdi_8U232AM_12MHz_b300 = 0x09c4, - ftdi_8U232AM_12MHz_b600 = 0x04E2, - ftdi_8U232AM_12MHz_b1200 = 0x0271, - ftdi_8U232AM_12MHz_b2400 = 0x4138, - ftdi_8U232AM_12MHz_b4800 = 0x809c, - ftdi_8U232AM_12MHz_b9600 = 0xc04e, - ftdi_8U232AM_12MHz_b19200 = 0x0027, - ftdi_8U232AM_12MHz_b38400 = 0x4013, - ftdi_8U232AM_12MHz_b57600 = 0x000d, - ftdi_8U232AM_12MHz_b115200 = 0x4006, - ftdi_8U232AM_12MHz_b230400 = 0x8003, -}; -/* Apparently all devices are 48MHz */ -enum { - ftdi_8U232AM_48MHz_b300 = 0x2710, - ftdi_8U232AM_48MHz_b600 = 0x1388, - ftdi_8U232AM_48MHz_b1200 = 0x09c4, - ftdi_8U232AM_48MHz_b2400 = 0x04e2, - ftdi_8U232AM_48MHz_b4800 = 0x0271, - ftdi_8U232AM_48MHz_b9600 = 0x4138, - ftdi_8U232AM_48MHz_b19200 = 0x809c, - ftdi_8U232AM_48MHz_b38400 = 0xc04e, - ftdi_8U232AM_48MHz_b57600 = 0x0034, - ftdi_8U232AM_48MHz_b115200 = 0x001a, - ftdi_8U232AM_48MHz_b230400 = 0x000d, - ftdi_8U232AM_48MHz_b460800 = 0x4006, - ftdi_8U232AM_48MHz_b921600 = 0x8003, - -}; +} FTDI_SIO_baudrate_t ; + +/* + * The ftdi_8U232AM_xxMHz_byyy constants have been removed. The encoded divisor values + * are calculated internally. + */ #define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA #define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40 @@ -443,6 +546,11 @@ enum { * B7 Error in RCVR FIFO * */ +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + #define FTDI_RS_DR 1 #define FTDI_RS_OE (1<<1) #define FTDI_RS_PE (1<<2) |
