diff options
Diffstat (limited to 'drivers/usb/cdns3/core.c')
-rw-r--r-- | drivers/usb/cdns3/core.c | 1312 |
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"); |