Add a quirk for Alereon HWA devices to concatenate the frames of isoc transfer requests. Signed-off-by: Thomas Pugliese <thomas.pugliese@xxxxxxxxx> --- drivers/usb/host/hwa-hc.c | 13 ++++++++--- drivers/usb/wusbcore/wa-hc.c | 4 +++- drivers/usb/wusbcore/wa-hc.h | 13 ++++++++++- drivers/usb/wusbcore/wa-xfer.c | 49 ++++++++++++++++++++++++++++++---------- 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index e5fb3cf..ada0a52 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -679,7 +679,8 @@ static void hwahc_security_release(struct hwahc *hwahc) /* nothing to do here so far... */ } -static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) +static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface, + kernel_ulong_t quirks) { int result; struct device *dev = &iface->dev; @@ -724,7 +725,7 @@ static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) dev_err(dev, "Can't create WUSB HC structures: %d\n", result); goto error_wusbhc_create; } - result = wa_create(&hwahc->wa, iface); + result = wa_create(&hwahc->wa, iface, quirks); if (result < 0) goto error_wa_create; return 0; @@ -780,7 +781,7 @@ static int hwahc_probe(struct usb_interface *usb_iface, wusbhc = usb_hcd_to_wusbhc(usb_hcd); hwahc = container_of(wusbhc, struct hwahc, wusbhc); hwahc_init(hwahc); - result = hwahc_create(hwahc, usb_iface); + result = hwahc_create(hwahc, usb_iface, id->driver_info); if (result < 0) { dev_err(dev, "Cannot initialize internals: %d\n", result); goto error_hwahc_create; @@ -824,6 +825,12 @@ static void hwahc_disconnect(struct usb_interface *usb_iface) } static struct usb_device_id hwahc_id_table[] = { + /* Alereon 5310 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5310, 0xe0, 0x02, 0x01), + .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC }, + /* Alereon 5611 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5611, 0xe0, 0x02, 0x01), + .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC }, /* FIXME: use class labels for this */ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, {}, diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c index 6c09b0e..368360f 100644 --- a/drivers/usb/wusbcore/wa-hc.c +++ b/drivers/usb/wusbcore/wa-hc.c @@ -33,7 +33,8 @@ * wa->usb_dev and wa->usb_iface initialized and refcounted, * wa->wa_descr initialized. */ -int wa_create(struct wahc *wa, struct usb_interface *iface) +int wa_create(struct wahc *wa, struct usb_interface *iface, + kernel_ulong_t quirks) { int result; struct device *dev = &iface->dev; @@ -41,6 +42,7 @@ int wa_create(struct wahc *wa, struct usb_interface *iface) result = wa_rpipes_create(wa); if (result < 0) goto error_rpipes_create; + wa->quirks = quirks; /* Fill up Data Transfer EP pointers */ wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc; wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc; diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h index 41afaa6..e614f02 100644 --- a/drivers/usb/wusbcore/wa-hc.h +++ b/drivers/usb/wusbcore/wa-hc.h @@ -128,6 +128,14 @@ enum wa_dti_state { WA_DTI_ISOC_PACKET_STATUS_PENDING }; +enum wa_quirks { + /* + * The Alereon HWA expects the data frames in isochronous transfer + * requests to be concatenated and not sent as separate packets. + */ + WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC = 0x01, +}; + /** * Instance of a HWA Host Controller * @@ -218,10 +226,13 @@ struct wahc { struct work_struct xfer_enqueue_work; struct work_struct xfer_error_work; atomic_t xfer_id_count; + + kernel_ulong_t quirks; }; -extern int wa_create(struct wahc *wa, struct usb_interface *iface); +extern int wa_create(struct wahc *wa, struct usb_interface *iface, + kernel_ulong_t); extern void __wa_destroy(struct wahc *wa); void wa_reset_all(struct wahc *wa); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 16918e9..2a69175 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -479,13 +479,29 @@ static int __wa_seg_calculate_isoc_frame_count(struct wa_xfer *xfer, { int segment_size = 0, frame_count = 0; int index = isoc_frame_offset; + struct usb_iso_packet_descriptor *iso_frame_desc = + xfer->urb->iso_frame_desc; while ((index < xfer->urb->number_of_packets) - && ((segment_size + xfer->urb->iso_frame_desc[index].length) + && ((segment_size + iso_frame_desc[index].length) <= xfer->seg_size)) { + /* + * For Alereon HWA devices, only include an isoc frame in a + * segment if it is physically contiguous with the previous + * frame. This is required because those devices expect + * the isoc frames to be sent as a single USB transaction as + * opposed to one transaction per frame with standard HWA. + */ + if ((xfer->wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC) + && (index > isoc_frame_offset) + && ((iso_frame_desc[index - 1].offset + + iso_frame_desc[index - 1].length) != + iso_frame_desc[index].offset)) + break; + /* this frame fits. count it. */ ++frame_count; - segment_size += xfer->urb->iso_frame_desc[index].length; + segment_size += iso_frame_desc[index].length; /* move to the next isoc frame. */ ++index; @@ -681,7 +697,11 @@ static void wa_seg_dto_cb(struct urb *urb) wa = xfer->wa; dev = &wa->usb_iface->dev; if (usb_pipeisoc(xfer->urb->pipe)) { - xfer->dto_isoc_frame_index += 1; + /* Alereon HWA sends all isoc frames in a single transfer. */ + if (wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC) + xfer->dto_isoc_frame_index += seg->isoc_frame_count; + else + xfer->dto_isoc_frame_index += 1; if (xfer->dto_isoc_frame_index < seg->isoc_frame_count) { data_send_done = 0; holding_dto = 1; /* checked in error cases. */ @@ -1007,17 +1027,18 @@ static struct scatterlist *wa_xfer_create_subset_sg(struct scatterlist *in_sg, static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer, struct wa_seg *seg, int curr_iso_frame) { - /* - * dto urb buffer address and size pulled from - * iso_frame_desc. - */ - seg->dto_urb->transfer_dma = xfer->urb->transfer_dma + - xfer->urb->iso_frame_desc[curr_iso_frame].offset; seg->dto_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; seg->dto_urb->sg = NULL; seg->dto_urb->num_sgs = 0; - seg->dto_urb->transfer_buffer_length = - xfer->urb->iso_frame_desc[curr_iso_frame].length; + /* dto urb buffer address pulled from iso_frame_desc. */ + seg->dto_urb->transfer_dma = xfer->urb->transfer_dma + + xfer->urb->iso_frame_desc[curr_iso_frame].offset; + /* The Alereon HWA sends a single URB with all isoc segs. */ + if (xfer->wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC) + seg->dto_urb->transfer_buffer_length = seg->isoc_size; + else + seg->dto_urb->transfer_buffer_length = + xfer->urb->iso_frame_desc[curr_iso_frame].length; } /* @@ -1298,6 +1319,8 @@ static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, } /* submit the isoc packet descriptor if present. */ if (seg->isoc_pack_desc_urb) { + struct wahc *wa = xfer->wa; + result = usb_submit_urb(seg->isoc_pack_desc_urb, GFP_ATOMIC); if (result < 0) { pr_err("%s: xfer %p#%u: ISO packet descriptor submit failed: %d\n", @@ -1308,8 +1331,10 @@ static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, /* * If this segment contains more than one isoc frame, hold * onto the dto resource until we send all frames. + * Only applies to non-Alereon devices. */ - if (seg->isoc_frame_count > 1) + if (((wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC) == 0) + && (seg->isoc_frame_count > 1)) *dto_done = 0; } /* submit the out data if this is an out request. */ -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html