aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/dwc_otg/dwc_otg_hcd.c')
-rw-r--r--drivers/usb/host/dwc_otg/dwc_otg_hcd.c4366
1 files changed, 4366 insertions, 0 deletions
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
new file mode 100644
index 000000000000..2ee688acf171
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
@@ -0,0 +1,4366 @@
+
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.c $
+ * $Revision: #104 $
+ * $Date: 2011/10/24 $
+ * $Change: 1871159 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS 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.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+/** @file
+ * This file implements HCD Core. All code in this file is portable and doesn't
+ * use any OS specific functions.
+ * Interface provided by HCD Core is defined in <code><hcd_if.h></code>
+ * header file.
+ */
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "dwc_otg_hcd.h"
+#include "dwc_otg_regs.h"
+#include "dwc_otg_fiq_fsm.h"
+
+extern bool microframe_schedule;
+extern uint16_t fiq_fsm_mask, nak_holdoff;
+
+//#define DEBUG_HOST_CHANNELS
+#ifdef DEBUG_HOST_CHANNELS
+static int last_sel_trans_num_per_scheduled = 0;
+static int last_sel_trans_num_nonper_scheduled = 0;
+static int last_sel_trans_num_avail_hc_at_start = 0;
+static int last_sel_trans_num_avail_hc_at_end = 0;
+#endif /* DEBUG_HOST_CHANNELS */
+
+static_assert(FIQ_PASSTHROUGH == 0);
+
+dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void)
+{
+ return DWC_ALLOC(sizeof(dwc_otg_hcd_t));
+}
+
+/**
+ * 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(void *ptr)
+{
+ DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, ptr);
+ DWC_PRINTF("Connect Timeout\n");
+ __DWC_ERROR("Device Not Connected/Responding\n");
+}
+
+#if defined(DEBUG)
+static void dump_channel_info(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+ if (qh->channel != NULL) {
+ dwc_hc_t *hc = qh->channel;
+ dwc_list_link_t *item;
+ dwc_otg_qh_t *qh_item;
+ int num_channels = hcd->core_if->core_params->host_channels;
+ int i;
+
+ dwc_otg_hc_regs_t *hc_regs;
+ hcchar_data_t hcchar;
+ hcsplt_data_t hcsplt;
+ hctsiz_data_t hctsiz;
+ uint32_t hcdma;
+
+ hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt);
+ hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+ hcdma = DWC_READ_REG32(&hc_regs->hcdma);
+
+ DWC_PRINTF(" Assigned to channel %p:\n", hc);
+ DWC_PRINTF(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32,
+ hcsplt.d32);
+ DWC_PRINTF(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32,
+ hcdma);
+ DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
+ hc->dev_addr, hc->ep_num, hc->ep_is_in);
+ DWC_PRINTF(" ep_type: %d\n", hc->ep_type);
+ DWC_PRINTF(" max_packet: %d\n", hc->max_packet);
+ DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start);
+ DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started);
+ DWC_PRINTF(" halt_status: %d\n", hc->halt_status);
+ DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff);
+ DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len);
+ DWC_PRINTF(" qh: %p\n", hc->qh);
+ DWC_PRINTF(" NP inactive sched:\n");
+ DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_inactive) {
+ qh_item =
+ DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+ DWC_PRINTF(" %p\n", qh_item);
+ }
+ DWC_PRINTF(" NP active sched:\n");
+ DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_active) {
+ qh_item =
+ DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+ DWC_PRINTF(" %p\n", qh_item);
+ }
+ DWC_PRINTF(" Channels: \n");
+ for (i = 0; i < num_channels; i++) {
+ dwc_hc_t *hc = hcd->hc_ptr_array[i];
+ DWC_PRINTF(" %2d: %p\n", i, hc);
+ }
+ }
+}
+#else
+#define dump_channel_info(hcd, qh)
+#endif /* DEBUG */
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The hcd_start() must be called in a process context.
+ */
+static void hcd_start_func(void *_vp)
+{
+ dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) _vp;
+
+ DWC_DEBUGPL(DBG_HCDV, "%s() %p\n", __func__, hcd);
+ if (hcd) {
+ hcd->fops->start(hcd);
+ }
+}
+
+static void del_xfer_timers(dwc_otg_hcd_t * hcd)
+{
+#ifdef DEBUG
+ int i;
+ int num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ DWC_TIMER_CANCEL(hcd->core_if->hc_xfer_timer[i]);
+ }
+#endif
+}
+
+static void del_timers(dwc_otg_hcd_t * hcd)
+{
+ del_xfer_timers(hcd);
+ DWC_TIMER_CANCEL(hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ESHUTDOWN and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
+{
+ dwc_list_link_t *qh_item, *qh_tmp;
+ dwc_otg_qh_t *qh;
+ dwc_otg_qtd_t *qtd, *qtd_tmp;
+ int quiesced = 0;
+
+ DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) {
+ qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
+ DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp,
+ &qh->qtd_list, qtd_list_entry) {
+ qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+ if (qtd->urb != NULL) {
+ hcd->fops->complete(hcd, qtd->urb->priv,
+ qtd->urb, -DWC_E_SHUTDOWN);
+ dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+ }
+
+ }
+ if(qh->channel) {
+ int n = qh->channel->hc_num;
+ /* Using hcchar.chen == 1 is not a reliable test.
+ * It is possible that the channel has already halted
+ * but not yet been through the IRQ handler.
+ */
+ if (fiq_fsm_enable && (hcd->fiq_state->channel[qh->channel->hc_num].fsm != FIQ_PASSTHROUGH)) {
+ qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
+ qh->channel->halt_pending = 1;
+ if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
+ hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+ hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
+ /* We're called from disconnect callback or in the middle of freeing the HCD here,
+ * so FIQ is disabled, top-level interrupts masked and we're holding the spinlock.
+ * No further URBs will be submitted, but wait 1 microframe for any previously
+ * submitted periodic DMA to finish.
+ */
+ if (!quiesced) {
+ udelay(125);
+ quiesced = 1;
+ }
+ } else {
+ dwc_otg_hc_halt(hcd->core_if, qh->channel,
+ DWC_OTG_HC_XFER_URB_DEQUEUE);
+ }
+ qh->channel = NULL;
+ }
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ }
+}
+
+/**
+ * Responds with an error status of ESHUTDOWN 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(dwc_otg_hcd_t * hcd)
+{
+ 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);
+}
+
+/**
+ * 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(dwc_otg_hcd_t * hcd)
+{
+ DWC_TIMER_SCHEDULE(hcd->conn_timer, 10000 /* 10 secs */ );
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_session_start_cb(void *p)
+{
+ dwc_otg_hcd_t *dwc_otg_hcd;
+ DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p);
+ dwc_otg_hcd = p;
+ dwc_otg_hcd_start_connect_timer(dwc_otg_hcd);
+ return 1;
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_start_cb(void *p)
+{
+ dwc_otg_hcd_t *dwc_otg_hcd = p;
+ dwc_otg_core_if_t *core_if;
+ hprt0_data_t hprt0;
+
+ core_if = dwc_otg_hcd->core_if;
+
+ if (core_if->op_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.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtrst = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ }
+ DWC_WORKQ_SCHEDULE_DELAYED(core_if->wq_otg,
+ hcd_start_func, dwc_otg_hcd, 50,
+ "start hcd");
+
+ return 1;
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_disconnect_cb(void *p)
+{
+ gintsts_data_t intr;
+ dwc_otg_hcd_t *dwc_otg_hcd = p;
+
+ DWC_SPINLOCK(dwc_otg_hcd->lock);
+ /*
+ * Set status flags for the hub driver.
+ */
+ dwc_otg_hcd->flags.b.port_connect_status_change = 1;
+ dwc_otg_hcd->flags.b.port_connect_status = 0;
+ if(fiq_enable) {
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+ }
+ /*
+ * Shutdown any transfers in process by clearing the Tx FIFO Empty
+ * interrupt mask and status bits and disabling subsequent host
+ * channel interrupts.
+ */
+ intr.d32 = 0;
+ intr.b.nptxfempty = 1;
+ intr.b.ptxfempty = 1;
+ intr.b.hcintr = 1;
+ DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk,
+ intr.d32, 0);
+ DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintsts,
+ intr.d32, 0);
+
+ del_timers(dwc_otg_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(dwc_otg_hcd->core_if)) {
+ if (dwc_otg_hcd->core_if->op_state != A_SUSPEND) {
+ hprt0_data_t hprt0 = {.d32 = 0 };
+ DWC_PRINTF("Disconnect: PortPower off\n");
+ hprt0.b.prtpwr = 0;
+ DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0,
+ hprt0.d32);
+ }
+
+ dwc_otg_disable_host_interrupts(dwc_otg_hcd->core_if);
+ }
+
+ /* Respond with an error status to all URBs in the schedule. */
+ kill_all_urbs(dwc_otg_hcd);
+
+ if (dwc_otg_is_host_mode(dwc_otg_hcd->core_if)) {
+ /* Clean up any host channels that were in use. */
+ int num_channels;
+ int i;
+ dwc_hc_t *channel;
+ dwc_otg_hc_regs_t *hc_regs;
+ hcchar_data_t hcchar;
+
+ num_channels = dwc_otg_hcd->core_if->core_params->host_channels;
+
+ if (!dwc_otg_hcd->core_if->dma_enable) {
+ /* Flush out any channel requests in slave mode. */
+ for (i = 0; i < num_channels; i++) {
+ channel = dwc_otg_hcd->hc_ptr_array[i];
+ if (DWC_CIRCLEQ_EMPTY_ENTRY
+ (channel, hc_list_entry)) {
+ hc_regs =
+ dwc_otg_hcd->core_if->
+ host_if->hc_regs[i];
+ hcchar.d32 =
+ DWC_READ_REG32(&hc_regs->hcchar);
+ if (hcchar.b.chen) {
+ hcchar.b.chen = 0;
+ hcchar.b.chdis = 1;
+ hcchar.b.epdir = 0;
+ DWC_WRITE_REG32
+ (&hc_regs->hcchar,
+ hcchar.d32);
+ }
+ }
+ }
+ }
+
+ if(fiq_fsm_enable) {
+ for(i=0; i < 128; i++) {
+ dwc_otg_hcd->hub_port[i] = 0;
+ }
+ }
+ }
+
+ if(fiq_enable) {
+ fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+ local_fiq_enable();
+ }
+
+ if (dwc_otg_hcd->fops->disconnect) {
+ dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
+ }
+
+ DWC_SPINUNLOCK(dwc_otg_hcd->lock);
+ return 1;
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_stop_cb(void *p)
+{
+ dwc_otg_hcd_t *dwc_otg_hcd = p;
+
+ DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p);
+ dwc_otg_hcd_stop(dwc_otg_hcd);
+ return 1;
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/**
+ * HCD Callback function for sleep of HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int dwc_otg_hcd_sleep_cb(void *p)
+{
+ dwc_otg_hcd_t *hcd = p;
+
+ dwc_otg_hcd_free_hc_from_lpm(hcd);
+
+ return 0;
+}
+#endif
+
+
+/**
+ * HCD Callback function for Remote Wakeup.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int dwc_otg_hcd_rem_wakeup_cb(void *p)
+{
+ dwc_otg_hcd_t *hcd = p;
+
+ if (hcd->core_if->lx_state == DWC_OTG_L2) {
+ hcd->flags.b.port_suspend_change = 1;
+ }
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ else {
+ hcd->flags.b.port_l1_change = 1;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd)
+{
+ hprt0_data_t hprt0 = {.d32 = 0 };
+
+ DWC_DEBUGPL(DBG_HCD, "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. */
+ dwc_otg_disable_host_interrupts(hcd->core_if);
+
+ /* Turn off the vbus power */
+ DWC_PRINTF("PortPower off\n");
+ hprt0.b.prtpwr = 0;
+ DWC_WRITE_REG32(hcd->core_if->host_if->hprt0, hprt0.d32);
+ dwc_mdelay(1);
+}
+
+int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd,
+ dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle,
+ int atomic_alloc)
+{
+ int retval = 0;
+ uint8_t needs_scheduling = 0;
+ dwc_otg_transaction_type_e tr_type;
+ dwc_otg_qtd_t *qtd;
+ gintmsk_data_t intr_mask = {.d32 = 0 };
+ hprt0_data_t hprt0 = { .d32 = 0 };
+
+#ifdef DEBUG /* integrity checks (Broadcom) */
+ if (NULL == hcd->core_if) {
+ DWC_ERROR("**** DWC OTG HCD URB Enqueue - HCD has NULL core_if\n");
+ /* No longer connected. */
+ return -DWC_E_INVALID;
+ }
+#endif
+ if (!hcd->flags.b.port_connect_status) {
+ /* No longer connected. */
+ DWC_ERROR("Not connected\n");
+ return -DWC_E_NO_DEVICE;
+ }
+
+ /* Some core configurations cannot support LS traffic on a FS root port */
+ if ((hcd->fops->speed(hcd, dwc_otg_urb->priv) == USB_SPEED_LOW) &&
+ (hcd->core_if->hwcfg2.b.fs_phy_type == 1) &&
+ (hcd->core_if->hwcfg2.b.hs_phy_type == 1)) {
+ hprt0.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0);
+ if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+ return -DWC_E_NO_DEVICE;
+ }
+ }
+
+ qtd = dwc_otg_hcd_qtd_create(dwc_otg_urb, atomic_alloc);
+ if (qtd == NULL) {
+ DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n");
+ return -DWC_E_NO_MEMORY;
+ }
+#ifdef DEBUG /* integrity checks (Broadcom) */
+ if (qtd->urb == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD with no URBs\n");
+ return -DWC_E_NO_MEMORY;
+ }
+ if (qtd->urb->priv == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD URB with no URB handle\n");
+ return -DWC_E_NO_MEMORY;
+ }
+#endif
+ intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk);
+ if(!intr_mask.b.sofintr || fiq_enable) needs_scheduling = 1;
+ if((((dwc_otg_qh_t *)ep_handle)->ep_type == UE_BULK) && !(qtd->urb->flags & URB_GIVEBACK_ASAP))
+ /* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */
+ needs_scheduling = 0;
+
+ retval = dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc);
+ // creates a new queue in ep_handle if it doesn't exist already
+ if (retval < 0) {
+ DWC_ERROR("DWC OTG HCD URB Enqueue failed adding QTD. "
+ "Error status %d\n", retval);
+ dwc_otg_hcd_qtd_free(qtd);
+ return retval;
+ }
+
+ if(needs_scheduling) {
+ tr_type = dwc_otg_hcd_select_transactions(hcd);
+ if (tr_type != DWC_OTG_TRANSACTION_NONE) {
+ dwc_otg_hcd_queue_transactions(hcd, tr_type);
+ }
+ }
+ return retval;
+}
+
+int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd,
+ dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+ dwc_otg_qh_t *qh;
+ dwc_otg_qtd_t *urb_qtd;
+ BUG_ON(!hcd);
+ BUG_ON(!dwc_otg_urb);
+
+#ifdef DEBUG /* integrity checks (Broadcom) */
+
+ if (hcd == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL HCD\n");
+ return -DWC_E_INVALID;
+ }
+ if (dwc_otg_urb == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL URB\n");
+ return -DWC_E_INVALID;
+ }
+ if (dwc_otg_urb->qtd == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Dequeue with NULL QTD\n");
+ return -DWC_E_INVALID;
+ }
+ urb_qtd = dwc_otg_urb->qtd;
+ BUG_ON(!urb_qtd);
+ if (urb_qtd->qh == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Dequeue with QTD with NULL Q handler\n");
+ return -DWC_E_INVALID;
+ }
+#else
+ urb_qtd = dwc_otg_urb->qtd;
+ BUG_ON(!urb_qtd);
+#endif
+ qh = urb_qtd->qh;
+ BUG_ON(!qh);
+ if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+ if (urb_qtd->in_process) {
+ dump_channel_info(hcd, qh);
+ }
+ }
+#ifdef DEBUG /* integrity checks (Broadcom) */
+ if (hcd->core_if == NULL) {
+ DWC_ERROR("**** DWC OTG HCD URB Dequeue HCD has NULL core_if\n");
+ return -DWC_E_INVALID;
+ }
+#endif
+ if (urb_qtd->in_process && qh->channel) {
+ /* The QTD is in process (it has been assigned to a channel). */
+ if (hcd->flags.b.port_connect_status) {
+ int n = qh->channel->hc_num;
+ /*
+ * 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.
+ */
+ /* In FIQ FSM mode, we need to shut down carefully.
+ * The FIQ may attempt to restart a disabled channel */
+ if (fiq_fsm_enable && (hcd->fiq_state->channel[n].fsm != FIQ_PASSTHROUGH)) {
+ int retries = 3;
+ int running = 0;
+ enum fiq_fsm_state state;
+
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+ qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
+ qh->channel->halt_pending = 1;
+ if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
+ hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+ hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
+ fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+ local_fiq_enable();
+
+ if (dwc_qh_is_non_per(qh)) {
+ do {
+ state = READ_ONCE(hcd->fiq_state->channel[n].fsm);
+ running = (state != FIQ_NP_SPLIT_DONE) &&
+ (state != FIQ_NP_SPLIT_LS_ABORTED) &&
+ (state != FIQ_NP_SPLIT_HS_ABORTED);
+ if (!running)
+ break;
+ udelay(125);
+ } while(--retries);
+ if (!retries)
+ DWC_WARN("Timed out waiting for FSM NP transfer to complete on %d",
+ qh->channel->hc_num);
+ }
+ } else {
+ dwc_otg_hc_halt(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_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue - "
+ "delete %sQueue handler\n",
+ hcd->core_if->dma_desc_enable?"DMA ":"");
+ if (!hcd->core_if->dma_desc_enable) {
+ uint8_t b = urb_qtd->in_process;
+ if (nak_holdoff && qh->do_split && dwc_qh_is_non_per(qh))
+ qh->nak_frame = 0xFFFF;
+ dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh);
+ if (b) {
+ dwc_otg_hcd_qh_deactivate(hcd, qh, 0);
+ qh->channel = NULL;
+ } else if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ }
+ } else {
+ dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh);
+ }
+ return 0;
+}
+
+int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle,
+ int retry)
+{
+ dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+ int retval = 0;
+ dwc_irqflags_t flags;
+
+ if (retry < 0) {
+ retval = -DWC_E_INVALID;
+ goto done;
+ }
+
+ if (!qh) {
+ retval = -DWC_E_INVALID;
+ goto done;
+ }
+
+ DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+
+ while (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list) && retry) {
+ DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+ retry--;
+ dwc_msleep(5);
+ DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+ }
+
+ dwc_otg_hcd_qh_remove(hcd, qh);
+
+ DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+ /*
+ * Split dwc_otg_hcd_qh_remove_and_free() into qh_remove
+ * and qh_free to prevent stack dump on DWC_DMA_FREE() with
+ * irq_disabled (spinlock_irqsave) in dwc_otg_hcd_desc_list_free()
+ * and dwc_otg_hcd_frame_list_alloc().
+ */
+ dwc_otg_hcd_qh_free(hcd, qh);
+
+done:
+ return retval;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+ int retval = 0;
+ dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+ if (!qh)
+ return -DWC_E_INVALID;
+
+ qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+ return retval;
+}
+#endif
+
+/**
+ * HCD Callback structure for handling mode switching.
+ */
+static dwc_otg_cil_callbacks_t 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,
+ .resume_wakeup = dwc_otg_hcd_rem_wakeup_cb,
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ .sleep = dwc_otg_hcd_sleep_cb,
+#endif
+ .p = 0,
+};
+
+/**
+ * Reset tasklet function
+ */
+static void reset_tasklet_func(void *data)
+{
+ dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *) data;
+ dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if;
+ hprt0_data_t hprt0;
+
+ DWC_DEBUGPL(DBG_HCDV, "USB RESET tasklet called\n");
+
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtrst = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ dwc_mdelay(60);
+
+ hprt0.b.prtrst = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ dwc_otg_hcd->flags.b.port_reset_change = 1;
+}
+
+static void completion_tasklet_func(void *ptr)
+{
+ dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) ptr;
+ struct urb *urb;
+ urb_tq_entry_t *item;
+ dwc_irqflags_t flags;
+
+ /* This could just be spin_lock_irq */
+ DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+ while (!DWC_TAILQ_EMPTY(&hcd->completed_urb_list)) {
+ item = DWC_TAILQ_FIRST(&hcd->completed_urb_list);
+ urb = item->urb;
+ DWC_TAILQ_REMOVE(&hcd->completed_urb_list, item,
+ urb_tq_entries);
+ DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+ DWC_FREE(item);
+
+ usb_hcd_giveback_urb(hcd->priv, urb, urb->status);
+
+
+ DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+ }
+ DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+ return;
+}
+
+static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
+{
+ dwc_list_link_t *item;
+ dwc_otg_qh_t *qh;
+ dwc_irqflags_t flags;
+
+ if (!qh_list->next) {
+ /* The list hasn't been initialized yet. */
+ return;
+ }
+ /*
+ * Hold spinlock here. Not needed in that case if bellow
+ * function is being called from ISR
+ */
+ DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, qh_list);
+ DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+
+ DWC_LIST_FOREACH(item, qh_list) {
+ qh = DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
+}
+
+/**
+ * Exit from Hibernation if Host did not detect SRP from connected SRP capable
+ * Device during SRP time by host power up.
+ */
+#ifdef DWC_DEV_SRPCAP
+static void dwc_otg_hcd_power_up(void *ptr)
+{
+ gpwrdn_data_t gpwrdn = {.d32 = 0 };
+ dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr;
+
+ DWC_PRINTF("%s called\n", __FUNCTION__);
+
+ if (!core_if->hibernation_suspend) {
+ DWC_PRINTF("Already exited from Hibernation\n");
+ return;
+ }
+
+ /* Switch on the voltage to the core */
+ gpwrdn.b.pwrdnswtch = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+ dwc_udelay(10);
+
+ /* Reset the core */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pwrdnrstn = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+ dwc_udelay(10);
+
+ /* Disable power clamps */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pwrdnclmp = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+ /* Remove reset the core signal */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pwrdnrstn = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+ dwc_udelay(10);
+
+ /* Disable PMU interrupt */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pmuintsel = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+ core_if->hibernation_suspend = 0;
+
+ /* Disable PMU */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pmuactv = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+ dwc_udelay(10);
+
+ /* Enable VBUS */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.dis_vbus = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+ core_if->op_state = A_HOST;
+ dwc_otg_core_init(core_if);
+ dwc_otg_enable_global_interrupts(core_if);
+ cil_hcd_start(core_if);
+}
+#endif
+
+void dwc_otg_cleanup_fiq_channel(dwc_otg_hcd_t *hcd, uint32_t num)
+{
+ struct fiq_channel_state *st = &hcd->fiq_state->channel[num];
+ struct fiq_dma_channel *split_dma = hcd->fiq_dmab;
+ int i;
+
+ st->fsm = FIQ_PASSTHROUGH;
+ st->hcchar_copy.d32 = 0;
+ st->hcsplt_copy.d32 = 0;
+ st->hcint_copy.d32 = 0;
+ st->hcintmsk_copy.d32 = 0;
+ st->hctsiz_copy.d32 = 0;
+ st->hcdma_copy.d32 = 0;
+ st->nr_errors = 0;
+ st->hub_addr = 0;
+ st->port_addr = 0;
+ st->expected_uframe = 0;
+ st->nrpackets = 0;
+ st->dma_info.index = 0;
+ for (i = 0; i < 6; i++)
+ st->dma_info.slot_len[i] = 255;
+ st->hs_isoc_info.index = 0;
+ st->hs_isoc_info.iso_desc = NULL;
+ st->hs_isoc_info.nrframes = 0;
+
+ DWC_MEMSET(&split_dma[num].index[0], 0x6b, 1128);
+}
+
+/**
+ * Frees secondary storage associated with the dwc_otg_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+ struct device *dev = dwc_otg_hcd_to_dev(dwc_otg_hcd);
+ int i;
+
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD FREE\n");
+
+ del_timers(dwc_otg_hcd);
+
+ /* Free memory for QH/QTD lists */
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_inactive);
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_active);
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_inactive);
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_ready);
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_assigned);
+ qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_queued);
+
+ /* Free memory for the host channels. */
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ dwc_hc_t *hc = dwc_otg_hcd->hc_ptr_array[i];
+
+#ifdef DEBUG
+ if (dwc_otg_hcd->core_if->hc_xfer_timer[i]) {
+ DWC_TIMER_FREE(dwc_otg_hcd->core_if->hc_xfer_timer[i]);
+ }
+#endif
+ if (hc != NULL) {
+ DWC_DEBUGPL(DBG_HCDV, "HCD Free channel #%i, hc=%p\n",
+ i, hc);
+ DWC_FREE(hc);
+ }
+ }
+
+ if (dwc_otg_hcd->core_if->dma_enable) {
+ if (dwc_otg_hcd->status_buf_dma) {
+ DWC_DMA_FREE(dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ dwc_otg_hcd->status_buf,
+ dwc_otg_hcd->status_buf_dma);
+ }
+ } else if (dwc_otg_hcd->status_buf != NULL) {
+ DWC_FREE(dwc_otg_hcd->status_buf);
+ }
+ DWC_SPINLOCK_FREE(dwc_otg_hcd->lock);
+ /* Set core_if's lock pointer to NULL */
+ dwc_otg_hcd->core_if->lock = NULL;
+
+ DWC_TIMER_FREE(dwc_otg_hcd->conn_timer);
+ DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet);
+ DWC_TASK_FREE(dwc_otg_hcd->completion_tasklet);
+ DWC_DMA_FREE(dev, 16, dwc_otg_hcd->fiq_state->dummy_send,
+ dwc_otg_hcd->fiq_state->dummy_send_dma);
+ DWC_FREE(dwc_otg_hcd->fiq_state);
+
+#ifdef DWC_DEV_SRPCAP
+ if (dwc_otg_hcd->core_if->power_down == 2 &&
+ dwc_otg_hcd->core_if->pwron_timer) {
+ DWC_TIMER_FREE(dwc_otg_hcd->core_if->pwron_timer);
+ }
+#endif
+ DWC_FREE(dwc_otg_hcd);
+}
+
+int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if)
+{
+ struct device *dev = dwc_otg_hcd_to_dev(hcd);
+ int retval = 0;
+ int num_channels;
+ int i;
+ dwc_hc_t *channel;
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK))
+ DWC_SPINLOCK_ALLOC_LINUX_DEBUG(hcd->lock);
+#else
+ hcd->lock = DWC_SPINLOCK_ALLOC();
+#endif
+ DWC_DEBUGPL(DBG_HCDV, "init of HCD %p given core_if %p\n",
+ hcd, core_if);
+ if (!hcd->lock) {
+ DWC_ERROR("Could not allocate lock for pcd");
+ DWC_FREE(hcd);
+ retval = -DWC_E_NO_MEMORY;
+ goto out;
+ }
+ hcd->core_if = core_if;
+
+ /* Register the HCD CIL Callbacks */
+ dwc_otg_cil_register_hcd_callbacks(hcd->core_if,
+ &hcd_cil_callbacks, hcd);
+
+ /* Initialize the non-periodic schedule. */
+ DWC_LIST_INIT(&hcd->non_periodic_sched_inactive);
+ DWC_LIST_INIT(&hcd->non_periodic_sched_active);
+
+ /* Initialize the periodic schedule. */
+ DWC_LIST_INIT(&hcd->periodic_sched_inactive);
+ DWC_LIST_INIT(&hcd->periodic_sched_ready);
+ DWC_LIST_INIT(&hcd->periodic_sched_assigned);
+ DWC_LIST_INIT(&hcd->periodic_sched_queued);
+ DWC_TAILQ_INIT(&hcd->completed_urb_list);
+ /*
+ * Create a host channel descriptor for each host channel implemented
+ * in the controller. Initialize the channel descriptor array.
+ */
+ DWC_CIRCLEQ_INIT(&hcd->free_hc_list);
+ num_channels = hcd->core_if->core_params->host_channels;
+ DWC_MEMSET(hcd->hc_ptr_array, 0, sizeof(hcd->hc_ptr_array));
+ for (i = 0; i < num_channels; i++) {
+ channel = DWC_ALLOC(sizeof(dwc_hc_t));
+ if (channel == NULL) {
+ retval = -DWC_E_NO_MEMORY;
+ DWC_ERROR("%s: host channel allocation failed\n",
+ __func__);
+ dwc_otg_hcd_free(hcd);
+ goto out;
+ }
+ channel->hc_num = i;
+ hcd->hc_ptr_array[i] = channel;
+#ifdef DEBUG
+ hcd->core_if->hc_xfer_timer[i] =
+ DWC_TIMER_ALLOC("hc timer", hc_xfer_timeout,
+ &hcd->core_if->hc_xfer_info[i]);
+#endif
+ DWC_DEBUGPL(DBG_HCDV, "HCD Added channel #%d, hc=%p\n", i,
+ channel);
+ }
+
+ if (fiq_enable) {
+ hcd->fiq_state = DWC_ALLOC(sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels));
+ if (!hcd->fiq_state) {
+ retval = -DWC_E_NO_MEMORY;
+ DWC_ERROR("%s: cannot allocate fiq_state structure\n", __func__);
+ dwc_otg_hcd_free(hcd);
+ goto out;
+ }
+ DWC_MEMSET(hcd->fiq_state, 0, (sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels)));
+
+#ifdef CONFIG_ARM64
+ spin_lock_init(&hcd->fiq_state->lock);
+#endif
+
+ hcd->fiq_state->dummy_send = DWC_DMA_ALLOC_ATOMIC(dev, 16,
+ &hcd->fiq_state->dummy_send_dma);
+
+ hcd->fiq_stack = DWC_ALLOC(sizeof(struct fiq_stack));
+ if (!hcd->fiq_stack) {
+ retval = -DWC_E_NO_MEMORY;
+ DWC_ERROR("%s: cannot allocate fiq_stack structure\n", __func__);
+ dwc_otg_hcd_free(hcd);
+ goto out;
+ }
+ hcd->fiq_stack->magic1 = 0xDEADBEEF;
+ hcd->fiq_stack->magic2 = 0xD00DFEED;
+ hcd->fiq_state->gintmsk_saved.d32 = ~0;
+ hcd->fiq_state->haintmsk_saved.b2.chint = ~0;
+
+ /* This bit is terrible and uses no API, but necessary. The FIQ has no concept of DMA pools
+ * (and if it did, would be a lot slower). This allocates a chunk of memory (~9kiB for 8 host channels)
+ * for use as transaction bounce buffers in a 2-D array. Our access into this chunk is done by some
+ * moderately readable array casts.
+ */
+ hcd->fiq_dmab = DWC_DMA_ALLOC(dev, (sizeof(struct fiq_dma_channel) * num_channels), &hcd->fiq_state->dma_base);
+ DWC_WARN("FIQ DMA bounce buffers: virt = %px dma = %pad len=%zu",
+ hcd->fiq_dmab, &hcd->fiq_state->dma_base,
+ sizeof(struct fiq_dma_channel) * num_channels);
+
+ DWC_MEMSET(hcd->fiq_dmab, 0x6b, 9024);
+
+ /* pointer for debug in fiq_print */
+ hcd->fiq_state->fiq_dmab = hcd->fiq_dmab;
+ if (fiq_fsm_enable) {
+ int i;
+ for (i=0; i < hcd->core_if->core_params->host_channels; i++) {
+ dwc_otg_cleanup_fiq_channel(hcd, i);
+ }
+ DWC_PRINTF("FIQ FSM acceleration enabled for :\n%s%s%s%s",
+ (fiq_fsm_mask & 0x1) ? "Non-periodic Split Transactions\n" : "",
+ (fiq_fsm_mask & 0x2) ? "Periodic Split Transactions\n" : "",
+ (fiq_fsm_mask & 0x4) ? "High-Speed Isochronous Endpoints\n" : "",
+ (fiq_fsm_mask & 0x8) ? "Interrupt/Control Split Transaction hack enabled\n" : "");
+ }
+ }
+
+ /* Initialize the Connection timeout timer. */
+ hcd->conn_timer = DWC_TIMER_ALLOC("Connection timer",
+ dwc_otg_hcd_connect_timeout, 0);
+
+ printk(KERN_DEBUG "dwc_otg: Microframe scheduler %s\n", microframe_schedule ? "enabled":"disabled");
+ if (microframe_schedule)
+ init_hcd_usecs(hcd);
+
+ /* Initialize reset tasklet. */
+ hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd);
+
+ hcd->completion_tasklet = DWC_TASK_ALLOC("completion_tasklet",
+ completion_tasklet_func, hcd);
+#ifdef DWC_DEV_SRPCAP
+ if (hcd->core_if->power_down == 2) {
+ /* Initialize Power on timer for Host power up in case hibernation */
+ hcd->core_if->pwron_timer = DWC_TIMER_ALLOC("PWRON TIMER",
+ dwc_otg_hcd_power_up, core_if);
+ }
+#endif
+
+ /*
+ * 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 (hcd->core_if->dma_enable) {
+ hcd->status_buf =
+ DWC_DMA_ALLOC(dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ &hcd->status_buf_dma);
+ } else {
+ hcd->status_buf = DWC_ALLOC(DWC_OTG_HCD_STATUS_BUF_SIZE);
+ }
+ if (!hcd->status_buf) {
+ retval = -DWC_E_NO_MEMORY;
+ DWC_ERROR("%s: status_buf allocation failed\n", __func__);
+ dwc_otg_hcd_free(hcd);
+ goto out;
+ }
+
+ hcd->otg_port = 1;
+ hcd->frame_list = NULL;
+ hcd->frame_list_dma = 0;
+ hcd->periodic_qh_count = 0;
+
+ DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port));
+#ifdef FIQ_DEBUG
+ DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc));
+#endif
+
+out:
+ return retval;
+}
+
+void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd)
+{
+ /* Turn off all host-specific interrupts. */
+ dwc_otg_disable_host_interrupts(hcd->core_if);
+
+ dwc_otg_hcd_free(hcd);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void dwc_otg_hcd_reinit(dwc_otg_hcd_t * hcd)
+{
+ int num_channels;
+ int i;
+ dwc_hc_t *channel;
+ dwc_hc_t *channel_tmp;
+
+ hcd->flags.d32 = 0;
+
+ hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+ if (!microframe_schedule) {
+ hcd->non_periodic_channels = 0;
+ hcd->periodic_channels = 0;
+ } else {
+ hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+ }
+ /*
+ * Put all channels in the free channel list and clean up channel
+ * states.
+ */
+ DWC_CIRCLEQ_FOREACH_SAFE(channel, channel_tmp,
+ &hcd->free_hc_list, hc_list_entry) {
+ DWC_CIRCLEQ_REMOVE(&hcd->free_hc_list, channel, hc_list_entry);
+ }
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, channel,
+ hc_list_entry);
+ dwc_otg_hc_cleanup(hcd->core_if, channel);
+ }
+
+ /* Initialize the DWC core for host mode operation. */
+ dwc_otg_core_host_init(hcd->core_if);
+
+ /* Set core_if's lock pointer to the hcd->lock */
+ hcd->core_if->lock = hcd->lock;
+}
+
+/**
+ * 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.
+ *
+ * @param hcd The HCD state structure.
+ * @param qh Transactions from the first QTD for this QH are selected and
+ * assigned to a free host channel.
+ */
+static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+ dwc_hc_t *hc;
+ dwc_otg_qtd_t *qtd;
+ dwc_otg_hcd_urb_t *urb;
+ void* ptr = NULL;
+ uint16_t wLength;
+ uint32_t intr_enable;
+ unsigned long flags;
+ gintmsk_data_t gintmsk = { .d32 = 0, };
+ struct device *dev = dwc_otg_hcd_to_dev(hcd);
+
+ qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+
+ urb = qtd->urb;
+
+ DWC_DEBUGPL(DBG_HCDV, "%s(%p,%p) - urb %x, actual_length %d\n", __func__, hcd, qh, (unsigned int)urb, urb->actual_length);
+
+ if (((urb->actual_length < 0) || (urb->actual_length > urb->length)) && !dwc_otg_hcd_is_pipe_in(&urb->pipe_info))
+ urb->actual_length = urb->length;
+
+
+ hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list);
+
+ /* Remove the host channel from the free list. */
+ DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry);
+
+ qh->channel = hc;
+
+ 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.
+ */
+ hc->dev_addr = dwc_otg_hcd_get_dev_addr(&urb->pipe_info);
+ hc->ep_num = dwc_otg_hcd_get_ep_num(&urb->pipe_info);
+ hc->speed = qh->dev_speed;
+ 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->ep_is_in = (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) != 0);
+ if (hc->ep_is_in) {
+ hc->do_ping = 0;
+ } else {
+ hc->do_ping = qh->ping_state;
+ }
+
+ hc->data_pid_start = qh->data_toggle;
+ hc->multi_count = 1;
+
+ if (hcd->core_if->dma_enable) {
+ hc->xfer_buff =
+ (uint8_t *)(uintptr_t)urb->dma + urb->actual_length;
+
+ /* For non-dword aligned case */
+ if (((unsigned long)hc->xfer_buff & 0x3)
+ && !hcd->core_if->dma_desc_enable) {
+ ptr = (uint8_t *) urb->buf + urb->actual_length;
+ }
+ } else {
+ hc->xfer_buff = (uint8_t *) urb->buf + urb->actual_length;
+ }
+ hc->xfer_len = urb->length - urb->actual_length;
+ hc->xfer_count = 0;
+
+ /*
+ * Set the split attributes
+ */
+ hc->do_split = 0;
+ if (qh->do_split) {
+ uint32_t hub_addr, port_addr;
+ hc->do_split = 1;
+ hc->start_pkt_count = 1;
+ hc->xact_pos = qtd->isoc_split_pos;
+ /* We don't need to do complete splits anymore */
+// if(fiq_fsm_enable)
+ if (0)
+ hc->complete_split = qtd->complete_split = 0;
+ else
+ hc->complete_split = qtd->complete_split;
+
+ hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr);
+ hc->hub_addr = (uint8_t) hub_addr;
+ hc->port_addr = (uint8_t) port_addr;
+ }
+
+ switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) {
+ case UE_CONTROL:
+ hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ DWC_DEBUGPL(DBG_HCDV, " Control setup transaction\n");
+ 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 =
+ (uint8_t *)(uintptr_t)urb->setup_dma;
+ } else {
+ hc->xfer_buff = (uint8_t *) urb->setup_packet;
+ }
+ hc->xfer_len = 8;
+ ptr = NULL;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ DWC_DEBUGPL(DBG_HCDV, " Control data transaction\n");
+ /*
+ * Hardware bug: small IN packets with length < 4
+ * cause a 4-byte write to memory. We can only catch
+ * the case where we know a short packet is going to be
+ * returned in a control transfer, as the length is
+ * specified in the setup packet. This is only an issue
+ * for drivers that insist on packing a device's various
+ * properties into a struct and querying them one at a
+ * time (uvcvideo).
+ * Force the use of align_buf so that the subsequent
+ * memcpy puts the right number of bytes in the URB's
+ * buffer.
+ */
+ wLength = ((uint16_t *)urb->setup_packet)[3];
+ #if 0
+ if (hc->ep_is_in && wLength < 4)
+ ptr = hc->xfer_buff;
+ #endif
+
+ hc->data_pid_start = qtd->data_toggle;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ /*
+ * Direction is opposite of data direction or IN if no
+ * data.
+ */
+ DWC_DEBUGPL(DBG_HCDV, " Control status transaction\n");
+ if (urb->length == 0) {
+ hc->ep_is_in = 1;
+ } else {
+ hc->ep_is_in =
+ dwc_otg_hcd_is_pipe_out(&urb->pipe_info);
+ }
+ 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 = (uint8_t *)
+ (uintptr_t)hcd->status_buf_dma;
+ } else {
+ hc->xfer_buff = (uint8_t *) hcd->status_buf;
+ }
+ ptr = NULL;
+ break;
+ }
+ break;
+ case UE_BULK:
+ hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+ break;
+ case UE_INTERRUPT:
+ hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+ break;
+ case UE_ISOCHRONOUS:
+ {
+ struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+
+ hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+ if (hcd->core_if->dma_desc_enable)
+ break;
+
+ frame_desc = &urb->iso_descs[qtd->isoc_frame_index];
+
+ frame_desc->status = 0;
+
+ if (hcd->core_if->dma_enable) {
+ hc->xfer_buff = (uint8_t *)(uintptr_t)urb->dma;
+ } else {
+ hc->xfer_buff = (uint8_t *) urb->buf;
+ }
+ hc->xfer_buff +=
+ frame_desc->offset + qtd->isoc_split_offset;
+ hc->xfer_len =
+ frame_desc->length - qtd->isoc_split_offset;
+
+ /* For non-dword aligned buffers */
+ if (((unsigned long)hc->xfer_buff & 0x3)
+ && hcd->core_if->dma_enable) {
+ ptr =
+ (uint8_t *) urb->buf + frame_desc->offset +
+ qtd->isoc_split_offset;
+ } else
+ ptr = NULL;
+
+ 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;
+ }
+ /* non DWORD-aligned buffer case */
+ if (ptr) {
+ uint32_t buf_size;
+ if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ buf_size = hcd->core_if->core_params->max_transfer_size;
+ } else {
+ buf_size = 4096;
+ }
+ if (!qh->dw_align_buf) {
+ qh->dw_align_buf = DWC_DMA_ALLOC_ATOMIC(dev, buf_size,
+ &qh->dw_align_buf_dma);
+ if (!qh->dw_align_buf) {
+ DWC_ERROR
+ ("%s: Failed to allocate memory to handle "
+ "non-dword aligned buffer case\n",
+ __func__);
+ return;
+ }
+ }
+ if (!hc->ep_is_in) {
+ dwc_memcpy(qh->dw_align_buf, ptr, hc->xfer_len);
+ }
+ hc->align_buff = qh->dw_align_buf_dma;
+ } else {
+ hc->align_buff = 0;
+ }
+
+ 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);
+ }
+
+ if (hcd->core_if->dma_desc_enable)
+ hc->desc_list_addr = qh->desc_list_dma;
+
+ dwc_otg_hc_init(hcd->core_if, hc);
+
+ local_irq_save(flags);
+
+ if (fiq_enable) {
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+ }
+
+ /* Enable the top level host channel interrupt. */
+ intr_enable = (1 << hc->hc_num);
+ DWC_MODIFY_REG32(&hcd->core_if->host_if->host_global_regs->haintmsk, 0, intr_enable);
+
+ /* Make sure host channel interrupts are enabled. */
+ gintmsk.b.hcintr = 1;
+ DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
+
+ if (fiq_enable) {
+ fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+ local_fiq_enable();
+ }
+
+ local_irq_restore(flags);
+ hc->qh = qh;
+}
+
+
+/**
+ * fiq_fsm_transaction_suitable() - Test a QH for compatibility with the FIQ
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: pointer to the endpoint's queue head
+ *
+ * Transaction start/end control flow is grafted onto the existing dwc_otg
+ * mechanisms, to avoid spaghettifying the functions more than they already are.
+ * This function's eligibility check is altered by debug parameter.
+ *
+ * Returns: 0 for unsuitable, 1 implies the FIQ can be enabled for this transaction.
+ */
+
+int fiq_fsm_transaction_suitable(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+ if (qh->do_split) {
+ switch (qh->ep_type) {
+ case UE_CONTROL:
+ case UE_BULK:
+ if (fiq_fsm_mask & (1 << 0))
+ return 1;
+ break;
+ case UE_INTERRUPT:
+ case UE_ISOCHRONOUS:
+ if (fiq_fsm_mask & (1 << 1))
+ return 1;
+ break;
+ default:
+ break;
+ }
+ } else if (qh->ep_type == UE_ISOCHRONOUS) {
+ if (fiq_fsm_mask & (1 << 2)) {
+ /* ISOCH support. We test for compatibility:
+ * - DWORD aligned buffers
+ * - Must be at least 2 transfers (otherwise pointless to use the FIQ)
+ * If yes, then the fsm enqueue function will handle the state machine setup.
+ */
+ dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+ dwc_otg_hcd_urb_t *urb = qtd->urb;
+ dwc_dma_t ptr;
+ int i;
+
+ if (urb->packet_count < 2)
+ return 0;
+ for (i = 0; i < urb->packet_count; i++) {
+ ptr = urb->dma + urb->iso_descs[i].offset;
+ if (ptr & 0x3)
+ return 0;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * fiq_fsm_setup_periodic_dma() - Set up DMA bounce buffers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Periodic split transactions are transmitted modulo 188 bytes.
+ * This necessitates slicing data up into buckets for isochronous out
+ * and fixing up the DMA address for all IN transfers.
+ *
+ * Returns 1 if the DMA bounce buffers have been used, 0 if the default
+ * HC buffer has been used.
+ */
+static int fiq_fsm_setup_periodic_dma(dwc_otg_hcd_t *hcd, struct fiq_channel_state *st, dwc_otg_qh_t *qh)
+ {
+ int frame_length, i = 0;
+ uint8_t *ptr = NULL;
+ dwc_hc_t *hc = qh->channel;
+ struct fiq_dma_channel *split_dma;
+ struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+
+ for (i = 0; i < 6; i++) {
+ st->dma_info.slot_len[i] = 255;
+ }
+ st->dma_info.index = 0;
+ i = 0;
+ if (hc->ep_is_in) {
+ /*
+ * Set dma_regs to bounce buffer. FIQ will update the
+ * state depending on transaction progress.
+ * Pointer arithmetic on hcd->fiq_state->dma_base (a dma_addr_t)
+ * to point it to the correct offset in the allocated buffers.
+ */
+ split_dma = (struct fiq_dma_channel *)
+ (uintptr_t)hcd->fiq_state->dma_base;
+ st->hcdma_copy.d32 = lower_32_bits((uintptr_t)
+ &split_dma[hc->hc_num].index[0].buf[0]);
+
+ /* Calculate the max number of CSPLITS such that the FIQ can time out
+ * a transaction if it fails.
+ */
+ frame_length = st->hcchar_copy.b.mps;
+ do {
+ i++;
+ frame_length -= 188;
+ } while (frame_length >= 0);
+ st->nrpackets = i;
+ return 1;
+ } else {
+ if (qh->ep_type == UE_ISOCHRONOUS) {
+
+ dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+
+ frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+ frame_length = frame_desc->length;
+
+ /* Virtual address for bounce buffers */
+ split_dma = hcd->fiq_dmab;
+
+ ptr = qtd->urb->buf + frame_desc->offset;
+ if (frame_length == 0) {
+ /*
+ * for isochronous transactions, we must still transmit a packet
+ * even if the length is zero.
+ */
+ st->dma_info.slot_len[0] = 0;
+ st->nrpackets = 1;
+ } else {
+ do {
+ if (frame_length <= 188) {
+ dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, frame_length);
+ st->dma_info.slot_len[i] = frame_length;
+ ptr += frame_length;
+ } else {
+ dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, 188);
+ st->dma_info.slot_len[i] = 188;
+ ptr += 188;
+ }
+ i++;
+ frame_length -= 188;
+ } while (frame_length > 0);
+ st->nrpackets = i;
+ }
+ ptr = qtd->urb->buf + frame_desc->offset;
+ /*
+ * Point the HC at the DMA address of the bounce buffers
+ *
+ * Pointer arithmetic on hcd->fiq_state->dma_base (a
+ * dma_addr_t) to point it to the correct offset in the
+ * allocated buffers.
+ */
+ split_dma = (struct fiq_dma_channel *)
+ (uintptr_t)hcd->fiq_state->dma_base;
+ st->hcdma_copy.d32 = lower_32_bits((uintptr_t)
+ &split_dma[hc->hc_num].index[0].buf[0]);
+
+ /* fixup xfersize to the actual packet size */
+ st->hctsiz_copy.b.pid = 0;
+ st->hctsiz_copy.b.xfersize = st->dma_info.slot_len[0];
+ return 1;
+ } else {
+ /* For interrupt, single OUT packet required, goes in the SSPLIT from hc_buff. */
+ return 0;
+ }
+ }
+}
+
+/**
+ * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Certain hub chips don't differentiate between IN and OUT non-periodic pipes
+ * with the same endpoint number. If transfers get completed out of order
+ * (disregarding the direction token) then the hub can lock up
+ * or return erroneous responses.
+ *
+ * Returns 1 if initiating the transfer would cause contention, 0 otherwise.
+ */
+static int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+ int i;
+ struct fiq_channel_state *st;
+ int dev_addr = qh->channel->dev_addr;
+ int ep_num = qh->channel->ep_num;
+ for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+ if (i == qh->channel->hc_num)
+ continue;
+ st = &hcd->fiq_state->channel[i];
+ switch (st->fsm) {
+ case FIQ_NP_SSPLIT_STARTED:
+ case FIQ_NP_SSPLIT_RETRY:
+ case FIQ_NP_SSPLIT_PENDING:
+ case FIQ_NP_OUT_CSPLIT_RETRY:
+ case FIQ_NP_IN_CSPLIT_RETRY:
+ if (st->hcchar_copy.b.devaddr == dev_addr &&
+ st->hcchar_copy.b.epnum == ep_num)
+ return 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Pushing a periodic request into the queue near the EOF1 point
+ * in a microframe causes erroneous behaviour (frmovrun) interrupt.
+ * Usually, the request goes out on the bus causing a transfer but
+ * the core does not transfer the data to memory.
+ * This guard interval (in number of 60MHz clocks) is required which
+ * must cater for CPU latency between reading the value and enabling
+ * the channel.
+ */
+#define PERIODIC_FRREM_BACKOFF 1000
+
+static int fiq_fsm_queue_isoc_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+ dwc_hc_t *hc = qh->channel;
+ dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+ dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+ int frame;
+ struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num];
+ int xfer_len, nrpackets;
+ hcdma_data_t hcdma;
+ hfnum_data_t hfnum;
+
+ if (st->fsm != FIQ_PASSTHROUGH)
+ return 0;
+
+ st->nr_errors = 0;
+
+ st->hcchar_copy.d32 = 0;
+ st->hcchar_copy.b.mps = hc->max_packet;
+ st->hcchar_copy.b.epdir = hc->ep_is_in;
+ st->hcchar_copy.b.devaddr = hc->dev_addr;
+ st->hcchar_copy.b.epnum = hc->ep_num;
+ st->hcchar_copy.b.eptype = hc->ep_type;
+
+ st->hcintmsk_copy.b.chhltd = 1;
+
+ frame = dwc_otg_hcd_get_frame_number(hcd);
+ st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1;
+
+ st->hcchar_copy.b.lspddev = 0;
+ /* Enable the channel later as a final register write. */
+
+ st->hcsplt_copy.d32 = 0;
+
+ st->hs_isoc_info.iso_desc = (struct dwc_otg_hcd_iso_packet_desc *) &qtd->urb->iso_descs;
+ st->hs_isoc_info.nrframes = qtd->urb->packet_count;
+ /* grab the next DMA address offset from the array */
+ st->hcdma_copy.d32 = qtd->urb->dma;
+ hcdma.d32 = st->hcdma_copy.d32 + st->hs_isoc_info.iso_desc[0].offset;
+
+ /* We need to set multi_count. This is a bit tricky - has to be set per-transaction as
+ * the core needs to be told to send the correct number. Caution: for IN transfers,
+ * this is always set to the maximum size of the endpoint. */
+ xfer_len = st->hs_isoc_info.iso_desc[0].length;
+ nrpackets = (xfer_len + st->hcchar_copy.b.mps - 1) / st->hcchar_copy.b.mps;
+ if (nrpackets == 0)
+ nrpackets = 1;
+ st->hcchar_copy.b.multicnt = nrpackets;
+ st->hctsiz_copy.b.pktcnt = nrpackets;
+
+ /* Initial PID also needs to be set */
+ if (st->hcchar_copy.b.epdir == 0) {
+ st->hctsiz_copy.b.xfersize = xfer_len;
+ switch (st->hcchar_copy.b.multicnt) {
+ case 1:
+ st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+ break;
+ case 2:
+ case 3:
+ st->hctsiz_copy.b.pid = DWC_PID_MDATA;
+ break;
+ }
+
+ } else {
+ st->hctsiz_copy.b.xfersize = nrpackets * st->hcchar_copy.b.mps;
+ switch (st->hcchar_copy.b.multicnt) {
+ case 1:
+ st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+ break;
+ case 2:
+ st->hctsiz_copy.b.pid = DWC_PID_DATA1;
+ break;
+ case 3:
+ st->hctsiz_copy.b.pid = DWC_PID_DATA2;
+ break;
+ }
+ }
+
+ st->hs_isoc_info.stride = qh->interval;
+ st->uframe_sleeps = 0;
+
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ %01d ", hc->hc_num);
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcchar_copy.d32);
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32);
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32);
+ hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+ DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32);
+ if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) {
+ /* Prevent queueing near EOF1. Bad things happen if a periodic
+ * split transaction is queued very close to EOF. SOF interrupt handler
+ * will wake this channel at the next interrupt.
+ */
+ st->fsm = FIQ_HS_ISOC_SLEEPING;
+ st->uframe_sleeps = 1;
+ } else {
+ st->fsm = FIQ_HS_ISOC_TURBO;
+ st->hcchar_copy.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+ }
+ mb();
+ st->hcchar_copy.b.chen = 0;
+ fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+ local_fiq_enable();
+ return 0;
+}
+
+
+/**
+ * fiq_fsm_queue_split_transaction() - Set up a host channel and FIQ state
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * This overrides the dwc_otg driver's normal method of queueing a transaction.
+ * Called from dwc_otg_hcd_queue_transactions(), this performs specific setup
+ * for the nominated host channel.
+ *
+ * For periodic transfers, it also peeks at the FIQ state to see if an immediate
+ * start is possible. If not, then the FIQ is left to start the transfer.
+ */
+static int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+ int start_immediate = 1, i;
+ hfnum_data_t hfnum;
+ dwc_hc_t *hc = qh->channel;
+ dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+ /* Program HC registers, setup FIQ_state, examine FIQ if periodic, start transfer (not if uframe 5) */
+ int hub_addr, port_addr, frame, uframe;
+ struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num];
+
+ /*
+ * Non-periodic channel assignments stay in the non_periodic_active queue.
+ * Therefore we get repeatedly called until the FIQ's done processing this channel.
+ */
+ if (qh->channel->xfer_started == 1)
+ return 0;
+
+ if (st->fsm != FIQ_PASSTHROUGH) {
+ pr_warn_ratelimited("%s:%d: Queue called for an active channel\n", __func__, __LINE__);
+ return 0;
+ }
+
+ qh->channel->xfer_started = 1;
+
+ st->nr_errors = 0;
+
+ st->hcchar_copy.d32 = 0;
+ st->hcchar_copy.b.mps = min_t(uint32_t, hc->xfer_len, hc->max_packet);
+ st->hcchar_copy.b.epdir = hc->ep_is_in;
+ st->hcchar_copy.b.devaddr = hc->dev_addr;
+ st->hcchar_copy.b.epnum = hc->ep_num;
+ st->hcchar_copy.b.eptype = hc->ep_type;
+ if (hc->ep_type & 0x1) {
+ if (hc->ep_is_in)
+ st->hcchar_copy.b.multicnt = 3;
+ else
+ /* Docs say set this to 1, but driver sets to 0! */
+ st->hcchar_copy.b.multicnt = 0;
+ } else {
+ st->hcchar_copy.b.multicnt = 1;
+ st->hcchar_copy.b.oddfrm = 0;
+ }
+ st->hcchar_copy.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW) ? 1 : 0;
+ /* Enable the channel later as a final register write. */
+
+ st->hcsplt_copy.d32 = 0;
+ if(qh->do_split) {
+ hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);
+ st->hcsplt_copy.b.compsplt = 0;
+ st->hcsplt_copy.b.spltena = 1;
+ // XACTPOS is for isoc-out only but needs initialising anyway.
+ st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_ALL;
+ if((qh->ep_type == DWC_OTG_EP_TYPE_ISOC) && (!qh->ep_is_in)) {
+ /* For packetsize 0 < L < 188, ISOC_XACTPOS_ALL.
+ * for longer than this, ISOC_XACTPOS_BEGIN and the FIQ
+ * will update as necessary.
+ */
+ if (hc->xfer_len > 188) {
+ st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_BEGIN;
+ }
+ }
+ st->hcsplt_copy.b.hubaddr = (uint8_t) hub_addr;
+ st->hcsplt_copy.b.prtaddr = (uint8_t) port_addr;
+ st->hub_addr = hub_addr;
+ st->port_addr = port_addr;
+ }
+
+ st->hctsiz_copy.d32 = 0;
+ st->hctsiz_copy.b.dopng = 0;
+ st->hctsiz_copy.b.pid = hc->data_pid_start;
+
+ if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) {
+ hc->xfer_len = min_t(uint32_t, hc->xfer_len, hc->max_packet);
+ } else if (!hc->ep_is_in && (hc->xfer_len > 188)) {
+ hc->xfer_len = 188;
+ }
+ st->hctsiz_copy.b.xfersize = hc->xfer_len;
+
+ st->hctsiz_copy.b.pktcnt = 1;
+
+ if (hc->ep_type & 0x1) {
+ /*
+ * For potentially multi-packet transfers, must use the DMA bounce buffers. For IN transfers,
+ * the DMA address is the address of the first 188byte slot buffer in the bounce buffer array.
+ * For multi-packet OUT transfers, we need to copy the data into the bounce buffer array so the FIQ can punt
+ * the right address out as necessary. hc->xfer_buff and hc->xfer_len have already been set
+ * in assign_and_init_hc(), but this is for the eventual transaction completion only. The FIQ
+ * must not touch internal driver state.
+ */
+ if(!fiq_fsm_setup_periodic_dma(hcd, st, qh)) {
+ if (hc->align_buff) {
+ st->hcdma_copy.d32 = hc->align_buff;
+ } else {
+ st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+ }
+ }
+ } else {
+ if (hc->align_buff) {
+ st->hcdma_copy.d32 = hc->align_buff;
+ } else {
+ st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+ }
+ }
+ /* The FIQ depends upon no other interrupts being enabled except channel halt.
+ * Fixup channel interrupt mask. */
+ st->hcintmsk_copy.d32 = 0;
+ st->hcintmsk_copy.b.chhltd = 1;
+ st->hcintmsk_copy.b.ahberr = 1;
+
+ /* Hack courtesy of FreeBSD: apparently forcing Interrupt Split transactions
+ * as Control puts the transfer into the non-periodic request queue and the
+ * non-periodic handler in the hub. Makes things lots easier.
+ */
+ if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT) {
+ st->hcchar_copy.b.multicnt = 0;
+ st->hcchar_copy.b.oddfrm = 0;
+ st->hcchar_copy.b.eptype = UE_CONTROL;
+ if (hc->align_buff) {
+ st->hcdma_copy.d32 = hc->align_buff;
+ } else {
+ st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+ }
+ }
+ DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32);
+
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+
+ if (hc->ep_type & 0x1) {
+ hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
+ frame = (hfnum.b.frnum & ~0x7) >> 3;
+ uframe = hfnum.b.frnum & 0x7;
+ if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) {
+ /* Prevent queueing near EOF1. Bad things happen if a periodic
+ * split transaction is queued very close to EOF.
+ */
+ start_immediate = 0;
+ } else if (uframe == 5) {
+ start_immediate = 0;
+ } else if (hc->ep_type == UE_ISOCHRONOUS && !hc->ep_is_in) {
+ start_immediate = 0;
+ } else if (hc->ep_is_in && fiq_fsm_too_late(hcd->fiq_state, hc->hc_num)) {
+ start_immediate = 0;
+ } else {
+ /* Search through all host channels to determine if a transaction
+ * is currently in progress */
+ for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+ if (i == hc->hc_num || hcd->fiq_state->channel[i].fsm == FIQ_PASSTHROUGH)
+ continue;
+ switch (hcd->fiq_state->channel[i].fsm) {
+ /* TT is reserved for channels that are in the middle of a periodic
+ * split transaction.
+ */
+ case FIQ_PER_SSPLIT_STARTED:
+ case FIQ_PER_CSPLIT_WAIT:
+ case FIQ_PER_CSPLIT_NYET1:
+ case FIQ_PER_CSPLIT_POLL:
+ case FIQ_PER_ISO_OUT_ACTIVE:
+ case FIQ_PER_ISO_OUT_LAST:
+ if (hcd->fiq_state->channel[i].hub_addr == hub_addr &&
+ hcd->fiq_state->channel[i].port_addr == port_addr) {
+ start_immediate = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ if (!start_immediate)
+ break;
+ }
+ }
+ }
+ if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT)
+ start_immediate = 1;
+
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ %01d %01d", hc->hc_num, start_immediate);
+ fiq_print(FIQDBG_INT, hcd->fiq_state, "%08d", hfnum.b.frrem);
+ //fiq_print(FIQDBG_INT, hcd->fiq_state, "H:%02dP:%02d", hub_addr, port_addr);
+ //fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32);
+ //fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32);
+ switch (hc->ep_type) {
+ case UE_CONTROL:
+ case UE_BULK:
+ if (fiq_fsm_np_tt_contended(hcd, qh)) {
+ st->fsm = FIQ_NP_SSPLIT_PENDING;
+ start_immediate = 0;
+ } else {
+ st->fsm = FIQ_NP_SSPLIT_STARTED;
+ }
+ break;
+ case UE_ISOCHRONOUS:
+ if (hc->ep_is_in) {
+ if (start_immediate) {
+ st->fsm = FIQ_PER_SSPLIT_STARTED;
+ } else {
+ st->fsm = FIQ_PER_SSPLIT_QUEUED;
+ }
+ } else {
+ if (start_immediate) {
+ /* Single-isoc OUT packets don't require FIQ involvement */
+ if (st->nrpackets == 1) {
+ st->fsm = FIQ_PER_ISO_OUT_LAST;
+ } else {
+ st->fsm = FIQ_PER_ISO_OUT_ACTIVE;
+ }
+ } else {
+ st->fsm = FIQ_PER_ISO_OUT_PENDING;
+ }
+ }
+ break;
+ case UE_INTERRUPT:
+ if (fiq_fsm_mask & 0x8) {
+ if (fiq_fsm_np_tt_contended(hcd, qh)) {
+ st->fsm = FIQ_NP_SSPLIT_PENDING;
+ start_immediate = 0;
+ } else {
+ st->fsm = FIQ_NP_SSPLIT_STARTED;
+ }
+ } else if (start_immediate) {
+ st->fsm = FIQ_PER_SSPLIT_STARTED;
+ } else {
+ st->fsm = FIQ_PER_SSPLIT_QUEUED;
+ }
+ break;
+ default:
+ break;
+ }
+ if (start_immediate) {
+ /* Set the oddfrm bit as close as possible to actual queueing */
+ frame = dwc_otg_hcd_get_frame_number(hcd);
+ st->expected_uframe = (frame + 1) & 0x3FFF;
+ st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1;
+ st->hcchar_copy.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+ }
+ mb();
+ fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+ local_fiq_enable();
+ return 0;
+}
+
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ *
+ * @param hcd The HCD state structure.
+ *
+ * @return The types of new transactions that were assigned to host channels.
+ */
+dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
+{
+ dwc_list_link_t *qh_ptr;
+ dwc_otg_qh_t *qh;
+ int num_channels;
+ dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE;
+
+#ifdef DEBUG_HOST_CHANNELS
+ last_sel_trans_num_per_scheduled = 0;
+ last_sel_trans_num_nonper_scheduled = 0;
+ last_sel_trans_num_avail_hc_at_start = hcd->available_host_channels;
+#endif /* DEBUG_HOST_CHANNELS */
+
+ /* Process entries in the periodic ready list. */
+ qh_ptr = DWC_LIST_FIRST(&hcd->periodic_sched_ready);
+
+ while (qh_ptr != &hcd->periodic_sched_ready &&
+ !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+
+ qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+
+ if (microframe_schedule) {
+ // Make sure we leave one channel for non periodic transactions.
+ if (hcd->available_host_channels <= 1) {
+ break;
+ }
+ hcd->available_host_channels--;
+#ifdef DEBUG_HOST_CHANNELS
+ last_sel_trans_num_per_scheduled++;
+#endif /* DEBUG_HOST_CHANNELS */
+ }
+ qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+
+ /*
+ * Move the QH from the periodic ready schedule to the
+ * periodic assigned schedule.
+ */
+ qh_ptr = DWC_LIST_NEXT(qh_ptr);
+ DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
+ &qh->qh_list_entry);
+ }
+
+ /*
+ * 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 &&
+ (microframe_schedule || hcd->non_periodic_channels <
+ num_channels - hcd->periodic_channels) &&
+ !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+
+ qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+ /*
+ * Check to see if this is a NAK'd retransmit, in which case ignore for retransmission
+ * we hold off on bulk retransmissions to reduce NAK interrupt overhead for full-speed
+ * cheeky devices that just hold off using NAKs
+ */
+ if (fiq_enable && nak_holdoff && qh->do_split) {
+ if (qh->nak_frame != 0xffff) {
+ uint16_t next_frame = dwc_frame_num_inc(qh->nak_frame, (qh->ep_type == UE_BULK) ? nak_holdoff : 8);
+ uint16_t frame = dwc_otg_hcd_get_frame_number(hcd);
+ if (dwc_frame_num_le(frame, next_frame)) {
+ if(dwc_frame_num_le(next_frame, hcd->fiq_state->next_sched_frame)) {
+ hcd->fiq_state->next_sched_frame = next_frame;
+ }
+ qh_ptr = DWC_LIST_NEXT(qh_ptr);
+ continue;
+ } else {
+ qh->nak_frame = 0xFFFF;
+ }
+ }
+ }
+
+ if (microframe_schedule) {
+ if (hcd->available_host_channels < 1) {
+ break;
+ }
+ hcd->available_host_channels--;
+#ifdef DEBUG_HOST_CHANNELS
+ last_sel_trans_num_nonper_scheduled++;
+#endif /* DEBUG_HOST_CHANNELS */
+ }
+
+ assign_and_init_hc(hcd, qh);
+
+ /*
+ * Move the QH from the non-periodic inactive schedule to the
+ * non-periodic active schedule.
+ */
+ qh_ptr = DWC_LIST_NEXT(qh_ptr);
+ DWC_LIST_MOVE_HEAD(&hcd->non_periodic_sched_active,
+ &qh->qh_list_entry);
+
+ if (!microframe_schedule)
+ hcd->non_periodic_channels++;
+ }
+ /* we moved a non-periodic QH to the active schedule. If the inactive queue is empty,
+ * stop the FIQ from kicking us. We could potentially still have elements here if we
+ * ran out of host channels.
+ */
+ if (fiq_enable) {
+ if (DWC_LIST_EMPTY(&hcd->non_periodic_sched_inactive)) {
+ hcd->fiq_state->kick_np_queues = 0;
+ } else {
+ /* For each entry remaining in the NP inactive queue,
+ * if this a NAK'd retransmit then don't set the kick flag.
+ */
+ if(nak_holdoff) {
+ DWC_LIST_FOREACH(qh_ptr, &hcd->non_periodic_sched_inactive) {
+ qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+ if (qh->nak_frame == 0xFFFF) {
+ hcd->fiq_state->kick_np_queues = 1;
+ }
+ }
+ }
+ }
+ }
+ if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned))
+ ret_val |= DWC_OTG_TRANSACTION_PERIODIC;
+
+ if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active))
+ ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC;
+
+
+#ifdef DEBUG_HOST_CHANNELS
+ last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels;
+#endif /* DEBUG_HOST_CHANNELS */
+ return ret_val;
+}
+
+/**
+ * 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.
+ *
+ * @param hcd The HCD state structure.
+ * @param hc Host channel descriptor associated with either a periodic or
+ * non-periodic transfer.
+ * @param 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.
+ */
+static int queue_transaction(dwc_otg_hcd_t * hcd,
+ dwc_hc_t * hc, uint16_t fifo_dwords_avail)
+{
+ int retval;
+
+ if (hcd->core_if->dma_enable) {
+ if (hcd->core_if->dma_desc_enable) {
+ if (!hc->xfer_started
+ || (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)) {
+ dwc_otg_hcd_start_xfer_ddma(hcd, hc->qh);
+ hc->qh->ping_state = 0;
+ }
+ } else if (!hc->xfer_started) {
+ if (fiq_fsm_enable && hc->error_state) {
+ hcd->fiq_state->channel[hc->hc_num].nr_errors =
+ DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list)->error_count;
+ hcd->fiq_state->channel[hc->hc_num].fsm =
+ FIQ_PASSTHROUGH_ERRORSTATE;
+ }
+ 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 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(dwc_otg_hcd_t * hcd)
+{
+ hptxsts_data_t tx_status;
+ dwc_list_link_t *qh_ptr;
+ dwc_otg_qh_t *qh;
+ int status = 0;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+
+ dwc_otg_host_global_regs_t *host_regs;
+ host_regs = hcd->core_if->host_if->host_global_regs;
+
+ DWC_DEBUGPL(DBG_HCDV, "Queue periodic transactions\n");
+#ifdef DEBUG
+ tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+ DWC_DEBUGPL(DBG_HCDV,
+ " P Tx Req Queue Space Avail (before queue): %d\n",
+ tx_status.b.ptxqspcavail);
+ DWC_DEBUGPL(DBG_HCDV, " P Tx FIFO Space Avail (before queue): %d\n",
+ tx_status.b.ptxfspcavail);
+#endif
+
+ qh_ptr = hcd->periodic_sched_assigned.next;
+ while (qh_ptr != &hcd->periodic_sched_assigned) {
+ tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+ if (tx_status.b.ptxqspcavail == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+
+ // Do not send a split start transaction any later than frame .6
+ // Note, we have to schedule a periodic in .5 to make it go in .6
+ if(fiq_fsm_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
+ {
+ qh_ptr = qh_ptr->next;
+ hcd->fiq_state->next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
+ continue;
+ }
+
+ if (fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) {
+ if (qh->do_split)
+ fiq_fsm_queue_split_transaction(hcd, qh);
+ else
+ fiq_fsm_queue_isoc_transaction(hcd, qh);
+ } else {
+
+ /*
+ * Set a flag if we're queueing 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,
+ tx_status.b.ptxfspcavail);
+ 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.
+ */
+ DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_queued,
+ &qh->qh_list_entry);
+
+ /* done queuing high bandwidth */
+ hcd->core_if->queuing_high_bandwidth = 0;
+ }
+ }
+
+ if (!hcd->core_if->dma_enable) {
+ dwc_otg_core_global_regs_t *global_regs;
+ gintmsk_data_t intr_mask = {.d32 = 0 };
+
+ global_regs = hcd->core_if->core_global_regs;
+ intr_mask.b.ptxfempty = 1;
+#ifdef DEBUG
+ tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+ DWC_DEBUGPL(DBG_HCDV,
+ " P Tx Req Queue Space Avail (after queue): %d\n",
+ tx_status.b.ptxqspcavail);
+ DWC_DEBUGPL(DBG_HCDV,
+ " P Tx FIFO Space Avail (after queue): %d\n",
+ tx_status.b.ptxfspcavail);
+#endif
+ if (!DWC_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_MODIFY_REG32(&global_regs->gintmsk, 0,
+ intr_mask.d32);
+ } 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_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32,
+ 0);
+ }
+ }
+}
+
+/**
+ * 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(dwc_otg_hcd_t * hcd)
+{
+ gnptxsts_data_t tx_status;
+ dwc_list_link_t *orig_qh_ptr;
+ dwc_otg_qh_t *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ int more_to_do = 0;
+
+ dwc_otg_core_global_regs_t *global_regs =
+ hcd->core_if->core_global_regs;
+
+ DWC_DEBUGPL(DBG_HCDV, "Queue non-periodic transactions\n");
+#ifdef DEBUG
+ tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+ DWC_DEBUGPL(DBG_HCDV,
+ " NP Tx Req Queue Space Avail (before queue): %d\n",
+ tx_status.b.nptxqspcavail);
+ DWC_DEBUGPL(DBG_HCDV, " NP Tx FIFO Space Avail (before queue): %d\n",
+ tx_status.b.nptxfspcavail);
+#endif
+ /*
+ * 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.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+ if (!hcd->core_if->dma_enable && tx_status.b.nptxqspcavail == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t,
+ qh_list_entry);
+
+ if(fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) {
+ fiq_fsm_queue_split_transaction(hcd, qh);
+ } else {
+ status = queue_transaction(hcd, qh->channel,
+ tx_status.b.nptxfspcavail);
+
+ 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) {
+ gintmsk_data_t intr_mask = {.d32 = 0 };
+ intr_mask.b.nptxfempty = 1;
+
+#ifdef DEBUG
+ tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+ DWC_DEBUGPL(DBG_HCDV,
+ " NP Tx Req Queue Space Avail (after queue): %d\n",
+ tx_status.b.nptxqspcavail);
+ DWC_DEBUGPL(DBG_HCDV,
+ " NP Tx FIFO Space Avail (after queue): %d\n",
+ tx_status.b.nptxfspcavail);
+#endif
+ 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_MODIFY_REG32(&global_regs->gintmsk, 0,
+ intr_mask.d32);
+ } 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_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32,
+ 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.
+ *
+ * @param hcd The HCD state structure.
+ * @param tr_type The type(s) of transactions to queue (non-periodic,
+ * periodic, or both).
+ */
+void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd,
+ dwc_otg_transaction_type_e tr_type)
+{
+#ifdef DEBUG_SOF
+ DWC_DEBUGPL(DBG_HCD, "Queue Transactions\n");
+#endif
+ /* Process host channels associated with periodic transfers. */
+ if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) &&
+ !DWC_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 (!DWC_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.
+ */
+ gintmsk_data_t gintmsk = {.d32 = 0 };
+ gintmsk.b.nptxfempty = 1;
+
+ if (fiq_enable) {
+ local_fiq_disable();
+ fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+ DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+ fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+ local_fiq_enable();
+ } else {
+ DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+ }
+ }
+ }
+}
+
+#ifdef DWC_HS_ELECT_TST
+/*
+ * Quick and dirty hack to implement the HS Electrical Test
+ * SINGLE_STEP_GET_DEVICE_DESCRIPTOR feature.
+ *
+ * This code was copied from our userspace app "hset". It sends a
+ * Get Device Descriptor control sequence in two parts, first the
+ * Setup packet by itself, followed some time later by the In and
+ * Ack packets. Rather than trying to figure out how to add this
+ * functionality to the normal driver code, we just hijack the
+ * hardware, using these two function to drive the hardware
+ * directly.
+ */
+
+static dwc_otg_core_global_regs_t *global_regs;
+static dwc_otg_host_global_regs_t *hc_global_regs;
+static dwc_otg_hc_regs_t *hc_regs;
+static uint32_t *data_fifo;
+
+static void do_setup(void)
+{
+ gintsts_data_t gintsts;
+ hctsiz_data_t hctsiz;
+ hcchar_data_t hcchar;
+ haint_data_t haint;
+ hcint_data_t hcint;
+
+ /* Enable HAINTs */
+ DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001);
+
+ /* Enable HCINTs */
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /*
+ * Send Setup packet (Get Device Descriptor)
+ */
+
+ /* Make sure channel is disabled */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ if (hcchar.b.chen) {
+ hcchar.b.chdis = 1;
+// hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+ //sleep(1);
+ dwc_mdelay(1000);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ }
+
+ /* Set HCTSIZ */
+ hctsiz.d32 = 0;
+ hctsiz.b.xfersize = 8;
+ hctsiz.b.pktcnt = 1;
+ hctsiz.b.pid = DWC_OTG_HC_PID_SETUP;
+ DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+ /* Set HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+ hcchar.b.epdir = 0;
+ hcchar.b.epnum = 0;
+ hcchar.b.mps = 8;
+ hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+ /* Fill FIFO with Setup data for Get Device Descriptor */
+ data_fifo = (uint32_t *) ((char *)global_regs + 0x1000);
+ DWC_WRITE_REG32(data_fifo++, 0x01000680);
+ DWC_WRITE_REG32(data_fifo++, 0x00080000);
+
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Wait for host channel interrupt */
+ do {
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+ } while (gintsts.b.hcintr == 0);
+
+ /* Disable HCINTs */
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000);
+
+ /* Disable HAINTs */
+ DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+}
+
+static void do_in_ack(void)
+{
+ gintsts_data_t gintsts;
+ hctsiz_data_t hctsiz;
+ hcchar_data_t hcchar;
+ haint_data_t haint;
+ hcint_data_t hcint;
+ host_grxsts_data_t grxsts;
+
+ /* Enable HAINTs */
+ DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001);
+
+ /* Enable HCINTs */
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /*
+ * Receive Control In packet
+ */
+
+ /* Make sure channel is disabled */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ if (hcchar.b.chen) {
+ hcchar.b.chdis = 1;
+ hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+ //sleep(1);
+ dwc_mdelay(1000);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ }
+
+ /* Set HCTSIZ */
+ hctsiz.d32 = 0;
+ hctsiz.b.xfersize = 8;
+ hctsiz.b.pktcnt = 1;
+ hctsiz.b.pid = DWC_OTG_HC_PID_DATA1;
+ DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+ /* Set HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+ hcchar.b.epdir = 1;
+ hcchar.b.epnum = 0;
+ hcchar.b.mps = 8;
+ hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Wait for receive status queue interrupt */
+ do {
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+ } while (gintsts.b.rxstsqlvl == 0);
+
+ /* Read RXSTS */
+ grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp);
+
+ /* Clear RXSTSQLVL in GINTSTS */
+ gintsts.d32 = 0;
+ gintsts.b.rxstsqlvl = 1;
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ switch (grxsts.b.pktsts) {
+ case DWC_GRXSTS_PKTSTS_IN:
+ /* Read the data into the host buffer */
+ if (grxsts.b.bcnt > 0) {
+ int i;
+ int word_count = (grxsts.b.bcnt + 3) / 4;
+
+ data_fifo = (uint32_t *) ((char *)global_regs + 0x1000);
+
+ for (i = 0; i < word_count; i++) {
+ (void)DWC_READ_REG32(data_fifo++);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Wait for receive status queue interrupt */
+ do {
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+ } while (gintsts.b.rxstsqlvl == 0);
+
+ /* Read RXSTS */
+ grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp);
+
+ /* Clear RXSTSQLVL in GINTSTS */
+ gintsts.d32 = 0;
+ gintsts.b.rxstsqlvl = 1;
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ switch (grxsts.b.pktsts) {
+ case DWC_GRXSTS_PKTSTS_IN_XFER_COMP:
+ break;
+
+ default:
+ break;
+ }
+
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Wait for host channel interrupt */
+ do {
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+ } while (gintsts.b.hcintr == 0);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+// usleep(100000);
+// mdelay(100);
+ dwc_mdelay(1);
+
+ /*
+ * Send handshake packet
+ */
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Make sure channel is disabled */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ if (hcchar.b.chen) {
+ hcchar.b.chdis = 1;
+ hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+ //sleep(1);
+ dwc_mdelay(1000);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ }
+
+ /* Set HCTSIZ */
+ hctsiz.d32 = 0;
+ hctsiz.b.xfersize = 0;
+ hctsiz.b.pktcnt = 1;
+ hctsiz.b.pid = DWC_OTG_HC_PID_DATA1;
+ DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+ /* Set HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+ hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+ hcchar.b.epdir = 0;
+ hcchar.b.epnum = 0;
+ hcchar.b.mps = 8;
+ hcchar.b.chen = 1;
+ DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+ /* Wait for host channel interrupt */
+ do {
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+ } while (gintsts.b.hcintr == 0);
+
+ /* Disable HCINTs */
+ DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000);
+
+ /* Disable HAINTs */
+ DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000);
+
+ /* Read HAINT */
+ haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+ /* Read HCINT */
+ hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+ /* Read HCCHAR */
+ hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+ /* Clear HCINT */
+ DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+ /* Clear HAINT */
+ DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+ /* Clear GINTSTS */
+ DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+ /* Read GINTSTS */
+ gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+}
+#endif
+
+/** Handles hub class-specific requests. */
+int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd,
+ uint16_t typeReq,
+ uint16_t wValue,
+ uint16_t wIndex, uint8_t * buf, uint16_t wLength)
+{
+ int retval = 0;
+
+ dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if;
+ usb_hub_descriptor_t *hub_desc;
+ hprt0_data_t hprt0 = {.d32 = 0 };
+
+ uint32_t port_status;
+
+ switch (typeReq) {
+ case UCR_CLEAR_HUB_FEATURE:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearHubFeature 0x%x\n", wValue);
+ switch (wValue) {
+ case UHF_C_HUB_LOCAL_POWER:
+ case UHF_C_HUB_OVER_CURRENT:
+ /* Nothing required here */
+ break;
+ default:
+ retval = -DWC_E_INVALID;
+ DWC_ERROR("DWC OTG HCD - "
+ "ClearHubFeature request %xh unknown\n",
+ wValue);
+ }
+ break;
+ case UCR_CLEAR_PORT_FEATURE:
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ if (wValue != UHF_PORT_L1)
+#endif
+ if (!wIndex || wIndex > 1)
+ goto error;
+
+ switch (wValue) {
+ case UHF_PORT_ENABLE:
+ DWC_DEBUGPL(DBG_ANY, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_ENABLE\n");
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtena = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ break;
+ case UHF_PORT_SUSPEND:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_SUSPEND\n");
+
+ if (core_if->power_down == 2) {
+ dwc_otg_host_hibernation_restore(core_if, 0, 0);
+ } else {
+ DWC_WRITE_REG32(core_if->pcgcctl, 0);
+ dwc_mdelay(5);
+
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtres = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ hprt0.b.prtsusp = 0;
+ /* Clear Resume bit */
+ dwc_mdelay(100);
+ hprt0.b.prtres = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ }
+ break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ case UHF_PORT_L1:
+ {
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ glpmcfg_data_t lpmcfg = {.d32 = 0 };
+
+ lpmcfg.d32 =
+ DWC_READ_REG32(&core_if->
+ core_global_regs->glpmcfg);
+ lpmcfg.b.en_utmi_sleep = 0;
+ lpmcfg.b.hird_thres &= (~(1 << 4));
+ lpmcfg.b.prt_sleep_sts = 1;
+ DWC_WRITE_REG32(&core_if->
+ core_global_regs->glpmcfg,
+ lpmcfg.d32);
+
+ /* Clear Enbl_L1Gating bit. */
+ pcgcctl.b.enbl_sleep_gating = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32,
+ 0);
+
+ dwc_mdelay(5);
+
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtres = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0,
+ hprt0.d32);
+ /* This bit will be cleared in wakeup interrupt handle */
+ break;
+ }
+#endif
+ case UHF_PORT_POWER:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_POWER\n");
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtpwr = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ break;
+ case UHF_PORT_INDICATOR:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_INDICATOR\n");
+ /* Port inidicator not supported */
+ break;
+ case UHF_C_PORT_CONNECTION:
+ /* Clears drivers internal connect status change
+ * flag */
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n");
+ dwc_otg_hcd->flags.b.port_connect_status_change = 0;
+ break;
+ case UHF_C_PORT_RESET:
+ /* Clears the driver's internal Port Reset Change
+ * flag */
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_C_RESET\n");
+ dwc_otg_hcd->flags.b.port_reset_change = 0;
+ break;
+ case UHF_C_PORT_ENABLE:
+ /* Clears the driver's internal Port
+ * Enable/Disable Change flag */
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n");
+ dwc_otg_hcd->flags.b.port_enable_change = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ /* Clears the driver's internal Port Suspend
+ * Change flag, which is set when resume signaling on
+ * the host port is complete */
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n");
+ dwc_otg_hcd->flags.b.port_suspend_change = 0;
+ break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ case UHF_C_PORT_L1:
+ dwc_otg_hcd->flags.b.port_l1_change = 0;
+ break;
+#endif
+ case UHF_C_PORT_OVER_CURRENT:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n");
+ dwc_otg_hcd->flags.b.port_over_current_change = 0;
+ break;
+ default:
+ retval = -DWC_E_INVALID;
+ DWC_ERROR("DWC OTG HCD - "
+ "ClearPortFeature request %xh "
+ "unknown or unsupported\n", wValue);
+ }
+ break;
+ case UCR_GET_HUB_DESCRIPTOR:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "GetHubDescriptor\n");
+ hub_desc = (usb_hub_descriptor_t *) buf;
+ hub_desc->bDescLength = 9;
+ hub_desc->bDescriptorType = 0x29;
+ hub_desc->bNbrPorts = 1;
+ USETW(hub_desc->wHubCharacteristics, 0x08);
+ hub_desc->bPwrOn2PwrGood = 1;
+ hub_desc->bHubContrCurrent = 0;
+ hub_desc->DeviceRemovable[0] = 0;
+ hub_desc->DeviceRemovable[1] = 0xff;
+ break;
+ case UCR_GET_HUB_STATUS:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "GetHubStatus\n");
+ DWC_MEMSET(buf, 0, 4);
+ break;
+ case UCR_GET_PORT_STATUS:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "GetPortStatus wIndex = 0x%04x FLAGS=0x%08x\n",
+ wIndex, dwc_otg_hcd->flags.d32);
+ if (!wIndex || wIndex > 1)
+ goto error;
+
+ port_status = 0;
+
+ if (dwc_otg_hcd->flags.b.port_connect_status_change)
+ port_status |= (1 << UHF_C_PORT_CONNECTION);
+
+ if (dwc_otg_hcd->flags.b.port_enable_change)
+ port_status |= (1 << UHF_C_PORT_ENABLE);
+
+ if (dwc_otg_hcd->flags.b.port_suspend_change)
+ port_status |= (1 << UHF_C_PORT_SUSPEND);
+
+ if (dwc_otg_hcd->flags.b.port_l1_change)
+ port_status |= (1 << UHF_C_PORT_L1);
+
+ if (dwc_otg_hcd->flags.b.port_reset_change) {
+ port_status |= (1 << UHF_C_PORT_RESET);
+ }
+
+ if (dwc_otg_hcd->flags.b.port_over_current_change) {
+ DWC_WARN("Overcurrent change detected\n");
+ port_status |= (1 << UHF_C_PORT_OVER_CURRENT);
+ }
+
+ if (!dwc_otg_hcd->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) = dwc_cpu_to_le32(&port_status);
+ break;
+ }
+
+ hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+ DWC_DEBUGPL(DBG_HCDV, " HPRT0: 0x%08x\n", hprt0.d32);
+
+ if (hprt0.b.prtconnsts)
+ port_status |= (1 << UHF_PORT_CONNECTION);
+
+ if (hprt0.b.prtena)
+ port_status |= (1 << UHF_PORT_ENABLE);
+
+ if (hprt0.b.prtsusp)
+ port_status |= (1 << UHF_PORT_SUSPEND);
+
+ if (hprt0.b.prtovrcurract)
+ port_status |= (1 << UHF_PORT_OVER_CURRENT);
+
+ if (hprt0.b.prtrst)
+ port_status |= (1 << UHF_PORT_RESET);
+
+ if (hprt0.b.prtpwr)
+ port_status |= (1 << UHF_PORT_POWER);
+
+ if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+ port_status |= (1 << UHF_PORT_HIGH_SPEED);
+ else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED)
+ port_status |= (1 << UHF_PORT_LOW_SPEED);
+
+ if (hprt0.b.prttstctl)
+ port_status |= (1 << UHF_PORT_TEST);
+ if (dwc_otg_get_lpm_portsleepstatus(dwc_otg_hcd->core_if)) {
+ port_status |= (1 << UHF_PORT_L1);
+ }
+ /*
+ For Synopsys HW emulation of Power down wkup_control asserts the
+ hreset_n and prst_n on suspned. This causes the HPRT0 to be zero.
+ We intentionally tell the software that port is in L2Suspend state.
+ Only for STE.
+ */
+ if ((core_if->power_down == 2)
+ && (core_if->hibernation_suspend == 1)) {
+ port_status |= (1 << UHF_PORT_SUSPEND);
+ }
+ /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+
+ *((__le32 *) buf) = dwc_cpu_to_le32(&port_status);
+
+ break;
+ case UCR_SET_HUB_FEATURE:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "SetHubFeature\n");
+ /* No HUB features supported */
+ break;
+ case UCR_SET_PORT_FEATURE:
+ if (wValue != UHF_PORT_TEST && (!wIndex || wIndex > 1))
+ goto error;
+
+ if (!dwc_otg_hcd->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 UHF_PORT_SUSPEND:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "SetPortFeature - USB_PORT_FEAT_SUSPEND\n");
+ if (dwc_otg_hcd_otg_port(dwc_otg_hcd) != wIndex) {
+ goto error;
+ }
+ if (core_if->power_down == 2) {
+ int timeout = 300;
+ dwc_irqflags_t flags;
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ gpwrdn_data_t gpwrdn = {.d32 = 0 };
+ gusbcfg_data_t gusbcfg = {.d32 = 0 };
+#ifdef DWC_DEV_SRPCAP
+ int32_t otg_cap_param = core_if->core_params->otg_cap;
+#endif
+ DWC_PRINTF("Preparing for complete power-off\n");
+
+ /* Save registers before hibernation */
+ dwc_otg_save_global_regs(core_if);
+ dwc_otg_save_host_regs(core_if);
+
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtsusp = 1;
+ hprt0.b.prtena = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ /* Spin hprt0.b.prtsusp to became 1 */
+ do {
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ if (hprt0.b.prtsusp) {
+ break;
+ }
+ dwc_mdelay(1);
+ } while (--timeout);
+ if (!timeout) {
+ DWC_WARN("Suspend wasn't genereted\n");
+ }
+ dwc_udelay(10);
+
+ /*
+ * We need to disable interrupts to prevent servicing of any IRQ
+ * during going to hibernation
+ */
+ DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+ core_if->lx_state = DWC_OTG_L2;
+#ifdef DWC_DEV_SRPCAP
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtpwr = 0;
+ hprt0.b.prtena = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0,
+ hprt0.d32);
+#endif
+ gusbcfg.d32 =
+ DWC_READ_REG32(&core_if->core_global_regs->
+ gusbcfg);
+ if (gusbcfg.b.ulpi_utmi_sel == 1) {
+ /* ULPI interface */
+ /* Suspend the Phy Clock */
+ pcgcctl.d32 = 0;
+ pcgcctl.b.stoppclk = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+ pcgcctl.d32);
+ dwc_udelay(10);
+ gpwrdn.b.pmuactv = 1;
+ DWC_MODIFY_REG32(&core_if->
+ core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+ } else {
+ /* UTMI+ Interface */
+ gpwrdn.b.pmuactv = 1;
+ DWC_MODIFY_REG32(&core_if->
+ core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+ dwc_udelay(10);
+ pcgcctl.b.stoppclk = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+ dwc_udelay(10);
+ }
+#ifdef DWC_DEV_SRPCAP
+ gpwrdn.d32 = 0;
+ gpwrdn.b.dis_vbus = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+#endif
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pmuintsel = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+ dwc_udelay(10);
+
+ gpwrdn.d32 = 0;
+#ifdef DWC_DEV_SRPCAP
+ gpwrdn.b.srp_det_msk = 1;
+#endif
+ gpwrdn.b.disconn_det_msk = 1;
+ gpwrdn.b.lnstchng_msk = 1;
+ gpwrdn.b.sts_chngint_msk = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+ dwc_udelay(10);
+
+ /* Enable Power Down Clamp and all interrupts in GPWRDN */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pwrdnclmp = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+ dwc_udelay(10);
+
+ /* Switch off VDD */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pwrdnswtch = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+
+#ifdef DWC_DEV_SRPCAP
+ if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE)
+ {
+ core_if->pwron_timer_started = 1;
+ DWC_TIMER_SCHEDULE(core_if->pwron_timer, 6000 /* 6 secs */ );
+ }
+#endif
+ /* Save gpwrdn register for further usage if stschng interrupt */
+ core_if->gr_backup->gpwrdn_local =
+ DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+
+ /* Set flag to indicate that we are in hibernation */
+ core_if->hibernation_suspend = 1;
+ DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock,flags);
+
+ DWC_PRINTF("Host hibernation completed\n");
+ // Exit from case statement
+ break;
+
+ }
+ if (dwc_otg_hcd_otg_port(dwc_otg_hcd) == wIndex &&
+ dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) {
+ gotgctl_data_t gotgctl = {.d32 = 0 };
+ gotgctl.b.hstsethnpen = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gotgctl, 0, gotgctl.d32);
+ core_if->op_state = A_SUSPEND;
+ }
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtsusp = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ {
+ dwc_irqflags_t flags;
+ /* Update lx_state */
+ DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+ core_if->lx_state = DWC_OTG_L2;
+ DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
+ }
+ /* Suspend the Phy Clock */
+ {
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ pcgcctl.b.stoppclk = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+ pcgcctl.d32);
+ dwc_udelay(10);
+ }
+
+ /* For HNP the bus must be suspended for at least 200ms. */
+ if (dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) {
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ pcgcctl.b.stoppclk = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+ dwc_mdelay(200);
+ }
+
+ /** @todo - check how sw can wait for 1 sec to check asesvld??? */
+#if 0 //vahrama !!!!!!!!!!!!!!!!!!
+ if (core_if->adp_enable) {
+ gotgctl_data_t gotgctl = {.d32 = 0 };
+ gpwrdn_data_t gpwrdn;
+
+ while (gotgctl.b.asesvld == 1) {
+ gotgctl.d32 =
+ DWC_READ_REG32(&core_if->
+ core_global_regs->
+ gotgctl);
+ dwc_mdelay(100);
+ }
+
+ /* Enable Power Down Logic */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.pmuactv = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+
+ /* Unmask SRP detected interrupt from Power Down Logic */
+ gpwrdn.d32 = 0;
+ gpwrdn.b.srp_det_msk = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->
+ gpwrdn, 0, gpwrdn.d32);
+
+ dwc_otg_adp_probe_start(core_if);
+ }
+#endif
+ break;
+ case UHF_PORT_POWER:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "SetPortFeature - USB_PORT_FEAT_POWER\n");
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtpwr = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ break;
+ case UHF_PORT_RESET:
+ if ((core_if->power_down == 2)
+ && (core_if->hibernation_suspend == 1)) {
+ /* If we are going to exit from Hibernated
+ * state via USB RESET.
+ */
+ dwc_otg_host_hibernation_restore(core_if, 0, 1);
+ } else {
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+
+ DWC_DEBUGPL(DBG_HCD,
+ "DWC OTG HCD HUB CONTROL - "
+ "SetPortFeature - USB_PORT_FEAT_RESET\n");
+ {
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ pcgcctl.b.enbl_sleep_gating = 1;
+ pcgcctl.b.stoppclk = 1;
+ DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+ DWC_WRITE_REG32(core_if->pcgcctl, 0);
+ }
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ {
+ glpmcfg_data_t lpmcfg;
+ lpmcfg.d32 =
+ DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+ if (lpmcfg.b.prt_sleep_sts) {
+ lpmcfg.b.en_utmi_sleep = 0;
+ lpmcfg.b.hird_thres &= (~(1 << 4));
+ DWC_WRITE_REG32
+ (&core_if->core_global_regs->glpmcfg,
+ lpmcfg.d32);
+ dwc_mdelay(1);
+ }
+ }
+#endif
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ /* Clear suspend bit if resetting from suspended state. */
+ hprt0.b.prtsusp = 0;
+ /* 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 (!dwc_otg_hcd_is_b_host(dwc_otg_hcd)) {
+ hprt0.b.prtpwr = 1;
+ hprt0.b.prtrst = 1;
+ DWC_PRINTF("Indeed it is in host mode hprt0 = %08x\n",hprt0.d32);
+ DWC_WRITE_REG32(core_if->host_if->hprt0,
+ hprt0.d32);
+ }
+ /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+ dwc_mdelay(60);
+ hprt0.b.prtrst = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ core_if->lx_state = DWC_OTG_L0; /* Now back to the on state */
+ }
+ break;
+#ifdef DWC_HS_ELECT_TST
+ case UHF_PORT_TEST:
+ {
+ uint32_t t;
+ gintmsk_data_t gintmsk;
+
+ t = (wIndex >> 8); /* MSB wIndex USB */
+ DWC_DEBUGPL(DBG_HCD,
+ "DWC OTG HCD HUB CONTROL - "
+ "SetPortFeature - USB_PORT_FEAT_TEST %d\n",
+ t);
+ DWC_WARN("USB_PORT_FEAT_TEST %d\n", t);
+ if (t < 6) {
+ hprt0.d32 = dwc_otg_read_hprt0(core_if);
+ hprt0.b.prttstctl = t;
+ DWC_WRITE_REG32(core_if->host_if->hprt0,
+ hprt0.d32);
+ } else {
+ /* Setup global vars with reg addresses (quick and
+ * dirty hack, should be cleaned up)
+ */
+ global_regs = core_if->core_global_regs;
+ hc_global_regs =
+ core_if->host_if->host_global_regs;
+ hc_regs =
+ (dwc_otg_hc_regs_t *) ((char *)
+ global_regs +
+ 0x500);
+ data_fifo =
+ (uint32_t *) ((char *)global_regs +
+ 0x1000);
+
+ if (t == 6) { /* HS_HOST_PORT_SUSPEND_RESUME */
+ /* Save current interrupt mask */
+ gintmsk.d32 =
+ DWC_READ_REG32
+ (&global_regs->gintmsk);
+
+ /* Disable all interrupts while we muck with
+ * the hardware directly
+ */
+ DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+ /* 15 second delay per the test spec */
+ dwc_mdelay(15000);
+
+ /* Drive suspend on the root port */
+ hprt0.d32 =
+ dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtsusp = 1;
+ hprt0.b.prtres = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+ /* 15 second delay per the test spec */
+ dwc_mdelay(15000);
+
+ /* Drive resume on the root port */
+ hprt0.d32 =
+ dwc_otg_read_hprt0(core_if);
+ hprt0.b.prtsusp = 0;
+ hprt0.b.prtres = 1;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+ dwc_mdelay(100);
+
+ /* Clear the resume bit */
+ hprt0.b.prtres = 0;
+ DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+ /* Restore interrupts */
+ DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+ } else if (t == 7) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */
+ /* Save current interrupt mask */
+ gintmsk.d32 =
+ DWC_READ_REG32
+ (&global_regs->gintmsk);
+
+ /* Disable all interrupts while we muck with
+ * the hardware directly
+ */
+ DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+ /* 15 second delay per the test spec */
+ dwc_mdelay(15000);
+
+ /* Send the Setup packet */
+ do_setup();
+
+ /* 15 second delay so nothing else happens for awhile */
+ dwc_mdelay(15000);
+
+ /* Restore interrupts */
+ DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+ } else if (t == 8) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */
+ /* Save current interrupt mask */
+ gintmsk.d32 =
+ DWC_READ_REG32
+ (&global_regs->gintmsk);
+
+ /* Disable all interrupts while we muck with
+ * the hardware directly
+ */
+ DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+ /* Send the Setup packet */
+ do_setup();
+
+ /* 15 second delay so nothing else happens for awhile */
+ dwc_mdelay(15000);
+
+ /* Send the In and Ack packets */
+ do_in_ack();
+
+ /* 15 second delay so nothing else happens for awhile */
+ dwc_mdelay(15000);
+
+ /* Restore interrupts */
+ DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+ }
+ }
+ break;
+ }
+#endif /* DWC_HS_ELECT_TST */
+
+ case UHF_PORT_INDICATOR:
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+ "SetPortFeature - USB_PORT_FEAT_INDICATOR\n");
+ /* Not supported */
+ break;
+ default:
+ retval = -DWC_E_INVALID;
+ DWC_ERROR("DWC OTG HCD - "
+ "SetPortFeature request %xh "
+ "unknown or unsupported\n", wValue);
+ break;
+ }
+ break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ case UCR_SET_AND_TEST_PORT_FEATURE:
+ if (wValue != UHF_PORT_L1) {
+ goto error;
+ }
+ {
+ int portnum, hird, devaddr, remwake;
+ glpmcfg_data_t lpmcfg;
+ uint32_t time_usecs;
+ gintsts_data_t gintsts;
+ gintmsk_data_t gintmsk;
+
+ if (!dwc_otg_get_param_lpm_enable(core_if)) {
+ goto error;
+ }
+ if (wValue != UHF_PORT_L1 || wLength != 1) {
+ goto error;
+ }
+ /* Check if the port currently is in SLEEP state */
+ lpmcfg.d32 =
+ DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+ if (lpmcfg.b.prt_sleep_sts) {
+ DWC_INFO("Port is already in sleep mode\n");
+ buf[0] = 0; /* Return success */
+ break;
+ }
+
+ portnum = wIndex & 0xf;
+ hird = (wIndex >> 4) & 0xf;
+ devaddr = (wIndex >> 8) & 0x7f;
+ remwake = (wIndex >> 15);
+
+ if (portnum != 1) {
+ retval = -DWC_E_INVALID;
+ DWC_WARN
+ ("Wrong port number(%d) in SetandTestPortFeature request\n",
+ portnum);
+ break;
+ }
+
+ DWC_PRINTF
+ ("SetandTestPortFeature request: portnum = %d, hird = %d, devaddr = %d, rewake = %d\n",
+ portnum, hird, devaddr, remwake);
+ /* Disable LPM interrupt */
+ gintmsk.d32 = 0;
+ gintmsk.b.lpmtranrcvd = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk,
+ gintmsk.d32, 0);
+
+ if (dwc_otg_hcd_send_lpm
+ (dwc_otg_hcd, devaddr, hird, remwake)) {
+ retval = -DWC_E_INVALID;
+ break;
+ }
+
+ time_usecs = 10 * (lpmcfg.b.retry_count + 1);
+ /* We will consider timeout if time_usecs microseconds pass,
+ * and we don't receive LPM transaction status.
+ * After receiving non-error responce(ACK/NYET/STALL) from device,
+ * core will set lpmtranrcvd bit.
+ */
+ do {
+ gintsts.d32 =
+ DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+ if (gintsts.b.lpmtranrcvd) {
+ break;
+ }
+ dwc_udelay(1);
+ } while (--time_usecs);
+ /* lpm_int bit will be cleared in LPM interrupt handler */
+
+ /* Now fill status
+ * 0x00 - Success
+ * 0x10 - NYET
+ * 0x11 - Timeout
+ */
+ if (!gintsts.b.lpmtranrcvd) {
+ buf[0] = 0x3; /* Completion code is Timeout */
+ dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd);
+ } else {
+ lpmcfg.d32 =
+ DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+ if (lpmcfg.b.lpm_resp == 0x3) {
+ /* ACK responce from the device */
+ buf[0] = 0x00; /* Success */
+ } else if (lpmcfg.b.lpm_resp == 0x2) {
+ /* NYET responce from the device */
+ buf[0] = 0x2;
+ } else {
+ /* Otherwise responce with Timeout */
+ buf[0] = 0x3;
+ }
+ }
+ DWC_PRINTF("Device responce to LPM trans is %x\n",
+ lpmcfg.b.lpm_resp);
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0,
+ gintmsk.d32);
+
+ break;
+ }
+#endif /* CONFIG_USB_DWC_OTG_LPM */
+ default:
+error:
+ retval = -DWC_E_INVALID;
+ DWC_WARN("DWC OTG HCD - "
+ "Unknown hub control request type or invalid typeReq: %xh wIndex: %xh wValue: %xh\n",
+ typeReq, wIndex, wValue);
+ break;
+ }
+
+ return retval;
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/** Returns index of host channel to perform LPM transaction. */
+int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, uint8_t devaddr)
+{
+ dwc_otg_core_if_t *core_if = hcd->core_if;
+ dwc_hc_t *hc;
+ hcchar_data_t hcchar;
+ gintmsk_data_t gintmsk = {.d32 = 0 };
+
+ if (DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+ DWC_PRINTF("No free channel to select for LPM transaction\n");
+ return -1;
+ }
+
+ hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list);
+
+ /* Mask host channel interrupts. */
+ gintmsk.b.hcintr = 1;
+ DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+
+ /* Fill fields that core needs for LPM transaction */
+ hcchar.b.devaddr = devaddr;
+ hcchar.b.epnum = 0;
+ hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+ hcchar.b.mps = 64;
+ hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW);
+ hcchar.b.epdir = 0; /* OUT */
+ DWC_WRITE_REG32(&core_if->host_if->hc_regs[hc->hc_num]->hcchar,
+ hcchar.d32);
+
+ /* Remove the host channel from the free list. */
+ DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry);
+
+ DWC_PRINTF("hcnum = %d devaddr = %d\n", hc->hc_num, devaddr);
+
+ return hc->hc_num;
+}
+
+/** Release hc after performing LPM transaction */
+void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd)
+{
+ dwc_hc_t *hc;
+ glpmcfg_data_t lpmcfg;
+ uint8_t hc_num;
+
+ lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg);
+ hc_num = lpmcfg.b.lpm_chan_index;
+
+ hc = hcd->hc_ptr_array[hc_num];
+
+ DWC_PRINTF("Freeing channel %d after LPM\n", hc_num);
+ /* Return host channel to free list */
+ DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry);
+}
+
+int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, uint8_t hird,
+ uint8_t bRemoteWake)
+{
+ glpmcfg_data_t lpmcfg;
+ pcgcctl_data_t pcgcctl = {.d32 = 0 };
+ int channel;
+
+ channel = dwc_otg_hcd_get_hc_for_lpm_tran(hcd, devaddr);
+ if (channel < 0) {
+ return channel;
+ }
+
+ pcgcctl.b.enbl_sleep_gating = 1;
+ DWC_MODIFY_REG32(hcd->core_if->pcgcctl, 0, pcgcctl.d32);
+
+ /* Read LPM config register */
+ lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg);
+
+ /* Program LPM transaction fields */
+ lpmcfg.b.rem_wkup_en = bRemoteWake;
+ lpmcfg.b.hird = hird;
+ lpmcfg.b.hird_thres = 0x1c;
+ lpmcfg.b.lpm_chan_index = channel;
+ lpmcfg.b.en_utmi_sleep = 1;
+ /* Program LPM config register */
+ DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+
+ /* Send LPM transaction */
+ lpmcfg.b.send_lpm = 1;
+ DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+
+ return 0;
+}
+
+#endif /* CONFIG_USB_DWC_OTG_LPM */
+
+int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port)
+{
+ int retval;
+
+ if (port != 1) {
+ return -DWC_E_INVALID;
+ }
+
+ retval = (hcd->flags.b.port_connect_status_change ||
+ hcd->flags.b.port_reset_change ||
+ hcd->flags.b.port_enable_change ||
+ hcd->flags.b.port_suspend_change ||
+ hcd->flags.b.port_over_current_change);
+#ifdef DEBUG
+ if (retval) {
+ DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB STATUS DATA:"
+ " Root port status changed\n");
+ DWC_DEBUGPL(DBG_HCDV, " port_connect_status_change: %d\n",
+ hcd->flags.b.port_connect_status_change);
+ DWC_DEBUGPL(DBG_HCDV, " port_reset_change: %d\n",
+ hcd->flags.b.port_reset_change);
+ DWC_DEBUGPL(DBG_HCDV, " port_enable_change: %d\n",
+ hcd->flags.b.port_enable_change);
+ DWC_DEBUGPL(DBG_HCDV, " port_suspend_change: %d\n",
+ hcd->flags.b.port_suspend_change);
+ DWC_DEBUGPL(DBG_HCDV, " port_over_current_change: %d\n",
+ hcd->flags.b.port_over_current_change);
+ }
+#endif
+ return retval;
+}
+
+int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+ hfnum_data_t hfnum;
+ hfnum.d32 =
+ DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs->
+ hfnum);
+
+#ifdef DEBUG_SOF
+ DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD GET FRAME NUMBER %d\n",
+ hfnum.b.frnum);
+#endif
+ return hfnum.b.frnum;
+}
+
+int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd,
+ struct dwc_otg_hcd_function_ops *fops)
+{
+ int retval = 0;
+
+ hcd->fops = fops;
+ if (!dwc_otg_is_device_mode(hcd->core_if) &&
+ (!hcd->core_if->adp_enable || hcd->core_if->adp.adp_started)) {
+ dwc_otg_hcd_reinit(hcd);
+ } else {
+ retval = -DWC_E_NO_DEVICE;
+ }
+
+ return retval;
+}
+
+void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd)
+{
+ return hcd->priv;
+}
+
+void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data)
+{
+ hcd->priv = priv_data;
+}
+
+uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd)
+{
+ return hcd->otg_port;
+}
+
+uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd)
+{
+ uint32_t is_b_host;
+ if (hcd->core_if->op_state == B_HOST) {
+ is_b_host = 1;
+ } else {
+ is_b_host = 0;
+ }
+
+ return is_b_host;
+}
+
+dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd,
+ int iso_desc_count, int atomic_alloc)
+{
+ dwc_otg_hcd_urb_t *dwc_otg_urb;
+ uint32_t size;
+
+ size =
+ sizeof(*dwc_otg_urb) +
+ iso_desc_count * sizeof(struct dwc_otg_hcd_iso_packet_desc);
+ if (atomic_alloc)
+ dwc_otg_urb = DWC_ALLOC_ATOMIC(size);
+ else
+ dwc_otg_urb = DWC_ALLOC(size);
+
+ if (dwc_otg_urb)
+ dwc_otg_urb->packet_count = iso_desc_count;
+ else {
+ DWC_ERROR("**** DWC OTG HCD URB alloc - "
+ "%salloc of %db failed\n",
+ atomic_alloc?"atomic ":"", size);
+ }
+ return dwc_otg_urb;
+}
+
+void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * dwc_otg_urb,
+ uint8_t dev_addr, uint8_t ep_num,
+ uint8_t ep_type, uint8_t ep_dir, uint16_t mps)
+{
+ dwc_otg_hcd_fill_pipe(&dwc_otg_urb->pipe_info, dev_addr, ep_num,
+ ep_type, ep_dir, mps);
+#if 0
+ DWC_PRINTF
+ ("addr = %d, ep_num = %d, ep_dir = 0x%x, ep_type = 0x%x, mps = %d\n",
+ dev_addr, ep_num, ep_dir, ep_type, mps);
+#endif
+}
+
+void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * dwc_otg_urb,
+ void *urb_handle, void *buf, dwc_dma_t dma,
+ uint32_t buflen, void *setup_packet,
+ dwc_dma_t setup_dma, uint32_t flags,
+ uint16_t interval)
+{
+ dwc_otg_urb->priv = urb_handle;
+ dwc_otg_urb->buf = buf;
+ dwc_otg_urb->dma = dma;
+ dwc_otg_urb->length = buflen;
+ dwc_otg_urb->setup_packet = setup_packet;
+ dwc_otg_urb->setup_dma = setup_dma;
+ dwc_otg_urb->flags = flags;
+ dwc_otg_urb->interval = interval;
+ dwc_otg_urb->status = -DWC_E_IN_PROGRESS;
+}
+
+uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+ return dwc_otg_urb->status;
+}
+
+uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+ return dwc_otg_urb->actual_length;
+}
+
+uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+ return dwc_otg_urb->error_count;
+}
+
+void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb,
+ int desc_num, uint32_t offset,
+ uint32_t length)
+{
+ dwc_otg_urb->iso_descs[desc_num].offset = offset;
+ dwc_otg_urb->iso_descs[desc_num].length = length;
+}
+
+uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * dwc_otg_urb,
+ int desc_num)
+{
+ return dwc_otg_urb->iso_descs[desc_num].status;
+}
+
+uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t *
+ dwc_otg_urb, int desc_num)
+{
+ return dwc_otg_urb->iso_descs[desc_num].actual_length;
+}
+
+int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+ int allocated = 0;
+ dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+
+ if (qh) {
+ if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+ allocated = 1;
+ }
+ }
+ return allocated;
+}
+
+int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+ dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+ int freed = 0;
+ DWC_ASSERT(qh, "qh is not allocated\n");
+
+ if (DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+ freed = 1;
+ }
+
+ return freed;
+}
+
+uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+ dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+ DWC_ASSERT(qh, "qh is not allocated\n");
+ return qh->usecs;
+}
+
+void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd)
+{
+#ifdef DEBUG
+ int num_channels;
+ int i;
+ gnptxsts_data_t np_tx_status;
+ hptxsts_data_t p_tx_status;
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ DWC_PRINTF("\n");
+ DWC_PRINTF
+ ("************************************************************\n");
+ DWC_PRINTF("HCD State:\n");
+ DWC_PRINTF(" Num channels: %d\n", num_channels);
+ for (i = 0; i < num_channels; i++) {
+ dwc_hc_t *hc = hcd->hc_ptr_array[i];
+ DWC_PRINTF(" Channel %d:\n", i);
+ DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
+ hc->dev_addr, hc->ep_num, hc->ep_is_in);
+ DWC_PRINTF(" speed: %d\n", hc->speed);
+ DWC_PRINTF(" ep_type: %d\n", hc->ep_type);
+ DWC_PRINTF(" max_packet: %d\n", hc->max_packet);
+ DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start);
+ DWC_PRINTF(" multi_count: %d\n", hc->multi_count);
+ DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started);
+ DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff);
+ DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len);
+ DWC_PRINTF(" xfer_count: %d\n", hc->xfer_count);
+ DWC_PRINTF(" halt_on_queue: %d\n", hc->halt_on_queue);
+ DWC_PRINTF(" halt_pending: %d\n", hc->halt_pending);
+ DWC_PRINTF(" halt_status: %d\n", hc->halt_status);
+ DWC_PRINTF(" do_split: %d\n", hc->do_split);
+ DWC_PRINTF(" complete_split: %d\n", hc->complete_split);
+ DWC_PRINTF(" hub_addr: %d\n", hc->hub_addr);
+ DWC_PRINTF(" port_addr: %d\n", hc->port_addr);
+ DWC_PRINTF(" xact_pos: %d\n", hc->xact_pos);
+ DWC_PRINTF(" requests: %d\n", hc->requests);
+ DWC_PRINTF(" qh: %p\n", hc->qh);
+ if (hc->xfer_started) {
+ hfnum_data_t hfnum;
+ hcchar_data_t hcchar;
+ hctsiz_data_t hctsiz;
+ hcint_data_t hcint;
+ hcintmsk_data_t hcintmsk;
+ hfnum.d32 =
+ DWC_READ_REG32(&hcd->core_if->
+ host_if->host_global_regs->hfnum);
+ hcchar.d32 =
+ DWC_READ_REG32(&hcd->core_if->host_if->
+ hc_regs[i]->hcchar);
+ hctsiz.d32 =
+ DWC_READ_REG32(&hcd->core_if->host_if->
+ hc_regs[i]->hctsiz);
+ hcint.d32 =
+ DWC_READ_REG32(&hcd->core_if->host_if->
+ hc_regs[i]->hcint);
+ hcintmsk.d32 =
+ DWC_READ_REG32(&hcd->core_if->host_if->
+ hc_regs[i]->hcintmsk);
+ DWC_PRINTF(" hfnum: 0x%08x\n", hfnum.d32);
+ DWC_PRINTF(" hcchar: 0x%08x\n", hcchar.d32);
+ DWC_PRINTF(" hctsiz: 0x%08x\n", hctsiz.d32);
+ DWC_PRINTF(" hcint: 0x%08x\n", hcint.d32);
+ DWC_PRINTF(" hcintmsk: 0x%08x\n", hcintmsk.d32);
+ }
+ if (hc->xfer_started && hc->qh) {
+ dwc_otg_qtd_t *qtd;
+ dwc_otg_hcd_urb_t *urb;
+
+ DWC_CIRCLEQ_FOREACH(qtd, &hc->qh->qtd_list, qtd_list_entry) {
+ if (!qtd->in_process)
+ break;
+
+ urb = qtd->urb;
+ DWC_PRINTF(" URB Info:\n");
+ DWC_PRINTF(" qtd: %p, urb: %p\n", qtd, urb);
+ if (urb) {
+ DWC_PRINTF(" Dev: %d, EP: %d %s\n",
+ dwc_otg_hcd_get_dev_addr(&urb->
+ pipe_info),
+ dwc_otg_hcd_get_ep_num(&urb->
+ pipe_info),
+ dwc_otg_hcd_is_pipe_in(&urb->
+ pipe_info) ?
+ "IN" : "OUT");
+ DWC_PRINTF(" Max packet size: %d\n",
+ dwc_otg_hcd_get_mps(&urb->
+ pipe_info));
+ DWC_PRINTF(" transfer_buffer: %p\n",
+ urb->buf);
+ DWC_PRINTF(" transfer_dma: %p\n",
+ (void *)urb->dma);
+ DWC_PRINTF(" transfer_buffer_length: %d\n",
+ urb->length);
+ DWC_PRINTF(" actual_length: %d\n",
+ urb->actual_length);
+ }
+ }
+ }
+ }
+ DWC_PRINTF(" non_periodic_channels: %d\n", hcd->non_periodic_channels);
+ DWC_PRINTF(" periodic_channels: %d\n", hcd->periodic_channels);
+ DWC_PRINTF(" periodic_usecs: %d\n", hcd->periodic_usecs);
+ np_tx_status.d32 =
+ DWC_READ_REG32(&hcd->core_if->core_global_regs->gnptxsts);
+ DWC_PRINTF(" NP Tx Req Queue Space Avail: %d\n",
+ np_tx_status.b.nptxqspcavail);
+ DWC_PRINTF(" NP Tx FIFO Space Avail: %d\n",
+ np_tx_status.b.nptxfspcavail);
+ p_tx_status.d32 =
+ DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hptxsts);
+ DWC_PRINTF(" P Tx Req Queue Space Avail: %d\n",
+ p_tx_status.b.ptxqspcavail);
+ DWC_PRINTF(" P Tx FIFO Space Avail: %d\n", p_tx_status.b.ptxfspcavail);
+ dwc_otg_hcd_dump_frrem(hcd);
+ dwc_otg_dump_global_registers(hcd->core_if);
+ dwc_otg_dump_host_registers(hcd->core_if);
+ DWC_PRINTF
+ ("************************************************************\n");
+ DWC_PRINTF("\n");
+#endif
+}
+
+#ifdef DEBUG
+void dwc_print_setup_data(uint8_t * setup)
+{
+ int i;
+ if (CHK_DEBUG_LEVEL(DBG_HCD)) {
+ DWC_PRINTF("Setup Data = MSB ");
+ for (i = 7; i >= 0; i--)
+ DWC_PRINTF("%02x ", setup[i]);
+ DWC_PRINTF("\n");
+ DWC_PRINTF(" bmRequestType Tranfer = %s\n",
+ (setup[0] & 0x80) ? "Device-to-Host" :
+ "Host-to-Device");
+ DWC_PRINTF(" bmRequestType Type = ");
+ switch ((setup[0] & 0x60) >> 5) {
+ case 0:
+ DWC_PRINTF("Standard\n");
+ break;
+ case 1:
+ DWC_PRINTF("Class\n");
+ break;
+ case 2:
+ DWC_PRINTF("Vendor\n");
+ break;
+ case 3:
+ DWC_PRINTF("Reserved\n");
+ break;
+ }
+ DWC_PRINTF(" bmRequestType Recipient = ");
+ switch (setup[0] & 0x1f) {
+ case 0:
+ DWC_PRINTF("Device\n");
+ break;
+ case 1:
+ DWC_PRINTF("Interface\n");
+ break;
+ case 2:
+ DWC_PRINTF("Endpoint\n");
+ break;
+ case 3:
+ DWC_PRINTF("Other\n");
+ break;
+ default:
+ DWC_PRINTF("Reserved\n");
+ break;
+ }
+ DWC_PRINTF(" bRequest = 0x%0x\n", setup[1]);
+ DWC_PRINTF(" wValue = 0x%0x\n", *((uint16_t *) & setup[2]));
+ DWC_PRINTF(" wIndex = 0x%0x\n", *((uint16_t *) & setup[4]));
+ DWC_PRINTF(" wLength = 0x%0x\n\n", *((uint16_t *) & setup[6]));
+ }
+}
+#endif
+
+void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd)
+{
+#if 0
+ DWC_PRINTF("Frame remaining at SOF:\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->frrem_samples, hcd->frrem_accum,
+ (hcd->frrem_samples > 0) ?
+ hcd->frrem_accum / hcd->frrem_samples : 0);
+
+ DWC_PRINTF("\n");
+ DWC_PRINTF("Frame remaining at start_transfer (uframe 7):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->core_if->hfnum_7_samples,
+ hcd->core_if->hfnum_7_frrem_accum,
+ (hcd->core_if->hfnum_7_samples >
+ 0) ? hcd->core_if->hfnum_7_frrem_accum /
+ hcd->core_if->hfnum_7_samples : 0);
+ DWC_PRINTF("Frame remaining at start_transfer (uframe 0):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->core_if->hfnum_0_samples,
+ hcd->core_if->hfnum_0_frrem_accum,
+ (hcd->core_if->hfnum_0_samples >
+ 0) ? hcd->core_if->hfnum_0_frrem_accum /
+ hcd->core_if->hfnum_0_samples : 0);
+ DWC_PRINTF("Frame remaining at start_transfer (uframe 1-6):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->core_if->hfnum_other_samples,
+ hcd->core_if->hfnum_other_frrem_accum,
+ (hcd->core_if->hfnum_other_samples >
+ 0) ? hcd->core_if->hfnum_other_frrem_accum /
+ hcd->core_if->hfnum_other_samples : 0);
+
+ DWC_PRINTF("\n");
+ DWC_PRINTF("Frame remaining at sample point A (uframe 7):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_7_samples_a, hcd->hfnum_7_frrem_accum_a,
+ (hcd->hfnum_7_samples_a > 0) ?
+ hcd->hfnum_7_frrem_accum_a / hcd->hfnum_7_samples_a : 0);
+ DWC_PRINTF("Frame remaining at sample point A (uframe 0):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_0_samples_a, hcd->hfnum_0_frrem_accum_a,
+ (hcd->hfnum_0_samples_a > 0) ?
+ hcd->hfnum_0_frrem_accum_a / hcd->hfnum_0_samples_a : 0);
+ DWC_PRINTF("Frame remaining at sample point A (uframe 1-6):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_other_samples_a, hcd->hfnum_other_frrem_accum_a,
+ (hcd->hfnum_other_samples_a > 0) ?
+ hcd->hfnum_other_frrem_accum_a /
+ hcd->hfnum_other_samples_a : 0);
+
+ DWC_PRINTF("\n");
+ DWC_PRINTF("Frame remaining at sample point B (uframe 7):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_7_samples_b, hcd->hfnum_7_frrem_accum_b,
+ (hcd->hfnum_7_samples_b > 0) ?
+ hcd->hfnum_7_frrem_accum_b / hcd->hfnum_7_samples_b : 0);
+ DWC_PRINTF("Frame remaining at sample point B (uframe 0):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_0_samples_b, hcd->hfnum_0_frrem_accum_b,
+ (hcd->hfnum_0_samples_b > 0) ?
+ hcd->hfnum_0_frrem_accum_b / hcd->hfnum_0_samples_b : 0);
+ DWC_PRINTF("Frame remaining at sample point B (uframe 1-6):\n");
+ DWC_PRINTF(" samples %u, accum %llu, avg %llu\n",
+ hcd->hfnum_other_samples_b, hcd->hfnum_other_frrem_accum_b,
+ (hcd->hfnum_other_samples_b > 0) ?
+ hcd->hfnum_other_frrem_accum_b /
+ hcd->hfnum_other_samples_b : 0);
+#endif
+}
+
+#endif /* DWC_DEVICE_ONLY */