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.c1014
1 files changed, 1014 insertions, 0 deletions
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
new file mode 100644
index 000000000000..df68728f1de3
--- /dev/null
+++ b/drivers/usb/cdns3/core.c
@@ -0,0 +1,1014 @@
+/**
+ * core.c - Cadence USB3 DRD Controller Core file
+ *
+ * Copyright 2017 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *
+ * 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.
+ *
+ * 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/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 "cdns3-nxp-reg-def.h"
+#include "core.h"
+#include "host-export.h"
+#include "gadget-export.h"
+
+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 cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role)
+{
+ u32 value;
+ int timeout_us = 100000;
+
+ if (role == CDNS3_ROLE_END)
+ 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);
+ }
+
+ 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");
+
+ 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 enum cdns3_roles cdns3_get_role(struct cdns3 *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;
+ }
+}
+
+/**
+ * cdns3_core_init_role - initialize role of operation
+ * @cdns: Pointer to cdns3 structure
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_core_init_role(struct cdns3 *cdns)
+{
+ struct device *dev = cdns->dev;
+ enum usb_dr_mode dr_mode = usb_get_dr_mode(dev);
+
+ 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) {
+ 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) {
+ if (cdns3_gadget_init(cdns))
+ dev_info(dev, "doesn't support gadget\n");
+ }
+
+ if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
+ dev_err(dev, "no supported roles\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * cdns3_irq - interrupt handler for cdns3 core device
+ *
+ * @irq: irq number for cdns3 core device
+ * @data: structure of cdns3
+ *
+ * Returns IRQ_HANDLED or IRQ_NONE
+ */
+static irqreturn_t cdns3_irq(int irq, void *data)
+{
+ 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;
+ }
+
+ /* Handle device/host interrupt */
+ if (cdns->role != CDNS3_ROLE_END)
+ ret = cdns3_role(cdns)->irq(cdns);
+
+ return ret;
+}
+
+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 int cdns3_prepare_enable_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i, j, ret = 0;
+
+ 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;
+ }
+ }
+
+ return ret;
+err:
+ for (j = i; j > 0; j--)
+ clk_disable_unprepare(cdns->cdns3_clks[j - 1]);
+
+ return ret;
+}
+
+static void cdns3_disable_unprepare_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i;
+
+ for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--)
+ clk_disable_unprepare(cdns->cdns3_clks[i]);
+}
+
+static void cdns3_remove_roles(struct cdns3 *cdns)
+{
+ cdns3_gadget_remove(cdns);
+ cdns3_host_remove(cdns);
+}
+
+static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
+{
+ int ret = 0;
+ enum cdns3_roles current_role;
+
+ dev_dbg(cdns->dev, "current role is %d, switch to %d\n",
+ cdns->role, role);
+
+ if (cdns->role == role)
+ return 0;
+
+ pm_runtime_get_sync(cdns->dev);
+ current_role = cdns->role;
+ 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;
+ }
+
+ 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",
+ role, current_role);
+ cdns_set_role(cdns, current_role);
+ ret = cdns3_role_start(cdns, current_role);
+ }
+
+ pm_runtime_put_sync(cdns->dev);
+ return ret;
+}
+
+/**
+ * cdns3_role_switch - work queue handler for role switch
+ *
+ * @work: work queue item structure
+ *
+ * Handles below events:
+ * - Role switch for dual-role devices
+ * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
+ */
+static void cdns3_role_switch(struct work_struct *work)
+{
+ 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;
+ }
+
+ if (device)
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
+ else
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
+}
+
+static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb);
+
+ queue_work(system_freezable_wq, &cdns->role_switch_wq);
+
+ return NOTIFY_DONE;
+}
+
+static int cdns3_register_extcon(struct cdns3 *cdns)
+{
+ struct extcon_dev *extcon;
+ struct device *dev = cdns->dev;
+ int ret;
+
+ 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);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ return 0;
+}
+
+/**
+ * cdns3_probe - probe for cdns3 core device
+ * @pdev: Pointer to cdns3 core platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct cdns3 *cdns;
+ void __iomem *regs;
+ int ret;
+
+ cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
+ if (!cdns)
+ return -ENOMEM;
+
+ cdns->dev = dev;
+ platform_set_drvdata(pdev, cdns);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "missing IRQ\n");
+ return -ENODEV;
+ }
+ cdns->irq = res->start;
+
+ /*
+ * 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;
+
+ 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(pdev, IORESOURCE_MEM, 2);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->dev_regs = regs;
+
+ 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;
+
+ 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;
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
+
+ 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 = usb_phy_init(cdns->usbphy);
+ if (ret)
+ goto err1;
+
+ ret = cdns3_core_init_role(cdns);
+ if (ret)
+ goto err2;
+
+ 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 = 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 = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
+ dev_name(dev), cdns);
+ if (ret)
+ 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
+ * power mode.
+ */
+ pm_runtime_set_autosuspend_delay(dev, 20);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_use_autosuspend(dev);
+ dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
+
+ return 0;
+
+err4:
+ cdns3_role_stop(cdns);
+err3:
+ cdns3_remove_roles(cdns);
+err2:
+ usb_phy_shutdown(cdns->usbphy);
+err1:
+ cdns3_disable_unprepare_clks(dev);
+ return ret;
+}
+
+/**
+ * cdns3_remove - unbind our drd driver and clean up
+ * @pdev: Pointer to Linux platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_remove(struct platform_device *pdev)
+{
+ struct cdns3 *cdns = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ cdns3_remove_roles(cdns);
+ usb_phy_shutdown(cdns->usbphy);
+ cdns3_disable_unprepare_clks(&pdev->dev);
+
+ return 0;
+}
+
+#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 void 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;
+
+ if (cdns->role != CDNS3_ROLE_HOST)
+ return;
+
+ disable_irq(cdns->irq);
+ if (suspend) {
+ value = readl(otg_regs + OTGREFCLK);
+ value |= OTG_STB_CLK_SWITCH_EN;
+ writel(value, otg_regs + OTGREFCLK);
+
+ value = readl(xhci_regs + XECP_PORT_CAP_REG);
+ value |= LPM_2_STB_SWITCH_EN;
+ writel(value, xhci_regs + XECP_PORT_CAP_REG);
+ if (cdns3_role(cdns)->suspend)
+ cdns3_role(cdns)->suspend(cdns, wakeup);
+
+ /*
+ * SW should ensure LPM_2_STB_SWITCH_EN and RXDET_IN_P3_32KHZ
+ * are aligned before setting 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);
+ /* SW request low power when all usb ports allow to it ??? */
+ value = readl(xhci_regs + XECP_PM_PMCSR);
+ value |= PS_D0;
+ 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_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");
+ }
+ enable_irq(cdns->irq);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cdns3_suspend(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ bool wakeup = device_may_wakeup(dev);
+ u32 value;
+
+ dev_dbg(dev, "at %s\n", __func__);
+
+ if (pm_runtime_status_suspended(dev))
+ pm_runtime_resume(dev);
+
+ if (cdns->role == CDNS3_ROLE_HOST)
+ cdns3_enter_suspend(cdns, true, wakeup);
+ else if (cdns->role == CDNS3_ROLE_GADGET) {
+ /* When at device mode, always 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);
+ }
+
+ if (wakeup)
+ enable_irq_wake(cdns->irq);
+
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns3_disable_unprepare_clks(dev);
+ cdns->in_lpm = true;
+
+ return 0;
+}
+
+static int cdns3_resume(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret;
+ bool power_lost;
+
+ 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;
+
+ 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 {
+ 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 /* CONFIG_PM_SLEEP */
+static int cdns3_runtime_suspend(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "at the begin of %s\n", __func__);
+ if (cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ cdns3_enter_suspend(cdns, true, true);
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns3_disable_unprepare_clks(dev);
+ cdns->in_lpm = true;
+
+ dev_dbg(dev, "at the end of %s\n", __func__);
+
+ return 0;
+}
+
+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);
+ 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)
+};
+
+static struct platform_driver cdns3_driver = {
+ .probe = cdns3_probe,
+ .remove = cdns3_remove,
+ .driver = {
+ .name = "cdns-usb3",
+ .of_match_table = of_match_ptr(of_cdns3_match),
+ .pm = &cdns3_pm_ops,
+ },
+};
+
+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("Peter Chen <peter.chen@nxp.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");