Merge tag 'usb-3.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 21 Jan 2014 00:13:02 +0000 (16:13 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 21 Jan 2014 00:13:02 +0000 (16:13 -0800)
Pull USB updates from Greg KH:
 "Here's the big USB pull request for 3.14-rc1

  Lots of little things all over the place, and the usual USB gadget
  updates, and XHCI fixes (some for an issue reported by a lot of
  people).  USB PHY updates as well as chipidea updates and fixes.

  All of these have been in the linux-next tree with no reported issues"

* tag 'usb-3.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (318 commits)
  usb: chipidea: udc: using MultO at TD as real mult value for ISO-TX
  usb: chipidea: need to mask INT_STATUS when write otgsc
  usb: chipidea: put hw_phymode_configure before ci_usb_phy_init
  usb: chipidea: Fix Internal error: : 808 [#1] ARM related to STS flag
  usb: chipidea: imx: set CI_HDRC_IMX28_WRITE_FIX for imx28
  usb: chipidea: add freescale imx28 special write register method
  usb: ehci: add freescale imx28 special write register method
  usb: core: check for valid id_table when using the RefId feature
  usb: cdc-wdm: resp_count can be 0 even if WDM_READ is set
  usb: core: bail out if user gives an unknown RefId when using new_id
  usb: core: allow a reference device for new_id
  usb: core: add sanity checks when using bInterfaceClass with new_id
  USB: image: correct spelling mistake in comment
  USB: c67x00: correct spelling mistakes in comments
  usb: delete non-required instances of include <linux/init.h>
  usb:hub set hub->change_bits when over-current happens
  Revert "usb: chipidea: imx: set CI_HDRC_IMX28_WRITE_FIX for imx28"
  xhci: Set scatter-gather limit to avoid failed block writes.
  xhci: Avoid infinite loop when sg urb requires too many trbs
  usb: gadget: remove unused variable in gr_queue_int()
  ...

1  2 
Documentation/devicetree/bindings/usb/dwc2.txt
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/usb/Kconfig
drivers/usb/dwc2/hcd.c
include/linux/tty.h

index 8b60e90efe33dababfd1854cf2ad3f4203e20dea,0000000000000000000000000000000000000000..b8b6871f116f39ef770c9ecabcf33b0abca517e0
mode 100644,000000..100644
--- /dev/null
@@@ -1,17 -1,0 +1,29 @@@
 +Platform DesignWare HS OTG USB 2.0 controller
 +-----------------------------------------------------
 +
 +Required properties:
 +- compatible : One of:
 +  - brcm,bcm2835-usb: The DWC2 USB controller instance in the BCM2835 SoC.
 +  - snps,dwc2: A generic DWC2 USB controller with default parameters.
 +- reg : Should contain 1 register range (address and length)
 +- interrupts : Should contain 1 interrupt
++- clocks: clock provider specifier
++- clock-names: shall be "otg"
++Refer to clk/clock-bindings.txt for generic clock consumer properties
++
++Optional properties:
++- phys: phy provider specifier
++- phy-names: shall be "device"
++Refer to phy/phy-bindings.txt for generic phy consumer properties
 +
 +Example:
 +
 +        usb@101c0000 {
 +                compatible = "ralink,rt3050-usb, snps,dwc2";
 +                reg = <0x101c0000 40000>;
 +                interrupts = <18>;
++              clocks = <&usb_otg_ahb_clk>;
++              clock-names = "otg";
++              phys = <&usbphy>;
++              phy-names = "usb2-phy";
 +        };
Simple merge
index 4e4adc96f753cfd9bd1d5a28d139b5c668d25a46,c447f1a98ac5f0ab07167deb66088573208a8d34..b57c25371cca48695cf6068f61cfbd85e44c281b
@@@ -3,8 -3,8 +3,9 @@@
  #
  
  obj-$(CONFIG_GENERIC_PHY)             += phy-core.o
+ obj-$(CONFIG_BCM_KONA_USB2_PHY)               += phy-bcm-kona-usb2.o
  obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO)     += phy-exynos-dp-video.o
  obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO)   += phy-exynos-mipi-video.o
 +obj-$(CONFIG_PHY_MVEBU_SATA)          += phy-mvebu-sata.o
  obj-$(CONFIG_OMAP_USB2)                       += phy-omap-usb2.o
  obj-$(CONFIG_TWL4030_USB)             += phy-twl4030-usb.o
index 7eb909aa8bc0f403518305dc5ba43702788952b4,a34fb9846417cff4966fa5fb9b6ab5bea5f7da48..2e6b832e004ba4926aaa1d870d7a1a01345dc8ac
@@@ -106,10 -104,10 +104,12 @@@ source "drivers/usb/image/Kconfig
  
  endif
  
+ source "drivers/usb/musb/Kconfig"
  source "drivers/usb/dwc3/Kconfig"
  
 +source "drivers/usb/dwc2/Kconfig"
 +
  source "drivers/usb/chipidea/Kconfig"
  
  comment "USB port drivers"
