staging: dwc2: add microframe scheduler from downstream Pi kernel
authorDom Cobley <popcornmix@gmail.com>
Mon, 23 Sep 2013 21:23:34 +0000 (14:23 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 25 Sep 2013 23:48:46 +0000 (16:48 -0700)
The transfer scheduler in the dwc2 driver is pretty basic, not to
mention buggy. It works fairly well with just a couple of devices
plugged in, but if you add, say, multiple devices with periodic
endpoints, the scheduler breaks down and can't even enumerate all
the devices.

To improve this, import the "microframe scheduler" patch from the
driver in the downstream Raspberry Pi kernel, which is based on
the Synopsys vendor driver. The original patch came from Denx
(http://git.denx.de/?p=linux-denx.git) and was commited to the
raspberrypi.org git tree by "popcornmix" (Dom Cobley).

I have added a driver parameter for this, enabled by default, in
case anyone has problems with it and needs to disable it. I don't
think we should add a DT binding for that, though, since I plan
to remove the option once any bugs are fixed.

[raspberrypi.org patch from Dom Cobley]
Signed-off-by: Dom Cobley <popcornmix@gmail.com>
[adapted to dwc2 driver by Paul Zimmerman]
Signed-off-by: Paul Zimmerman <paulz@synopsys.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/dwc2/core.c
drivers/staging/dwc2/core.h
drivers/staging/dwc2/hcd.c
drivers/staging/dwc2/hcd.h
drivers/staging/dwc2/hcd_ddma.c
drivers/staging/dwc2/hcd_intr.c
drivers/staging/dwc2/hcd_queue.c
drivers/staging/dwc2/pci.c

index 8dbd174c91293608d1aa1663880ef1bfce3fd0a6..9140f5e37e80da3014f5d24a561ab4099872d709 100644 (file)
@@ -2736,6 +2736,26 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg)
        return 0;
 }
 
+int dwc2_set_param_uframe_sched(struct dwc2_hsotg *hsotg, int val)
+{
+       int retval = 0;
+
+       if (DWC2_PARAM_TEST(val, 0, 1)) {
+               if (val >= 0) {
+                       dev_err(hsotg->dev,
+                               "'%d' invalid for parameter uframe_sched\n",
+                               val);
+                       dev_err(hsotg->dev, "uframe_sched must be 0 or 1\n");
+               }
+               val = 1;
+               dev_dbg(hsotg->dev, "Setting uframe_sched to %d\n", val);
+               retval = -EINVAL;
+       }
+
+       hsotg->core_params->uframe_sched = val;
+       return retval;
+}
+
 /*
  * This function is called during module intialization to pass module parameters
  * for the DWC_otg core. It returns non-0 if any parameters are invalid.
@@ -2782,6 +2802,7 @@ int dwc2_set_parameters(struct dwc2_hsotg *hsotg,
        retval |= dwc2_set_param_reload_ctl(hsotg, params->reload_ctl);
        retval |= dwc2_set_param_ahbcfg(hsotg, params->ahbcfg);
        retval |= dwc2_set_param_otg_ver(hsotg, params->otg_ver);
+       retval |= dwc2_set_param_uframe_sched(hsotg, params->uframe_sched);
 
        return retval;
 }
index 9102f66d0111e8676ebd5be2ce1bc00dbdc4c8c5..f7ba34b7071cc7e344e44f86240b779a96bf1a33 100644 (file)
@@ -188,6 +188,7 @@ enum dwc2_lx_state {
  *                      bits defined by GAHBCFG_CTRL_MASK are controlled
  *                      by the driver and are ignored in this
  *                      configuration value.
+ * @uframe_sched:       True to enable the microframe scheduler
  *
  * The following parameters may be specified when starting the module. These
  * parameters define how the DWC_otg controller should be configured. A
@@ -224,6 +225,7 @@ struct dwc2_core_params {
        int ts_dline;
        int reload_ctl;
        int ahbcfg;
+       int uframe_sched;
 };
 
 /**
@@ -370,6 +372,7 @@ struct dwc2_hw_params {
  *                      This value is in microseconds per (micro)frame. The
  *                      assumption is that all periodic transfers may occur in
  *                      the same (micro)frame.
+ * @frame_usecs:        Internal variable used by the microframe scheduler
  * @frame_number:       Frame number read from the core at SOF. The value ranges
  *                      from 0 to HFNUM_MAX_FRNUM.
  * @periodic_qh_count:  Count of periodic QHs, if using several eps. Used for
@@ -382,6 +385,8 @@ struct dwc2_hw_params {
  *                      host channel is available for non-periodic transactions.
  * @non_periodic_channels: Number of host channels assigned to non-periodic
  *                      transfers
+ * @available_host_channels Number of host channels available for the microframe
+ *                      scheduler to use
  * @hc_ptr_array:       Array of pointers to the host channel descriptors.
  *                      Allows accessing a host channel descriptor given the
  *                      host channel number. This is useful in interrupt
@@ -436,6 +441,7 @@ struct dwc2_hsotg {
        struct list_head periodic_sched_assigned;
        struct list_head periodic_sched_queued;
        u16 periodic_usecs;
+       u16 frame_usecs[8];
        u16 frame_number;
        u16 periodic_qh_count;
 
@@ -451,6 +457,7 @@ struct dwc2_hsotg {
        struct list_head free_hc_list;
        int periodic_channels;
        int non_periodic_channels;
+       int available_host_channels;
        struct dwc2_host_chan *hc_ptr_array[MAX_EPS_CHANNELS];
        u8 *status_buf;
        dma_addr_t status_buf_dma;
index b24540572e921a20cf7a84b8f50af090e9a3a95c..83629d84155d569be1c7ed86fb239459e176a90d 100644 (file)
@@ -537,10 +537,15 @@ static void dwc2_hcd_reinit(struct dwc2_hsotg *hsotg)
        int i;
 
        hsotg->flags.d32 = 0;
-
        hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active;
-       hsotg->non_periodic_channels = 0;
-       hsotg->periodic_channels = 0;
+
+       if (hsotg->core_params->uframe_sched > 0) {
+               hsotg->available_host_channels =
+                       hsotg->core_params->host_channels;
+       } else {
+               hsotg->non_periodic_channels = 0;
+               hsotg->periodic_channels = 0;
+       }
 
        /*
         * Put all channels in the free channel list and clean up channel
@@ -716,8 +721,7 @@ static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
  * @qh:    Transactions from the first QTD for this QH are selected and assigned
  *         to a free host channel
  */
