static int msm_hsusb_reset(struct msm_hcd *mhcd) { struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct msm_usb_host_platform_data *pdata; unsigned long timeout; int ret; if (mhcd->alt_core_clk) clk_prepare_enable(mhcd->alt_core_clk); ret = msm_ehci_phy_reset(mhcd); if (ret) { dev_err(mhcd->dev, "phy_reset failed\n"); return ret; } writel_relaxed(USBCMD_RESET, USB_USBCMD); timeout = jiffies + usecs_to_jiffies(LINK_RESET_TIMEOUT_USEC); while (readl_relaxed(USB_USBCMD) & USBCMD_RESET) { if (time_after(jiffies, timeout)) return -ETIMEDOUT; udelay(1); } /* select ULPI phy */ writel_relaxed(0x80000000, USB_PORTSC); pdata = mhcd->dev->platform_data; if (pdata && pdata->use_sec_phy) writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16), USB_PHY_CTRL2); /* Reset USB PHY after performing USB Link RESET */ msm_usb_phy_reset(mhcd); msleep(100); writel_relaxed(0x0, USB_AHBBURST); writel_relaxed(0x08, USB_AHBMODE); /* Ensure that RESET operation is completed before turning off clock */ mb(); if (mhcd->alt_core_clk) clk_disable_unprepare(mhcd->alt_core_clk); /*rising edge interrupts with Dp rise and fall enabled*/ msm_ulpi_write(mhcd, ULPI_INT_DP, ULPI_USB_INT_EN_RISE); msm_ulpi_write(mhcd, ULPI_INT_DP, ULPI_USB_INT_EN_FALL); /*Clear the PHY interrupts by reading the PHY interrupt latch register*/ msm_ulpi_read(mhcd, ULPI_USB_INT_LATCH); return 0; }
static ssize_t debug_write_phy_data(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct msm_hcd *mhcd = file->private_data; char kbuf[10]; u32 data = 0; memset(kbuf, 0, 10); if (copy_from_user(kbuf, buf, min_t(size_t, sizeof(kbuf) - 1, count))) return -EFAULT; if (sscanf(kbuf, "%x", &data) != 1) return -EINVAL; pm_runtime_get(mhcd->dev); if (msm_ulpi_write(mhcd, data, addr) < 0) { dev_err(mhcd->dev, "%s(): ulpi write timeout\n", __func__); return -ETIMEDOUT; } pm_runtime_put(mhcd->dev); return count; }
static int msm_ehci_phy_reset(struct msm_hcd *mhcd) { struct usb_hcd *hcd = mhcd_to_hcd(mhcd); u32 val; int ret; int retries; ret = msm_ehci_link_clk_reset(mhcd, 1); if (ret) return ret; udelay(1); ret = msm_ehci_link_clk_reset(mhcd, 0); if (ret) return ret; val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); for (retries = 3; retries > 0; retries--) { ret = msm_ulpi_write(mhcd, ULPI_FUNC_CTRL_SUSPENDM, ULPI_CLR(ULPI_FUNC_CTRL)); if (!ret) break; } if (!retries) return -ETIMEDOUT; /* Wakeup the PHY with a reg-access for calibration */ for (retries = 3; retries > 0; retries--) { ret = msm_ulpi_read(mhcd, ULPI_DEBUG); if (ret != -ETIMEDOUT) break; } if (!retries) return -ETIMEDOUT; dev_info(mhcd->dev, "phy_reset: success\n"); return 0; }
static int msm_ehci_resume(struct msm_hcd *mhcd) { struct usb_hcd *hcd = mhcd_to_hcd(mhcd); unsigned long timeout; unsigned temp; unsigned long flags; u32 func_ctrl; const struct msm_usb_host_platform_data *pdata; if (!atomic_read(&mhcd->in_lpm)) { dev_dbg(mhcd->dev, "%s called in !in_lpm\n", __func__); return 0; } /* Handles race with Async interrupt */ disable_irq(hcd->irq); if (mhcd->pmic_gpio_dp_irq_enabled) { disable_irq_wake(mhcd->pmic_gpio_dp_irq); disable_irq_nosync(mhcd->pmic_gpio_dp_irq); mhcd->pmic_gpio_dp_irq_enabled = 0; } spin_lock_irqsave(&mhcd->wakeup_lock, flags); if (mhcd->async_irq_enabled) { disable_irq_wake(mhcd->async_irq); disable_irq_nosync(mhcd->async_irq); mhcd->async_irq_enabled = 0; } if (mhcd->wakeup_irq) { if (mhcd->wakeup_irq_enabled) { disable_irq_wake(mhcd->wakeup_irq); disable_irq_nosync(mhcd->wakeup_irq); mhcd->wakeup_irq_enabled = 0; } } spin_unlock_irqrestore(&mhcd->wakeup_lock, flags); pm_stay_awake(mhcd->dev); /* Vote for TCXO when waking up the phy */ if (mhcd->xo_clk) clk_prepare_enable(mhcd->xo_clk); clk_prepare_enable(mhcd->core_clk); clk_prepare_enable(mhcd->iface_clk); msm_ehci_config_vddcx(mhcd, 1); temp = readl_relaxed(USB_USBCMD); temp &= ~ASYNC_INTR_CTRL; temp &= ~ULPI_STP_CTRL; writel_relaxed(temp, USB_USBCMD); if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) goto skip_phy_resume; temp = readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD; writel_relaxed(temp, USB_PORTSC); timeout = jiffies + usecs_to_jiffies(PHY_RESUME_TIMEOUT_USEC); while ((readl_relaxed(USB_PORTSC) & PORTSC_PHCD) || !(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE)) { if (time_after(jiffies, timeout)) { /*This is a fatal error. Reset the link and PHY*/ dev_err(mhcd->dev, "Unable to resume USB. Resetting the h/w\n"); msm_hsusb_reset(mhcd); break; } udelay(1); } skip_phy_resume: pdata = mhcd->dev->platform_data; if (pdata && pdata->is_uicc) { /* put the controller in normal mode */ func_ctrl = msm_ulpi_read(mhcd, ULPI_FUNC_CTRL); func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; msm_ulpi_write(mhcd, func_ctrl, ULPI_FUNC_CTRL); } set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); usb_hcd_resume_root_hub(hcd); atomic_set(&mhcd->in_lpm, 0); if (atomic_read(&mhcd->pm_usage_cnt)) { atomic_set(&mhcd->pm_usage_cnt, 0); pm_runtime_put_noidle(mhcd->dev); } enable_irq(hcd->irq); dev_info(mhcd->dev, "EHCI USB exited from low power mode\n"); return 0; }
static int msm_ehci_suspend(struct msm_hcd *mhcd) { struct usb_hcd *hcd = mhcd_to_hcd(mhcd); unsigned long timeout; u32 portsc; const struct msm_usb_host_platform_data *pdata; u32 func_ctrl; if (atomic_read(&mhcd->in_lpm)) { dev_dbg(mhcd->dev, "%s called in lpm\n", __func__); return 0; } disable_irq(hcd->irq); /* make sure we don't race against a remote wakeup */ if (test_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags) || readl_relaxed(USB_PORTSC) & PORT_RESUME) { dev_dbg(mhcd->dev, "wakeup pending, aborting suspend\n"); enable_irq(hcd->irq); return -EBUSY; } pdata = mhcd->dev->platform_data; if (pdata && pdata->is_uicc) { /* put the controller in non-driving mode */ func_ctrl = msm_ulpi_read(mhcd, ULPI_FUNC_CTRL); func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; msm_ulpi_write(mhcd, func_ctrl, ULPI_FUNC_CTRL); } /* If port is enabled wait 5ms for PHCD to come up. Reset PHY * and link if it fails to do so. * If port is not enabled set the PHCD bit and poll for it to * come up with in 500ms. Reset phy and link if it fails to do so. */ portsc = readl_relaxed(USB_PORTSC); if (portsc & PORT_PE) { usleep_range(5000, 5000); if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) { dev_err(mhcd->dev, "Unable to suspend PHY. portsc: %8x\n", readl_relaxed(USB_PORTSC)); goto reset_phy_and_link; } } else { writel_relaxed(portsc | PORTSC_PHCD, USB_PORTSC); timeout = jiffies + msecs_to_jiffies(PHY_SUSP_TIMEOUT_MSEC); while (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) { if (time_after(jiffies, timeout)) { dev_err(mhcd->dev, "Unable to suspend PHY. portsc: %8x\n", readl_relaxed(USB_PORTSC)); goto reset_phy_and_link; } usleep_range(10000, 10000); } } /* * PHY has capability to generate interrupt asynchronously in low * power mode (LPM). This interrupt is level triggered. So USB IRQ * line must be disabled till async interrupt enable bit is cleared * in USBCMD register. Assert STP (ULPI interface STOP signal) to * block data communication from PHY. Enable asynchronous interrupt * only when wakeup gpio IRQ is not present. */ if (mhcd->wakeup_irq) writel_relaxed(readl_relaxed(USB_USBCMD) | ULPI_STP_CTRL, USB_USBCMD); else writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); /* * Ensure that hardware is put in low power mode before * clocks are turned OFF and VDD is allowed to minimize. */ mb(); clk_disable_unprepare(mhcd->iface_clk); clk_disable_unprepare(mhcd->core_clk); /* usb phy does not require TCXO clock, hence vote for TCXO disable */ if (mhcd->xo_clk) clk_disable_unprepare(mhcd->xo_clk); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); msm_ehci_config_vddcx(mhcd, 0); atomic_set(&mhcd->in_lpm, 1); enable_irq(hcd->irq); if (mhcd->wakeup_irq) { mhcd->wakeup_irq_enabled = 1; enable_irq_wake(mhcd->wakeup_irq); enable_irq(mhcd->wakeup_irq); } if (mhcd->pmic_gpio_dp_irq) { mhcd->pmic_gpio_dp_irq_enabled = 1; enable_irq_wake(mhcd->pmic_gpio_dp_irq); enable_irq(mhcd->pmic_gpio_dp_irq); } if (mhcd->async_irq) { mhcd->async_irq_enabled = 1; enable_irq_wake(mhcd->async_irq); enable_irq(mhcd->async_irq); } pm_relax(mhcd->dev); dev_info(mhcd->dev, "EHCI USB in low power mode\n"); return 0; reset_phy_and_link: schedule_work(&mhcd->phy_susp_fail_work); return -ETIMEDOUT; }