index 07dfe855dc203d8c69eed6533847807f192e52d7,0000000000000000000000000000000000000000..f59484d43b355b15b1297f3341c832a0a395ebb3
mode 100644,000000..100644
--- /dev/null
@@@ -1,2990 -1,0 +1,2992 @@@
 +/*
 + * hcd.c - DesignWare HS OTG Controller host-mode routines
 + *
 + * Copyright (C) 2004-2013 Synopsys, Inc.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions, and the following disclaimer,
 + *    without modification.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. The names of the above-listed copyright holders may not be used
 + *    to endorse or promote products derived from this software without
 + *    specific prior written permission.
 + *
 + * ALTERNATIVELY, this software may be distributed under the terms of the
 + * GNU General Public License ("GPL") as published by the Free Software
 + * Foundation; either version 2 of the License, or (at your option) any
 + * later version.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + */
 +
 +/*
 + * This file contains the core HCD code, and implements the Linux hc_driver
 + * API
 + */
 +#include <linux/kernel.h>
 +#include <linux/module.h>
 +#include <linux/spinlock.h>
 +#include <linux/interrupt.h>
 +#include <linux/dma-mapping.h>
 +#include <linux/delay.h>
 +#include <linux/io.h>
 +#include <linux/slab.h>
 +#include <linux/usb.h>
 +
 +#include <linux/usb/hcd.h>
 +#include <linux/usb/ch11.h>
 +
 +#include "core.h"
 +#include "hcd.h"
 +
 +/**
 + * dwc2_dump_channel_info() - Prints the state of a host channel
 + *
 + * @hsotg: Programming view of DWC_otg controller
 + * @chan:  Pointer to the channel to dump
 + *
 + * Must be called with interrupt disabled and spinlock held
 + *
 + * NOTE: This function will be removed once the peripheral controller code
 + * is integrated and the driver is stable
 + */
 +static void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg,
 +                                 struct dwc2_host_chan *chan)
 +{
 +#ifdef VERBOSE_DEBUG
 +      int num_channels = hsotg->core_params->host_channels;
 +      struct dwc2_qh *qh;
 +      u32 hcchar;
 +      u32 hcsplt;
 +      u32 hctsiz;
 +      u32 hc_dma;
 +      int i;
 +
 +      if (chan == NULL)
 +              return;
 +
 +      hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num));
 +      hcsplt = readl(hsotg->regs + HCSPLT(chan->hc_num));
 +      hctsiz = readl(hsotg->regs + HCTSIZ(chan->hc_num));
 +      hc_dma = readl(hsotg->regs + HCDMA(chan->hc_num));
 +
 +      dev_dbg(hsotg->dev, "  Assigned to channel %p:\n", chan);
 +      dev_dbg(hsotg->dev, "    hcchar 0x%08x, hcsplt 0x%08x\n",
 +              hcchar, hcsplt);
 +      dev_dbg(hsotg->dev, "    hctsiz 0x%08x, hc_dma 0x%08x\n",
 +              hctsiz, hc_dma);
 +      dev_dbg(hsotg->dev, "    dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
 +              chan->dev_addr, chan->ep_num, chan->ep_is_in);
 +      dev_dbg(hsotg->dev, "    ep_type: %d\n", chan->ep_type);
 +      dev_dbg(hsotg->dev, "    max_packet: %d\n", chan->max_packet);
 +      dev_dbg(hsotg->dev, "    data_pid_start: %d\n", chan->data_pid_start);
 +      dev_dbg(hsotg->dev, "    xfer_started: %d\n", chan->xfer_started);
 +      dev_dbg(hsotg->dev, "    halt_status: %d\n", chan->halt_status);
 +      dev_dbg(hsotg->dev, "    xfer_buf: %p\n", chan->xfer_buf);
 +      dev_dbg(hsotg->dev, "    xfer_dma: %08lx\n",
 +              (unsigned long)chan->xfer_dma);
 +      dev_dbg(hsotg->dev, "    xfer_len: %d\n", chan->xfer_len);
 +      dev_dbg(hsotg->dev, "    qh: %p\n", chan->qh);
 +      dev_dbg(hsotg->dev, "  NP inactive sched:\n");
 +      list_for_each_entry(qh, &hsotg->non_periodic_sched_inactive,
 +                          qh_list_entry)
 +              dev_dbg(hsotg->dev, "    %p\n", qh);
 +      dev_dbg(hsotg->dev, "  NP active sched:\n");
 +      list_for_each_entry(qh, &hsotg->non_periodic_sched_active,
 +                          qh_list_entry)
 +              dev_dbg(hsotg->dev, "    %p\n", qh);
 +      dev_dbg(hsotg->dev, "  Channels:\n");
 +      for (i = 0; i < num_channels; i++) {
 +              struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i];
 +
 +              dev_dbg(hsotg->dev, "    %2d: %p\n", i, chan);
 +      }
 +#endif /* VERBOSE_DEBUG */
 +}
 +
 +/*
 + * Processes all the URBs in a single list of QHs. Completes them with
 + * -ETIMEDOUT and frees the QTD.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static void dwc2_kill_urbs_in_qh_list(struct dwc2_hsotg *hsotg,
 +                                    struct list_head *qh_list)
 +{
 +      struct dwc2_qh *qh, *qh_tmp;
 +      struct dwc2_qtd *qtd, *qtd_tmp;
 +
 +      list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) {
 +              list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list,
 +                                       qtd_list_entry) {
 +                      dwc2_host_complete(hsotg, qtd, -ETIMEDOUT);
 +                      dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh);
 +              }
 +      }
 +}
 +
 +static void dwc2_qh_list_free(struct dwc2_hsotg *hsotg,
 +                            struct list_head *qh_list)
 +{
 +      struct dwc2_qtd *qtd, *qtd_tmp;
 +      struct dwc2_qh *qh, *qh_tmp;
 +      unsigned long flags;
 +
 +      if (!qh_list->next)
 +              /* The list hasn't been initialized yet */
 +              return;
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      /* Ensure there are no QTDs or URBs left */
 +      dwc2_kill_urbs_in_qh_list(hsotg, qh_list);
 +
 +      list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) {
 +              dwc2_hcd_qh_unlink(hsotg, qh);
 +
 +              /* Free each QTD in the QH's QTD list */
 +              list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list,
 +                                       qtd_list_entry)
 +                      dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh);
 +
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +              dwc2_hcd_qh_free(hsotg, qh);
 +              spin_lock_irqsave(&hsotg->lock, flags);
 +      }
 +
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +}
 +
 +/*
 + * Responds with an error status of -ETIMEDOUT to all URBs in the non-periodic
 + * and periodic schedules. The QTD associated with each URB is removed from
 + * the schedule and freed. This function may be called when a disconnect is
 + * detected or when the HCD is being stopped.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg)
 +{
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive);
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active);
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive);
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready);
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_assigned);
 +      dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_queued);
 +}
 +
 +/**
 + * dwc2_hcd_start() - Starts the HCD when switching to Host mode
 + *
 + * @hsotg: Pointer to struct dwc2_hsotg
 + */
 +void dwc2_hcd_start(struct dwc2_hsotg *hsotg)
 +{
 +      u32 hprt0;
 +
 +      if (hsotg->op_state == OTG_STATE_B_HOST) {
 +              /*
 +               * Reset the port. During a HNP mode switch the reset
 +               * needs to occur within 1ms and have a duration of at
 +               * least 50ms.
 +               */
 +              hprt0 = dwc2_read_hprt0(hsotg);
 +              hprt0 |= HPRT0_RST;
 +              writel(hprt0, hsotg->regs + HPRT0);
 +      }
 +
 +      queue_delayed_work(hsotg->wq_otg, &hsotg->start_work,
 +                         msecs_to_jiffies(50));
 +}
 +
 +/* Must be called with interrupt disabled and spinlock held */
 +static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg)
 +{
 +      int num_channels = hsotg->core_params->host_channels;
 +      struct dwc2_host_chan *channel;
 +      u32 hcchar;
 +      int i;
 +
 +      if (hsotg->core_params->dma_enable <= 0) {
 +              /* Flush out any channel requests in slave mode */
 +              for (i = 0; i < num_channels; i++) {
 +                      channel = hsotg->hc_ptr_array[i];
 +                      if (!list_empty(&channel->hc_list_entry))
 +                              continue;
 +                      hcchar = readl(hsotg->regs + HCCHAR(i));
 +                      if (hcchar & HCCHAR_CHENA) {
 +                              hcchar &= ~(HCCHAR_CHENA | HCCHAR_EPDIR);
 +                              hcchar |= HCCHAR_CHDIS;
 +                              writel(hcchar, hsotg->regs + HCCHAR(i));
 +                      }
 +              }
 +      }
 +
 +      for (i = 0; i < num_channels; i++) {
 +              channel = hsotg->hc_ptr_array[i];
 +              if (!list_empty(&channel->hc_list_entry))
 +                      continue;
 +              hcchar = readl(hsotg->regs + HCCHAR(i));
 +              if (hcchar & HCCHAR_CHENA) {
 +                      /* Halt the channel */
 +                      hcchar |= HCCHAR_CHDIS;
 +                      writel(hcchar, hsotg->regs + HCCHAR(i));
 +              }
 +
 +              dwc2_hc_cleanup(hsotg, channel);
 +              list_add_tail(&channel->hc_list_entry, &hsotg->free_hc_list);
 +              /*
 +               * Added for Descriptor DMA to prevent channel double cleanup in
 +               * release_channel_ddma(), which is called from ep_disable when
 +               * device disconnects
 +               */
 +              channel->qh = NULL;
 +      }
 +}
 +
 +/**
 + * dwc2_hcd_disconnect() - Handles disconnect of the HCD
 + *
 + * @hsotg: Pointer to struct dwc2_hsotg
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg)
 +{
 +      u32 intr;
 +
 +      /* Set status flags for the hub driver */
 +      hsotg->flags.b.port_connect_status_change = 1;
 +      hsotg->flags.b.port_connect_status = 0;
 +
 +      /*
 +       * Shutdown any transfers in process by clearing the Tx FIFO Empty
 +       * interrupt mask and status bits and disabling subsequent host
 +       * channel interrupts.
 +       */
 +      intr = readl(hsotg->regs + GINTMSK);
 +      intr &= ~(GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT);
 +      writel(intr, hsotg->regs + GINTMSK);
 +      intr = GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT;
 +      writel(intr, hsotg->regs + GINTSTS);
 +
 +      /*
 +       * Turn off the vbus power only if the core has transitioned to device
 +       * mode. If still in host mode, need to keep power on to detect a
 +       * reconnection.
 +       */
 +      if (dwc2_is_device_mode(hsotg)) {
 +              if (hsotg->op_state != OTG_STATE_A_SUSPEND) {
 +                      dev_dbg(hsotg->dev, "Disconnect: PortPower off\n");
 +                      writel(0, hsotg->regs + HPRT0);
 +              }
 +
 +              dwc2_disable_host_interrupts(hsotg);
 +      }
 +
 +      /* Respond with an error status to all URBs in the schedule */
 +      dwc2_kill_all_urbs(hsotg);
 +
 +      if (dwc2_is_host_mode(hsotg))
 +              /* Clean up any host channels that were in use */
 +              dwc2_hcd_cleanup_channels(hsotg);
 +
 +      dwc2_host_disconnect(hsotg);
 +}
 +
 +/**
 + * dwc2_hcd_rem_wakeup() - Handles Remote Wakeup
 + *
 + * @hsotg: Pointer to struct dwc2_hsotg
 + */
 +static void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg)
 +{
 +      if (hsotg->lx_state == DWC2_L2)
 +              hsotg->flags.b.port_suspend_change = 1;
 +      else
 +              hsotg->flags.b.port_l1_change = 1;
 +}
 +
 +/**
 + * dwc2_hcd_stop() - Halts the DWC_otg host mode operations in a clean manner
 + *
 + * @hsotg: Pointer to struct dwc2_hsotg
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +void dwc2_hcd_stop(struct dwc2_hsotg *hsotg)
 +{
 +      dev_dbg(hsotg->dev, "DWC OTG HCD STOP\n");
 +
 +      /*
 +       * The root hub should be disconnected before this function is called.
 +       * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
 +       * and the QH lists (via ..._hcd_endpoint_disable).
 +       */
 +
 +      /* Turn off all host-specific interrupts */
 +      dwc2_disable_host_interrupts(hsotg);
 +
 +      /* Turn off the vbus power */
 +      dev_dbg(hsotg->dev, "PortPower off\n");
 +      writel(0, hsotg->regs + HPRT0);
 +}
 +
 +static int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg,
 +                              struct dwc2_hcd_urb *urb, void **ep_handle,
 +                              gfp_t mem_flags)
 +{
 +      struct dwc2_qtd *qtd;
 +      unsigned long flags;
 +      u32 intr_mask;
 +      int retval;
 +      int dev_speed;
 +
 +      if (!hsotg->flags.b.port_connect_status) {
 +              /* No longer connected */
 +              dev_err(hsotg->dev, "Not connected\n");
 +              return -ENODEV;
 +      }
 +
 +      dev_speed = dwc2_host_get_speed(hsotg, urb->priv);
 +
 +      /* Some configurations cannot support LS traffic on a FS root port */
 +      if ((dev_speed == USB_SPEED_LOW) &&
 +          (hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) &&
 +          (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI)) {
 +              u32 hprt0 = readl(hsotg->regs + HPRT0);
 +              u32 prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
 +
 +              if (prtspd == HPRT0_SPD_FULL_SPEED)
 +                      return -ENODEV;
 +      }
 +
 +      qtd = kzalloc(sizeof(*qtd), mem_flags);
 +      if (!qtd)
 +              return -ENOMEM;
 +
 +      dwc2_hcd_qtd_init(qtd, urb);
 +      retval = dwc2_hcd_qtd_add(hsotg, qtd, (struct dwc2_qh **)ep_handle,
 +                                mem_flags);
 +      if (retval) {
 +              dev_err(hsotg->dev,
 +                      "DWC OTG HCD URB Enqueue failed adding QTD. Error status %d\n",
 +                      retval);
 +              kfree(qtd);
 +              return retval;
 +      }
 +
 +      intr_mask = readl(hsotg->regs + GINTMSK);
 +      if (!(intr_mask & GINTSTS_SOF)) {
 +              enum dwc2_transaction_type tr_type;
 +
 +              if (qtd->qh->ep_type == USB_ENDPOINT_XFER_BULK &&
 +                  !(qtd->urb->flags & URB_GIVEBACK_ASAP))
 +                      /*
 +                       * Do not schedule SG transactions until qtd has
 +                       * URB_GIVEBACK_ASAP set
 +                       */
 +                      return 0;
 +
 +              spin_lock_irqsave(&hsotg->lock, flags);
 +              tr_type = dwc2_hcd_select_transactions(hsotg);
 +              if (tr_type != DWC2_TRANSACTION_NONE)
 +                      dwc2_hcd_queue_transactions(hsotg, tr_type);
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +      }
 +
 +      return 0;
 +}
 +
 +/* Must be called with interrupt disabled and spinlock held */
 +static int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *hsotg,
 +                              struct dwc2_hcd_urb *urb)
 +{
 +      struct dwc2_qh *qh;
 +      struct dwc2_qtd *urb_qtd;
 +
 +      urb_qtd = urb->qtd;
 +      if (!urb_qtd) {
 +              dev_dbg(hsotg->dev, "## Urb QTD is NULL ##\n");
 +              return -EINVAL;
 +      }
 +
 +      qh = urb_qtd->qh;
 +      if (!qh) {
 +              dev_dbg(hsotg->dev, "## Urb QTD QH is NULL ##\n");
 +              return -EINVAL;
 +      }
 +
 +      urb->priv = NULL;
 +
 +      if (urb_qtd->in_process && qh->channel) {
 +              dwc2_dump_channel_info(hsotg, qh->channel);
 +
 +              /* The QTD is in process (it has been assigned to a channel) */
 +              if (hsotg->flags.b.port_connect_status)
 +                      /*
 +                       * If still connected (i.e. in host mode), halt the
 +                       * channel so it can be used for other transfers. If
 +                       * no longer connected, the host registers can't be
 +                       * written to halt the channel since the core is in
 +                       * device mode.
 +                       */
 +                      dwc2_hc_halt(hsotg, qh->channel,
 +                                   DWC2_HC_XFER_URB_DEQUEUE);
 +      }
 +
 +      /*
 +       * Free the QTD and clean up the associated QH. Leave the QH in the
 +       * schedule if it has any remaining QTDs.
 +       */
 +      if (hsotg->core_params->dma_desc_enable <= 0) {
 +              u8 in_process = urb_qtd->in_process;
 +
 +              dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh);
 +              if (in_process) {
 +                      dwc2_hcd_qh_deactivate(hsotg, qh, 0);
 +                      qh->channel = NULL;
 +              } else if (list_empty(&qh->qtd_list)) {
 +                      dwc2_hcd_qh_unlink(hsotg, qh);
 +              }
 +      } else {
 +              dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh);
 +      }
 +
 +      return 0;
 +}
 +
 +/* Must NOT be called with interrupt disabled or spinlock held */
 +static int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg,
 +                                   struct usb_host_endpoint *ep, int retry)
 +{
 +      struct dwc2_qtd *qtd, *qtd_tmp;
 +      struct dwc2_qh *qh;
 +      unsigned long flags;
 +      int rc;
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      qh = ep->hcpriv;
 +      if (!qh) {
 +              rc = -EINVAL;
 +              goto err;
 +      }
 +
 +      while (!list_empty(&qh->qtd_list) && retry--) {
 +              if (retry == 0) {
 +                      dev_err(hsotg->dev,
 +                              "## timeout in dwc2_hcd_endpoint_disable() ##\n");
 +                      rc = -EBUSY;
 +                      goto err;
 +              }
 +
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +              usleep_range(20000, 40000);
 +              spin_lock_irqsave(&hsotg->lock, flags);
 +              qh = ep->hcpriv;
 +              if (!qh) {
 +                      rc = -EINVAL;
 +                      goto err;
 +              }
 +      }
 +
 +      dwc2_hcd_qh_unlink(hsotg, qh);
 +
 +      /* Free each QTD in the QH's QTD list */
 +      list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry)
 +              dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh);
 +
 +      ep->hcpriv = NULL;
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +      dwc2_hcd_qh_free(hsotg, qh);
 +
 +      return 0;
 +
 +err:
 +      ep->hcpriv = NULL;
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +
 +      return rc;
 +}
 +
 +/* Must be called with interrupt disabled and spinlock held */
 +static int dwc2_hcd_endpoint_reset(struct dwc2_hsotg *hsotg,
 +                                 struct usb_host_endpoint *ep)
 +{
 +      struct dwc2_qh *qh = ep->hcpriv;
 +
 +      if (!qh)
 +              return -EINVAL;
 +
 +      qh->data_toggle = DWC2_HC_PID_DATA0;
 +
 +      return 0;
 +}
 +
 +/*
 + * Initializes dynamic portions of the DWC_otg HCD state
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static void dwc2_hcd_reinit(struct dwc2_hsotg *hsotg)
 +{
 +      struct dwc2_host_chan *chan, *chan_tmp;
 +      int num_channels;
 +      int i;
 +
 +      hsotg->flags.d32 = 0;
 +      hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active;
 +
 +      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
 +       * states
 +       */
 +      list_for_each_entry_safe(chan, chan_tmp, &hsotg->free_hc_list,
 +                               hc_list_entry)
 +              list_del_init(&chan->hc_list_entry);
 +
 +      num_channels = hsotg->core_params->host_channels;
 +      for (i = 0; i < num_channels; i++) {
 +              chan = hsotg->hc_ptr_array[i];
 +              list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list);
 +              dwc2_hc_cleanup(hsotg, chan);
 +      }
 +
 +      /* Initialize the DWC core for host mode operation */
 +      dwc2_core_host_init(hsotg);
 +}
 +
 +static void dwc2_hc_init_split(struct dwc2_hsotg *hsotg,
 +                             struct dwc2_host_chan *chan,
 +                             struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb)
 +{
 +      int hub_addr, hub_port;
 +
 +      chan->do_split = 1;
 +      chan->xact_pos = qtd->isoc_split_pos;
 +      chan->complete_split = qtd->complete_split;
 +      dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port);
 +      chan->hub_addr = (u8)hub_addr;
 +      chan->hub_port = (u8)hub_port;
 +}
 +
 +static void *dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg,
 +                             struct dwc2_host_chan *chan,
 +                             struct dwc2_qtd *qtd, void *bufptr)
 +{
 +      struct dwc2_hcd_urb *urb = qtd->urb;
 +      struct dwc2_hcd_iso_packet_desc *frame_desc;
 +
 +      switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) {
 +      case USB_ENDPOINT_XFER_CONTROL:
 +              chan->ep_type = USB_ENDPOINT_XFER_CONTROL;
 +
 +              switch (qtd->control_phase) {
 +              case DWC2_CONTROL_SETUP:
 +                      dev_vdbg(hsotg->dev, "  Control setup transaction\n");
 +                      chan->do_ping = 0;
 +                      chan->ep_is_in = 0;
 +                      chan->data_pid_start = DWC2_HC_PID_SETUP;
 +                      if (hsotg->core_params->dma_enable > 0)
 +                              chan->xfer_dma = urb->setup_dma;
 +                      else
 +                              chan->xfer_buf = urb->setup_packet;
 +                      chan->xfer_len = 8;
 +                      bufptr = NULL;
 +                      break;
 +
 +              case DWC2_CONTROL_DATA:
 +                      dev_vdbg(hsotg->dev, "  Control data transaction\n");
 +                      chan->data_pid_start = qtd->data_toggle;
 +                      break;
 +
 +              case DWC2_CONTROL_STATUS:
 +                      /*
 +                       * Direction is opposite of data direction or IN if no
 +                       * data
 +                       */
 +                      dev_vdbg(hsotg->dev, "  Control status transaction\n");
 +                      if (urb->length == 0)
 +                              chan->ep_is_in = 1;
 +                      else
 +                              chan->ep_is_in =
 +                                      dwc2_hcd_is_pipe_out(&urb->pipe_info);
 +                      if (chan->ep_is_in)
 +                              chan->do_ping = 0;
 +                      chan->data_pid_start = DWC2_HC_PID_DATA1;
 +                      chan->xfer_len = 0;
 +                      if (hsotg->core_params->dma_enable > 0)
 +                              chan->xfer_dma = hsotg->status_buf_dma;
 +                      else
 +                              chan->xfer_buf = hsotg->status_buf;
 +                      bufptr = NULL;
 +                      break;
 +              }
 +              break;
 +
 +      case USB_ENDPOINT_XFER_BULK:
 +              chan->ep_type = USB_ENDPOINT_XFER_BULK;
 +              break;
 +
 +      case USB_ENDPOINT_XFER_INT:
 +              chan->ep_type = USB_ENDPOINT_XFER_INT;
 +              break;
 +
 +      case USB_ENDPOINT_XFER_ISOC:
 +              chan->ep_type = USB_ENDPOINT_XFER_ISOC;
 +              if (hsotg->core_params->dma_desc_enable > 0)
 +                      break;
 +
 +              frame_desc = &urb->iso_descs[qtd->isoc_frame_index];
 +              frame_desc->status = 0;
 +
 +              if (hsotg->core_params->dma_enable > 0) {
 +                      chan->xfer_dma = urb->dma;
 +                      chan->xfer_dma += frame_desc->offset +
 +                                      qtd->isoc_split_offset;
 +              } else {
 +                      chan->xfer_buf = urb->buf;
 +                      chan->xfer_buf += frame_desc->offset +
 +                                      qtd->isoc_split_offset;
 +              }
 +
 +              chan->xfer_len = frame_desc->length - qtd->isoc_split_offset;
 +
 +              /* For non-dword aligned buffers */
 +              if (hsotg->core_params->dma_enable > 0 &&
 +                  (chan->xfer_dma & 0x3))
 +                      bufptr = (u8 *)urb->buf + frame_desc->offset +
 +                                      qtd->isoc_split_offset;
 +              else
 +                      bufptr = NULL;
 +
 +              if (chan->xact_pos == DWC2_HCSPLT_XACTPOS_ALL) {
 +                      if (chan->xfer_len <= 188)
 +                              chan->xact_pos = DWC2_HCSPLT_XACTPOS_ALL;
 +                      else
 +                              chan->xact_pos = DWC2_HCSPLT_XACTPOS_BEGIN;
 +              }
 +              break;
 +      }
 +
 +      return bufptr;
 +}
 +
 +static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
 +                                 struct dwc2_host_chan *chan, void *bufptr)
 +{
 +      u32 buf_size;
 +
 +      if (chan->ep_type != USB_ENDPOINT_XFER_ISOC)
 +              buf_size = hsotg->core_params->max_transfer_size;
 +      else
 +              buf_size = 4096;
 +
 +      if (!qh->dw_align_buf) {
 +              qh->dw_align_buf = dma_alloc_coherent(hsotg->dev, buf_size,
 +                                                    &qh->dw_align_buf_dma,
 +                                                    GFP_ATOMIC);
 +              if (!qh->dw_align_buf)
 +                      return -ENOMEM;
 +      }
 +
 +      if (!chan->ep_is_in && chan->xfer_len) {
 +              dma_sync_single_for_cpu(hsotg->dev, chan->xfer_dma, buf_size,
 +                                      DMA_TO_DEVICE);
 +              memcpy(qh->dw_align_buf, bufptr, chan->xfer_len);
 +              dma_sync_single_for_device(hsotg->dev, chan->xfer_dma, buf_size,
 +                                         DMA_TO_DEVICE);
 +      }
 +
 +      chan->align_buf = qh->dw_align_buf_dma;
 +      return 0;
 +}
 +
 +/**
 + * dwc2_assign_and_init_hc() - Assigns transactions from a QTD to a free host
 + * channel and initializes the host channel to perform the transactions. The
 + * host channel is removed from the free list.
 + *
 + * @hsotg: The HCD state structure
 + * @qh:    Transactions from the first QTD for this QH are selected and assigned
 + *         to a free host channel
 + */
 +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;
 +      struct dwc2_qtd *qtd;
 +      void *bufptr = NULL;
 +
 +      if (dbg_qh(qh))
 +              dev_vdbg(hsotg->dev, "%s(%p,%p)\n", __func__, hsotg, qh);
 +
 +      if (list_empty(&qh->qtd_list)) {
 +              dev_dbg(hsotg->dev, "No QTDs in QH list\n");
 +              return -ENOMEM;
 +      }
 +
 +      if (list_empty(&hsotg->free_hc_list)) {
 +              dev_dbg(hsotg->dev, "No free channel to assign\n");
 +              return -ENOMEM;
 +      }
 +
 +      chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan,
 +                              hc_list_entry);
 +
 +      /* 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);
 +      urb = qtd->urb;
 +      qh->channel = chan;
 +      qtd->in_process = 1;
 +
 +      /*
 +       * Use usb_pipedevice to determine device address. This address is
 +       * 0 before the SET_ADDRESS command and the correct address afterward.
 +       */
 +      chan->dev_addr = dwc2_hcd_get_dev_addr(&urb->pipe_info);
 +      chan->ep_num = dwc2_hcd_get_ep_num(&urb->pipe_info);
 +      chan->speed = qh->dev_speed;
 +      chan->max_packet = dwc2_max_packet(qh->maxp);
 +
 +      chan->xfer_started = 0;
 +      chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS;
 +      chan->error_state = (qtd->error_count > 0);
 +      chan->halt_on_queue = 0;
 +      chan->halt_pending = 0;
 +      chan->requests = 0;
 +
 +      /*
 +       * The following values may be modified in the transfer type section
 +       * below. The xfer_len value may be reduced when the transfer is
 +       * started to accommodate the max widths of the XferSize and PktCnt
 +       * fields in the HCTSIZn register.
 +       */
 +
 +      chan->ep_is_in = (dwc2_hcd_is_pipe_in(&urb->pipe_info) != 0);
 +      if (chan->ep_is_in)
 +              chan->do_ping = 0;
 +      else
 +              chan->do_ping = qh->ping_state;
 +
 +      chan->data_pid_start = qh->data_toggle;
 +      chan->multi_count = 1;
 +
 +      if (urb->actual_length > urb->length &&
 +              !dwc2_hcd_is_pipe_in(&urb->pipe_info))
 +              urb->actual_length = urb->length;
 +
 +      if (hsotg->core_params->dma_enable > 0) {
 +              chan->xfer_dma = urb->dma + urb->actual_length;
 +
 +              /* For non-dword aligned case */
 +              if (hsotg->core_params->dma_desc_enable <= 0 &&
 +                  (chan->xfer_dma & 0x3))
 +                      bufptr = (u8 *)urb->buf + urb->actual_length;
 +      } else {
 +              chan->xfer_buf = (u8 *)urb->buf + urb->actual_length;
 +      }
 +
 +      chan->xfer_len = urb->length - urb->actual_length;
 +      chan->xfer_count = 0;
 +
 +      /* Set the split attributes if required */
 +      if (qh->do_split)
 +              dwc2_hc_init_split(hsotg, chan, qtd, urb);
 +      else
 +              chan->do_split = 0;
 +
 +      /* Set the transfer attributes */
 +      bufptr = dwc2_hc_init_xfer(hsotg, chan, qtd, bufptr);
 +
 +      /* Non DWORD-aligned buffer case */
 +      if (bufptr) {
 +              dev_vdbg(hsotg->dev, "Non-aligned buffer\n");
 +              if (dwc2_hc_setup_align_buf(hsotg, qh, chan, bufptr)) {
 +                      dev_err(hsotg->dev,
 +                              "%s: Failed to allocate memory to handle non-dword aligned buffer\n",
 +                              __func__);
 +                      /* Add channel back to free list */
 +                      chan->align_buf = 0;
 +                      chan->multi_count = 0;
 +                      list_add_tail(&chan->hc_list_entry,
 +                                    &hsotg->free_hc_list);
 +                      qtd->in_process = 0;
 +                      qh->channel = NULL;
 +                      return -ENOMEM;
 +              }
 +      } else {
 +              chan->align_buf = 0;
 +      }
 +
 +      if (chan->ep_type == USB_ENDPOINT_XFER_INT ||
 +          chan->ep_type == USB_ENDPOINT_XFER_ISOC)
 +              /*
 +               * This value may be modified when the transfer is started
 +               * to reflect the actual transfer length
 +               */
 +              chan->multi_count = dwc2_hb_mult(qh->maxp);
 +
 +      if (hsotg->core_params->dma_desc_enable > 0)
 +              chan->desc_list_addr = qh->desc_list_dma;
 +
 +      dwc2_hc_init(hsotg, chan);
 +      chan->qh = qh;
 +
 +      return 0;
 +}
 +
 +/**
 + * dwc2_hcd_select_transactions() - Selects transactions from the HCD transfer
 + * schedule and assigns them to available host channels. Called from the HCD
 + * interrupt handler functions.
 + *
 + * @hsotg: The HCD state structure
 + *
 + * Return: The types of new transactions that were assigned to host channels
 + */
 +enum dwc2_transaction_type dwc2_hcd_select_transactions(
 +              struct dwc2_hsotg *hsotg)
 +{
 +      enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE;
 +      struct list_head *qh_ptr;
 +      struct dwc2_qh *qh;
 +      int num_channels;
 +
 +#ifdef DWC2_DEBUG_SOF
 +      dev_vdbg(hsotg->dev, "  Select Transactions\n");
 +#endif
 +
 +      /* Process entries in the periodic ready list */
 +      qh_ptr = hsotg->periodic_sched_ready.next;
 +      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);
 +              if (dwc2_assign_and_init_hc(hsotg, qh))
 +                      break;
 +
 +              /*
 +               * Move the QH from the periodic ready schedule to the
 +               * periodic assigned schedule
 +               */
 +              qh_ptr = qh_ptr->next;
 +              list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned);
 +              ret_val = DWC2_TRANSACTION_PERIODIC;
 +      }
 +
 +      /*
 +       * Process entries in the inactive portion of the non-periodic
 +       * schedule. Some free host channels may not be used if they are
 +       * reserved for periodic transfers.
 +       */
 +      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->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);
 +              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
 +               * non-periodic active schedule
 +               */
 +              qh_ptr = qh_ptr->next;
 +              list_move(&qh->qh_list_entry,
 +                        &hsotg->non_periodic_sched_active);
 +
 +              if (ret_val == DWC2_TRANSACTION_NONE)
 +                      ret_val = DWC2_TRANSACTION_NON_PERIODIC;
 +              else
 +                      ret_val = DWC2_TRANSACTION_ALL;
 +
 +              if (hsotg->core_params->uframe_sched <= 0)
 +                      hsotg->non_periodic_channels++;
 +      }
 +
 +      return ret_val;
 +}
 +
 +/**
 + * dwc2_queue_transaction() - Attempts to queue a single transaction request for
 + * a host channel associated with either a periodic or non-periodic transfer
 + *
 + * @hsotg: The HCD state structure
 + * @chan:  Host channel descriptor associated with either a periodic or
 + *         non-periodic transfer
 + * @fifo_dwords_avail: Number of DWORDs available in the periodic Tx FIFO
 + *                     for periodic transfers or the non-periodic Tx FIFO
 + *                     for non-periodic transfers
 + *
 + * Return: 1 if a request is queued and more requests may be needed to
 + * complete the transfer, 0 if no more requests are required for this
 + * transfer, -1 if there is insufficient space in the Tx FIFO
 + *
 + * This function assumes that there is space available in the appropriate
 + * request queue. For an OUT transfer or SETUP transaction in Slave mode,
 + * it checks whether space is available in the appropriate Tx FIFO.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg,
 +                                struct dwc2_host_chan *chan,
 +                                u16 fifo_dwords_avail)
 +{
 +      int retval = 0;
 +
 +      if (hsotg->core_params->dma_enable > 0) {
 +              if (hsotg->core_params->dma_desc_enable > 0) {
 +                      if (!chan->xfer_started ||
 +                          chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
 +                              dwc2_hcd_start_xfer_ddma(hsotg, chan->qh);
 +                              chan->qh->ping_state = 0;
 +                      }
 +              } else if (!chan->xfer_started) {
 +                      dwc2_hc_start_transfer(hsotg, chan);
 +                      chan->qh->ping_state = 0;
 +              }
 +      } else if (chan->halt_pending) {
 +              /* Don't queue a request if the channel has been halted */
 +      } else if (chan->halt_on_queue) {
 +              dwc2_hc_halt(hsotg, chan, chan->halt_status);
 +      } else if (chan->do_ping) {
 +              if (!chan->xfer_started)
 +                      dwc2_hc_start_transfer(hsotg, chan);
 +      } else if (!chan->ep_is_in ||
 +                 chan->data_pid_start == DWC2_HC_PID_SETUP) {
 +              if ((fifo_dwords_avail * 4) >= chan->max_packet) {
 +                      if (!chan->xfer_started) {
 +                              dwc2_hc_start_transfer(hsotg, chan);
 +                              retval = 1;
 +                      } else {
 +                              retval = dwc2_hc_continue_transfer(hsotg, chan);
 +                      }
 +              } else {
 +                      retval = -1;
 +              }
 +      } else {
 +              if (!chan->xfer_started) {
 +                      dwc2_hc_start_transfer(hsotg, chan);
 +                      retval = 1;
 +              } else {
 +                      retval = dwc2_hc_continue_transfer(hsotg, chan);
 +              }
 +      }
 +
 +      return retval;
 +}
 +
 +/*
 + * Processes periodic channels for the next frame and queues transactions for
 + * these channels to the DWC_otg controller. After queueing transactions, the
 + * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions
 + * to queue as Periodic Tx FIFO or request queue space becomes available.
 + * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg)
 +{
 +      struct list_head *qh_ptr;
 +      struct dwc2_qh *qh;
 +      u32 tx_status;
 +      u32 fspcavail;
 +      u32 gintmsk;
 +      int status;
 +      int no_queue_space = 0;
 +      int no_fifo_space = 0;
 +      u32 qspcavail;
 +
 +      if (dbg_perio())
 +              dev_vdbg(hsotg->dev, "Queue periodic transactions\n");
 +
 +      tx_status = readl(hsotg->regs + HPTXSTS);
 +      qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                  TXSTS_QSPCAVAIL_SHIFT;
 +      fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                  TXSTS_FSPCAVAIL_SHIFT;
 +
 +      if (dbg_perio()) {
 +              dev_vdbg(hsotg->dev, "  P Tx Req Queue Space Avail (before queue): %d\n",
 +                       qspcavail);
 +              dev_vdbg(hsotg->dev, "  P Tx FIFO Space Avail (before queue): %d\n",
 +                       fspcavail);
 +      }
 +
 +      qh_ptr = hsotg->periodic_sched_assigned.next;
 +      while (qh_ptr != &hsotg->periodic_sched_assigned) {
 +              tx_status = readl(hsotg->regs + HPTXSTS);
 +              qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                          TXSTS_QSPCAVAIL_SHIFT;
 +              if (qspcavail == 0) {
 +                      no_queue_space = 1;
 +                      break;
 +              }
 +
 +              qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry);
 +              if (!qh->channel) {
 +                      qh_ptr = qh_ptr->next;
 +                      continue;
 +              }
 +
 +              /* Make sure EP's TT buffer is clean before queueing qtds */
 +              if (qh->tt_buffer_dirty) {
 +                      qh_ptr = qh_ptr->next;
 +                      continue;
 +              }
 +
 +              /*
 +               * Set a flag if we're queuing high-bandwidth in slave mode.
 +               * The flag prevents any halts to get into the request queue in
 +               * the middle of multiple high-bandwidth packets getting queued.
 +               */
 +              if (hsotg->core_params->dma_enable <= 0 &&
 +                              qh->channel->multi_count > 1)
 +                      hsotg->queuing_high_bandwidth = 1;
 +
 +              fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                          TXSTS_FSPCAVAIL_SHIFT;
 +              status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail);
 +              if (status < 0) {
 +                      no_fifo_space = 1;
 +                      break;
 +              }
 +
 +              /*
 +               * In Slave mode, stay on the current transfer until there is
 +               * nothing more to do or the high-bandwidth request count is
 +               * reached. In DMA mode, only need to queue one request. The
 +               * controller automatically handles multiple packets for
 +               * high-bandwidth transfers.
 +               */
 +              if (hsotg->core_params->dma_enable > 0 || status == 0 ||
 +                  qh->channel->requests == qh->channel->multi_count) {
 +                      qh_ptr = qh_ptr->next;
 +                      /*
 +                       * Move the QH from the periodic assigned schedule to
 +                       * the periodic queued schedule
 +                       */
 +                      list_move(&qh->qh_list_entry,
 +                                &hsotg->periodic_sched_queued);
 +
 +                      /* done queuing high bandwidth */
 +                      hsotg->queuing_high_bandwidth = 0;
 +              }
 +      }
 +
 +      if (hsotg->core_params->dma_enable <= 0) {
 +              tx_status = readl(hsotg->regs + HPTXSTS);
 +              qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                          TXSTS_QSPCAVAIL_SHIFT;
 +              fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                          TXSTS_FSPCAVAIL_SHIFT;
 +              if (dbg_perio()) {
 +                      dev_vdbg(hsotg->dev,
 +                               "  P Tx Req Queue Space Avail (after queue): %d\n",
 +                               qspcavail);
 +                      dev_vdbg(hsotg->dev,
 +                               "  P Tx FIFO Space Avail (after queue): %d\n",
 +                               fspcavail);
 +              }
 +
 +              if (!list_empty(&hsotg->periodic_sched_assigned) ||
 +                  no_queue_space || no_fifo_space) {
 +                      /*
 +                       * May need to queue more transactions as the request
 +                       * queue or Tx FIFO empties. Enable the periodic Tx
 +                       * FIFO empty interrupt. (Always use the half-empty
 +                       * level to ensure that new requests are loaded as
 +                       * soon as possible.)
 +                       */
 +                      gintmsk = readl(hsotg->regs + GINTMSK);
 +                      gintmsk |= GINTSTS_PTXFEMP;
 +                      writel(gintmsk, hsotg->regs + GINTMSK);
 +              } else {
 +                      /*
 +                       * Disable the Tx FIFO empty interrupt since there are
 +                       * no more transactions that need to be queued right
 +                       * now. This function is called from interrupt
 +                       * handlers to queue more transactions as transfer
 +                       * states change.
 +                       */
 +                      gintmsk = readl(hsotg->regs + GINTMSK);
 +                      gintmsk &= ~GINTSTS_PTXFEMP;
 +                      writel(gintmsk, hsotg->regs + GINTMSK);
 +              }
 +      }
 +}
 +
 +/*
 + * Processes active non-periodic channels and queues transactions for these
 + * channels to the DWC_otg controller. After queueing transactions, the NP Tx
 + * FIFO Empty interrupt is enabled if there are more transactions to queue as
 + * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx
 + * FIFO Empty interrupt is disabled.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +static void dwc2_process_non_periodic_channels(struct dwc2_hsotg *hsotg)
 +{
 +      struct list_head *orig_qh_ptr;
 +      struct dwc2_qh *qh;
 +      u32 tx_status;
 +      u32 qspcavail;
 +      u32 fspcavail;
 +      u32 gintmsk;
 +      int status;
 +      int no_queue_space = 0;
 +      int no_fifo_space = 0;
 +      int more_to_do = 0;
 +
 +      dev_vdbg(hsotg->dev, "Queue non-periodic transactions\n");
 +
 +      tx_status = readl(hsotg->regs + GNPTXSTS);
 +      qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                  TXSTS_QSPCAVAIL_SHIFT;
 +      fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                  TXSTS_FSPCAVAIL_SHIFT;
 +      dev_vdbg(hsotg->dev, "  NP Tx Req Queue Space Avail (before queue): %d\n",
 +               qspcavail);
 +      dev_vdbg(hsotg->dev, "  NP Tx FIFO Space Avail (before queue): %d\n",
 +               fspcavail);
 +
 +      /*
 +       * Keep track of the starting point. Skip over the start-of-list
 +       * entry.
 +       */
 +      if (hsotg->non_periodic_qh_ptr == &hsotg->non_periodic_sched_active)
 +              hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next;
 +      orig_qh_ptr = hsotg->non_periodic_qh_ptr;
 +
 +      /*
 +       * Process once through the active list or until no more space is
 +       * available in the request queue or the Tx FIFO
 +       */
 +      do {
 +              tx_status = readl(hsotg->regs + GNPTXSTS);
 +              qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                          TXSTS_QSPCAVAIL_SHIFT;
 +              if (hsotg->core_params->dma_enable <= 0 && qspcavail == 0) {
 +                      no_queue_space = 1;
 +                      break;
 +              }
 +
 +              qh = list_entry(hsotg->non_periodic_qh_ptr, struct dwc2_qh,
 +                              qh_list_entry);
 +              if (!qh->channel)
 +                      goto next;
 +
 +              /* Make sure EP's TT buffer is clean before queueing qtds */
 +              if (qh->tt_buffer_dirty)
 +                      goto next;
 +
 +              fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                          TXSTS_FSPCAVAIL_SHIFT;
 +              status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail);
 +
 +              if (status > 0) {
 +                      more_to_do = 1;
 +              } else if (status < 0) {
 +                      no_fifo_space = 1;
 +                      break;
 +              }
 +next:
 +              /* Advance to next QH, skipping start-of-list entry */
 +              hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next;
 +              if (hsotg->non_periodic_qh_ptr ==
 +                              &hsotg->non_periodic_sched_active)
 +                      hsotg->non_periodic_qh_ptr =
 +                                      hsotg->non_periodic_qh_ptr->next;
 +      } while (hsotg->non_periodic_qh_ptr != orig_qh_ptr);
 +
 +      if (hsotg->core_params->dma_enable <= 0) {
 +              tx_status = readl(hsotg->regs + GNPTXSTS);
 +              qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >>
 +                          TXSTS_QSPCAVAIL_SHIFT;
 +              fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >>
 +                          TXSTS_FSPCAVAIL_SHIFT;
 +              dev_vdbg(hsotg->dev,
 +                       "  NP Tx Req Queue Space Avail (after queue): %d\n",
 +                       qspcavail);
 +              dev_vdbg(hsotg->dev,
 +                       "  NP Tx FIFO Space Avail (after queue): %d\n",
 +                       fspcavail);
 +
 +              if (more_to_do || no_queue_space || no_fifo_space) {
 +                      /*
 +                       * May need to queue more transactions as the request
 +                       * queue or Tx FIFO empties. Enable the non-periodic
 +                       * Tx FIFO empty interrupt. (Always use the half-empty
 +                       * level to ensure that new requests are loaded as
 +                       * soon as possible.)
 +                       */
 +                      gintmsk = readl(hsotg->regs + GINTMSK);
 +                      gintmsk |= GINTSTS_NPTXFEMP;
 +                      writel(gintmsk, hsotg->regs + GINTMSK);
 +              } else {
 +                      /*
 +                       * Disable the Tx FIFO empty interrupt since there are
 +                       * no more transactions that need to be queued right
 +                       * now. This function is called from interrupt
 +                       * handlers to queue more transactions as transfer
 +                       * states change.
 +                       */
 +                      gintmsk = readl(hsotg->regs + GINTMSK);
 +                      gintmsk &= ~GINTSTS_NPTXFEMP;
 +                      writel(gintmsk, hsotg->regs + GINTMSK);
 +              }
 +      }
 +}
 +
 +/**
 + * dwc2_hcd_queue_transactions() - Processes the currently active host channels
 + * and queues transactions for these channels to the DWC_otg controller. Called
 + * from the HCD interrupt handler functions.
 + *
 + * @hsotg:   The HCD state structure
 + * @tr_type: The type(s) of transactions to queue (non-periodic, periodic,
 + *           or both)
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg,
 +                               enum dwc2_transaction_type tr_type)
 +{
 +#ifdef DWC2_DEBUG_SOF
 +      dev_vdbg(hsotg->dev, "Queue Transactions\n");
 +#endif
 +      /* Process host channels associated with periodic transfers */
 +      if ((tr_type == DWC2_TRANSACTION_PERIODIC ||
 +           tr_type == DWC2_TRANSACTION_ALL) &&
 +          !list_empty(&hsotg->periodic_sched_assigned))
 +              dwc2_process_periodic_channels(hsotg);
 +
 +      /* Process host channels associated with non-periodic transfers */
 +      if (tr_type == DWC2_TRANSACTION_NON_PERIODIC ||
 +          tr_type == DWC2_TRANSACTION_ALL) {
 +              if (!list_empty(&hsotg->non_periodic_sched_active)) {
 +                      dwc2_process_non_periodic_channels(hsotg);
 +              } else {
 +                      /*
 +                       * Ensure NP Tx FIFO empty interrupt is disabled when
 +                       * there are no non-periodic transfers to process
 +                       */
 +                      u32 gintmsk = readl(hsotg->regs + GINTMSK);
 +
 +                      gintmsk &= ~GINTSTS_NPTXFEMP;
 +                      writel(gintmsk, hsotg->regs + GINTMSK);
 +              }
 +      }
 +}
 +
 +static void dwc2_conn_id_status_change(struct work_struct *work)
 +{
 +      struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg,
 +                                              wf_otg);
 +      u32 count = 0;
 +      u32 gotgctl;
 +
 +      dev_dbg(hsotg->dev, "%s()\n", __func__);
 +
 +      gotgctl = readl(hsotg->regs + GOTGCTL);
 +      dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl);
 +      dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n",
 +              !!(gotgctl & GOTGCTL_CONID_B));
 +
 +      /* B-Device connector (Device Mode) */
 +      if (gotgctl & GOTGCTL_CONID_B) {
 +              /* Wait for switch to device mode */
 +              dev_dbg(hsotg->dev, "connId B\n");
 +              while (!dwc2_is_device_mode(hsotg)) {
 +                      dev_info(hsotg->dev,
 +                               "Waiting for Peripheral Mode, Mode=%s\n",
 +                               dwc2_is_host_mode(hsotg) ? "Host" :
 +                               "Peripheral");
 +                      usleep_range(20000, 40000);
 +                      if (++count > 250)
 +                              break;
 +              }
 +              if (count > 250)
 +                      dev_err(hsotg->dev,
 +                              "Connection id status change timed out\n");
 +              hsotg->op_state = OTG_STATE_B_PERIPHERAL;
 +              dwc2_core_init(hsotg, false, -1);
 +              dwc2_enable_global_interrupts(hsotg);
 +      } else {
 +              /* A-Device connector (Host Mode) */
 +              dev_dbg(hsotg->dev, "connId A\n");
 +              while (!dwc2_is_host_mode(hsotg)) {
 +                      dev_info(hsotg->dev, "Waiting for Host Mode, Mode=%s\n",
 +                               dwc2_is_host_mode(hsotg) ?
 +                               "Host" : "Peripheral");
 +                      usleep_range(20000, 40000);
 +                      if (++count > 250)
 +                              break;
 +              }
 +              if (count > 250)
 +                      dev_err(hsotg->dev,
 +                              "Connection id status change timed out\n");
 +              hsotg->op_state = OTG_STATE_A_HOST;
 +
 +              /* Initialize the Core for Host mode */
 +              dwc2_core_init(hsotg, false, -1);
 +              dwc2_enable_global_interrupts(hsotg);
 +              dwc2_hcd_start(hsotg);
 +      }
 +}
 +
 +static void dwc2_wakeup_detected(unsigned long data)
 +{
 +      struct dwc2_hsotg *hsotg = (struct dwc2_hsotg *)data;
 +      u32 hprt0;
 +
 +      dev_dbg(hsotg->dev, "%s()\n", __func__);
 +
 +      /*
 +       * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms
 +       * so that OPT tests pass with all PHYs.)
 +       */
 +      hprt0 = dwc2_read_hprt0(hsotg);
 +      dev_dbg(hsotg->dev, "Resume: HPRT0=%0x\n", hprt0);
 +      hprt0 &= ~HPRT0_RES;
 +      writel(hprt0, hsotg->regs + HPRT0);
 +      dev_dbg(hsotg->dev, "Clear Resume: HPRT0=%0x\n",
 +              readl(hsotg->regs + HPRT0));
 +
 +      dwc2_hcd_rem_wakeup(hsotg);
 +
 +      /* Change to L0 state */
 +      hsotg->lx_state = DWC2_L0;
 +}
 +
 +static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg)
 +{
 +      struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg);
 +
 +      return hcd->self.b_hnp_enable;
 +}
 +
 +/* Must NOT be called with interrupt disabled or spinlock held */
 +static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)
 +{
 +      unsigned long flags;
 +      u32 hprt0;
 +      u32 pcgctl;
 +      u32 gotgctl;
 +
 +      dev_dbg(hsotg->dev, "%s()\n", __func__);
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      if (windex == hsotg->otg_port && dwc2_host_is_b_hnp_enabled(hsotg)) {
 +              gotgctl = readl(hsotg->regs + GOTGCTL);
 +              gotgctl |= GOTGCTL_HSTSETHNPEN;
 +              writel(gotgctl, hsotg->regs + GOTGCTL);
 +              hsotg->op_state = OTG_STATE_A_SUSPEND;
 +      }
 +
 +      hprt0 = dwc2_read_hprt0(hsotg);
 +      hprt0 |= HPRT0_SUSP;
 +      writel(hprt0, hsotg->regs + HPRT0);
 +
 +      /* Update lx_state */
 +      hsotg->lx_state = DWC2_L2;
 +
 +      /* Suspend the Phy Clock */
 +      pcgctl = readl(hsotg->regs + PCGCTL);
 +      pcgctl |= PCGCTL_STOPPCLK;
 +      writel(pcgctl, hsotg->regs + PCGCTL);
 +      udelay(10);
 +
 +      /* For HNP the bus must be suspended for at least 200ms */
 +      if (dwc2_host_is_b_hnp_enabled(hsotg)) {
 +              pcgctl = readl(hsotg->regs + PCGCTL);
 +              pcgctl &= ~PCGCTL_STOPPCLK;
 +              writel(pcgctl, hsotg->regs + PCGCTL);
 +
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +
 +              usleep_range(200000, 250000);
 +      } else {
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +      }
 +}
 +
 +/* Handles hub class-specific requests */
 +static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,
 +                              u16 wvalue, u16 windex, char *buf, u16 wlength)
 +{
 +      struct usb_hub_descriptor *hub_desc;
 +      int retval = 0;
 +      u32 hprt0;
 +      u32 port_status;
 +      u32 speed;
 +      u32 pcgctl;
 +
 +      switch (typereq) {
 +      case ClearHubFeature:
 +              dev_dbg(hsotg->dev, "ClearHubFeature %1xh\n", wvalue);
 +
 +              switch (wvalue) {
 +              case C_HUB_LOCAL_POWER:
 +              case C_HUB_OVER_CURRENT:
 +                      /* Nothing required here */
 +                      break;
 +
 +              default:
 +                      retval = -EINVAL;
 +                      dev_err(hsotg->dev,
 +                              "ClearHubFeature request %1xh unknown\n",
 +                              wvalue);
 +              }
 +              break;
 +
 +      case ClearPortFeature:
 +              if (wvalue != USB_PORT_FEAT_L1)
 +                      if (!windex || windex > 1)
 +                              goto error;
 +              switch (wvalue) {
 +              case USB_PORT_FEAT_ENABLE:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_ENABLE\n");
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      hprt0 |= HPRT0_ENA;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      break;
 +
 +              case USB_PORT_FEAT_SUSPEND:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_SUSPEND\n");
 +                      writel(0, hsotg->regs + PCGCTL);
 +                      usleep_range(20000, 40000);
 +
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      hprt0 |= HPRT0_RES;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      hprt0 &= ~HPRT0_SUSP;
 +                      usleep_range(100000, 150000);
 +
 +                      hprt0 &= ~HPRT0_RES;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      break;
 +
 +              case USB_PORT_FEAT_POWER:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_POWER\n");
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      hprt0 &= ~HPRT0_PWR;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      break;
 +
 +              case USB_PORT_FEAT_INDICATOR:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_INDICATOR\n");
 +                      /* Port indicator not supported */
 +                      break;
 +
 +              case USB_PORT_FEAT_C_CONNECTION:
 +                      /*
 +                       * Clears driver's internal Connect Status Change flag
 +                       */
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n");
 +                      hsotg->flags.b.port_connect_status_change = 0;
 +                      break;
 +
 +              case USB_PORT_FEAT_C_RESET:
 +                      /* Clears driver's internal Port Reset Change flag */
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_RESET\n");
 +                      hsotg->flags.b.port_reset_change = 0;
 +                      break;
 +
 +              case USB_PORT_FEAT_C_ENABLE:
 +                      /*
 +                       * Clears the driver's internal Port Enable/Disable
 +                       * Change flag
 +                       */
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n");
 +                      hsotg->flags.b.port_enable_change = 0;
 +                      break;
 +
 +              case USB_PORT_FEAT_C_SUSPEND:
 +                      /*
 +                       * Clears the driver's internal Port Suspend Change
 +                       * flag, which is set when resume signaling on the host
 +                       * port is complete
 +                       */
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n");
 +                      hsotg->flags.b.port_suspend_change = 0;
 +                      break;
 +
 +              case USB_PORT_FEAT_C_PORT_L1:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_PORT_L1\n");
 +                      hsotg->flags.b.port_l1_change = 0;
 +                      break;
 +
 +              case USB_PORT_FEAT_C_OVER_CURRENT:
 +                      dev_dbg(hsotg->dev,
 +                              "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n");
 +                      hsotg->flags.b.port_over_current_change = 0;
 +                      break;
 +
 +              default:
 +                      retval = -EINVAL;
 +                      dev_err(hsotg->dev,
 +                              "ClearPortFeature request %1xh unknown or unsupported\n",
 +                              wvalue);
 +              }
 +              break;
 +
 +      case GetHubDescriptor:
 +              dev_dbg(hsotg->dev, "GetHubDescriptor\n");
 +              hub_desc = (struct usb_hub_descriptor *)buf;
 +              hub_desc->bDescLength = 9;
 +              hub_desc->bDescriptorType = 0x29;
 +              hub_desc->bNbrPorts = 1;
 +              hub_desc->wHubCharacteristics = cpu_to_le16(0x08);
 +              hub_desc->bPwrOn2PwrGood = 1;
 +              hub_desc->bHubContrCurrent = 0;
 +              hub_desc->u.hs.DeviceRemovable[0] = 0;
 +              hub_desc->u.hs.DeviceRemovable[1] = 0xff;
 +              break;
 +
 +      case GetHubStatus:
 +              dev_dbg(hsotg->dev, "GetHubStatus\n");
 +              memset(buf, 0, 4);
 +              break;
 +
 +      case GetPortStatus:
 +              dev_vdbg(hsotg->dev,
 +                       "GetPortStatus wIndex=0x%04x flags=0x%08x\n", windex,
 +                       hsotg->flags.d32);
 +              if (!windex || windex > 1)
 +                      goto error;
 +
 +              port_status = 0;
 +              if (hsotg->flags.b.port_connect_status_change)
 +                      port_status |= USB_PORT_STAT_C_CONNECTION << 16;
 +              if (hsotg->flags.b.port_enable_change)
 +                      port_status |= USB_PORT_STAT_C_ENABLE << 16;
 +              if (hsotg->flags.b.port_suspend_change)
 +                      port_status |= USB_PORT_STAT_C_SUSPEND << 16;
 +              if (hsotg->flags.b.port_l1_change)
 +                      port_status |= USB_PORT_STAT_C_L1 << 16;
 +              if (hsotg->flags.b.port_reset_change)
 +                      port_status |= USB_PORT_STAT_C_RESET << 16;
 +              if (hsotg->flags.b.port_over_current_change) {
 +                      dev_warn(hsotg->dev, "Overcurrent change detected\n");
 +                      port_status |= USB_PORT_STAT_C_OVERCURRENT << 16;
 +              }
 +
 +              if (!hsotg->flags.b.port_connect_status) {
 +                      /*
 +                       * The port is disconnected, which means the core is
 +                       * either in device mode or it soon will be. Just
 +                       * return 0's for the remainder of the port status
 +                       * since the port register can't be read if the core
 +                       * is in device mode.
 +                       */
 +                      *(__le32 *)buf = cpu_to_le32(port_status);
 +                      break;
 +              }
 +
 +              hprt0 = readl(hsotg->regs + HPRT0);
 +              dev_vdbg(hsotg->dev, "  HPRT0: 0x%08x\n", hprt0);
 +
 +              if (hprt0 & HPRT0_CONNSTS)
 +                      port_status |= USB_PORT_STAT_CONNECTION;
 +              if (hprt0 & HPRT0_ENA)
 +                      port_status |= USB_PORT_STAT_ENABLE;
 +              if (hprt0 & HPRT0_SUSP)
 +                      port_status |= USB_PORT_STAT_SUSPEND;
 +              if (hprt0 & HPRT0_OVRCURRACT)
 +                      port_status |= USB_PORT_STAT_OVERCURRENT;
 +              if (hprt0 & HPRT0_RST)
 +                      port_status |= USB_PORT_STAT_RESET;
 +              if (hprt0 & HPRT0_PWR)
 +                      port_status |= USB_PORT_STAT_POWER;
 +
 +              speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT;
 +              if (speed == HPRT0_SPD_HIGH_SPEED)
 +                      port_status |= USB_PORT_STAT_HIGH_SPEED;
 +              else if (speed == HPRT0_SPD_LOW_SPEED)
 +                      port_status |= USB_PORT_STAT_LOW_SPEED;
 +
 +              if (hprt0 & HPRT0_TSTCTL_MASK)
 +                      port_status |= USB_PORT_STAT_TEST;
 +              /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
 +
 +              dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status);
 +              *(__le32 *)buf = cpu_to_le32(port_status);
 +              break;
 +
 +      case SetHubFeature:
 +              dev_dbg(hsotg->dev, "SetHubFeature\n");
 +              /* No HUB features supported */
 +              break;
 +
 +      case SetPortFeature:
 +              dev_dbg(hsotg->dev, "SetPortFeature\n");
 +              if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1))
 +                      goto error;
 +
 +              if (!hsotg->flags.b.port_connect_status) {
 +                      /*
 +                       * The port is disconnected, which means the core is
 +                       * either in device mode or it soon will be. Just
 +                       * return without doing anything since the port
 +                       * register can't be written if the core is in device
 +                       * mode.
 +                       */
 +                      break;
 +              }
 +
 +              switch (wvalue) {
 +              case USB_PORT_FEAT_SUSPEND:
 +                      dev_dbg(hsotg->dev,
 +                              "SetPortFeature - USB_PORT_FEAT_SUSPEND\n");
 +                      if (windex != hsotg->otg_port)
 +                              goto error;
 +                      dwc2_port_suspend(hsotg, windex);
 +                      break;
 +
 +              case USB_PORT_FEAT_POWER:
 +                      dev_dbg(hsotg->dev,
 +                              "SetPortFeature - USB_PORT_FEAT_POWER\n");
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      hprt0 |= HPRT0_PWR;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      break;
 +
 +              case USB_PORT_FEAT_RESET:
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      dev_dbg(hsotg->dev,
 +                              "SetPortFeature - USB_PORT_FEAT_RESET\n");
 +                      pcgctl = readl(hsotg->regs + PCGCTL);
 +                      pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK);
 +                      writel(pcgctl, hsotg->regs + PCGCTL);
 +                      /* ??? Original driver does this */
 +                      writel(0, hsotg->regs + PCGCTL);
 +
 +                      hprt0 = dwc2_read_hprt0(hsotg);
 +                      /* Clear suspend bit if resetting from suspend state */
 +                      hprt0 &= ~HPRT0_SUSP;
 +
 +                      /*
 +                       * When B-Host the Port reset bit is set in the Start
 +                       * HCD Callback function, so that the reset is started
 +                       * within 1ms of the HNP success interrupt
 +                       */
 +                      if (!dwc2_hcd_is_b_host(hsotg)) {
 +                              hprt0 |= HPRT0_PWR | HPRT0_RST;
 +                              dev_dbg(hsotg->dev,
 +                                      "In host mode, hprt0=%08x\n", hprt0);
 +                              writel(hprt0, hsotg->regs + HPRT0);
 +                      }
 +
 +                      /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
 +                      usleep_range(50000, 70000);
 +                      hprt0 &= ~HPRT0_RST;
 +                      writel(hprt0, hsotg->regs + HPRT0);
 +                      hsotg->lx_state = DWC2_L0; /* Now back to On state */
 +                      break;
 +
 +              case USB_PORT_FEAT_INDICATOR:
 +                      dev_dbg(hsotg->dev,
 +                              "SetPortFeature - USB_PORT_FEAT_INDICATOR\n");
 +                      /* Not supported */
 +                      break;
 +
 +              default:
 +                      retval = -EINVAL;
 +                      dev_err(hsotg->dev,
 +                              "SetPortFeature %1xh unknown or unsupported\n",
 +                              wvalue);
 +                      break;
 +              }
 +              break;
 +
 +      default:
 +error:
 +              retval = -EINVAL;
 +              dev_dbg(hsotg->dev,
 +                      "Unknown hub control request: %1xh wIndex: %1xh wValue: %1xh\n",
 +                      typereq, windex, wvalue);
 +              break;
 +      }
 +
 +      return retval;
 +}
 +
 +static int dwc2_hcd_is_status_changed(struct dwc2_hsotg *hsotg, int port)
 +{
 +      int retval;
 +
 +      if (port != 1)
 +              return -EINVAL;
 +
 +      retval = (hsotg->flags.b.port_connect_status_change ||
 +                hsotg->flags.b.port_reset_change ||
 +                hsotg->flags.b.port_enable_change ||
 +                hsotg->flags.b.port_suspend_change ||
 +                hsotg->flags.b.port_over_current_change);
 +
 +      if (retval) {
 +              dev_dbg(hsotg->dev,
 +                      "DWC OTG HCD HUB STATUS DATA: Root port status changed\n");
 +              dev_dbg(hsotg->dev, "  port_connect_status_change: %d\n",
 +                      hsotg->flags.b.port_connect_status_change);
 +              dev_dbg(hsotg->dev, "  port_reset_change: %d\n",
 +                      hsotg->flags.b.port_reset_change);
 +              dev_dbg(hsotg->dev, "  port_enable_change: %d\n",
 +                      hsotg->flags.b.port_enable_change);
 +              dev_dbg(hsotg->dev, "  port_suspend_change: %d\n",
 +                      hsotg->flags.b.port_suspend_change);
 +              dev_dbg(hsotg->dev, "  port_over_current_change: %d\n",
 +                      hsotg->flags.b.port_over_current_change);
 +      }
 +
 +      return retval;
 +}
 +
 +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg)
 +{
 +      u32 hfnum = readl(hsotg->regs + HFNUM);
 +
 +#ifdef DWC2_DEBUG_SOF
 +      dev_vdbg(hsotg->dev, "DWC OTG HCD GET FRAME NUMBER %d\n",
 +               (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT);
 +#endif
 +      return (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT;
 +}
 +
 +int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg)
 +{
 +      return hsotg->op_state == OTG_STATE_B_HOST;
 +}
 +
 +static struct dwc2_hcd_urb *dwc2_hcd_urb_alloc(struct dwc2_hsotg *hsotg,
 +                                             int iso_desc_count,
 +                                             gfp_t mem_flags)
 +{
 +      struct dwc2_hcd_urb *urb;
 +      u32 size = sizeof(*urb) + iso_desc_count *
 +                 sizeof(struct dwc2_hcd_iso_packet_desc);
 +
 +      urb = kzalloc(size, mem_flags);
 +      if (urb)
 +              urb->packet_count = iso_desc_count;
 +      return urb;
 +}
 +
 +static void dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *hsotg,
 +                                    struct dwc2_hcd_urb *urb, u8 dev_addr,
 +                                    u8 ep_num, u8 ep_type, u8 ep_dir, u16 mps)
 +{
 +      if (dbg_perio() ||
 +          ep_type == USB_ENDPOINT_XFER_BULK ||
 +          ep_type == USB_ENDPOINT_XFER_CONTROL)
 +              dev_vdbg(hsotg->dev,
 +                       "addr=%d, ep_num=%d, ep_dir=%1x, ep_type=%1x, mps=%d\n",
 +                       dev_addr, ep_num, ep_dir, ep_type, mps);
 +      urb->pipe_info.dev_addr = dev_addr;
 +      urb->pipe_info.ep_num = ep_num;
 +      urb->pipe_info.pipe_type = ep_type;
 +      urb->pipe_info.pipe_dir = ep_dir;
 +      urb->pipe_info.mps = mps;
 +}
 +
 +/*
 + * NOTE: This function will be removed once the peripheral controller code
 + * is integrated and the driver is stable
 + */
 +void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg)
 +{
 +#ifdef DEBUG
 +      struct dwc2_host_chan *chan;
 +      struct dwc2_hcd_urb *urb;
 +      struct dwc2_qtd *qtd;
 +      int num_channels;
 +      u32 np_tx_status;
 +      u32 p_tx_status;
 +      int i;
 +
 +      num_channels = hsotg->core_params->host_channels;
 +      dev_dbg(hsotg->dev, "\n");
 +      dev_dbg(hsotg->dev,
 +              "************************************************************\n");
 +      dev_dbg(hsotg->dev, "HCD State:\n");
 +      dev_dbg(hsotg->dev, "  Num channels: %d\n", num_channels);
 +
 +      for (i = 0; i < num_channels; i++) {
 +              chan = hsotg->hc_ptr_array[i];
 +              dev_dbg(hsotg->dev, "  Channel %d:\n", i);
 +              dev_dbg(hsotg->dev,
 +                      "    dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
 +                      chan->dev_addr, chan->ep_num, chan->ep_is_in);
 +              dev_dbg(hsotg->dev, "    speed: %d\n", chan->speed);
 +              dev_dbg(hsotg->dev, "    ep_type: %d\n", chan->ep_type);
 +              dev_dbg(hsotg->dev, "    max_packet: %d\n", chan->max_packet);
 +              dev_dbg(hsotg->dev, "    data_pid_start: %d\n",
 +                      chan->data_pid_start);
 +              dev_dbg(hsotg->dev, "    multi_count: %d\n", chan->multi_count);
 +              dev_dbg(hsotg->dev, "    xfer_started: %d\n",
 +                      chan->xfer_started);
 +              dev_dbg(hsotg->dev, "    xfer_buf: %p\n", chan->xfer_buf);
 +              dev_dbg(hsotg->dev, "    xfer_dma: %08lx\n",
 +                      (unsigned long)chan->xfer_dma);
 +              dev_dbg(hsotg->dev, "    xfer_len: %d\n", chan->xfer_len);
 +              dev_dbg(hsotg->dev, "    xfer_count: %d\n", chan->xfer_count);
 +              dev_dbg(hsotg->dev, "    halt_on_queue: %d\n",
 +                      chan->halt_on_queue);
 +              dev_dbg(hsotg->dev, "    halt_pending: %d\n",
 +                      chan->halt_pending);
 +              dev_dbg(hsotg->dev, "    halt_status: %d\n", chan->halt_status);
 +              dev_dbg(hsotg->dev, "    do_split: %d\n", chan->do_split);
 +              dev_dbg(hsotg->dev, "    complete_split: %d\n",
 +                      chan->complete_split);
 +              dev_dbg(hsotg->dev, "    hub_addr: %d\n", chan->hub_addr);
 +              dev_dbg(hsotg->dev, "    hub_port: %d\n", chan->hub_port);
 +              dev_dbg(hsotg->dev, "    xact_pos: %d\n", chan->xact_pos);
 +              dev_dbg(hsotg->dev, "    requests: %d\n", chan->requests);
 +              dev_dbg(hsotg->dev, "    qh: %p\n", chan->qh);
 +
 +              if (chan->xfer_started) {
 +                      u32 hfnum, hcchar, hctsiz, hcint, hcintmsk;
 +
 +                      hfnum = readl(hsotg->regs + HFNUM);
 +                      hcchar = readl(hsotg->regs + HCCHAR(i));
 +                      hctsiz = readl(hsotg->regs + HCTSIZ(i));
 +                      hcint = readl(hsotg->regs + HCINT(i));
 +                      hcintmsk = readl(hsotg->regs + HCINTMSK(i));
 +                      dev_dbg(hsotg->dev, "    hfnum: 0x%08x\n", hfnum);
 +                      dev_dbg(hsotg->dev, "    hcchar: 0x%08x\n", hcchar);
 +                      dev_dbg(hsotg->dev, "    hctsiz: 0x%08x\n", hctsiz);
 +                      dev_dbg(hsotg->dev, "    hcint: 0x%08x\n", hcint);
 +                      dev_dbg(hsotg->dev, "    hcintmsk: 0x%08x\n", hcintmsk);
 +              }
 +
 +              if (!(chan->xfer_started && chan->qh))
 +                      continue;
 +
 +              list_for_each_entry(qtd, &chan->qh->qtd_list, qtd_list_entry) {
 +                      if (!qtd->in_process)
 +                              break;
 +                      urb = qtd->urb;
 +                      dev_dbg(hsotg->dev, "    URB Info:\n");
 +                      dev_dbg(hsotg->dev, "      qtd: %p, urb: %p\n",
 +                              qtd, urb);
 +                      if (urb) {
 +                              dev_dbg(hsotg->dev,
 +                                      "      Dev: %d, EP: %d %s\n",
 +                                      dwc2_hcd_get_dev_addr(&urb->pipe_info),
 +                                      dwc2_hcd_get_ep_num(&urb->pipe_info),
 +                                      dwc2_hcd_is_pipe_in(&urb->pipe_info) ?
 +                                      "IN" : "OUT");
 +                              dev_dbg(hsotg->dev,
 +                                      "      Max packet size: %d\n",
 +                                      dwc2_hcd_get_mps(&urb->pipe_info));
 +                              dev_dbg(hsotg->dev,
 +                                      "      transfer_buffer: %p\n",
 +                                      urb->buf);
 +                              dev_dbg(hsotg->dev,
 +                                      "      transfer_dma: %08lx\n",
 +                                      (unsigned long)urb->dma);
 +                              dev_dbg(hsotg->dev,
 +                                      "      transfer_buffer_length: %d\n",
 +                                      urb->length);
 +                              dev_dbg(hsotg->dev, "      actual_length: %d\n",
 +                                      urb->actual_length);
 +                      }
 +              }
 +      }
 +
 +      dev_dbg(hsotg->dev, "  non_periodic_channels: %d\n",
 +              hsotg->non_periodic_channels);
 +      dev_dbg(hsotg->dev, "  periodic_channels: %d\n",
 +              hsotg->periodic_channels);
 +      dev_dbg(hsotg->dev, "  periodic_usecs: %d\n", hsotg->periodic_usecs);
 +      np_tx_status = readl(hsotg->regs + GNPTXSTS);
 +      dev_dbg(hsotg->dev, "  NP Tx Req Queue Space Avail: %d\n",
 +              (np_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT);
 +      dev_dbg(hsotg->dev, "  NP Tx FIFO Space Avail: %d\n",
 +              (np_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT);
 +      p_tx_status = readl(hsotg->regs + HPTXSTS);
 +      dev_dbg(hsotg->dev, "  P Tx Req Queue Space Avail: %d\n",
 +              (p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT);
 +      dev_dbg(hsotg->dev, "  P Tx FIFO Space Avail: %d\n",
 +              (p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT);
 +      dwc2_hcd_dump_frrem(hsotg);
 +      dwc2_dump_global_registers(hsotg);
 +      dwc2_dump_host_registers(hsotg);
 +      dev_dbg(hsotg->dev,
 +              "************************************************************\n");
 +      dev_dbg(hsotg->dev, "\n");
 +#endif
 +}
 +
 +/*
 + * NOTE: This function will be removed once the peripheral controller code
 + * is integrated and the driver is stable
 + */
 +void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg)
 +{
 +#ifdef DWC2_DUMP_FRREM
 +      dev_dbg(hsotg->dev, "Frame remaining at SOF:\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->frrem_samples, hsotg->frrem_accum,
 +              hsotg->frrem_samples > 0 ?
 +              hsotg->frrem_accum / hsotg->frrem_samples : 0);
 +      dev_dbg(hsotg->dev, "\n");
 +      dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 7):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_7_samples,
 +              hsotg->hfnum_7_frrem_accum,
 +              hsotg->hfnum_7_samples > 0 ?
 +              hsotg->hfnum_7_frrem_accum / hsotg->hfnum_7_samples : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 0):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_0_samples,
 +              hsotg->hfnum_0_frrem_accum,
 +              hsotg->hfnum_0_samples > 0 ?
 +              hsotg->hfnum_0_frrem_accum / hsotg->hfnum_0_samples : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 1-6):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_other_samples,
 +              hsotg->hfnum_other_frrem_accum,
 +              hsotg->hfnum_other_samples > 0 ?
 +              hsotg->hfnum_other_frrem_accum / hsotg->hfnum_other_samples :
 +              0);
 +      dev_dbg(hsotg->dev, "\n");
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 7):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_7_samples_a, hsotg->hfnum_7_frrem_accum_a,
 +              hsotg->hfnum_7_samples_a > 0 ?
 +              hsotg->hfnum_7_frrem_accum_a / hsotg->hfnum_7_samples_a : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 0):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_0_samples_a, hsotg->hfnum_0_frrem_accum_a,
 +              hsotg->hfnum_0_samples_a > 0 ?
 +              hsotg->hfnum_0_frrem_accum_a / hsotg->hfnum_0_samples_a : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 1-6):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_other_samples_a, hsotg->hfnum_other_frrem_accum_a,
 +              hsotg->hfnum_other_samples_a > 0 ?
 +              hsotg->hfnum_other_frrem_accum_a / hsotg->hfnum_other_samples_a
 +              : 0);
 +      dev_dbg(hsotg->dev, "\n");
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 7):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_7_samples_b, hsotg->hfnum_7_frrem_accum_b,
 +              hsotg->hfnum_7_samples_b > 0 ?
 +              hsotg->hfnum_7_frrem_accum_b / hsotg->hfnum_7_samples_b : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 0):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_0_samples_b, hsotg->hfnum_0_frrem_accum_b,
 +              (hsotg->hfnum_0_samples_b > 0) ?
 +              hsotg->hfnum_0_frrem_accum_b / hsotg->hfnum_0_samples_b : 0);
 +      dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 1-6):\n");
 +      dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n",
 +              hsotg->hfnum_other_samples_b, hsotg->hfnum_other_frrem_accum_b,
 +              (hsotg->hfnum_other_samples_b > 0) ?
 +              hsotg->hfnum_other_frrem_accum_b / hsotg->hfnum_other_samples_b
 +              : 0);
 +#endif
 +}
 +
 +struct wrapper_priv_data {
 +      struct dwc2_hsotg *hsotg;
 +};
 +
 +/* Gets the dwc2_hsotg from a usb_hcd */
 +static struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *hcd)
 +{
 +      struct wrapper_priv_data *p;
 +
 +      p = (struct wrapper_priv_data *) &hcd->hcd_priv;
 +      return p->hsotg;
 +}
 +
 +static int _dwc2_hcd_start(struct usb_hcd *hcd);
 +
 +void dwc2_host_start(struct dwc2_hsotg *hsotg)
 +{
 +      struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg);
 +
 +      hcd->self.is_b_host = dwc2_hcd_is_b_host(hsotg);
 +      _dwc2_hcd_start(hcd);
 +}
 +
 +void dwc2_host_disconnect(struct dwc2_hsotg *hsotg)
 +{
 +      struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg);
 +
 +      hcd->self.is_b_host = 0;
 +}
 +
 +void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, int *hub_addr,
 +                      int *hub_port)
 +{
 +      struct urb *urb = context;
 +
 +      if (urb->dev->tt)
 +              *hub_addr = urb->dev->tt->hub->devnum;
 +      else
 +              *hub_addr = 0;
 +      *hub_port = urb->dev->ttport;
 +}
 +
 +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context)
 +{
 +      struct urb *urb = context;
 +
 +      return urb->dev->speed;
 +}
 +
 +static void dwc2_allocate_bus_bandwidth(struct usb_hcd *hcd, u16 bw,
 +                                      struct urb *urb)
 +{
 +      struct usb_bus *bus = hcd_to_bus(hcd);
 +
 +      if (urb->interval)
 +              bus->bandwidth_allocated += bw / urb->interval;
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS)
 +              bus->bandwidth_isoc_reqs++;
 +      else
 +              bus->bandwidth_int_reqs++;
 +}
 +
 +static void dwc2_free_bus_bandwidth(struct usb_hcd *hcd, u16 bw,
 +                                  struct urb *urb)
 +{
 +      struct usb_bus *bus = hcd_to_bus(hcd);
 +
 +      if (urb->interval)
 +              bus->bandwidth_allocated -= bw / urb->interval;
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS)
 +              bus->bandwidth_isoc_reqs--;
 +      else
 +              bus->bandwidth_int_reqs--;
 +}
 +
 +/*
 + * Sets the final status of an URB and returns it to the upper layer. Any
 + * required cleanup of the URB is performed.
 + *
 + * Must be called with interrupt disabled and spinlock held
 + */
 +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd,
 +                      int status)
 +{
 +      struct urb *urb;
 +      int i;
 +
 +      if (!qtd) {
 +              dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__);
 +              return;
 +      }
 +
 +      if (!qtd->urb) {
 +              dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__);
 +              return;
 +      }
 +
 +      urb = qtd->urb->priv;
 +      if (!urb) {
 +              dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__);
 +              return;
 +      }
 +
 +      urb->actual_length = dwc2_hcd_urb_get_actual_length(qtd->urb);
 +
 +      if (dbg_urb(urb))
 +              dev_vdbg(hsotg->dev,
 +                       "%s: urb %p device %d ep %d-%s status %d actual %d\n",
 +                       __func__, urb, usb_pipedevice(urb->pipe),
 +                       usb_pipeendpoint(urb->pipe),
 +                       usb_pipein(urb->pipe) ? "IN" : "OUT", status,
 +                       urb->actual_length);
 +
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS && dbg_perio()) {
 +              for (i = 0; i < urb->number_of_packets; i++)
 +                      dev_vdbg(hsotg->dev, " ISO Desc %d status %d\n",
 +                               i, urb->iso_frame_desc[i].status);
 +      }
 +
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
 +              urb->error_count = dwc2_hcd_urb_get_error_count(qtd->urb);
 +              for (i = 0; i < urb->number_of_packets; ++i) {
 +                      urb->iso_frame_desc[i].actual_length =
 +                              dwc2_hcd_urb_get_iso_desc_actual_length(
 +                                              qtd->urb, i);
 +                      urb->iso_frame_desc[i].status =
 +                              dwc2_hcd_urb_get_iso_desc_status(qtd->urb, i);
 +              }
 +      }
 +
 +      urb->status = status;
 +      if (!status) {
 +              if ((urb->transfer_flags & URB_SHORT_NOT_OK) &&
 +                  urb->actual_length < urb->transfer_buffer_length)
 +                      urb->status = -EREMOTEIO;
 +      }
 +
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS ||
 +          usb_pipetype(urb->pipe) == PIPE_INTERRUPT) {
 +              struct usb_host_endpoint *ep = urb->ep;
 +
 +              if (ep)
 +                      dwc2_free_bus_bandwidth(dwc2_hsotg_to_hcd(hsotg),
 +                                      dwc2_hcd_get_ep_bandwidth(hsotg, ep),
 +                                      urb);
 +      }
 +
 +      usb_hcd_unlink_urb_from_ep(dwc2_hsotg_to_hcd(hsotg), urb);
 +      urb->hcpriv = NULL;
 +      kfree(qtd->urb);
 +      qtd->urb = NULL;
 +
 +      spin_unlock(&hsotg->lock);
 +      usb_hcd_giveback_urb(dwc2_hsotg_to_hcd(hsotg), urb, status);
 +      spin_lock(&hsotg->lock);
 +}
 +
 +/*
 + * Work queue function for starting the HCD when A-Cable is connected
 + */
 +static void dwc2_hcd_start_func(struct work_struct *work)
 +{
 +      struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg,
 +                                              start_work.work);
 +
 +      dev_dbg(hsotg->dev, "%s() %p\n", __func__, hsotg);
 +      dwc2_host_start(hsotg);
 +}
 +
 +/*
 + * Reset work queue function
 + */
 +static void dwc2_hcd_reset_func(struct work_struct *work)
 +{
 +      struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg,
 +                                              reset_work.work);
 +      u32 hprt0;
 +
 +      dev_dbg(hsotg->dev, "USB RESET function called\n");
 +      hprt0 = dwc2_read_hprt0(hsotg);
 +      hprt0 &= ~HPRT0_RST;
 +      writel(hprt0, hsotg->regs + HPRT0);
 +      hsotg->flags.b.port_reset_change = 1;
 +}
 +
 +/*
 + * =========================================================================
 + *  Linux HC Driver Functions
 + * =========================================================================
 + */
 +
 +/*
 + * Initializes the DWC_otg controller and its root hub and prepares it for host
 + * mode operation. Activates the root port. Returns 0 on success and a negative
 + * error code on failure.
 + */
 +static int _dwc2_hcd_start(struct usb_hcd *hcd)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      struct usb_bus *bus = hcd_to_bus(hcd);
 +      unsigned long flags;
 +
 +      dev_dbg(hsotg->dev, "DWC OTG HCD START\n");
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      hcd->state = HC_STATE_RUNNING;
 +
 +      if (dwc2_is_device_mode(hsotg)) {
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +              return 0;       /* why 0 ?? */
 +      }
 +
 +      dwc2_hcd_reinit(hsotg);
 +
 +      /* Initialize and connect root hub if one is not already attached */
 +      if (bus->root_hub) {
 +              dev_dbg(hsotg->dev, "DWC OTG HCD Has Root Hub\n");
 +              /* Inform the HUB driver to resume */
 +              usb_hcd_resume_root_hub(hcd);
 +      }
 +
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +      return 0;
 +}
 +
 +/*
 + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
 + * stopped.
 + */
 +static void _dwc2_hcd_stop(struct usb_hcd *hcd)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +      dwc2_hcd_stop(hsotg);
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +
 +      usleep_range(1000, 3000);
 +}
 +
 +/* Returns the current frame number */
 +static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +
 +      return dwc2_hcd_get_frame_number(hsotg);
 +}
 +
 +static void dwc2_dump_urb_info(struct usb_hcd *hcd, struct urb *urb,
 +                             char *fn_name)
 +{
 +#ifdef VERBOSE_DEBUG
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      char *pipetype;
 +      char *speed;
 +
 +      dev_vdbg(hsotg->dev, "%s, urb %p\n", fn_name, urb);
 +      dev_vdbg(hsotg->dev, "  Device address: %d\n",
 +               usb_pipedevice(urb->pipe));
 +      dev_vdbg(hsotg->dev, "  Endpoint: %d, %s\n",
 +               usb_pipeendpoint(urb->pipe),
 +               usb_pipein(urb->pipe) ? "IN" : "OUT");
 +
 +      switch (usb_pipetype(urb->pipe)) {
 +      case PIPE_CONTROL:
 +              pipetype = "CONTROL";
 +              break;
 +      case PIPE_BULK:
 +              pipetype = "BULK";
 +              break;
 +      case PIPE_INTERRUPT:
 +              pipetype = "INTERRUPT";
 +              break;
 +      case PIPE_ISOCHRONOUS:
 +              pipetype = "ISOCHRONOUS";
 +              break;
 +      default:
 +              pipetype = "UNKNOWN";
 +              break;
 +      }
 +
 +      dev_vdbg(hsotg->dev, "  Endpoint type: %s %s (%s)\n", pipetype,
 +               usb_urb_dir_in(urb) ? "IN" : "OUT", usb_pipein(urb->pipe) ?
 +               "IN" : "OUT");
 +
 +      switch (urb->dev->speed) {
 +      case USB_SPEED_HIGH:
 +              speed = "HIGH";
 +              break;
 +      case USB_SPEED_FULL:
 +              speed = "FULL";
 +              break;
 +      case USB_SPEED_LOW:
 +              speed = "LOW";
 +              break;
 +      default:
 +              speed = "UNKNOWN";
 +              break;
 +      }
 +
 +      dev_vdbg(hsotg->dev, "  Speed: %s\n", speed);
 +      dev_vdbg(hsotg->dev, "  Max packet size: %d\n",
 +               usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)));
 +      dev_vdbg(hsotg->dev, "  Data buffer length: %d\n",
 +               urb->transfer_buffer_length);
 +      dev_vdbg(hsotg->dev, "  Transfer buffer: %p, Transfer DMA: %08lx\n",
 +               urb->transfer_buffer, (unsigned long)urb->transfer_dma);
 +      dev_vdbg(hsotg->dev, "  Setup buffer: %p, Setup DMA: %08lx\n",
 +               urb->setup_packet, (unsigned long)urb->setup_dma);
 +      dev_vdbg(hsotg->dev, "  Interval: %d\n", urb->interval);
 +
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
 +              int i;
 +
 +              for (i = 0; i < urb->number_of_packets; i++) {
 +                      dev_vdbg(hsotg->dev, "  ISO Desc %d:\n", i);
 +                      dev_vdbg(hsotg->dev, "    offset: %d, length %d\n",
 +                               urb->iso_frame_desc[i].offset,
 +                               urb->iso_frame_desc[i].length);
 +              }
 +      }
 +#endif
 +}
 +
 +/*
 + * Starts processing a USB transfer request specified by a USB Request Block
 + * (URB). mem_flags indicates the type of memory allocation to use while
 + * processing this URB.
 + */
 +static int _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
 +                               gfp_t mem_flags)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      struct usb_host_endpoint *ep = urb->ep;
 +      struct dwc2_hcd_urb *dwc2_urb;
 +      int i;
 +      int retval;
 +      int alloc_bandwidth = 0;
 +      u8 ep_type = 0;
 +      u32 tflags = 0;
 +      void *buf;
 +      unsigned long flags;
 +
 +      if (dbg_urb(urb)) {
 +              dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n");
 +              dwc2_dump_urb_info(hcd, urb, "urb_enqueue");
 +      }
 +
 +      if (ep == NULL)
 +              return -EINVAL;
 +
 +      if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS ||
 +          usb_pipetype(urb->pipe) == PIPE_INTERRUPT) {
 +              spin_lock_irqsave(&hsotg->lock, flags);
 +              if (!dwc2_hcd_is_bandwidth_allocated(hsotg, ep))
 +                      alloc_bandwidth = 1;
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +      }
 +
 +      switch (usb_pipetype(urb->pipe)) {
 +      case PIPE_CONTROL:
 +              ep_type = USB_ENDPOINT_XFER_CONTROL;
 +              break;
 +      case PIPE_ISOCHRONOUS:
 +              ep_type = USB_ENDPOINT_XFER_ISOC;
 +              break;
 +      case PIPE_BULK:
 +              ep_type = USB_ENDPOINT_XFER_BULK;
 +              break;
 +      case PIPE_INTERRUPT:
 +              ep_type = USB_ENDPOINT_XFER_INT;
 +              break;
 +      default:
 +              dev_warn(hsotg->dev, "Wrong ep type\n");
 +      }
 +
 +      dwc2_urb = dwc2_hcd_urb_alloc(hsotg, urb->number_of_packets,
 +                                    mem_flags);
 +      if (!dwc2_urb)
 +              return -ENOMEM;
 +
 +      dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, usb_pipedevice(urb->pipe),
 +                                usb_pipeendpoint(urb->pipe), ep_type,
 +                                usb_pipein(urb->pipe),
 +                                usb_maxpacket(urb->dev, urb->pipe,
 +                                              !(usb_pipein(urb->pipe))));
 +
 +      buf = urb->transfer_buffer;
 +
 +      if (hcd->self.uses_dma) {
 +              if (!buf && (urb->transfer_dma & 3)) {
 +                      dev_err(hsotg->dev,
 +                              "%s: unaligned transfer with no transfer_buffer",
 +                              __func__);
 +                      retval = -EINVAL;
 +                      goto fail1;
 +              }
 +      }
 +
 +      if (!(urb->transfer_flags & URB_NO_INTERRUPT))
 +              tflags |= URB_GIVEBACK_ASAP;
 +      if (urb->transfer_flags & URB_ZERO_PACKET)
 +              tflags |= URB_SEND_ZERO_PACKET;
 +
 +      dwc2_urb->priv = urb;
 +      dwc2_urb->buf = buf;
 +      dwc2_urb->dma = urb->transfer_dma;
 +      dwc2_urb->length = urb->transfer_buffer_length;
 +      dwc2_urb->setup_packet = urb->setup_packet;
 +      dwc2_urb->setup_dma = urb->setup_dma;
 +      dwc2_urb->flags = tflags;
 +      dwc2_urb->interval = urb->interval;
 +      dwc2_urb->status = -EINPROGRESS;
 +
 +      for (i = 0; i < urb->number_of_packets; ++i)
 +              dwc2_hcd_urb_set_iso_desc_params(dwc2_urb, i,
 +                                               urb->iso_frame_desc[i].offset,
 +                                               urb->iso_frame_desc[i].length);
 +
 +      urb->hcpriv = dwc2_urb;
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +      retval = usb_hcd_link_urb_to_ep(hcd, urb);
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +      if (retval)
 +              goto fail1;
 +
 +      retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, &ep->hcpriv, mem_flags);
 +      if (retval)
 +              goto fail2;
 +
 +      if (alloc_bandwidth) {
 +              spin_lock_irqsave(&hsotg->lock, flags);
 +              dwc2_allocate_bus_bandwidth(hcd,
 +                              dwc2_hcd_get_ep_bandwidth(hsotg, ep),
 +                              urb);
 +              spin_unlock_irqrestore(&hsotg->lock, flags);
 +      }
 +
 +      return 0;
 +
 +fail2:
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +      dwc2_urb->priv = NULL;
 +      usb_hcd_unlink_urb_from_ep(hcd, urb);
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +fail1:
 +      urb->hcpriv = NULL;
 +      kfree(dwc2_urb);
 +
 +      return retval;
 +}
 +
 +/*
 + * Aborts/cancels a USB transfer request. Always returns 0 to indicate success.
 + */
 +static int _dwc2_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
 +                               int status)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      int rc;
 +      unsigned long flags;
 +
 +      dev_dbg(hsotg->dev, "DWC OTG HCD URB Dequeue\n");
 +      dwc2_dump_urb_info(hcd, urb, "urb_dequeue");
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      rc = usb_hcd_check_unlink_urb(hcd, urb, status);
 +      if (rc)
 +              goto out;
 +
 +      if (!urb->hcpriv) {
 +              dev_dbg(hsotg->dev, "## urb->hcpriv is NULL ##\n");
 +              goto out;
 +      }
 +
 +      rc = dwc2_hcd_urb_dequeue(hsotg, urb->hcpriv);
 +
 +      usb_hcd_unlink_urb_from_ep(hcd, urb);
 +
 +      kfree(urb->hcpriv);
 +      urb->hcpriv = NULL;
 +
 +      /* Higher layer software sets URB status */
 +      spin_unlock(&hsotg->lock);
 +      usb_hcd_giveback_urb(hcd, urb, status);
 +      spin_lock(&hsotg->lock);
 +
 +      dev_dbg(hsotg->dev, "Called usb_hcd_giveback_urb()\n");
 +      dev_dbg(hsotg->dev, "  urb->status = %d\n", urb->status);
 +out:
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +
 +      return rc;
 +}
 +
 +/*
 + * Frees resources in the DWC_otg controller related to a given endpoint. Also
 + * clears state in the HCD related to the endpoint. Any URBs for the endpoint
 + * must already be dequeued.
 + */
 +static void _dwc2_hcd_endpoint_disable(struct usb_hcd *hcd,
 +                                     struct usb_host_endpoint *ep)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +
 +      dev_dbg(hsotg->dev,
 +              "DWC OTG HCD EP DISABLE: bEndpointAddress=0x%02x, ep->hcpriv=%p\n",
 +              ep->desc.bEndpointAddress, ep->hcpriv);
 +      dwc2_hcd_endpoint_disable(hsotg, ep, 250);
 +}
 +
 +/*
 + * Resets endpoint specific parameter values, in current version used to reset
 + * the data toggle (as a WA). This function can be called from usb_clear_halt
 + * routine.
 + */
 +static void _dwc2_hcd_endpoint_reset(struct usb_hcd *hcd,
 +                                   struct usb_host_endpoint *ep)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      int is_control = usb_endpoint_xfer_control(&ep->desc);
 +      int is_out = usb_endpoint_dir_out(&ep->desc);
 +      int epnum = usb_endpoint_num(&ep->desc);
 +      struct usb_device *udev;
 +      unsigned long flags;
 +
 +      dev_dbg(hsotg->dev,
 +              "DWC OTG HCD EP RESET: bEndpointAddress=0x%02x\n",
 +              ep->desc.bEndpointAddress);
 +
 +      udev = to_usb_device(hsotg->dev);
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +
 +      usb_settoggle(udev, epnum, is_out, 0);
 +      if (is_control)
 +              usb_settoggle(udev, epnum, !is_out, 0);
 +      dwc2_hcd_endpoint_reset(hsotg, ep);
 +
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +}
 +
 +/*
 + * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
 + * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
 + * interrupt.
 + *
 + * This function is called by the USB core when an interrupt occurs
 + */
 +static irqreturn_t _dwc2_hcd_irq(struct usb_hcd *hcd)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +
 +      return dwc2_handle_hcd_intr(hsotg);
 +}
 +
 +/*
 + * Creates Status Change bitmap for the root hub and root port. The bitmap is
 + * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
 + * is the status change indicator for the single root port. Returns 1 if either
 + * change indicator is 1, otherwise returns 0.
 + */
 +static int _dwc2_hcd_hub_status_data(struct usb_hcd *hcd, char *buf)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +
 +      buf[0] = dwc2_hcd_is_status_changed(hsotg, 1) << 1;
 +      return buf[0] != 0;
 +}
 +
 +/* Handles hub class-specific requests */
 +static int _dwc2_hcd_hub_control(struct usb_hcd *hcd, u16 typereq, u16 wvalue,
 +                               u16 windex, char *buf, u16 wlength)
 +{
 +      int retval = dwc2_hcd_hub_control(dwc2_hcd_to_hsotg(hcd), typereq,
 +                                        wvalue, windex, buf, wlength);
 +      return retval;
 +}
 +
 +/* Handles hub TT buffer clear completions */
 +static void _dwc2_hcd_clear_tt_buffer_complete(struct usb_hcd *hcd,
 +                                             struct usb_host_endpoint *ep)
 +{
 +      struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
 +      struct dwc2_qh *qh;
 +      unsigned long flags;
 +
 +      qh = ep->hcpriv;
 +      if (!qh)
 +              return;
 +
 +      spin_lock_irqsave(&hsotg->lock, flags);
 +      qh->tt_buffer_dirty = 0;
 +
 +      if (hsotg->flags.b.port_connect_status)
 +              dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_ALL);
 +
 +      spin_unlock_irqrestore(&hsotg->lock, flags);
 +}
 +
 +static struct hc_driver dwc2_hc_driver = {
 +      .description = "dwc2_hsotg",
 +      .product_desc = "DWC OTG Controller",
 +      .hcd_priv_size = sizeof(struct wrapper_priv_data),
 +
 +      .irq = _dwc2_hcd_irq,
 +      .flags = HCD_MEMORY | HCD_USB2,
 +
 +      .start = _dwc2_hcd_start,
 +      .stop = _dwc2_hcd_stop,
 +      .urb_enqueue = _dwc2_hcd_urb_enqueue,
 +      .urb_dequeue = _dwc2_hcd_urb_dequeue,
 +      .endpoint_disable = _dwc2_hcd_endpoint_disable,
 +      .endpoint_reset = _dwc2_hcd_endpoint_reset,
 +      .get_frame_number = _dwc2_hcd_get_frame_number,
 +
 +      .hub_status_data = _dwc2_hcd_hub_status_data,
 +      .hub_control = _dwc2_hcd_hub_control,
 +      .clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete,
 +};
 +
 +/*
 + * Frees secondary storage associated with the dwc2_hsotg structure contained
 + * in the struct usb_hcd field
 + */
 +static void dwc2_hcd_free(struct dwc2_hsotg *hsotg)
 +{
 +      u32 ahbcfg;
 +      u32 dctl;
 +      int i;
 +
 +      dev_dbg(hsotg->dev, "DWC OTG HCD FREE\n");
 +
 +      /* Free memory for QH/QTD lists */
 +      dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_inactive);
 +      dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_active);
 +      dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_inactive);
 +      dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_ready);
 +      dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_assigned);
 +      dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_queued);
 +
 +      /* Free memory for the host channels */
 +      for (i = 0; i < MAX_EPS_CHANNELS; i++) {
 +              struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i];
 +
 +              if (chan != NULL) {
 +                      dev_dbg(hsotg->dev, "HCD Free channel #%i, chan=%p\n",
 +                              i, chan);
 +                      hsotg->hc_ptr_array[i] = NULL;
 +                      kfree(chan);
 +              }
 +      }
 +
 +      if (hsotg->core_params->dma_enable > 0) {
 +              if (hsotg->status_buf) {
 +                      dma_free_coherent(hsotg->dev, DWC2_HCD_STATUS_BUF_SIZE,
 +                                        hsotg->status_buf,
 +                                        hsotg->status_buf_dma);
 +                      hsotg->status_buf = NULL;
 +              }
 +      } else {
 +              kfree(hsotg->status_buf);
 +              hsotg->status_buf = NULL;
 +      }
 +
 +      ahbcfg = readl(hsotg->regs + GAHBCFG);
 +
 +      /* Disable all interrupts */
 +      ahbcfg &= ~GAHBCFG_GLBL_INTR_EN;
 +      writel(ahbcfg, hsotg->regs + GAHBCFG);
 +      writel(0, hsotg->regs + GINTMSK);
 +
 +      if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) {
 +              dctl = readl(hsotg->regs + DCTL);
 +              dctl |= DCTL_SFTDISCON;
 +              writel(dctl, hsotg->regs + DCTL);
 +      }
 +
 +      if (hsotg->wq_otg) {
 +              if (!cancel_work_sync(&hsotg->wf_otg))
 +                      flush_workqueue(hsotg->wq_otg);
 +              destroy_workqueue(hsotg->wq_otg);
 +      }
 +
 +      kfree(hsotg->core_params);
 +      hsotg->core_params = NULL;
 +      del_timer(&hsotg->wkp_timer);
 +}
 +
 +static void dwc2_hcd_release(struct dwc2_hsotg *hsotg)
 +{
 +      /* Turn off all host-specific interrupts */
 +      dwc2_disable_host_interrupts(hsotg);
 +
 +      dwc2_hcd_free(hsotg);
 +}
 +
 +/*
 + * Sets all parameters to the given value.
 + *
 + * Assumes that the dwc2_core_params struct contains only integers.
 + */
 +void dwc2_set_all_params(struct dwc2_core_params *params, int value)
 +{
 +      int *p = (int *)params;
 +      size_t size = sizeof(*params) / sizeof(*p);
 +      int i;
 +
 +      for (i = 0; i < size; i++)
 +              p[i] = value;
 +}
 +EXPORT_SYMBOL_GPL(dwc2_set_all_params);
 +
 +/*
 + * Initializes the HCD. This function allocates memory for and initializes the
 + * static parts of the usb_hcd and dwc2_hsotg structures. It also registers the
 + * USB bus with the core and calls the hc_driver->start() function. It returns
 + * a negative error on failure.
 + */
 +int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq,
 +                const struct dwc2_core_params *params)
 +{
 +      struct usb_hcd *hcd;
 +      struct dwc2_host_chan *channel;
 +      u32 hcfg;
 +      int i, num_channels;
 +      int retval;
 +
 +      dev_dbg(hsotg->dev, "DWC OTG HCD INIT\n");
 +
 +      /* Detect config values from hardware */
 +      retval = dwc2_get_hwparams(hsotg);
 +
 +      if (retval)
 +              return retval;
 +
 +      retval = -ENOMEM;
 +
 +      hcfg = readl(hsotg->regs + HCFG);
 +      dev_dbg(hsotg->dev, "hcfg=%08x\n", hcfg);
 +
 +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
 +      hsotg->frame_num_array = kzalloc(sizeof(*hsotg->frame_num_array) *
 +                                       FRAME_NUM_ARRAY_SIZE, GFP_KERNEL);
 +      if (!hsotg->frame_num_array)
 +              goto error1;
 +      hsotg->last_frame_num_array = kzalloc(
 +                      sizeof(*hsotg->last_frame_num_array) *
 +                      FRAME_NUM_ARRAY_SIZE, GFP_KERNEL);
 +      if (!hsotg->last_frame_num_array)
 +              goto error1;
 +      hsotg->last_frame_num = HFNUM_MAX_FRNUM;
 +#endif
 +
 +      hsotg->core_params = kzalloc(sizeof(*hsotg->core_params), GFP_KERNEL);
 +      if (!hsotg->core_params)
 +              goto error1;
 +
 +      dwc2_set_all_params(hsotg->core_params, -1);
 +
 +      /* Validate parameter values */
 +      dwc2_set_parameters(hsotg, params);
 +
 +      /* Check if the bus driver or platform code has setup a dma_mask */
 +      if (hsotg->core_params->dma_enable > 0 &&
 +          hsotg->dev->dma_mask == NULL) {
 +              dev_warn(hsotg->dev,
 +                       "dma_mask not set, disabling DMA\n");
 +              hsotg->core_params->dma_enable = 0;
 +              hsotg->core_params->dma_desc_enable = 0;
 +      }
 +
 +      /* Set device flags indicating whether the HCD supports DMA */
 +      if (hsotg->core_params->dma_enable > 0) {
 +              if (dma_set_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0)
 +                      dev_warn(hsotg->dev, "can't set DMA mask\n");
 +              if (dma_set_coherent_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0)
 +                      dev_warn(hsotg->dev, "can't set coherent DMA mask\n");
 +      }
 +
 +      hcd = usb_create_hcd(&dwc2_hc_driver, hsotg->dev, dev_name(hsotg->dev));
 +      if (!hcd)
 +              goto error1;
 +
 +      if (hsotg->core_params->dma_enable <= 0)
 +              hcd->self.uses_dma = 0;
 +
 +      hcd->has_tt = 1;
 +
 +      spin_lock_init(&hsotg->lock);
 +      ((struct wrapper_priv_data *) &hcd->hcd_priv)->hsotg = hsotg;
 +      hsotg->priv = hcd;
 +
 +      /*
 +       * Disable the global interrupt until all the interrupt handlers are
 +       * installed
 +       */
 +      dwc2_disable_global_interrupts(hsotg);
 +
 +      /* Initialize the DWC_otg core, and select the Phy type */
 +      retval = dwc2_core_init(hsotg, true, irq);
 +      if (retval)
 +              goto error2;
 +
 +      /* Create new workqueue and init work */
 +      retval = -ENOMEM;
 +      hsotg->wq_otg = create_singlethread_workqueue("dwc2");
 +      if (!hsotg->wq_otg) {
 +              dev_err(hsotg->dev, "Failed to create workqueue\n");
 +              goto error2;
 +      }
 +      INIT_WORK(&hsotg->wf_otg, dwc2_conn_id_status_change);
 +
 +      setup_timer(&hsotg->wkp_timer, dwc2_wakeup_detected,
 +                  (unsigned long)hsotg);
 +
 +      /* Initialize the non-periodic schedule */
 +      INIT_LIST_HEAD(&hsotg->non_periodic_sched_inactive);
 +      INIT_LIST_HEAD(&hsotg->non_periodic_sched_active);
 +
 +      /* Initialize the periodic schedule */
 +      INIT_LIST_HEAD(&hsotg->periodic_sched_inactive);
 +      INIT_LIST_HEAD(&hsotg->periodic_sched_ready);
 +      INIT_LIST_HEAD(&hsotg->periodic_sched_assigned);
 +      INIT_LIST_HEAD(&hsotg->periodic_sched_queued);
 +
 +      /*
 +       * Create a host channel descriptor for each host channel implemented
 +       * in the controller. Initialize the channel descriptor array.
 +       */
 +      INIT_LIST_HEAD(&hsotg->free_hc_list);
 +      num_channels = hsotg->core_params->host_channels;
 +      memset(&hsotg->hc_ptr_array[0], 0, sizeof(hsotg->hc_ptr_array));
 +
 +      for (i = 0; i < num_channels; i++) {
 +              channel = kzalloc(sizeof(*channel), GFP_KERNEL);
 +              if (channel == NULL)
 +                      goto error3;
 +              channel->hc_num = i;
 +              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);
 +
 +      /* Initialize port reset work */
 +      INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func);
 +
 +      /*
 +       * Allocate space for storing data on status transactions. Normally no
 +       * data is sent, but this space acts as a bit bucket. This must be
 +       * done after usb_add_hcd since that function allocates the DMA buffer
 +       * pool.
 +       */
 +      if (hsotg->core_params->dma_enable > 0)
 +              hsotg->status_buf = dma_alloc_coherent(hsotg->dev,
 +                                      DWC2_HCD_STATUS_BUF_SIZE,
 +                                      &hsotg->status_buf_dma, GFP_KERNEL);
 +      else
 +              hsotg->status_buf = kzalloc(DWC2_HCD_STATUS_BUF_SIZE,
 +                                        GFP_KERNEL);
 +
 +      if (!hsotg->status_buf)
 +              goto error3;
 +
 +      hsotg->otg_port = 1;
 +      hsotg->frame_list = NULL;
 +      hsotg->frame_list_dma = 0;
 +      hsotg->periodic_qh_count = 0;
 +
 +      /* Initiate lx_state to L3 disconnected state */
 +      hsotg->lx_state = DWC2_L3;
 +
 +      hcd->self.otg_port = hsotg->otg_port;
 +
 +      /* Don't support SG list at this point */
 +      hcd->self.sg_tablesize = 0;
 +
 +      /*
 +       * Finish generic HCD initialization and start the HCD. This function
 +       * allocates the DMA buffer pool, registers the USB bus, requests the
 +       * IRQ line, and calls hcd_start method.
 +       */
 +      retval = usb_add_hcd(hcd, irq, IRQF_SHARED);
 +      if (retval < 0)
 +              goto error3;
 +
