ChangeSet 1.1006.11.5, 2003/03/11 17:45:38-08:00, greg@kroah.com [PATCH] USB: add support for Treo devices to the visor driver Thanks to Adam Pennington for the bulk of this work. drivers/usb/serial/visor.c | 227 +++++++++++++++++++++++++++++++++++++-------- drivers/usb/serial/visor.h | 37 ++++++- 2 files changed, 223 insertions(+), 41 deletions(-) diff -Nru a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c --- a/drivers/usb/serial/visor.c Thu Mar 27 16:02:35 2003 +++ b/drivers/usb/serial/visor.c Thu Mar 27 16:02:35 2003 @@ -2,7 +2,7 @@ * USB HandSpring Visor, Palm m50x, and Sony Clie driver * (supports all of the Palm OS USB devices) * - * Copyright (C) 1999 - 2002 + * Copyright (C) 1999 - 2003 * Greg Kroah-Hartman (greg@kroah.com) * * This program is free software; you can redistribute it and/or modify @@ -16,6 +16,16 @@ * Added support for the Sony Clie NZ90V device. Thanks to Martin Brachtl * for the information. * + * (3/07/2003) Adam Pennington + * Backported version 2.1 of the driver from the 2.5 bitkeeper tree + * making Treo actually work. + * + * (2/18/2003) Adam Powell + * Backported 2.5 driver mods to support Handspring Treo. + * + * (2/11/2003) Adam Powell + * Added device and vendor ids for the Samsung I330 phone. + * * (04/03/2002) gkh * Added support for the Sony OS 4.1 devices. Thanks to Hiroyuki ARAKI * for the information. @@ -150,9 +160,9 @@ /* * Version Information */ -#define DRIVER_VERSION "v1.6" +#define DRIVER_VERSION "v1.7" #define DRIVER_AUTHOR "Greg Kroah-Hartman " -#define DRIVER_DESC "USB HandSpring Visor, Palm m50x, Sony Clié driver" +#define DRIVER_DESC "USB HandSpring Visor, Palm m50x, Treo, Sony Clié driver" /* function prototypes for a handspring visor */ static int visor_open (struct usb_serial_port *port, struct file *filp); @@ -168,10 +178,14 @@ static void visor_set_termios (struct usb_serial_port *port, struct termios *old_termios); static void visor_write_bulk_callback (struct urb *urb); static void visor_read_bulk_callback (struct urb *urb); +static void visor_read_int_callback (struct urb *urb); static int clie_3_5_startup (struct usb_serial *serial); +static void treo_attach (struct usb_serial *serial); static struct usb_device_id id_table [] = { + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID) }, @@ -182,12 +196,12 @@ { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) }, - { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_S360_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID) }, { } /* Terminating entry */ }; @@ -198,6 +212,7 @@ static __devinitdata struct usb_device_id id_table_combined [] = { { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID) }, { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID) }, @@ -214,6 +229,7 @@ { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID) }, { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID) }, { } /* Terminating entry */ }; @@ -224,9 +240,9 @@ /* All of the device info needed for the Handspring Visor, and Palm 4.0 devices */ static struct usb_serial_device_type handspring_device = { .owner = THIS_MODULE, - .name = "Handspring Visor / Palm 4.0 / Clié 4.x", + .name = "Handspring Visor / Treo / Palm 4.0 / Clié 4.x", .id_table = id_table, - .num_interrupt_in = 0, + .num_interrupt_in = NUM_DONT_CARE, .num_bulk_in = 2, .num_bulk_out = 2, .num_ports = 2, @@ -243,6 +259,7 @@ .chars_in_buffer = visor_chars_in_buffer, .write_bulk_callback = visor_write_bulk_callback, .read_bulk_callback = visor_read_bulk_callback, + .read_int_callback = visor_read_int_callback, }; /* device info for the Sony Clie OS version 3.5 */ @@ -315,9 +332,19 @@ port->read_urb->transfer_buffer_length, visor_read_bulk_callback, port); result = usb_submit_urb(port->read_urb); - if (result) + if (result) { err("%s - failed submitting read urb, error %d", __FUNCTION__, result); + goto exit; + } + if (port->interrupt_in_urb) { + dbg("%s - adding interrupt input for treo", __FUNCTION__); + result = usb_submit_urb(port->interrupt_in_urb); + if (result) + err("%s - failed submitting interrupt urb, error %d\n", + __FUNCTION__, result); + } +exit: return result; } @@ -336,6 +363,9 @@ if (!serial) return; + + + if (serial->dev) { /* only send a shutdown message if the * device is still here */ @@ -351,11 +381,25 @@ transfer_buffer, 0x12, 300); kfree (transfer_buffer); } - /* shutdown our bulk read */ + + /* shutdown our urbs */ usb_unlink_urb (port->read_urb); + if (port->interrupt_in_urb) + usb_unlink_urb (port->interrupt_in_urb); + /* Try to send shutdown message, if the device is gone, this will just fail. */ + transfer_buffer = kmalloc (0x12, GFP_KERNEL); + if (transfer_buffer) { + usb_control_msg (serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + VISOR_CLOSE_NOTIFICATION, 0xc2, + 0x0000, 0x0000, + transfer_buffer, 0x12, 300); + kfree (transfer_buffer); + + } } /* Uncomment the following line if you want to see some statistics in your syslog */ - /* info ("Bytes In = %d Bytes Out = %d", bytes_in, bytes_out); */ + info ("Bytes In = %d Bytes Out = %d", bytes_in, bytes_out); } @@ -552,6 +596,40 @@ } +static void visor_read_int_callback (struct urb *urb) +{ + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); + goto exit; + } + + /* + * This information is still unknown what it can be used for. + * If anyone has an idea, please let the author know... + * + * Rumor has it this endpoint is used to notify when data + * is ready to be read from the bulk ones. + */ + usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, + urb->transfer_buffer); + +exit: + return; +} + + static void visor_throttle (struct usb_serial_port *port) { dbg("%s - port %d", __FUNCTION__, port->number); @@ -575,30 +653,49 @@ { int response; int i; - unsigned char *transfer_buffer = kmalloc (256, GFP_KERNEL); - - if (!transfer_buffer) { - err("%s - kmalloc(%d) failed.", __FUNCTION__, 256); - return -ENOMEM; - } + unsigned char *transfer_buffer; dbg("%s", __FUNCTION__); dbg("%s - Set config to 1", __FUNCTION__); usb_set_configuration (serial->dev, 1); - /* send a get connection info request */ - response = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), VISOR_GET_CONNECTION_INFORMATION, - 0xc2, 0x0000, 0x0000, transfer_buffer, 0x12, 300); - if (response < 0) { - err("%s - error getting connection information", __FUNCTION__); - } else { - struct visor_connection_info *connection_info = (struct visor_connection_info *)transfer_buffer; + if ((serial->dev->descriptor.idVendor == HANDSPRING_VENDOR_ID) && + (serial->dev->descriptor.idProduct == HANDSPRING_VISOR_ID)) { + struct visor_connection_info *connection_info; char *string; + int num_ports; + + transfer_buffer = kmalloc (sizeof (*connection_info), + GFP_KERNEL); + if (!transfer_buffer) { + err("%s - kmalloc(%d) failed.", __FUNCTION__, + sizeof (*connection_info)); + return -ENOMEM; + } + /* send a get connection info request */ + response = usb_control_msg (serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + VISOR_GET_CONNECTION_INFORMATION, + 0xc2, 0x0000, 0x0000, + transfer_buffer, + sizeof (*connection_info), 300); + if (response < 0) { + err("%s - error getting connection information", + __FUNCTION__); + goto exit; + } + + connection_info = (struct visor_connection_info *)transfer_buffer; le16_to_cpus(&connection_info->num_ports); + num_ports = connection_info->num_ports; + + /* handle devices that report invalid stuff here */ + if (num_ports > 2) + num_ports = 2; info("%s: Number of ports: %d", serial->type->name, connection_info->num_ports); - for (i = 0; i < connection_info->num_ports; ++i) { + for (i = 0; i < num_ports; ++i) { switch (connection_info->connections[i].port_function_id) { case VISOR_FUNCTION_GENERIC: string = "Generic"; @@ -622,32 +719,32 @@ info("%s: port %d, is for %s use and is bound to ttyUSB%d", serial->type->name, connection_info->connections[i].port, string, serial->minor + i); } - } + } else { + struct palm_ext_connection_info *connection_info; - if ((serial->dev->descriptor.idVendor == PALM_VENDOR_ID) || - ((serial->dev->descriptor.idVendor == SONY_VENDOR_ID) && - (serial->dev->descriptor.idProduct != SONY_CLIE_4_1_ID))) { - /* Palm OS 4.0 Hack */ - response = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), - PALM_GET_SOME_UNKNOWN_INFORMATION, - 0xc2, 0x0000, 0x0000, transfer_buffer, - 0x14, 300); - if (response < 0) { - err("%s - error getting first unknown palm command", __FUNCTION__); - } else { - usb_serial_debug_data (__FILE__, __FUNCTION__, 0x14, transfer_buffer); + transfer_buffer = kmalloc (sizeof (*connection_info), + GFP_KERNEL); + if (!transfer_buffer) { + err("%s - kmalloc(%d) failed.", __FUNCTION__, + sizeof (*connection_info)); + return -ENOMEM; } + response = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), - PALM_GET_SOME_UNKNOWN_INFORMATION, + PALM_GET_EXT_CONNECTION_INFORMATION, 0xc2, 0x0000, 0x0000, transfer_buffer, - 0x14, 300); + sizeof (*connection_info), 300); if (response < 0) { - err("%s - error getting second unknown palm command", __FUNCTION__); + err("%s - error %d getting connection info", + __FUNCTION__, response); } else { usb_serial_debug_data (__FILE__, __FUNCTION__, 0x14, transfer_buffer); } } + /* Do our horrible Treo hack, if we should */ + treo_attach(serial); + /* ask for the number of bytes available, but ignore the response as it is broken */ response = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), VISOR_REQUEST_BYTES_AVAILABLE, 0xc2, 0x0000, 0x0005, transfer_buffer, 0x02, 300); @@ -655,6 +752,7 @@ err("%s - error getting bytes available request", __FUNCTION__); } +exit: kfree (transfer_buffer); /* continue on with initialization */ @@ -702,6 +800,46 @@ return 0; } +static void treo_attach (struct usb_serial *serial) +{ + struct usb_serial_port *port; + int i; + + /* Only do this endpoint hack for the Handspring devices with + * interrupt in endpoints, which for now are the Treo devices. */ + if ((serial->dev->descriptor.idVendor != HANDSPRING_VENDOR_ID) || + (serial->num_interrupt_in == 0)) + return; + + dbg("%s", __FUNCTION__); + + /* Ok, this is pretty ugly, but these devices want to use the + * interrupt endpoint as paired up with a bulk endpoint for a + * "virtual serial port". So let's force the endpoints to be + * where we want them to be. */ + for (i = serial->num_bulk_in; i < serial->num_ports; ++i) { + port = &serial->port[i]; + port->read_urb = serial->port[0].read_urb; + port->bulk_in_endpointAddress = serial->port[0].bulk_in_endpointAddress; + port->bulk_in_buffer = serial->port[0].bulk_in_buffer; + } + + for (i = serial->num_bulk_out; i < serial->num_ports; ++i) { + port = &serial->port[i]; + port->write_urb = serial->port[0].write_urb; + port->bulk_out_size = serial->port[0].bulk_out_size; + port->bulk_out_endpointAddress = serial->port[0].bulk_out_endpointAddress; + port->bulk_out_buffer = serial->port[0].bulk_out_buffer; + } + + for (i = serial->num_interrupt_in; i < serial->num_ports; ++i) { + port = &serial->port[i]; + port->interrupt_in_urb = serial->port[0].interrupt_in_urb; + port->interrupt_in_endpointAddress = serial->port[0].interrupt_in_endpointAddress; + port->interrupt_in_buffer = serial->port[0].interrupt_in_buffer; + } +} + static void visor_shutdown (struct usb_serial *serial) { dbg("%s", __FUNCTION__); @@ -849,4 +987,15 @@ MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug enabled or not"); + + + + + + + + + + + diff -Nru a/drivers/usb/serial/visor.h b/drivers/usb/serial/visor.h --- a/drivers/usb/serial/visor.h Thu Mar 27 16:02:35 2003 +++ b/drivers/usb/serial/visor.h Thu Mar 27 16:02:35 2003 @@ -1,7 +1,7 @@ /* * USB HandSpring Visor driver * - * Copyright (C) 1999 - 2002 + * Copyright (C) 1999 - 2003 * Greg Kroah-Hartman (greg@kroah.com) * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #define HANDSPRING_VENDOR_ID 0x082d #define HANDSPRING_VISOR_ID 0x0100 +#define HANDSPRING_TREO_ID 0x0200 #define PALM_VENDOR_ID 0x0830 #define PALM_M500_ID 0x0001 @@ -40,6 +41,9 @@ #define SONY_CLIE_NX60_ID 0x00DA #define SONY_CLIE_NZ90V_ID 0x00E9 +#define SAMSUNG_VENDOR_ID 0x04E8 +#define SAMSUNG_SCH_I330_ID 0x8001 + /**************************************************************************** * Handspring Visor Vendor specific request codes (bRequest values) * A big thank you to Handspring for providing the following information. @@ -95,7 +99,36 @@ * PALM_GET_SOME_UNKNOWN_INFORMATION is sent by the host during enumeration to * get some information from the M series devices, that is currently unknown. ****************************************************************************/ -#define PALM_GET_SOME_UNKNOWN_INFORMATION 0x04 +#define PALM_GET_EXT_CONNECTION_INFORMATION 0x04 + +/** + * struct palm_ext_connection_info - return data from a PALM_GET_EXT_CONNECTION_INFORMATION request + * @num_ports: maximum number of functions/connections in use + * @endpoint_numbers_different: will be 1 if in and out endpoints numbers are + * different, otherwise it is 0. If value is 1, then + * connections.end_point_info is non-zero. If value is 0, then + * connections.port contains the endpoint number, which is the same for in + * and out. + * @port_function_id: contains the creator id of the applicaton that opened + * this connection. + * @port: contains the in/out endpoint number. Is 0 if in and out endpoint + * numbers are different. + * @end_point_info: high nubbe is in endpoint and low nibble will indicate out + * endpoint. Is 0 if in and out endpoints are the same. + * + * The maximum number of connections currently supported is 2 + */ +struct palm_ext_connection_info { + __u8 num_ports; + __u8 endpoint_numbers_different; + __u16 reserved1; + struct { + __u32 port_function_id; + __u8 port; + __u8 end_point_info; + __u16 reserved; + } connections[2]; +}; #endif