-static void dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg,
-                                   struct dwc2_qh *qh)
+static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
 {
        struct dwc2_host_chan *chan;
        struct dwc2_hcd_urb *urb;
@@ -729,18 +733,18 @@ static void dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg,
 
        if (list_empty(&qh->qtd_list)) {
                dev_dbg(hsotg->dev, "No QTDs in QH list\n");
-               return;
+               return -ENOMEM;
        }
 
        if (list_empty(&hsotg->free_hc_list)) {
                dev_dbg(hsotg->dev, "No free channel to assign\n");
-               return;
+               return -ENOMEM;
        }
 
        chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan,
                                hc_list_entry);
 
-       /* Remove the host channel from the free list */
+       /* Remove host channel from free list */
        list_del_init(&chan->hc_list_entry);
 
        qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry);
@@ -821,7 +825,7 @@ static void dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg,
                                      &hsotg->free_hc_list);
                        qtd->in_process = 0;
                        qh->channel = NULL;
-                       return;
+                       return -ENOMEM;
                }
        } else {
                chan->align_buf = 0;
@@ -840,6 +844,8 @@ static void dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg,
 
        dwc2_hc_init(hsotg, chan);
        chan->qh = qh;
+
+       return 0;
 }
 
 /**
@@ -868,8 +874,14 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions(
        while (qh_ptr != &hsotg->periodic_sched_ready) {
                if (list_empty(&hsotg->free_hc_list))
                        break;
+               if (hsotg->core_params->uframe_sched > 0) {
+                       if (hsotg->available_host_channels <= 1)
+                               break;
+                       hsotg->available_host_channels--;
+               }
                qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry);
-               dwc2_assign_and_init_hc(hsotg, qh);
+               if (dwc2_assign_and_init_hc(hsotg, qh))
+                       break;
 
                /*
                 * Move the QH from the periodic ready schedule to the
@@ -888,13 +900,21 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions(
        num_channels = hsotg->core_params->host_channels;
        qh_ptr = hsotg->non_periodic_sched_inactive.next;
        while (qh_ptr != &hsotg->non_periodic_sched_inactive) {
-               if (hsotg->non_periodic_channels >= num_channels -
+               if (hsotg->core_params->uframe_sched <= 0 &&
+                   hsotg->non_periodic_channels >= num_channels -
                                                hsotg->periodic_channels)
                        break;
                if (list_empty(&hsotg->free_hc_list))
                        break;
                qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry);
-               dwc2_assign_and_init_hc(hsotg, qh);
+               if (hsotg->core_params->uframe_sched > 0) {
+                       if (hsotg->available_host_channels < 1)
+                               break;
+                       hsotg->available_host_channels--;
+               }
+
+               if (dwc2_assign_and_init_hc(hsotg, qh))
+                       break;
 
                /*
                 * Move the QH from the non-periodic inactive schedule to the
@@ -909,7 +929,8 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions(
                else
                        ret_val = DWC2_TRANSACTION_ALL;
 
-               hsotg->non_periodic_channels++;
+               if (hsotg->core_params->uframe_sched <= 0)
+                       hsotg->non_periodic_channels++;
        }
 
        return ret_val;
@@ -2852,6 +2873,9 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq,
                hsotg->hc_ptr_array[i] = channel;
        }
 
+       if (hsotg->core_params->uframe_sched > 0)
+               dwc2_hcd_init_usecs(hsotg);
+
        /* Initialize hsotg start work */
        INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func);
 