++      device_wakeup_enable(hcd->self.controller);
++
 +      dwc2_hcd_dump_state(hsotg);
 +
 +      dwc2_enable_global_interrupts(hsotg);
 +
 +      return 0;
 +
 +error3:
 +      dwc2_hcd_release(hsotg);
 +error2:
 +      usb_put_hcd(hcd);
 +error1:
 +      kfree(hsotg->core_params);
 +
 +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
 +      kfree(hsotg->last_frame_num_array);
 +      kfree(hsotg->frame_num_array);
 +#endif
 +
 +      dev_err(hsotg->dev, "%s() FAILED, returning %d\n", __func__, retval);
 +      return retval;
 +}
 +EXPORT_SYMBOL_GPL(dwc2_hcd_init);
 +
 +/*
 + * Removes the HCD.
 + * Frees memory and resources associated with the HCD and deregisters the bus.
 + */
 +void dwc2_hcd_remove(struct dwc2_hsotg *hsotg)
 +{
 +      struct usb_hcd *hcd;
 +
 +      dev_dbg(hsotg->dev, "DWC OTG HCD REMOVE\n");
 +
 +      hcd = dwc2_hsotg_to_hcd(hsotg);
 +      dev_dbg(hsotg->dev, "hsotg->hcd = %p\n", hcd);
 +
 +      if (!hcd) {
 +              dev_dbg(hsotg->dev, "%s: dwc2_hsotg_to_hcd(hsotg) NULL!\n",
 +                      __func__);
 +              return;
 +      }
 +
 +      usb_remove_hcd(hcd);
 +      hsotg->priv = NULL;
 +      dwc2_hcd_release(hsotg);
 +      usb_put_hcd(hcd);
 +
 +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
 +      kfree(hsotg->last_frame_num_array);
 +      kfree(hsotg->frame_num_array);
 +#endif
 +}
 +EXPORT_SYMBOL_GPL(dwc2_hcd_remove);
Simple merge