diff options
author | David Woodhouse <dwmw2@shinybook.infradead.org> | 2005-06-18 08:36:46 +0100 |
---|---|---|
committer | David Woodhouse <dwmw2@shinybook.infradead.org> | 2005-06-18 08:36:46 +0100 |
commit | 0107b3cf3225aed6ddde4fa8dbcd4ed643b34f4d (patch) | |
tree | 9b9337ae627fc56a0eda43c60860765f25efaa0b /drivers/usb | |
parent | 1c3f45ab2f7f879ea482501c83899505c31f7539 (diff) | |
parent | 9ee1c939d1cb936b1f98e8d81aeffab57bae46ab (diff) |
Merge with master.kernel.org:/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/sysfs.c | 22 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 5 | ||||
-rw-r--r-- | drivers/usb/input/hid-core.c | 18 | ||||
-rw-r--r-- | drivers/usb/media/pwc/ChangeLog | 143 | ||||
-rw-r--r-- | drivers/usb/media/pwc/pwc-if.c | 4 | ||||
-rw-r--r-- | drivers/usb/net/kaweth.c | 2 | ||||
-rw-r--r-- | drivers/usb/net/usbnet.c | 2 | ||||
-rw-r--r-- | drivers/usb/serial/Kconfig | 11 | ||||
-rw-r--r-- | drivers/usb/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/serial/cp2101.c | 363 | ||||
-rw-r--r-- | drivers/usb/serial/ftdi_sio.c | 118 | ||||
-rw-r--r-- | drivers/usb/serial/option.c | 729 | ||||
-rw-r--r-- | drivers/usb/storage/unusual_devs.h | 9 |
13 files changed, 1150 insertions, 277 deletions
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 4ab50009291d..4d0c9e65cd03 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -290,32 +290,30 @@ static ssize_t show_modalias(struct device *dev, char *buf) { struct usb_interface *intf; struct usb_device *udev; + int len; intf = to_usb_interface(dev); udev = interface_to_usbdev(intf); - if (udev->descriptor.bDeviceClass == 0) { - struct usb_host_interface *alt = intf->cur_altsetting; - return sprintf(buf, "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02Xic%02Xisc%02Xip%02X\n", + len = sprintf(buf, "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02Xic", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), le16_to_cpu(udev->descriptor.bcdDevice), udev->descriptor.bDeviceClass, udev->descriptor.bDeviceSubClass, - udev->descriptor.bDeviceProtocol, + udev->descriptor.bDeviceProtocol); + buf += len; + + if (udev->descriptor.bDeviceClass == 0) { + struct usb_host_interface *alt = intf->cur_altsetting; + + return len + sprintf(buf, "%02Xisc%02Xip%02X\n", alt->desc.bInterfaceClass, alt->desc.bInterfaceSubClass, alt->desc.bInterfaceProtocol); } else { - return sprintf(buf, "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02Xic*isc*ip*\n", - le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct), - le16_to_cpu(udev->descriptor.bcdDevice), - udev->descriptor.bDeviceClass, - udev->descriptor.bDeviceSubClass, - udev->descriptor.bDeviceProtocol); + return len + sprintf(buf, "*isc*ip*\n"); } - } static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 429330bc38de..d7b4f7939ded 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -439,9 +439,12 @@ static int ehci_hub_control ( /* force reset to complete */ writel (temp & ~PORT_RESET, &ehci->regs->port_status [wIndex]); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ retval = handshake ( &ehci->regs->port_status [wIndex], - PORT_RESET, 0, 500); + PORT_RESET, 0, 750); if (retval != 0) { ehci_err (ehci, "port %d reset error %d\n", wIndex + 1, retval); diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index 869ff73690ac..2d8bd9dcc6ed 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -1315,6 +1315,8 @@ void hid_init_reports(struct hid_device *hid) #define USB_DEVICE_ID_WACOM_INTUOS2 0x0040 #define USB_DEVICE_ID_WACOM_VOLITO 0x0060 #define USB_DEVICE_ID_WACOM_PTU 0x0003 +#define USB_DEVICE_ID_WACOM_INTUOS3 0x00B0 +#define USB_DEVICE_ID_WACOM_CINTIQ 0x003F #define USB_VENDOR_ID_KBGEAR 0x084e #define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 @@ -1401,6 +1403,7 @@ void hid_init_reports(struct hid_device *hid) #define USB_VENDOR_ID_DELORME 0x1163 #define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100 +#define USB_DEVICE_ID_DELORME_EM_LT20 0x0200 #define USB_VENDOR_ID_MCC 0x09db #define USB_DEVICE_ID_MCC_PMD1024LS 0x0076 @@ -1412,6 +1415,12 @@ void hid_init_reports(struct hid_device *hid) #define USB_VENDOR_ID_BTC 0x046e #define USB_DEVICE_ID_BTC_KEYBOARD 0x5303 +#define USB_VENDOR_ID_VERNIER 0x08f7 +#define USB_DEVICE_ID_VERNIER_LABPRO 0x0001 +#define USB_DEVICE_ID_VERNIER_GOTEMP 0x0002 +#define USB_DEVICE_ID_VERNIER_SKIP 0x0003 +#define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004 + /* * Alphabetically sorted blacklist by quirk type. @@ -1437,6 +1446,7 @@ static struct hid_blacklist { { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_4_PHIDGETSERVO_30, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_1_PHIDGETSERVO_30, HID_QUIRK_IGNORE }, @@ -1456,6 +1466,10 @@ static struct hid_blacklist { { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 500, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LABPRO, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PENPARTNER, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE + 1, HID_QUIRK_IGNORE }, @@ -1481,6 +1495,10 @@ static struct hid_blacklist { { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS2 + 7, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_VOLITO, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_PTU, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS3, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS3 + 1, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS3 + 2, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_CINTIQ, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_4_PHIDGETSERVO_20, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_1_PHIDGETSERVO_20, HID_QUIRK_IGNORE }, diff --git a/drivers/usb/media/pwc/ChangeLog b/drivers/usb/media/pwc/ChangeLog deleted file mode 100644 index b2eb71a9afb5..000000000000 --- a/drivers/usb/media/pwc/ChangeLog +++ /dev/null @@ -1,143 +0,0 @@ -9.0.2 - -* Adding #ifdef to compile PWC before and after 2.6.5 - -9.0.1 - -9.0 - - -8.12 - -* Implement motorized pan/tilt feature for Logitech QuickCam Orbit/Spere. - -8.11.1 - -* Fix for PCVC720/40, would not be able to set videomode -* Fix for Samsung MPC models, appearantly they are based on a newer chipset - -8.11 - -* 20 dev_hints (per request) -* Hot unplugging should be better, no more dangling pointers or memory leaks -* Added reserved Logitech webcam IDs -* Device now remembers size & fps between close()/open() -* Removed palette stuff altogether - -8.10.1 - -* Added IDs for PCVC720K/40 and Creative Labs Webcam Pro - -8.10 - -* Fixed ID for QuickCam Notebook pro -* Added GREALSIZE ioctl() call -* Fixed bug in case PWCX was not loaded and invalid size was set - -8.9 - -* Merging with kernel 2.5.49 -* Adding IDs for QuickCam Zoom & QuickCam Notebook - -8.8 - -* Fixing 'leds' parameter -* Adding IDs for Logitech QuickCam Pro 4000 -* Making URB init/cleanup a little nicer - -8.7 - -* Incorporating changes in ioctl() parameter passing -* Also changes to URB mechanism - -8.6 - -* Added ID's for Visionite VCS UM100 and UC300 -* Removed YUV420-interlaced palette altogether (was confusing) -* Removed MIRROR stuff as it didn't work anyway -* Fixed a problem with the 'leds' parameter (wouldn't blink) -* Added ioctl()s for advanced features: 'extended' whitebalance ioctl()s, - CONTOUR, BACKLIGHT, FLICKER, DYNNOISE. -* VIDIOCGCAP.name now contains real camera model name instead of - 'Philips xxx webcam' -* Added PROBE ioctl (see previous point & API doc) - -8.5 - -* Adding IDs for Creative Labs Webcam 5 -* Adding IDs for SOTEC CMS-001 webcam -* Solving possible hang in VIDIOCSYNC when unplugging the cam -* Forgot to return structure in VIDIOCPWCGAWB, oops -* Time interval for the LEDs are now in milliseconds - -8.4 - -* Fixing power_save option for Vesta range -* Handling new error codes in ISOC callback -* Adding dev_hint module parameter, to specify /dev/videoX device nodes - -8.3 - -* Adding Samsung C10 and C30 cameras -* Removing palette module parameter -* Fixed typo in ID of QuickCam 3000 Pro -* Adding LED settings (blinking while in use) for ToUCam cameras. -* Turns LED off when camera is not in use. - -8.2 - -* Making module more silent when trace = 0 -* Adding QuickCam 3000 Pro IDs -* Chrominance control for the Vesta cameras -* Hopefully fixed problems on machines with BIGMEM and > 1GB of RAM -* Included Oliver Neukem's lock_kernel() patch -* Allocates less memory for image buffers -* Adds ioctl()s for the whitebalancing - -8.1 - -* Adding support for 750 -* Adding V4L GAUDIO/SAUDIO/UNIT ioctl() calls - -8.0 -* 'damage control' after inclusion in 2.4.5. -* Changed wait-queue mechanism in read/mmap/poll according to the book. -* Included YUV420P palette. -* Changed interface to decompressor module. -* Cleaned up pwc structure a bit. - -7.0 - -* Fixed bug in vcvt_420i_yuyv; extra variables on stack were misaligned. -* There is now a clear error message when an image size is selected that - is only supported using the decompressor, and the decompressor isn't - loaded. -* When the decompressor wasn't loaded, selecting large image size - would create skewed or double images. - -6.3 - -* Introduced spinlocks for the buffer pointer manipulation; a number of - reports seem to suggest the down()/up() semaphores were the cause of - lockups, since they are not suitable for interrupt/user locking. -* Separated decompressor and core code into 2 modules. - -6.2 - -* Non-integral image sizes are now padded with gray or black. -* Added SHUTTERSPEED ioctl(). -* Fixed buglet in VIDIOCPWCSAGC; the function would always return an error, - even though the call succeeded. -* Added hotplug support for 2.4.*. -* Memory: the 645/646 uses less memory now. - -6.1 - -* VIDIOCSPICT returns -EINVAL with invalid palettes. -* Added saturation control. -* Split decompressors from rest. -* Fixed bug that would reset the framerate to the default framerate if - the rate field was set to 0 (which is not what I intended, nl. do not - change the framerate!). -* VIDIOCPWCSCQUAL (setting compression quality) now takes effect immediately. -* Workaround for a bug in the 730 sensor. diff --git a/drivers/usb/media/pwc/pwc-if.c b/drivers/usb/media/pwc/pwc-if.c index 5429ff3b9753..b77e65c03659 100644 --- a/drivers/usb/media/pwc/pwc-if.c +++ b/drivers/usb/media/pwc/pwc-if.c @@ -332,10 +332,6 @@ static int pwc_allocate_buffers(struct pwc_device *pdev) #endif ; } - if (kbuf == NULL) { - Err("Failed to allocate decompress table.\n"); - return -ENOMEM; - } pdev->decompress_data = kbuf; /* Allocate image buffer; double buffer for mmap() */ diff --git a/drivers/usb/net/kaweth.c b/drivers/usb/net/kaweth.c index a9a7cf4a38eb..fd6ff4cb2c62 100644 --- a/drivers/usb/net/kaweth.c +++ b/drivers/usb/net/kaweth.c @@ -520,7 +520,7 @@ static void int_callback(struct urb *u, struct pt_regs *regs) /* we check the link state to report changes */ if (kaweth->linkstate != (act_state = ( kaweth->intbuffer[STATE_OFFSET] | STATE_MASK) >> STATE_SHIFT)) { - if (!act_state) + if (act_state) netif_carrier_on(kaweth->net); else netif_carrier_off(kaweth->net); diff --git a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c index 85476e76b244..4cbb408af727 100644 --- a/drivers/usb/net/usbnet.c +++ b/drivers/usb/net/usbnet.c @@ -2765,7 +2765,7 @@ static int blan_mdlm_bind (struct usbnet *dev, struct usb_interface *intf) } /* expect bcdVersion 1.0, ignore */ if (memcmp(&desc->bGUID, blan_guid, 16) - && memcmp(&desc->bGUID, blan_guid, 16) ) { + && memcmp(&desc->bGUID, safe_guid, 16) ) { /* hey, this one might _really_ be MDLM! */ dev_dbg (&intf->dev, "MDLM guid\n"); goto bad_desc; diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index bc798edf0358..9438909e87a5 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -455,6 +455,17 @@ config USB_SERIAL_XIRCOM To compile this driver as a module, choose M here: the module will be called keyspan_pda. +config USB_SERIAL_OPTION + tristate "USB Option PCMCIA serial driver" + depends on USB_SERIAL && USB_OHCI_HCD && PCCARD + help + Say Y here if you want to use an Option card. This is a + GSM card, controlled by three serial ports which are connected + via an OHCI adapter located on a PC card. + + To compile this driver as a module, choose M here: the + module will be called option. + config USB_SERIAL_OMNINET tristate "USB ZyXEL omni.net LCD Plus Driver (EXPERIMENTAL)" depends on USB_SERIAL && EXPERIMENTAL diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index d56ff6d86cce..6c7cdcc99a9e 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o obj-$(CONFIG_USB_SERIAL_KOBIL_SCT) += kobil_sct.o obj-$(CONFIG_USB_SERIAL_MCT_U232) += mct_u232.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o +obj-$(CONFIG_USB_SERIAL_OPTION) += option.o obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o diff --git a/drivers/usb/serial/cp2101.c b/drivers/usb/serial/cp2101.c index 7e9bb63eb466..4ace9964fc6b 100644 --- a/drivers/usb/serial/cp2101.c +++ b/drivers/usb/serial/cp2101.c @@ -7,6 +7,14 @@ * modify it under the terms of the GNU General Public License version * 2 as published by the Free Software Foundation. * + * Support to set flow control line levels using TIOCMGET and TIOCMSET + * thanks to Karl Hiramoto karl@hiramoto.org. RTSCTS hardware flow + * control thanks to Munir Nassar nassarmu@real-time.com + * + * Outstanding Issues: + * Buffers are not flushed when the port is opened. + * Multiple calls to write() may fail with "Resource temporarily unavailable" + * */ #include <linux/config.h> @@ -24,7 +32,7 @@ /* * Version Information */ -#define DRIVER_VERSION "v0.03" +#define DRIVER_VERSION "v0.04" #define DRIVER_DESC "Silicon Labs CP2101/CP2102 RS232 serial adaptor driver" /* @@ -35,6 +43,9 @@ static void cp2101_cleanup(struct usb_serial_port*); static void cp2101_close(struct usb_serial_port*, struct file*); static void cp2101_get_termios(struct usb_serial_port*); static void cp2101_set_termios(struct usb_serial_port*, struct termios*); +static int cp2101_tiocmget (struct usb_serial_port *, struct file *); +static int cp2101_tiocmset (struct usb_serial_port *, struct file *, + unsigned int, unsigned int); static void cp2101_break_ctl(struct usb_serial_port*, int); static int cp2101_startup (struct usb_serial *); static void cp2101_shutdown(struct usb_serial*); @@ -43,9 +54,10 @@ static void cp2101_shutdown(struct usb_serial*); static int debug; static struct usb_device_id id_table [] = { - {USB_DEVICE(0x10c4, 0xea60) }, /*Silicon labs factory default*/ - {USB_DEVICE(0x10ab, 0x10c5) }, /*Siemens MC60 Cable*/ - { } /* Terminating Entry*/ + { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */ + { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */ + { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE (usb, id_table); @@ -70,32 +82,35 @@ static struct usb_serial_device_type cp2101_device = { .close = cp2101_close, .break_ctl = cp2101_break_ctl, .set_termios = cp2101_set_termios, + .tiocmget = cp2101_tiocmget, + .tiocmset = cp2101_tiocmset, .attach = cp2101_startup, .shutdown = cp2101_shutdown, }; -/*Config request types*/ +/* Config request types */ #define REQTYPE_HOST_TO_DEVICE 0x41 #define REQTYPE_DEVICE_TO_HOST 0xc1 -/*Config SET requests. To GET, add 1 to the request number*/ -#define CP2101_UART 0x00 /*Enable / Disable*/ -#define CP2101_BAUDRATE 0x01 /*(BAUD_RATE_GEN_FREQ / baudrate)*/ -#define CP2101_BITS 0x03 /*0x(0)(data bits)(parity)(stop bits)*/ -#define CP2101_BREAK 0x05 /*On / Off*/ -#define CP2101_DTRRTS 0x07 /*101 / 202 ???*/ -#define CP2101_CONFIG_16 0x13 /*16 bytes of config data ???*/ -#define CP2101_CONFIG_6 0x19 /*6 bytes of config data ???*/ +/* Config SET requests. To GET, add 1 to the request number */ +#define CP2101_UART 0x00 /* Enable / Disable */ +#define CP2101_BAUDRATE 0x01 /* (BAUD_RATE_GEN_FREQ / baudrate) */ +#define CP2101_BITS 0x03 /* 0x(0)(databits)(parity)(stopbits) */ +#define CP2101_BREAK 0x05 /* On / Off */ +#define CP2101_CONTROL 0x07 /* Flow control line states */ +#define CP2101_MODEMCTL 0x13 /* Modem controls */ +#define CP2101_CONFIG_6 0x19 /* 6 bytes of config data ??? */ -/*CP2101_UART*/ +/* CP2101_UART */ #define UART_ENABLE 0x0001 #define UART_DISABLE 0x0000 -/*CP2101_BAUDRATE*/ +/* CP2101_BAUDRATE */ #define BAUD_RATE_GEN_FREQ 0x384000 -/*CP2101_BITS*/ +/* CP2101_BITS */ #define BITS_DATA_MASK 0X0f00 +#define BITS_DATA_5 0X0500 #define BITS_DATA_6 0X0600 #define BITS_DATA_7 0X0700 #define BITS_DATA_8 0X0800 @@ -112,64 +127,137 @@ static struct usb_serial_device_type cp2101_device = { #define BITS_STOP_1 0x0000 #define BITS_STOP_1_5 0x0001 #define BITS_STOP_2 0x0002 + +/* CP2101_BREAK */ #define BREAK_ON 0x0000 #define BREAK_OFF 0x0001 +/* CP2101_CONTROL */ +#define CONTROL_DTR 0x0001 +#define CONTROL_RTS 0x0002 +#define CONTROL_CTS 0x0010 +#define CONTROL_DSR 0x0020 +#define CONTROL_RING 0x0040 +#define CONTROL_DCD 0x0080 +#define CONTROL_WRITE_DTR 0x0100 +#define CONTROL_WRITE_RTS 0x0200 -static int cp2101_get_config(struct usb_serial_port* port, u8 request) +/* + * cp2101_get_config + * Reads from the CP2101 configuration registers + * 'size' is specified in bytes. + * 'data' is a pointer to a pre-allocated array of integers large + * enough to hold 'size' bytes (with 4 bytes to each integer) + */ +static int cp2101_get_config(struct usb_serial_port* port, u8 request, + unsigned int *data, int size) { struct usb_serial *serial = port->serial; - unsigned char buf[4]; - unsigned int value; - int result, i; + u32 *buf; + int result, i, length; + + /* Number of integers required to contain the array */ + length = (((size - 1) | 3) + 1)/4; + + buf = kmalloc (length * sizeof(u32), GFP_KERNEL); + memset(buf, 0, length * sizeof(u32)); + + if (!buf) { + dev_err(&port->dev, "%s - out of memory.\n", __FUNCTION__); + return -ENOMEM; + } - /*For get requests, the request number must be incremented*/ + /* For get requests, the request number must be incremented */ request++; - /*Issue the request, attempting to read 4 bytes*/ + /* Issue the request, attempting to read 'size' bytes */ result = usb_control_msg (serial->dev,usb_rcvctrlpipe (serial->dev, 0), request, REQTYPE_DEVICE_TO_HOST, 0x0000, - 0, buf, 4, 300); + 0, buf, size, 300); - if (result < 0) { - dev_err(&port->dev, "%s - Unable to send config request, " - "request=0x%x result=%d\n", - __FUNCTION__, request, result); - return result; - } + /* Convert data into an array of integers */ + for (i=0; i<length; i++) + data[i] = le32_to_cpu(buf[i]); - /*Assemble each byte read into an integer value*/ - value = 0; - for (i=0; i<4 && i<result; i++) - value |= (buf[i] << (i * 8)); + kfree(buf); - dbg( " %s - request=0x%x result=%d value=0x%x", - __FUNCTION__, request, result, value); + if (result != size) { + dev_err(&port->dev, "%s - Unable to send config request, " + "request=0x%x size=%d result=%d\n", + __FUNCTION__, request, size, result); + return -EPROTO; + } - return value; + return 0; } -static int cp2101_set_config(struct usb_serial_port* port, u8 request, u16 value) +/* + * cp2101_set_config + * Writes to the CP2101 configuration registers + * Values less than 16 bits wide are sent directly + * 'size' is specified in bytes. + */ +static int cp2101_set_config(struct usb_serial_port* port, u8 request, + unsigned int *data, int size) { struct usb_serial *serial = port->serial; - int result; - result = usb_control_msg (serial->dev, usb_sndctrlpipe(serial->dev, 0), - request, REQTYPE_HOST_TO_DEVICE, value, - 0, NULL, 0, 300); + u32 *buf; + int result, i, length; - if (result <0) { - dev_err(&port->dev, "%s - Unable to send config request, " - "request=0x%x value=0x%x result=%d\n", - __FUNCTION__, request, value, result); - return result; + /* Number of integers required to contain the array */ + length = (((size - 1) | 3) + 1)/4; + + buf = kmalloc(length * sizeof(u32), GFP_KERNEL); + if (!buf) { + dev_err(&port->dev, "%s - out of memory.\n", + __FUNCTION__); + return -ENOMEM; + } + + /* Array of integers into bytes */ + for (i = 0; i < length; i++) + buf[i] = cpu_to_le32(data[i]); + + if (size > 2) { + result = usb_control_msg (serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, REQTYPE_HOST_TO_DEVICE, 0x0000, + 0, buf, size, 300); + } else { + result = usb_control_msg (serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, REQTYPE_HOST_TO_DEVICE, data[0], + 0, NULL, 0, 300); } - dbg(" %s - request=0x%x value=0x%x result=%d", - __FUNCTION__, request, value, result); + kfree(buf); + + if ((size > 2 && result != size) || result < 0) { + dev_err(&port->dev, "%s - Unable to send request, " + "request=0x%x size=%d result=%d\n", + __FUNCTION__, request, size, result); + return -EPROTO; + } + /* Single data value */ + result = usb_control_msg (serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, REQTYPE_HOST_TO_DEVICE, data[0], + 0, NULL, 0, 300); return 0; } +/* + * cp2101_set_config_single + * Convenience function for calling cp2101_set_config on single data values + * without requiring an integer pointer + */ +static inline int cp2101_set_config_single(struct usb_serial_port* port, + u8 request, unsigned int data) +{ + return cp2101_set_config(port, request, &data, 2); +} + static int cp2101_open (struct usb_serial_port *port, struct file *filp) { struct usb_serial *serial = port->serial; @@ -177,7 +265,7 @@ static int cp2101_open (struct usb_serial_port *port, struct file *filp) dbg("%s - port %d", __FUNCTION__, port->number); - if (cp2101_set_config(port, CP2101_UART, UART_ENABLE)) { + if (cp2101_set_config_single(port, CP2101_UART, UART_ENABLE)) { dev_err(&port->dev, "%s - Unable to enable UART\n", __FUNCTION__); return -EPROTO; @@ -198,9 +286,12 @@ static int cp2101_open (struct usb_serial_port *port, struct file *filp) return result; } - /*Configure the termios structure*/ + /* Configure the termios structure */ cp2101_get_termios(port); + /* Set the DTR and RTS pins low */ + cp2101_tiocmset(port, NULL, TIOCM_DTR | TIOCM_RTS, 0); + return 0; } @@ -228,16 +319,18 @@ static void cp2101_close (struct usb_serial_port *port, struct file * filp) usb_kill_urb(port->write_urb); usb_kill_urb(port->read_urb); - cp2101_set_config(port, CP2101_UART, UART_DISABLE); + cp2101_set_config_single(port, CP2101_UART, UART_DISABLE); } -/* cp2101_get_termios*/ -/* Reads the baud rate, data bits, parity and stop bits from the device*/ -/* Corrects any unsupported values*/ -/* Configures the termios structure to reflect the state of the device*/ +/* + * cp2101_get_termios + * Reads the baud rate, data bits, parity, stop bits and flow control mode + * from the device, corrects any unsupported values, and configures the + * termios structure to reflect the state of the device + */ static void cp2101_get_termios (struct usb_serial_port *port) { - unsigned int cflag; + unsigned int cflag, modem_ctl[4]; int baud; int bits; @@ -249,15 +342,16 @@ static void cp2101_get_termios (struct usb_serial_port *port) } cflag = port->tty->termios->c_cflag; - baud = cp2101_get_config(port, CP2101_BAUDRATE); - /*Convert to baudrate*/ + cp2101_get_config(port, CP2101_BAUDRATE, &baud, 2); + /* Convert to baudrate */ if (baud) baud = BAUD_RATE_GEN_FREQ / baud; dbg("%s - baud rate = %d", __FUNCTION__, baud); cflag &= ~CBAUD; switch (baud) { - /* The baud rates which are commented out below + /* + * The baud rates which are commented out below * appear to be supported by the device * but are non-standard */ @@ -284,14 +378,18 @@ static void cp2101_get_termios (struct usb_serial_port *port) dbg("%s - Baud rate is not supported, " "using 9600 baud", __FUNCTION__); cflag |= B9600; - cp2101_set_config(port, CP2101_BAUDRATE, + cp2101_set_config_single(port, CP2101_BAUDRATE, (BAUD_RATE_GEN_FREQ/9600)); break; } - bits = cp2101_get_config(port, CP2101_BITS); + cp2101_get_config(port, CP2101_BITS, &bits, 2); cflag &= ~CSIZE; switch(bits & BITS_DATA_MASK) { + case BITS_DATA_5: + dbg("%s - data bits = 5", __FUNCTION__); + cflag |= CS5; + break; case BITS_DATA_6: dbg("%s - data bits = 6", __FUNCTION__); cflag |= CS6; @@ -310,7 +408,7 @@ static void cp2101_get_termios (struct usb_serial_port *port) cflag |= CS8; bits &= ~BITS_DATA_MASK; bits |= BITS_DATA_8; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; default: dbg("%s - Unknown number of data bits, " @@ -318,7 +416,7 @@ static void cp2101_get_termios (struct usb_serial_port *port) cflag |= CS8; bits &= ~BITS_DATA_MASK; bits |= BITS_DATA_8; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; } @@ -341,21 +439,21 @@ static void cp2101_get_termios (struct usb_serial_port *port) "disabling parity)", __FUNCTION__); cflag &= ~PARENB; bits &= ~BITS_PARITY_MASK; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; case BITS_PARITY_SPACE: dbg("%s - parity = SPACE (not supported, " "disabling parity)", __FUNCTION__); cflag &= ~PARENB; bits &= ~BITS_PARITY_MASK; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; default: dbg("%s - Unknown parity mode, " "disabling parity", __FUNCTION__); cflag &= ~PARENB; bits &= ~BITS_PARITY_MASK; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; } @@ -366,9 +464,9 @@ static void cp2101_get_termios (struct usb_serial_port *port) break; case BITS_STOP_1_5: dbg("%s - stop bits = 1.5 (not supported, " - "using 1 stop bit", __FUNCTION__); + "using 1 stop bit)", __FUNCTION__); bits &= ~BITS_STOP_MASK; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; case BITS_STOP_2: dbg("%s - stop bits = 2", __FUNCTION__); @@ -378,10 +476,19 @@ static void cp2101_get_termios (struct usb_serial_port *port) dbg("%s - Unknown number of stop bits, " "using 1 stop bit", __FUNCTION__); bits &= ~BITS_STOP_MASK; - cp2101_set_config(port, CP2101_BITS, bits); + cp2101_set_config(port, CP2101_BITS, &bits, 2); break; } + cp2101_get_config(port, CP2101_MODEMCTL, modem_ctl, 16); + if (modem_ctl[0] & 0x0008) { + dbg("%s - flow control = CRTSCTS", __FUNCTION__); + cflag |= CRTSCTS; + } else { + dbg("%s - flow control = NONE", __FUNCTION__); + cflag &= ~CRTSCTS; + } + port->tty->termios->c_cflag = cflag; } @@ -389,8 +496,8 @@ static void cp2101_set_termios (struct usb_serial_port *port, struct termios *old_termios) { unsigned int cflag, old_cflag=0; - int baud=0; - int bits; + int baud=0, bits; + unsigned int modem_ctl[4]; dbg("%s - port %d", __FUNCTION__, port->number); @@ -400,7 +507,7 @@ static void cp2101_set_termios (struct usb_serial_port *port, } cflag = port->tty->termios->c_cflag; - /* check that they really want us to change something */ + /* Check that they really want us to change something */ if (old_termios) { if ((cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(port->tty->termios->c_iflag) @@ -415,7 +522,8 @@ static void cp2101_set_termios (struct usb_serial_port *port, /* If the baud rate is to be updated*/ if ((cflag & CBAUD) != (old_cflag & CBAUD)) { switch (cflag & CBAUD) { - /* The baud rates which are commented out below + /* + * The baud rates which are commented out below * appear to be supported by the device * but are non-standard */ @@ -448,18 +556,22 @@ static void cp2101_set_termios (struct usb_serial_port *port, if (baud) { dbg("%s - Setting baud rate to %d baud", __FUNCTION__, baud); - if (cp2101_set_config(port, CP2101_BAUDRATE, + if (cp2101_set_config_single(port, CP2101_BAUDRATE, (BAUD_RATE_GEN_FREQ / baud))) dev_err(&port->dev, "Baud rate requested not " "supported by device\n"); } } - /*If the number of data bits is to be updated*/ + /* If the number of data bits is to be updated */ if ((cflag & CSIZE) != (old_cflag & CSIZE)) { - bits = cp2101_get_config(port, CP2101_BITS); + cp2101_get_config(port, CP2101_BITS, &bits, 2); bits &= ~BITS_DATA_MASK; switch (cflag & CSIZE) { + case CS5: + bits |= BITS_DATA_5; + dbg("%s - data bits = 5", __FUNCTION__); + break; case CS6: bits |= BITS_DATA_6; dbg("%s - data bits = 6", __FUNCTION__); @@ -483,13 +595,13 @@ static void cp2101_set_termios (struct usb_serial_port *port, bits |= BITS_DATA_8; break; } - if (cp2101_set_config(port, CP2101_BITS, bits)) + if (cp2101_set_config(port, CP2101_BITS, &bits, 2)) dev_err(&port->dev, "Number of data bits requested " "not supported by device\n"); } if ((cflag & (PARENB|PARODD)) != (old_cflag & (PARENB|PARODD))) { - bits = cp2101_get_config(port, CP2101_BITS); + cp2101_get_config(port, CP2101_BITS, &bits, 2); bits &= ~BITS_PARITY_MASK; if (cflag & PARENB) { if (cflag & PARODD) { @@ -500,13 +612,13 @@ static void cp2101_set_termios (struct usb_serial_port *port, dbg("%s - parity = EVEN", __FUNCTION__); } } - if (cp2101_set_config(port, CP2101_BITS, bits)) + if (cp2101_set_config(port, CP2101_BITS, &bits, 2)) dev_err(&port->dev, "Parity mode not supported " "by device\n"); } if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - bits = cp2101_get_config(port, CP2101_BITS); + cp2101_get_config(port, CP2101_BITS, &bits, 2); bits &= ~BITS_STOP_MASK; if (cflag & CSTOPB) { bits |= BITS_STOP_2; @@ -515,15 +627,90 @@ static void cp2101_set_termios (struct usb_serial_port *port, bits |= BITS_STOP_1; dbg("%s - stop bits = 1", __FUNCTION__); } - if (cp2101_set_config(port, CP2101_BITS, bits)) + if (cp2101_set_config(port, CP2101_BITS, &bits, 2)) dev_err(&port->dev, "Number of stop bits requested " "not supported by device\n"); } + + if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { + cp2101_get_config(port, CP2101_MODEMCTL, modem_ctl, 16); + dbg("%s - read modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x", + __FUNCTION__, modem_ctl[0], modem_ctl[1], + modem_ctl[2], modem_ctl[3]); + + if (cflag & CRTSCTS) { + modem_ctl[0] &= ~0x7B; + modem_ctl[0] |= 0x09; + modem_ctl[1] = 0x80; + dbg("%s - flow control = CRTSCTS", __FUNCTION__); + } else { + modem_ctl[0] &= ~0x7B; + modem_ctl[0] |= 0x01; + modem_ctl[1] |= 0x40; + dbg("%s - flow control = NONE", __FUNCTION__); + } + + dbg("%s - write modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x", + __FUNCTION__, modem_ctl[0], modem_ctl[1], + modem_ctl[2], modem_ctl[3]); + cp2101_set_config(port, CP2101_MODEMCTL, modem_ctl, 16); + } + +} + +static int cp2101_tiocmset (struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear) +{ + int control = 0; + + dbg("%s - port %d", __FUNCTION__, port->number); + + if (set & TIOCM_RTS) { + control |= CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (set & TIOCM_DTR) { + control |= CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + if (clear & TIOCM_RTS) { + control &= ~CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (clear & TIOCM_DTR) { + control &= ~CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + + dbg("%s - control = 0x%.4x", __FUNCTION__, control); + + return cp2101_set_config(port, CP2101_CONTROL, &control, 2); + +} + +static int cp2101_tiocmget (struct usb_serial_port *port, struct file *file) +{ + int control, result; + + dbg("%s - port %d", __FUNCTION__, port->number); + + cp2101_get_config(port, CP2101_CONTROL, &control, 1); + + result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0) + |((control & CONTROL_RTS) ? TIOCM_RTS : 0) + |((control & CONTROL_CTS) ? TIOCM_CTS : 0) + |((control & CONTROL_DSR) ? TIOCM_DSR : 0) + |((control & CONTROL_RING)? TIOCM_RI : 0) + |((control & CONTROL_DCD) ? TIOCM_CD : 0); + + dbg("%s - control = 0x%.2x", __FUNCTION__, control); + + return result; } static void cp2101_break_ctl (struct usb_serial_port *port, int break_state) { - u16 state; + int state; dbg("%s - port %d", __FUNCTION__, port->number); if (break_state == 0) @@ -532,12 +719,12 @@ static void cp2101_break_ctl (struct usb_serial_port *port, int break_state) state = BREAK_ON; dbg("%s - turning break %s", __FUNCTION__, state==BREAK_OFF ? "off" : "on"); - cp2101_set_config(port, CP2101_BREAK, state); + cp2101_set_config(port, CP2101_BREAK, &state, 2); } static int cp2101_startup (struct usb_serial *serial) { - /*CP2101 buffers behave strangely unless device is reset*/ + /* CP2101 buffers behave strangely unless device is reset */ usb_reset_device(serial->dev); return 0; } @@ -548,7 +735,7 @@ static void cp2101_shutdown (struct usb_serial *serial) dbg("%s", __FUNCTION__); - /* stop reads and writes on all ports */ + /* Stop reads and writes on all ports */ for (i=0; i < serial->num_ports; ++i) { cp2101_cleanup(serial->port[i]); } @@ -560,16 +747,16 @@ static int __init cp2101_init (void) retval = usb_serial_register(&cp2101_device); if (retval) - return retval; /*Failed to register*/ + return retval; /* Failed to register */ retval = usb_register(&cp2101_driver); if (retval) { - /*Failed to register*/ + /* Failed to register */ usb_serial_deregister(&cp2101_device); return retval; } - /*Success*/ + /* Success */ info(DRIVER_DESC " " DRIVER_VERSION); return 0; } diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 051c3a77b41b..3bfcc7b9f861 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -264,7 +264,7 @@ /* * Version Information */ -#define DRIVER_VERSION "v1.4.1" +#define DRIVER_VERSION "v1.4.2" #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" @@ -687,6 +687,8 @@ struct ftdi_private { char prev_status, diff_status; /* Used for TIOCMIWAIT */ __u8 rx_flags; /* receive state flags (throttling) */ spinlock_t rx_lock; /* spinlock for receive state */ + struct work_struct rx_work; + int rx_processed; __u16 interface; /* FT2232C port interface (0 for FT232/245) */ @@ -717,7 +719,7 @@ 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_process_read (struct usb_serial_port *port); +static void ftdi_process_read (void *param); static void ftdi_set_termios (struct usb_serial_port *port, struct termios * old); static int ftdi_tiocmget (struct usb_serial_port *port, struct file *file); static int ftdi_tiocmset (struct usb_serial_port *port, struct file * file, unsigned int set, unsigned int clear); @@ -1387,6 +1389,8 @@ static int ftdi_common_startup (struct usb_serial *serial) port->read_urb->transfer_buffer_length = BUFSZ; } + INIT_WORK(&priv->rx_work, ftdi_process_read, port); + /* Free port's existing write urb and transfer buffer. */ if (port->write_urb) { usb_free_urb (port->write_urb); @@ -1617,6 +1621,7 @@ static int ftdi_open (struct usb_serial_port *port, struct file *filp) spin_unlock_irqrestore(&priv->rx_lock, flags); /* Start reading from the device */ + priv->rx_processed = 0; usb_fill_bulk_urb(port->read_urb, dev, usb_rcvbulkpipe(dev, port->bulk_in_endpointAddress), port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, @@ -1667,6 +1672,10 @@ static void ftdi_close (struct usb_serial_port *port, struct file *filp) err("Error from RTS LOW urb"); } } /* Note change no line if hupcl is off */ + + /* cancel any scheduled reading */ + cancel_delayed_work(&priv->rx_work); + flush_scheduled_work(); /* shutdown our bulk read */ if (port->read_urb) @@ -1862,23 +1871,14 @@ static void ftdi_read_bulk_callback (struct urb *urb, struct pt_regs *regs) return; } - /* If throttled, delay receive processing until unthrottled. */ - spin_lock(&priv->rx_lock); - if (priv->rx_flags & THROTTLED) { - dbg("Deferring read urb processing until unthrottled"); - priv->rx_flags |= ACTUALLY_THROTTLED; - spin_unlock(&priv->rx_lock); - return; - } - spin_unlock(&priv->rx_lock); - ftdi_process_read(port); } /* ftdi_read_bulk_callback */ -static void ftdi_process_read (struct usb_serial_port *port) +static void ftdi_process_read (void *param) { /* ftdi_process_read */ + struct usb_serial_port *port = (struct usb_serial_port*)param; struct urb *urb; struct tty_struct *tty; struct ftdi_private *priv; @@ -1889,6 +1889,7 @@ static void ftdi_process_read (struct usb_serial_port *port) int result; int need_flip; int packet_offset; + unsigned long flags; dbg("%s - port %d", __FUNCTION__, port->number); @@ -1915,12 +1916,18 @@ static void ftdi_process_read (struct usb_serial_port *port) data = urb->transfer_buffer; - /* The first two bytes of every read packet are status */ - if (urb->actual_length > 2) { - usb_serial_debug_data(debug, &port->dev, __FUNCTION__, urb->actual_length, data); + if (priv->rx_processed) { + dbg("%s - already processed: %d bytes, %d remain", __FUNCTION__, + priv->rx_processed, + urb->actual_length - priv->rx_processed); } else { - dbg("Status only: %03oo %03oo",data[0],data[1]); - } + /* The first two bytes of every read packet are status */ + if (urb->actual_length > 2) { + usb_serial_debug_data(debug, &port->dev, __FUNCTION__, urb->actual_length, data); + } else { + dbg("Status only: %03oo %03oo",data[0],data[1]); + } + } /* TO DO -- check for hung up line and handle appropriately: */ @@ -1929,8 +1936,12 @@ static void ftdi_process_read (struct usb_serial_port *port) /* if CD is dropped and the line is not CLOCAL then we should hangup */ need_flip = 0; - for (packet_offset=0; packet_offset < urb->actual_length; packet_offset += PKTSZ) { + for (packet_offset = priv->rx_processed; packet_offset < urb->actual_length; packet_offset += PKTSZ) { + int length; + /* Compare new line status to the old one, signal if different */ + /* N.B. packet may be processed more than once, but differences + * are only processed once. */ if (priv != NULL) { char new_status = data[packet_offset+0] & FTDI_STATUS_B0_MASK; if (new_status != priv->prev_status) { @@ -1940,6 +1951,35 @@ static void ftdi_process_read (struct usb_serial_port *port) } } + length = min(PKTSZ, urb->actual_length-packet_offset)-2; + if (length < 0) { + err("%s - bad packet length: %d", __FUNCTION__, length+2); + length = 0; + } + + /* have to make sure we don't overflow the buffer + with tty_insert_flip_char's */ + if (tty->flip.count+length > TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + need_flip = 0; + + if (tty->flip.count != 0) { + /* flip didn't work, this happens when ftdi_process_read() is + * called from ftdi_unthrottle, because TTY_DONT_FLIP is set */ + dbg("%s - flip buffer push failed", __FUNCTION__); + break; + } + } + if (priv->rx_flags & THROTTLED) { + dbg("%s - throttled", __FUNCTION__); + break; + } + if (tty->ldisc.receive_room(tty)-tty->flip.count < length) { + /* break out & wait for throttling/unthrottling to happen */ + dbg("%s - receive room low", __FUNCTION__); + break; + } + /* Handle errors and break */ error_flag = TTY_NORMAL; /* Although the device uses a bitmask and hence can have multiple */ @@ -1962,13 +2002,8 @@ static void ftdi_process_read (struct usb_serial_port *port) 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); - } + if (length > 0) { + for (i = 2; i < length+2; i++) { /* Note that the error flag is duplicated for every character received since we don't know which character it applied to */ @@ -2005,6 +2040,35 @@ static void ftdi_process_read (struct usb_serial_port *port) tty_flip_buffer_push(tty); } + if (packet_offset < urb->actual_length) { + /* not completely processed - record progress */ + priv->rx_processed = packet_offset; + dbg("%s - incomplete, %d bytes processed, %d remain", + __FUNCTION__, packet_offset, + urb->actual_length - packet_offset); + /* check if we were throttled while processing */ + spin_lock_irqsave(&priv->rx_lock, flags); + if (priv->rx_flags & THROTTLED) { + priv->rx_flags |= ACTUALLY_THROTTLED; + spin_unlock_irqrestore(&priv->rx_lock, flags); + dbg("%s - deferring remainder until unthrottled", + __FUNCTION__); + return; + } + spin_unlock_irqrestore(&priv->rx_lock, flags); + /* if the port is closed stop trying to read */ + if (port->open_count > 0){ + /* delay processing of remainder */ + schedule_delayed_work(&priv->rx_work, 1); + } else { + dbg("%s - port is closed", __FUNCTION__); + } + return; + } + + /* urb is completely processed */ + priv->rx_processed = 0; + /* if the port is closed stop trying to read */ if (port->open_count > 0){ /* Continue trying to always read */ @@ -2444,7 +2508,7 @@ static void ftdi_unthrottle (struct usb_serial_port *port) spin_unlock_irqrestore(&priv->rx_lock, flags); if (actually_throttled) - ftdi_process_read(port); + schedule_work(&priv->rx_work); } static int __init ftdi_init (void) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c new file mode 100644 index 000000000000..b722175f108f --- /dev/null +++ b/drivers/usb/serial/option.c @@ -0,0 +1,729 @@ +/* + Option Card (PCMCIA to) USB to Serial Driver + + Copyright (C) 2005 Matthias Urlichs <smurf@smurf.noris.de> + + This driver is free software; you can redistribute it and/or modify + it under the terms of Version 2 of the GNU General Public License as + published by the Free Software Foundation. + + Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org> + + History: + + 2005-05-19 v0.1 Initial version, based on incomplete docs + and analysis of misbehavior of the standard driver + 2005-05-20 v0.2 Extended the input buffer to avoid losing + random 64-byte chunks of data + 2005-05-21 v0.3 implemented chars_in_buffer() + turned on low_latency + simplified the code somewhat +*/ +#define DRIVER_VERSION "v0.3" +#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>" +#define DRIVER_DESC "Option Card (PC-Card to) USB to Serial Driver" + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/errno.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/usb.h> +#include "usb-serial.h" + +/* Function prototypes */ +static int option_open (struct usb_serial_port *port, struct file *filp); +static void option_close (struct usb_serial_port *port, struct file *filp); +static int option_startup (struct usb_serial *serial); +static void option_shutdown (struct usb_serial *serial); +static void option_rx_throttle (struct usb_serial_port *port); +static void option_rx_unthrottle (struct usb_serial_port *port); +static int option_write_room (struct usb_serial_port *port); + +static void option_instat_callback(struct urb *urb, struct pt_regs *regs); + + +static int option_write (struct usb_serial_port *port, + const unsigned char *buf, int count); + +static int option_chars_in_buffer (struct usb_serial_port *port); +static int option_ioctl (struct usb_serial_port *port, struct file *file, + unsigned int cmd, unsigned long arg); +static void option_set_termios (struct usb_serial_port *port, + struct termios *old); +static void option_break_ctl (struct usb_serial_port *port, int break_state); +static int option_tiocmget (struct usb_serial_port *port, struct file *file); +static int option_tiocmset (struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear); +static int option_send_setup (struct usb_serial_port *port); + +/* Vendor and product IDs */ +#define OPTION_VENDOR_ID 0x0AF0 + +#define OPTION_PRODUCT_OLD 0x5000 +#define OPTION_PRODUCT_WLAN 0x6000 + +static struct usb_device_id option_ids[] = { + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_OLD) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_WLAN) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, option_ids); + +static struct usb_driver option_driver = { + .owner = THIS_MODULE, + .name = "option", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = option_ids, +}; + +/* The card has three separate interfaces, wich the serial driver + * recognizes separately, thus num_port=1. + */ +static struct usb_serial_device_type option_3port_device = { + .owner = THIS_MODULE, + .name = "Option 3-port card", + .short_name = "option", + .id_table = option_ids, + .num_interrupt_in = NUM_DONT_CARE, + .num_bulk_in = NUM_DONT_CARE, + .num_bulk_out = NUM_DONT_CARE, + .num_ports = 1, /* 3 */ + .open = option_open, + .close = option_close, + .write = option_write, + .write_room = option_write_room, + .chars_in_buffer = option_chars_in_buffer, + .throttle = option_rx_throttle, + .unthrottle = option_rx_unthrottle, + .ioctl = option_ioctl, + .set_termios = option_set_termios, + .break_ctl = option_break_ctl, + .tiocmget = option_tiocmget, + .tiocmset = option_tiocmset, + .attach = option_startup, + .shutdown = option_shutdown, + .read_int_callback = option_instat_callback, +}; + +static int debug; + +/* per port private data */ + +#define N_IN_URB 4 +#define N_OUT_URB 1 +#define IN_BUFLEN 1024 +#define OUT_BUFLEN 1024 + +struct option_port_private { + /* Input endpoints and buffer for this port */ + struct urb *in_urbs[N_IN_URB]; + char in_buffer[N_IN_URB][IN_BUFLEN]; + /* Output endpoints and buffer for this port */ + struct urb *out_urbs[N_OUT_URB]; + char out_buffer[N_OUT_URB][OUT_BUFLEN]; + + /* Settings for the port */ + int rts_state; /* Handshaking pins (outputs) */ + int dtr_state; + int cts_state; /* Handshaking pins (inputs) */ + int dsr_state; + int dcd_state; + int ri_state; + // int break_on; + + unsigned long tx_start_time[N_OUT_URB]; +}; + + +/* Functions used by new usb-serial code. */ +static int __init +option_init (void) +{ + int retval; + retval = usb_serial_register(&option_3port_device); + if (retval) + goto failed_3port_device_register; + retval = usb_register(&option_driver); + if (retval) + goto failed_driver_register; + + info(DRIVER_DESC ": " DRIVER_VERSION); + + return 0; + +failed_driver_register: + usb_serial_deregister (&option_3port_device); +failed_3port_device_register: + return retval; +} + +static void __exit +option_exit (void) +{ + usb_deregister (&option_driver); + usb_serial_deregister (&option_3port_device); +} + +module_init(option_init); +module_exit(option_exit); + +static void +option_rx_throttle (struct usb_serial_port *port) +{ + dbg("%s", __FUNCTION__); +} + + +static void +option_rx_unthrottle (struct usb_serial_port *port) +{ + dbg("%s", __FUNCTION__); +} + + +static void +option_break_ctl (struct usb_serial_port *port, int break_state) +{ + /* Unfortunately, I don't know how to send a break */ + dbg("%s", __FUNCTION__); +} + + +static void +option_set_termios (struct usb_serial_port *port, + struct termios *old_termios) +{ + dbg("%s", __FUNCTION__); + + option_send_setup(port); +} + +static int +option_tiocmget(struct usb_serial_port *port, struct file *file) +{ + unsigned int value; + struct option_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + value = ((portdata->rts_state) ? TIOCM_RTS : 0) | + ((portdata->dtr_state) ? TIOCM_DTR : 0) | + ((portdata->cts_state) ? TIOCM_CTS : 0) | + ((portdata->dsr_state) ? TIOCM_DSR : 0) | + ((portdata->dcd_state) ? TIOCM_CAR : 0) | + ((portdata->ri_state) ? TIOCM_RNG : 0); + + return value; +} + +static int +option_tiocmset (struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear) +{ + struct option_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + if (set & TIOCM_RTS) + portdata->rts_state = 1; + if (set & TIOCM_DTR) + portdata->dtr_state = 1; + + if (clear & TIOCM_RTS) + portdata->rts_state = 0; + if (clear & TIOCM_DTR) + portdata->dtr_state = 0; + return option_send_setup(port); +} + +static int +option_ioctl (struct usb_serial_port *port, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +/* Write */ +static int +option_write(struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct option_port_private *portdata; + int i; + int left, todo; + struct urb *this_urb = NULL; /* spurious */ + int err; + + portdata = usb_get_serial_port_data(port); + + dbg("%s: write (%d chars)", __FUNCTION__, count); + +#if 0 + spin_lock(&port->lock); + if (port->write_urb_busy) { + spin_unlock(&port->lock); + dbg("%s: already writing", __FUNCTION__); + return 0; + } + port->write_urb_busy = 1; + spin_unlock(&port->lock); +#endif + + i = 0; + left = count; + while (left>0) { + todo = left; + if (todo > OUT_BUFLEN) + todo = OUT_BUFLEN; + + for (;i < N_OUT_URB; i++) { + /* Check we have a valid urb/endpoint before we use it... */ + this_urb = portdata->out_urbs[i]; + if (this_urb->status != -EINPROGRESS) + break; + if (this_urb->transfer_flags & URB_ASYNC_UNLINK) + continue; + if (time_before(jiffies, portdata->tx_start_time[i] + 10 * HZ)) + continue; + this_urb->transfer_flags |= URB_ASYNC_UNLINK; + usb_unlink_urb(this_urb); + } + + if (i == N_OUT_URB) { + /* no bulk out free! */ + dbg("%s: no output urb -- left %d", __FUNCTION__,count-left); +#if 0 + port->write_urb_busy = 0; +#endif + return count-left; + } + + dbg("%s: endpoint %d buf %d", __FUNCTION__, usb_pipeendpoint(this_urb->pipe), i); + + memcpy (this_urb->transfer_buffer, buf, todo); + + /* send the data out the bulk port */ + this_urb->transfer_buffer_length = todo; + + this_urb->transfer_flags &= ~URB_ASYNC_UNLINK; + this_urb->dev = port->serial->dev; + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err) { + dbg("usb_submit_urb %p (write bulk) failed (%d,, has %d)", this_urb, err, this_urb->status); + continue; + } + portdata->tx_start_time[i] = jiffies; + buf += todo; + left -= todo; + } + + count -= left; +#if 0 + port->write_urb_busy = 0; +#endif + dbg("%s: wrote (did %d)", __FUNCTION__, count); + return count; +} + +static void +option_indat_callback (struct urb *urb, struct pt_regs *regs) +{ + int i, err; + int endpoint; + struct usb_serial_port *port; + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; + + dbg("%s: %p", __FUNCTION__, urb); + + endpoint = usb_pipeendpoint(urb->pipe); + port = (struct usb_serial_port *) urb->context; + + if (urb->status) { + dbg("%s: nonzero status: %d on endpoint %02x.", + __FUNCTION__, urb->status, endpoint); + } else { + tty = port->tty; + if (urb->actual_length) { + for (i = 0; i < urb->actual_length ; ++i) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + tty_flip_buffer_push(tty); + tty_insert_flip_char(tty, data[i], 0); + } + tty_flip_buffer_push(tty); + } else { + dbg("%s: empty read urb received", __FUNCTION__); + } + + /* Resubmit urb so we continue receiving */ + if (port->open_count && urb->status != -ESHUTDOWN) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + printk(KERN_ERR "%s: resubmit read urb failed. (%d)", __FUNCTION__, err); + } + } + return; +} + +static void +option_outdat_callback (struct urb *urb, struct pt_regs *regs) +{ + struct usb_serial_port *port; + + dbg("%s", __FUNCTION__); + + port = (struct usb_serial_port *) urb->context; + + if (port->open_count) + schedule_work(&port->work); +} + +static void +option_instat_callback (struct urb *urb, struct pt_regs *regs) +{ + int err; + struct usb_serial_port *port = (struct usb_serial_port *) urb->context; + struct option_port_private *portdata = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + + dbg("%s", __FUNCTION__); + dbg("%s: urb %p port %p has data %p", __FUNCTION__,urb,port,portdata); + + if (urb->status == 0) { + struct usb_ctrlrequest *req_pkt = + (struct usb_ctrlrequest *)urb->transfer_buffer; + + if (!req_pkt) { + dbg("%s: NULL req_pkt\n", __FUNCTION__); + return; + } + if ((req_pkt->bRequestType == 0xA1) && (req_pkt->bRequest == 0x20)) { + int old_dcd_state; + unsigned char signals = *((unsigned char *) + urb->transfer_buffer + sizeof(struct usb_ctrlrequest)); + + dbg("%s: signal x%x", __FUNCTION__, signals); + + old_dcd_state = portdata->dcd_state; + portdata->cts_state = 1; + portdata->dcd_state = ((signals & 0x01) ? 1 : 0); + portdata->dsr_state = ((signals & 0x02) ? 1 : 0); + portdata->ri_state = ((signals & 0x08) ? 1 : 0); + + if (port->tty && !C_CLOCAL(port->tty) + && old_dcd_state && !portdata->dcd_state) { + tty_hangup(port->tty); + } + } else + dbg("%s: type %x req %x", __FUNCTION__, req_pkt->bRequestType,req_pkt->bRequest); + } else + dbg("%s: error %d", __FUNCTION__, urb->status); + + /* Resubmit urb so we continue receiving IRQ data */ + if (urb->status != -ESHUTDOWN) { + urb->dev = serial->dev; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dbg("%s: resubmit intr urb failed. (%d)", __FUNCTION__, err); + } +} + + +static int +option_write_room (struct usb_serial_port *port) +{ + struct option_port_private *portdata; + int i; + int data_len = 0; + struct urb *this_urb; + + portdata = usb_get_serial_port_data(port); + + for (i=0; i < N_OUT_URB; i++) + this_urb = portdata->out_urbs[i]; + if (this_urb && this_urb->status != -EINPROGRESS) + data_len += OUT_BUFLEN; + + dbg("%s: %d", __FUNCTION__, data_len); + return data_len; +} + + +static int +option_chars_in_buffer (struct usb_serial_port *port) +{ + struct option_port_private *portdata; + int i; + int data_len = 0; + struct urb *this_urb; + + portdata = usb_get_serial_port_data(port); + + for (i=0; i < N_OUT_URB; i++) + this_urb = portdata->out_urbs[i]; + if (this_urb && this_urb->status == -EINPROGRESS) + data_len += this_urb->transfer_buffer_length; + + dbg("%s: %d", __FUNCTION__, data_len); + return data_len; +} + + +static int +option_open (struct usb_serial_port *port, struct file *filp) +{ + struct option_port_private *portdata; + struct usb_serial *serial = port->serial; + int i, err; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + dbg("%s", __FUNCTION__); + + /* Set some sane defaults */ + portdata->rts_state = 1; + portdata->dtr_state = 1; + + /* Reset low level data toggle and start reading from endpoints */ + for (i = 0; i < N_IN_URB; i++) { + urb = portdata->in_urbs[i]; + if (! urb) + continue; + if (urb->dev != serial->dev) { + dbg("%s: dev %p != %p", __FUNCTION__, urb->dev, serial->dev); + continue; + } + + /* make sure endpoint data toggle is synchronized with the device */ + + usb_clear_halt(urb->dev, urb->pipe); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) { + dbg("%s: submit urb %d failed (%d) %d", __FUNCTION__, i, err, + urb->transfer_buffer_length); + } + } + + /* Reset low level data toggle on out endpoints */ + for (i = 0; i < N_OUT_URB; i++) { + urb = portdata->out_urbs[i]; + if (! urb) + continue; + urb->dev = serial->dev; + /* usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), 0); */ + } + + port->tty->low_latency = 1; + + option_send_setup(port); + + return (0); +} + +static inline void +stop_urb(struct urb *urb) +{ + if (urb && urb->status == -EINPROGRESS) { + urb->transfer_flags &= ~URB_ASYNC_UNLINK; + usb_kill_urb(urb); + } +} + +static void +option_close(struct usb_serial_port *port, struct file *filp) +{ + int i; + struct usb_serial *serial = port->serial; + struct option_port_private *portdata; + + dbg("%s", __FUNCTION__); + portdata = usb_get_serial_port_data(port); + + portdata->rts_state = 0; + portdata->dtr_state = 0; + + if (serial->dev) { + option_send_setup(port); + + /* Stop reading/writing urbs */ + for (i = 0; i < N_IN_URB; i++) + stop_urb(portdata->in_urbs[i]); + for (i = 0; i < N_OUT_URB; i++) + stop_urb(portdata->out_urbs[i]); + } + port->tty = NULL; +} + + +/* Helper functions used by option_setup_urbs */ +static struct urb * +option_setup_urb (struct usb_serial *serial, int endpoint, + int dir, void *ctx, char *buf, int len, + void (*callback)(struct urb *, struct pt_regs *regs)) +{ + struct urb *urb; + + if (endpoint == -1) + return NULL; /* endpoint not needed */ + + urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ + if (urb == NULL) { + dbg("%s: alloc for endpoint %d failed.", __FUNCTION__, endpoint); + return NULL; + } + + /* Fill URB using supplied data. */ + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx); + + return urb; +} + +/* Setup urbs */ +static void +option_setup_urbs(struct usb_serial *serial) +{ + int j; + struct usb_serial_port *port; + struct option_port_private *portdata; + + dbg("%s", __FUNCTION__); + + port = serial->port[0]; + portdata = usb_get_serial_port_data(port); + + /* Do indat endpoints first */ + for (j = 0; j <= N_IN_URB; ++j) { + portdata->in_urbs[j] = option_setup_urb (serial, + port->bulk_in_endpointAddress, USB_DIR_IN, port, + portdata->in_buffer[j], IN_BUFLEN, option_indat_callback); + } + + /* outdat endpoints */ + for (j = 0; j <= N_OUT_URB; ++j) { + portdata->out_urbs[j] = option_setup_urb (serial, + port->bulk_out_endpointAddress, USB_DIR_OUT, port, + portdata->out_buffer[j], OUT_BUFLEN, option_outdat_callback); + } +} + + +static int +option_send_setup(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct option_port_private *portdata; + + dbg("%s", __FUNCTION__); + + portdata = usb_get_serial_port_data(port); + + if (port->tty) { + int val = 0; + if (portdata->dtr_state) + val |= 0x01; + if (portdata->rts_state) + val |= 0x02; + + return usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 0x22,0x21,val,0,NULL,0,USB_CTRL_SET_TIMEOUT); + } + + return 0; +} + + +static int +option_startup (struct usb_serial *serial) +{ + int i, err; + struct usb_serial_port *port; + struct option_port_private *portdata; + + dbg("%s", __FUNCTION__); + + /* Now setup per port private data */ + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + portdata = kmalloc(sizeof(struct option_port_private), GFP_KERNEL); + if (!portdata) { + dbg("%s: kmalloc for option_port_private (%d) failed!.", __FUNCTION__, i); + return (1); + } + memset(portdata, 0, sizeof(struct option_port_private)); + + usb_set_serial_port_data(port, portdata); + + if (! port->interrupt_in_urb) + continue; + err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (err) + dbg("%s: submit irq_in urb failed %d", __FUNCTION__, err); + } + + option_setup_urbs(serial); + + return (0); +} + +static void +option_shutdown (struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct option_port_private *portdata; + + dbg("%s", __FUNCTION__); + + /* Stop reading/writing urbs */ + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + for (j = 0; j < N_IN_URB; j++) + stop_urb(portdata->in_urbs[j]); + for (j = 0; j < N_OUT_URB; j++) + stop_urb(portdata->out_urbs[j]); + } + + /* Now free them */ + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + + for (j = 0; j < N_IN_URB; j++) { + if (portdata->in_urbs[j]) { + usb_free_urb(portdata->in_urbs[j]); + portdata->in_urbs[j] = NULL; + } + } + for (j = 0; j < N_OUT_URB; j++) { + if (portdata->out_urbs[j]) { + usb_free_urb(portdata->out_urbs[j]); + portdata->out_urbs[j] = NULL; + } + } + } + + /* Now free per port private data */ + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + kfree(usb_get_serial_port_data(port)); + } +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages"); + diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index d2891f475793..9fcc7bd1fbe4 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -862,6 +862,15 @@ UNUSUAL_DEV( 0x090a, 0x1001, 0x0100, 0x0100, US_SC_DEVICE, US_PR_BULK, NULL, US_FL_NEED_OVERRIDE ), +/* Reported by Filippo Bardelli <filibard@libero.it> + * The device reports a subclass of RBC, which is wrong. + */ +UNUSUAL_DEV( 0x090a, 0x1050, 0x0100, 0x0100, + "Trumpion Microelectronics, Inc.", + "33520 USB Digital Voice Recorder", + US_SC_UFI, US_PR_DEVICE, NULL, + 0), + /* Trumpion Microelectronics MP3 player (felipe_alfaro@linuxmail.org) */ UNUSUAL_DEV( 0x090a, 0x1200, 0x0000, 0x9999, "Trumpion", |