diff options
Diffstat (limited to 'drivers/usb/serial/pl2303.c')
-rw-r--r-- | drivers/usb/serial/pl2303.c | 82 |
1 files changed, 69 insertions, 13 deletions
diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index ca69eb42071b..c9ebefd8f35f 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -33,9 +33,11 @@ #define PL2303_QUIRK_UART_STATE_IDX0 BIT(0) #define PL2303_QUIRK_LEGACY BIT(1) +#define PL2303_QUIRK_ENDPOINT_HACK BIT(2) static const struct usb_device_id id_table[] = { - { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) }, @@ -48,7 +50,8 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ZTEK) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, - { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID2) }, { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) }, { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) }, @@ -68,7 +71,8 @@ static const struct usb_device_id id_table[] = { .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75), .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, - { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */ { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) }, { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) }, @@ -78,7 +82,8 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) }, { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) }, { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) }, - { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID) }, + { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) }, { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) }, { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) }, @@ -218,20 +223,68 @@ static int pl2303_probe(struct usb_serial *serial, return 0; } +/* + * Use interrupt endpoint from first interface if available. + * + * This is needed due to the looney way its endpoints are set up. + */ +static int pl2303_endpoint_hack(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct usb_interface *interface = serial->interface; + struct usb_device *dev = serial->dev; + struct device *ddev = &interface->dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + unsigned int i; + + if (interface == dev->actconfig->interface[0]) + return 0; + + /* check out the endpoints of the other interface */ + iface_desc = dev->actconfig->interface[0]->cur_altsetting; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + continue; + + dev_dbg(ddev, "found interrupt in on separate interface\n"); + if (epds->num_interrupt_in < ARRAY_SIZE(epds->interrupt_in)) + epds->interrupt_in[epds->num_interrupt_in++] = endpoint; + } + + return 0; +} + +static int pl2303_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + unsigned long quirks = (unsigned long)usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int ret; + + if (quirks & PL2303_QUIRK_ENDPOINT_HACK) { + ret = pl2303_endpoint_hack(serial, epds); + if (ret) + return ret; + } + + if (epds->num_interrupt_in < 1) { + dev_err(dev, "required interrupt-in endpoint missing\n"); + return -ENODEV; + } + + return 1; +} + static int pl2303_startup(struct usb_serial *serial) { struct pl2303_serial_private *spriv; - unsigned char num_ports = serial->num_ports; enum pl2303_type type = TYPE_01; unsigned char *buf; - if (serial->num_bulk_in < num_ports || - serial->num_bulk_out < num_ports || - serial->num_interrupt_in < num_ports) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); if (!spriv) return -ENOMEM; @@ -938,7 +991,9 @@ static struct usb_serial_driver pl2303_device = { .name = "pl2303", }, .id_table = id_table, - .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 0, /* see pl2303_calc_num_ports */ .bulk_in_size = 256, .bulk_out_size = 256, .open = pl2303_open, @@ -954,6 +1009,7 @@ static struct usb_serial_driver pl2303_device = { .process_read_urb = pl2303_process_read_urb, .read_int_callback = pl2303_read_int_callback, .probe = pl2303_probe, + .calc_num_ports = pl2303_calc_num_ports, .attach = pl2303_startup, .release = pl2303_release, .port_probe = pl2303_port_probe, |