index cc0a117083194c2ec7d962591e9ba41ff4a8e237..89a5484f5b740eeb30f742d4308d212c6cee2480 100644 (file)
@@ -238,6 +238,7 @@ enum dwc2_transaction_type {
  * @interval:           Interval between transfers in (micro)frames
  * @sched_frame:        (Micro)frame to initialize a periodic transfer.
  *                      The transfer executes in the following (micro)frame.
+ * @frame_usecs:        Internal variable used by the microframe scheduler
  * @start_split_frame:  (Micro)frame at which last start split was initialized
  * @ntd:                Actual number of transfer descriptors in a list
  * @dw_align_buf:       Used instead of original buffer if its physical address
@@ -271,6 +272,7 @@ struct dwc2_qh {
        u16 usecs;
        u16 interval;
        u16 sched_frame;
+       u16 frame_usecs[8];
        u16 start_split_frame;
        u16 ntd;
        u8 *dw_align_buf;
@@ -463,6 +465,7 @@ extern void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg,
 
 /* Schedule Queue Functions */
 /* Implemented in hcd_queue.c */
+extern void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg);
 extern void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh);
 extern int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh);
 extern void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh);
index 69070f4442a81ad9c7e77cc2b1d1eac0ed5c4bc2..c7d434519776eb4fdf42fa69a5566639dc6a7e06 100644 (file)
@@ -271,10 +271,14 @@ static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg,
 {
        struct dwc2_host_chan *chan = qh->channel;
 
-       if (dwc2_qh_is_non_per(qh))
-               hsotg->non_periodic_channels--;
-       else
+       if (dwc2_qh_is_non_per(qh)) {
+               if (hsotg->core_params->uframe_sched > 0)
+                       hsotg->available_host_channels++;
+               else
+                       hsotg->non_periodic_channels--;
+       } else {
                dwc2_update_frame_list(hsotg, qh, 0);
+       }
 
        /*
         * The condition is added to prevent double cleanup try in case of
@@ -370,7 +374,8 @@ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
 
        if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC ||
             qh->ep_type == USB_ENDPOINT_XFER_INT) &&
-           !hsotg->periodic_channels && hsotg->frame_list) {
+           (hsotg->core_params->uframe_sched > 0 ||
+            !hsotg->periodic_channels) && hsotg->frame_list) {
                dwc2_per_sched_disable(hsotg);
                dwc2_frame_list_free(hsotg);
        }
index e143f69939f526dd5b8b3115f71aab2e7e9e92bd..cad433a75abae4cb91be0b5ce4271c5ad11286b7 100644 (file)
@@ -748,18 +748,23 @@ cleanup:
        dwc2_hc_cleanup(hsotg, chan);
        list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list);
 
-       switch (chan->ep_type) {
-       case USB_ENDPOINT_XFER_CONTROL:
-       case USB_ENDPOINT_XFER_BULK:
-               hsotg->non_periodic_channels--;
-               break;
-       default:
-               /*
-                * Don't release reservations for periodic channels here.
-                * That's done when a periodic transfer is descheduled (i.e.
-                * when the QH is removed from the periodic schedule).
-                */
-               break;
+       if (hsotg->core_params->uframe_sched > 0) {
+               hsotg->available_host_channels++;
+       } else {
+               switch (chan->ep_type) {
+               case USB_ENDPOINT_XFER_CONTROL:
+               case USB_ENDPOINT_XFER_BULK:
+                       hsotg->non_periodic_channels--;
+                       break;
+               default:
+                       /*
+                        * Don't release reservations for periodic channels
+                        * here. That's done when a periodic transfer is
+                        * descheduled (i.e. when the QH is removed from the
+                        * periodic schedule).
+                        */
+                       break;
+               }
        }
 
        haintmsk = readl(hsotg->regs + HAINTMSK);
index c09b2db2c89a042766d4b947d9e1590ff01be33e..f200f1f6e1c67ddf3d151552e3aeed0088b5b8f8 100644 (file)
@@ -323,6 +323,146 @@ static int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg,
        return status;
 }
 
