[<prev] [next>] [day] [month] [year] [list]
Message-Id: <201205031232.q43CWYAl025560@amcc.com>
Date: Thu, 3 May 2012 18:02:34 +0530
From: Rupjyoti Sarmah <rsarmah@...c.com>
To: linuxppc-dev@...abs.org, linux-kernel@...r.kernel.org
Cc: rsarmah@....com
Subject: [PATCH v16 04/10]USB/ppc4xx: Add Synopsys DWC OTG HCD function
Implements DWC OTG USB Host Controller Driver (HCD) and interface to
USB Host controller Driver framework.
Signed-off-by: Rupjyoti Sarmah <rsarmah@....com>
Signed-off-by: Tirumala R Marri <tmarri@....com>
Signed-off-by: Fushen Chen <fchen@....com>
Signed-off-by: Mark Miesfeld <mmiesfeld@....com>
---
drivers/usb/dwc/hcd.c | 2435 +++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/hcd.h | 416 +++++++++
2 files changed, 2851 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/hcd.c
create mode 100644 drivers/usb/dwc/hcd.h
diff --git a/drivers/usb/dwc/hcd.c b/drivers/usb/dwc/hcd.c
new file mode 100644
index 0000000..b237957
--- /dev/null
+++ b/drivers/usb/dwc/hcd.c
@@ -0,0 +1,2435 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@....com>
+ * Modified by Stefan Roese <sr@...x.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@...ptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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 implementation of the HCD. In Linux, the HCD
+ * implements the hc_driver API.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-mapping.h>
+
+#include "hcd.h"
+
+static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";
+
+/**
+ * Clears the transfer state for a host channel. This function is normally
+ * called after a transfer is done and the host channel is being released. It
+ * clears the channel interrupt enables and any unhandled channel interrupt
+ * conditions.
+ */
+void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc)
+{
+ ulong regs;
+
+ hc->xfer_started = 0;
+ regs = core_if->host_if->hc_regs[hc->hc_num];
+ dwc_reg_write(regs, DWC_HCINTMSK, 0);
+ dwc_reg_write(regs, DWC_HCINT, 0xFFFFFFFF);
+}
+
+/**
+ * This function enables the Host mode interrupts.
+ */
+static void dwc_otg_enable_host_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Enable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * host mode.
+ *
+ * This function flushes the Tx and Rx FIFOs and it flushes any entries in the
+ * request queues. Host channels are reset to ensure that they are ready for
+ * performing transfers.
+ */
+static void dwc_otg_core_host_init(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ struct dwc_host_if *host_if = core_if->host_if;
+ struct core_params *params = core_if->core_params;
+ u32 hprt0 = 0;
+ u32 nptxfifosize = 0;
+ u32 ptxfifosize = 0;
+ u32 i;
+ u32 hcchar;
+ ulong hcfg;
+ ulong hc_regs;
+ int num_channels;
+ u32 gotgctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(core_if->pcgcctl, 0, 0);
+
+ /* Initialize Host Configuration Register */
+ init_fslspclksel(core_if);
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+ hcfg = dwc_reg_read(host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSUPP_RW(hcfg, 1);
+ dwc_reg_write(host_if->host_global_regs, DWC_HCFG, hcfg);
+ }
+
+ /* Configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2)
+ && params->enable_dynamic_fifo) {
+ /* Rx FIFO */
+ dwc_reg_write(global_regs, DWC_GRXFSIZ,
+ params->host_rx_fifo_size);
+
+ /* Non-periodic Tx FIFO */
+ nptxfifosize = DWC_TX_FIFO_DEPTH_WR(nptxfifosize,
+ params->
+ host_nperio_tx_fifo_size);
+ nptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(nptxfifosize,
+ params->host_rx_fifo_size);
+ dwc_reg_write(global_regs, DWC_GNPTXFSIZ, nptxfifosize);
+
+ /* Periodic Tx FIFO */
+ ptxfifosize = DWC_TX_FIFO_DEPTH_WR(ptxfifosize,
+ params->
+ host_perio_tx_fifo_size);
+ ptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(ptxfifosize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (nptxfifosize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (nptxfifosize)));
+ dwc_reg_write(global_regs, DWC_HPTXFSIZ, ptxfifosize);
+ }
+
+ /* Clear Host Set HNP Enable in the OTG Control Register */
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ /* Make sure the FIFOs are flushed. */
+ dwc_otg_flush_tx_fifo(core_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(core_if);
+
+ /* Flush out any leftover queued requests. */
+ num_channels = core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+ }
+
+ /* Halt all channels to put them into a known state. */
+ for (i = 0; i < num_channels; i++) {
+ int count = 0;
+
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ do {
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (++count > 200) {
+ pr_err("%s: Unable to clear halt on "
+ "channel %d\n", __func__, i);
+ break;
+ }
+ udelay(100);
+ } while (DWC_HCCHAR_ENA_RD(hcchar));
+ }
+
+ /* Turn on the vbus power. */
+ pr_info("Init: Port Power? op_state=%s\n",
+ otg_state_string(core_if->xceiv->state));
+
+ if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ pr_info("Init: Power Port (%d)\n", DWC_HPRT0_PRT_PWR_RD(hprt0));
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0) == 0) {
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(host_if->hprt0, 0, hprt0);
+ }
+ }
+ dwc_otg_enable_host_interrupts(core_if);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void hcd_reinit(struct dwc_hcd *hcd)
+{
+ struct list_head *item;
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+
+ hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+ hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+
+ /*
+ * Put all channels in the free channel list and clean up channel
+ * states.
+ */
+ item = hcd->free_hc_list.next;
+ while (item != &hcd->free_hc_list) {
+ list_del(item);
+ item = hcd->free_hc_list.next;
+ }
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ list_add_tail(&channel->hc_list_entry, &hcd->free_hc_list);
+ dwc_otg_hc_cleanup(hcd->core_if, channel);
+ }
+
+ /* Initialize the DWC core for host mode operation. */
+ dwc_otg_core_host_init(hcd->core_if);
+}
+
+/* Gets the dwc_hcd from a struct usb_hcd */
+static inline struct dwc_hcd *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
+{
+ return (struct dwc_hcd *)hcd->hcd_priv;
+}
+
+/**
+ * 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 dwc_otg_hcd_start(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct usb_bus *bus = hcd_to_bus(hcd);
+
+ hcd->state = HC_STATE_RUNNING;
+
+ /* Inform the HUB driver to resume. */
+ if (bus->root_hub)
+ usb_hcd_resume_root_hub(hcd);
+
+ hcd_reinit(dwc_hcd);
+ return 0;
+}
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The dwc_otg_hcd_start() must be called in a process context.
+ */
+
+static void hcd_start_func(struct work_struct *work)
+{
+ struct dwc_hcd *priv = container_of(work, struct dwc_hcd, start_work);
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)priv->_p;
+
+ if (usb_hcd)
+ dwc_otg_hcd_start(usb_hcd);
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ */
+static int dwc_otg_hcd_start_cb(void *_p)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = dwc_hcd->core_if;
+ u32 hprt0;
+
+ if (core_if->xceiv->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 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ ((struct usb_hcd *)_p)->self.is_b_host = 1;
+ } else {
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ }
+
+ /* Need to start the HCD in a non-interrupt context. */
+ dwc_hcd->_p = _p;
+ schedule_work(&dwc_hcd->start_work);
+ return 1;
+}
+
+/**
+ * This function disables the Host Mode interrupts.
+ */
+static void dwc_otg_disable_host_interrupts(struct core_if *core_if)
+{
+ u32 global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /*
+ * Disable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ intr_mask |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0);
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+static void dwc_otg_hcd_stop(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hprt0 = 0;
+
+ /* Turn off all host-specific interrupts. */
+ spin_lock(&dwc_hcd->lock);
+ dwc_otg_disable_host_interrupts(dwc_hcd->core_if);
+ spin_unlock(&dwc_hcd->lock);
+
+ /*
+ * 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 the vbus power */
+ pr_info("PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_hcd->core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ */
+static int dwc_otg_hcd_stop_cb(void *_p)
+{
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)_p;
+
+ dwc_otg_hcd_stop(usb_hcd);
+ return 1;
+}
+
+static void del_timers(struct dwc_hcd *hcd)
+{
+ del_timer_sync(&hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ETIMEDOUT and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(struct dwc_hcd *hcd, struct list_head *qh_list)
+{
+ struct list_head *qh_item, *q;
+
+ qh_item = qh_list->next;
+ list_for_each_safe(qh_item, q, qh_list) {
+ struct dwc_qh *qh;
+ struct list_head *qtd_item;
+ struct dwc_qtd *qtd;
+
+ qh = list_entry(qh_item, struct dwc_qh, qh_list_entry);
+ qtd_item = qh->qtd_list.next;
+ qtd = list_entry(qtd_item, struct dwc_qtd, qtd_list_entry);
+ if (qtd->urb != NULL) {
+ spin_lock(&hcd->lock);
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -ETIMEDOUT);
+ dwc_otg_hcd_qtd_remove_and_free(qtd);
+ spin_unlock(&hcd->lock);
+ }
+ }
+}
+
+/**
+ * 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.
+ */
+static void kill_all_urbs(struct dwc_hcd *hcd)
+{
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_deferred);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_disconnect_cb(void *_p)
+{
+ u32 intr;
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = hcd->core_if;
+
+ /*
+ * Shutdown any transfers in process by clearing the Tx FIFO Empty
+ * interrupt mask and status bits and disabling subsequent host
+ * channel interrupts.
+ */
+ intr = 0;
+ intr |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ intr |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr |= DWC_INTMSK_HST_CHAN;
+ spin_lock(&hcd->lock);
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr, 0);
+ dwc_reg_modify(gintsts_reg(hcd), 0, intr, 0);
+ spin_unlock(&hcd->lock);
+
+ del_timers(hcd);
+
+ /*
+ * 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 (dwc_otg_is_device_mode(core_if)) {
+ if (core_if->xceiv->state != OTG_STATE_A_SUSPEND) {
+ u32 hprt0 = 0;
+
+ pr_info("Disconnect: PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+ dwc_otg_disable_host_interrupts(core_if);
+ }
+
+ /* Respond with an error status to all URBs in the schedule. */
+ kill_all_urbs(hcd);
+ if (dwc_otg_is_host_mode(core_if)) {
+ /* Clean up any host channels that were in use. */
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ ulong regs;
+ u32 hcchar;
+
+ num_channels = core_if->core_params->host_channels;
+ if (!core_if->dma_enable) {
+ /* Flush out any channel requests in slave mode. */
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs =
+ core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hcchar =
+ DWC_HCCHAR_ENA_RW(hcchar,
+ 0);
+ hcchar =
+ DWC_HCCHAR_DIS_RW(hcchar,
+ 1);
+ hcchar =
+ DWC_HCCHAR_EPDIR_RW(hcchar,
+ 0);
+ dwc_reg_write(regs, DWC_HCCHAR,
+ hcchar);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ /* Halt the channel. */
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ dwc_reg_write(regs, DWC_HCCHAR, hcchar);
+ }
+ dwc_otg_hc_cleanup(core_if, channel);
+ list_add_tail(&channel->hc_list_entry,
+ &hcd->free_hc_list);
+ }
+ }
+ }
+
+ /*
+ * A disconnect will end the session so the B-Device is no
+ * longer a B-host.
+ */
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ return 1;
+}
+
+/**
+ * Connection timeout function. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.
+ */
+static void dwc_otg_hcd_connect_timeout(unsigned long _ptr)
+{
+ pr_info("Connect Timeout\n");
+ pr_err("Device Not Connected/Responding\n");
+}
+
+/**
+ * Start the connection timer. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds. The
+ * timer is deleted if a port connect interrupt occurs before the
+ * timer expires.
+ */
+static void dwc_otg_hcd_start_connect_timer(struct dwc_hcd *hcd)
+{
+ init_timer(&hcd->conn_timer);
+ hcd->conn_timer.function = dwc_otg_hcd_connect_timeout;
+ hcd->conn_timer.data = (unsigned long)0;
+ hcd->conn_timer.expires = jiffies + (HZ * 10);
+ add_timer(&hcd->conn_timer);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_session_start_cb(void *_p)
+{
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+
+ dwc_otg_hcd_start_connect_timer(hcd);
+ return 1;
+}
+
+/* HCD Callback structure for handling mode switching. */
+static struct cil_callbacks hcd_cil_callbacks = {
+ .start = dwc_otg_hcd_start_cb,
+ .stop = dwc_otg_hcd_stop_cb,
+ .disconnect = dwc_otg_hcd_disconnect_cb,
+ .session_start = dwc_otg_hcd_session_start_cb,
+ .p = NULL,
+};
+
+/*
+ * Reset Workqueue implementation
+ */
+static void port_reset_wqfunc(struct work_struct *work)
+{
+ struct dwc_hcd *hcd = container_of(work, struct dwc_hcd,
+ usb_port_reset);
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ pr_info("%s\n", __func__);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_wakeup_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(to_delayed_work(work),
+ struct core_if, usb_port_wakeup);
+ u32 hprt0;
+
+ pr_info("%s\n", __func__);
+ /* Now wait for 70 ms. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ msleep(70);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * 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 dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t _mem_flags)
+{
+ int retval;
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct dwc_qtd *qtd;
+
+ qtd = dwc_otg_hcd_qtd_create(urb, _mem_flags);
+ if (!qtd) {
+ pr_err("DWC OTG HCD URB Enqueue failed creating " "QTD\n");
+ retval = -ENOMEM;
+ goto err_enq;
+ }
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ retval = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (unlikely(retval))
+ goto fail;
+
+ retval = dwc_otg_hcd_qtd_add(qtd, dwc_hcd);
+ if (retval < 0) {
+ pr_err("DWC OTG HCD URB Enqueue failed adding QTD. "
+ "Error status %d\n", retval);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ goto fail;
+ }
+
+fail:
+ if (retval)
+ dwc_otg_hcd_qtd_free(qtd);
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+err_enq:
+
+ return retval;
+}
+
+/**
+ * Attempts to halt a host channel. This function should only be called in
+ * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under
+ * normal circumstances in DMA mode, the controller halts the channel when the
+ * transfer is complete or a condition occurs that requires application
+ * intervention.
+ *
+ * In slave mode, checks for a free request queue entry, then sets the Channel
+ * Enable and Channel Disable bits of the Host Channel Characteristics
+ * register of the specified channel to intiate the halt. If there is no free
+ * request queue entry, sets only the Channel Disable bit of the HCCHARn
+ * register to flush requests for this channel. In the latter case, sets a
+ * flag to indicate that the host channel needs to be halted when a request
+ * queue slot is open.
+ *
+ * In DMA mode, always sets the Channel Enable and Channel Disable bits of the
+ * HCCHARn register. The controller ensures there is space in the request
+ * queue before submitting the halt request.
+ *
+ * Some time may elapse before the core flushes any posted requests for this
+ * host channel and halts. The Channel Halted interrupt handler completes the
+ * deactivation of the host channel.
+ */
+void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status hlt_sts)
+{
+ u32 nptxsts;
+ u32 hptxsts = 0;
+ u32 hcchar;
+ ulong hc_regs;
+ ulong global_regs = core_if->core_global_regs;
+ ulong host_global_regs;
+
+ hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ host_global_regs = core_if->host_if->host_global_regs;
+
+ WARN_ON(hlt_sts == DWC_OTG_HC_XFER_NO_HALT_STATUS);
+
+ if (hlt_sts == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hlt_sts == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Disable all channel interrupts except Ch Halted. The QTD
+ * and QH state associated with this transfer has been cleared
+ * (in the case of URB_DEQUEUE), so the channel needs to be
+ * shut down carefully to prevent crashes.
+ */
+ u32 hcintmsk;
+ hcintmsk = 0;
+ hcintmsk = DWC_HCINTMSK_CHAN_HALTED_RW(hcintmsk, 1);
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hcintmsk);
+
+ /*
+ * Make sure no other interrupts besides halt are currently
+ * pending. Handling another interrupt could cause a crash due
+ * to the QTD and QH state.
+ */
+ dwc_reg_write(hc_regs, DWC_HCINT, ~hcintmsk);
+
+ /*
+ * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR
+ * even if the channel was already halted for some other reason.
+ */
+ hc->halt_status = hlt_sts;
+
+ /*
+ * If the channel is not enabled, the channel is either already
+ * halted or it hasn't started yet. In DMA mode, the transfer
+ * may halt if it finishes normally or a condition occurs that
+ * requires driver intervention. Don't want to halt the channel
+ * again. In either Slave or DMA mode, it's possible that the
+ * transfer has been assigned to a channel, but not started yet
+ * when an URB is dequeued. Don't want to halt a channel that
+ * hasn't started yet.
+ */
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (!DWC_HCCHAR_ENA_RD(hcchar))
+ return;
+ }
+
+ if (hc->halt_pending)
+ /*
+ * A halt has already been issued for this channel. This might
+ * happen when a transfer is aborted by a higher level in
+ * the stack.
+ */
+ return;
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ if (!core_if->dma_enable) {
+ /* Check for space in the request queue to issue the halt. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ nptxsts = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ if (!DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(nptxsts))
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ } else {
+ hptxsts =
+ dwc_reg_read(host_global_regs, DWC_HPTXSTS);
+
+ if (!DWC_HPTXSTS_PTXSPC_AVAIL_RD(hptxsts) ||
+ core_if->queuing_high_bandwidth)
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->halt_status = hlt_sts;
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hc->halt_pending = 1;
+ hc->halt_on_queue = 0;
+ } else {
+ hc->halt_on_queue = 1;
+ }
+}
+
+/**
+ * Aborts/cancels a USB transfer request. Always returns 0 to indicate
+ * success.
+ */
+static int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_qtd *urb_qtd;
+ struct dwc_qh *qh;
+ int retval;
+
+ urb_qtd = (struct dwc_qtd *)urb->hcpriv;
+ if (!urb_qtd)
+ return -EINVAL;
+ qh = (struct dwc_qh *)urb_qtd->qtd_qh_ptr;
+ if (!qh)
+ return -EINVAL;
+
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ retval = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (retval) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+ }
+
+ if (urb_qtd == qh->qtd_in_process) {
+ /*
+ * 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.
+ */
+ dwc_otg_hc_halt(dwc_hcd->core_if, qh->channel,
+ DWC_OTG_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.
+ */
+ dwc_otg_hcd_qtd_remove_and_free(urb_qtd);
+ if (qh && urb_qtd == qh->qtd_in_process) {
+ dwc_otg_hcd_qh_deactivate(dwc_hcd, qh, 0);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ } else if (qh && list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(dwc_hcd, qh);
+ }
+
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+
+ /* Higher layer software sets URB status. */
+ usb_hcd_giveback_urb(hcd, urb, status);
+
+ return 0;
+}
+
+/* Remove and free a QH */
+static inline void dwc_otg_hcd_qh_remove_and_free(struct dwc_hcd *hcd,
+ struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ dwc_otg_hcd_qh_free(qh);
+}
+
+static void qh_list_free(struct dwc_hcd *hcd, struct list_head *_qh_list)
+{
+ struct list_head *item, *tmp;
+ struct dwc_qh *qh;
+
+ /* If the list hasn't been initialized yet, return. */
+ if (_qh_list->next == NULL)
+ return;
+
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, _qh_list);
+
+ list_for_each_safe(item, tmp, _qh_list) {
+ qh = list_entry(item, struct dwc_qh, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
+}
+
+/**
+ * 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 dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct dwc_qh *qh;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (qh) {
+ dwc_otg_hcd_qh_remove_and_free(dwc_hcd, qh);
+ ep->hcpriv = NULL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+}
+
+/**
+ * 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 dwc_otg_hcd_hub_status_data(struct dwc_hcd *hcd, char *buf)
+{
+ u32 hprt0;
+ hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ buf[0] = 0;
+ buf[0] |= (DWC_HPRT0_PRT_STS_RD(hprt0)\
+ || DWC_HPRT0_PRT_RES_RD(hprt0)\
+ || DWC_HPRT0_PRT_ENA_RD(hprt0)\
+ || DWC_HPRT0_PRT_SUS_RD(hprt0)\
+ || DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0));
+
+ return (buf[0] != 0);
+}
+
+/* Handles the hub class-specific ClearPortFeature request.*/
+static int do_clear_port_feature(struct dwc_hcd *hcd, u16 val)
+{
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hcd->lock, flags);
+ switch (val) {
+ case USB_PORT_FEAT_ENABLE:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Clear Resume bit */
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(100);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Port inidicator not supported */
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ /* Clears drivers internal connect status change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_STS_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* Clears driver's internal Port Reset Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ /* Clears driver's internal Port Enable/Disable Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ 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
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SPD_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ default:
+ pr_err("DWC OTG HCD - ClearPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return 0;
+}
+
+/* Handles the hub class-specific SetPortFeature request.*/
+static int do_set_port_feature(struct usb_hcd *hcd, u16 val, u16 index)
+{
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ u32 hprt0 = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+ u32 pcgcctl = 0;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ switch (val) {
+ case USB_PORT_FEAT_SUSPEND:
+ if (hcd->self.otg_port == index && hcd->self.b_hnp_enable) {
+ u32 gotgctl = 0;
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(core_if->core_global_regs,
+ DWC_GOTGCTL, 0, gotgctl);
+ core_if->xceiv->state = OTG_STATE_A_SUSPEND;
+ }
+
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SUS_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Suspend the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_write(core_if->pcgcctl, 0, pcgcctl);
+
+ /* For HNP the bus must be suspended for at least 200ms. */
+ if (hcd->self.b_hnp_enable) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(200);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ }
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_RESET:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+
+ /*
+ * 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 (!hcd->self.is_b_host) {
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+
+ /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Not supported */
+ break;
+ default:
+ pr_err("DWC OTG HCD - "
+ "SetPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return 0;
+}
+
+/* Handles hub class-specific requests.*/
+static int dwc_otg_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 val,
+ u16 index, char *buf, u16 len)
+{
+ int retval = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ struct usb_hub_descriptor *desc;
+ u32 hprt0 = 0;
+ u32 port_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ switch (req_type) {
+ case ClearHubFeature:
+ switch (val) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* Nothing required here */
+ break;
+ default:
+ retval = -EINVAL;
+ pr_err("DWC OTG HCD - ClearHubFeature request"
+ " %xh unknown\n", val);
+ }
+ break;
+ case ClearPortFeature:
+ if (!index || index > 1)
+ goto error;
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_clear_port_feature(dwc_hcd, val);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ case GetHubDescriptor:
+ desc = (struct usb_hub_descriptor *)buf;
+ desc->bDescLength = 9;
+ desc->bDescriptorType = 0x29;
+ desc->bNbrPorts = 1;
+ desc->wHubCharacteristics = 0x08;
+ desc->bPwrOn2PwrGood = 1;
+ desc->bHubContrCurrent = 0;
+ break;
+ case GetHubStatus:
+ memset(buf, 0, 4);
+ break;
+ case GetPortStatus:
+ if (!index || index > 1)
+ goto error;
+
+ port_status = 0;
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+
+ if (DWC_HPRT0_PRT_STS_RD(hprt0))
+ port_status |= USB_PORT_STAT_CONNECTION;
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_status |= USB_PORT_STAT_ENABLE;
+ if (DWC_HPRT0_PRT_SUS_RD(hprt0))
+ port_status |= USB_PORT_STAT_SUSPEND;
+ if (DWC_HPRT0_PRT_OVRCURR_ACT_RD(hprt0))
+ port_status |= USB_PORT_STAT_OVERCURRENT;
+ if (DWC_HPRT0_PRT_RST_RD(hprt0))
+ port_status |= USB_PORT_STAT_RESET;
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0))
+ port_status |= USB_PORT_STAT_POWER;
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+ port_status |= USB_PORT_STAT_HIGH_SPEED;
+ else if (DWC_HPRT0_PRT_SPD_RD(hprt0) ==
+ DWC_HPRT0_PRTSPD_LOW_SPEED)
+ port_status |= USB_PORT_STAT_LOW_SPEED;
+
+ if (DWC_HPRT0_PRT_TST_CTL_RD(hprt0))
+ port_status |= (1 << USB_PORT_FEAT_TEST);
+
+ /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+ *((__le32 *) buf) = cpu_to_le32(port_status);
+ break;
+ case SetHubFeature:
+ /* No HUB features supported */
+ break;
+ case SetPortFeature:
+ if (val != USB_PORT_FEAT_TEST && (!index || index > 1))
+ goto error;
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_set_port_feature(hcd, val, index);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ default:
+error:
+ retval = -EINVAL;
+ pr_warning("DWC OTG HCD - Unknown hub control request"
+ " type or invalid req_type: %xh index: %xh "
+ "val: %xh\n", req_type, index, val);
+ break;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+}
+
+/**
+ * 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 dwc_otg_hcd_irq(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+ return IRQ_RETVAL(dwc_otg_hcd_handle_intr(dwc_hcd));
+}
+
+static const struct hc_driver dwc_otg_hc_driver = {
+ .description = dwc_otg_hcd_name,
+ .product_desc = "DWC OTG Controller",
+ .hcd_priv_size = sizeof(struct dwc_hcd),
+ .irq = dwc_otg_hcd_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+ .start = dwc_otg_hcd_start,
+ .stop = dwc_otg_hcd_stop,
+ .urb_enqueue = dwc_otg_hcd_urb_enqueue,
+ .urb_dequeue = dwc_otg_hcd_urb_dequeue,
+ .endpoint_disable = dwc_otg_hcd_endpoint_disable,
+ .get_frame_number = dwc_otg_hcd_get_frame_number,
+ .hub_status_data = dwc_otg_hcd_hub_status_data,
+ .hub_control = dwc_otg_hcd_hub_control,
+};
+
+/**
+ * Frees secondary storage associated with the dwc_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 i;
+
+ del_timers(dwc_hcd);
+
+ /* Free memory for QH/QTD lists */
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_deferred);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_active);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_ready);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_assigned);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_queued);
+
+ /* Free memory for the host channels. */
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ struct dwc_hc *hc = dwc_hcd->hc_ptr_array[i];
+
+ kfree(hc);
+ }
+ if (dwc_hcd->core_if->dma_enable) {
+ if (dwc_hcd->status_buf_dma)
+ dma_free_coherent(hcd->self.controller,
+ DWC_OTG_HCD_STATUS_BUF_SIZE,
+ dwc_hcd->status_buf,
+ dwc_hcd->status_buf_dma);
+ } else {
+ kfree(dwc_hcd->status_buf);
+ }
+
+}
+
+/**
+ * Initializes the HCD. This function allocates memory for and initializes the
+ * static parts of the usb_hcd and dwc_hcd 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 __devinit dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_otg_device)
+{
+ struct usb_hcd *hcd;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ int retval = 0;
+
+ /*
+ * Allocate memory for the base HCD plus the DWC OTG HCD.
+ * Initialize the base HCD.
+ */
+ hcd = usb_create_hcd(&dwc_otg_hc_driver, _dev, dwc_otg_hcd_name);
+ if (!hcd) {
+ retval = -ENOMEM;
+ goto error1;
+ }
+ dev_set_drvdata(_dev, dwc_otg_device);
+ hcd->regs = otg_dev->base;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+ hcd->self.otg_port = 1;
+
+ /* Initialize the DWC OTG HCD. */
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ dwc_hcd->core_if = otg_dev->core_if;
+ spin_lock_init(&dwc_hcd->lock);
+ otg_dev->hcd = dwc_hcd;
+
+ /* Register the HCD CIL Callbacks */
+ dwc_otg_cil_register_hcd_callbacks(otg_dev->core_if, &hcd_cil_callbacks,
+ hcd);
+
+ /* Initialize the non-periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_active);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_deferred);
+
+ /* Initialize the periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_ready);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_assigned);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_queued);
+
+ /*
+ * Create a host channel descriptor for each host channel implemented
+ * in the controller. Initialize the channel descriptor array.
+ */
+ INIT_LIST_HEAD(&dwc_hcd->free_hc_list);
+ num_channels = dwc_hcd->core_if->core_params->host_channels;
+
+ for (i = 0; i < num_channels; i++) {
+ channel = kzalloc(sizeof(struct dwc_hc), GFP_KERNEL);
+ if (!channel) {
+ retval = -ENOMEM;
+ pr_err("%s: host channel allocation failed\n",
+ __func__);
+ goto error2;
+ }
+
+ channel->hc_num = i;
+ dwc_hcd->hc_ptr_array[i] = channel;
+ }
+
+ /* Initialize the Connection timeout timer. */
+ init_timer(&dwc_hcd->conn_timer);
+
+ /* Initialize workqueue */
+ INIT_WORK(&dwc_hcd->usb_port_reset, port_reset_wqfunc);
+ INIT_WORK(&dwc_hcd->start_work, hcd_start_func);
+ INIT_WORK(&dwc_hcd->core_if->usb_port_otg, NULL);
+ INIT_DELAYED_WORK(&dwc_hcd->core_if->usb_port_wakeup,
+ port_wakeup_wqfunc);
+
+ /* Set device flags indicating whether the HCD supports DMA. */
+ if (otg_dev->core_if->dma_enable) {
+ static u64 dummy_mask = DMA_BIT_MASK(32);
+
+ pr_info("Using DMA mode\n");
+ _dev->dma_mask = (void *)&dummy_mask;
+ _dev->coherent_dma_mask = ~0;
+ } else {
+ pr_info("Using Slave mode\n");
+ _dev->dma_mask = (void *)0;
+ _dev->coherent_dma_mask = 0;
+ }
+
+ init_hcd_usecs(dwc_hcd);
+ /*
+ * 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 dwc_otg_hcd_start method.
+ */
+ retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED);
+ if (retval < 0)
+ goto error2;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+
+ /*
+ * 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 (otg_dev->core_if->dma_enable) {
+ dwc_hcd->status_buf =
+ dma_alloc_coherent(_dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ &dwc_hcd->status_buf_dma,
+ GFP_KERNEL | GFP_DMA);
+ } else {
+ dwc_hcd->status_buf = kmalloc(DWC_OTG_HCD_STATUS_BUF_SIZE,
+ GFP_KERNEL);
+ }
+ if (!dwc_hcd->status_buf) {
+ retval = -ENOMEM;
+ pr_err("%s: status_buf allocation failed\n", __func__);
+ goto error3;
+ }
+ return 0;
+
+error3:
+ usb_remove_hcd(hcd);
+error2:
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+error1:
+ return retval;
+}
+
+/**
+ * Removes the HCD.
+ * Frees memory and resources associated with the HCD and deregisters the bus.
+ */
+void __devexit dwc_otg_hcd_remove(struct device *_dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ struct dwc_hcd *dwc_hcd = otg_dev->hcd;
+ struct usb_hcd *hcd = dwc_otg_hcd_to_hcd(dwc_hcd);
+
+ /* Turn off all interrupts */
+ dwc_reg_write(gintmsk_reg(dwc_hcd), 0, 0);
+ spin_lock(&dwc_hcd->lock);
+ dwc_reg_modify(gahbcfg_reg(dwc_hcd), 0, 1, 0);
+ spin_unlock(&dwc_hcd->lock);
+
+ cancel_work_sync(&dwc_hcd->start_work);
+ cancel_work_sync(&dwc_hcd->usb_port_reset);
+ cancel_work_sync(&dwc_hcd->core_if->usb_port_otg);
+
+ usb_remove_hcd(hcd);
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+}
+
+/** Returns the current frame number. */
+int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(dwc_hcd->core_if->host_if->
+ host_global_regs, DWC_HFNUM);
+
+ return DWC_HFNUM_FRNUM_RD(hfnum);
+}
+
+/**
+ * Prepares a host channel for transferring packets to/from a specific
+ * endpoint. The HCCHARn register is set up with the characteristics specified
+ * in _hc. Host channel interrupts that may need to be serviced while this
+ * transfer is in progress are enabled.
+ */
+static void dwc_otg_hc_init(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 intr_enable;
+ ulong global_regs = core_if->core_global_regs;
+ u32 hc_intr_mask = 0;
+ u32 gintmsk = 0;
+ u32 hcchar;
+ u32 hcsplt;
+ u8 hc_num = hc->hc_num;
+ struct dwc_host_if *host_if = core_if->host_if;
+ ulong hc_regs = host_if->hc_regs[hc_num];
+
+ /* Clear old interrupt conditions for this host channel. */
+ hc_intr_mask = 0x3FF;
+ dwc_reg_write(hc_regs, DWC_HCINT, hc_intr_mask);
+
+ /* Enable channel interrupts required for this transfer. */
+ hc_intr_mask = 0;
+ hc_intr_mask = DWC_HCINTMSK_CHAN_HALTED_RW(hc_intr_mask, 1);
+ if (core_if->dma_enable) {
+ hc_intr_mask = DWC_HCINTMSK_AHB_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->error_state && !hc->do_split &&
+ hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask,
+ 1);
+ if (hc->ep_type != DWC_OTG_EP_TYPE_INTR)
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ }
+ } else {
+ switch (hc->ep_type) {
+ case DWC_OTG_EP_TYPE_CONTROL:
+ case DWC_OTG_EP_TYPE_BULK:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ } else {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->do_ping)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->do_split) {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ break;
+ case DWC_OTG_EP_TYPE_INTR:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in)
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+
+ if (hc->do_split) {
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ break;
+ case DWC_OTG_EP_TYPE_ISOC:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ }
+ break;
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hc_intr_mask);
+
+ /* Enable the top level host channel interrupt. */
+ intr_enable = (1 << hc_num);
+ dwc_reg_modify(host_if->host_global_regs, DWC_HAINTMSK, 0,
+ intr_enable);
+
+ /* Make sure host channel interrupts are enabled. */
+ gintmsk |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmsk);
+
+ /*
+ * Program the HCCHARn register with the endpoint characteristics for
+ * the current transfer.
+ */
+ hcchar = 0;
+ hcchar = DWC_HCCHAR_DEV_ADDR_RW(hcchar, hc->dev_addr);
+ hcchar = DWC_HCCHAR_EP_NUM_RW(hcchar, hc->ep_num);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, hc->ep_is_in);
+ hcchar = DWC_HCCHAR_LSP_DEV_RW(hcchar, (hc->speed ==
+ DWC_OTG_EP_SPEED_LOW));
+ hcchar = DWC_HCCHAR_EPTYPE_RW(hcchar, hc->ep_type);
+ hcchar = DWC_HCCHAR_MPS_RW(hcchar, hc->max_packet);
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCCHAR, hcchar);
+
+ /* Program the HCSPLIT register for SPLITs */
+ hcsplt = 0;
+ if (hc->do_split) {
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, hc->complete_split);
+ hcsplt = DWC_HCSPLT_TRANS_POS_RW(hcsplt, hc->xact_pos);
+ hcsplt = DWC_HCSPLT_HUB_ADDR_RW(hcsplt, hc->hub_addr);
+ hcsplt = DWC_HCSPLT_PRT_ADDR_RW(hcsplt, hc->port_addr);
+ }
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCSPLT, hcsplt);
+}
+
+/**
+ * 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.
+ */
+static void assign_and_init_hc(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct dwc_hc *hc;
+ struct dwc_qtd *qtd;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ hc = list_entry(hcd->free_hc_list.next, struct dwc_hc, hc_list_entry);
+
+ /* Remove the host channel from the free list. */
+ list_del_init(&hc->hc_list_entry);
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ urb = qtd->urb;
+ qh->channel = hc;
+ qh->qtd_in_process = qtd;
+
+ /*
+ * Use usb_pipedevice to determine device address. This address is
+ * 0 before the SET_ADDRESS command and the correct address afterward.
+ */
+ hc->dev_addr = usb_pipedevice(urb->pipe);
+ hc->ep_num = usb_pipeendpoint(urb->pipe);
+
+ if (urb->dev->speed == USB_SPEED_LOW)
+ hc->speed = DWC_OTG_EP_SPEED_LOW;
+ else if (urb->dev->speed == USB_SPEED_FULL)
+ hc->speed = DWC_OTG_EP_SPEED_FULL;
+ else
+ hc->speed = DWC_OTG_EP_SPEED_HIGH;
+
+ hc->max_packet = dwc_max_packet(qh->maxp);
+ hc->xfer_started = 0;
+ hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS;
+ hc->error_state = (qtd->error_count > 0);
+ hc->halt_on_queue = 0;
+ hc->halt_pending = 0;
+ hc->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.
+ */
+ hc->do_ping = qh->ping_state;
+ hc->ep_is_in = (usb_pipein(urb->pipe) != 0);
+ hc->data_pid_start = qh->data_toggle;
+ hc->multi_count = 1;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = urb->transfer_dma + (u8 *) urb->actual_length;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer +
+ urb->actual_length;
+
+ hc->xfer_len = urb->transfer_buffer_length - urb->actual_length;
+ hc->xfer_count = 0;
+
+ /*
+ * Set the split attributes
+ */
+ hc->do_split = 0;
+ if (qh->do_split) {
+ hc->do_split = 1;
+ hc->xact_pos = qtd->isoc_split_pos;
+ hc->complete_split = qtd->complete_split;
+ hc->hub_addr = urb->dev->tt->hub->devnum;
+ hc->port_addr = urb->dev->ttport;
+ }
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ hc->do_ping = 0;
+ hc->ep_is_in = 0;
+ hc->data_pid_start = DWC_OTG_HC_PID_SETUP;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->setup_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->setup_packet;
+
+ hc->xfer_len = 8;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ hc->data_pid_start = qtd->data_toggle;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ /*
+ * Direction is opposite of data direction or IN if no
+ * data.
+ */
+ if (urb->transfer_buffer_length == 0)
+ hc->ep_is_in = 1;
+ else
+ hc->ep_is_in = (usb_pipein(urb->pipe) !=
+ USB_DIR_IN);
+
+ if (hc->ep_is_in)
+ hc->do_ping = 0;
+
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ hc->xfer_len = 0;
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff =
+ (u8 *) (u32) hcd->status_buf_dma;
+ else
+ hc->xfer_buff = (u8 *) hcd->status_buf;
+ break;
+ }
+ break;
+ case PIPE_BULK:
+ hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+ break;
+ case PIPE_INTERRUPT:
+ hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+ break;
+ case PIPE_ISOCHRONOUS:
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+ hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->transfer_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer;
+
+ hc->xfer_buff += frame_desc->offset + qtd->isoc_split_offset;
+ hc->xfer_len = frame_desc->length - qtd->isoc_split_offset;
+
+ if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ if (hc->xfer_len <= 188)
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ else
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_BEGIN;
+ }
+ break;
+ }
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * This value may be modified when the transfer is started to
+ * reflect the actual transfer length.
+ */
+ hc->multi_count = dwc_hb_mult(qh->maxp);
+
+ dwc_otg_hc_init(hcd->core_if, hc);
+ hc->qh = qh;
+}
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ */
+enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd *hcd)
+{
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int num_channels;
+ enum dwc_transaction_type ret_val = DWC_OTG_TRANSACTION_NONE;
+
+ /* Process entries in the periodic ready list. */
+ num_channels = hcd->core_if->core_params->host_channels;
+ qh_ptr = hcd->periodic_sched_ready.next;
+ while (qh_ptr != &hcd->periodic_sched_ready &&
+ !list_empty(&hcd->free_hc_list)) {
+ /* Leave one channel for non periodic transactions. */
+ if (hcd->available_host_channels <= 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the periodic ready schedule to the
+ * periodic assigned schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->periodic_sched_assigned);
+ ret_val = DWC_OTG_TRANSACTION_PERIODIC;
+ }
+
+ /*
+ * Process entries in the deferred portion of the non-periodic list.
+ * A NAK put them here and, at the right time, they need to be
+ * placed on the sched_inactive list.
+ */
+ qh_ptr = hcd->non_periodic_sched_deferred.next;
+ while (qh_ptr != &hcd->non_periodic_sched_deferred) {
+ u16 frame_number =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ qh_ptr = qh_ptr->next;
+
+ if (dwc_frame_num_le(qh->sched_frame, frame_number))
+ /*
+ * Move the QH from the non periodic deferred schedule
+ * to the non periodic inactive schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ }
+
+ /*
+ * 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.
+ */
+ qh_ptr = hcd->non_periodic_sched_inactive.next;
+ num_channels = hcd->core_if->core_params->host_channels;
+
+ while (qh_ptr != &hcd->non_periodic_sched_inactive
+ && !list_empty(&hcd->free_hc_list)) {
+ if (hcd->available_host_channels < 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * 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, &hcd->non_periodic_sched_active);
+ if (ret_val == DWC_OTG_TRANSACTION_NONE)
+ ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
+ else
+ ret_val = DWC_OTG_TRANSACTION_ALL;
+
+ }
+ return ret_val;
+}
+
+/**
+ * Sets the channel property that indicates in which frame a periodic transfer
+ * should occur. This is always set to the _next_ frame. This function has no
+ * effect on non-periodic transfers.
+ */
+static inline void hc_set_even_odd_frame(struct core_if *core_if,
+ struct dwc_hc *hc, u32 * hcchar)
+{
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ /* 1 if _next_ frame is odd, 0 if it's even */
+ *hcchar = DWC_HCCHAR_ODD_FRAME_RW(*hcchar,
+ ((DWC_HFNUM_FRNUM_RD(hfnum) &
+ 0x1) ? 0 : 1));
+ }
+}
+
+static void set_initial_xfer_pid(struct dwc_hc *hc)
+{
+ if (hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+ if (hc->ep_is_in) {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else if (hc->multi_count == 2)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA2;
+ } else {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_MDATA;
+ }
+ } else {
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ }
+}
+
+/**
+ * Starts a PING transfer. This function should only be called in Slave mode.
+ * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled.
+ */
+static void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hctsiz = 0;
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, 1);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+static void dwc_otg_hc_write_packet(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 i;
+ u32 remaining_count;
+ u32 byte_count;
+ u32 dword_count;
+ u32 *data_buff = (u32 *) (hc->xfer_buff);
+ u32 data_fifo = core_if->data_fifo[hc->hc_num];
+
+ remaining_count = hc->xfer_len - hc->xfer_count;
+ if (remaining_count > hc->max_packet)
+ byte_count = hc->max_packet;
+ else
+ byte_count = remaining_count;
+
+ dword_count = (byte_count + 3) / 4;
+
+ if (((unsigned long)data_buff) & 0x3)
+ /* xfer_buff is not DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo,
+ get_unaligned(data_buff));
+ else
+ /* xfer_buff is DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo, *data_buff);
+
+ hc->xfer_count += byte_count;
+ hc->xfer_buff += byte_count;
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel and
+ * starts the transfer. May be called in either Slave mode or DMA mode. In
+ * Slave mode, the caller must ensure that there is sufficient space in the
+ * request queue and Tx Data FIFO.
+ *
+ * For an OUT transfer in Slave mode, it loads a data packet into the
+ * appropriate FIFO. If necessary, additional data packets will be loaded in
+ * the Host ISR.
+ *
+ * For an IN transfer in Slave mode, a data packet is requested. The data
+ * packets are unloaded from the Rx FIFO in the Host ISR. If necessary,
+ * additional data packets are requested in the Host ISR.
+ *
+ * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ
+ * register along with a packet count of 1 and the channel is enabled. This
+ * causes a single PING transaction to occur. Other fields in HCTSIZ are
+ * simply set to 0 since no data transfer occurs in this case.
+ *
+ * For a PING transfer in DMA mode, the HCTSIZ register is initialized with
+ * all the information required to perform the subsequent data transfer. In
+ * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the
+ * controller performs the entire PING protocol, then starts the data
+ * transfer.
+ */
+static void dwc_otg_hc_start_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+ u16 num_packets;
+ u32 max_hc_xfer_size = core_if->core_params->max_transfer_size;
+ u16 max_hc_pkt_count = core_if->core_params->max_packet_count;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ hctsiz = 0;
+
+ if (hc->do_ping) {
+ if (!core_if->dma_enable) {
+ dwc_otg_hc_do_ping(core_if, hc);
+ hc->xfer_started = 1;
+ return;
+ } else {
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ }
+ }
+
+ if (hc->do_split) {
+ num_packets = 1;
+
+ if (hc->complete_split && !hc->ep_is_in)
+ /*
+ * For CSPLIT OUT Transfer, set the size to 0 so the
+ * core doesn't expect any data written to the FIFO
+ */
+ hc->xfer_len = 0;
+ else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet))
+ hc->xfer_len = hc->max_packet;
+ else if (!hc->ep_is_in && (hc->xfer_len > 188))
+ hc->xfer_len = 188;
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ } else {
+ /*
+ * Ensure that the transfer length and packet count will fit
+ * in the widths allocated for them in the HCTSIZn register.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 max_len = hc->multi_count * hc->max_packet;
+
+ /*
+ * Make sure the transfer size is no larger than one
+ * (micro)frame's worth of data. (A check was done
+ * when the periodic transfer was accepted to ensure
+ * that a (micro)frame's worth of data can be
+ * programmed into a channel.)
+ */
+ if (hc->xfer_len > max_len)
+ hc->xfer_len = max_len;
+ } else if (hc->xfer_len > max_hc_xfer_size) {
+ /*
+ * Make sure that xfer_len is a multiple of max packet
+ * size.
+ */
+ hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1;
+ }
+ if (hc->xfer_len > 0) {
+ num_packets = (hc->xfer_len + hc->max_packet - 1) /
+ hc->max_packet;
+ if (num_packets > max_hc_pkt_count) {
+ num_packets = max_hc_pkt_count;
+ hc->xfer_len = num_packets * hc->max_packet;
+ }
+ } else {
+ /* Need 1 packet for transfer length of 0. */
+ num_packets = 1;
+ }
+
+ if (hc->ep_is_in)
+ /*
+ * Always program an integral # of max packets for IN
+ * transfers.
+ */
+ hc->xfer_len = num_packets * hc->max_packet;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * Make sure that the multi_count field matches the
+ * actual transfer length.
+ */
+ hc->multi_count = num_packets;
+
+ /* Set up the initial PID for the transfer. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ set_initial_xfer_pid(hc);
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ }
+
+ hc->start_pkt_count = num_packets;
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, num_packets);
+ hctsiz = DWC_HCTSIZ_PKT_PID_RW(hctsiz, hc->data_pid_start);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ if (core_if->dma_enable)
+ dwc_reg_write(hc_regs, DWC_HCDMA, (u32) hc->xfer_buff);
+
+ /* Start the split */
+ if (hc->do_split) {
+ u32 hcsplt;
+
+ hcsplt = dwc_reg_read(hc_regs, DWC_HCSPLT);
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, 1);
+ dwc_reg_write(hc_regs, DWC_HCSPLT, hcsplt);
+ }
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_MULTI_CNT_RW(hcchar, hc->multi_count);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ /* Set host channel enable after all other setup is complete. */
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->xfer_started = 1;
+ hc->requests++;
+ if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0)
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+}
+
+/**
+ * This function continues a data transfer that was started by previous call
+ * to dwc_otg_hc_start_transfer</code>. The caller must ensure there is
+ * sufficient space in the request queue and Tx Data FIFO. This function
+ * should only be called in Slave mode. In DMA mode, the controller acts
+ * autonomously to complete transfers programmed to a host channel.
+ *
+ * For an OUT transfer, a new data packet is loaded into the appropriate FIFO
+ * if there is any data remaining to be queued. For an IN transfer, another
+ * data packet is always requested. For the SETUP phase of a control transfer,
+ * this function does nothing.
+ */
+static int dwc_otg_hc_continue_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ if (hc->do_split) {
+ /* SPLITs always queue just once per channel */
+ return 0;
+ } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+ /* SETUPs are queued only once since they can't be NAKed. */
+ return 0;
+ } else if (hc->ep_is_in) {
+ /*
+ * Always queue another request for other IN transfers. If
+ * back-to-back INs are issued and NAKs are received for both,
+ * the driver may still be processing the first NAK when the
+ * second NAK is received. When the interrupt handler clears
+ * the NAK interrupt for the first NAK, the second NAK will
+ * not be seen. So we can't depend on the NAK interrupt
+ * handler to requeue a NAKed request. Instead, IN requests
+ * are issued each time this function is called. When the
+ * transfer completes, the extra requests for the channel will
+ * be flushed.
+ */
+ u32 hcchar;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->requests++;
+ return 1;
+ } else {
+ /* OUT transfers. */
+ if (hc->xfer_count < hc->xfer_len) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hcchar;
+ u32 hc_regs;
+
+ hc_regs =
+ core_if->host_if->hc_regs[hc->hc_num];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+ }
+
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+ hc->requests++;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+
+/**
+ * Attempts to queue a single transaction request for a host channel
+ * associated with either a periodic or non-periodic transfer. 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.
+ */
+static int queue_transaction(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u16 _fifo_dwords_avail)
+{
+ int retval;
+
+ if (hcd->core_if->dma_enable) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ hc->qh->ping_state = 0;
+ }
+ retval = 0;
+ } else if (hc->halt_pending) {
+ /* Don't queue a request if the channel has been halted. */
+ retval = 0;
+ } else if (hc->halt_on_queue) {
+ dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+ retval = 0;
+ } else if (hc->do_ping) {
+ if (!hc->xfer_started)
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 0;
+ } else if (!hc->ep_is_in || hc->data_pid_start ==
+ DWC_OTG_HC_PID_SETUP) {
+ if ((_fifo_dwords_avail * 4) >= hc->max_packet) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval =
+ dwc_otg_hc_continue_transfer(hcd->core_if,
+ hc);
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc);
+ }
+ }
+ return retval;
+}
+
+/**
+ * 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.
+ */
+static void process_non_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *orig_qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ int more_to_do = 0;
+ ulong regs = hcd->core_if->core_global_regs;
+
+ /*
+ * Keep track of the starting point. Skip over the start-of-list
+ * entry.
+ */
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ orig_qh_ptr = hcd->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 = dwc_reg_read(regs, DWC_GNPTXSTS);
+ if (!hcd->core_if->dma_enable &&
+ DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(hcd->non_periodic_qh_ptr, struct dwc_qh,
+ qh_list_entry);
+ status = queue_transaction(hcd, qh->channel,
+ DWC_GNPTXSTS_NPTXFSPCAVAIL_RD
+ (tx_status));
+
+ if (status > 0) {
+ more_to_do = 1;
+ } else if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /* Advance to next QH, skipping start-of-list entry. */
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ } while (hcd->non_periodic_qh_ptr != orig_qh_ptr);
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ 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.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ } 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.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+ }
+}
+
+/**
+ * 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.
+ */
+static void process_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ ulong host_regs;
+
+ host_regs = hcd->core_if->host_if->host_global_regs;
+
+ qh_ptr = hcd->periodic_sched_assigned.next;
+ while (qh_ptr != &hcd->periodic_sched_assigned) {
+ tx_status = dwc_reg_read(host_regs, DWC_HPTXSTS);
+ if (DWC_HPTXSTS_PTXSPC_AVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+
+ /*
+ * 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 (!hcd->core_if->dma_enable && qh->channel->multi_count > 1)
+ hcd->core_if->queuing_high_bandwidth = 1;
+
+ status = queue_transaction(hcd, qh->channel,
+ DWC_HPTXSTS_PTXFSPC_AVAIL_RD
+ (tx_status));
+ 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 (hcd->core_if->dma_enable || (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,
+ &hcd->periodic_sched_queued);
+
+ /* done queuing high bandwidth */
+ hcd->core_if->queuing_high_bandwidth = 0;
+ }
+ }
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+
+ if (!list_empty(&hcd->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.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ 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.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+}
+
+/**
+ * This function processes the currently active host channels and queues
+ * transactions for these channels to the DWC_otg controller. It is called
+ * from HCD interrupt handler functions.
+ */
+void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type)
+{
+ /* Process host channels associated with periodic transfers. */
+ if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) &&
+ !list_empty(&hcd->periodic_sched_assigned))
+ process_periodic_channels(hcd);
+
+ /* Process host channels associated with non-periodic transfers. */
+ if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) {
+ if (!list_empty(&hcd->non_periodic_sched_active)) {
+ process_non_periodic_channels(hcd);
+ } else {
+ /*
+ * Ensure NP Tx FIFO empty interrupt is disabled when
+ * there are no non-periodic transfers to process.
+ */
+ u32 gintmsk = 0;
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, gintmsk, 0);
+ }
+ }
+}
+
+/**
+ * Sets the final status of an URB and returns it to the device driver. Any
+ * required cleanup of the URB is performed.
+ */
+void dwc_otg_hcd_complete_urb(struct dwc_hcd *hcd, struct urb *urb, int status)
+__releases(hcd->lock) __acquires(hcd->lock)
+{
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+
+ spin_unlock(&hcd->lock);
+ usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status);
+ spin_lock(&hcd->lock);
+}
diff --git a/drivers/usb/dwc/hcd.h b/drivers/usb/dwc/hcd.h
new file mode 100644
index 0000000..c3d86e4
--- /dev/null
+++ b/drivers/usb/dwc/hcd.h
@@ -0,0 +1,416 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@....com>
+ * Modified by Stefan Roese <sr@...x.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@...ptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#if !defined(__DWC_HCD_H__)
+#define __DWC_HCD_H__
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Host Contoller Driver (HCD).
+ *
+ * The Host Controller Driver (HCD) is responsible for translating requests
+ * from the USB Driver into the appropriate actions on the DWC_otg controller.
+ * It isolates the USBD from the specifics of the controller by providing an
+ * API to the USBD.
+ */
+
+/* Phases for control transfers. */
+enum dwc_control_phase {
+ DWC_OTG_CONTROL_SETUP,
+ DWC_OTG_CONTROL_DATA,
+ DWC_OTG_CONTROL_STATUS
+};
+
+/* Transaction types. */
+enum dwc_transaction_type {
+ DWC_OTG_TRANSACTION_NONE,
+ DWC_OTG_TRANSACTION_PERIODIC,
+ DWC_OTG_TRANSACTION_NON_PERIODIC,
+ DWC_OTG_TRANSACTION_ALL
+};
+
+/*
+ * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
+ * interrupt, or isochronous transfer. A single QTD is created for each URB
+ * (of one of these types) submitted to the HCD. The transfer associated with
+ * a QTD may require one or multiple transactions.
+ *
+ * A QTD is linked to a Queue Head, which is entered in either the
+ * non-periodic or periodic schedule for execution. When a QTD is chosen for
+ * execution, some or all of its transactions may be executed. After
+ * execution, the state of the QTD is updated. The QTD may be retired if all
+ * its transactions are complete or if an error occurred. Otherwise, it
+ * remains in the schedule so more transactions can be executed later.
+ */
+struct dwc_qtd {
+ /*
+ * Determines the PID of the next data packet for the data phase of
+ * control transfers. Ignored for other transfer types.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Current phase for control transfers (Setup, Data, or Status). */
+ enum dwc_control_phase control_phase;
+
+ /*
+ * Keep track of the current split type
+ * for FS/LS endpoints on a HS Hub
+ */
+ u8 complete_split;
+
+ /* How many bytes transferred during SSPLIT OUT */
+ u32 ssplit_out_xfer_count;
+
+ /*
+ * Holds the number of bus errors that have occurred for a transaction
+ * within this transfer.
+ */
+ u8 error_count;
+
+ /*
+ * Index of the next frame descriptor for an isochronous transfer. A
+ * frame descriptor describes the buffer position and length of the
+ * data to be transferred in the next scheduled (micro)frame of an
+ * isochronous transfer. It also holds status for that transaction.
+ * The frame index starts at 0.
+ */
+ int isoc_frame_index;
+
+ /* Position of the ISOC split on full/low speed */
+ u8 isoc_split_pos;
+
+ /* Position of the ISOC split in the buffer for the current frame */
+ u16 isoc_split_offset;
+
+ /* URB for this transfer */
+ struct urb *urb;
+
+ /* This list of QTDs */
+ struct list_head qtd_list_entry;
+
+ /* Field to track the qh pointer */
+ struct dwc_qh *qtd_qh_ptr;
+};
+
+/*
+ * A Queue Head (QH) holds the static characteristics of an endpoint and
+ * maintains a list of transfers (QTDs) for that endpoint. A QH structure may
+ * be entered in either the non-periodic or periodic schedule.
+ */
+struct dwc_qh {
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - USB_ENDPOINT_XFER_CONTROL
+ * - USB_ENDPOINT_XFER_ISOC
+ * - USB_ENDPOINT_XFER_BULK
+ * - USB_ENDPOINT_XFER_INT
+ */
+ u8 ep_type;
+ u8 ep_is_in;
+
+ /* wMaxPacketSize Field of Endpoint Descriptor. */
+ u16 maxp;
+
+ /*
+ * Determines the PID of the next data packet for non-control
+ * transfers. Ignored for control transfers.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Ping state if 1. */
+ u8 ping_state;
+
+ /* List of QTDs for this QH. */
+ struct list_head qtd_list;
+
+ /* Host channel currently processing transfers for this QH. */
+ struct dwc_hc *channel;
+
+ /* QTD currently assigned to a host channel for this QH. */
+ struct dwc_qtd *qtd_in_process;
+
+ /* Full/low speed endpoint on high-speed hub requires split. */
+ u8 do_split;
+
+ /* Periodic schedule information */
+
+ /* Bandwidth in microseconds per (micro)frame. */
+ u8 usecs;
+
+ /* Interval between transfers in (micro)frames. */
+ u16 interval;
+
+ /*
+ * (micro)frame to initialize a periodic transfer. The transfer
+ * executes in the following (micro)frame.
+ */
+ u16 sched_frame;
+
+ /* (micro)frame at which last start split was initialized. */
+ u16 start_split_frame;
+
+ u16 speed;
+ u16 frame_usecs[8];
+
+ /* Entry for QH in either the periodic or non-periodic schedule. */
+ struct list_head qh_list_entry;
+};
+
+/* Gets the struct usb_hcd that contains a struct dwc_hcd. */
+static inline struct usb_hcd *dwc_otg_hcd_to_hcd(struct dwc_hcd *dwc_hcd)
+{
+ return container_of((void *)dwc_hcd, struct usb_hcd, hcd_priv);
+}
+
+/* HCD Create/Destroy Functions */
+extern int __init dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in host
+ * mode.
+ */
+extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd);
+extern void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc);
+extern void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status _halt_status);
+
+/* Transaction Execution Functions */
+extern enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd
+ *hcd);
+extern void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type);
+extern void dwc_otg_hcd_complete_urb(struct dwc_hcd *_hcd, struct urb *urb,
+ int status);
+
+/* Interrupt Handler Functions */
+extern int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd);
+
+/* Schedule Queue Functions */
+extern int init_hcd_usecs(struct dwc_hcd *hcd);
+extern void dwc_otg_hcd_qh_free(struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_csplit);
+extern int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int delay);
+extern struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb,
+ gfp_t _mem_flags);
+extern int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *dwc_hcd);
+
+/*
+ * Frees the memory for a QTD structure. QTD should already be removed from
+ * list.
+ */
+static inline void dwc_otg_hcd_qtd_free(struct dwc_qtd *_qtd)
+{
+ kfree(_qtd);
+}
+
+/* Removes a QTD from list. */
+static inline void dwc_otg_hcd_qtd_remove(struct dwc_qtd *_qtd)
+{
+ list_del(&_qtd->qtd_list_entry);
+}
+
+/* Remove and free a QTD */
+static inline void dwc_otg_hcd_qtd_remove_and_free(struct dwc_qtd *_qtd)
+{
+ dwc_otg_hcd_qtd_remove(_qtd);
+ dwc_otg_hcd_qtd_free(_qtd);
+}
+
+struct dwc_qh *dwc_urb_to_qh(struct urb *_urb);
+
+/* Gets the usb_host_endpoint associated with an URB. */
+static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb)
+{
+ struct usb_device *dev = _urb->dev;
+ int ep_num = usb_pipeendpoint(_urb->pipe);
+
+ if (usb_pipein(_urb->pipe))
+ return dev->ep_in[ep_num];
+ else
+ return dev->ep_out[ep_num];
+}
+
+/*
+ * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
+ * qualified with its direction (possible 32 endpoints per device).
+ */
+#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) \
+ ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
+ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
+
+/* Gets the QH that contains the list_head */
+#define dwc_list_to_qh(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qh, qh_list_entry))
+
+/* Gets the QTD that contains the list_head */
+#define dwc_list_to_qtd(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qtd, qtd_list_entry))
+
+/* Check if QH is non-periodic */
+#define dwc_qh_is_non_per(_qh_ptr_) \
+ ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \
+ (_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL))
+
+/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */
+#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/* Packet size for any kind of endpoint descriptor */
+#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+
+/*
+ * Returns true if _frame1 is less than or equal to _frame2. The comparison is
+ * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
+ * frame number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_le(u16 _frame1, u16 _frame2)
+{
+ return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <=
+ (DWC_HFNUM_MAX_FRNUM >> 1);
+}
+
+/*
+ * Returns true if _frame1 is greater than _frame2. The comparison is done
+ * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
+ * number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_gt(u16 _frame1, u16 _frame2)
+{
+ return (_frame1 != _frame2) &&
+ (((_frame1 - _frame2) &
+ DWC_HFNUM_MAX_FRNUM) < (DWC_HFNUM_MAX_FRNUM >> 1));
+}
+
+/*
+ * Increments _frame by the amount specified by _inc. The addition is done
+ * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
+ */
+static inline u16 dwc_frame_num_inc(u16 _frame, u16 _inc)
+{
+ return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM;
+}
+
+static inline u16 dwc_full_frame_num(u16 _frame)
+{
+ return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3;
+}
+
+static inline u16 dwc_micro_frame_num(u16 _frame)
+{
+ return (_frame) & 0x7;
+}
+
+static inline ulong gintsts_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTSTS;
+}
+
+static inline ulong gintmsk_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTMSK;
+}
+
+static inline ulong gahbcfg_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GAHBCFG;
+}
+
+static inline const char *pipetype_str(unsigned int pipe)
+{
+ switch (usb_pipetype(pipe)) {
+ case PIPE_CONTROL:
+ return "control";
+ case PIPE_BULK:
+ return "bulk";
+ case PIPE_INTERRUPT:
+ return "interrupt";
+ case PIPE_ISOCHRONOUS:
+ return "isochronous";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *dev_speed_str(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ return "high";
+ case USB_SPEED_FULL:
+ return "full";
+ case USB_SPEED_LOW:
+ return "low";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *ep_type_str(u8 type)
+{
+ switch (type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ return "isochronous";
+ case USB_ENDPOINT_XFER_INT:
+ return "interrupt";
+ case USB_ENDPOINT_XFER_CONTROL:
+ return "control";
+ case USB_ENDPOINT_XFER_BULK:
+ return "bulk";
+ default:
+ return "?";
+ }
+}
+#endif
--
1.7.0.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists