From f07ddb9ef5c25c1044ab5b6509241320fb5e831a Mon Sep 17 00:00:00 2001 From: Thomas Pugliese Date: Wed, 23 Oct 2013 14:44:28 -0500 Subject: usb: wusbcore: add a quirk for Alereon HWA device isoc behavior Add a quirk for Alereon HWA devices to concatenate the frames of isoc transfer requests. Signed-off-by: Thomas Pugliese Signed-off-by: Greg Kroah-Hartman --- drivers/usb/wusbcore/wa-hc.c | 4 +++- drivers/usb/wusbcore/wa-hc.h | 13 ++++++++++- drivers/usb/wusbcore/wa-xfer.c | 49 +++++++++++++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 14 deletions(-) (limited to 'drivers/usb/wusbcore') diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c index 6c09b0e4672..368360f9a93 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 41afaa6d01d..e614f02f0cf 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 9325d27453c..090ac308c75 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. */ -- cgit v1.2.3-70-g09d2