+/**
+ * Microframe scheduler
+ * track the total use in hsotg->frame_usecs
+ * keep each qh use in qh->frame_usecs
+ * when surrendering the qh then donate the time back
+ */
+static const unsigned short max_uframe_usecs[] = {
+       100, 100, 100, 100, 100, 100, 30, 0
+};
+
+void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg)
+{
+       int i;
+
+       for (i = 0; i < 8; i++)
+               hsotg->frame_usecs[i] = max_uframe_usecs[i];
+}
+
+static int dwc2_find_single_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
+{
+       unsigned short utime = qh->usecs;
+       int done = 0;
+       int i = 0;
+       int ret = -1;
+
+       while (!done) {
+               /* At the start hsotg->frame_usecs[i] = max_uframe_usecs[i] */
+               if (utime <= hsotg->frame_usecs[i]) {
+                       hsotg->frame_usecs[i] -= utime;
+                       qh->frame_usecs[i] += utime;
+                       ret = i;
+                       done = 1;
+               } else {
+                       i++;
+                       if (i == 8)
+                               done = 1;
+               }
+       }
+
+       return ret;
+}
+
+/*
+ * use this for FS apps that can span multiple uframes
+ */
+static int dwc2_find_multi_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
+{
+       unsigned short utime = qh->usecs;
+       unsigned short xtime;
+       int t_left = utime;
+       int done = 0;
+       int i = 0;
+       int j;
+       int ret = -1;
+
+       while (!done) {
+               if (hsotg->frame_usecs[i] <= 0) {
+                       i++;
+                       if (i == 8) {
+                               ret = -1;
+                               done = 1;
+                       }
+                       continue;
+               }
+
+               /*
+                * we need n consecutive slots so use j as a start slot
+                * j plus j+1 must be enough time (for now)
+                */
+               xtime = hsotg->frame_usecs[i];
+               for (j = i + 1; j < 8; j++) {
+                       /*
+                        * if we add this frame remaining time to xtime we may
+                        * be OK, if not we need to test j for a complete frame
+                        */
+                       if (xtime + hsotg->frame_usecs[j] < utime) {
+                               if (hsotg->frame_usecs[j] <
+                                                       max_uframe_usecs[j]) {
+                                       ret = -1;
+                                       break;
+                               }
+                       }
+                       if (xtime >= utime) {
+                               ret = i;
+                               break;
+                       }
+                       /* add the frame time to x time */
+                       xtime += hsotg->frame_usecs[j];
+                       /* we must have a fully available next frame or break */
+                       if (xtime < utime &&
+                          hsotg->frame_usecs[j] == max_uframe_usecs[j]) {
+                               ret = -1;
+                               break;
+                       }
+               }
+               if (ret >= 0) {
+                       t_left = utime;
+                       for (j = i; t_left > 0 && j < 8; j++) {
+                               t_left -= hsotg->frame_usecs[j];
+                               if (t_left <= 0) {
+                                       qh->frame_usecs[j] +=
+                                               hsotg->frame_usecs[j] + t_left;
+                                       hsotg->frame_usecs[j] = -t_left;
+                                       ret = i;
+                                       done = 1;
+                               } else {
+                                       qh->frame_usecs[j] +=
+                                               hsotg->frame_usecs[j];
+                                       hsotg->frame_usecs[j] = 0;
+                               }
+                       }
+               } else {
+                       i++;
+                       if (i == 8) {
+                               ret = -1;
+                               done = 1;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static int dwc2_find_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
+{
+       int ret;
+
+       if (qh->dev_speed == USB_SPEED_HIGH) {
+               /* if this is a hs transaction we need a full frame */
+               ret = dwc2_find_single_uframe(hsotg, qh);
+       } else {
+               /*
+                * if this is a fs transaction we may need a sequence
+                * of frames
+                */
+               ret = dwc2_find_multi_uframe(hsotg, qh);
+       }
+       return ret;
+}
+
 /**
  * dwc2_check_max_xfer_size() - Checks that the max transfer size allowed in a
  * host channel is large enough to handle the maximum data transfer in a single
@@ -367,15 +507,35 @@ static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
 {
        int status;
 
-       status = dwc2_periodic_channel_available(hsotg);
-       if (status) {
-               dev_dbg(hsotg->dev,
-                       "%s: No host channel available for periodic transfer\n",
-                       __func__);
-               return status;
+       if (hsotg->core_params->uframe_sched > 0) {
+               int frame = -1;
+
+               status = dwc2_find_uframe(hsotg, qh);
+               if (status == 0)
+                       frame = 7;
+               else if (status > 0)
+                       frame = status - 1;
+
+               /* Set the new frame up */
+               if (frame > -1) {
+                       qh->sched_frame &= ~0x7;
+                       qh->sched_frame |= (frame & 7);
+               }
+
+               if (status != -1)
+                       status = 0;
+       } else {
+               status = dwc2_periodic_channel_available(hsotg);
+               if (status) {
+                       dev_info(hsotg->dev,
+                                "%s: No host channel available for periodic transfer\n",
+                                __func__);
+                       return status;
+               }
+
+               status = dwc2_check_periodic_bandwidth(hsotg, qh);
        }
 
-       status = dwc2_check_periodic_bandwidth(hsotg, qh);
        if (status) {
                dev_dbg(hsotg->dev,
                        "%s: Insufficient periodic bandwidth for periodic transfer\n",
@@ -399,8 +559,9 @@ static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
                list_add_tail(&qh->qh_list_entry,
                              &hsotg->periodic_sched_inactive);
 
-       /* Reserve periodic channel */
-       hsotg->periodic_channels++;
+       if (hsotg->core_params->uframe_sched <= 0)
+               /* Reserve periodic channel */
+               hsotg->periodic_channels++;
 
        /* Update claimed usecs per (micro)frame */
        hsotg->periodic_usecs += qh->usecs;
@@ -418,13 +579,22 @@ static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
 static void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg,
                                     struct dwc2_qh *qh)
 {
-       list_del_init(&qh->qh_list_entry);
+       int i;
 
-       /* Release periodic channel reservation */
-       hsotg->periodic_channels--;
+       list_del_init(&qh->qh_list_entry);
 
        /* Update claimed usecs per (micro)frame */
        hsotg->periodic_usecs -= qh->usecs;
+
+       if (hsotg->core_params->uframe_sched > 0) {
+               for (i = 0; i < 8; i++) {
+                       hsotg->frame_usecs[i] += qh->frame_usecs[i];
+                       qh->frame_usecs[i] = 0;
+               }
+       } else {
+               /* Release periodic channel reservation */
+               hsotg->periodic_channels--;
+       }
 }
 
 /**
@@ -581,7 +751,10 @@ void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
                         * Remove from periodic_sched_queued and move to
                         * appropriate queue
                         */
-                       if (qh->sched_frame == frame_number)
+                       if ((hsotg->core_params->uframe_sched > 0 &&
+                            dwc2_frame_num_le(qh->sched_frame, frame_number))
+                        || (hsotg->core_params->uframe_sched <= 0 &&
+                            qh->sched_frame == frame_number))
                                list_move(&qh->qh_list_entry,
                                          &hsotg->periodic_sched_ready);
                        else
index 9020260d5df8f96b53ff1697f6b0f568341be3bd..3d14c8870fcac75f6f1e6af1ef315b49c23778a7 100644 (file)
@@ -84,6 +84,7 @@ static const struct dwc2_core_params dwc2_module_params = {
        .ts_dline                       = -1,
        .reload_ctl                     = -1,
        .ahbcfg                         = -1,
+       .uframe_sched                   = -1,
 };
 
 /**