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 <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c
index 9325d27..090ac30 100644
--- a/drivers/usb/wusbcore/wa-xfer.c
+++ b/drivers/usb/wusbcore/wa-xfer.c
@@ -479,13 +479,29 @@
 {
 	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 @@
 	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 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 @@
 	}
 	/* 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 @@
 		/*
 		 * 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. */