summaryrefslogtreecommitdiff
path: root/drivers/usb/cdns3/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/cdns3/core.c')
-rw-r--r--drivers/usb/cdns3/core.c1312
1 files changed, 890 insertions, 422 deletions
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index c2123ef8d8a3..2065c2cb1b1d 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -1,89 +1,289 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Cadence USBSS DRD Driver.
+/**
+ * core.c - Cadence USB3 DRD Controller Core file
+ *
+ * Copyright 2017-2019 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
*
- * Copyright (C) 2018-2019 Cadence.
- * Copyright (C) 2017-2018 NXP
- * Copyright (C) 2019 Texas Instruments
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
*
- * Author: Peter Chen <peter.chen@nxp.com>
- * Pawel Laszczak <pawell@cadence.com>
- * Roger Quadros <rogerq@ti.com>
+ * 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 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/>.
*/
-#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/usb/of.h>
+#include <linux/usb/phy.h>
+#include <linux/extcon.h>
#include <linux/pm_runtime.h>
-#include "gadget.h"
+#include "cdns3-nxp-reg-def.h"
#include "core.h"
#include "host-export.h"
#include "gadget-export.h"
-#include "drd.h"
-
-static int cdns3_idle_init(struct cdns3 *cdns);
-
-static inline
-struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
-{
- WARN_ON(!cdns->roles[cdns->role]);
- return cdns->roles[cdns->role];
-}
-static int cdns3_role_start(struct cdns3 *cdns, enum usb_role role)
+/**
+ * cdns3_handshake - spin reading until handshake completes or fails
+ * @ptr: address of device controller register to be read
+ * @mask: bits to look at in result of read
+ * @done: value of those bits when handshake succeeds
+ * @usec: timeout in microseconds
+ *
+ * Returns negative errno, or zero on success
+ *
+ * Success happens when the "mask" bits have the specified value (hardware
+ * handshake done). There are two failure modes: "usec" have passed (major
+ * hardware flakeout), or the register reads as all-ones (hardware removed).
+ */
+int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
{
- int ret;
-
- if (WARN_ON(role > USB_ROLE_DEVICE))
- return 0;
+ u32 result;
- mutex_lock(&cdns->mutex);
- cdns->role = role;
- mutex_unlock(&cdns->mutex);
+ do {
+ result = readl(ptr);
+ if (result == ~(u32)0) /* card removed */
+ return -ENODEV;
- if (!cdns->roles[role])
- return -ENXIO;
+ result &= mask;
+ if (result == done)
+ return 0;
- if (cdns->roles[role]->state == CDNS3_ROLE_STATE_ACTIVE)
- return 0;
+ udelay(1);
+ usec--;
+ } while (usec > 0);
- mutex_lock(&cdns->mutex);
- ret = cdns->roles[role]->start(cdns);
- if (!ret)
- cdns->roles[role]->state = CDNS3_ROLE_STATE_ACTIVE;
- mutex_unlock(&cdns->mutex);
+ return -ETIMEDOUT;
+}
- return ret;
+static void cdns3_usb_phy_init(void __iomem *regs)
+{
+ u32 value;
+
+ pr_debug("begin of %s\n", __func__);
+
+ writel(0x0830, regs + PHY_PMA_CMN_CTRL1);
+ writel(0x10, regs + TB_ADDR_CMN_DIAG_HSCLK_SEL);
+ writel(0x00F0, regs + TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR);
+ writel(0x0018, regs + TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR);
+ writel(0x00D0, regs + TB_ADDR_CMN_PLL0_INTDIV);
+ writel(0x4aaa, regs + TB_ADDR_CMN_PLL0_FRACDIV);
+ writel(0x0034, regs + TB_ADDR_CMN_PLL0_HIGH_THR);
+ writel(0x1ee, regs + TB_ADDR_CMN_PLL0_SS_CTRL1);
+ writel(0x7F03, regs + TB_ADDR_CMN_PLL0_SS_CTRL2);
+ writel(0x0020, regs + TB_ADDR_CMN_PLL0_DSM_DIAG);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_OVRD);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD);
+ writel(0x0007, regs + TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE);
+ writel(0x0027, regs + TB_ADDR_CMN_DIAG_PLL0_CP_TUNE);
+ writel(0x0008, regs + TB_ADDR_CMN_DIAG_PLL0_LF_PROG);
+ writel(0x0022, regs + TB_ADDR_CMN_DIAG_PLL0_TEST_MODE);
+ writel(0x000a, regs + TB_ADDR_CMN_PSM_CLK_CTRL);
+ writel(0x139, regs + TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR);
+ writel(0xbefc, regs + TB_ADDR_XCVR_PSM_RCTRL);
+
+ writel(0x7799, regs + TB_ADDR_TX_PSC_A0);
+ writel(0x7798, regs + TB_ADDR_TX_PSC_A1);
+ writel(0x509b, regs + TB_ADDR_TX_PSC_A2);
+ writel(0x3, regs + TB_ADDR_TX_DIAG_ECTRL_OVRD);
+ writel(0x509b, regs + TB_ADDR_TX_PSC_A3);
+ writel(0x2090, regs + TB_ADDR_TX_PSC_CAL);
+ writel(0x2090, regs + TB_ADDR_TX_PSC_RDY);
+
+ writel(0xA6FD, regs + TB_ADDR_RX_PSC_A0);
+ writel(0xA6FD, regs + TB_ADDR_RX_PSC_A1);
+ writel(0xA410, regs + TB_ADDR_RX_PSC_A2);
+ writel(0x2410, regs + TB_ADDR_RX_PSC_A3);
+
+ writel(0x23FF, regs + TB_ADDR_RX_PSC_CAL);
+ writel(0x2010, regs + TB_ADDR_RX_PSC_RDY);
+
+ writel(0x0020, regs + TB_ADDR_TX_TXCC_MGNLS_MULT_000);
+ writel(0x00ff, regs + TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY);
+ writel(0x0002, regs + TB_ADDR_RX_SLC_CU_ITER_TMR);
+ writel(0x0013, regs + TB_ADDR_RX_SIGDET_HL_FILT_TMR);
+ writel(0x0000, regs + TB_ADDR_RX_SAMP_DAC_CTRL);
+ writel(0x1004, regs + TB_ADDR_RX_DIAG_SIGDET_TUNE);
+ writel(0x4041, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE2);
+ writel(0x0480, regs + TB_ADDR_RX_DIAG_BS_TM);
+ writel(0x8006, regs + TB_ADDR_RX_DIAG_DFE_CTRL1);
+ writel(0x003f, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM4);
+ writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_E_TRIM0);
+ writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_IQ_TRIM0);
+ writel(0x0000, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM6);
+ writel(0x8000, regs + TB_ADDR_RX_DIAG_RXFE_TM3);
+ writel(0x0003, regs + TB_ADDR_RX_DIAG_RXFE_TM4);
+ writel(0x2408, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE);
+ writel(0x05ca, regs + TB_ADDR_RX_DIAG_DFE_CTRL3);
+ writel(0x0258, regs + TB_ADDR_RX_DIAG_SC2C_DELAY);
+ writel(0x1fff, regs + TB_ADDR_RX_REE_VGA_GAIN_NODFE);
+
+ writel(0x02c6, regs + TB_ADDR_XCVR_PSM_CAL_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0BYP_TMR);
+ writel(0x02c6, regs + TB_ADDR_XCVR_PSM_A0IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A1IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A2IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A3IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A4IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A5IN_TMR);
+
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A1OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A2OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A3OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A4OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A5OUT_TMR);
+
+ /* Change rx detect parameter */
+ writel(0x960, regs + TB_ADDR_TX_RCVDET_EN_TMR);
+ writel(0x01e0, regs + TB_ADDR_TX_RCVDET_ST_TMR);
+ writel(0x0090, regs + TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR);
+
+ /* RXDET_IN_P3_32KHZ, Receiver detect slow clock enable */
+ value = readl(regs + TB_ADDR_TX_RCVDETSC_CTRL);
+ value |= RXDET_IN_P3_32KHZ;
+ writel(value, regs + TB_ADDR_TX_RCVDETSC_CTRL);
+
+ udelay(10);
+
+ pr_debug("end of %s\n", __func__);
}
-static void cdns3_role_stop(struct cdns3 *cdns)
+static void cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role)
{
- enum usb_role role = cdns->role;
+ u32 value;
+ int timeout_us = 100000;
+ void __iomem *xhci_regs = cdns->xhci_regs;
- if (WARN_ON(role > USB_ROLE_DEVICE))
+ if (role == CDNS3_ROLE_END)
return;
- if (cdns->roles[role]->state == CDNS3_ROLE_STATE_INACTIVE)
- return;
+ /* Wait clk value */
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ writel(value, cdns->none_core_regs + USB3_SSPHY_STATUS);
+ udelay(1);
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ while ((value & 0xf0000000) != 0xf0000000 && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ dev_dbg(cdns->dev, "clkvld:0x%x\n", value);
+ udelay(1);
+ }
- mutex_lock(&cdns->mutex);
- cdns->roles[role]->stop(cdns);
- cdns->roles[role]->state = CDNS3_ROLE_STATE_INACTIVE;
- mutex_unlock(&cdns->mutex);
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait clkvld timeout\n");
+
+ /* Set all Reset bits */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value |= ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ udelay(1);
+
+ if (role == CDNS3_ROLE_HOST) {
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value = (value & ~MODE_STRAP_MASK) | HOST_MODE | OC_DISABLE;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~PHYAHB_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ mdelay(1);
+ cdns3_usb_phy_init(cdns->phy_regs);
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ mdelay(1);
+
+ value = readl(cdns->none_core_regs + USB3_INT_REG);
+ value |= HOST_INT1_EN;
+ writel(value, cdns->none_core_regs + USB3_INT_REG);
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ dev_dbg(cdns->dev, "wait xhci_power_on_ready\n");
+
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
+
+ value = readl(xhci_regs + XECP_PORT_CAP_REG);
+ value |= LPM_2_STB_SWITCH_EN;
+ writel(value, xhci_regs + XECP_PORT_CAP_REG);
+
+ mdelay(1);
+
+ dev_dbg(cdns->dev, "switch to host role successfully\n");
+ } else { /* gadget mode */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value = (value & ~MODE_STRAP_MASK) | DEV_MODE;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~PHYAHB_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ cdns3_usb_phy_init(cdns->phy_regs);
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ value = readl(cdns->none_core_regs + USB3_INT_REG);
+ value |= DEV_INT_EN;
+ writel(value, cdns->none_core_regs + USB3_INT_REG);
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ dev_dbg(cdns->dev, "wait gadget_power_on_ready\n");
+
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & DEV_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev,
+ "wait gadget_power_on_ready timeout\n");
+
+ mdelay(1);
+
+ dev_dbg(cdns->dev, "switch to gadget role successfully\n");
+ }
}
-static void cdns3_exit_roles(struct cdns3 *cdns)
+static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
{
- cdns3_role_stop(cdns);
- cdns3_drd_exit(cdns);
+ if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
+ if (extcon_get_state(cdns->extcon, EXTCON_USB_HOST))
+ return CDNS3_ROLE_HOST;
+ else if (extcon_get_state(cdns->extcon, EXTCON_USB))
+ return CDNS3_ROLE_GADGET;
+ else
+ return CDNS3_ROLE_END;
+ } else {
+ return cdns->roles[CDNS3_ROLE_HOST]
+ ? CDNS3_ROLE_HOST
+ : CDNS3_ROLE_GADGET;
+ }
}
-static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns);
-
/**
* cdns3_core_init_role - initialize role of operation
* @cdns: Pointer to cdns3 structure
@@ -93,340 +293,311 @@ static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns);
static int cdns3_core_init_role(struct cdns3 *cdns)
{
struct device *dev = cdns->dev;
- enum usb_dr_mode best_dr_mode;
- enum usb_dr_mode dr_mode;
- int ret = 0;
-
- dr_mode = usb_get_dr_mode(dev);
- cdns->role = USB_ROLE_NONE;
+ enum usb_dr_mode dr_mode = usb_get_dr_mode(dev);
- /*
- * If driver can't read mode by means of usb_get_dr_mode function then
- * chooses mode according with Kernel configuration. This setting
- * can be restricted later depending on strap pin configuration.
- */
- if (dr_mode == USB_DR_MODE_UNKNOWN) {
- if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
- IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
- dr_mode = USB_DR_MODE_OTG;
- else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
- dr_mode = USB_DR_MODE_HOST;
- else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
- dr_mode = USB_DR_MODE_PERIPHERAL;
- }
-
- /*
- * At this point cdns->dr_mode contains strap configuration.
- * Driver try update this setting considering kernel configuration
- */
- best_dr_mode = cdns->dr_mode;
-
- ret = cdns3_idle_init(cdns);
- if (ret)
- return ret;
-
- if (dr_mode == USB_DR_MODE_OTG) {
- best_dr_mode = cdns->dr_mode;
- } else if (cdns->dr_mode == USB_DR_MODE_OTG) {
- best_dr_mode = dr_mode;
- } else if (cdns->dr_mode != dr_mode) {
- dev_err(dev, "Incorrect DRD configuration\n");
- return -EINVAL;
- }
-
- dr_mode = best_dr_mode;
+ cdns->role = CDNS3_ROLE_END;
+ if (dr_mode == USB_DR_MODE_UNKNOWN)
+ dr_mode = USB_DR_MODE_OTG;
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
- ret = cdns3_host_init(cdns);
- if (ret) {
- dev_err(dev, "Host initialization failed with %d\n",
- ret);
- goto err;
- }
+ if (cdns3_host_init(cdns))
+ dev_info(dev, "doesn't support host\n");
}
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
- ret = cdns3_gadget_init(cdns);
- if (ret) {
- dev_err(dev, "Device initialization failed with %d\n",
- ret);
- goto err;
- }
+ if (cdns3_gadget_init(cdns))
+ dev_info(dev, "doesn't support gadget\n");
}
- cdns->dr_mode = dr_mode;
-
- ret = cdns3_drd_update_mode(cdns);
- if (ret)
- goto err;
-
- /* Initialize idle role to start with */
- ret = cdns3_role_start(cdns, USB_ROLE_NONE);
- if (ret)
- goto err;
-
- switch (cdns->dr_mode) {
- case USB_DR_MODE_OTG:
- ret = cdns3_hw_role_switch(cdns);
- if (ret)
- goto err;
- break;
- case USB_DR_MODE_PERIPHERAL:
- ret = cdns3_role_start(cdns, USB_ROLE_DEVICE);
- if (ret)
- goto err;
- break;
- case USB_DR_MODE_HOST:
- ret = cdns3_role_start(cdns, USB_ROLE_HOST);
- if (ret)
- goto err;
- break;
- default:
- ret = -EINVAL;
- goto err;
+ if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
+ dev_err(dev, "no supported roles\n");
+ return -ENODEV;
}
- return ret;
-err:
- cdns3_exit_roles(cdns);
- return ret;
+ return 0;
}
/**
- * cdsn3_hw_role_state_machine - role switch state machine based on hw events.
- * @cdns: Pointer to controller structure.
+ * cdns3_irq - interrupt handler for cdns3 core device
*
- * Returns next role to be entered based on hw events.
+ * @irq: irq number for cdns3 core device
+ * @data: structure of cdns3
+ *
+ * Returns IRQ_HANDLED or IRQ_NONE
*/
-static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns)
+static irqreturn_t cdns3_irq(int irq, void *data)
{
- enum usb_role role;
- int id, vbus;
+ struct cdns3 *cdns = data;
+ irqreturn_t ret = IRQ_NONE;
+
+ if (cdns->in_lpm) {
+ disable_irq_nosync(cdns->irq);
+ cdns->wakeup_int = true;
+ pm_runtime_get(cdns->dev);
+ return IRQ_HANDLED;
+ }
- if (cdns->dr_mode != USB_DR_MODE_OTG)
- goto not_otg;
+ /* Handle device/host interrupt */
+ if (cdns->role != CDNS3_ROLE_END)
+ ret = cdns3_role(cdns)->irq(cdns);
- id = cdns3_get_id(cdns);
- vbus = cdns3_get_vbus(cdns);
+ return ret;
+}
- /*
- * Role change state machine
- * Inputs: ID, VBUS
- * Previous state: cdns->role
- * Next state: role
- */
- role = cdns->role;
+static irqreturn_t cdns3_thread_irq(int irq, void *data)
+{
+ struct cdns3 *cdns = data;
+ irqreturn_t ret = IRQ_NONE;
- switch (role) {
- case USB_ROLE_NONE:
- /*
- * Driver treats USB_ROLE_NONE synonymous to IDLE state from
- * controller specification.
- */
- if (!id)
- role = USB_ROLE_HOST;
- else if (vbus)
- role = USB_ROLE_DEVICE;
- break;
- case USB_ROLE_HOST: /* from HOST, we can only change to NONE */
- if (id)
- role = USB_ROLE_NONE;
- break;
- case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/
- if (!vbus)
- role = USB_ROLE_NONE;
- break;
- }
-
- dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role);
-
- return role;
-
-not_otg:
- if (cdns3_is_host(cdns))
- role = USB_ROLE_HOST;
- if (cdns3_is_device(cdns))
- role = USB_ROLE_DEVICE;
-
- return role;
+ /* Handle device/host interrupt */
+ if (cdns->role != CDNS3_ROLE_END && cdns3_role(cdns)->thread_irq)
+ ret = cdns3_role(cdns)->thread_irq(cdns);
+
+ return ret;
}
-static int cdns3_idle_role_start(struct cdns3 *cdns)
+static int cdns3_get_clks(struct device *dev)
{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret = 0;
+
+ cdns->cdns3_clks[0] = devm_clk_get(dev, "usb3_lpm_clk");
+ if (IS_ERR(cdns->cdns3_clks[0])) {
+ ret = PTR_ERR(cdns->cdns3_clks[0]);
+ dev_err(dev, "Failed to get usb3_lpm_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[1] = devm_clk_get(dev, "usb3_bus_clk");
+ if (IS_ERR(cdns->cdns3_clks[1])) {
+ ret = PTR_ERR(cdns->cdns3_clks[1]);
+ dev_err(dev, "Failed to get usb3_bus_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[2] = devm_clk_get(dev, "usb3_aclk");
+ if (IS_ERR(cdns->cdns3_clks[2])) {
+ ret = PTR_ERR(cdns->cdns3_clks[2]);
+ dev_err(dev, "Failed to get usb3_aclk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[3] = devm_clk_get(dev, "usb3_ipg_clk");
+ if (IS_ERR(cdns->cdns3_clks[3])) {
+ ret = PTR_ERR(cdns->cdns3_clks[3]);
+ dev_err(dev, "Failed to get usb3_ipg_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[4] = devm_clk_get(dev, "usb3_core_pclk");
+ if (IS_ERR(cdns->cdns3_clks[4])) {
+ ret = PTR_ERR(cdns->cdns3_clks[4]);
+ dev_err(dev, "Failed to get usb3_core_pclk, err=%d\n", ret);
+ return ret;
+ }
+
return 0;
}
-static void cdns3_idle_role_stop(struct cdns3 *cdns)
+static int cdns3_prepare_enable_clks(struct device *dev)
{
- /* Program Lane swap and bring PHY out of RESET */
- phy_reset(cdns->usb3_phy);
-}
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i, j, ret = 0;
-static int cdns3_idle_init(struct cdns3 *cdns)
-{
- struct cdns3_role_driver *rdrv;
+ for (i = 0; i < CDNS3_NUM_OF_CLKS; i++) {
+ ret = clk_prepare_enable(cdns->cdns3_clks[i]);
+ if (ret) {
+ dev_err(dev,
+ "Failed to prepare/enable cdns3 clk, err=%d\n",
+ ret);
+ goto err;
+ }
+ }
- rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
- if (!rdrv)
- return -ENOMEM;
+ return ret;
+err:
+ for (j = i; j > 0; j--)
+ clk_disable_unprepare(cdns->cdns3_clks[j - 1]);
- rdrv->start = cdns3_idle_role_start;
- rdrv->stop = cdns3_idle_role_stop;
- rdrv->state = CDNS3_ROLE_STATE_INACTIVE;
- rdrv->suspend = NULL;
- rdrv->resume = NULL;
- rdrv->name = "idle";
+ return ret;
+}
- cdns->roles[USB_ROLE_NONE] = rdrv;
+static void cdns3_disable_unprepare_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i;
- return 0;
+ for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--)
+ clk_disable_unprepare(cdns->cdns3_clks[i]);
}
-/**
- * cdns3_hw_role_switch - switch roles based on HW state
- * @cdns3: controller
- */
-int cdns3_hw_role_switch(struct cdns3 *cdns)
+static void cdns3_remove_roles(struct cdns3 *cdns)
+{
+ cdns3_gadget_exit(cdns);
+ cdns3_host_remove(cdns);
+}
+
+static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
{
- enum usb_role real_role, current_role;
int ret = 0;
+ enum cdns3_roles current_role;
+
+ dev_dbg(cdns->dev, "current role is %d, switch to %d\n",
+ cdns->role, role);
- /* Do nothing if role based on syfs. */
- if (cdns->role_override)
+ if (cdns->role == role)
return 0;
pm_runtime_get_sync(cdns->dev);
-
current_role = cdns->role;
- real_role = cdsn3_hw_role_state_machine(cdns);
-
- /* Do nothing if nothing changed */
- if (current_role == real_role)
- goto exit;
-
cdns3_role_stop(cdns);
+ if (role == CDNS3_ROLE_END) {
+ /* Force B Session Valid as 0 */
+ writel(0x0040, cdns->phy_regs + 0x380a4);
+ pm_runtime_put_sync(cdns->dev);
+ return 0;
+ }
- dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role);
-
- ret = cdns3_role_start(cdns, real_role);
+ /*
+ * WORKAROUND: Mass storage gadget calls .ep_disable after
+ * disconnect with host, wait some time for .ep_disable completion.
+ */
+ msleep(20);
+ cdns_set_role(cdns, role);
+ ret = cdns3_role_start(cdns, role);
if (ret) {
/* Back to current role */
dev_err(cdns->dev, "set %d has failed, back to %d\n",
- real_role, current_role);
+ role, current_role);
+ cdns_set_role(cdns, current_role);
ret = cdns3_role_start(cdns, current_role);
- if (ret)
- dev_err(cdns->dev, "back to %d failed too\n",
- current_role);
}
-exit:
+
pm_runtime_put_sync(cdns->dev);
return ret;
}
/**
- * cdsn3_role_get - get current role of controller.
+ * cdns3_role_switch - work queue handler for role switch
*
- * @dev: Pointer to device structure
+ * @work: work queue item structure
*
- * Returns role
+ * Handles below events:
+ * - Role switch for dual-role devices
+ * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
*/
-static enum usb_role cdns3_role_get(struct device *dev)
+static void cdns3_role_switch(struct work_struct *work)
{
- struct cdns3 *cdns = dev_get_drvdata(dev);
+ struct cdns3 *cdns = container_of(work, struct cdns3,
+ role_switch_wq);
+ bool device, host;
+
+ host = extcon_get_state(cdns->extcon, EXTCON_USB_HOST);
+ device = extcon_get_state(cdns->extcon, EXTCON_USB);
+
+ if (host) {
+ if (cdns->roles[CDNS3_ROLE_HOST])
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
+ return;
+ }
- return cdns->role;
+ if (device)
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
+ else
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
}
-/**
- * cdns3_role_set - set current role of controller.
- *
- * @dev: pointer to device object
- * @role - the previous role
- * Handles below events:
- * - Role switch for dual-role devices
- * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices
- */
-static int cdns3_role_set(struct device *dev, enum usb_role role)
+static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
{
- struct cdns3 *cdns = dev_get_drvdata(dev);
- int ret = 0;
+ struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb);
- pm_runtime_get_sync(cdns->dev);
+ queue_work(system_freezable_wq, &cdns->role_switch_wq);
- /*
- * FIXME: switch role framework should be extended to meet
- * requirements. Driver assumes that role can be controlled
- * by SW or HW. Temporary workaround is to use USB_ROLE_NONE to
- * switch from SW to HW control.
- *
- * For dr_mode == USB_DR_MODE_OTG:
- * if user sets USB_ROLE_HOST or USB_ROLE_DEVICE then driver
- * sets role_override flag and forces that role.
- * if user sets USB_ROLE_NONE, driver clears role_override and lets
- * HW state machine take over.
- *
- * For dr_mode != USB_DR_MODE_OTG:
- * Assumptions:
- * 1. Restricted user control between NONE and dr_mode.
- * 2. Driver doesn't need to rely on role_override flag.
- * 3. Driver needs to ensure that HW state machine is never called
- * if dr_mode != USB_DR_MODE_OTG.
- */
- if (role == USB_ROLE_NONE)
- cdns->role_override = 0;
- else
- cdns->role_override = 1;
+ return NOTIFY_DONE;
+}
- /*
- * HW state might have changed so driver need to trigger
- * HW state machine if dr_mode == USB_DR_MODE_OTG.
- */
- if (!cdns->role_override && cdns->dr_mode == USB_DR_MODE_OTG) {
- cdns3_hw_role_switch(cdns);
- goto pm_put;
- }
+static int cdns3_register_extcon(struct cdns3 *cdns)
+{
+ struct extcon_dev *extcon;
+ struct device *dev = cdns->dev;
+ int ret;
- if (cdns->role == role)
- goto pm_put;
+ if (of_property_read_bool(dev->of_node, "extcon")) {
+ extcon = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(extcon))
+ return PTR_ERR(extcon);
- if (cdns->dr_mode == USB_DR_MODE_HOST) {
- switch (role) {
- case USB_ROLE_NONE:
- case USB_ROLE_HOST:
- break;
- default:
- ret = -EPERM;
- goto pm_put;
+ ret = devm_extcon_register_notifier(dev, extcon,
+ EXTCON_USB_HOST, &cdns->extcon_nb);
+ if (ret < 0) {
+ dev_err(dev, "register Host Connector failed\n");
+ return ret;
}
- }
- if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) {
- switch (role) {
- case USB_ROLE_NONE:
- case USB_ROLE_DEVICE:
- break;
- default:
- ret = -EPERM;
- goto pm_put;
+ ret = devm_extcon_register_notifier(dev, extcon,
+ EXTCON_USB, &cdns->extcon_nb);
+ if (ret < 0) {
+ dev_err(dev, "register Device Connector failed\n");
+ return ret;
}
+
+ cdns->extcon = extcon;
+ cdns->extcon_nb.notifier_call = cdns3_extcon_notifier;
}
- cdns3_role_stop(cdns);
- ret = cdns3_role_start(cdns, role);
- if (ret) {
- dev_err(cdns->dev, "set role %d has failed\n", role);
- ret = -EPERM;
+ return 0;
+}
+
+static ssize_t cdns3_role_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+
+ if (cdns->role != CDNS3_ROLE_END)
+ return sprintf(buf, "%s\n", cdns3_role(cdns)->name);
+ else
+ return sprintf(buf, "%s\n", "none");
+}
+
+static ssize_t cdns3_role_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t n)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ enum cdns3_roles role;
+ int ret;
+
+ if (!(cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET])) {
+ dev_warn(dev, "Current configuration is not dual-role, quit\n");
+ return -EPERM;
}
-pm_put:
- pm_runtime_put_sync(cdns->dev);
- return ret;
+ for (role = CDNS3_ROLE_HOST; role <= CDNS3_ROLE_GADGET; role++)
+ if (!strncmp(buf, cdns->roles[role]->name,
+ strlen(cdns->roles[role]->name)))
+ break;
+
+ if (role == CDNS3_ROLE_END)
+ return -EINVAL;
+
+ if (role == cdns->role)
+ return n;
+
+ disable_irq(cdns->irq);
+ ret = cdns3_do_role_switch(cdns, role);
+ enable_irq(cdns->irq);
+
+ return (ret == 0) ? n : ret;
}
+static DEVICE_ATTR(role, 0644, cdns3_role_show, cdns3_role_store);
-static const struct usb_role_switch_desc cdns3_switch_desc = {
- .set = cdns3_role_set,
- .get = cdns3_role_get,
- .allow_userspace_control = true,
+static struct attribute *cdns3_attrs[] = {
+ &dev_attr_role.attr,
+ NULL,
+};
+
+static const struct attribute_group cdns3_attr_group = {
+ .attrs = cdns3_attrs,
};
/**
@@ -443,111 +614,113 @@ static int cdns3_probe(struct platform_device *pdev)
void __iomem *regs;
int ret;
- ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
- if (ret) {
- dev_err(dev, "error setting dma mask: %d\n", ret);
- return -ENODEV;
- }
-
cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
if (!cdns)
return -ENOMEM;
cdns->dev = dev;
-
platform_set_drvdata(pdev, cdns);
- res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host");
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
- dev_err(dev, "missing host IRQ\n");
+ dev_err(dev, "missing IRQ\n");
return -ENODEV;
}
+ cdns->irq = res->start;
- cdns->xhci_res[0] = *res;
-
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci");
- if (!res) {
- dev_err(dev, "couldn't get xhci resource\n");
- return -ENXIO;
- }
-
- cdns->xhci_res[1] = *res;
-
- cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral");
- if (cdns->dev_irq == -EPROBE_DEFER)
- return cdns->dev_irq;
+ /*
+ * Request memory region
+ * region-0: nxp wrap registers
+ * region-1: xHCI
+ * region-2: Peripheral
+ * region-3: PHY registers
+ * region-4: OTG registers
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->none_core_regs = regs;
- if (cdns->dev_irq < 0)
- dev_err(dev, "couldn't get peripheral irq\n");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->xhci_regs = regs;
+ cdns->xhci_res = res;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dev");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->dev_regs = regs;
- cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
- if (cdns->otg_irq == -EPROBE_DEFER)
- return cdns->otg_irq;
-
- if (cdns->otg_irq < 0) {
- dev_err(dev, "couldn't get otg irq\n");
- return cdns->otg_irq;
- }
-
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
- if (!res) {
- dev_err(dev, "couldn't get otg resource\n");
- return -ENXIO;
- }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->phy_regs = regs;
- cdns->otg_res = *res;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->otg_regs = regs;
mutex_init(&cdns->mutex);
+ ret = cdns3_get_clks(dev);
+ if (ret)
+ return ret;
- cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
- if (IS_ERR(cdns->usb2_phy))
- return PTR_ERR(cdns->usb2_phy);
-
- ret = phy_init(cdns->usb2_phy);
+ ret = cdns3_prepare_enable_clks(dev);
if (ret)
return ret;
- cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
- if (IS_ERR(cdns->usb3_phy))
- return PTR_ERR(cdns->usb3_phy);
+ cdns->usbphy = devm_usb_get_phy_by_phandle(dev, "cdns3,usbphy", 0);
+ if (IS_ERR(cdns->usbphy)) {
+ ret = PTR_ERR(cdns->usbphy);
+ if (ret == -ENODEV)
+ ret = -EINVAL;
+ goto err1;
+ }
- ret = phy_init(cdns->usb3_phy);
+ ret = usb_phy_init(cdns->usbphy);
if (ret)
goto err1;
- ret = phy_power_on(cdns->usb2_phy);
+ ret = cdns3_core_init_role(cdns);
if (ret)
goto err2;
- ret = phy_power_on(cdns->usb3_phy);
- if (ret)
- goto err3;
+ if (cdns->roles[CDNS3_ROLE_GADGET]) {
+ INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
+ ret = cdns3_register_extcon(cdns);
+ if (ret)
+ goto err3;
+ }
- cdns->role_sw = usb_role_switch_register(dev, &cdns3_switch_desc);
- if (IS_ERR(cdns->role_sw)) {
- ret = PTR_ERR(cdns->role_sw);
- dev_warn(dev, "Unable to register Role Switch\n");
- goto err4;
+ cdns->role = cdns3_get_role(cdns);
+ dev_dbg(dev, "the init role is %d\n", cdns->role);
+ cdns_set_role(cdns, cdns->role);
+ ret = cdns3_role_start(cdns, cdns->role);
+ if (ret) {
+ dev_err(dev, "can't start %s role\n",
+ cdns3_role(cdns)->name);
+ goto err3;
}
- ret = cdns3_drd_init(cdns);
+ ret = devm_request_threaded_irq(dev, cdns->irq, cdns3_irq,
+ cdns3_thread_irq, IRQF_SHARED, dev_name(dev), cdns);
if (ret)
- goto err5;
+ goto err4;
- ret = cdns3_core_init_role(cdns);
+ ret = sysfs_create_group(&dev->kobj, &cdns3_attr_group);
if (ret)
- goto err5;
+ goto err4;
device_set_wakeup_capable(dev, true);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
-
/*
* The controller needs less time between bus and controller suspend,
* and we also needs a small delay to avoid frequently entering low
@@ -559,24 +732,20 @@ static int cdns3_probe(struct platform_device *pdev)
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
return 0;
-err5:
- cdns3_drd_exit(cdns);
- usb_role_switch_unregister(cdns->role_sw);
-err4:
- phy_power_off(cdns->usb3_phy);
+err4:
+ cdns3_role_stop(cdns);
err3:
- phy_power_off(cdns->usb2_phy);
+ cdns3_remove_roles(cdns);
err2:
- phy_exit(cdns->usb3_phy);
+ usb_phy_shutdown(cdns->usbphy);
err1:
- phy_exit(cdns->usb2_phy);
-
+ cdns3_disable_unprepare_clks(dev);
return ret;
}
/**
- * cdns3_remove - unbind drd driver and clean up
+ * cdns3_remove - unbind our drd driver and clean up
* @pdev: Pointer to Linux platform device
*
* Returns 0 on success otherwise negative errno
@@ -584,75 +753,363 @@ err1:
static int cdns3_remove(struct platform_device *pdev)
{
struct cdns3 *cdns = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ pm_runtime_get_sync(dev);
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ sysfs_remove_group(&dev->kobj, &cdns3_attr_group);
+ cdns3_remove_roles(cdns);
+ usb_phy_shutdown(cdns->usbphy);
+ cdns3_disable_unprepare_clks(dev);
- pm_runtime_get_sync(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
- pm_runtime_put_noidle(&pdev->dev);
- cdns3_exit_roles(cdns);
- usb_role_switch_unregister(cdns->role_sw);
- phy_power_off(cdns->usb2_phy);
- phy_power_off(cdns->usb3_phy);
- phy_exit(cdns->usb2_phy);
- phy_exit(cdns->usb3_phy);
return 0;
}
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_OF
+static const struct of_device_id of_cdns3_match[] = {
+ { .compatible = "Cadence,usb3" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_cdns3_match);
+#endif
+
+#ifdef CONFIG_PM
+static inline bool controller_power_is_lost(struct cdns3 *cdns)
+{
+ u32 value;
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ if ((value & SW_RESET_MASK) == ALL_SW_RESET)
+ return true;
+ else
+ return false;
+}
+static void cdns3_set_wakeup(void *none_core_regs, bool enable)
+{
+ u32 value;
+
+ if (enable) {
+ /* Enable wakeup and phy_refclk_req */
+ value = readl(none_core_regs + USB3_INT_REG);
+ value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN;
+ writel(value, none_core_regs + USB3_INT_REG);
+ } else {
+ /* disable wakeup and phy_refclk_req */
+ value = readl(none_core_regs + USB3_INT_REG);
+ value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN);
+ writel(value, none_core_regs + USB3_INT_REG);
+ }
+}
+
+static int cdns3_enter_suspend(struct cdns3 *cdns, bool suspend, bool wakeup)
+{
+ void __iomem *otg_regs = cdns->otg_regs;
+ void __iomem *xhci_regs = cdns->xhci_regs;
+ void __iomem *none_core_regs = cdns->none_core_regs;
+ u32 value;
+ int timeout_us = 100000;
+ int ret = 0;
+
+ if (cdns->role == CDNS3_ROLE_GADGET) {
+ if (suspend) {
+ /* When at device mode, set controller at reset mode */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value |= ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ }
+ return 0;
+ } else if (cdns->role == CDNS3_ROLE_END) {
+ return 0;
+ }
+
+ if (suspend) {
+ if (cdns3_role(cdns)->suspend)
+ ret = cdns3_role(cdns)->suspend(cdns, wakeup);
+
+ if (ret)
+ return ret;
+
+ /* SW request low power when all usb ports allow to it ??? */
+ value = readl(xhci_regs + XECP_PM_PMCSR);
+ value &= ~PS_MASK;
+ value |= PS_D1;
+ writel(value, xhci_regs + XECP_PM_PMCSR);
+
+ /* mdctrl_clk_sel */
+ value = readl(none_core_regs + USB3_CORE_CTRL1);
+ value |= MDCTRL_CLK_SEL;
+ writel(value, none_core_regs + USB3_CORE_CTRL1);
+
+ /* wait for mdctrl_clk_status */
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ while (!(value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
+
+ dev_dbg(cdns->dev, "mdctrl_clk_status is set\n");
+
+ /* wait lpm_clk_req to be 0 */
+ value = readl(none_core_regs + USB3_INT_REG);
+ timeout_us = 100000;
+ while ((value & LPM_CLK_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_INT_REG);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait lpm_clk_req timeout\n");
+
+ dev_dbg(cdns->dev, "lpm_clk_req cleared\n");
+
+ /* wait phy_refclk_req to be 0 */
+ value = readl(none_core_regs + USB3_SSPHY_STATUS);
+ timeout_us = 100000;
+ while ((value & PHY_REFCLK_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_SSPHY_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait phy_refclk_req timeout\n");
+
+ dev_dbg(cdns->dev, "phy_refclk_req cleared\n");
+ cdns3_set_wakeup(none_core_regs, true);
+ } else {
+ value = readl(none_core_regs + USB3_INT_REG);
+ /* wait CLK_125_REQ to be 1 */
+ value = readl(none_core_regs + USB3_INT_REG);
+ while (!(value & CLK_125_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_INT_REG);
+ udelay(1);
+ }
+
+ cdns3_set_wakeup(none_core_regs, false);
+
+ /* SW request D0 */
+ value = readl(xhci_regs + XECP_PM_PMCSR);
+ value &= ~PS_MASK;
+ value |= PS_D0;
+ writel(value, xhci_regs + XECP_PM_PMCSR);
+
+ /* clr CFG_RXDET_P3_EN */
+ value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
+ value &= ~CFG_RXDET_P3_EN;
+ writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
+
+ /* clear mdctrl_clk_sel */
+ value = readl(none_core_regs + USB3_CORE_CTRL1);
+ value &= ~MDCTRL_CLK_SEL;
+ writel(value, none_core_regs + USB3_CORE_CTRL1);
+
+ /* wait for mdctrl_clk_status is cleared */
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while ((value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
+
+ dev_dbg(cdns->dev, "mdctrl_clk_status cleared\n");
+
+ /* Wait until OTG_NRDY is 0 */
+ value = readl(otg_regs + OTGSTS);
+ timeout_us = 100000;
+ while ((value & OTG_NRDY) && timeout_us-- > 0) {
+ value = readl(otg_regs + OTGSTS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait OTG ready timeout\n");
+
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
+ }
+
+ return ret;
+}
+
+static int cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup)
+{
+ int ret = 0;
+
+ disable_irq(cdns->irq);
+ ret = cdns3_enter_suspend(cdns, true, wakeup);
+ if (ret) {
+ enable_irq(cdns->irq);
+ return ret;
+ }
+
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns->in_lpm = true;
+ enable_irq(cdns->irq);
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
static int cdns3_suspend(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
- unsigned long flags;
+ bool wakeup = device_may_wakeup(dev);
+ int ret;
- if (cdns->role == USB_ROLE_HOST)
- return 0;
+ dev_dbg(dev, "at %s\n", __func__);
if (pm_runtime_status_suspended(dev))
pm_runtime_resume(dev);
- if (cdns->roles[cdns->role]->suspend) {
- spin_lock_irqsave(&cdns->gadget_dev->lock, flags);
- cdns->roles[cdns->role]->suspend(cdns, false);
- spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags);
- }
+ ret = cdns3_controller_suspend(cdns, wakeup);
+ if (ret)
+ return ret;
- return 0;
+ cdns3_disable_unprepare_clks(dev);
+ if (wakeup)
+ enable_irq_wake(cdns->irq);
+
+ return ret;
}
static int cdns3_resume(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
- unsigned long flags;
+ int ret;
+ bool power_lost;
- if (cdns->role == USB_ROLE_HOST)
+ dev_dbg(dev, "at %s\n", __func__);
+ if (!cdns->in_lpm) {
+ WARN_ON(1);
return 0;
+ }
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
- if (cdns->roles[cdns->role]->resume) {
- spin_lock_irqsave(&cdns->gadget_dev->lock, flags);
- cdns->roles[cdns->role]->resume(cdns, false);
- spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags);
+ usb_phy_set_suspend(cdns->usbphy, 0);
+ cdns->in_lpm = false;
+ if (device_may_wakeup(dev))
+ disable_irq_wake(cdns->irq);
+ power_lost = controller_power_is_lost(cdns);
+ if (power_lost) {
+ dev_dbg(dev, "power is lost, the role is %d\n", cdns->role);
+ cdns_set_role(cdns, cdns->role);
+ if ((cdns->role != CDNS3_ROLE_END)
+ && cdns3_role(cdns)->resume) {
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ cdns3_role(cdns)->resume(cdns, true);
+ }
+ } else {
+ /* At resume path, never return error */
+ cdns3_enter_suspend(cdns, false, false);
+ if (cdns->wakeup_int) {
+ cdns->wakeup_int = false;
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+ enable_irq(cdns->irq);
+ }
+
+ if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
+ cdns3_role(cdns)->resume(cdns, false);
}
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
+ if (cdns->role == CDNS3_ROLE_HOST) {
+ /*
+ * There is no PM APIs for cdns->host_dev, we can only do
+ * it at its parent PM APIs
+ */
+ pm_runtime_disable(cdns->host_dev);
+ pm_runtime_set_active(cdns->host_dev);
+ pm_runtime_enable(cdns->host_dev);
+ }
+
+ dev_dbg(dev, "at end of %s\n", __func__);
+
return 0;
}
-#endif
+#endif /* CONFIG_PM_SLEEP */
+static int cdns3_runtime_suspend(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "at the begin of %s\n", __func__);
+ if (cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = cdns3_controller_suspend(cdns, true);
+ if (ret)
+ return ret;
+
+ cdns3_disable_unprepare_clks(dev);
+
+ dev_dbg(dev, "at the end of %s\n", __func__);
+
+ return ret;
+}
+
+static int cdns3_runtime_resume(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret;
+
+ if (!cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
+
+ usb_phy_set_suspend(cdns->usbphy, 0);
+ /* At resume path, never return error */
+ cdns3_enter_suspend(cdns, false, false);
+ cdns->in_lpm = 0;
+
+ if (cdns->role == CDNS3_ROLE_HOST) {
+ if (cdns->wakeup_int) {
+ cdns->wakeup_int = false;
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+ enable_irq(cdns->irq);
+ }
+
+ if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
+ cdns3_role(cdns)->resume(cdns, false);
+ }
+ dev_dbg(dev, "at %s\n", __func__);
+ return 0;
+}
+#endif /* CONFIG_PM */
static const struct dev_pm_ops cdns3_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
+ SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
};
-#ifdef CONFIG_OF
-static const struct of_device_id of_cdns3_match[] = {
- { .compatible = "cdns,usb3" },
- { },
-};
-MODULE_DEVICE_TABLE(of, of_cdns3_match);
-#endif
-
static struct platform_driver cdns3_driver = {
.probe = cdns3_probe,
.remove = cdns3_remove,
@@ -663,9 +1120,20 @@ static struct platform_driver cdns3_driver = {
},
};
-module_platform_driver(cdns3_driver);
+static int __init cdns3_driver_platform_register(void)
+{
+ cdns3_host_driver_init();
+ return platform_driver_register(&cdns3_driver);
+}
+module_init(cdns3_driver_platform_register);
+
+static void __exit cdns3_driver_platform_unregister(void)
+{
+ platform_driver_unregister(&cdns3_driver);
+}
+module_exit(cdns3_driver_platform_unregister);
-MODULE_ALIAS("platform:cdns3");
-MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
+MODULE_ALIAS("platform:cdns-usb3");
+MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");