usb: wusbcore: add a quirk for Alereon HWA device isoc behavior
authorThomas Pugliese <thomas.pugliese@gmail.com>
Wed, 23 Oct 2013 19:44:28 +0000 (14:44 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 29 Oct 2013 23:44:49 +0000 (16:44 -0700)
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>
drivers/usb/host/hwa-hc.c
drivers/usb/wusbcore/wa-hc.c
drivers/usb/wusbcore/wa-hc.h
drivers/usb/wusbcore/wa-xfer.c

index e5fb3cf..ada0a52 100644 (file)
@@ -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), },
        {},
index 6c09b0e..368360f 100644 (file)
@@ -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;
index 41afaa6..e614f02 100644 (file)
@@ -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);
 
index 9325d27..090ac30 100644 (file)
@@ -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. */