* [PATCH v16 04/10]USB/ppc4xx: Add Synopsys DWC OTG HCD function
From: Rupjyoti Sarmah @ 2012-05-03 12:32 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Implements DWC OTG USB Host Controller Driver (HCD) and interface to
USB Host controller Driver framework.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/hcd.c | 2435 +++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/hcd.h | 416 +++++++++
2 files changed, 2851 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/hcd.c
create mode 100644 drivers/usb/dwc/hcd.h
diff --git a/drivers/usb/dwc/hcd.c b/drivers/usb/dwc/hcd.c
new file mode 100644
index 0000000..b237957
--- /dev/null
+++ b/drivers/usb/dwc/hcd.c
@@ -0,0 +1,2435 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This file contains the implementation of the HCD. In Linux, the HCD
+ * implements the hc_driver API.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-mapping.h>
+
+#include "hcd.h"
+
+static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";
+
+/**
+ * Clears the transfer state for a host channel. This function is normally
+ * called after a transfer is done and the host channel is being released. It
+ * clears the channel interrupt enables and any unhandled channel interrupt
+ * conditions.
+ */
+void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc)
+{
+ ulong regs;
+
+ hc->xfer_started = 0;
+ regs = core_if->host_if->hc_regs[hc->hc_num];
+ dwc_reg_write(regs, DWC_HCINTMSK, 0);
+ dwc_reg_write(regs, DWC_HCINT, 0xFFFFFFFF);
+}
+
+/**
+ * This function enables the Host mode interrupts.
+ */
+static void dwc_otg_enable_host_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Enable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * host mode.
+ *
+ * This function flushes the Tx and Rx FIFOs and it flushes any entries in the
+ * request queues. Host channels are reset to ensure that they are ready for
+ * performing transfers.
+ */
+static void dwc_otg_core_host_init(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ struct dwc_host_if *host_if = core_if->host_if;
+ struct core_params *params = core_if->core_params;
+ u32 hprt0 = 0;
+ u32 nptxfifosize = 0;
+ u32 ptxfifosize = 0;
+ u32 i;
+ u32 hcchar;
+ ulong hcfg;
+ ulong hc_regs;
+ int num_channels;
+ u32 gotgctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(core_if->pcgcctl, 0, 0);
+
+ /* Initialize Host Configuration Register */
+ init_fslspclksel(core_if);
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+ hcfg = dwc_reg_read(host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSUPP_RW(hcfg, 1);
+ dwc_reg_write(host_if->host_global_regs, DWC_HCFG, hcfg);
+ }
+
+ /* Configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2)
+ && params->enable_dynamic_fifo) {
+ /* Rx FIFO */
+ dwc_reg_write(global_regs, DWC_GRXFSIZ,
+ params->host_rx_fifo_size);
+
+ /* Non-periodic Tx FIFO */
+ nptxfifosize = DWC_TX_FIFO_DEPTH_WR(nptxfifosize,
+ params->
+ host_nperio_tx_fifo_size);
+ nptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(nptxfifosize,
+ params->host_rx_fifo_size);
+ dwc_reg_write(global_regs, DWC_GNPTXFSIZ, nptxfifosize);
+
+ /* Periodic Tx FIFO */
+ ptxfifosize = DWC_TX_FIFO_DEPTH_WR(ptxfifosize,
+ params->
+ host_perio_tx_fifo_size);
+ ptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(ptxfifosize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (nptxfifosize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (nptxfifosize)));
+ dwc_reg_write(global_regs, DWC_HPTXFSIZ, ptxfifosize);
+ }
+
+ /* Clear Host Set HNP Enable in the OTG Control Register */
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ /* Make sure the FIFOs are flushed. */
+ dwc_otg_flush_tx_fifo(core_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(core_if);
+
+ /* Flush out any leftover queued requests. */
+ num_channels = core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+ }
+
+ /* Halt all channels to put them into a known state. */
+ for (i = 0; i < num_channels; i++) {
+ int count = 0;
+
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ do {
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (++count > 200) {
+ pr_err("%s: Unable to clear halt on "
+ "channel %d\n", __func__, i);
+ break;
+ }
+ udelay(100);
+ } while (DWC_HCCHAR_ENA_RD(hcchar));
+ }
+
+ /* Turn on the vbus power. */
+ pr_info("Init: Port Power? op_state=%s\n",
+ otg_state_string(core_if->xceiv->state));
+
+ if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ pr_info("Init: Power Port (%d)\n", DWC_HPRT0_PRT_PWR_RD(hprt0));
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0) == 0) {
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(host_if->hprt0, 0, hprt0);
+ }
+ }
+ dwc_otg_enable_host_interrupts(core_if);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void hcd_reinit(struct dwc_hcd *hcd)
+{
+ struct list_head *item;
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+
+ hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+ hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+
+ /*
+ * Put all channels in the free channel list and clean up channel
+ * states.
+ */
+ item = hcd->free_hc_list.next;
+ while (item != &hcd->free_hc_list) {
+ list_del(item);
+ item = hcd->free_hc_list.next;
+ }
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ list_add_tail(&channel->hc_list_entry, &hcd->free_hc_list);
+ dwc_otg_hc_cleanup(hcd->core_if, channel);
+ }
+
+ /* Initialize the DWC core for host mode operation. */
+ dwc_otg_core_host_init(hcd->core_if);
+}
+
+/* Gets the dwc_hcd from a struct usb_hcd */
+static inline struct dwc_hcd *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
+{
+ return (struct dwc_hcd *)hcd->hcd_priv;
+}
+
+/**
+ * Initializes the DWC_otg controller and its root hub and prepares it for host
+ * mode operation. Activates the root port. Returns 0 on success and a negative
+ * error code on failure.
+*/
+
+static int dwc_otg_hcd_start(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct usb_bus *bus = hcd_to_bus(hcd);
+
+ hcd->state = HC_STATE_RUNNING;
+
+ /* Inform the HUB driver to resume. */
+ if (bus->root_hub)
+ usb_hcd_resume_root_hub(hcd);
+
+ hcd_reinit(dwc_hcd);
+ return 0;
+}
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The dwc_otg_hcd_start() must be called in a process context.
+ */
+
+static void hcd_start_func(struct work_struct *work)
+{
+ struct dwc_hcd *priv = container_of(work, struct dwc_hcd, start_work);
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)priv->_p;
+
+ if (usb_hcd)
+ dwc_otg_hcd_start(usb_hcd);
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ */
+static int dwc_otg_hcd_start_cb(void *_p)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = dwc_hcd->core_if;
+ u32 hprt0;
+
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ /*
+ * Reset the port. During a HNP mode switch the reset
+ * needs to occur within 1ms and have a duration of at
+ * least 50ms.
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ ((struct usb_hcd *)_p)->self.is_b_host = 1;
+ } else {
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ }
+
+ /* Need to start the HCD in a non-interrupt context. */
+ dwc_hcd->_p = _p;
+ schedule_work(&dwc_hcd->start_work);
+ return 1;
+}
+
+/**
+ * This function disables the Host Mode interrupts.
+ */
+static void dwc_otg_disable_host_interrupts(struct core_if *core_if)
+{
+ u32 global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /*
+ * Disable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ intr_mask |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0);
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+static void dwc_otg_hcd_stop(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hprt0 = 0;
+
+ /* Turn off all host-specific interrupts. */
+ spin_lock(&dwc_hcd->lock);
+ dwc_otg_disable_host_interrupts(dwc_hcd->core_if);
+ spin_unlock(&dwc_hcd->lock);
+
+ /*
+ * The root hub should be disconnected before this function is called.
+ * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
+ * and the QH lists (via ..._hcd_endpoint_disable).
+ */
+
+ /* Turn off the vbus power */
+ pr_info("PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_hcd->core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ */
+static int dwc_otg_hcd_stop_cb(void *_p)
+{
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)_p;
+
+ dwc_otg_hcd_stop(usb_hcd);
+ return 1;
+}
+
+static void del_timers(struct dwc_hcd *hcd)
+{
+ del_timer_sync(&hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ETIMEDOUT and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(struct dwc_hcd *hcd, struct list_head *qh_list)
+{
+ struct list_head *qh_item, *q;
+
+ qh_item = qh_list->next;
+ list_for_each_safe(qh_item, q, qh_list) {
+ struct dwc_qh *qh;
+ struct list_head *qtd_item;
+ struct dwc_qtd *qtd;
+
+ qh = list_entry(qh_item, struct dwc_qh, qh_list_entry);
+ qtd_item = qh->qtd_list.next;
+ qtd = list_entry(qtd_item, struct dwc_qtd, qtd_list_entry);
+ if (qtd->urb != NULL) {
+ spin_lock(&hcd->lock);
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -ETIMEDOUT);
+ dwc_otg_hcd_qtd_remove_and_free(qtd);
+ spin_unlock(&hcd->lock);
+ }
+ }
+}
+
+/**
+ * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
+ * and periodic schedules. The QTD associated with each URB is removed from
+ * the schedule and freed. This function may be called when a disconnect is
+ * detected or when the HCD is being stopped.
+ */
+static void kill_all_urbs(struct dwc_hcd *hcd)
+{
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_deferred);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_disconnect_cb(void *_p)
+{
+ u32 intr;
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = hcd->core_if;
+
+ /*
+ * Shutdown any transfers in process by clearing the Tx FIFO Empty
+ * interrupt mask and status bits and disabling subsequent host
+ * channel interrupts.
+ */
+ intr = 0;
+ intr |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ intr |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr |= DWC_INTMSK_HST_CHAN;
+ spin_lock(&hcd->lock);
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr, 0);
+ dwc_reg_modify(gintsts_reg(hcd), 0, intr, 0);
+ spin_unlock(&hcd->lock);
+
+ del_timers(hcd);
+
+ /*
+ * Turn off the vbus power only if the core has transitioned to device
+ * mode. If still in host mode, need to keep power on to detect a
+ * reconnection.
+ */
+ if (dwc_otg_is_device_mode(core_if)) {
+ if (core_if->xceiv->state != OTG_STATE_A_SUSPEND) {
+ u32 hprt0 = 0;
+
+ pr_info("Disconnect: PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+ dwc_otg_disable_host_interrupts(core_if);
+ }
+
+ /* Respond with an error status to all URBs in the schedule. */
+ kill_all_urbs(hcd);
+ if (dwc_otg_is_host_mode(core_if)) {
+ /* Clean up any host channels that were in use. */
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ ulong regs;
+ u32 hcchar;
+
+ num_channels = core_if->core_params->host_channels;
+ if (!core_if->dma_enable) {
+ /* Flush out any channel requests in slave mode. */
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs =
+ core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hcchar =
+ DWC_HCCHAR_ENA_RW(hcchar,
+ 0);
+ hcchar =
+ DWC_HCCHAR_DIS_RW(hcchar,
+ 1);
+ hcchar =
+ DWC_HCCHAR_EPDIR_RW(hcchar,
+ 0);
+ dwc_reg_write(regs, DWC_HCCHAR,
+ hcchar);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ /* Halt the channel. */
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ dwc_reg_write(regs, DWC_HCCHAR, hcchar);
+ }
+ dwc_otg_hc_cleanup(core_if, channel);
+ list_add_tail(&channel->hc_list_entry,
+ &hcd->free_hc_list);
+ }
+ }
+ }
+
+ /*
+ * A disconnect will end the session so the B-Device is no
+ * longer a B-host.
+ */
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ return 1;
+}
+
+/**
+ * Connection timeout function. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.
+ */
+static void dwc_otg_hcd_connect_timeout(unsigned long _ptr)
+{
+ pr_info("Connect Timeout\n");
+ pr_err("Device Not Connected/Responding\n");
+}
+
+/**
+ * Start the connection timer. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds. The
+ * timer is deleted if a port connect interrupt occurs before the
+ * timer expires.
+ */
+static void dwc_otg_hcd_start_connect_timer(struct dwc_hcd *hcd)
+{
+ init_timer(&hcd->conn_timer);
+ hcd->conn_timer.function = dwc_otg_hcd_connect_timeout;
+ hcd->conn_timer.data = (unsigned long)0;
+ hcd->conn_timer.expires = jiffies + (HZ * 10);
+ add_timer(&hcd->conn_timer);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_session_start_cb(void *_p)
+{
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+
+ dwc_otg_hcd_start_connect_timer(hcd);
+ return 1;
+}
+
+/* HCD Callback structure for handling mode switching. */
+static struct cil_callbacks hcd_cil_callbacks = {
+ .start = dwc_otg_hcd_start_cb,
+ .stop = dwc_otg_hcd_stop_cb,
+ .disconnect = dwc_otg_hcd_disconnect_cb,
+ .session_start = dwc_otg_hcd_session_start_cb,
+ .p = NULL,
+};
+
+/*
+ * Reset Workqueue implementation
+ */
+static void port_reset_wqfunc(struct work_struct *work)
+{
+ struct dwc_hcd *hcd = container_of(work, struct dwc_hcd,
+ usb_port_reset);
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ pr_info("%s\n", __func__);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_wakeup_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(to_delayed_work(work),
+ struct core_if, usb_port_wakeup);
+ u32 hprt0;
+
+ pr_info("%s\n", __func__);
+ /* Now wait for 70 ms. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ msleep(70);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * Starts processing a USB transfer request specified by a USB Request Block
+ * (URB). mem_flags indicates the type of memory allocation to use while
+ * processing this URB.
+ */
+static int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t _mem_flags)
+{
+ int retval;
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct dwc_qtd *qtd;
+
+ qtd = dwc_otg_hcd_qtd_create(urb, _mem_flags);
+ if (!qtd) {
+ pr_err("DWC OTG HCD URB Enqueue failed creating " "QTD\n");
+ retval = -ENOMEM;
+ goto err_enq;
+ }
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ retval = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (unlikely(retval))
+ goto fail;
+
+ retval = dwc_otg_hcd_qtd_add(qtd, dwc_hcd);
+ if (retval < 0) {
+ pr_err("DWC OTG HCD URB Enqueue failed adding QTD. "
+ "Error status %d\n", retval);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ goto fail;
+ }
+
+fail:
+ if (retval)
+ dwc_otg_hcd_qtd_free(qtd);
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+err_enq:
+
+ return retval;
+}
+
+/**
+ * Attempts to halt a host channel. This function should only be called in
+ * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under
+ * normal circumstances in DMA mode, the controller halts the channel when the
+ * transfer is complete or a condition occurs that requires application
+ * intervention.
+ *
+ * In slave mode, checks for a free request queue entry, then sets the Channel
+ * Enable and Channel Disable bits of the Host Channel Characteristics
+ * register of the specified channel to intiate the halt. If there is no free
+ * request queue entry, sets only the Channel Disable bit of the HCCHARn
+ * register to flush requests for this channel. In the latter case, sets a
+ * flag to indicate that the host channel needs to be halted when a request
+ * queue slot is open.
+ *
+ * In DMA mode, always sets the Channel Enable and Channel Disable bits of the
+ * HCCHARn register. The controller ensures there is space in the request
+ * queue before submitting the halt request.
+ *
+ * Some time may elapse before the core flushes any posted requests for this
+ * host channel and halts. The Channel Halted interrupt handler completes the
+ * deactivation of the host channel.
+ */
+void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status hlt_sts)
+{
+ u32 nptxsts;
+ u32 hptxsts = 0;
+ u32 hcchar;
+ ulong hc_regs;
+ ulong global_regs = core_if->core_global_regs;
+ ulong host_global_regs;
+
+ hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ host_global_regs = core_if->host_if->host_global_regs;
+
+ WARN_ON(hlt_sts == DWC_OTG_HC_XFER_NO_HALT_STATUS);
+
+ if (hlt_sts == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hlt_sts == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Disable all channel interrupts except Ch Halted. The QTD
+ * and QH state associated with this transfer has been cleared
+ * (in the case of URB_DEQUEUE), so the channel needs to be
+ * shut down carefully to prevent crashes.
+ */
+ u32 hcintmsk;
+ hcintmsk = 0;
+ hcintmsk = DWC_HCINTMSK_CHAN_HALTED_RW(hcintmsk, 1);
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hcintmsk);
+
+ /*
+ * Make sure no other interrupts besides halt are currently
+ * pending. Handling another interrupt could cause a crash due
+ * to the QTD and QH state.
+ */
+ dwc_reg_write(hc_regs, DWC_HCINT, ~hcintmsk);
+
+ /*
+ * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR
+ * even if the channel was already halted for some other reason.
+ */
+ hc->halt_status = hlt_sts;
+
+ /*
+ * If the channel is not enabled, the channel is either already
+ * halted or it hasn't started yet. In DMA mode, the transfer
+ * may halt if it finishes normally or a condition occurs that
+ * requires driver intervention. Don't want to halt the channel
+ * again. In either Slave or DMA mode, it's possible that the
+ * transfer has been assigned to a channel, but not started yet
+ * when an URB is dequeued. Don't want to halt a channel that
+ * hasn't started yet.
+ */
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (!DWC_HCCHAR_ENA_RD(hcchar))
+ return;
+ }
+
+ if (hc->halt_pending)
+ /*
+ * A halt has already been issued for this channel. This might
+ * happen when a transfer is aborted by a higher level in
+ * the stack.
+ */
+ return;
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ if (!core_if->dma_enable) {
+ /* Check for space in the request queue to issue the halt. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ nptxsts = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ if (!DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(nptxsts))
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ } else {
+ hptxsts =
+ dwc_reg_read(host_global_regs, DWC_HPTXSTS);
+
+ if (!DWC_HPTXSTS_PTXSPC_AVAIL_RD(hptxsts) ||
+ core_if->queuing_high_bandwidth)
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->halt_status = hlt_sts;
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hc->halt_pending = 1;
+ hc->halt_on_queue = 0;
+ } else {
+ hc->halt_on_queue = 1;
+ }
+}
+
+/**
+ * Aborts/cancels a USB transfer request. Always returns 0 to indicate
+ * success.
+ */
+static int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_qtd *urb_qtd;
+ struct dwc_qh *qh;
+ int retval;
+
+ urb_qtd = (struct dwc_qtd *)urb->hcpriv;
+ if (!urb_qtd)
+ return -EINVAL;
+ qh = (struct dwc_qh *)urb_qtd->qtd_qh_ptr;
+ if (!qh)
+ return -EINVAL;
+
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ retval = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (retval) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+ }
+
+ if (urb_qtd == qh->qtd_in_process) {
+ /*
+ * If still connected (i.e. in host mode), halt the
+ * channel so it can be used for other transfers. If
+ * no longer connected, the host registers can't be
+ * written to halt the channel since the core is in
+ * device mode.
+ */
+ dwc_otg_hc_halt(dwc_hcd->core_if, qh->channel,
+ DWC_OTG_HC_XFER_URB_DEQUEUE);
+
+ }
+
+ /*
+ * Free the QTD and clean up the associated QH. Leave the QH in the
+ * schedule if it has any remaining QTDs.
+ */
+ dwc_otg_hcd_qtd_remove_and_free(urb_qtd);
+ if (qh && urb_qtd == qh->qtd_in_process) {
+ dwc_otg_hcd_qh_deactivate(dwc_hcd, qh, 0);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ } else if (qh && list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(dwc_hcd, qh);
+ }
+
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+
+ /* Higher layer software sets URB status. */
+ usb_hcd_giveback_urb(hcd, urb, status);
+
+ return 0;
+}
+
+/* Remove and free a QH */
+static inline void dwc_otg_hcd_qh_remove_and_free(struct dwc_hcd *hcd,
+ struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ dwc_otg_hcd_qh_free(qh);
+}
+
+static void qh_list_free(struct dwc_hcd *hcd, struct list_head *_qh_list)
+{
+ struct list_head *item, *tmp;
+ struct dwc_qh *qh;
+
+ /* If the list hasn't been initialized yet, return. */
+ if (_qh_list->next == NULL)
+ return;
+
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, _qh_list);
+
+ list_for_each_safe(item, tmp, _qh_list) {
+ qh = list_entry(item, struct dwc_qh, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
+}
+
+/**
+ * Frees resources in the DWC_otg controller related to a given endpoint. Also
+ * clears state in the HCD related to the endpoint. Any URBs for the endpoint
+ * must already be dequeued.
+ */
+static void dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct dwc_qh *qh;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (qh) {
+ dwc_otg_hcd_qh_remove_and_free(dwc_hcd, qh);
+ ep->hcpriv = NULL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+}
+
+/**
+ * Creates Status Change bitmap for the root hub and root port. The bitmap is
+ * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
+ * is the status change indicator for the single root port. Returns 1 if either
+ * change indicator is 1, otherwise returns 0.
+ */
+
+static int dwc_otg_hcd_hub_status_data(struct dwc_hcd *hcd, char *buf)
+{
+ u32 hprt0;
+ hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ buf[0] = 0;
+ buf[0] |= (DWC_HPRT0_PRT_STS_RD(hprt0)\
+ || DWC_HPRT0_PRT_RES_RD(hprt0)\
+ || DWC_HPRT0_PRT_ENA_RD(hprt0)\
+ || DWC_HPRT0_PRT_SUS_RD(hprt0)\
+ || DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0));
+
+ return (buf[0] != 0);
+}
+
+/* Handles the hub class-specific ClearPortFeature request.*/
+static int do_clear_port_feature(struct dwc_hcd *hcd, u16 val)
+{
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hcd->lock, flags);
+ switch (val) {
+ case USB_PORT_FEAT_ENABLE:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Clear Resume bit */
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(100);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Port inidicator not supported */
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ /* Clears drivers internal connect status change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_STS_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* Clears driver's internal Port Reset Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ /* Clears driver's internal Port Enable/Disable Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ /*
+ * Clears the driver's internal Port Suspend
+ * Change flag, which is set when resume signaling on
+ * the host port is complete
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SPD_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ default:
+ pr_err("DWC OTG HCD - ClearPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return 0;
+}
+
+/* Handles the hub class-specific SetPortFeature request.*/
+static int do_set_port_feature(struct usb_hcd *hcd, u16 val, u16 index)
+{
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ u32 hprt0 = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+ u32 pcgcctl = 0;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ switch (val) {
+ case USB_PORT_FEAT_SUSPEND:
+ if (hcd->self.otg_port == index && hcd->self.b_hnp_enable) {
+ u32 gotgctl = 0;
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(core_if->core_global_regs,
+ DWC_GOTGCTL, 0, gotgctl);
+ core_if->xceiv->state = OTG_STATE_A_SUSPEND;
+ }
+
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SUS_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Suspend the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_write(core_if->pcgcctl, 0, pcgcctl);
+
+ /* For HNP the bus must be suspended for at least 200ms. */
+ if (hcd->self.b_hnp_enable) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(200);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ }
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_RESET:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+
+ /*
+ * When B-Host the Port reset bit is set in the Start HCD
+ * Callback function, so that the reset is started within 1ms
+ * of the HNP success interrupt.
+ */
+ if (!hcd->self.is_b_host) {
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+
+ /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Not supported */
+ break;
+ default:
+ pr_err("DWC OTG HCD - "
+ "SetPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return 0;
+}
+
+/* Handles hub class-specific requests.*/
+static int dwc_otg_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 val,
+ u16 index, char *buf, u16 len)
+{
+ int retval = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ struct usb_hub_descriptor *desc;
+ u32 hprt0 = 0;
+ u32 port_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ switch (req_type) {
+ case ClearHubFeature:
+ switch (val) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* Nothing required here */
+ break;
+ default:
+ retval = -EINVAL;
+ pr_err("DWC OTG HCD - ClearHubFeature request"
+ " %xh unknown\n", val);
+ }
+ break;
+ case ClearPortFeature:
+ if (!index || index > 1)
+ goto error;
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_clear_port_feature(dwc_hcd, val);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ case GetHubDescriptor:
+ desc = (struct usb_hub_descriptor *)buf;
+ desc->bDescLength = 9;
+ desc->bDescriptorType = 0x29;
+ desc->bNbrPorts = 1;
+ desc->wHubCharacteristics = 0x08;
+ desc->bPwrOn2PwrGood = 1;
+ desc->bHubContrCurrent = 0;
+ break;
+ case GetHubStatus:
+ memset(buf, 0, 4);
+ break;
+ case GetPortStatus:
+ if (!index || index > 1)
+ goto error;
+
+ port_status = 0;
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+
+ if (DWC_HPRT0_PRT_STS_RD(hprt0))
+ port_status |= USB_PORT_STAT_CONNECTION;
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_status |= USB_PORT_STAT_ENABLE;
+ if (DWC_HPRT0_PRT_SUS_RD(hprt0))
+ port_status |= USB_PORT_STAT_SUSPEND;
+ if (DWC_HPRT0_PRT_OVRCURR_ACT_RD(hprt0))
+ port_status |= USB_PORT_STAT_OVERCURRENT;
+ if (DWC_HPRT0_PRT_RST_RD(hprt0))
+ port_status |= USB_PORT_STAT_RESET;
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0))
+ port_status |= USB_PORT_STAT_POWER;
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+ port_status |= USB_PORT_STAT_HIGH_SPEED;
+ else if (DWC_HPRT0_PRT_SPD_RD(hprt0) ==
+ DWC_HPRT0_PRTSPD_LOW_SPEED)
+ port_status |= USB_PORT_STAT_LOW_SPEED;
+
+ if (DWC_HPRT0_PRT_TST_CTL_RD(hprt0))
+ port_status |= (1 << USB_PORT_FEAT_TEST);
+
+ /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+ *((__le32 *) buf) = cpu_to_le32(port_status);
+ break;
+ case SetHubFeature:
+ /* No HUB features supported */
+ break;
+ case SetPortFeature:
+ if (val != USB_PORT_FEAT_TEST && (!index || index > 1))
+ goto error;
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_set_port_feature(hcd, val, index);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ default:
+error:
+ retval = -EINVAL;
+ pr_warning("DWC OTG HCD - Unknown hub control request"
+ " type or invalid req_type: %xh index: %xh "
+ "val: %xh\n", req_type, index, val);
+ break;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+}
+
+/**
+ * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
+ * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
+ * interrupt.
+ *
+ * This function is called by the USB core when an interrupt occurs
+ */
+static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+ return IRQ_RETVAL(dwc_otg_hcd_handle_intr(dwc_hcd));
+}
+
+static const struct hc_driver dwc_otg_hc_driver = {
+ .description = dwc_otg_hcd_name,
+ .product_desc = "DWC OTG Controller",
+ .hcd_priv_size = sizeof(struct dwc_hcd),
+ .irq = dwc_otg_hcd_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+ .start = dwc_otg_hcd_start,
+ .stop = dwc_otg_hcd_stop,
+ .urb_enqueue = dwc_otg_hcd_urb_enqueue,
+ .urb_dequeue = dwc_otg_hcd_urb_dequeue,
+ .endpoint_disable = dwc_otg_hcd_endpoint_disable,
+ .get_frame_number = dwc_otg_hcd_get_frame_number,
+ .hub_status_data = dwc_otg_hcd_hub_status_data,
+ .hub_control = dwc_otg_hcd_hub_control,
+};
+
+/**
+ * Frees secondary storage associated with the dwc_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 i;
+
+ del_timers(dwc_hcd);
+
+ /* Free memory for QH/QTD lists */
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_deferred);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_active);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_ready);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_assigned);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_queued);
+
+ /* Free memory for the host channels. */
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ struct dwc_hc *hc = dwc_hcd->hc_ptr_array[i];
+
+ kfree(hc);
+ }
+ if (dwc_hcd->core_if->dma_enable) {
+ if (dwc_hcd->status_buf_dma)
+ dma_free_coherent(hcd->self.controller,
+ DWC_OTG_HCD_STATUS_BUF_SIZE,
+ dwc_hcd->status_buf,
+ dwc_hcd->status_buf_dma);
+ } else {
+ kfree(dwc_hcd->status_buf);
+ }
+
+}
+
+/**
+ * Initializes the HCD. This function allocates memory for and initializes the
+ * static parts of the usb_hcd and dwc_hcd structures. It also registers the
+ * USB bus with the core and calls the hc_driver->start() function. It returns
+ * a negative error on failure.
+ */
+int __devinit dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_otg_device)
+{
+ struct usb_hcd *hcd;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ int retval = 0;
+
+ /*
+ * Allocate memory for the base HCD plus the DWC OTG HCD.
+ * Initialize the base HCD.
+ */
+ hcd = usb_create_hcd(&dwc_otg_hc_driver, _dev, dwc_otg_hcd_name);
+ if (!hcd) {
+ retval = -ENOMEM;
+ goto error1;
+ }
+ dev_set_drvdata(_dev, dwc_otg_device);
+ hcd->regs = otg_dev->base;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+ hcd->self.otg_port = 1;
+
+ /* Initialize the DWC OTG HCD. */
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ dwc_hcd->core_if = otg_dev->core_if;
+ spin_lock_init(&dwc_hcd->lock);
+ otg_dev->hcd = dwc_hcd;
+
+ /* Register the HCD CIL Callbacks */
+ dwc_otg_cil_register_hcd_callbacks(otg_dev->core_if, &hcd_cil_callbacks,
+ hcd);
+
+ /* Initialize the non-periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_active);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_deferred);
+
+ /* Initialize the periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_ready);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_assigned);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_queued);
+
+ /*
+ * Create a host channel descriptor for each host channel implemented
+ * in the controller. Initialize the channel descriptor array.
+ */
+ INIT_LIST_HEAD(&dwc_hcd->free_hc_list);
+ num_channels = dwc_hcd->core_if->core_params->host_channels;
+
+ for (i = 0; i < num_channels; i++) {
+ channel = kzalloc(sizeof(struct dwc_hc), GFP_KERNEL);
+ if (!channel) {
+ retval = -ENOMEM;
+ pr_err("%s: host channel allocation failed\n",
+ __func__);
+ goto error2;
+ }
+
+ channel->hc_num = i;
+ dwc_hcd->hc_ptr_array[i] = channel;
+ }
+
+ /* Initialize the Connection timeout timer. */
+ init_timer(&dwc_hcd->conn_timer);
+
+ /* Initialize workqueue */
+ INIT_WORK(&dwc_hcd->usb_port_reset, port_reset_wqfunc);
+ INIT_WORK(&dwc_hcd->start_work, hcd_start_func);
+ INIT_WORK(&dwc_hcd->core_if->usb_port_otg, NULL);
+ INIT_DELAYED_WORK(&dwc_hcd->core_if->usb_port_wakeup,
+ port_wakeup_wqfunc);
+
+ /* Set device flags indicating whether the HCD supports DMA. */
+ if (otg_dev->core_if->dma_enable) {
+ static u64 dummy_mask = DMA_BIT_MASK(32);
+
+ pr_info("Using DMA mode\n");
+ _dev->dma_mask = (void *)&dummy_mask;
+ _dev->coherent_dma_mask = ~0;
+ } else {
+ pr_info("Using Slave mode\n");
+ _dev->dma_mask = (void *)0;
+ _dev->coherent_dma_mask = 0;
+ }
+
+ init_hcd_usecs(dwc_hcd);
+ /*
+ * Finish generic HCD initialization and start the HCD. This function
+ * allocates the DMA buffer pool, registers the USB bus, requests the
+ * IRQ line, and calls dwc_otg_hcd_start method.
+ */
+ retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED);
+ if (retval < 0)
+ goto error2;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+
+ /*
+ * Allocate space for storing data on status transactions. Normally no
+ * data is sent, but this space acts as a bit bucket. This must be
+ * done after usb_add_hcd since that function allocates the DMA buffer
+ * pool.
+ */
+ if (otg_dev->core_if->dma_enable) {
+ dwc_hcd->status_buf =
+ dma_alloc_coherent(_dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ &dwc_hcd->status_buf_dma,
+ GFP_KERNEL | GFP_DMA);
+ } else {
+ dwc_hcd->status_buf = kmalloc(DWC_OTG_HCD_STATUS_BUF_SIZE,
+ GFP_KERNEL);
+ }
+ if (!dwc_hcd->status_buf) {
+ retval = -ENOMEM;
+ pr_err("%s: status_buf allocation failed\n", __func__);
+ goto error3;
+ }
+ return 0;
+
+error3:
+ usb_remove_hcd(hcd);
+error2:
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+error1:
+ return retval;
+}
+
+/**
+ * Removes the HCD.
+ * Frees memory and resources associated with the HCD and deregisters the bus.
+ */
+void __devexit dwc_otg_hcd_remove(struct device *_dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ struct dwc_hcd *dwc_hcd = otg_dev->hcd;
+ struct usb_hcd *hcd = dwc_otg_hcd_to_hcd(dwc_hcd);
+
+ /* Turn off all interrupts */
+ dwc_reg_write(gintmsk_reg(dwc_hcd), 0, 0);
+ spin_lock(&dwc_hcd->lock);
+ dwc_reg_modify(gahbcfg_reg(dwc_hcd), 0, 1, 0);
+ spin_unlock(&dwc_hcd->lock);
+
+ cancel_work_sync(&dwc_hcd->start_work);
+ cancel_work_sync(&dwc_hcd->usb_port_reset);
+ cancel_work_sync(&dwc_hcd->core_if->usb_port_otg);
+
+ usb_remove_hcd(hcd);
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+}
+
+/** Returns the current frame number. */
+int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(dwc_hcd->core_if->host_if->
+ host_global_regs, DWC_HFNUM);
+
+ return DWC_HFNUM_FRNUM_RD(hfnum);
+}
+
+/**
+ * Prepares a host channel for transferring packets to/from a specific
+ * endpoint. The HCCHARn register is set up with the characteristics specified
+ * in _hc. Host channel interrupts that may need to be serviced while this
+ * transfer is in progress are enabled.
+ */
+static void dwc_otg_hc_init(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 intr_enable;
+ ulong global_regs = core_if->core_global_regs;
+ u32 hc_intr_mask = 0;
+ u32 gintmsk = 0;
+ u32 hcchar;
+ u32 hcsplt;
+ u8 hc_num = hc->hc_num;
+ struct dwc_host_if *host_if = core_if->host_if;
+ ulong hc_regs = host_if->hc_regs[hc_num];
+
+ /* Clear old interrupt conditions for this host channel. */
+ hc_intr_mask = 0x3FF;
+ dwc_reg_write(hc_regs, DWC_HCINT, hc_intr_mask);
+
+ /* Enable channel interrupts required for this transfer. */
+ hc_intr_mask = 0;
+ hc_intr_mask = DWC_HCINTMSK_CHAN_HALTED_RW(hc_intr_mask, 1);
+ if (core_if->dma_enable) {
+ hc_intr_mask = DWC_HCINTMSK_AHB_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->error_state && !hc->do_split &&
+ hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask,
+ 1);
+ if (hc->ep_type != DWC_OTG_EP_TYPE_INTR)
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ }
+ } else {
+ switch (hc->ep_type) {
+ case DWC_OTG_EP_TYPE_CONTROL:
+ case DWC_OTG_EP_TYPE_BULK:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ } else {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->do_ping)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->do_split) {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ break;
+ case DWC_OTG_EP_TYPE_INTR:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in)
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+
+ if (hc->do_split) {
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ break;
+ case DWC_OTG_EP_TYPE_ISOC:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ }
+ break;
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hc_intr_mask);
+
+ /* Enable the top level host channel interrupt. */
+ intr_enable = (1 << hc_num);
+ dwc_reg_modify(host_if->host_global_regs, DWC_HAINTMSK, 0,
+ intr_enable);
+
+ /* Make sure host channel interrupts are enabled. */
+ gintmsk |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmsk);
+
+ /*
+ * Program the HCCHARn register with the endpoint characteristics for
+ * the current transfer.
+ */
+ hcchar = 0;
+ hcchar = DWC_HCCHAR_DEV_ADDR_RW(hcchar, hc->dev_addr);
+ hcchar = DWC_HCCHAR_EP_NUM_RW(hcchar, hc->ep_num);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, hc->ep_is_in);
+ hcchar = DWC_HCCHAR_LSP_DEV_RW(hcchar, (hc->speed ==
+ DWC_OTG_EP_SPEED_LOW));
+ hcchar = DWC_HCCHAR_EPTYPE_RW(hcchar, hc->ep_type);
+ hcchar = DWC_HCCHAR_MPS_RW(hcchar, hc->max_packet);
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCCHAR, hcchar);
+
+ /* Program the HCSPLIT register for SPLITs */
+ hcsplt = 0;
+ if (hc->do_split) {
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, hc->complete_split);
+ hcsplt = DWC_HCSPLT_TRANS_POS_RW(hcsplt, hc->xact_pos);
+ hcsplt = DWC_HCSPLT_HUB_ADDR_RW(hcsplt, hc->hub_addr);
+ hcsplt = DWC_HCSPLT_PRT_ADDR_RW(hcsplt, hc->port_addr);
+ }
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCSPLT, hcsplt);
+}
+
+/**
+ * Assigns transactions from a QTD to a free host channel and initializes the
+ * host channel to perform the transactions. The host channel is removed from
+ * the free list.
+ */
+static void assign_and_init_hc(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct dwc_hc *hc;
+ struct dwc_qtd *qtd;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ hc = list_entry(hcd->free_hc_list.next, struct dwc_hc, hc_list_entry);
+
+ /* Remove the host channel from the free list. */
+ list_del_init(&hc->hc_list_entry);
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ urb = qtd->urb;
+ qh->channel = hc;
+ qh->qtd_in_process = qtd;
+
+ /*
+ * Use usb_pipedevice to determine device address. This address is
+ * 0 before the SET_ADDRESS command and the correct address afterward.
+ */
+ hc->dev_addr = usb_pipedevice(urb->pipe);
+ hc->ep_num = usb_pipeendpoint(urb->pipe);
+
+ if (urb->dev->speed == USB_SPEED_LOW)
+ hc->speed = DWC_OTG_EP_SPEED_LOW;
+ else if (urb->dev->speed == USB_SPEED_FULL)
+ hc->speed = DWC_OTG_EP_SPEED_FULL;
+ else
+ hc->speed = DWC_OTG_EP_SPEED_HIGH;
+
+ hc->max_packet = dwc_max_packet(qh->maxp);
+ hc->xfer_started = 0;
+ hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS;
+ hc->error_state = (qtd->error_count > 0);
+ hc->halt_on_queue = 0;
+ hc->halt_pending = 0;
+ hc->requests = 0;
+
+ /*
+ * The following values may be modified in the transfer type section
+ * below. The xfer_len value may be reduced when the transfer is
+ * started to accommodate the max widths of the XferSize and PktCnt
+ * fields in the HCTSIZn register.
+ */
+ hc->do_ping = qh->ping_state;
+ hc->ep_is_in = (usb_pipein(urb->pipe) != 0);
+ hc->data_pid_start = qh->data_toggle;
+ hc->multi_count = 1;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = urb->transfer_dma + (u8 *) urb->actual_length;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer +
+ urb->actual_length;
+
+ hc->xfer_len = urb->transfer_buffer_length - urb->actual_length;
+ hc->xfer_count = 0;
+
+ /*
+ * Set the split attributes
+ */
+ hc->do_split = 0;
+ if (qh->do_split) {
+ hc->do_split = 1;
+ hc->xact_pos = qtd->isoc_split_pos;
+ hc->complete_split = qtd->complete_split;
+ hc->hub_addr = urb->dev->tt->hub->devnum;
+ hc->port_addr = urb->dev->ttport;
+ }
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ hc->do_ping = 0;
+ hc->ep_is_in = 0;
+ hc->data_pid_start = DWC_OTG_HC_PID_SETUP;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->setup_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->setup_packet;
+
+ hc->xfer_len = 8;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ hc->data_pid_start = qtd->data_toggle;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ /*
+ * Direction is opposite of data direction or IN if no
+ * data.
+ */
+ if (urb->transfer_buffer_length == 0)
+ hc->ep_is_in = 1;
+ else
+ hc->ep_is_in = (usb_pipein(urb->pipe) !=
+ USB_DIR_IN);
+
+ if (hc->ep_is_in)
+ hc->do_ping = 0;
+
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ hc->xfer_len = 0;
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff =
+ (u8 *) (u32) hcd->status_buf_dma;
+ else
+ hc->xfer_buff = (u8 *) hcd->status_buf;
+ break;
+ }
+ break;
+ case PIPE_BULK:
+ hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+ break;
+ case PIPE_INTERRUPT:
+ hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+ break;
+ case PIPE_ISOCHRONOUS:
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+ hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->transfer_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer;
+
+ hc->xfer_buff += frame_desc->offset + qtd->isoc_split_offset;
+ hc->xfer_len = frame_desc->length - qtd->isoc_split_offset;
+
+ if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ if (hc->xfer_len <= 188)
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ else
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_BEGIN;
+ }
+ break;
+ }
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * This value may be modified when the transfer is started to
+ * reflect the actual transfer length.
+ */
+ hc->multi_count = dwc_hb_mult(qh->maxp);
+
+ dwc_otg_hc_init(hcd->core_if, hc);
+ hc->qh = qh;
+}
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ */
+enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd *hcd)
+{
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int num_channels;
+ enum dwc_transaction_type ret_val = DWC_OTG_TRANSACTION_NONE;
+
+ /* Process entries in the periodic ready list. */
+ num_channels = hcd->core_if->core_params->host_channels;
+ qh_ptr = hcd->periodic_sched_ready.next;
+ while (qh_ptr != &hcd->periodic_sched_ready &&
+ !list_empty(&hcd->free_hc_list)) {
+ /* Leave one channel for non periodic transactions. */
+ if (hcd->available_host_channels <= 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the periodic ready schedule to the
+ * periodic assigned schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->periodic_sched_assigned);
+ ret_val = DWC_OTG_TRANSACTION_PERIODIC;
+ }
+
+ /*
+ * Process entries in the deferred portion of the non-periodic list.
+ * A NAK put them here and, at the right time, they need to be
+ * placed on the sched_inactive list.
+ */
+ qh_ptr = hcd->non_periodic_sched_deferred.next;
+ while (qh_ptr != &hcd->non_periodic_sched_deferred) {
+ u16 frame_number =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ qh_ptr = qh_ptr->next;
+
+ if (dwc_frame_num_le(qh->sched_frame, frame_number))
+ /*
+ * Move the QH from the non periodic deferred schedule
+ * to the non periodic inactive schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ }
+
+ /*
+ * Process entries in the inactive portion of the non-periodic
+ * schedule. Some free host channels may not be used if they are
+ * reserved for periodic transfers.
+ */
+ qh_ptr = hcd->non_periodic_sched_inactive.next;
+ num_channels = hcd->core_if->core_params->host_channels;
+
+ while (qh_ptr != &hcd->non_periodic_sched_inactive
+ && !list_empty(&hcd->free_hc_list)) {
+ if (hcd->available_host_channels < 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the non-periodic inactive schedule to the
+ * non-periodic active schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active);
+ if (ret_val == DWC_OTG_TRANSACTION_NONE)
+ ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
+ else
+ ret_val = DWC_OTG_TRANSACTION_ALL;
+
+ }
+ return ret_val;
+}
+
+/**
+ * Sets the channel property that indicates in which frame a periodic transfer
+ * should occur. This is always set to the _next_ frame. This function has no
+ * effect on non-periodic transfers.
+ */
+static inline void hc_set_even_odd_frame(struct core_if *core_if,
+ struct dwc_hc *hc, u32 * hcchar)
+{
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ /* 1 if _next_ frame is odd, 0 if it's even */
+ *hcchar = DWC_HCCHAR_ODD_FRAME_RW(*hcchar,
+ ((DWC_HFNUM_FRNUM_RD(hfnum) &
+ 0x1) ? 0 : 1));
+ }
+}
+
+static void set_initial_xfer_pid(struct dwc_hc *hc)
+{
+ if (hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+ if (hc->ep_is_in) {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else if (hc->multi_count == 2)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA2;
+ } else {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_MDATA;
+ }
+ } else {
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ }
+}
+
+/**
+ * Starts a PING transfer. This function should only be called in Slave mode.
+ * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled.
+ */
+static void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hctsiz = 0;
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, 1);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+static void dwc_otg_hc_write_packet(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 i;
+ u32 remaining_count;
+ u32 byte_count;
+ u32 dword_count;
+ u32 *data_buff = (u32 *) (hc->xfer_buff);
+ u32 data_fifo = core_if->data_fifo[hc->hc_num];
+
+ remaining_count = hc->xfer_len - hc->xfer_count;
+ if (remaining_count > hc->max_packet)
+ byte_count = hc->max_packet;
+ else
+ byte_count = remaining_count;
+
+ dword_count = (byte_count + 3) / 4;
+
+ if (((unsigned long)data_buff) & 0x3)
+ /* xfer_buff is not DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo,
+ get_unaligned(data_buff));
+ else
+ /* xfer_buff is DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo, *data_buff);
+
+ hc->xfer_count += byte_count;
+ hc->xfer_buff += byte_count;
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel and
+ * starts the transfer. May be called in either Slave mode or DMA mode. In
+ * Slave mode, the caller must ensure that there is sufficient space in the
+ * request queue and Tx Data FIFO.
+ *
+ * For an OUT transfer in Slave mode, it loads a data packet into the
+ * appropriate FIFO. If necessary, additional data packets will be loaded in
+ * the Host ISR.
+ *
+ * For an IN transfer in Slave mode, a data packet is requested. The data
+ * packets are unloaded from the Rx FIFO in the Host ISR. If necessary,
+ * additional data packets are requested in the Host ISR.
+ *
+ * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ
+ * register along with a packet count of 1 and the channel is enabled. This
+ * causes a single PING transaction to occur. Other fields in HCTSIZ are
+ * simply set to 0 since no data transfer occurs in this case.
+ *
+ * For a PING transfer in DMA mode, the HCTSIZ register is initialized with
+ * all the information required to perform the subsequent data transfer. In
+ * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the
+ * controller performs the entire PING protocol, then starts the data
+ * transfer.
+ */
+static void dwc_otg_hc_start_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+ u16 num_packets;
+ u32 max_hc_xfer_size = core_if->core_params->max_transfer_size;
+ u16 max_hc_pkt_count = core_if->core_params->max_packet_count;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ hctsiz = 0;
+
+ if (hc->do_ping) {
+ if (!core_if->dma_enable) {
+ dwc_otg_hc_do_ping(core_if, hc);
+ hc->xfer_started = 1;
+ return;
+ } else {
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ }
+ }
+
+ if (hc->do_split) {
+ num_packets = 1;
+
+ if (hc->complete_split && !hc->ep_is_in)
+ /*
+ * For CSPLIT OUT Transfer, set the size to 0 so the
+ * core doesn't expect any data written to the FIFO
+ */
+ hc->xfer_len = 0;
+ else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet))
+ hc->xfer_len = hc->max_packet;
+ else if (!hc->ep_is_in && (hc->xfer_len > 188))
+ hc->xfer_len = 188;
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ } else {
+ /*
+ * Ensure that the transfer length and packet count will fit
+ * in the widths allocated for them in the HCTSIZn register.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 max_len = hc->multi_count * hc->max_packet;
+
+ /*
+ * Make sure the transfer size is no larger than one
+ * (micro)frame's worth of data. (A check was done
+ * when the periodic transfer was accepted to ensure
+ * that a (micro)frame's worth of data can be
+ * programmed into a channel.)
+ */
+ if (hc->xfer_len > max_len)
+ hc->xfer_len = max_len;
+ } else if (hc->xfer_len > max_hc_xfer_size) {
+ /*
+ * Make sure that xfer_len is a multiple of max packet
+ * size.
+ */
+ hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1;
+ }
+ if (hc->xfer_len > 0) {
+ num_packets = (hc->xfer_len + hc->max_packet - 1) /
+ hc->max_packet;
+ if (num_packets > max_hc_pkt_count) {
+ num_packets = max_hc_pkt_count;
+ hc->xfer_len = num_packets * hc->max_packet;
+ }
+ } else {
+ /* Need 1 packet for transfer length of 0. */
+ num_packets = 1;
+ }
+
+ if (hc->ep_is_in)
+ /*
+ * Always program an integral # of max packets for IN
+ * transfers.
+ */
+ hc->xfer_len = num_packets * hc->max_packet;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * Make sure that the multi_count field matches the
+ * actual transfer length.
+ */
+ hc->multi_count = num_packets;
+
+ /* Set up the initial PID for the transfer. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ set_initial_xfer_pid(hc);
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ }
+
+ hc->start_pkt_count = num_packets;
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, num_packets);
+ hctsiz = DWC_HCTSIZ_PKT_PID_RW(hctsiz, hc->data_pid_start);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ if (core_if->dma_enable)
+ dwc_reg_write(hc_regs, DWC_HCDMA, (u32) hc->xfer_buff);
+
+ /* Start the split */
+ if (hc->do_split) {
+ u32 hcsplt;
+
+ hcsplt = dwc_reg_read(hc_regs, DWC_HCSPLT);
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, 1);
+ dwc_reg_write(hc_regs, DWC_HCSPLT, hcsplt);
+ }
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_MULTI_CNT_RW(hcchar, hc->multi_count);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ /* Set host channel enable after all other setup is complete. */
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->xfer_started = 1;
+ hc->requests++;
+ if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0)
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+}
+
+/**
+ * This function continues a data transfer that was started by previous call
+ * to dwc_otg_hc_start_transfer</code>. The caller must ensure there is
+ * sufficient space in the request queue and Tx Data FIFO. This function
+ * should only be called in Slave mode. In DMA mode, the controller acts
+ * autonomously to complete transfers programmed to a host channel.
+ *
+ * For an OUT transfer, a new data packet is loaded into the appropriate FIFO
+ * if there is any data remaining to be queued. For an IN transfer, another
+ * data packet is always requested. For the SETUP phase of a control transfer,
+ * this function does nothing.
+ */
+static int dwc_otg_hc_continue_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ if (hc->do_split) {
+ /* SPLITs always queue just once per channel */
+ return 0;
+ } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+ /* SETUPs are queued only once since they can't be NAKed. */
+ return 0;
+ } else if (hc->ep_is_in) {
+ /*
+ * Always queue another request for other IN transfers. If
+ * back-to-back INs are issued and NAKs are received for both,
+ * the driver may still be processing the first NAK when the
+ * second NAK is received. When the interrupt handler clears
+ * the NAK interrupt for the first NAK, the second NAK will
+ * not be seen. So we can't depend on the NAK interrupt
+ * handler to requeue a NAKed request. Instead, IN requests
+ * are issued each time this function is called. When the
+ * transfer completes, the extra requests for the channel will
+ * be flushed.
+ */
+ u32 hcchar;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->requests++;
+ return 1;
+ } else {
+ /* OUT transfers. */
+ if (hc->xfer_count < hc->xfer_len) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hcchar;
+ u32 hc_regs;
+
+ hc_regs =
+ core_if->host_if->hc_regs[hc->hc_num];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+ }
+
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+ hc->requests++;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+
+/**
+ * Attempts to queue a single transaction request for a host channel
+ * associated with either a periodic or non-periodic transfer. This function
+ * assumes that there is space available in the appropriate request queue. For
+ * an OUT transfer or SETUP transaction in Slave mode, it checks whether space
+ * is available in the appropriate Tx FIFO.
+ */
+static int queue_transaction(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u16 _fifo_dwords_avail)
+{
+ int retval;
+
+ if (hcd->core_if->dma_enable) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ hc->qh->ping_state = 0;
+ }
+ retval = 0;
+ } else if (hc->halt_pending) {
+ /* Don't queue a request if the channel has been halted. */
+ retval = 0;
+ } else if (hc->halt_on_queue) {
+ dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+ retval = 0;
+ } else if (hc->do_ping) {
+ if (!hc->xfer_started)
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 0;
+ } else if (!hc->ep_is_in || hc->data_pid_start ==
+ DWC_OTG_HC_PID_SETUP) {
+ if ((_fifo_dwords_avail * 4) >= hc->max_packet) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval =
+ dwc_otg_hc_continue_transfer(hcd->core_if,
+ hc);
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc);
+ }
+ }
+ return retval;
+}
+
+/**
+ * Processes active non-periodic channels and queues transactions for these
+ * channels to the DWC_otg controller. After queueing transactions, the NP Tx
+ * FIFO Empty interrupt is enabled if there are more transactions to queue as
+ * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx
+ * FIFO Empty interrupt is disabled.
+ */
+static void process_non_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *orig_qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ int more_to_do = 0;
+ ulong regs = hcd->core_if->core_global_regs;
+
+ /*
+ * Keep track of the starting point. Skip over the start-of-list
+ * entry.
+ */
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ orig_qh_ptr = hcd->non_periodic_qh_ptr;
+
+ /*
+ * Process once through the active list or until no more space is
+ * available in the request queue or the Tx FIFO.
+ */
+ do {
+ tx_status = dwc_reg_read(regs, DWC_GNPTXSTS);
+ if (!hcd->core_if->dma_enable &&
+ DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(hcd->non_periodic_qh_ptr, struct dwc_qh,
+ qh_list_entry);
+ status = queue_transaction(hcd, qh->channel,
+ DWC_GNPTXSTS_NPTXFSPCAVAIL_RD
+ (tx_status));
+
+ if (status > 0) {
+ more_to_do = 1;
+ } else if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /* Advance to next QH, skipping start-of-list entry. */
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ } while (hcd->non_periodic_qh_ptr != orig_qh_ptr);
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ if (more_to_do || no_queue_space || no_fifo_space) {
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the non-periodic
+ * Tx FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ } else {
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+ }
+}
+
+/**
+ * Processes periodic channels for the next frame and queues transactions for
+ * these channels to the DWC_otg controller. After queueing transactions, the
+ * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions
+ * to queue as Periodic Tx FIFO or request queue space becomes available.
+ * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled.
+ */
+static void process_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ ulong host_regs;
+
+ host_regs = hcd->core_if->host_if->host_global_regs;
+
+ qh_ptr = hcd->periodic_sched_assigned.next;
+ while (qh_ptr != &hcd->periodic_sched_assigned) {
+ tx_status = dwc_reg_read(host_regs, DWC_HPTXSTS);
+ if (DWC_HPTXSTS_PTXSPC_AVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+
+ /*
+ * Set a flag if we're queuing high-bandwidth in slave mode.
+ * The flag prevents any halts to get into the request queue in
+ * the middle of multiple high-bandwidth packets getting queued.
+ */
+ if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1)
+ hcd->core_if->queuing_high_bandwidth = 1;
+
+ status = queue_transaction(hcd, qh->channel,
+ DWC_HPTXSTS_PTXFSPC_AVAIL_RD
+ (tx_status));
+ if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /*
+ * In Slave mode, stay on the current transfer until there is
+ * nothing more to do or the high-bandwidth request count is
+ * reached. In DMA mode, only need to queue one request. The
+ * controller automatically handles multiple packets for
+ * high-bandwidth transfers.
+ */
+ if (hcd->core_if->dma_enable || (status == 0 ||
+ qh->channel->requests ==
+ qh->channel->multi_count)) {
+ qh_ptr = qh_ptr->next;
+
+ /*
+ * Move the QH from the periodic assigned schedule to
+ * the periodic queued schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_queued);
+
+ /* done queuing high bandwidth */
+ hcd->core_if->queuing_high_bandwidth = 0;
+ }
+ }
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+
+ if (!list_empty(&hcd->periodic_sched_assigned) ||
+ no_queue_space || no_fifo_space)
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the periodic Tx
+ * FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ else
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+}
+
+/**
+ * This function processes the currently active host channels and queues
+ * transactions for these channels to the DWC_otg controller. It is called
+ * from HCD interrupt handler functions.
+ */
+void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type)
+{
+ /* Process host channels associated with periodic transfers. */
+ if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) &&
+ !list_empty(&hcd->periodic_sched_assigned))
+ process_periodic_channels(hcd);
+
+ /* Process host channels associated with non-periodic transfers. */
+ if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) {
+ if (!list_empty(&hcd->non_periodic_sched_active)) {
+ process_non_periodic_channels(hcd);
+ } else {
+ /*
+ * Ensure NP Tx FIFO empty interrupt is disabled when
+ * there are no non-periodic transfers to process.
+ */
+ u32 gintmsk = 0;
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, gintmsk, 0);
+ }
+ }
+}
+
+/**
+ * Sets the final status of an URB and returns it to the device driver. Any
+ * required cleanup of the URB is performed.
+ */
+void dwc_otg_hcd_complete_urb(struct dwc_hcd *hcd, struct urb *urb, int status)
+__releases(hcd->lock) __acquires(hcd->lock)
+{
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+
+ spin_unlock(&hcd->lock);
+ usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status);
+ spin_lock(&hcd->lock);
+}
diff --git a/drivers/usb/dwc/hcd.h b/drivers/usb/dwc/hcd.h
new file mode 100644
index 0000000..c3d86e4
--- /dev/null
+++ b/drivers/usb/dwc/hcd.h
@@ -0,0 +1,416 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#if !defined(__DWC_HCD_H__)
+#define __DWC_HCD_H__
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Host Contoller Driver (HCD).
+ *
+ * The Host Controller Driver (HCD) is responsible for translating requests
+ * from the USB Driver into the appropriate actions on the DWC_otg controller.
+ * It isolates the USBD from the specifics of the controller by providing an
+ * API to the USBD.
+ */
+
+/* Phases for control transfers. */
+enum dwc_control_phase {
+ DWC_OTG_CONTROL_SETUP,
+ DWC_OTG_CONTROL_DATA,
+ DWC_OTG_CONTROL_STATUS
+};
+
+/* Transaction types. */
+enum dwc_transaction_type {
+ DWC_OTG_TRANSACTION_NONE,
+ DWC_OTG_TRANSACTION_PERIODIC,
+ DWC_OTG_TRANSACTION_NON_PERIODIC,
+ DWC_OTG_TRANSACTION_ALL
+};
+
+/*
+ * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
+ * interrupt, or isochronous transfer. A single QTD is created for each URB
+ * (of one of these types) submitted to the HCD. The transfer associated with
+ * a QTD may require one or multiple transactions.
+ *
+ * A QTD is linked to a Queue Head, which is entered in either the
+ * non-periodic or periodic schedule for execution. When a QTD is chosen for
+ * execution, some or all of its transactions may be executed. After
+ * execution, the state of the QTD is updated. The QTD may be retired if all
+ * its transactions are complete or if an error occurred. Otherwise, it
+ * remains in the schedule so more transactions can be executed later.
+ */
+struct dwc_qtd {
+ /*
+ * Determines the PID of the next data packet for the data phase of
+ * control transfers. Ignored for other transfer types.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Current phase for control transfers (Setup, Data, or Status). */
+ enum dwc_control_phase control_phase;
+
+ /*
+ * Keep track of the current split type
+ * for FS/LS endpoints on a HS Hub
+ */
+ u8 complete_split;
+
+ /* How many bytes transferred during SSPLIT OUT */
+ u32 ssplit_out_xfer_count;
+
+ /*
+ * Holds the number of bus errors that have occurred for a transaction
+ * within this transfer.
+ */
+ u8 error_count;
+
+ /*
+ * Index of the next frame descriptor for an isochronous transfer. A
+ * frame descriptor describes the buffer position and length of the
+ * data to be transferred in the next scheduled (micro)frame of an
+ * isochronous transfer. It also holds status for that transaction.
+ * The frame index starts at 0.
+ */
+ int isoc_frame_index;
+
+ /* Position of the ISOC split on full/low speed */
+ u8 isoc_split_pos;
+
+ /* Position of the ISOC split in the buffer for the current frame */
+ u16 isoc_split_offset;
+
+ /* URB for this transfer */
+ struct urb *urb;
+
+ /* This list of QTDs */
+ struct list_head qtd_list_entry;
+
+ /* Field to track the qh pointer */
+ struct dwc_qh *qtd_qh_ptr;
+};
+
+/*
+ * A Queue Head (QH) holds the static characteristics of an endpoint and
+ * maintains a list of transfers (QTDs) for that endpoint. A QH structure may
+ * be entered in either the non-periodic or periodic schedule.
+ */
+struct dwc_qh {
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - USB_ENDPOINT_XFER_CONTROL
+ * - USB_ENDPOINT_XFER_ISOC
+ * - USB_ENDPOINT_XFER_BULK
+ * - USB_ENDPOINT_XFER_INT
+ */
+ u8 ep_type;
+ u8 ep_is_in;
+
+ /* wMaxPacketSize Field of Endpoint Descriptor. */
+ u16 maxp;
+
+ /*
+ * Determines the PID of the next data packet for non-control
+ * transfers. Ignored for control transfers.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Ping state if 1. */
+ u8 ping_state;
+
+ /* List of QTDs for this QH. */
+ struct list_head qtd_list;
+
+ /* Host channel currently processing transfers for this QH. */
+ struct dwc_hc *channel;
+
+ /* QTD currently assigned to a host channel for this QH. */
+ struct dwc_qtd *qtd_in_process;
+
+ /* Full/low speed endpoint on high-speed hub requires split. */
+ u8 do_split;
+
+ /* Periodic schedule information */
+
+ /* Bandwidth in microseconds per (micro)frame. */
+ u8 usecs;
+
+ /* Interval between transfers in (micro)frames. */
+ u16 interval;
+
+ /*
+ * (micro)frame to initialize a periodic transfer. The transfer
+ * executes in the following (micro)frame.
+ */
+ u16 sched_frame;
+
+ /* (micro)frame at which last start split was initialized. */
+ u16 start_split_frame;
+
+ u16 speed;
+ u16 frame_usecs[8];
+
+ /* Entry for QH in either the periodic or non-periodic schedule. */
+ struct list_head qh_list_entry;
+};
+
+/* Gets the struct usb_hcd that contains a struct dwc_hcd. */
+static inline struct usb_hcd *dwc_otg_hcd_to_hcd(struct dwc_hcd *dwc_hcd)
+{
+ return container_of((void *)dwc_hcd, struct usb_hcd, hcd_priv);
+}
+
+/* HCD Create/Destroy Functions */
+extern int __init dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in host
+ * mode.
+ */
+extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd);
+extern void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc);
+extern void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status _halt_status);
+
+/* Transaction Execution Functions */
+extern enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd
+ *hcd);
+extern void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type);
+extern void dwc_otg_hcd_complete_urb(struct dwc_hcd *_hcd, struct urb *urb,
+ int status);
+
+/* Interrupt Handler Functions */
+extern int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd);
+
+/* Schedule Queue Functions */
+extern int init_hcd_usecs(struct dwc_hcd *hcd);
+extern void dwc_otg_hcd_qh_free(struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_csplit);
+extern int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int delay);
+extern struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb,
+ gfp_t _mem_flags);
+extern int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *dwc_hcd);
+
+/*
+ * Frees the memory for a QTD structure. QTD should already be removed from
+ * list.
+ */
+static inline void dwc_otg_hcd_qtd_free(struct dwc_qtd *_qtd)
+{
+ kfree(_qtd);
+}
+
+/* Removes a QTD from list. */
+static inline void dwc_otg_hcd_qtd_remove(struct dwc_qtd *_qtd)
+{
+ list_del(&_qtd->qtd_list_entry);
+}
+
+/* Remove and free a QTD */
+static inline void dwc_otg_hcd_qtd_remove_and_free(struct dwc_qtd *_qtd)
+{
+ dwc_otg_hcd_qtd_remove(_qtd);
+ dwc_otg_hcd_qtd_free(_qtd);
+}
+
+struct dwc_qh *dwc_urb_to_qh(struct urb *_urb);
+
+/* Gets the usb_host_endpoint associated with an URB. */
+static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb)
+{
+ struct usb_device *dev = _urb->dev;
+ int ep_num = usb_pipeendpoint(_urb->pipe);
+
+ if (usb_pipein(_urb->pipe))
+ return dev->ep_in[ep_num];
+ else
+ return dev->ep_out[ep_num];
+}
+
+/*
+ * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
+ * qualified with its direction (possible 32 endpoints per device).
+ */
+#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) \
+ ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
+ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
+
+/* Gets the QH that contains the list_head */
+#define dwc_list_to_qh(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qh, qh_list_entry))
+
+/* Gets the QTD that contains the list_head */
+#define dwc_list_to_qtd(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qtd, qtd_list_entry))
+
+/* Check if QH is non-periodic */
+#define dwc_qh_is_non_per(_qh_ptr_) \
+ ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \
+ (_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL))
+
+/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */
+#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/* Packet size for any kind of endpoint descriptor */
+#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+
+/*
+ * Returns true if _frame1 is less than or equal to _frame2. The comparison is
+ * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
+ * frame number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_le(u16 _frame1, u16 _frame2)
+{
+ return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <=
+ (DWC_HFNUM_MAX_FRNUM >> 1);
+}
+
+/*
+ * Returns true if _frame1 is greater than _frame2. The comparison is done
+ * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
+ * number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_gt(u16 _frame1, u16 _frame2)
+{
+ return (_frame1 != _frame2) &&
+ (((_frame1 - _frame2) &
+ DWC_HFNUM_MAX_FRNUM) < (DWC_HFNUM_MAX_FRNUM >> 1));
+}
+
+/*
+ * Increments _frame by the amount specified by _inc. The addition is done
+ * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
+ */
+static inline u16 dwc_frame_num_inc(u16 _frame, u16 _inc)
+{
+ return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM;
+}
+
+static inline u16 dwc_full_frame_num(u16 _frame)
+{
+ return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3;
+}
+
+static inline u16 dwc_micro_frame_num(u16 _frame)
+{
+ return (_frame) & 0x7;
+}
+
+static inline ulong gintsts_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTSTS;
+}
+
+static inline ulong gintmsk_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTMSK;
+}
+
+static inline ulong gahbcfg_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GAHBCFG;
+}
+
+static inline const char *pipetype_str(unsigned int pipe)
+{
+ switch (usb_pipetype(pipe)) {
+ case PIPE_CONTROL:
+ return "control";
+ case PIPE_BULK:
+ return "bulk";
+ case PIPE_INTERRUPT:
+ return "interrupt";
+ case PIPE_ISOCHRONOUS:
+ return "isochronous";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *dev_speed_str(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ return "high";
+ case USB_SPEED_FULL:
+ return "full";
+ case USB_SPEED_LOW:
+ return "low";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *ep_type_str(u8 type)
+{
+ switch (type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ return "isochronous";
+ case USB_ENDPOINT_XFER_INT:
+ return "interrupt";
+ case USB_ENDPOINT_XFER_CONTROL:
+ return "control";
+ case USB_ENDPOINT_XFER_BULK:
+ return "bulk";
+ default:
+ return "?";
+ }
+}
+#endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 03/10]USB/ppc4xx: Add Synopsys DWC OTG Core Interface Layer (CIL)
From: Rupjyoti Sarmah @ 2012-05-03 12:28 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Core Interface Layer Common provides common functions for both host
controller and peripheral controller. CIL manages the memory map
for the core. It also handles basic tasks like reading/writing the
registers and data FIFOs in the controller. CIL performs basic
services that are not specific to either the host or device modes
of operation. These services include management of the OTG Host
Negotiation Protocol (HNP) and Session Request Protocol (SRP).
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/cil.c | 849 ++++++++++++++++++++++++++++++++
drivers/usb/dwc/cil.h | 1161 ++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/cil_intr.c | 584 ++++++++++++++++++++++
3 files changed, 2594 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/cil.c
create mode 100644 drivers/usb/dwc/cil.h
create mode 100644 drivers/usb/dwc/cil_intr.c
diff --git a/drivers/usb/dwc/cil.c b/drivers/usb/dwc/cil.c
new file mode 100644
index 0000000..1df8852
--- /dev/null
+++ b/drivers/usb/dwc/cil.c
@@ -0,0 +1,849 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Rupjyoti Sarmah <rsarmah@apm.com>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <linux/delay.h>
+
+#include "cil.h"
+
+void dwc_otg_enable_global_interrupts(struct core_if *core_if)
+{
+ u32 ahbcfg = 0;
+
+ ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK;
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 0,
+ ahbcfg);
+}
+
+void dwc_otg_disable_global_interrupts(struct core_if *core_if)
+{
+ u32 ahbcfg = 0;
+
+ ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK;
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG,
+ ahbcfg, 0);
+}
+
+/**
+ * Tests if the current hardware is using a full speed phy.
+ */
+static inline int full_speed_phy(struct core_if *core_if)
+{
+ if ((DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls) ||
+ core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)
+ return 1;
+ return 0;
+}
+
+/**
+ * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY
+ * type.
+ */
+void init_fslspclksel(struct core_if *core_if)
+{
+ u32 val;
+ u32 hcfg;
+
+ if (full_speed_phy(core_if))
+ val = DWC_HCFG_48_MHZ;
+ else
+ /* High speed PHY running at full speed or high speed */
+ val = DWC_HCFG_30_60_MHZ;
+
+ hcfg = dwc_reg_read(core_if->host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, val);
+ dwc_reg_write(core_if->host_if->host_global_regs, DWC_HCFG, hcfg);
+}
+
+/**
+ * Initializes the DevSpd field of the DCFG register depending on the PHY type
+ * and the enumeration speed of the device.
+ */
+static void init_devspd(struct core_if *core_if)
+{
+ u32 val;
+ u32 dcfg;
+
+ if (full_speed_phy(core_if))
+ val = 0x3;
+ else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL)
+ /* High speed PHY running at full speed */
+ val = 0x1;
+ else
+ /* High speed PHY running at high speed */
+ val = 0x0;
+
+ dcfg = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_DEV_SPEED_WR(dcfg, val);
+ dwc_reg_write(core_if->dev_if->dev_global_regs, DWC_DCFG, dcfg);
+}
+
+/**
+ * Do core a soft reset of the core. Be careful with this because it
+ * resets all the internal state machines of the core.
+ */
+static void dwc_otg_core_reset(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset;
+ int count = 0;
+
+ /* Wait for AHB master IDLE state. */
+ do {
+ udelay(10);
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 100000) {
+ pr_warning("%s() HANG! AHB Idle GRSTCTL=%0x\n",
+ __func__, greset);
+ return;
+ }
+ } while (!(greset & DWC_RSTCTL_AHB_IDLE));
+
+ /* Core Soft Reset */
+ count = 0;
+ greset |= DWC_RSTCTL_SFT_RST;
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! Soft Reset "
+ "GRSTCTL=%0x\n", __func__, greset);
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_SFT_RST);
+
+ /* Wait for 3 PHY Clocks */
+ msleep(100);
+}
+
+/**
+ * This function initializes the commmon interrupts, used in both
+ * device and host modes.
+ */
+void dwc_otg_enable_common_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Clear any pending OTG Interrupts */
+ dwc_reg_write(global_regs, DWC_GOTGINT, 0xFFFFFFFF);
+
+ /* Clear any pending interrupts */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the interrupts in the GINTMSK. */
+ intr_mask = DWC_INTMSK_MODE_MISMTC|\
+ DWC_INTMSK_OTG|\
+ DWC_INTMSK_CON_ID_STS_CHG|\
+ DWC_INTMSK_WKP|\
+ DWC_INTMSK_SES_DISCON_DET|\
+ DWC_INTMSK_USB_SUSP|\
+ DWC_INTMSK_NEW_SES_DET;
+ if (!core_if->dma_enable)
+ intr_mask |= DWC_INTMSK_RXFIFO_NOT_EMPT;
+ dwc_reg_write(global_regs, DWC_GINTMSK, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers and prepares the
+ * core for device mode or host mode operation.
+ */
+void dwc_otg_core_init(struct core_if *core_if)
+{
+ u32 i;
+ ulong global_reg = core_if->core_global_regs;
+ struct device_if *dev_if = core_if->dev_if;
+ u32 ahbcfg = 0;
+ u32 i2cctl = 0;
+ u32 gusbcfg;
+
+ /* Common Initialization */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+
+ /* Program the ULPI External VBUS bit if needed */
+ gusbcfg |= DWC_USBCFG_ULPI_EXT_VBUS_DRV;
+
+ /* Set external TS Dline pulsing */
+ if (core_if->core_params->ts_dline == 1)
+ gusbcfg |= DWC_USBCFG_TERM_SEL_DL_PULSE;
+ else
+ gusbcfg = gusbcfg & (~((u32) DWC_USBCFG_TERM_SEL_DL_PULSE));
+
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset the Controller */
+ dwc_otg_core_reset(core_if);
+
+ for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ dev_if->perio_tx_fifo_size[i] =
+ dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16;
+ }
+ for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); i++) {
+ dev_if->tx_fifo_size[i] =
+ dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16;
+ }
+
+ core_if->total_fifo_size = DWC_HWCFG3_DFIFO_DEPTH_RD(core_if->hwcfg3);
+ core_if->rx_fifo_size = dwc_reg_read(global_reg, DWC_GRXFSIZ);
+ core_if->nperio_tx_fifo_size =
+ dwc_reg_read(global_reg, DWC_GRXFSIZ) >> 16;
+ /*
+ * This programming sequence needs to happen in FS mode before any
+ * other programming occurs
+ */
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL &&
+ core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) {
+ /*
+ * core_init() is now called on every switch so only call the
+ * following for the first time through.
+ */
+ if (!core_if->phy_init_done) {
+ core_if->phy_init_done = 1;
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset after a PHY select */
+ dwc_otg_core_reset(core_if);
+ }
+
+ /*
+ * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS.
+ * Also do this on HNP Dev/Host mode switches (done in dev_init
+ * and host_init).
+ */
+ if (dwc_otg_is_host_mode(core_if))
+ init_fslspclksel(core_if);
+ else
+ init_devspd(core_if);
+
+ if (core_if->core_params->i2c_enable) {
+ /* Program GUSBCFG.OtgUtmifsSel to I2C */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_OTGUTMIFSSEL;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Program GI2CCTL.I2CEn */
+ i2cctl = dwc_reg_read(global_reg, DWC_GI2CCTL);
+ i2cctl |= DWC_I2CCTL_I2CDEVADDR(1);
+ i2cctl &= ~DWC_I2CCTL_I2CEN;
+ dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl);
+ i2cctl |= DWC_I2CCTL_I2CEN;
+ dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl);
+ }
+ } else if (!core_if->phy_init_done) {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ core_if->phy_init_done = 1;
+ if (core_if->core_params->phy_type)
+ gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_UTMI_SEL);
+
+ if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) {
+ /* ULPI interface */
+ gusbcfg |= DWC_USBCFG_PHYIF;
+ if (core_if->core_params->phy_ulpi_ddr)
+ gusbcfg |= DWC_USBCFG_DDRSEL;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_DDRSEL);
+ } else {
+ /* UTMI+ interface */
+ if (core_if->core_params->phy_utmi_width == 16)
+ gusbcfg |= DWC_USBCFG_PHYIF;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_PHYIF);
+ }
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset after setting the PHY parameters */
+ dwc_otg_core_reset(core_if);
+ }
+
+ if (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls) {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_ULPI_FSLS;
+ gusbcfg |= DWC_USBCFG_ULPI_CLK_SUS_M;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+ } else {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_FSLS);
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_CLK_SUS_M);
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+ }
+
+ /* Program the GAHBCFG Register. */
+ switch (DWC_HWCFG2_ARCH_RD(core_if->hwcfg2)) {
+ case DWC_SLAVE_ONLY_ARCH:
+ ahbcfg &= ~DWC_AHBCFG_NPFIFO_EMPTY; /* HALF empty */
+ ahbcfg &= ~DWC_AHBCFG_FIFO_EMPTY; /* HALF empty */
+ core_if->dma_enable = 0;
+ break;
+ case DWC_EXT_DMA_ARCH:
+ ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) |
+ DWC_AHBCFG_BURST_LEN(core_if->core_params->dma_burst_size);
+ core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+ break;
+ case DWC_INT_DMA_ARCH:
+ ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) |
+ DWC_AHBCFG_BURST_LEN(DWC_GAHBCFG_INT_DMA_BURST_INCR);
+ core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+ break;
+ }
+
+ if (core_if->dma_enable)
+ ahbcfg |= DWC_AHBCFG_DMA_ENA;
+ else
+ ahbcfg &= ~DWC_AHBCFG_DMA_ENA;
+ dwc_reg_write(global_reg, DWC_GAHBCFG, ahbcfg);
+ core_if->en_multiple_tx_fifo =
+ DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4);
+
+ /* Program the GUSBCFG register. */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ switch (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2)) {
+ case DWC_MODE_HNP_SRP_CAPABLE:
+ if (core_if->core_params->otg_cap ==
+ DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_HNP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_ONLY_CAPABLE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_HNP_SRP_CAPABLE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_CAPABLE_DEVICE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_SRP_CAPABLE_DEVICE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_CAPABLE_HOST:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_SRP_CAPABLE_HOST:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ }
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Enable common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Do device or host intialization based on mode during PCD
+ * and HCD initialization
+ */
+ if (dwc_otg_is_host_mode(core_if)) {
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ } else {
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ if (dwc_has_feature(core_if, DWC_DEVICE_ONLY))
+ dwc_otg_core_dev_init(core_if);
+ }
+}
+
+/**
+ * This function enables the Device mode interrupts.
+ *
+ * Note that the bits in the Device IN endpoint mask register are laid out
+ * exactly the same as the Device IN endpoint interrupt register.
+ */
+static void dwc_otg_enable_device_interrupts(struct core_if *core_if)
+{
+ u32 intr_mask = 0;
+ u32 msk = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /* Enable interrupts */
+ intr_mask |= DWC_INTMSK_USB_RST|\
+ DWC_INTMSK_ENUM_DONE|\
+ DWC_INTMSK_IN_ENDP|\
+ DWC_INTMSK_OUT_ENDP|\
+ DWC_INTMSK_EARLY_SUSP;
+ if (!core_if->en_multiple_tx_fifo)
+ intr_mask |= DWC_INTMSK_ENDP_MIS_MTCH;
+
+ /* Periodic EP */
+ intr_mask |= DWC_INTMSK_ISYNC_OUTPKT_DRP|\
+ DWC_INTMSK_END_OF_PFRM|\
+ DWC_INTMSK_INCMP_IN_ATX|\
+ DWC_INTMSK_INCMP_OUT_PTX;
+
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+
+ msk = DWC_DIEPMSK_TXFIFO_UNDERN_RW(msk, 1);
+ dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DIEPMSK,
+ msk, msk);
+}
+
+/**
+ * Configures the device data fifo sizes when dynamic sizing is enabled.
+ */
+static void config_dev_dynamic_fifos(struct core_if *core_if)
+{
+ u32 i;
+ ulong regs = core_if->core_global_regs;
+ struct core_params *params = core_if->core_params;
+ u32 txsize = 0;
+ u32 nptxsize = 0;
+ u32 ptxsize = 0;
+
+ /* Rx FIFO */
+ dwc_reg_write(regs, DWC_GRXFSIZ, params->dev_rx_fifo_size);
+
+ /* Set Periodic and Non-periodic Tx FIFO Mask bits to all 0 */
+ core_if->p_tx_msk = 0;
+ core_if->tx_msk = 0;
+
+ if (core_if->en_multiple_tx_fifo == 0) {
+ /* Non-periodic Tx FIFO */
+ nptxsize = DWC_TX_FIFO_DEPTH_WR(nptxsize,
+ params->
+ dev_nperio_tx_fifo_size);
+ nptxsize =
+ DWC_TX_FIFO_START_ADDR_WR(nptxsize,
+ params->dev_rx_fifo_size);
+ dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize);
+
+ ptxsize = DWC_TX_FIFO_START_ADDR_WR(ptxsize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (nptxsize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (nptxsize)));
+ for (i = 0;
+ i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ ptxsize =
+ DWC_TX_FIFO_DEPTH_WR(ptxsize,
+ params->
+ dev_perio_tx_fifo_size[i]);
+ dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i), ptxsize);
+ ptxsize = DWC_TX_FIFO_START_ADDR_WR(ptxsize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (ptxsize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (ptxsize)));
+ }
+ } else {
+ nptxsize = DWC_TX_FIFO_DEPTH_WR(nptxsize,
+ params->
+ dev_nperio_tx_fifo_size);
+ nptxsize =
+ DWC_TX_FIFO_START_ADDR_WR(nptxsize,
+ params->dev_rx_fifo_size);
+ dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize);
+
+ txsize = DWC_TX_FIFO_START_ADDR_WR(txsize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (nptxsize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (nptxsize)));
+ for (i = 1;
+ i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ txsize =
+ DWC_TX_FIFO_DEPTH_WR(txsize,
+ params->dev_tx_fifo_size[i]);
+ dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i - 1),
+ txsize);
+ txsize = DWC_TX_FIFO_START_ADDR_WR(txsize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (txsize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (txsize)));
+ }
+ }
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * device mode.
+ */
+void dwc_otg_core_dev_init(struct core_if *c_if)
+{
+ u32 i;
+ struct device_if *d_if = c_if->dev_if;
+ struct core_params *params = c_if->core_params;
+ u32 dcfg;
+ u32 resetctl = 0;
+ u32 dthrctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(c_if->pcgcctl, 0, 0);
+
+ /* Device configuration register */
+ init_devspd(c_if);
+ dcfg = dwc_reg_read(d_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_P_FRM_INTRVL_WR(dcfg, DWC_DCFG_FRAME_INTERVAL_80);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DCFG, dcfg);
+
+ /* If needed configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(c_if->hwcfg2) && params->enable_dynamic_fifo)
+ config_dev_dynamic_fifos(c_if);
+
+ /* Flush the FIFOs */
+ dwc_otg_flush_tx_fifo(c_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(c_if);
+
+ /* Flush the Learning Queue. */
+ resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH;
+ dwc_reg_write(c_if->core_global_regs, DWC_GRSTCTL, resetctl);
+
+ /* Clear all pending Device Interrupts */
+ dwc_reg_write(d_if->dev_global_regs, DWC_DIEPMSK, 0);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DOEPMSK, 0);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DAINT, 0xFFFFFFFF);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DAINTMSK, 0);
+
+ for (i = 0; i <= d_if->num_in_eps; i++) {
+ u32 depctl = 0;
+
+ depctl = dwc_reg_read(d_if->in_ep_regs[i], DWC_DIEPCTL);
+ if (DWC_DEPCTL_EPENA_RD(depctl)) {
+ depctl = 0;
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1);
+ } else {
+ depctl = 0;
+ }
+
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPCTL, depctl);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPTSIZ, 0);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPDMA, 0);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPINT, 0xFF);
+ }
+
+ for (i = 0; i <= d_if->num_out_eps; i++) {
+ u32 depctl = 0;
+ depctl = dwc_reg_read(d_if->out_ep_regs[i], DWC_DOEPCTL);
+ if (DWC_DEPCTL_EPENA_RD(depctl)) {
+ depctl = 0;
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1);
+ } else {
+ depctl = 0;
+ }
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPCTL, depctl);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPTSIZ, 0);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPDMA, 0);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPINT, 0xFF);
+ }
+
+ if (c_if->en_multiple_tx_fifo && c_if->dma_enable) {
+ d_if->non_iso_tx_thr_en = c_if->core_params->thr_ctl & 0x1;
+ d_if->iso_tx_thr_en = (c_if->core_params->thr_ctl >> 1) & 0x1;
+ d_if->rx_thr_en = (c_if->core_params->thr_ctl >> 2) & 0x1;
+ d_if->rx_thr_length = c_if->core_params->rx_thr_length;
+ d_if->tx_thr_length = c_if->core_params->tx_thr_length;
+
+ dthrctl = 0;
+ dthrctl = DWC_DTHCTRL_NON_ISO_THR_ENA_RW\
+ (dthrctl, d_if->non_iso_tx_thr_en);
+ dthrctl = DWC_DTHCTRL_ISO_THR_EN_RW\
+ (dthrctl, d_if->iso_tx_thr_en);
+ dthrctl = DWC_DTHCTRL_TX_THR_LEN_RW\
+ (dthrctl, d_if->tx_thr_length);
+ dthrctl = DWC_DTHCTRL_RX_THR_EN_RW(dthrctl, d_if->rx_thr_en);
+ dthrctl = DWC_DTHCTRL_RX_THR_LEN_RW\
+ (dthrctl, d_if->rx_thr_length);
+ dwc_reg_write(d_if->dev_global_regs,
+ DWC_DTKNQR3_DTHRCTL, dthrctl);
+
+ }
+
+ dwc_otg_enable_device_interrupts(c_if);
+}
+
+/**
+ * This function reads a packet from the Rx FIFO into the destination buffer.
+ * To read SETUP data use dwc_otg_read_setup_packet.
+ */
+
+void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 _bytes)
+{
+ u32 i;
+ int word_count = (_bytes + 3) / 4;
+ u32 fifo = core_if->data_fifo[0];
+ u32 *data_buff = (u32 *) dest;
+
+ /*
+ * This requires reading data from the FIFO into a u32 temp buffer,
+ * then moving it into the data buffer.
+ */
+ for (i = 0; i < word_count; i++, data_buff++)
+ *data_buff = dwc_read_fifo32(fifo);
+}
+
+/**
+ * Flush a Tx FIFO.
+ */
+void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int num)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset = 0;
+ int count = 0;
+
+ greset |= DWC_RSTCTL_TX_FIFO_FLUSH;
+ greset = DWC_RSTCTL_TX_FIFO_NUM(greset, num);
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! GRSTCTL=%0x "
+ "GNPTXSTS=0x%08x\n", __func__, greset,
+ dwc_reg_read(global_regs, DWC_GNPTXSTS));
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_TX_FIFO_FLUSH);
+
+ /* Wait for 3 PHY Clocks */
+ udelay(1);
+}
+
+/**
+ * Flush Rx FIFO.
+ */
+void dwc_otg_flush_rx_fifo(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset = 0;
+ int count = 0;
+
+ greset |= DWC_RSTCTL_RX_FIFO_FLUSH;
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! GRSTCTL=%0x\n",
+ __func__, greset);
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_RX_FIFO_FLUSH);
+
+ /* Wait for 3 PHY Clocks */
+ udelay(1);
+}
+
+/**
+ * Register HCD callbacks.
+ * The callbacks are used to start and stop the HCD for interrupt processing.
+ */
+void __devinit dwc_otg_cil_register_hcd_callbacks(struct core_if *c_if,
+ struct cil_callbacks *cb,
+ void *p)
+{
+ c_if->hcd_cb = cb;
+ cb->p = p;
+}
+
+/**
+ * Register PCD callbacks.
+ * The callbacks are used to start and stop the PCD for interrupt processing.
+ */
+void __devinit dwc_otg_cil_register_pcd_callbacks(struct core_if *c_if,
+ struct cil_callbacks *cb,
+ void *p)
+{
+ c_if->pcd_cb = cb;
+ cb->p = p;
+}
+
+/**
+ * This function is called to initialize the DWC_otg CSR data structures.
+ *
+ * The register addresses in the device and host structures are initialized from
+ * the base address supplied by the caller. The calling function must make the
+ * OS calls to get the base address of the DWC_otg controller registers.
+ *
+ * The params argument holds the parameters that specify how the core should be
+ * configured.
+ */
+struct core_if __devinit *dwc_otg_cil_init(const __iomem u32 * base,
+ struct core_params *params)
+{
+ struct core_if *core_if;
+ struct device_if *dev_if;
+ struct dwc_host_if *host_if;
+ u8 *reg_base = (__force u8 *)base;
+ u32 offset;
+ u32 i;
+
+ core_if = kzalloc(sizeof(*core_if), GFP_KERNEL);
+ if (!core_if)
+ goto exit;
+
+ core_if->core_params = params;
+ core_if->core_global_regs = (ulong)reg_base;
+
+ /* Allocate the Device Mode structures. */
+ dev_if = kmalloc(sizeof(*dev_if), GFP_KERNEL);
+ if (!dev_if) {
+ kfree(core_if);
+ goto exit;
+ }
+
+ dev_if->dev_global_regs = (ulong)(reg_base + DWC_DEV_GLOBAL_REG_OFFSET);
+
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ offset = i * DWC_EP_REG_OFFSET;
+
+ dev_if->in_ep_regs[i] = (ulong)(reg_base +
+ DWC_DEV_IN_EP_REG_OFFSET +
+ offset);
+
+ dev_if->out_ep_regs[i] = (ulong)(reg_base +
+ DWC_DEV_OUT_EP_REG_OFFSET +
+ offset);
+ }
+
+ dev_if->speed = 0; /* unknown */
+ core_if->dev_if = dev_if;
+
+ /* Allocate the Host Mode structures. */
+ host_if = kmalloc(sizeof(*host_if), GFP_KERNEL);
+ if (!host_if) {
+ kfree(dev_if);
+ kfree(core_if);
+ goto exit;
+ }
+
+ host_if->host_global_regs = (ulong)(reg_base +
+ DWC_OTG_HOST_GLOBAL_REG_OFFSET);
+
+ host_if->hprt0 = (ulong)(reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET);
+
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ offset = i * DWC_OTG_CHAN_REGS_OFFSET;
+
+ host_if->hc_regs[i] = (ulong)(reg_base +
+ DWC_OTG_HOST_CHAN_REGS_OFFSET +
+ offset);
+ }
+
+ host_if->num_host_channels = MAX_EPS_CHANNELS;
+ core_if->host_if = host_if;
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ core_if->data_fifo[i] =
+ (ulong)(reg_base + DWC_OTG_DATA_FIFO_OFFSET +
+ (i * DWC_OTG_DATA_FIFO_SIZE));
+ }
+ core_if->pcgcctl = (ulong)(reg_base + DWC_OTG_PCGCCTL_OFFSET);
+
+ /*
+ * Store the contents of the hardware configuration registers here for
+ * easy access later.
+ */
+ core_if->hwcfg1 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG1);
+ core_if->hwcfg2 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG2);
+ core_if->hwcfg3 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG3);
+ core_if->hwcfg4 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG4);
+
+ /* Set the SRP sucess bit for FS-I2c */
+ core_if->srp_success = 0;
+ core_if->srp_timer_started = 0;
+ return core_if;
+exit:
+ return core_if;
+
+
+}
+
+/**
+ * This function frees the structures allocated by dwc_otg_cil_init().
+ */
+
+void dwc_otg_cil_remove(struct core_if *core_if)
+{
+ /* Disable all interrupts */
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 1, 0);
+ dwc_reg_write(core_if->core_global_regs, DWC_GINTMSK, 0);
+
+ if (core_if) {
+ kfree(core_if->dev_if);
+ kfree(core_if->host_if);
+ }
+ kfree(core_if);
+}
+
diff --git a/drivers/usb/dwc/cil.h b/drivers/usb/dwc/cil.h
new file mode 100644
index 0000000..ff01eac
--- /dev/null
+++ b/drivers/usb/dwc/cil.h
@@ -0,0 +1,1161 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#if !defined(__DWC_CIL_H__)
+#define __DWC_CIL_H__
+#include <linux/io.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/interrupt.h>
+#include <linux/dmapool.h>
+#include <linux/spinlock.h>
+#include <linux/usb/otg.h>
+
+#include "regs.h"
+
+#ifdef CONFIG_DWC_DEBUG
+#define DEBUG
+#endif
+
+/**
+ * Reads the content of a register.
+ */
+static inline u32 dwc_reg_read(ulong reg , u32 offset)
+{
+
+#ifdef CONFIG_DWC_OTG_REG_LE
+ return in_le32((void __iomem *)(reg + offset));
+#else
+ return in_be32((void __iomem *)(reg + offset));
+#endif
+}
+
+static inline void dwc_reg_write(ulong reg, u32 offset, const u32 value)
+{
+#ifdef CONFIG_DWC_OTG_REG_LE
+ out_le32((void __iomem *)(reg + offset), value);
+#else
+ out_be32((void __iomem *)(reg + offset), value);
+#endif
+};
+/**
+ * This function modifies bit values in a register. Using the
+ * algorithm: (reg_contents & ~clear_mask) | set_mask.
+ */
+static inline void dwc_reg_modify(ulong reg, u32 offset, const u32 _clear_mask,
+ const u32 _set_mask)
+{
+#ifdef CONFIG_DWC_OTG_REG_LE
+ out_le32((void __iomem *)(reg + offset),
+ (in_le32((void __iomem *)(reg + offset))
+ & ~_clear_mask) | _set_mask);
+#else
+ out_be32((void __iomem *)(reg + offset),
+ (in_be32(((void __iomem *))(reg + offset))
+ & ~_clear_mask) | _set_mask);
+#endif
+};
+
+static inline void dwc_write_fifo32(ulong reg, const u32 _value)
+{
+#ifdef CONFIG_DWC_OTG_FIFO_LE
+ out_le32((void __iomem *)reg, _value);
+#else
+ out_be32((void __iomem *)reg, _value);
+#endif
+};
+
+static inline u32 dwc_read_fifo32(ulong _reg)
+{
+#ifdef CONFIG_DWC_OTG_FIFO_LE
+ return in_le32((void __iomem *) _reg);
+#else
+ return in_be32((void __iomem *) _reg);
+#endif
+};
+
+/*
+ * Debugging support vanishes in non-debug builds.
+ */
+/* Display CIL Debug messages */
+#define dwc_dbg_cil (0x2)
+
+/* Display CIL Verbose debug messages */
+#define dwc_dbg_cilv (0x20)
+
+/* Display PCD (Device) debug messages */
+#define dwc_dbg_pcd (0x4)
+
+/* Display PCD (Device) Verbose debug messages */
+#define dwc_dbg_pcdv (0x40)
+
+/* Display Host debug messages */
+#define dwc_dbg_hcd (0x8)
+
+/* Display Verbose Host debug messages */
+#define dwc_dbg_hcdv (0x80)
+
+/* Display enqueued URBs in host mode. */
+#define dwc_dbg_hcd_urb (0x800)
+
+/* Display "special purpose" debug messages */
+#define dwc_dbg_sp (0x400)
+
+/* Display all debug messages */
+#define dwc_dbg_any (0xFF)
+
+/* All debug messages off */
+#define dwc_dbg_off 0
+
+/* Prefix string for DWC_DEBUG print macros. */
+#define usb_dwc "dwc_otg: "
+
+/*
+ * This file contains the interface to the Core Interface Layer.
+ */
+
+/*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ *
+ * MAX_XFER_LEN is set to 1024 right now, but could be 2047,
+ * since the xfer-size field in the 405EZ USB device controller
+ * implementation has 11 bits. Using 1024 seems to work for now.
+ */
+#define MAX_XFER_LEN 1024
+
+/*
+ * The dwc_ep structure represents the state of a single endpoint when acting in
+ * device mode. It contains the data items needed for an endpoint to be
+ * activated and transfer packets.
+ */
+struct dwc_ep {
+ /* EP number used for register address lookup */
+ u8 num;
+ /* EP direction 0 = OUT */
+ unsigned is_in:1;
+ /* EP active. */
+ unsigned active:1;
+
+ /*
+ * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use
+ * non-periodic Tx FIFO If dedicated Tx FIFOs are enabled for all
+ * IN Eps - Tx FIFO # FOR IN EPs
+ */
+ unsigned tx_fifo_num:4;
+ /* EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */
+ unsigned type:2;
+#define DWC_OTG_EP_TYPE_CONTROL 0
+#define DWC_OTG_EP_TYPE_ISOC 1
+#define DWC_OTG_EP_TYPE_BULK 2
+#define DWC_OTG_EP_TYPE_INTR 3
+
+ /* DATA start PID for INTR and BULK EP */
+ unsigned data_pid_start:1;
+ /* Frame (even/odd) for ISOC EP */
+ unsigned even_odd_frame:1;
+ /* Max Packet bytes */
+ unsigned maxpacket:11;
+
+ ulong dma_addr;
+
+ /*
+ * Pointer to the beginning of the transfer buffer -- do not modify
+ * during transfer.
+ */
+ u8 *start_xfer_buff;
+ /* pointer to the transfer buffer */
+ u8 *xfer_buff;
+ /* Number of bytes to transfer */
+ unsigned xfer_len:19;
+ /* Number of bytes transferred. */
+ unsigned xfer_count:19;
+ /* Sent ZLP */
+ unsigned sent_zlp:1;
+ /* Total len for control transfer */
+ unsigned total_len:19;
+
+ /* stall clear flag */
+ unsigned stall_clear_flag:1;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ *
+ * "bytes_pending" will hold the amount of bytes that are
+ * still pending to be send in further messages to complete
+ * the bigger transfer.
+ */
+ u32 bytes_pending;
+};
+
+/*
+ * States of EP0.
+ */
+enum ep0_state {
+ EP0_DISCONNECT = 0, /* no host */
+ EP0_IDLE = 1,
+ EP0_IN_DATA_PHASE = 2,
+ EP0_OUT_DATA_PHASE = 3,
+ EP0_STATUS = 4,
+ EP0_STALL = 5,
+};
+
+/* Fordward declaration.*/
+struct dwc_pcd;
+
+/*
+ * This structure describes an EP, there is an array of EPs in the PCD
+ * structure.
+ */
+struct pcd_ep {
+ /* USB EP data */
+ struct usb_ep ep;
+ /* USB EP Descriptor */
+ const struct usb_endpoint_descriptor *desc;
+
+ /* queue of dwc_otg_pcd_requests. */
+ struct list_head queue;
+ unsigned stopped:1;
+ unsigned disabling:1;
+ unsigned dma:1;
+ unsigned queue_sof:1;
+ unsigned wedged:1;
+
+ /* DWC_otg ep data. */
+ struct dwc_ep dwc_ep;
+
+ /* Pointer to PCD */
+ struct dwc_pcd *pcd;
+};
+
+/*
+ * DWC_otg PCD Structure.
+ * This structure encapsulates the data for the dwc_otg PCD.
+ */
+struct dwc_pcd {
+ /* USB gadget */
+ struct usb_gadget gadget;
+ /* USB gadget driver pointer */
+ struct usb_gadget_driver *driver;
+ /* The DWC otg device pointer. */
+ struct dwc_otg_device *otg_dev;
+
+ /* State of EP0 */
+ enum ep0_state ep0state;
+ /* EP0 Request is pending */
+ unsigned ep0_pending:1;
+ /* Indicates when SET CONFIGURATION Request is in process */
+ unsigned request_config:1;
+ /* The state of the Remote Wakeup Enable. */
+ unsigned remote_wakeup_enable:1;
+ /* The state of the B-Device HNP Enable. */
+ unsigned b_hnp_enable:1;
+ /* The state of A-Device HNP Support. */
+ unsigned a_hnp_support:1;
+ /* The state of the A-Device Alt HNP support. */
+ unsigned a_alt_hnp_support:1;
+ /* Count of pending Requests */
+ unsigned request_pending;
+
+ /*
+ * SETUP packet for EP0. This structure is allocated as a DMA buffer on
+ * PCD initialization with enough space for up to 3 setup packets.
+ */
+ union {
+ struct usb_ctrlrequest req;
+ u32 d32[2];
+ } *setup_pkt;
+
+ struct dma_pool *dwc_pool;
+ dma_addr_t setup_pkt_dma_handle;
+
+ /* 2-byte dma buffer used to return status from GET_STATUS */
+ u16 *status_buf;
+ dma_addr_t status_buf_dma_handle;
+
+ /* Array of EPs. */
+ struct pcd_ep ep0;
+ /* Array of IN EPs. */
+ struct pcd_ep in_ep[MAX_EPS_CHANNELS - 1];
+ /* Array of OUT EPs. */
+ struct pcd_ep out_ep[MAX_EPS_CHANNELS - 1];
+ spinlock_t lock;
+ /*
+ * Timer for SRP. If it expires before SRP is successful clear the
+ * SRP.
+ */
+ struct timer_list srp_timer;
+
+ /*
+ * Tasklet to defer starting of TEST mode transmissions until Status
+ * Phase has been completed.
+ */
+ struct tasklet_struct test_mode_tasklet;
+
+ /* Tasklet to delay starting of xfer in DMA mode */
+ struct tasklet_struct *start_xfer_tasklet;
+
+ /* The test mode to enter when the tasklet is executed. */
+ unsigned test_mode;
+};
+
+#define PRT_CONN_STS_CHNG(x) (x >> 31) & 0x1
+/*
+ * This structure holds the state of the HCD, including the non-periodic and
+ * periodic schedules.
+ */
+struct dwc_hcd {
+ spinlock_t lock;
+
+ /* DWC OTG Core Interface Layer */
+ struct core_if *core_if;
+
+ /*
+ * Inactive items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are not
+ * currently assigned to a host channel.
+ */
+ struct list_head non_periodic_sched_inactive;
+
+ /*
+ * Deferred items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are not
+ * currently assigned to a host channel.
+ * When we get an NAK, the QH goes here.
+ */
+ struct list_head non_periodic_sched_deferred;
+
+ /*
+ * Active items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are
+ * currently assigned to a host channel.
+ */
+ struct list_head non_periodic_sched_active;
+
+ /*
+ * Pointer to the next Queue Head to process in the active
+ * non-periodic schedule.
+ */
+ struct list_head *non_periodic_qh_ptr;
+
+ /*
+ * Inactive items in the periodic schedule. This is a list of QHs for
+ * periodic transfers that are _not_ scheduled for the next frame.
+ * Each QH in the list has an interval counter that determines when it
+ * needs to be scheduled for execution. This scheduling mechanism
+ * allows only a simple calculation for periodic bandwidth used (i.e.
+ * must assume that all periodic transfers may need to execute in the
+ * same frame). However, it greatly simplifies scheduling and should
+ * be sufficient for the vast majority of OTG hosts, which need to
+ * connect to a small number of peripherals at one time.
+ *
+ * Items move from this list to periodic_sched_ready when the QH
+ * interval counter is 0 at SOF.
+ */
+ struct list_head periodic_sched_inactive;
+
+ /*
+ * List of periodic QHs that are ready for execution in the next
+ * frame, but have not yet been assigned to host channels.
+ *
+ * Items move from this list to periodic_sched_assigned as host
+ * channels become available during the current frame.
+ */
+ struct list_head periodic_sched_ready;
+
+ /*
+ * List of periodic QHs to be executed in the next frame that are
+ * assigned to host channels.
+ *
+ * Items move from this list to periodic_sched_queued as the
+ * transactions for the QH are queued to the DWC_otg controller.
+ */
+ struct list_head periodic_sched_assigned;
+
+ /*
+ * List of periodic QHs that have been queued for execution.
+ *
+ * Items move from this list to either periodic_sched_inactive or
+ * periodic_sched_ready when the channel associated with the transfer
+ * is released. If the interval for the QH is 1, the item moves to
+ * periodic_sched_ready because it must be rescheduled for the next
+ * frame. Otherwise, the item moves to periodic_sched_inactive.
+ */
+ struct list_head periodic_sched_queued;
+
+ /*
+ * Total bandwidth claimed so far for periodic transfers. This value
+ * is in microseconds per (micro)frame. The assumption is that all
+ * periodic transfers may occur in the same (micro)frame.
+ */
+ u16 periodic_usecs;
+
+ /*
+ * Total bandwidth claimed so far for all periodic transfers
+ * in a frame.
+ * This will include a mixture of HS and FS transfers.
+ * Units are microseconds per (micro)frame.
+ * We have a budget per frame and have to schedule
+ * transactions accordingly.
+ * Watch out for the fact that things are actually scheduled for the
+ * "next frame".
+ */
+ u16 frame_usecs[8];
+
+ /*
+ * Frame number read from the core at SOF. The value ranges from 0 to
+ * DWC_HFNUM_MAX_FRNUM.
+ */
+ u16 frame_number;
+
+ /*
+ * Free host channels in the controller. This is a list of
+ * struct dwc_hc items.
+ */
+ struct list_head free_hc_list;
+
+ /*
+ * Number of available host channels.
+ */
+ u32 available_host_channels;
+
+ /*
+ * Array of pointers to the host channel descriptors. Allows accessing
+ * a host channel descriptor given the host channel number. This is
+ * useful in interrupt handlers.
+ */
+ struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS];
+
+ /*
+ * Buffer to use for any data received during the status phase of a
+ * control transfer. Normally no data is transferred during the status
+ * phase. This buffer is used as a bit bucket.
+ */
+ u8 *status_buf;
+
+ /*
+ * DMA address for status_buf.
+ */
+ dma_addr_t status_buf_dma;
+#define DWC_OTG_HCD_STATUS_BUF_SIZE 64
+
+ /*
+ * Structure to allow starting the HCD in a non-interrupt context
+ * during an OTG role change.
+ */
+ struct work_struct start_work;
+ struct usb_hcd *_p;
+
+ /*
+ * Connection timer. An OTG host must display a message if the device
+ * does not connect. Started when the VBus power is turned on via
+ * sysfs attribute "buspower".
+ */
+ struct timer_list conn_timer;
+
+ /* workqueue for port wakeup */
+ struct work_struct usb_port_reset;
+
+ /* Addition HCD interrupt */
+ int cp_irq; /* charge pump interrupt */
+ int cp_irq_installed;
+};
+
+/*
+ * Reasons for halting a host channel.
+ */
+enum dwc_halt_status {
+ DWC_OTG_HC_XFER_NO_HALT_STATUS,
+ DWC_OTG_HC_XFER_COMPLETE,
+ DWC_OTG_HC_XFER_URB_COMPLETE,
+ DWC_OTG_HC_XFER_ACK,
+ DWC_OTG_HC_XFER_NAK,
+ DWC_OTG_HC_XFER_NYET,
+ DWC_OTG_HC_XFER_STALL,
+ DWC_OTG_HC_XFER_XACT_ERR,
+ DWC_OTG_HC_XFER_FRAME_OVERRUN,
+ DWC_OTG_HC_XFER_BABBLE_ERR,
+ DWC_OTG_HC_XFER_DATA_TOGGLE_ERR,
+ DWC_OTG_HC_XFER_AHB_ERR,
+ DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
+ DWC_OTG_HC_XFER_URB_DEQUEUE
+};
+
+/*
+ * Host channel descriptor. This structure represents the state of a single
+ * host channel when acting in host mode. It contains the data items needed to
+ * transfer packets to an endpoint via a host channel.
+ */
+struct dwc_hc {
+ /* Host channel number used for register address lookup */
+ u8 hc_num;
+
+ /* Device to access */
+ unsigned dev_addr:7;
+
+ /* EP to access */
+ unsigned ep_num:4;
+
+ /* EP direction. 0: OUT, 1: IN */
+ unsigned ep_is_in:1;
+
+ /*
+ * EP speed.
+ * One of the following values:
+ * - DWC_OTG_EP_SPEED_LOW
+ * - DWC_OTG_EP_SPEED_FULL
+ * - DWC_OTG_EP_SPEED_HIGH
+ */
+ unsigned speed:2;
+#define DWC_OTG_EP_SPEED_LOW 0
+#define DWC_OTG_EP_SPEED_FULL 1
+#define DWC_OTG_EP_SPEED_HIGH 2
+
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - DWC_OTG_EP_TYPE_CONTROL: 0
+ * - DWC_OTG_EP_TYPE_ISOC: 1
+ * - DWC_OTG_EP_TYPE_BULK: 2
+ * - DWC_OTG_EP_TYPE_INTR: 3
+ */
+ unsigned ep_type:2;
+
+ /* Max packet size in bytes */
+ unsigned max_packet:11;
+
+ /*
+ * PID for initial transaction.
+ * 0: DATA0,
+ * 1: DATA2,
+ * 2: DATA1,
+ * 3: MDATA (non-Control EP),
+ * SETUP (Control EP)
+ */
+ unsigned data_pid_start:2;
+#define DWC_OTG_HC_PID_DATA0 0
+#define DWC_OTG_HC_PID_DATA2 1
+#define DWC_OTG_HC_PID_DATA1 2
+#define DWC_OTG_HC_PID_MDATA 3
+#define DWC_OTG_HC_PID_SETUP 3
+
+ /* Number of periodic transactions per (micro)frame */
+ unsigned multi_count:2;
+
+ /* Pointer to the current transfer buffer position. */
+ u8 *xfer_buff;
+ /* Total number of bytes to transfer. */
+ u32 xfer_len;
+ /* Number of bytes transferred so far. */
+ u32 xfer_count;
+ /* Packet count at start of transfer. */
+ u16 start_pkt_count;
+
+ /*
+ * Flag to indicate whether the transfer has been started. Set to 1 if
+ * it has been started, 0 otherwise.
+ */
+ u8 xfer_started;
+
+ /*
+ * Set to 1 to indicate that a PING request should be issued on this
+ * channel. If 0, process normally.
+ */
+ u8 do_ping;
+
+ /*
+ * Set to 1 to indicate that the error count for this transaction is
+ * non-zero. Set to 0 if the error count is 0.
+ */
+ u8 error_state;
+
+ /*
+ * Set to 1 to indicate that this channel should be halted the next
+ * time a request is queued for the channel. This is necessary in
+ * slave mode if no request queue space is available when an attempt
+ * is made to halt the channel.
+ */
+ u8 halt_on_queue;
+
+ /*
+ * Set to 1 if the host channel has been halted, but the core is not
+ * finished flushing queued requests. Otherwise 0.
+ */
+ u8 halt_pending;
+
+ /* Reason for halting the host channel. */
+ enum dwc_halt_status halt_status;
+
+ /* Split settings for the host channel */
+ u8 do_split; /* Enable split for the channel */
+ u8 complete_split; /* Enable complete split */
+ u8 hub_addr; /* Address of high speed hub */
+ u8 port_addr; /* Port of the low/full speed device */
+
+ /*
+ * Split transaction position. One of the following values:
+ * - DWC_HCSPLIT_XACTPOS_MID
+ * - DWC_HCSPLIT_XACTPOS_BEGIN
+ * - DWC_HCSPLIT_XACTPOS_END
+ * - DWC_HCSPLIT_XACTPOS_ALL */
+ u8 xact_pos;
+
+ /* Set when the host channel does a short read. */
+ u8 short_read;
+
+ /*
+ * Number of requests issued for this channel since it was assigned to
+ * the current transfer (not counting PINGs).
+ */
+ u8 requests;
+
+ /* Queue Head for the transfer being processed by this channel. */
+ struct dwc_qh *qh;
+
+ /* Entry in list of host channels. */
+ struct list_head hc_list_entry;
+};
+
+/*
+ * The following parameters may be specified when starting the module. These
+ * parameters define how the DWC_otg controller should be configured. Parameter
+ * values are passed to the CIL initialization function dwc_otg_cil_init.
+ */
+struct core_params {
+ /*
+ * Specifies the OTG capabilities. The driver will automatically
+ * detect the value for this parameter if none is specified.
+ * 0 - HNP and SRP capable (default)
+ * 1 - SRP Only capable
+ * 2 - No HNP/SRP capable
+ */
+ int otg_cap;
+#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0
+#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1
+#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2
+
+#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE
+
+ /*
+ * Specifies whether to use slave or DMA mode for accessing the data
+ * FIFOs. The driver will automatically detect the value for this
+ * parameter if none is specified.
+ * 0 - Slave
+ * 1 - DMA (default, if available)
+ */
+ int dma_enable;
+#ifdef CONFIG_DWC_SLAVE
+#define dwc_param_dma_enable_default 0
+#else
+#define dwc_param_dma_enable_default 1
+#endif
+
+ /*
+ * The DMA Burst size (applicable only for External DMA Mode).
+ * 1, 4, 8 16, 32, 64, 128, 256 (default 32)
+ */
+ int dma_burst_size; /* Translate this to GAHBCFG values */
+#define dwc_param_dma_burst_size_default 32
+
+ /*
+ * Specifies the maximum speed of operation in host and device mode.
+ * The actual speed depends on the speed of the attached device and
+ * the value of phy_type. The actual speed depends on the speed of the
+ * attached device.
+ * 0 - High Speed (default)
+ * 1 - Full Speed
+ */
+ int speed;
+#define dwc_param_speed_default 0
+#define DWC_SPEED_PARAM_HIGH 0
+#define DWC_SPEED_PARAM_FULL 1
+
+ /*
+ * Specifies whether low power mode is supported when attached to a Full
+ * Speed or Low Speed device in host mode.
+ * 0 - Don't support low power mode (default)
+ * 1 - Support low power mode
+ */
+ int host_support_fs_ls_low_power;
+#define dwc_param_host_support_fs_ls_low_power_default 0
+
+ /*
+ * Specifies the PHY clock rate in low power mode when connected to a
+ * Low Speed device in host mode. This parameter is applicable only if
+ * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS
+ * then defaults to 6 MHZ otherwise 48 MHZ.
+ *
+ * 0 - 48 MHz
+ * 1 - 6 MHz
+ */
+ int host_ls_low_power_phy_clk;
+#define dwc_param_host_ls_low_power_phy_clk_default 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1
+
+ /*
+ * 0 - Use cC FIFO size parameters
+ * 1 - Allow dynamic FIFO sizing (default)
+ */
+ int enable_dynamic_fifo;
+#define dwc_param_enable_dynamic_fifo_default 1
+
+ /*
+ * Number of 4-byte words in the Rx FIFO in device mode when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1064)
+ */
+ int dev_rx_fifo_size;
+#define dwc_param_dev_rx_fifo_size_default 1064
+
+ /*
+ * Number of 4-byte words in the non-periodic Tx FIFO in device mode
+ * when dynamic FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int dev_nperio_tx_fifo_size;
+#define dwc_param_dev_nperio_tx_fifo_size_default 1024
+
+ /*
+ * Number of 4-byte words in each of the periodic Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256)
+ */
+ u32 dev_perio_tx_fifo_size[MAX_PERIO_FIFOS];
+#define dwc_param_dev_perio_tx_fifo_size_default 256
+
+ /*
+ * Number of 4-byte words in the Rx FIFO in host mode when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int host_rx_fifo_size;
+#define dwc_param_host_rx_fifo_size_default 1024
+
+ /*
+ * Number of 4-byte words in the non-periodic Tx FIFO in host mode
+ * when Dynamic FIFO sizing is enabled in the core. 16 to 32768
+ * (default 1024)
+ */
+ int host_nperio_tx_fifo_size;
+#define dwc_param_host_nperio_tx_fifo_size_default 1024
+
+ /*
+ Number of 4-byte words in the host periodic Tx FIFO when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int host_perio_tx_fifo_size;
+#define dwc_param_host_perio_tx_fifo_size_default 1024
+
+ /*
+ * The maximum transfer size supported in bytes. 2047 to 65,535
+ * (default 65,535)
+ */
+ int max_transfer_size;
+#define dwc_param_max_transfer_size_default 65535
+
+ /*
+ * The maximum number of packets in a transfer. 15 to 511 (default 511)
+ */
+ int max_packet_count;
+#define dwc_param_max_packet_count_default 511
+
+ /*
+ * The number of host channel registers to use.
+ * 1 to 16 (default 12)
+ * Note: The FPGA configuration supports a maximum of 12 host channels.
+ */
+ int host_channels;
+#define dwc_param_host_channels_default 12
+
+ /*
+ * The number of endpoints in addition to EP0 available for device
+ * mode operations.
+ * 1 to 15 (default 6 IN and OUT)
+ * Note: The FPGA configuration supports a maximum of 6 IN and OUT
+ * endpoints in addition to EP0.
+ */
+ int dev_endpoints;
+#define dwc_param_dev_endpoints_default 6
+
+ /*
+ * Specifies the type of PHY interface to use. By default, the driver
+ * will automatically detect the phy_type.
+ *
+ * 0 - Full Speed PHY
+ * 1 - UTMI+ (default)
+ * 2 - ULPI
+ */
+ int phy_type;
+#define DWC_PHY_TYPE_PARAM_FS 0
+#define DWC_PHY_TYPE_PARAM_UTMI 1
+#define DWC_PHY_TYPE_PARAM_ULPI 2
+#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI
+
+ /*
+ * Specifies the UTMI+ Data Width. This parameter is applicable for a
+ * PHY_TYPE of UTMI+ or ULPI. (For a ULPI PHY_TYPE, this parameter
+ * indicates the data width between the MAC and the ULPI Wrapper.) Also,
+ * this parameter is applicable only if the OTG_HSPHY_WIDTH cC parameter
+ * was set to "8 and 16 bits", meaning that the core has been configured
+ * to work at either data path width.
+ *
+ * 8 or 16 bits (default 16)
+ */
+ int phy_utmi_width;
+#define dwc_param_phy_utmi_width_default 16
+
+ /*
+ * Specifies whether the ULPI operates at double or single
+ * data rate. This parameter is only applicable if PHY_TYPE is
+ * ULPI.
+ *
+ * 0 - single data rate ULPI interface with 8 bit wide data
+ * bus (default)
+ * 1 - double data rate ULPI interface with 4 bit wide data
+ * bus
+ */
+ int phy_ulpi_ddr;
+#define dwc_param_phy_ulpi_ddr_default 0
+
+ /*
+ * Specifies whether to use the internal or external supply to
+ * drive the vbus with a ULPI phy.
+ */
+ int phy_ulpi_ext_vbus;
+#define DWC_PHY_ULPI_INTERNAL_VBUS 0
+#define DWC_PHY_ULPI_EXTERNAL_VBUS 1
+#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS
+
+ /*
+ * Specifies whether to use the I2Cinterface for full speed PHY. This
+ * parameter is only applicable if PHY_TYPE is FS.
+ * 0 - No (default)
+ * 1 - Yes
+ */
+ int i2c_enable;
+#define dwc_param_i2c_enable_default 0
+
+ int ulpi_fs_ls;
+#define dwc_param_ulpi_fs_ls_default 0
+
+ int ts_dline;
+#define dwc_param_ts_dline_default 0
+
+ /*
+ * Specifies whether dedicated transmit FIFOs are enabled for non
+ * periodic IN endpoints in device mode
+ * 0 - No
+ * 1 - Yes
+ */
+ int en_multiple_tx_fifo;
+#define dwc_param_en_multiple_tx_fifo_default 1
+
+ /*
+ * Number of 4-byte words in each of the Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256)
+ */
+ u32 dev_tx_fifo_size[MAX_TX_FIFOS];
+#define dwc_param_dev_tx_fifo_size_default 256
+
+ /*
+ * Thresholding enable flag
+ * bit 0 - enable non-ISO Tx thresholding
+ * bit 1 - enable ISO Tx thresholding
+ * bit 2 - enable Rx thresholding
+ */
+ u32 thr_ctl;
+#define dwc_param_thr_ctl_default 0
+
+ /* Thresholding length for Tx FIFOs in 32 bit DWORDs */
+ u32 tx_thr_length;
+#define dwc_param_tx_thr_length_default 64
+
+ /* Thresholding length for Rx FIFOs in 32 bit DWORDs */
+ u32 rx_thr_length;
+#define dwc_param_rx_thr_length_default 64
+
+};
+
+/*
+ * The core_if structure contains information needed to manage the
+ * DWC_otg controller acting in either host or device mode. It represents the
+ * programming view of the controller as a whole.
+ */
+struct core_if {
+ /* Parameters that define how the core should be configured. */
+ struct core_params *core_params;
+
+ /* Core Global registers starting at offset 000h. */
+ ulong core_global_regs;
+
+ /* Device-specific information */
+ struct device_if *dev_if;
+ /* Host-specific information */
+ struct dwc_host_if *host_if;
+
+ /*
+ * Set to 1 if the core PHY interface bits in USBCFG have been
+ * initialized.
+ */
+ u8 phy_init_done;
+
+ /*
+ * SRP Success flag, set by srp success interrupt in FS I2C mode
+ */
+ u8 srp_success;
+ u8 srp_timer_started;
+
+ /* Common configuration information */
+ /* Power and Clock Gating Control Register */
+ ulong pcgcctl;
+#define DWC_OTG_PCGCCTL_OFFSET 0xE00
+
+ /* Push/pop addresses for endpoints or host channels. */
+ ulong data_fifo[MAX_EPS_CHANNELS];
+#define DWC_OTG_DATA_FIFO_OFFSET 0x1000
+#define DWC_OTG_DATA_FIFO_SIZE 0x1000
+
+ /* Total RAM for FIFOs (Bytes) */
+ u16 total_fifo_size;
+ /* Size of Rx FIFO (Bytes) */
+ u16 rx_fifo_size;
+ /* Size of Non-periodic Tx FIFO (Bytes) */
+ u16 nperio_tx_fifo_size;
+
+ /* 1 if DMA is enabled, 0 otherwise. */
+ u8 dma_enable;
+
+ /* 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */
+ u8 en_multiple_tx_fifo;
+
+ /*
+ * Set to 1 if multiple packets of a high-bandwidth transfer is in
+ * process of being queued
+ */
+ u8 queuing_high_bandwidth;
+
+ /* Hardware Configuration -- stored here for convenience. */
+ ulong hwcfg1;
+ ulong hwcfg2;
+ ulong hwcfg3;
+ ulong hwcfg4;
+
+ /* HCD callbacks */
+ /* include/linux/usb/otg.h */
+
+ /* HCD callbacks */
+ struct cil_callbacks *hcd_cb;
+ /* PCD callbacks */
+ struct cil_callbacks *pcd_cb;
+
+ /* Device mode Periodic Tx FIFO Mask */
+ u32 p_tx_msk;
+ /* Device mode Periodic Tx FIFO Mask */
+ u32 tx_msk;
+
+ /* Features of various DWC implementation */
+ u32 features;
+
+ /* Added to support PLB DMA : phys-virt mapping */
+ resource_size_t phys_addr;
+
+ struct delayed_work usb_port_wakeup;
+ struct work_struct usb_port_otg;
+ struct otg_transceiver *xceiv;
+};
+
+/*
+ * The following functions support initialization of the CIL driver component
+ * and the DWC_otg controller.
+ */
+extern void dwc_otg_core_init(struct core_if *core_if);
+extern void init_fslspclksel(struct core_if *core_if);
+extern void dwc_otg_core_dev_init(struct core_if *core_if);
+extern void dwc_otg_enable_global_interrupts(struct core_if *core_if);
+extern void dwc_otg_disable_global_interrupts(struct core_if *core_if);
+extern void dwc_otg_enable_common_interrupts(struct core_if *core_if);
+
+/**
+ * This function Reads HPRT0 in preparation to modify. It keeps the WC bits 0
+ * so that if they are read as 1, they won't clear when you write it back
+ */
+static inline u32 dwc_otg_read_hprt0(struct core_if *core_if)
+{
+ u32 hprt0 = 0;
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_CONN_DET_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_OVRCURR_ACT_RW(hprt0, 0);
+ return hprt0;
+}
+
+/*
+ * The following functions support managing the DWC_otg controller in either
+ * device or host mode.
+ */
+extern void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 bytes);
+extern void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int _num);
+extern void dwc_otg_flush_rx_fifo(struct core_if *core_if);
+
+#define NP_TXFIFO_EMPTY -1
+#define MAX_NP_TXREQUEST_Q_SLOTS 8
+
+/**
+ * This function returns the Core Interrupt register.
+ */
+static inline u32 dwc_otg_read_core_intr(struct core_if *core_if)
+{
+ u32 global_regs = (u32) core_if->core_global_regs;
+ return dwc_reg_read(global_regs, DWC_GINTSTS) &
+ dwc_reg_read(global_regs, DWC_GINTMSK);
+}
+
+/**
+ * This function returns the mode of the operation, host or device.
+ */
+static inline u32 dwc_otg_mode(struct core_if *core_if)
+{
+ u32 global_regs = (u32) core_if->core_global_regs;
+ return dwc_reg_read(global_regs, DWC_GINTSTS) & 0x1;
+}
+
+static inline u8 dwc_otg_is_device_mode(struct core_if *core_if)
+{
+ return dwc_otg_mode(core_if) != DWC_HOST_MODE;
+}
+static inline u8 dwc_otg_is_host_mode(struct core_if *core_if)
+{
+ return dwc_otg_mode(core_if) == DWC_HOST_MODE;
+}
+
+extern int dwc_otg_handle_common_intr(struct core_if *core_if);
+
+/*
+ * DWC_otg CIL callback structure. This structure allows the HCD and PCD to
+ * register functions used for starting and stopping the PCD and HCD for role
+ * change on for a DRD.
+ */
+struct cil_callbacks {
+ /* Start function for role change */
+ int (*start) (void *_p);
+ /* Stop Function for role change */
+ int (*stop) (void *_p);
+ /* Disconnect Function for role change */
+ int (*disconnect) (void *_p);
+ /* Resume/Remote wakeup Function */
+ int (*resume_wakeup) (void *_p);
+ /* Suspend function */
+ int (*suspend) (void *_p);
+ /* Session Start (SRP) */
+ int (*session_start) (void *_p);
+ /* Pointer passed to start() and stop() */
+ void *p;
+};
+
+extern void dwc_otg_cil_register_pcd_callbacks(struct core_if *core_if,
+ struct cil_callbacks *cb,
+ void *p);
+extern void dwc_otg_cil_register_hcd_callbacks(struct core_if *core_if,
+ struct cil_callbacks *cb,
+ void *p);
+
+#define DWC_LIMITED_XFER 0x00000000
+#define DWC_DEVICE_ONLY 0x00000000
+#define DWC_HOST_ONLY 0x00000000
+
+#ifdef DWC_LIMITED_XFER_SIZE
+#undef DWC_LIMITED_XFER
+#define DWC_LIMITED_XFER 0x00000001
+#endif
+
+#ifdef CONFIG_DWC_DEVICE_ONLY
+#undef DWC_DEVICE_ONLY
+#define DWC_DEVICE_ONLY 0x00000002
+static inline void dwc_otg_hcd_remove(struct device *dev)
+{
+}
+static inline int dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev)
+{
+ return 0;
+}
+#else
+extern int __init dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+#endif
+
+#ifdef CONFIG_DWC_HOST_ONLY
+#undef DWC_HOST_ONLY
+#define DWC_HOST_ONLY 0x00000004
+static inline void dwc_otg_pcd_remove(struct device *dev)
+{
+}
+static inline int dwc_otg_pcd_init(struct device *dev)
+{
+ return 0;
+}
+#else
+extern void dwc_otg_pcd_remove(struct device *dev);
+extern int __init dwc_otg_pcd_init(struct device *dev);
+#endif
+
+extern void dwc_otg_cil_remove(struct core_if *core_if);
+extern struct core_if __devinit *dwc_otg_cil_init(const __iomem u32 * base,
+ struct core_params *params);
+
+static inline void dwc_set_feature(struct core_if *core_if)
+{
+ core_if->features = DWC_LIMITED_XFER | DWC_DEVICE_ONLY | DWC_HOST_ONLY;
+}
+
+static inline int dwc_has_feature(struct core_if *core_if,
+ unsigned long feature)
+{
+ return core_if->features & feature;
+}
+extern struct core_params dwc_otg_module_params;
+extern int __devinit check_parameters(struct core_if *core_if);
+#endif
diff --git a/drivers/usb/dwc/cil_intr.c b/drivers/usb/dwc/cil_intr.c
new file mode 100644
index 0000000..32f7dc4
--- /dev/null
+++ b/drivers/usb/dwc/cil_intr.c
@@ -0,0 +1,584 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * The Core Interface Layer provides basic services for accessing and
+ * managing the DWC_otg hardware. These services are used by both the
+ * Host Controller Driver and the Peripheral Controller Driver.
+ *
+ * This file contains the Common Interrupt handlers.
+ */
+#include <linux/delay.h>
+
+#include "cil.h"
+
+/**
+ * This function will log a debug message
+ */
+static int dwc_otg_handle_mode_mismatch_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+
+ pr_warning("Mode Mismatch Interrupt: currently in %s mode\n",
+ dwc_otg_mode(core_if) ? "Host" : "Device");
+
+ /* Clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_MODE_MISMTC);
+ return 1;
+}
+
+/**
+ * Start the HCD. Helper function for using the HCD callbacks.
+ */
+static inline void hcd_start(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->start)
+ core_if->hcd_cb->start(core_if->hcd_cb->p);
+}
+
+/**
+ * Disconnect the HCD. Helper function for using the HCD callbacks.
+ */
+static inline void hcd_disconnect(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->disconnect)
+ core_if->hcd_cb->disconnect(core_if->hcd_cb->p);
+}
+
+/**
+ * Inform the HCD the a New Session has begun. Helper function for using the
+ * HCD callbacks.
+ */
+static inline void hcd_session_start(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->session_start)
+ core_if->hcd_cb->session_start(core_if->hcd_cb->p);
+}
+
+/**
+ * Start the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_start(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->start) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->start(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Stop the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_stop(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->stop) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->stop(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Suspend the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_suspend(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->suspend) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->suspend(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Resume the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_resume(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * This function handles the OTG Interrupts. It reads the OTG
+ * Interrupt Register (GOTGINT) to determine what interrupt has
+ * occurred.
+ */
+static int dwc_otg_handle_otg_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 gotgint;
+ u32 gotgctl;
+
+ gotgint = dwc_reg_read(global_regs, DWC_GOTGINT);
+ if (gotgint & DWC_GINT_SES_ENDDET) {
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ } else {
+ /*
+ * If not B_HOST and Device HNP still set. HNP did not
+ * succeed
+ */
+ if (gotgctl & DWC_GCTL_DEV_HNP_ENA)
+ pr_err("Device Not Connected / "
+ "Responding\n");
+ /*
+ * If Session End Detected the B-Cable has been
+ * disconnected. Reset PCD and Gadget driver to a
+ * clean state.
+ */
+ pcd_stop(core_if);
+ }
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, DWC_GCTL_DEV_HNP_ENA,\
+ 0);
+ }
+ if (gotgint & DWC_GINT_SES_REQSUC) {
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_SES_REQ_SUCCESS) {
+ if (core_if->core_params->phy_type ==
+ DWC_PHY_TYPE_PARAM_FS &&
+ core_if->core_params->i2c_enable) {
+ core_if->srp_success = 1;
+ } else {
+ pcd_resume(core_if);
+
+ /* Clear Session Request */
+ dwc_reg_modify(global_regs, DWC_GOTGCTL,
+ DWC_GCTL_SES_REQ, 0);
+ }
+ }
+ }
+ if (gotgint & DWC_GINT_HST_NEGSUC) {
+ /*
+ * Print statements during the HNP interrupt handling can cause
+ * it to fail.
+ */
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_HOST_NEG_SUCCES) {
+ if (dwc_otg_is_host_mode(core_if)) {
+ core_if->xceiv->state = OTG_STATE_B_HOST;
+ /*
+ * Need to disable SOF interrupt immediately.
+ * When switching from device to host, the PCD
+ * interrupt handler won't handle the
+ * interrupt if host mode is already set. The
+ * HCD interrupt handler won't get called if
+ * the HCD state is HALT. This means that the
+ * interrupt does not get handled and Linux
+ * complains loudly.
+ */
+ dwc_reg_modify(global_regs, DWC_GINTMSK,
+ DWC_INTMSK_STRT_OF_FRM, 0);
+ pcd_stop(core_if);
+ /* Initialize the Core for Host mode. */
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_HOST;
+ }
+ } else {
+ gotgctl = 0;
+ gotgctl = DWC_GCTL_HNP_REQ |\
+ DWC_GCTL_DEV_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ pr_err("Device Not Connected / Responding\n");
+ }
+ }
+ if (gotgint & DWC_GINT_HST_NEGDET) {
+ /*
+ * The disconnect interrupt is set at the same time as
+ * Host Negotiation Detected. During the mode
+ * switch all interrupts are cleared so the disconnect
+ * interrupt handler will not get executed.
+ */
+ if (dwc_otg_is_device_mode(core_if)) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_PERIPHERAL;
+ } else {
+ /*
+ * Need to disable SOF interrupt immediately. When
+ * switching from device to host, the PCD interrupt
+ * handler won't handle the interrupt if host mode is
+ * already set. The HCD interrupt handler won't get
+ * called if the HCD state is HALT. This means that
+ * the interrupt does not get handled and Linux
+ * complains loudly.
+ */
+ dwc_reg_modify(global_regs, DWC_GINTMSK,\
+ DWC_INTMSK_STRT_OF_FRM, 0);
+ pcd_stop(core_if);
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ }
+ }
+ if (gotgint & DWC_GINT_DEVTOUT)
+ pr_info(" ++OTG Interrupt: A-Device Timeout " "Change++\n");
+ if (gotgint & DWC_GINT_DEBDONE)
+ pr_info(" ++OTG Interrupt: Debounce Done++\n");
+
+ /* Clear GOTGINT */
+ dwc_reg_write(global_regs, DWC_GOTGINT, gotgint);
+ return 1;
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_otg_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(work, struct core_if,
+ usb_port_otg);
+ ulong global_regs = core_if->core_global_regs;
+ u32 count = 0;
+ u32 gotgctl;
+
+ pr_info("%s\n", __func__);
+
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_CONN_ID_STATUS) {
+ /*
+ * B-Device connector (device mode) wait for switch to device
+ * mode.
+ */
+ while (!dwc_otg_is_device_mode(core_if) && ++count <= 10000) {
+ pr_info("Waiting for Peripheral Mode, "
+ "Mode=%s\n", dwc_otg_is_host_mode(core_if) ?
+ "Host" : "Peripheral");
+ msleep(100);
+ }
+ BUG_ON(count > 10000);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ dwc_otg_core_init(core_if);
+ dwc_otg_enable_global_interrupts(core_if);
+ pcd_start(core_if);
+ } else {
+ /*
+ * A-Device connector (host mode) wait for switch to host
+ * mode.
+ */
+ while (!dwc_otg_is_host_mode(core_if) && ++count <= 10000) {
+ pr_info("Waiting for Host Mode, Mode=%s\n",
+ dwc_otg_is_host_mode(core_if) ?
+ "Host" : "Peripheral");
+ msleep(100);
+ }
+ BUG_ON(count > 10000);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ dwc_otg_core_init(core_if);
+ dwc_otg_enable_global_interrupts(core_if);
+ hcd_start(core_if);
+ }
+}
+
+/**
+ * This function handles the Connector ID Status Change Interrupt. It
+ * reads the OTG Interrupt Register (GOTCTL) to determine whether this
+ * is a Device to Host Mode transition or a Host Mode to Device
+ * Transition.
+ *
+ * This only occurs when the cable is connected/removed from the PHY
+ * connector.
+ */
+static int dwc_otg_handle_conn_id_status_change_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ /*
+ * Need to disable SOF interrupt immediately. If switching from device
+ * to host, the PCD interrupt handler won't handle the interrupt if
+ * host mode is already set. The HCD interrupt handler won't get
+ * called if the HCD state is HALT. This means that the interrupt does
+ * not get handled and Linux complains loudly.
+ */
+ dwc_reg_modify(global_regs, DWC_GINTMSK, DWC_INTSTS_STRT_OF_FRM, 0);
+
+ INIT_WORK(&core_if->usb_port_otg, port_otg_wqfunc);
+ schedule_work(&core_if->usb_port_otg);
+
+ /* Set flag and clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_CON_ID_STS_CHG);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that a device is initiating the Session
+ * Request Protocol to request the host to turn on bus power so a new
+ * session can begin. The handler responds by turning on bus power. If
+ * the DWC_otg controller is in low power mode, the handler brings the
+ * controller out of low power mode before turning on bus power.
+ */
+static int dwc_otg_handle_session_req_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+
+ if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) {
+ u32 hprt0;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ pr_info("SRP: Device mode\n");
+ } else {
+ pr_info("SRP: Host mode\n");
+
+ /* Turn on the port power bit. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /*
+ * Start the Connection timer.
+ * A message can be displayed,
+ * if connect does not occur within 10 seconds.
+ */
+ hcd_session_start(core_if);
+ }
+ }
+ /* Clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_NEW_SES_DET);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that the DWC_otg controller has detected a
+ * resume or remote wakeup sequence. If the DWC_otg controller is in
+ * low power mode, the handler must brings the controller out of low
+ * power mode. The controller automatically begins resume
+ * signaling. The handler schedules a time to stop resume signaling.
+ */
+static int dwc_otg_handle_wakeup_detected_intr(struct core_if *core_if)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ ulong global_regs = core_if->core_global_regs;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ u32 dctl = 0;
+
+ /* Clear the Remote Wakeup Signalling */
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, 0);
+
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup)
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+ } else {
+ u32 pcgcctl = 0;
+
+ /* Restart the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_modify(core_if->pcgcctl, 0, pcgcctl, 0);
+ schedule_delayed_work(&core_if->usb_port_wakeup, 10);
+ }
+
+ /* Clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_WKP);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that a device has been disconnected from
+ * the root port.
+ */
+static int dwc_otg_handle_disconnect_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+
+ if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) {
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ } else if (dwc_otg_is_device_mode(core_if)) {
+ u32 gotgctl;
+
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+
+ /*
+ * If HNP is in process, do nothing.
+ * The OTG "Host Negotiation Detected"
+ * interrupt will do the mode switch.
+ * Otherwise, since we are in device mode,
+ * disconnect and stop the HCD,
+ * then start the PCD.
+ */
+ if ((gotgctl) & DWC_GCTL_DEV_HNP_ENA) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ }
+ } else if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ /* A-Cable still connected but device disconnected. */
+ hcd_disconnect(core_if);
+ }
+ }
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_SES_DISCON_DET);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that SUSPEND state has been detected on
+ * the USB.
+ *
+ * For HNP the USB Suspend interrupt signals the change from
+ * "a_peripheral" to "a_host".
+ *
+ * When power management is enabled the core will be put in low power
+ * mode.
+ */
+static int dwc_otg_handle_usb_suspend_intr(struct core_if *core_if)
+{
+ u32 dsts = 0;
+ ulong global_regs = core_if->core_global_regs;
+ struct device_if *dev_if = core_if->dev_if;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ /*
+ * Check the Device status register to determine if the Suspend
+ * state is active.
+ */
+ dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS);
+ /* PCD callback for suspend. */
+ pcd_suspend(core_if);
+ } else {
+ if (core_if->xceiv->state == OTG_STATE_A_PERIPHERAL) {
+ /* Clear the a_peripheral flag, back to a_host. */
+ pcd_stop(core_if);
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ }
+ }
+
+ /* Clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTMSK_USB_SUSP);
+ return 1;
+}
+
+/**
+ * This function returns the Core Interrupt register.
+ *
+ * Although the Host Port interrupt (portintr) is documented as host mode
+ * only, it appears to occur in device mode when Port Enable / Disable Changed
+ * bit in HPRT0 is set. The code in dwc_otg_handle_common_intr checks if in
+ * device mode and just clears the interrupt.
+ */
+static inline u32 dwc_otg_read_common_intr(struct core_if *core_if)
+{
+ u32 gintsts;
+ u32 gintmsk;
+ u32 gintmsk_common = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ gintmsk_common = DWC_INTMSK_WKP |\
+ DWC_INTMSK_NEW_SES_DET |\
+ DWC_INTMSK_CON_ID_STS_CHG |\
+ DWC_INTMSK_OTG |\
+ DWC_INTMSK_MODE_MISMTC |\
+ DWC_INTMSK_SES_DISCON_DET |\
+ DWC_INTMSK_USB_SUSP |\
+ DWC_INTMSK_HST_PORT;
+
+ gintsts = dwc_reg_read(global_regs, DWC_GINTSTS);
+ gintmsk = dwc_reg_read(global_regs, DWC_GINTMSK);
+
+ return (gintsts & gintmsk) & gintmsk_common;
+}
+
+/**
+ * Common interrupt handler.
+ *
+ * The common interrupts are those that occur in both Host and Device mode.
+ * This handler handles the following interrupts:
+ * - Mode Mismatch Interrupt
+ * - Disconnect Interrupt
+ * - OTG Interrupt
+ * - Connector ID Status Change Interrupt
+ * - Session Request Interrupt.
+ * - Resume / Remote Wakeup Detected Interrupt.
+ *
+ * - Host Port Interrupt. Although this interrupt is documented as only
+ * occurring in Host mode, it also occurs in Device mode when Port Enable /
+ * Disable Changed bit in HPRT0 is set. If it is seen here, while in Device
+ * mode, the interrupt is just cleared.
+ *
+ */
+int dwc_otg_handle_common_intr(struct core_if *core_if)
+{
+ int retval = 0;
+ u32 gintsts;
+ ulong global_regs = core_if->core_global_regs;
+
+ gintsts = dwc_otg_read_common_intr(core_if);
+
+ if (gintsts & DWC_INTSTS_MODE_MISMTC)
+ retval |= dwc_otg_handle_mode_mismatch_intr(core_if);
+ if (gintsts & DWC_INTSTS_OTG)
+ retval |= dwc_otg_handle_otg_intr(core_if);
+ if (gintsts & DWC_INTSTS_CON_ID_STS_CHG)
+ retval |= dwc_otg_handle_conn_id_status_change_intr(core_if);
+ if (gintsts & DWC_INTSTS_SES_DISCON_DET)
+ retval |= dwc_otg_handle_disconnect_intr(core_if);
+ if (gintsts & DWC_INTSTS_NEW_SES_DET)
+ retval |= dwc_otg_handle_session_req_intr(core_if);
+ if (gintsts & DWC_INTSTS_WKP)
+ retval |= dwc_otg_handle_wakeup_detected_intr(core_if);
+ if (gintsts & DWC_INTMSK_USB_SUSP)
+ retval |= dwc_otg_handle_usb_suspend_intr(core_if);
+
+ if ((gintsts & DWC_INTSTS_HST_PORT) &&
+ dwc_otg_is_device_mode(core_if)) {
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_HST_PORT;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ retval |= 1;
+ pr_info("RECEIVED PORTINT while in Device mode\n");
+ }
+
+ return retval;
+}
--
1.7.0.4
^ permalink raw reply related
* RE: [PATCH] powerpc/windfarm: don't pass const strings to snprintf
From: David Laight @ 2012-05-03 10:35 UTC (permalink / raw)
To: Stephen Rothwell, Benjamin Herrenschmidt; +Cc: ppc-dev
In-Reply-To: <20120503161943.7d71d02e7d0aae6b9c0306bf@canb.auug.org.au>
=20
> --- a/drivers/macintosh/windfarm_smu_sat.c
> +++ b/drivers/macintosh/windfarm_smu_sat.c
> @@ -287,7 +287,7 @@ static int wf_sat_probe(struct i2c_client *client,
> sens->sat =3D sat;
> sens->sens.ops =3D &wf_sat_ops;
> sens->sens.name =3D (char *) (sens + 1);
> - snprintf(sens->sens.name, 16, "%s-%d", name, cpu);
> + snprintf((char *)sens->sens.name, 16, "%s-%d", name,
cpu);
> =20
> if (wf_register_sensor(&sens->sens))
> kfree(sens);
Wouldn't it be better to do:
snprintf((char *)(sens + 1), 16, "%s-%d", name, cpu);
David
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-03 8:09 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA22CC5.3010107@gmail.com>
On Thu, 2012-05-03 at 14:59 +0800, Wang Sheng-Hui wrote:
> On 2012年05月03日 14:33, Wang Sheng-Hui wrote:
> > if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
> >> __hard_irq_disable();
>
> I have commented out the 2 lines.
No, Only comment the test, you must absolutely leave the
__hard_irq_disable() call ! That's the whole point of the test, make
sure we unconditionally disable to see if that fixes the problem, in
which case that will tell us that we somewhere accidentally leave
irq_happened set to 0x01 while irqs are hard enabled.
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 6:59 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA226A8.1080903@gmail.com>
On 2012年05月03日 14:33, Wang Sheng-Hui wrote:
> if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
>> __hard_irq_disable();
I have commented out the 2 lines.
FYI.
thanks,
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-03 6:52 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA21CD8.4060800@gmail.com>
On Thu, 2012-05-03 at 13:51 +0800, Wang Sheng-Hui wrote:
> On 2012年05月03日 12:22, Benjamin Herrenschmidt wrote:
> >
> >> It should not as __check_irq_replay() should always be called
> >> with interrupts hard disabled... Do you see any code path
> >> where that is not the case ?
> >
> > More specifically, your backtrace seems to indicate that
> > __check_irq_repay() was called from arch_local_irq_restore() which
> > should have done this before calling __check_irq_replay():
> >
> > if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
> > __hard_irq_disable();
> >
> > Now, the only possibility that I can see for an interrupt to come in
> > and trip the problem you observed would be if for some reason we
> > had irq_happened set to PACA_IRQ_HARD_DIS while interrupts were
> > not hard disabled.
>
> I have a chance to notice that the value is 0x05, not just 0x01.
> So I think this is not the case.
Well, it depends, the value could have been 0x01 before it hit there...
However 0x05 means that EE is set too which means it should never have
hard-enabled to begin with. This is all very odd, we'll need to dig.
If the value had been anything other than 0x01 it would have hard
disabled interrupts meaning that paca->irq_happened cannot change
anymore until they are re-enabled at the bottom of the function.
So please try making this disable unconditional see if that makes any
difference...
Cheers,
Ben.
> > Can you try if removing the test (and thus unconditionally calling
> > __hard_irq_disable()) fixes the problem for you ?
> >
> > If that is the case, then we need to audit the code to figure out how we
> > can end up with that bit in irq_happened set and interrupts hard
> > enabled.
> >
> > Something like may_hard_irq_enable() shouldn't cause it since it should
> > only be called while hard disabled but adding a check in there might be
> > worth it (something like WARN_ON(mfmsr() & MSR_EE)).
> >
> > Cheers,
> > Ben.
> >
> >> Cheers,
> >> Ben.
> >>
> >>> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
> >>> ---
> >>> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
> >>> 1 files changed, 30 insertions(+), 16 deletions(-)
> >>>
> >>> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
> >>> index 5ec1b23..3d48b23 100644
> >>> --- a/arch/powerpc/kernel/irq.c
> >>> +++ b/arch/powerpc/kernel/irq.c
> >>> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
> >>> */
> >>> notrace unsigned int __check_irq_replay(void)
> >>> {
> >>> + unsigned int ret_val;
> >>> /*
> >>> * We use local_paca rather than get_paca() to avoid all
> >>> * the debug_smp_processor_id() business in this low level
> >>> * function
> >>> */
> >>> - unsigned char happened = local_paca->irq_happened;
> >>> + unsigned char happened, irq_happened;
> >>> + happened = irq_happened = local_paca->irq_happened;
> >>>
> >>> /* Clear bit 0 which we wouldn't clear otherwise */
> >>> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
> >>> + irq_happened &= ~PACA_IRQ_HARD_DIS;
> >>>
> >>> /*
> >>> * Force the delivery of pending soft-disabled interrupts on PS3.
> >>> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
> >>> * decrementer itself rather than the paca irq_happened field
> >>> * in case we also had a rollover while hard disabled
> >>> */
> >>> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
> >>> - if (decrementer_check_overflow())
> >>> - return 0x900;
> >>> + irq_happened &= ~PACA_IRQ_DEC;
> >>> + if (decrementer_check_overflow()) {
> >>> + ret_val = 0x900;
> >>> + goto replay;
> >>> + }
> >>>
> >>> /* Finally check if an external interrupt happened */
> >>> - local_paca->irq_happened &= ~PACA_IRQ_EE;
> >>> - if (happened & PACA_IRQ_EE)
> >>> - return 0x500;
> >>> + irq_happened &= ~PACA_IRQ_EE;
> >>> + if (happened & PACA_IRQ_EE) {
> >>> + ret_val = 0x500;
> >>> + goto replay;
> >>> + }
> >>>
> >>> #ifdef CONFIG_PPC_BOOK3E
> >>> /* Finally check if an EPR external interrupt happened
> >>> * this bit is typically set if we need to handle another
> >>> * "edge" interrupt from within the MPIC "EPR" handler
> >>> */
> >>> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
> >>> - if (happened & PACA_IRQ_EE_EDGE)
> >>> - return 0x500;
> >>> + irq_happened &= ~PACA_IRQ_EE_EDGE;
> >>> + if (happened & PACA_IRQ_EE_EDGE) {
> >>> + ret_val = 0x500;
> >>> + goto replay;
> >>> + }
> >>>
> >>> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
> >>> - if (happened & PACA_IRQ_DBELL)
> >>> - return 0x280;
> >>> + irq_happened &= ~PACA_IRQ_DBELL;
> >>> + if (happened & PACA_IRQ_DBELL) {
> >>> + ret_val = 0x280;
> >>> + goto replay;
> >>> + }
> >>> #endif /* CONFIG_PPC_BOOK3E */
> >>>
> >>> /* There should be nothing left ! */
> >>> - BUG_ON(local_paca->irq_happened != 0);
> >>> + BUG_ON(irq_happened != 0);
> >>> + ret_val = 0;
> >>>
> >>> - return 0;
> >>> +replay:
> >>> + local_paca->irq_happened = irq_happened;
> >>> +
> >>> + return ret_val;
> >>> }
> >>>
> >>> notrace void arch_local_irq_restore(unsigned long en)
> >>
> >>
> >> --
> >> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >> the body of a message to majordomo@vger.kernel.org
> >> More majordomo info at http://vger.kernel.org/majordomo-info.html
> >> Please read the FAQ at http://www.tux.org/lkml/
> >
> >
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 6:33 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336018961.2653.11.camel@pasglop>
On 2012年05月03日 12:22, Benjamin Herrenschmidt wrote:
>
>> It should not as __check_irq_replay() should always be called
>> with interrupts hard disabled... Do you see any code path
>> where that is not the case ?
>
> More specifically, your backtrace seems to indicate that
> __check_irq_repay() was called from arch_local_irq_restore() which
> should have done this before calling __check_irq_replay():
>
> if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
> __hard_irq_disable();
>
> Now, the only possibility that I can see for an interrupt to come in
> and trip the problem you observed would be if for some reason we
> had irq_happened set to PACA_IRQ_HARD_DIS while interrupts were
> not hard disabled.
>
> Can you try if removing the test (and thus unconditionally calling
> __hard_irq_disable()) fixes the problem for you ?
The system crashed before I started the LTP test run.
========================================================
kernel BUG at /usr/src/kernels/linux/arch/powerpc/kernel/irq.c:188!
cpu 0x3: Vector: 700 (Program Check) at [c00000026ffd3bb0]
pc: c00000000000ea9c: .__check_irq_replay+0x7c/0x90
lr: c00000000001058c: .arch_local_irq_restore+0x4c/0x90
sp: c00000026ffd3e30
msr: 8000000000029032
current = 0xc0000002694e0110
paca = 0xc000000003580900 softe: 0 irq_happened: 0x01
pid = 0, comm = swapper/3
kernel BUG at /usr/src/kernels/linux/arch/powerpc/kernel/irq.c:188!
enter ? for help
[link register ] c00000000001058c .arch_local_irq_restore+0x4c/0x90
[c00000026ffd3e30] c000000000f42100 softirq_vec+0x0/0x80 (unreliable)
[c00000026ffd3ea0] c0000000000857d4 .__do_softirq+0xa4/0x2a0
[c00000026ffd3f90] c000000000022958 .call_do_softirq+0x14/0x24
[c0000002694778e0] c0000000000106c8 .do_softirq+0xf8/0x130
[c000000269477980] c0000000000854c4 .irq_exit+0xc4/0xf0
[c000000269477a00] c00000000001e970 .timer_interrupt+0x120/0x290
[c000000269477ab0] c000000000003a40 decrementer_common+0x140/0x180
--- Exception: 901 (Decrementer) at c0000000000105c4 .arch_local_irq_restore+0x84/0x90
[c000000269477da0] c000000000058400 .pSeries_idle+0x10/0x40 (unreliable)
[c000000269477e10] c000000000017d50 .cpu_idle+0x190/0x290
[c000000269477ed0] c00000000061ab04 .start_secondary+0x348/0x354
[c000000269477f90] c00000000000936c .start_secondary_prolog+0x10/0x14
3:mon> e
cpu 0x3: Vector: 700 (Program Check) at [c00000026ffd3bb0]
pc: c00000000000ea9c: .__check_irq_replay+0x7c/0x90
lr: c00000000001058c: .arch_local_irq_restore+0x4c/0x90
sp: c00000026ffd3e30
msr: 8000000000029032
current = 0xc0000002694e0110
paca = 0xc000000003580900 softe: 0 irq_happened: 0x01
pid = 0, comm = swapper/3
kernel BUG at /usr/src/kernels/linux/arch/powerpc/kernel/irq.c:188!
3:mon> r
R00 = 0000000000000001 R16 = 0000000000000000
R01 = c00000026ffd3e30 R17 = 0000000000000000
R02 = c000000000edd220 R18 = 0000000000000000
R03 = 0000000000000500 R19 = 0000000000000000
R04 = 0000000000000000 R20 = c000000000f42100
R05 = 00000000000004ca R21 = 0000000000000000
R06 = 0000000002dcc370 R22 = c000000000955b80
R07 = 003524b183dca42e R23 = c000000000955b80
R08 = 0000000000920000 R24 = 000000000000000a
R09 = c000000003580900 R25 = 0000000000000003
R10 = 0000000000000008 R26 = c000000269474100
R11 = 0000000000000000 R27 = c00000026ffd0000
R12 = c000000000653ba8 R28 = 0000000000000000
R13 = c000000003580900 R29 = c000000000f42100
R14 = c000000269477f90 R30 = c000000000e60890
R15 = 000000000ef03f20 R31 = 0000000000000082
pc = c00000000000ea9c .__check_irq_replay+0x7c/0x90
cfar= c00000000000ea38 .__check_irq_replay+0x18/0x90
lr = c00000000001058c .arch_local_irq_restore+0x4c/0x90
msr = 8000000000029032 cr = 28000028
ctr = c00000000001df50 xer = 0000000000000000 trap = 700
3:mon> t
[link register ] c00000000001058c .arch_local_irq_restore+0x4c/0x90
[c00000026ffd3e30] c000000000f42100 softirq_vec+0x0/0x80 (unreliable)
[c00000026ffd3ea0] c0000000000857d4 .__do_softirq+0xa4/0x2a0
[c00000026ffd3f90] c000000000022958 .call_do_softirq+0x14/0x24
[c0000002694778e0] c0000000000106c8 .do_softirq+0xf8/0x130
[c000000269477980] c0000000000854c4 .irq_exit+0xc4/0xf0
[c000000269477a00] c00000000001e970 .timer_interrupt+0x120/0x290
[c000000269477ab0] c000000000003a40 decrementer_common+0x140/0x180
--- Exception: 901 (Decrementer) at c0000000000105c4 .arch_local_irq_restore+0x84/0x90
[c000000269477da0] c000000000058400 .pSeries_idle+0x10/0x40 (unreliable)
[c000000269477e10] c000000000017d50 .cpu_idle+0x190/0x290
[c000000269477ed0] c00000000061ab04 .start_secondary+0x348/0x354
[c000000269477f90] c00000000000936c .start_secondary_prolog+0x10/0x14
3:mon> di c00000000000ea9c
c00000000000ea9c 0b000000 tdnei r0,0
c00000000000eaa0 38600000 li r3,0
c00000000000eaa4 4bffffc4 b c00000000000ea68 # .__check_irq_replay+0x48/0x90
c00000000000eaa8 60000000 nop
...
c00000000000eab0 7c0802a6 mflr r0
c00000000000eab4 fbc1fff0 std r30,-16(r1)
c00000000000eab8 fba1ffe8 std r29,-24(r1)
c00000000000eabc fbe1fff8 std r31,-8(r1)
c00000000000eac0 ebc28128 ld r30,-32472(r2)
c00000000000eac4 3d230002 addis r9,r3,2
c00000000000eac8 f8010010 std r0,16(r1)
c00000000000eacc f821ff71 stdu r1,-144(r1)
c00000000000ead0 38a5ffd8 addi r5,r5,-40
c00000000000ead4 ebe92060 ld r31,8288(r9)
c00000000000ead8 80050048 lwz r0,72(r5)
>
> If that is the case, then we need to audit the code to figure out how we
> can end up with that bit in irq_happened set and interrupts hard
> enabled.
>
> Something like may_hard_irq_enable() shouldn't cause it since it should
> only be called while hard disabled but adding a check in there might be
> worth it (something like WARN_ON(mfmsr() & MSR_EE)).
>
> Cheers,
> Ben.
>
>> Cheers,
>> Ben.
>>
>>> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
>>> ---
>>> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
>>> 1 files changed, 30 insertions(+), 16 deletions(-)
>>>
>>> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
>>> index 5ec1b23..3d48b23 100644
>>> --- a/arch/powerpc/kernel/irq.c
>>> +++ b/arch/powerpc/kernel/irq.c
>>> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
>>> */
>>> notrace unsigned int __check_irq_replay(void)
>>> {
>>> + unsigned int ret_val;
>>> /*
>>> * We use local_paca rather than get_paca() to avoid all
>>> * the debug_smp_processor_id() business in this low level
>>> * function
>>> */
>>> - unsigned char happened = local_paca->irq_happened;
>>> + unsigned char happened, irq_happened;
>>> + happened = irq_happened = local_paca->irq_happened;
>>>
>>> /* Clear bit 0 which we wouldn't clear otherwise */
>>> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
>>> + irq_happened &= ~PACA_IRQ_HARD_DIS;
>>>
>>> /*
>>> * Force the delivery of pending soft-disabled interrupts on PS3.
>>> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
>>> * decrementer itself rather than the paca irq_happened field
>>> * in case we also had a rollover while hard disabled
>>> */
>>> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
>>> - if (decrementer_check_overflow())
>>> - return 0x900;
>>> + irq_happened &= ~PACA_IRQ_DEC;
>>> + if (decrementer_check_overflow()) {
>>> + ret_val = 0x900;
>>> + goto replay;
>>> + }
>>>
>>> /* Finally check if an external interrupt happened */
>>> - local_paca->irq_happened &= ~PACA_IRQ_EE;
>>> - if (happened & PACA_IRQ_EE)
>>> - return 0x500;
>>> + irq_happened &= ~PACA_IRQ_EE;
>>> + if (happened & PACA_IRQ_EE) {
>>> + ret_val = 0x500;
>>> + goto replay;
>>> + }
>>>
>>> #ifdef CONFIG_PPC_BOOK3E
>>> /* Finally check if an EPR external interrupt happened
>>> * this bit is typically set if we need to handle another
>>> * "edge" interrupt from within the MPIC "EPR" handler
>>> */
>>> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
>>> - if (happened & PACA_IRQ_EE_EDGE)
>>> - return 0x500;
>>> + irq_happened &= ~PACA_IRQ_EE_EDGE;
>>> + if (happened & PACA_IRQ_EE_EDGE) {
>>> + ret_val = 0x500;
>>> + goto replay;
>>> + }
>>>
>>> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
>>> - if (happened & PACA_IRQ_DBELL)
>>> - return 0x280;
>>> + irq_happened &= ~PACA_IRQ_DBELL;
>>> + if (happened & PACA_IRQ_DBELL) {
>>> + ret_val = 0x280;
>>> + goto replay;
>>> + }
>>> #endif /* CONFIG_PPC_BOOK3E */
>>>
>>> /* There should be nothing left ! */
>>> - BUG_ON(local_paca->irq_happened != 0);
>>> + BUG_ON(irq_happened != 0);
>>> + ret_val = 0;
>>>
>>> - return 0;
>>> +replay:
>>> + local_paca->irq_happened = irq_happened;
>>> +
>>> + return ret_val;
>>> }
>>>
>>> notrace void arch_local_irq_restore(unsigned long en)
>>
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>> Please read the FAQ at http://www.tux.org/lkml/
>
>
^ permalink raw reply
* [PATCH] powerpc/windfarm: don't pass const strings to snprintf
From: Stephen Rothwell @ 2012-05-03 6:19 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: ppc-dev
[-- Attachment #1: Type: text/plain, Size: 1838 bytes --]
Fixes these build warnings:
drivers/macintosh/windfarm_smu_sat.c: In function 'wf_sat_probe':
drivers/macintosh/windfarm_smu_sat.c:290:3: warning: passing argument 1 of 'snprintf' discards qualifiers from pointer target type
include/linux/kernel.h:323:5: note: expected 'char *' but argument is of type 'const char *'
drivers/macintosh/windfarm_smu_sat.c:317:3: warning: passing argument 1 of 'snprintf' discards qualifiers from pointer target type
include/linux/kernel.h:323:5: note: expected 'char *' but argument is of type 'const char *'
Introduced by commit e074d08e2b98 ("powerpc/windfarm: const'ify and add
"priv" field to controls & sensors").
Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au>
---
drivers/macintosh/windfarm_smu_sat.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/macintosh/windfarm_smu_sat.c b/drivers/macintosh/windfarm_smu_sat.c
index 72dfe19..e2989ce 100644
--- a/drivers/macintosh/windfarm_smu_sat.c
+++ b/drivers/macintosh/windfarm_smu_sat.c
@@ -287,7 +287,7 @@ static int wf_sat_probe(struct i2c_client *client,
sens->sat = sat;
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
- snprintf(sens->sens.name, 16, "%s-%d", name, cpu);
+ snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu);
if (wf_register_sensor(&sens->sens))
kfree(sens);
@@ -314,7 +314,7 @@ static int wf_sat_probe(struct i2c_client *client,
sens->sat = sat;
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
- snprintf(sens->sens.name, 16, "cpu-power-%d", cpu);
+ snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu);
if (wf_register_sensor(&sens->sens))
kfree(sens);
--
1.7.10.280.gaa39
--
Cheers,
Stephen Rothwell sfr@canb.auug.org.au
[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply related
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 5:51 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336018961.2653.11.camel@pasglop>
On 2012年05月03日 12:22, Benjamin Herrenschmidt wrote:
>
>> It should not as __check_irq_replay() should always be called
>> with interrupts hard disabled... Do you see any code path
>> where that is not the case ?
>
> More specifically, your backtrace seems to indicate that
> __check_irq_repay() was called from arch_local_irq_restore() which
> should have done this before calling __check_irq_replay():
>
> if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
> __hard_irq_disable();
>
> Now, the only possibility that I can see for an interrupt to come in
> and trip the problem you observed would be if for some reason we
> had irq_happened set to PACA_IRQ_HARD_DIS while interrupts were
> not hard disabled.
I have a chance to notice that the value is 0x05, not just 0x01.
So I think this is not the case.
>
> Can you try if removing the test (and thus unconditionally calling
> __hard_irq_disable()) fixes the problem for you ?
>
> If that is the case, then we need to audit the code to figure out how we
> can end up with that bit in irq_happened set and interrupts hard
> enabled.
>
> Something like may_hard_irq_enable() shouldn't cause it since it should
> only be called while hard disabled but adding a check in there might be
> worth it (something like WARN_ON(mfmsr() & MSR_EE)).
>
> Cheers,
> Ben.
>
>> Cheers,
>> Ben.
>>
>>> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
>>> ---
>>> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
>>> 1 files changed, 30 insertions(+), 16 deletions(-)
>>>
>>> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
>>> index 5ec1b23..3d48b23 100644
>>> --- a/arch/powerpc/kernel/irq.c
>>> +++ b/arch/powerpc/kernel/irq.c
>>> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
>>> */
>>> notrace unsigned int __check_irq_replay(void)
>>> {
>>> + unsigned int ret_val;
>>> /*
>>> * We use local_paca rather than get_paca() to avoid all
>>> * the debug_smp_processor_id() business in this low level
>>> * function
>>> */
>>> - unsigned char happened = local_paca->irq_happened;
>>> + unsigned char happened, irq_happened;
>>> + happened = irq_happened = local_paca->irq_happened;
>>>
>>> /* Clear bit 0 which we wouldn't clear otherwise */
>>> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
>>> + irq_happened &= ~PACA_IRQ_HARD_DIS;
>>>
>>> /*
>>> * Force the delivery of pending soft-disabled interrupts on PS3.
>>> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
>>> * decrementer itself rather than the paca irq_happened field
>>> * in case we also had a rollover while hard disabled
>>> */
>>> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
>>> - if (decrementer_check_overflow())
>>> - return 0x900;
>>> + irq_happened &= ~PACA_IRQ_DEC;
>>> + if (decrementer_check_overflow()) {
>>> + ret_val = 0x900;
>>> + goto replay;
>>> + }
>>>
>>> /* Finally check if an external interrupt happened */
>>> - local_paca->irq_happened &= ~PACA_IRQ_EE;
>>> - if (happened & PACA_IRQ_EE)
>>> - return 0x500;
>>> + irq_happened &= ~PACA_IRQ_EE;
>>> + if (happened & PACA_IRQ_EE) {
>>> + ret_val = 0x500;
>>> + goto replay;
>>> + }
>>>
>>> #ifdef CONFIG_PPC_BOOK3E
>>> /* Finally check if an EPR external interrupt happened
>>> * this bit is typically set if we need to handle another
>>> * "edge" interrupt from within the MPIC "EPR" handler
>>> */
>>> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
>>> - if (happened & PACA_IRQ_EE_EDGE)
>>> - return 0x500;
>>> + irq_happened &= ~PACA_IRQ_EE_EDGE;
>>> + if (happened & PACA_IRQ_EE_EDGE) {
>>> + ret_val = 0x500;
>>> + goto replay;
>>> + }
>>>
>>> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
>>> - if (happened & PACA_IRQ_DBELL)
>>> - return 0x280;
>>> + irq_happened &= ~PACA_IRQ_DBELL;
>>> + if (happened & PACA_IRQ_DBELL) {
>>> + ret_val = 0x280;
>>> + goto replay;
>>> + }
>>> #endif /* CONFIG_PPC_BOOK3E */
>>>
>>> /* There should be nothing left ! */
>>> - BUG_ON(local_paca->irq_happened != 0);
>>> + BUG_ON(irq_happened != 0);
>>> + ret_val = 0;
>>>
>>> - return 0;
>>> +replay:
>>> + local_paca->irq_happened = irq_happened;
>>> +
>>> + return ret_val;
>>> }
>>>
>>> notrace void arch_local_irq_restore(unsigned long en)
>>
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>> Please read the FAQ at http://www.tux.org/lkml/
>
>
^ permalink raw reply
* [PATCH] powerpc/windfarm: fix compiler warning
From: Stephen Rothwell @ 2012-05-03 4:34 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: ppc-dev
[-- Attachment #1: Type: text/plain, Size: 1206 bytes --]
Fixes this warning:
drivers/macintosh/windfarm_pm91.c: In function 'wf_smu_cpu_fans_tick':
drivers/macintosh/windfarm_pm91.c:237:2: warning: passing argument 1 of 'wf_sensor_get' from incompatible pointer type
drivers/macintosh/windfarm.h:124:19: note: expected 'struct wf_sensor *' but argument is of type 'struct wf_sensor **'
Introduced by commit 33e6820b767a ("powerpc/windfarm: Add useful
accessors").
Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au>
---
drivers/macintosh/windfarm_pm91.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c
index e18002b..7653603 100644
--- a/drivers/macintosh/windfarm_pm91.c
+++ b/drivers/macintosh/windfarm_pm91.c
@@ -234,7 +234,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
return;
}
- rc = wf_sensor_get(&sensor_cpu_power, &power);
+ rc = wf_sensor_get(sensor_cpu_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
rc);
--
1.7.10.280.gaa39
--
Cheers,
Stephen Rothwell sfr@canb.auug.org.au
http://www.canb.auug.org.au/~sfr/
[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply related
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-03 4:26 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA1EE2C.6050201@gmail.com>
On Thu, 2012-05-03 at 10:32 +0800, Wang Sheng-Hui wrote:
> > It should not as __check_irq_replay() should always be called
> > with interrupts hard disabled... Do you see any code path
> > where that is not the case ?
>
> Since __check_irq_replay() should always be called with interrupts
> hard disabled, I think it's harmless to use local var here.
No, that would be papering over the real problem. All oprofile does is
trigger perfmon interrupts (which act as some kind of NMI when
soft-disabled but should be masked by MSR:EE when hard disabled).
So there's a deeper issue here that we need to understand before we can
propose a fix. IE. It should not have happened.
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-03 4:22 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336011306.2653.3.camel@pasglop>
> It should not as __check_irq_replay() should always be called
> with interrupts hard disabled... Do you see any code path
> where that is not the case ?
More specifically, your backtrace seems to indicate that
__check_irq_repay() was called from arch_local_irq_restore() which
should have done this before calling __check_irq_replay():
if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
__hard_irq_disable();
Now, the only possibility that I can see for an interrupt to come in
and trip the problem you observed would be if for some reason we
had irq_happened set to PACA_IRQ_HARD_DIS while interrupts were
not hard disabled.
Can you try if removing the test (and thus unconditionally calling
__hard_irq_disable()) fixes the problem for you ?
If that is the case, then we need to audit the code to figure out how we
can end up with that bit in irq_happened set and interrupts hard
enabled.
Something like may_hard_irq_enable() shouldn't cause it since it should
only be called while hard disabled but adding a check in there might be
worth it (something like WARN_ON(mfmsr() & MSR_EE)).
Cheers,
Ben.
> Cheers,
> Ben.
>
> > Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
> > ---
> > arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
> > 1 files changed, 30 insertions(+), 16 deletions(-)
> >
> > diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
> > index 5ec1b23..3d48b23 100644
> > --- a/arch/powerpc/kernel/irq.c
> > +++ b/arch/powerpc/kernel/irq.c
> > @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
> > */
> > notrace unsigned int __check_irq_replay(void)
> > {
> > + unsigned int ret_val;
> > /*
> > * We use local_paca rather than get_paca() to avoid all
> > * the debug_smp_processor_id() business in this low level
> > * function
> > */
> > - unsigned char happened = local_paca->irq_happened;
> > + unsigned char happened, irq_happened;
> > + happened = irq_happened = local_paca->irq_happened;
> >
> > /* Clear bit 0 which we wouldn't clear otherwise */
> > - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
> > + irq_happened &= ~PACA_IRQ_HARD_DIS;
> >
> > /*
> > * Force the delivery of pending soft-disabled interrupts on PS3.
> > @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
> > * decrementer itself rather than the paca irq_happened field
> > * in case we also had a rollover while hard disabled
> > */
> > - local_paca->irq_happened &= ~PACA_IRQ_DEC;
> > - if (decrementer_check_overflow())
> > - return 0x900;
> > + irq_happened &= ~PACA_IRQ_DEC;
> > + if (decrementer_check_overflow()) {
> > + ret_val = 0x900;
> > + goto replay;
> > + }
> >
> > /* Finally check if an external interrupt happened */
> > - local_paca->irq_happened &= ~PACA_IRQ_EE;
> > - if (happened & PACA_IRQ_EE)
> > - return 0x500;
> > + irq_happened &= ~PACA_IRQ_EE;
> > + if (happened & PACA_IRQ_EE) {
> > + ret_val = 0x500;
> > + goto replay;
> > + }
> >
> > #ifdef CONFIG_PPC_BOOK3E
> > /* Finally check if an EPR external interrupt happened
> > * this bit is typically set if we need to handle another
> > * "edge" interrupt from within the MPIC "EPR" handler
> > */
> > - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
> > - if (happened & PACA_IRQ_EE_EDGE)
> > - return 0x500;
> > + irq_happened &= ~PACA_IRQ_EE_EDGE;
> > + if (happened & PACA_IRQ_EE_EDGE) {
> > + ret_val = 0x500;
> > + goto replay;
> > + }
> >
> > - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
> > - if (happened & PACA_IRQ_DBELL)
> > - return 0x280;
> > + irq_happened &= ~PACA_IRQ_DBELL;
> > + if (happened & PACA_IRQ_DBELL) {
> > + ret_val = 0x280;
> > + goto replay;
> > + }
> > #endif /* CONFIG_PPC_BOOK3E */
> >
> > /* There should be nothing left ! */
> > - BUG_ON(local_paca->irq_happened != 0);
> > + BUG_ON(irq_happened != 0);
> > + ret_val = 0;
> >
> > - return 0;
> > +replay:
> > + local_paca->irq_happened = irq_happened;
> > +
> > + return ret_val;
> > }
> >
> > notrace void arch_local_irq_restore(unsigned long en)
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 2:32 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336011306.2653.3.camel@pasglop>
On 2012年05月03日 10:15, Benjamin Herrenschmidt wrote:
> On Thu, 2012-05-03 at 09:53 +0800, Wang Sheng-Hui wrote:
>> local_paca->irq_happened may be changed asychronously.
>>
>> In my test env (IBM Power 9117-MMA), I installed the RHEL6.2 with the shipped
>> oprofile. Then I run into kernel v3.4-rc4, setup/start oprofile and start the
>> LTP test suite.
>>
>> In a short while, the system would crash. Seems that oprofile may change
>> the irq_happened.
>
> .../...
>
>> Use local var instead of local_paca->irq_happened directly in this function here.
>>
>> Please check this patch. Any comments are welcome.
>
> It should not as __check_irq_replay() should always be called
> with interrupts hard disabled... Do you see any code path
> where that is not the case ?
Since __check_irq_replay() should always be called with interrupts hard disabled,
I think it's harmless to use local var here.
>
> Cheers,
> Ben.
>
>> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
>> ---
>> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
>> 1 files changed, 30 insertions(+), 16 deletions(-)
>>
>> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
>> index 5ec1b23..3d48b23 100644
>> --- a/arch/powerpc/kernel/irq.c
>> +++ b/arch/powerpc/kernel/irq.c
>> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
>> */
>> notrace unsigned int __check_irq_replay(void)
>> {
>> + unsigned int ret_val;
>> /*
>> * We use local_paca rather than get_paca() to avoid all
>> * the debug_smp_processor_id() business in this low level
>> * function
>> */
>> - unsigned char happened = local_paca->irq_happened;
>> + unsigned char happened, irq_happened;
>> + happened = irq_happened = local_paca->irq_happened;
>>
>> /* Clear bit 0 which we wouldn't clear otherwise */
>> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
>> + irq_happened &= ~PACA_IRQ_HARD_DIS;
>>
>> /*
>> * Force the delivery of pending soft-disabled interrupts on PS3.
>> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
>> * decrementer itself rather than the paca irq_happened field
>> * in case we also had a rollover while hard disabled
>> */
>> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
>> - if (decrementer_check_overflow())
>> - return 0x900;
>> + irq_happened &= ~PACA_IRQ_DEC;
>> + if (decrementer_check_overflow()) {
>> + ret_val = 0x900;
>> + goto replay;
>> + }
>>
>> /* Finally check if an external interrupt happened */
>> - local_paca->irq_happened &= ~PACA_IRQ_EE;
>> - if (happened & PACA_IRQ_EE)
>> - return 0x500;
>> + irq_happened &= ~PACA_IRQ_EE;
>> + if (happened & PACA_IRQ_EE) {
>> + ret_val = 0x500;
>> + goto replay;
>> + }
>>
>> #ifdef CONFIG_PPC_BOOK3E
>> /* Finally check if an EPR external interrupt happened
>> * this bit is typically set if we need to handle another
>> * "edge" interrupt from within the MPIC "EPR" handler
>> */
>> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
>> - if (happened & PACA_IRQ_EE_EDGE)
>> - return 0x500;
>> + irq_happened &= ~PACA_IRQ_EE_EDGE;
>> + if (happened & PACA_IRQ_EE_EDGE) {
>> + ret_val = 0x500;
>> + goto replay;
>> + }
>>
>> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
>> - if (happened & PACA_IRQ_DBELL)
>> - return 0x280;
>> + irq_happened &= ~PACA_IRQ_DBELL;
>> + if (happened & PACA_IRQ_DBELL) {
>> + ret_val = 0x280;
>> + goto replay;
>> + }
>> #endif /* CONFIG_PPC_BOOK3E */
>>
>> /* There should be nothing left ! */
>> - BUG_ON(local_paca->irq_happened != 0);
>> + BUG_ON(irq_happened != 0);
>> + ret_val = 0;
>>
>> - return 0;
>> +replay:
>> + local_paca->irq_happened = irq_happened;
>> +
>> + return ret_val;
>> }
>>
>> notrace void arch_local_irq_restore(unsigned long en)
>
>
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 2:27 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336011306.2653.3.camel@pasglop>
On 2012年05月03日 10:15, Benjamin Herrenschmidt wrote:
> On Thu, 2012-05-03 at 09:53 +0800, Wang Sheng-Hui wrote:
>> local_paca->irq_happened may be changed asychronously.
>>
>> In my test env (IBM Power 9117-MMA), I installed the RHEL6.2 with the shipped
>> oprofile. Then I run into kernel v3.4-rc4, setup/start oprofile and start the
>> LTP test suite.
>>
>> In a short while, the system would crash. Seems that oprofile may change
>> the irq_happened.
>
> .../...
>
>> Use local var instead of local_paca->irq_happened directly in this function here.
>>
>> Please check this patch. Any comments are welcome.
>
> It should not as __check_irq_replay() should always be called
> with interrupts hard disabled... Do you see any code path
> where that is not the case ?
This is the only case.
I have run LTP test suite on my system without oprofile over 24 hours
with 3.4-rc4 kernel.
Then I started oprofile, and the system crashed quickly.
I wonder if oprofile does some special changes with the running.
But I'm not familiar with the internal of oprofile.
I tried to change BUG_ON to WARN_ON, and got lots of warnning messages
in dmesg. So I changed it to local var here.
>
> Cheers,
> Ben.
>
>> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
>> ---
>> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
>> 1 files changed, 30 insertions(+), 16 deletions(-)
>>
>> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
>> index 5ec1b23..3d48b23 100644
>> --- a/arch/powerpc/kernel/irq.c
>> +++ b/arch/powerpc/kernel/irq.c
>> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
>> */
>> notrace unsigned int __check_irq_replay(void)
>> {
>> + unsigned int ret_val;
>> /*
>> * We use local_paca rather than get_paca() to avoid all
>> * the debug_smp_processor_id() business in this low level
>> * function
>> */
>> - unsigned char happened = local_paca->irq_happened;
>> + unsigned char happened, irq_happened;
>> + happened = irq_happened = local_paca->irq_happened;
>>
>> /* Clear bit 0 which we wouldn't clear otherwise */
>> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
>> + irq_happened &= ~PACA_IRQ_HARD_DIS;
>>
>> /*
>> * Force the delivery of pending soft-disabled interrupts on PS3.
>> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
>> * decrementer itself rather than the paca irq_happened field
>> * in case we also had a rollover while hard disabled
>> */
>> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
>> - if (decrementer_check_overflow())
>> - return 0x900;
>> + irq_happened &= ~PACA_IRQ_DEC;
>> + if (decrementer_check_overflow()) {
>> + ret_val = 0x900;
>> + goto replay;
>> + }
>>
>> /* Finally check if an external interrupt happened */
>> - local_paca->irq_happened &= ~PACA_IRQ_EE;
>> - if (happened & PACA_IRQ_EE)
>> - return 0x500;
>> + irq_happened &= ~PACA_IRQ_EE;
>> + if (happened & PACA_IRQ_EE) {
>> + ret_val = 0x500;
>> + goto replay;
>> + }
>>
>> #ifdef CONFIG_PPC_BOOK3E
>> /* Finally check if an EPR external interrupt happened
>> * this bit is typically set if we need to handle another
>> * "edge" interrupt from within the MPIC "EPR" handler
>> */
>> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
>> - if (happened & PACA_IRQ_EE_EDGE)
>> - return 0x500;
>> + irq_happened &= ~PACA_IRQ_EE_EDGE;
>> + if (happened & PACA_IRQ_EE_EDGE) {
>> + ret_val = 0x500;
>> + goto replay;
>> + }
>>
>> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
>> - if (happened & PACA_IRQ_DBELL)
>> - return 0x280;
>> + irq_happened &= ~PACA_IRQ_DBELL;
>> + if (happened & PACA_IRQ_DBELL) {
>> + ret_val = 0x280;
>> + goto replay;
>> + }
>> #endif /* CONFIG_PPC_BOOK3E */
>>
>> /* There should be nothing left ! */
>> - BUG_ON(local_paca->irq_happened != 0);
>> + BUG_ON(irq_happened != 0);
>> + ret_val = 0;
>>
>> - return 0;
>> +replay:
>> + local_paca->irq_happened = irq_happened;
>> +
>> + return ret_val;
>> }
>>
>> notrace void arch_local_irq_restore(unsigned long en)
>
>
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-03 2:15 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA1E527.1090807@gmail.com>
On Thu, 2012-05-03 at 09:53 +0800, Wang Sheng-Hui wrote:
> local_paca->irq_happened may be changed asychronously.
>
> In my test env (IBM Power 9117-MMA), I installed the RHEL6.2 with the shipped
> oprofile. Then I run into kernel v3.4-rc4, setup/start oprofile and start the
> LTP test suite.
>
> In a short while, the system would crash. Seems that oprofile may change
> the irq_happened.
.../...
> Use local var instead of local_paca->irq_happened directly in this function here.
>
> Please check this patch. Any comments are welcome.
It should not as __check_irq_replay() should always be called
with interrupts hard disabled... Do you see any code path
where that is not the case ?
Cheers,
Ben.
> Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
> ---
> arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
> 1 files changed, 30 insertions(+), 16 deletions(-)
>
> diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
> index 5ec1b23..3d48b23 100644
> --- a/arch/powerpc/kernel/irq.c
> +++ b/arch/powerpc/kernel/irq.c
> @@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
> */
> notrace unsigned int __check_irq_replay(void)
> {
> + unsigned int ret_val;
> /*
> * We use local_paca rather than get_paca() to avoid all
> * the debug_smp_processor_id() business in this low level
> * function
> */
> - unsigned char happened = local_paca->irq_happened;
> + unsigned char happened, irq_happened;
> + happened = irq_happened = local_paca->irq_happened;
>
> /* Clear bit 0 which we wouldn't clear otherwise */
> - local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
> + irq_happened &= ~PACA_IRQ_HARD_DIS;
>
> /*
> * Force the delivery of pending soft-disabled interrupts on PS3.
> @@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
> * decrementer itself rather than the paca irq_happened field
> * in case we also had a rollover while hard disabled
> */
> - local_paca->irq_happened &= ~PACA_IRQ_DEC;
> - if (decrementer_check_overflow())
> - return 0x900;
> + irq_happened &= ~PACA_IRQ_DEC;
> + if (decrementer_check_overflow()) {
> + ret_val = 0x900;
> + goto replay;
> + }
>
> /* Finally check if an external interrupt happened */
> - local_paca->irq_happened &= ~PACA_IRQ_EE;
> - if (happened & PACA_IRQ_EE)
> - return 0x500;
> + irq_happened &= ~PACA_IRQ_EE;
> + if (happened & PACA_IRQ_EE) {
> + ret_val = 0x500;
> + goto replay;
> + }
>
> #ifdef CONFIG_PPC_BOOK3E
> /* Finally check if an EPR external interrupt happened
> * this bit is typically set if we need to handle another
> * "edge" interrupt from within the MPIC "EPR" handler
> */
> - local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
> - if (happened & PACA_IRQ_EE_EDGE)
> - return 0x500;
> + irq_happened &= ~PACA_IRQ_EE_EDGE;
> + if (happened & PACA_IRQ_EE_EDGE) {
> + ret_val = 0x500;
> + goto replay;
> + }
>
> - local_paca->irq_happened &= ~PACA_IRQ_DBELL;
> - if (happened & PACA_IRQ_DBELL)
> - return 0x280;
> + irq_happened &= ~PACA_IRQ_DBELL;
> + if (happened & PACA_IRQ_DBELL) {
> + ret_val = 0x280;
> + goto replay;
> + }
> #endif /* CONFIG_PPC_BOOK3E */
>
> /* There should be nothing left ! */
> - BUG_ON(local_paca->irq_happened != 0);
> + BUG_ON(irq_happened != 0);
> + ret_val = 0;
>
> - return 0;
> +replay:
> + local_paca->irq_happened = irq_happened;
> +
> + return ret_val;
> }
>
> notrace void arch_local_irq_restore(unsigned long en)
^ permalink raw reply
* [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 1:53 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Milton Miller, Grant Likely,
Stephen Rothwell, Anton Blanchard, linuxppc-dev, linux-kernel
local_paca->irq_happened may be changed asychronously.
In my test env (IBM Power 9117-MMA), I installed the RHEL6.2 with the shipped
oprofile. Then I run into kernel v3.4-rc4, setup/start oprofile and start the
LTP test suite.
In a short while, the system would crash. Seems that oprofile may change
the irq_happened.
======================================================================
KERNEL: /boot/vmlinux-3.4.0-rc4-00104-gaf3a3ab
DUMPFILE: vmcore [PARTIAL DUMP]
CPUS: 10
DATE: Fri Apr 27 18:54:34 2012
UPTIME: 00:02:34
LOAD AVERAGE: 0.60, 0.27, 0.10
TASKS: 369
NODENAME: feastlp3.upt.austin.ibm.com
RELEASE: 3.4.0-rc4-00104-gaf3a3ab
VERSION: #4 SMP Fri Apr 27 03:13:43 CDT 2012
MACHINE: ppc64 (4704 Mhz)
MEMORY: 9.8 GB
PANIC: "kernel BUG at /usr/src/kernels/linux/arch/powerpc/kernel/irq.c:188!"
PID: 0
COMMAND: "swapper/4"
TASK: c0000002694e3cc0 (1 of 10) [THREAD_INFO: c0000002694f8000]
CPU: 4
STATE: TASK_RUNNING (PANIC)
crash> bt
PID: 0 TASK: c0000002694e3cc0 CPU: 4 COMMAND: "swapper/4"
#0 [c00000026ffcb6e0] .crash_kexec at c0000000000f22e8
#1 [c00000026ffcb8e0] .oops_end at c00000000060aed8
#2 [c00000026ffcb980] ._exception at c000000000020900
#3 [c00000026ffcbb40] program_check_common at c0000000000053b4
Breakpoint trap [700] exception frame:
R0: 0000000000000001 R1: c00000026ffcbe30 R2: c000000000edd170
R3: 0000000000000500 R4: 0000000000000000 R5: 00000000000007fd
R6: 000000000124a180 R7: 003450cf9bd1233b R8: 0000000000940000
R9: c000000003400c00 R10: 0000000000000001 R11: 0000000000000000
R12: 0000000000000002 R13: c000000003400c00 R14: c0000002694fbf90
R15: 0000000002000040 R16: 0000000000000004 R17: 0000000000000000
R18: 0000000000000000 R19: 0000000000000000 R20: c000000000f42100
R21: 0000000000000000 R22: c000000000955b80 R23: c000000000955b80
R24: 000000000000000a R25: 0000000000000004 R26: c0000002694f8100
R27: c00000026ffc8000 R28: 0000000000000000 R29: c000000000f42100
R30: c000000000e60810 R31: 0000000000000040
NIP: c00000000000ea9c MSR: 8000000000029032 OR3: c00000000000ea3c
CTR: c000000000063e40 LR: c000000000010578 XER: 0000000000000000
CCR: 0000000028000048 MQ: 0000000000000000 DAR: c000000001295d00
DSISR: 0000000000000000 Syscall Result: 0000000000000000
#4 [c00000026ffcbe30] .__check_irq_replay at c00000000000ea9c
[Link Register ] [c00000026ffcbe30] .arch_local_irq_restore at c000000000010578
#5 [c00000026ffcbea0] .__do_softirq at c000000000085724
#6 [c00000026ffcbf90] .call_do_softirq at c000000000022928
#7 [c0000002694fb8d0] .do_softirq at c0000000000106c8
#8 [c0000002694fb970] .irq_exit at c000000000085414
#9 [c0000002694fb9f0] .do_IRQ at c0000000000100a4
#10 [c0000002694fbab0] hardware_interrupt_common at c0000000000038c0
Hardware Interrupt [501] exception frame:
R0: 0000000000000001 R1: c0000002694fbda0 R2: c000000000edd170
R3: 0000000000000000 R4: 0000000000000000 R5: 0000000000000000
R6: 00000000000000e0 R7: 003450cf9bd1233b R8: 0000000000940000
R9: ffffffffffffffff R10: 0000000000243694 R11: 0000000000000001
R12: 0000000000000002 R13: c000000003400c00
NIP: c0000000000105b4 MSR: 8000000000009032 OR3: 0000000000000c00
CTR: c0000000004de3a0 LR: c0000000000105b4 XER: 0000000000000000
CCR: 0000000044000044 MQ: 0000000000000001 DAR: c0000000012990b0
DSISR: c0000002694fbce0 Syscall Result: 0000000000000000
#11 [c0000002694fbda0] .arch_local_irq_restore at c0000000000105b4 (unreliable)
#12 [c0000002694fbe10] .cpu_idle at c000000000017d20
#13 [c0000002694fbed0] .start_secondary at c00000000061a934
#14 [c0000002694fbf90] .start_secondary_prolog at c00000000000936c
Use local var instead of local_paca->irq_happened directly in this function here.
Please check this patch. Any comments are welcome.
Signed-off-by: Wang Sheng-Hui <shhuiw@gmail.com>
---
arch/powerpc/kernel/irq.c | 46 +++++++++++++++++++++++++++++---------------
1 files changed, 30 insertions(+), 16 deletions(-)
diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
index 5ec1b23..3d48b23 100644
--- a/arch/powerpc/kernel/irq.c
+++ b/arch/powerpc/kernel/irq.c
@@ -137,15 +137,17 @@ static inline notrace int decrementer_check_overflow(void)
*/
notrace unsigned int __check_irq_replay(void)
{
+ unsigned int ret_val;
/*
* We use local_paca rather than get_paca() to avoid all
* the debug_smp_processor_id() business in this low level
* function
*/
- unsigned char happened = local_paca->irq_happened;
+ unsigned char happened, irq_happened;
+ happened = irq_happened = local_paca->irq_happened;
/* Clear bit 0 which we wouldn't clear otherwise */
- local_paca->irq_happened &= ~PACA_IRQ_HARD_DIS;
+ irq_happened &= ~PACA_IRQ_HARD_DIS;
/*
* Force the delivery of pending soft-disabled interrupts on PS3.
@@ -161,33 +163,45 @@ notrace unsigned int __check_irq_replay(void)
* decrementer itself rather than the paca irq_happened field
* in case we also had a rollover while hard disabled
*/
- local_paca->irq_happened &= ~PACA_IRQ_DEC;
- if (decrementer_check_overflow())
- return 0x900;
+ irq_happened &= ~PACA_IRQ_DEC;
+ if (decrementer_check_overflow()) {
+ ret_val = 0x900;
+ goto replay;
+ }
/* Finally check if an external interrupt happened */
- local_paca->irq_happened &= ~PACA_IRQ_EE;
- if (happened & PACA_IRQ_EE)
- return 0x500;
+ irq_happened &= ~PACA_IRQ_EE;
+ if (happened & PACA_IRQ_EE) {
+ ret_val = 0x500;
+ goto replay;
+ }
#ifdef CONFIG_PPC_BOOK3E
/* Finally check if an EPR external interrupt happened
* this bit is typically set if we need to handle another
* "edge" interrupt from within the MPIC "EPR" handler
*/
- local_paca->irq_happened &= ~PACA_IRQ_EE_EDGE;
- if (happened & PACA_IRQ_EE_EDGE)
- return 0x500;
+ irq_happened &= ~PACA_IRQ_EE_EDGE;
+ if (happened & PACA_IRQ_EE_EDGE) {
+ ret_val = 0x500;
+ goto replay;
+ }
- local_paca->irq_happened &= ~PACA_IRQ_DBELL;
- if (happened & PACA_IRQ_DBELL)
- return 0x280;
+ irq_happened &= ~PACA_IRQ_DBELL;
+ if (happened & PACA_IRQ_DBELL) {
+ ret_val = 0x280;
+ goto replay;
+ }
#endif /* CONFIG_PPC_BOOK3E */
/* There should be nothing left ! */
- BUG_ON(local_paca->irq_happened != 0);
+ BUG_ON(irq_happened != 0);
+ ret_val = 0;
- return 0;
+replay:
+ local_paca->irq_happened = irq_happened;
+
+ return ret_val;
}
notrace void arch_local_irq_restore(unsigned long en)
--
1.7.1
^ permalink raw reply related
* Re: [PATCH] net/pasemi: fix compiler warning
From: David Miller @ 2012-05-03 0:53 UTC (permalink / raw)
To: sfr; +Cc: olof, netdev, netdev, linuxppc-dev
In-Reply-To: <20120503105146.83f0a4a074dafad0443b875d@canb.auug.org.au>
From: Stephen Rothwell <sfr@canb.auug.org.au>
Date: Thu, 3 May 2012 10:51:46 +1000
> Fix this compiler warning (on PowerPC) by not marking a parameter as
> const:
>
> drivers/net/ethernet/pasemi/pasemi_mac.c: In function 'pasemi_mac_replenish_rx_ring':
> drivers/net/ethernet/pasemi/pasemi_mac.c:646:3: warning: passing argument 1 of 'netdev_alloc_skb' discards qualifiers from pointer target type
> include/linux/skbuff.h:1706:31: note: expected 'struct net_device *' but argument is of type 'const struct net_device *'
>
> Cc: Olof Johansson <olof@lixom.net>
> Cc: Pradeep A. Dalvi <netdev@pradeepdalvi.com>
> Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au>
Applied, thanks.
^ permalink raw reply
* [PATCH] net/pasemi: fix compiler warning
From: Stephen Rothwell @ 2012-05-03 0:51 UTC (permalink / raw)
To: David Miller, netdev; +Cc: Olof Johansson, Pradeep A. Dalvi, ppc-dev
[-- Attachment #1: Type: text/plain, Size: 1449 bytes --]
Fix this compiler warning (on PowerPC) by not marking a parameter as
const:
drivers/net/ethernet/pasemi/pasemi_mac.c: In function 'pasemi_mac_replenish_rx_ring':
drivers/net/ethernet/pasemi/pasemi_mac.c:646:3: warning: passing argument 1 of 'netdev_alloc_skb' discards qualifiers from pointer target type
include/linux/skbuff.h:1706:31: note: expected 'struct net_device *' but argument is of type 'const struct net_device *'
Cc: Olof Johansson <olof@lixom.net>
Cc: Pradeep A. Dalvi <netdev@pradeepdalvi.com>
Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au>
---
drivers/net/ethernet/pasemi/pasemi_mac.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
The warning was introduced by commit dae2e9f430c4 ("netdev: ethernet
dev_alloc_skb to netdev_alloc_skb").
diff --git a/drivers/net/ethernet/pasemi/pasemi_mac.c b/drivers/net/ethernet/pasemi/pasemi_mac.c
index ddc95b0..e559dfa 100644
--- a/drivers/net/ethernet/pasemi/pasemi_mac.c
+++ b/drivers/net/ethernet/pasemi/pasemi_mac.c
@@ -623,7 +623,7 @@ static void pasemi_mac_free_rx_resources(struct pasemi_mac *mac)
mac->rx = NULL;
}
-static void pasemi_mac_replenish_rx_ring(const struct net_device *dev,
+static void pasemi_mac_replenish_rx_ring(struct net_device *dev,
const int limit)
{
const struct pasemi_mac *mac = netdev_priv(dev);
--
1.7.10.280.gaa39
--
Cheers,
Stephen Rothwell sfr@canb.auug.org.au
[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply related
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Hugh Dickins @ 2012-05-03 0:24 UTC (permalink / raw)
To: Paul E. McKenney
Cc: linuxppc-dev, Christoph Lameter, linux-kernel, Paul E. McKenney
In-Reply-To: <20120503001433.GO2450@linux.vnet.ibm.com>
[-- Attachment #1: Type: TEXT/PLAIN, Size: 2760 bytes --]
On Wed, 2 May 2012, Paul E. McKenney wrote:
> On Wed, May 02, 2012 at 03:54:24PM -0700, Hugh Dickins wrote:
> > On Wed, May 2, 2012 at 2:54 PM, Paul E. McKenney
> > <paulmck@linux.vnet.ibm.com> wrote:
> > > On Thu, May 03, 2012 at 07:20:15AM +1000, Benjamin Herrenschmidt wrote:
> > >> On Wed, 2012-05-02 at 13:25 -0700, Hugh Dickins wrote:
> > >> > Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> > >> > __rcu_read_unlock() are not safe to be using __this_cpu operations,
> > >> > the cpu may change in between the rmw's read and write: they should
> > >> > be using this_cpu operations (or, I put preempt_disable/enable in the
> > >> > __rcu_read_unlock below). __this_cpus there work out fine on x86,
> > >> > which was given good instructions to use; but not so well on PowerPC.
> > >> >
> > >> > I've been running successfully for an hour now with the patch below;
> > >> > but I expect you'll want to consider the tradeoffs, and may choose a
> > >> > different solution.
> > >>
> > >> Didn't Linus recently rant about these __this_cpu vs this_cpu nonsense ?
> > >>
> > >> I thought that was going out..
> > >
> > > Linus did rant about __raw_get_cpu_var() because it cannot use the x86
> > > %fs segement overrides a bit more than a month ago. The __this_cpu
> > > stuff is useful if you have preemption disabled -- avoids the extra
> > > layer of preempt_disable().
> > >
> > > Or was this a different rant?
> >
> > https://lkml.org/lkml/2011/11/29/321
> >
> > I think it ended up with Christoph removing the more egregious
> > variants, but this_cpu_that and __this_cpu_the_other remaining.
>
> Ah, thank you for the pointer.
>
> It would be nice to have the CPU transparency of x86 on other
> architectures, but from what I can see, that would require dedicating
> a register to this purpose -- and even then requires that the arch
> have indexed addressing modes. There are some other approaches, for
> example, having __this_cpu_that() be located at a special address that
> the scheduler treated as implicitly preempt_disable(). Or I suppose
> that the arch-specific trap-handling code could fake it. A little
> bit messy, but the ability to access a given CPU's per-CPU variable
> while running on that CPU does appear to have at least a couple of
> uses -- inlining RCU and also making preempt_disable() use per-CPU
> variables.
>
> In any case, I must confess that I feel quite silly about my series
> of patches. I have reverted them aside from a couple that did useful
> optimizations, and they should show up in -next shortly.
A wee bit sad, but thank you - it was an experiment worth trying,
and perhaps there will be reason to come back to it future.
Hugh
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Paul E. McKenney @ 2012-05-03 0:14 UTC (permalink / raw)
To: Hugh Dickins
Cc: linuxppc-dev, Christoph Lameter, linux-kernel, Paul E. McKenney
In-Reply-To: <CANsGZ6bw4JwMRgUriooDvFB=g-HkpyL8XJa9s47RPfOEPCMcpw@mail.gmail.com>
On Wed, May 02, 2012 at 03:54:24PM -0700, Hugh Dickins wrote:
> On Wed, May 2, 2012 at 2:54 PM, Paul E. McKenney
> <paulmck@linux.vnet.ibm.com> wrote:
> > On Thu, May 03, 2012 at 07:20:15AM +1000, Benjamin Herrenschmidt wrote:
> >> On Wed, 2012-05-02 at 13:25 -0700, Hugh Dickins wrote:
> >> > Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> >> > __rcu_read_unlock() are not safe to be using __this_cpu operations,
> >> > the cpu may change in between the rmw's read and write: they should
> >> > be using this_cpu operations (or, I put preempt_disable/enable in the
> >> > __rcu_read_unlock below). __this_cpus there work out fine on x86,
> >> > which was given good instructions to use; but not so well on PowerPC.
> >> >
> >> > I've been running successfully for an hour now with the patch below;
> >> > but I expect you'll want to consider the tradeoffs, and may choose a
> >> > different solution.
> >>
> >> Didn't Linus recently rant about these __this_cpu vs this_cpu nonsense ?
> >>
> >> I thought that was going out..
> >
> > Linus did rant about __raw_get_cpu_var() because it cannot use the x86
> > %fs segement overrides a bit more than a month ago. The __this_cpu
> > stuff is useful if you have preemption disabled -- avoids the extra
> > layer of preempt_disable().
> >
> > Or was this a different rant?
>
> https://lkml.org/lkml/2011/11/29/321
>
> I think it ended up with Christoph removing the more egregious
> variants, but this_cpu_that and __this_cpu_the_other remaining.
Ah, thank you for the pointer.
It would be nice to have the CPU transparency of x86 on other
architectures, but from what I can see, that would require dedicating
a register to this purpose -- and even then requires that the arch
have indexed addressing modes. There are some other approaches, for
example, having __this_cpu_that() be located at a special address that
the scheduler treated as implicitly preempt_disable(). Or I suppose
that the arch-specific trap-handling code could fake it. A little
bit messy, but the ability to access a given CPU's per-CPU variable
while running on that CPU does appear to have at least a couple of
uses -- inlining RCU and also making preempt_disable() use per-CPU
variables.
In any case, I must confess that I feel quite silly about my series
of patches. I have reverted them aside from a couple that did useful
optimizations, and they should show up in -next shortly.
Thanx, Paul
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Hugh Dickins @ 2012-05-02 22:54 UTC (permalink / raw)
To: Paul E. McKenney
Cc: linuxppc-dev, Christoph Lameter, linux-kernel, Paul E. McKenney
In-Reply-To: <20120502215406.GL2450@linux.vnet.ibm.com>
On Wed, May 2, 2012 at 2:54 PM, Paul E. McKenney
<paulmck@linux.vnet.ibm.com> wrote:
> On Thu, May 03, 2012 at 07:20:15AM +1000, Benjamin Herrenschmidt wrote:
>> On Wed, 2012-05-02 at 13:25 -0700, Hugh Dickins wrote:
>> > Got it at last. =C2=A0Embarrassingly obvious. =C2=A0__rcu_read_lock() =
and
>> > __rcu_read_unlock() are not safe to be using __this_cpu operations,
>> > the cpu may change in between the rmw's read and write: they should
>> > be using this_cpu operations (or, I put preempt_disable/enable in the
>> > __rcu_read_unlock below). =C2=A0__this_cpus there work out fine on x86=
,
>> > which was given good instructions to use; but not so well on PowerPC.
>> >
>> > I've been running successfully for an hour now with the patch below;
>> > but I expect you'll want to consider the tradeoffs, and may choose a
>> > different solution.
>>
>> Didn't Linus recently rant about these __this_cpu vs this_cpu nonsense ?
>>
>> I thought that was going out..
>
> Linus did rant about __raw_get_cpu_var() because it cannot use the x86
> %fs segement overrides a bit more than a month ago. =C2=A0The __this_cpu
> stuff is useful if you have preemption disabled -- avoids the extra
> layer of preempt_disable().
>
> Or was this a different rant?
https://lkml.org/lkml/2011/11/29/321
I think it ended up with Christoph removing the more egregious
variants, but this_cpu_that and __this_cpu_the_other remaining.
Hugh
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Paul E. McKenney @ 2012-05-02 21:54 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Paul E. McKenney, linuxppc-dev, Hugh Dickins, linux-kernel
In-Reply-To: <1335993615.4088.1.camel@pasglop>
On Thu, May 03, 2012 at 07:20:15AM +1000, Benjamin Herrenschmidt wrote:
> On Wed, 2012-05-02 at 13:25 -0700, Hugh Dickins wrote:
> > Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> > __rcu_read_unlock() are not safe to be using __this_cpu operations,
> > the cpu may change in between the rmw's read and write: they should
> > be using this_cpu operations (or, I put preempt_disable/enable in the
> > __rcu_read_unlock below). __this_cpus there work out fine on x86,
> > which was given good instructions to use; but not so well on PowerPC.
> >
> > I've been running successfully for an hour now with the patch below;
> > but I expect you'll want to consider the tradeoffs, and may choose a
> > different solution.
>
> Didn't Linus recently rant about these __this_cpu vs this_cpu nonsense ?
>
> I thought that was going out..
Linus did rant about __raw_get_cpu_var() because it cannot use the x86
%fs segement overrides a bit more than a month ago. The __this_cpu
stuff is useful if you have preemption disabled -- avoids the extra
layer of preempt_disable().
Or was this a different rant?
Thanx, Paul
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Paul E. McKenney @ 2012-05-02 21:36 UTC (permalink / raw)
To: Hugh Dickins; +Cc: linuxppc-dev, linux-kernel, Paul E. McKenney
In-Reply-To: <20120502213238.GA12153@linux.vnet.ibm.com>
On Wed, May 02, 2012 at 02:32:38PM -0700, Paul E. McKenney wrote:
> On Wed, May 02, 2012 at 01:49:54PM -0700, Paul E. McKenney wrote:
> > On Wed, May 02, 2012 at 01:25:30PM -0700, Hugh Dickins wrote:
> > > On Tue, 1 May 2012, Paul E. McKenney wrote:
> > > > > > > > On Mon, 2012-04-30 at 15:37 -0700, Hugh Dickins wrote:
> > > > > > > > >
> > > > > > > > > BUG: sleeping function called from invalid context at include/linux/pagemap.h:354
> > > > > > > > > in_atomic(): 0, irqs_disabled(): 0, pid: 6886, name: cc1
> > > > > > > > > Call Trace:
> > > > > > > > > [c0000001a99f78e0] [c00000000000f34c] .show_stack+0x6c/0x16c (unreliable)
> > > > > > > > > [c0000001a99f7990] [c000000000077b40] .__might_sleep+0x11c/0x134
> > > > > > > > > [c0000001a99f7a10] [c0000000000c6228] .filemap_fault+0x1fc/0x494
> > > > > > > > > [c0000001a99f7af0] [c0000000000e7c9c] .__do_fault+0x120/0x684
> > > > > > > > > [c0000001a99f7c00] [c000000000025790] .do_page_fault+0x458/0x664
> > > > > > > > > [c0000001a99f7e30] [c000000000005868] handle_page_fault+0x10/0x30
> > >
> > > Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> > > __rcu_read_unlock() are not safe to be using __this_cpu operations,
> > > the cpu may change in between the rmw's read and write: they should
> > > be using this_cpu operations (or, I put preempt_disable/enable in the
> > > __rcu_read_unlock below). __this_cpus there work out fine on x86,
> > > which was given good instructions to use; but not so well on PowerPC.
> >
> > Thank you very much for tracking this down!!!
> >
> > > I've been running successfully for an hour now with the patch below;
> > > but I expect you'll want to consider the tradeoffs, and may choose a
> > > different solution.
> >
> > The thing that puzzles me about this is that the normal path through
> > the scheduler would save and restore these per-CPU variables to and
> > from the task structure. There must be a sneak path through the
> > scheduler that I failed to account for.
>
> Sigh... I am slow today, I guess. The preemption could of course
> happen between the time that the task calculated the address of the
> per-CPU variable and the time that it modified it. If this happens,
> we are modifying some other CPU's per-CPU variable.
>
> Given that Linus saw no performance benefit from this patchset, I am
> strongly tempted to just drop this inlinable-__rcu_read_lock() series
> at this point.
>
> I suppose that the other option is to move preempt_count() to a
> per-CPU variable, then use the space in the task_info struct.
> But that didn't generate anywhere near as good of code...
But preempt_count() would suffer exactly the same problem. The address
is calculated, the task moves to some other CPU, and then the task
is messing with some other CPU's preemption counter. Blech.
Thanx, Paul
> > But given your good work, this should be easy for me to chase down
> > even on my x86-based laptop -- just convert from __this_cpu_inc() to a
> > read-inc-delay-write sequence. And check that the underlying variable
> > didn't change in the meantime. And dump an ftrace if it did. ;-)
> >
> > Thank you again, Hugh!
> >
> > Thanx, Paul
> >
> > > Hugh
> > >
> > > --- 3.4-rc4-next-20120427/include/linux/rcupdate.h 2012-04-28 09:26:38.000000000 -0700
> > > +++ testing/include/linux/rcupdate.h 2012-05-02 11:46:06.000000000 -0700
> > > @@ -159,7 +159,7 @@ DECLARE_PER_CPU(struct task_struct *, rc
> > > */
> > > static inline void __rcu_read_lock(void)
> > > {
> > > - __this_cpu_inc(rcu_read_lock_nesting);
> > > + this_cpu_inc(rcu_read_lock_nesting);
> > > barrier(); /* Keep code within RCU read-side critical section. */
> > > }
> > >
> > > --- 3.4-rc4-next-20120427/kernel/rcupdate.c 2012-04-28 09:26:40.000000000 -0700
> > > +++ testing/kernel/rcupdate.c 2012-05-02 11:44:13.000000000 -0700
> > > @@ -72,6 +72,7 @@ DEFINE_PER_CPU(struct task_struct *, rcu
> > > */
> > > void __rcu_read_unlock(void)
> > > {
> > > + preempt_disable();
> > > if (__this_cpu_read(rcu_read_lock_nesting) != 1)
> > > __this_cpu_dec(rcu_read_lock_nesting);
> > > else {
> > > @@ -83,13 +84,14 @@ void __rcu_read_unlock(void)
> > > barrier(); /* ->rcu_read_unlock_special load before assign */
> > > __this_cpu_write(rcu_read_lock_nesting, 0);
> > > }
> > > -#ifdef CONFIG_PROVE_LOCKING
> > > +#if 1 /* CONFIG_PROVE_LOCKING */
> > > {
> > > int rln = __this_cpu_read(rcu_read_lock_nesting);
> > >
> > > - WARN_ON_ONCE(rln < 0 && rln > INT_MIN / 2);
> > > + BUG_ON(rln < 0 && rln > INT_MIN / 2);
> > > }
> > > #endif /* #ifdef CONFIG_PROVE_LOCKING */
> > > + preempt_enable();
> > > }
> > > EXPORT_SYMBOL_GPL(__rcu_read_unlock);
> > >
> > > --- 3.4-rc4-next-20120427/kernel/sched/core.c 2012-04-28 09:26:40.000000000 -0700
> > > +++ testing/kernel/sched/core.c 2012-05-01 22:40:46.000000000 -0700
> > > @@ -2024,7 +2024,7 @@ asmlinkage void schedule_tail(struct tas
> > > {
> > > struct rq *rq = this_rq();
> > >
> > > - rcu_switch_from(prev);
> > > + /* rcu_switch_from(prev); */
> > > rcu_switch_to();
> > > finish_task_switch(rq, prev);
> > >
> > > @@ -7093,6 +7093,10 @@ void __might_sleep(const char *file, int
> > > "BUG: sleeping function called from invalid context at %s:%d\n",
> > > file, line);
> > > printk(KERN_ERR
> > > + "cpu=%d preempt_count=%x preempt_offset=%x rcu_nesting=%x nesting_save=%x\n",
> > > + raw_smp_processor_id(), preempt_count(), preempt_offset,
> > > + rcu_preempt_depth(), current->rcu_read_lock_nesting_save);
> > > + printk(KERN_ERR
> > > "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
> > > in_atomic(), irqs_disabled(),
> > > current->pid, current->comm);
> > > --
> > > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> > > the body of a message to majordomo@vger.kernel.org
> > > More majordomo info at http://vger.kernel.org/majordomo-info.html
> > > Please read the FAQ at http://www.tux.org/lkml/
> > >
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Paul E. McKenney @ 2012-05-02 21:32 UTC (permalink / raw)
To: Hugh Dickins; +Cc: linuxppc-dev, linux-kernel, Paul E. McKenney
In-Reply-To: <20120502204954.GK2450@linux.vnet.ibm.com>
On Wed, May 02, 2012 at 01:49:54PM -0700, Paul E. McKenney wrote:
> On Wed, May 02, 2012 at 01:25:30PM -0700, Hugh Dickins wrote:
> > On Tue, 1 May 2012, Paul E. McKenney wrote:
> > > > > > > On Mon, 2012-04-30 at 15:37 -0700, Hugh Dickins wrote:
> > > > > > > >
> > > > > > > > BUG: sleeping function called from invalid context at include/linux/pagemap.h:354
> > > > > > > > in_atomic(): 0, irqs_disabled(): 0, pid: 6886, name: cc1
> > > > > > > > Call Trace:
> > > > > > > > [c0000001a99f78e0] [c00000000000f34c] .show_stack+0x6c/0x16c (unreliable)
> > > > > > > > [c0000001a99f7990] [c000000000077b40] .__might_sleep+0x11c/0x134
> > > > > > > > [c0000001a99f7a10] [c0000000000c6228] .filemap_fault+0x1fc/0x494
> > > > > > > > [c0000001a99f7af0] [c0000000000e7c9c] .__do_fault+0x120/0x684
> > > > > > > > [c0000001a99f7c00] [c000000000025790] .do_page_fault+0x458/0x664
> > > > > > > > [c0000001a99f7e30] [c000000000005868] handle_page_fault+0x10/0x30
> >
> > Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> > __rcu_read_unlock() are not safe to be using __this_cpu operations,
> > the cpu may change in between the rmw's read and write: they should
> > be using this_cpu operations (or, I put preempt_disable/enable in the
> > __rcu_read_unlock below). __this_cpus there work out fine on x86,
> > which was given good instructions to use; but not so well on PowerPC.
>
> Thank you very much for tracking this down!!!
>
> > I've been running successfully for an hour now with the patch below;
> > but I expect you'll want to consider the tradeoffs, and may choose a
> > different solution.
>
> The thing that puzzles me about this is that the normal path through
> the scheduler would save and restore these per-CPU variables to and
> from the task structure. There must be a sneak path through the
> scheduler that I failed to account for.
Sigh... I am slow today, I guess. The preemption could of course
happen between the time that the task calculated the address of the
per-CPU variable and the time that it modified it. If this happens,
we are modifying some other CPU's per-CPU variable.
Given that Linus saw no performance benefit from this patchset, I am
strongly tempted to just drop this inlinable-__rcu_read_lock() series
at this point.
I suppose that the other option is to move preempt_count() to a
per-CPU variable, then use the space in the task_info struct.
But that didn't generate anywhere near as good of code...
Thanx, Paul
> But given your good work, this should be easy for me to chase down
> even on my x86-based laptop -- just convert from __this_cpu_inc() to a
> read-inc-delay-write sequence. And check that the underlying variable
> didn't change in the meantime. And dump an ftrace if it did. ;-)
>
> Thank you again, Hugh!
>
> Thanx, Paul
>
> > Hugh
> >
> > --- 3.4-rc4-next-20120427/include/linux/rcupdate.h 2012-04-28 09:26:38.000000000 -0700
> > +++ testing/include/linux/rcupdate.h 2012-05-02 11:46:06.000000000 -0700
> > @@ -159,7 +159,7 @@ DECLARE_PER_CPU(struct task_struct *, rc
> > */
> > static inline void __rcu_read_lock(void)
> > {
> > - __this_cpu_inc(rcu_read_lock_nesting);
> > + this_cpu_inc(rcu_read_lock_nesting);
> > barrier(); /* Keep code within RCU read-side critical section. */
> > }
> >
> > --- 3.4-rc4-next-20120427/kernel/rcupdate.c 2012-04-28 09:26:40.000000000 -0700
> > +++ testing/kernel/rcupdate.c 2012-05-02 11:44:13.000000000 -0700
> > @@ -72,6 +72,7 @@ DEFINE_PER_CPU(struct task_struct *, rcu
> > */
> > void __rcu_read_unlock(void)
> > {
> > + preempt_disable();
> > if (__this_cpu_read(rcu_read_lock_nesting) != 1)
> > __this_cpu_dec(rcu_read_lock_nesting);
> > else {
> > @@ -83,13 +84,14 @@ void __rcu_read_unlock(void)
> > barrier(); /* ->rcu_read_unlock_special load before assign */
> > __this_cpu_write(rcu_read_lock_nesting, 0);
> > }
> > -#ifdef CONFIG_PROVE_LOCKING
> > +#if 1 /* CONFIG_PROVE_LOCKING */
> > {
> > int rln = __this_cpu_read(rcu_read_lock_nesting);
> >
> > - WARN_ON_ONCE(rln < 0 && rln > INT_MIN / 2);
> > + BUG_ON(rln < 0 && rln > INT_MIN / 2);
> > }
> > #endif /* #ifdef CONFIG_PROVE_LOCKING */
> > + preempt_enable();
> > }
> > EXPORT_SYMBOL_GPL(__rcu_read_unlock);
> >
> > --- 3.4-rc4-next-20120427/kernel/sched/core.c 2012-04-28 09:26:40.000000000 -0700
> > +++ testing/kernel/sched/core.c 2012-05-01 22:40:46.000000000 -0700
> > @@ -2024,7 +2024,7 @@ asmlinkage void schedule_tail(struct tas
> > {
> > struct rq *rq = this_rq();
> >
> > - rcu_switch_from(prev);
> > + /* rcu_switch_from(prev); */
> > rcu_switch_to();
> > finish_task_switch(rq, prev);
> >
> > @@ -7093,6 +7093,10 @@ void __might_sleep(const char *file, int
> > "BUG: sleeping function called from invalid context at %s:%d\n",
> > file, line);
> > printk(KERN_ERR
> > + "cpu=%d preempt_count=%x preempt_offset=%x rcu_nesting=%x nesting_save=%x\n",
> > + raw_smp_processor_id(), preempt_count(), preempt_offset,
> > + rcu_preempt_depth(), current->rcu_read_lock_nesting_save);
> > + printk(KERN_ERR
> > "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
> > in_atomic(), irqs_disabled(),
> > current->pid, current->comm);
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
> > Please read the FAQ at http://www.tux.org/lkml/
> >
^ permalink raw reply
* Re: linux-next ppc64: RCU mods cause __might_sleep BUGs
From: Benjamin Herrenschmidt @ 2012-05-02 21:20 UTC (permalink / raw)
To: Hugh Dickins
Cc: Paul E. McKenney, Paul E. McKenney, linuxppc-dev, linux-kernel
In-Reply-To: <alpine.LSU.2.00.1205021324430.24246@eggly.anvils>
On Wed, 2012-05-02 at 13:25 -0700, Hugh Dickins wrote:
> Got it at last. Embarrassingly obvious. __rcu_read_lock() and
> __rcu_read_unlock() are not safe to be using __this_cpu operations,
> the cpu may change in between the rmw's read and write: they should
> be using this_cpu operations (or, I put preempt_disable/enable in the
> __rcu_read_unlock below). __this_cpus there work out fine on x86,
> which was given good instructions to use; but not so well on PowerPC.
>
> I've been running successfully for an hour now with the patch below;
> but I expect you'll want to consider the tradeoffs, and may choose a
> different solution.
Didn't Linus recently rant about these __this_cpu vs this_cpu nonsense ?
I thought that was going out..
Cheers,
Ben.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox