summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/Kconfig2
-rw-r--r--drivers/usb/Makefile1
-rw-r--r--drivers/usb/cdns3/Kconfig26
-rw-r--r--drivers/usb/cdns3/Makefile5
-rw-r--r--drivers/usb/cdns3/cdns3-nxp-reg-def.h174
-rw-r--r--drivers/usb/cdns3/core.c1009
-rw-r--r--drivers/usb/cdns3/core.h131
-rw-r--r--drivers/usb/cdns3/dev-regs-macro.h894
-rw-r--r--drivers/usb/cdns3/dev-regs-map.h126
-rw-r--r--drivers/usb/cdns3/gadget-export.h36
-rw-r--r--drivers/usb/cdns3/gadget.c2555
-rw-r--r--drivers/usb/cdns3/gadget.h225
-rw-r--r--drivers/usb/cdns3/host-export.h43
-rw-r--r--drivers/usb/cdns3/host.c286
-rw-r--r--drivers/usb/cdns3/io.h35
-rw-r--r--drivers/usb/chipidea/ci.h45
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c319
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.h22
-rw-r--r--drivers/usb/chipidea/core.c202
-rw-r--r--drivers/usb/chipidea/host.c332
-rw-r--r--drivers/usb/chipidea/host.h8
-rw-r--r--drivers/usb/chipidea/otg.c112
-rw-r--r--drivers/usb/chipidea/otg.h14
-rw-r--r--drivers/usb/chipidea/otg_fsm.c218
-rw-r--r--drivers/usb/chipidea/otg_fsm.h28
-rw-r--r--drivers/usb/chipidea/udc.c228
-rw-r--r--drivers/usb/chipidea/udc.h13
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c672
-rw-r--r--drivers/usb/common/usb-otg-fsm.c54
-rw-r--r--drivers/usb/core/hcd.c134
-rw-r--r--drivers/usb/core/hub.c23
-rw-r--r--drivers/usb/core/otg_whitelist.h88
-rw-r--r--drivers/usb/dwc3/core.c83
-rw-r--r--drivers/usb/dwc3/core.h4
-rw-r--r--drivers/usb/dwc3/dwc3-of-simple.c1
-rw-r--r--drivers/usb/dwc3/gadget.c22
-rw-r--r--drivers/usb/gadget/Kconfig6
-rw-r--r--drivers/usb/gadget/function/f_mass_storage.c70
-rw-r--r--drivers/usb/gadget/function/fsl_updater.c644
-rw-r--r--drivers/usb/gadget/function/fsl_updater.h152
-rw-r--r--drivers/usb/host/ehci-hcd.c6
-rw-r--r--drivers/usb/host/ehci-hub.c141
-rw-r--r--drivers/usb/host/ehci-q.c14
-rw-r--r--drivers/usb/host/pci-quirks.c20
-rw-r--r--drivers/usb/host/pci-quirks.h1
-rw-r--r--drivers/usb/host/xhci-dbg.c6
-rw-r--r--drivers/usb/host/xhci-hub.c10
-rw-r--r--drivers/usb/host/xhci-pci.c7
-rw-r--r--drivers/usb/host/xhci-plat.c31
-rw-r--r--drivers/usb/host/xhci-ring.c135
-rw-r--r--drivers/usb/host/xhci.c5
-rw-r--r--drivers/usb/host/xhci.h13
-rw-r--r--drivers/usb/misc/ehset.c25
-rw-r--r--drivers/usb/phy/Kconfig2
-rw-r--r--drivers/usb/phy/phy-mxs-usb.c627
-rw-r--r--drivers/usb/phy/phy.c14
-rw-r--r--drivers/usb/typec/typec.c48
57 files changed, 9666 insertions, 481 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 72eb3e41e3b6..5e810e2ae7df 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -125,6 +125,8 @@ source "drivers/usb/chipidea/Kconfig"
source "drivers/usb/isp1760/Kconfig"
+source "drivers/usb/cdns3/Kconfig"
+
comment "USB port drivers"
if USB
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 060643a1b5c8..4a96f18a616f 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_USB_SUPPORT) += phy/
obj-$(CONFIG_USB_DWC3) += dwc3/
obj-$(CONFIG_USB_DWC2) += dwc2/
obj-$(CONFIG_USB_ISP1760) += isp1760/
+obj-$(CONFIG_USB_CDNS3) += cdns3/
obj-$(CONFIG_USB_MON) += mon/
obj-$(CONFIG_USB_MTU3) += mtu3/
diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
new file mode 100644
index 000000000000..165afdb99d15
--- /dev/null
+++ b/drivers/usb/cdns3/Kconfig
@@ -0,0 +1,26 @@
+config USB_CDNS3
+ tristate "Cadence USB3 Dual-Role Controller"
+ depends on ((USB_XHCI_HCD && USB_GADGET) || (USB_XHCI_HCD && !USB_GADGET) || (!USB_XHCI_HCD && USB_GADGET)) && HAS_DMA
+ select EXTCON
+ help
+ Say Y here if your system has a cadence USB3 dual-role controller.
+ It supports: dual-role switch Host-only, and Peripheral-only.
+
+ When compiled dynamically, the module will be called cdns3.ko.
+
+if USB_CDNS3
+
+config USB_CDNS3_GADGET
+ bool "Cadence USB3 device controller"
+ depends on USB_GADGET
+ help
+ Say Y here to enable device controller functionality of the
+ cadence usb3 driver.
+
+config USB_CDNS3_HOST
+ bool "Cadence USB3 host controller"
+ depends on USB_XHCI_HCD
+ help
+ Say Y here to enable host controller functionality of the
+ cadence usb3 driver.
+endif
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
new file mode 100644
index 000000000000..7328cb9fcc89
--- /dev/null
+++ b/drivers/usb/cdns3/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_USB_CDNS3) += cdns3.o
+
+cdns3-y := core.o
+cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o
+cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o
diff --git a/drivers/usb/cdns3/cdns3-nxp-reg-def.h b/drivers/usb/cdns3/cdns3-nxp-reg-def.h
new file mode 100644
index 000000000000..4ac73005c023
--- /dev/null
+++ b/drivers/usb/cdns3/cdns3-nxp-reg-def.h
@@ -0,0 +1,174 @@
+/**
+ * cdns3-nxp-reg-def.h - nxp wrap layer register definition
+ *
+ * Copyright 2017 NXP
+ *
+ * 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/>.
+ */
+
+#ifndef __DRIVERS_USB_CDNS3_NXP_H
+#define __DRIVERS_USB_CDNS3_NXP_H
+
+#define USB3_CORE_CTRL1 0x00
+#define USB3_CORE_CTRL2 0x04
+#define USB3_INT_REG 0x08
+#define USB3_CORE_STATUS 0x0c
+#define XHCI_DEBUG_LINK_ST 0x10
+#define XHCI_DEBUG_BUS 0x14
+#define USB3_SSPHY_CTRL1 0x40
+#define USB3_SSPHY_CTRL2 0x44
+#define USB3_SSPHY_STATUS 0x4c
+#define USB2_PHY_CTRL1 0x50
+#define USB2_PHY_CTRL2 0x54
+#define USB2_PHY_STATUS 0x5c
+
+/* Register bits definition */
+
+/* USB3_CORE_CTRL1 */
+#define SW_RESET_MASK (0x3f << 26)
+#define PWR_SW_RESET (1 << 31)
+#define APB_SW_RESET (1 << 30)
+#define AXI_SW_RESET (1 << 29)
+#define RW_SW_RESET (1 << 28)
+#define PHY_SW_RESET (1 << 27)
+#define PHYAHB_SW_RESET (1 << 26)
+#define ALL_SW_RESET (PWR_SW_RESET | APB_SW_RESET | AXI_SW_RESET | \
+ RW_SW_RESET | PHY_SW_RESET | PHYAHB_SW_RESET)
+#define OC_DISABLE (1 << 9)
+#define MDCTRL_CLK_SEL (1 << 7)
+#define MODE_STRAP_MASK (0x7)
+#define DEV_MODE (1 << 2)
+#define HOST_MODE (1 << 1)
+#define OTG_MODE (1 << 0)
+
+/* USB3_INT_REG */
+#define CLK_125_REQ (1 << 29)
+#define LPM_CLK_REQ (1 << 28)
+#define DEVU3_WAEKUP_EN (1 << 14)
+#define OTG_WAKEUP_EN (1 << 12)
+#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */
+#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */
+
+/* USB3_CORE_STATUS */
+#define MDCTRL_CLK_STATUS (1 << 15)
+#define DEV_POWER_ON_READY (1 << 13)
+#define HOST_POWER_ON_READY (1 << 12)
+
+/* USB3_SSPHY_STATUS */
+#define PHY_REFCLK_REQ (1 << 0)
+
+
+/* PHY register definition */
+#define PHY_PMA_CMN_CTRL1 (0xC800 * 4)
+#define TB_ADDR_CMN_DIAG_HSCLK_SEL (0x01e0 * 4)
+#define TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR (0x0084 * 4)
+#define TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR (0x0085 * 4)
+#define TB_ADDR_CMN_PLL0_INTDIV (0x0094 * 4)
+#define TB_ADDR_CMN_PLL0_FRACDIV (0x0095 * 4)
+#define TB_ADDR_CMN_PLL0_HIGH_THR (0x0096 * 4)
+#define TB_ADDR_CMN_PLL0_SS_CTRL1 (0x0098 * 4)
+#define TB_ADDR_CMN_PLL0_SS_CTRL2 (0x0099 * 4)
+#define TB_ADDR_CMN_PLL0_DSM_DIAG (0x0097 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_OVRD (0x01c2 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD (0x01c0 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD (0x01c1 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE (0x01C5 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_CP_TUNE (0x01C6 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_LF_PROG (0x01C7 * 4)
+#define TB_ADDR_CMN_DIAG_PLL0_TEST_MODE (0x01c4 * 4)
+#define TB_ADDR_CMN_PSM_CLK_CTRL (0x0061 * 4)
+#define TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR (0x40ea * 4)
+#define TB_ADDR_XCVR_PSM_RCTRL (0x4001 * 4)
+#define TB_ADDR_TX_PSC_A0 (0x4100 * 4)
+#define TB_ADDR_TX_PSC_A1 (0x4101 * 4)
+#define TB_ADDR_TX_PSC_A2 (0x4102 * 4)
+#define TB_ADDR_TX_PSC_A3 (0x4103 * 4)
+#define TB_ADDR_TX_DIAG_ECTRL_OVRD (0x41f5 * 4)
+#define TB_ADDR_TX_PSC_CAL (0x4106 * 4)
+#define TB_ADDR_TX_PSC_RDY (0x4107 * 4)
+#define TB_ADDR_RX_PSC_A0 (0x8000 * 4)
+#define TB_ADDR_RX_PSC_A1 (0x8001 * 4)
+#define TB_ADDR_RX_PSC_A2 (0x8002 * 4)
+#define TB_ADDR_RX_PSC_A3 (0x8003 * 4)
+#define TB_ADDR_RX_PSC_CAL (0x8006 * 4)
+#define TB_ADDR_RX_PSC_RDY (0x8007 * 4)
+#define TB_ADDR_TX_TXCC_MGNLS_MULT_000 (0x4058 * 4)
+#define TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 * 4)
+#define TB_ADDR_RX_SLC_CU_ITER_TMR (0x80e3 * 4)
+#define TB_ADDR_RX_SIGDET_HL_FILT_TMR (0x8090 * 4)
+#define TB_ADDR_RX_SAMP_DAC_CTRL (0x8058 * 4)
+#define TB_ADDR_RX_DIAG_SIGDET_TUNE (0x81dc * 4)
+#define TB_ADDR_RX_DIAG_LFPSDET_TUNE2 (0x81df * 4)
+#define TB_ADDR_RX_DIAG_BS_TM (0x81f5 * 4)
+#define TB_ADDR_RX_DIAG_DFE_CTRL1 (0x81d3 * 4)
+#define TB_ADDR_RX_DIAG_ILL_IQE_TRIM4 (0x81c7 * 4)
+#define TB_ADDR_RX_DIAG_ILL_E_TRIM0 (0x81c2 * 4)
+#define TB_ADDR_RX_DIAG_ILL_IQ_TRIM0 (0x81c1 * 4)
+#define TB_ADDR_RX_DIAG_ILL_IQE_TRIM6 (0x81c9 * 4)
+#define TB_ADDR_RX_DIAG_RXFE_TM3 (0x81f8 * 4)
+#define TB_ADDR_RX_DIAG_RXFE_TM4 (0x81f9 * 4)
+#define TB_ADDR_RX_DIAG_LFPSDET_TUNE (0x81dd * 4)
+#define TB_ADDR_RX_DIAG_DFE_CTRL3 (0x81d5 * 4)
+#define TB_ADDR_RX_DIAG_SC2C_DELAY (0x81e1 * 4)
+#define TB_ADDR_RX_REE_VGA_GAIN_NODFE (0x81bf * 4)
+#define TB_ADDR_XCVR_PSM_CAL_TMR (0x4002 * 4)
+#define TB_ADDR_XCVR_PSM_A0BYP_TMR (0x4004 * 4)
+#define TB_ADDR_XCVR_PSM_A0IN_TMR (0x4003 * 4)
+#define TB_ADDR_XCVR_PSM_A1IN_TMR (0x4005 * 4)
+#define TB_ADDR_XCVR_PSM_A2IN_TMR (0x4006 * 4)
+#define TB_ADDR_XCVR_PSM_A3IN_TMR (0x4007 * 4)
+#define TB_ADDR_XCVR_PSM_A4IN_TMR (0x4008 * 4)
+#define TB_ADDR_XCVR_PSM_A5IN_TMR (0x4009 * 4)
+#define TB_ADDR_XCVR_PSM_A0OUT_TMR (0x400a * 4)
+#define TB_ADDR_XCVR_PSM_A1OUT_TMR (0x400b * 4)
+#define TB_ADDR_XCVR_PSM_A2OUT_TMR (0x400c * 4)
+#define TB_ADDR_XCVR_PSM_A3OUT_TMR (0x400d * 4)
+#define TB_ADDR_XCVR_PSM_A4OUT_TMR (0x400e * 4)
+#define TB_ADDR_XCVR_PSM_A5OUT_TMR (0x400f * 4)
+#define TB_ADDR_TX_RCVDET_EN_TMR (0x4122 * 4)
+#define TB_ADDR_TX_RCVDET_ST_TMR (0x4123 * 4)
+#define TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR (0x40f2 * 4)
+#define TB_ADDR_TX_RCVDETSC_CTRL (0x4124 * 4)
+
+/* Register bits definition */
+
+/* TB_ADDR_TX_RCVDETSC_CTRL */
+#define RXDET_IN_P3_32KHZ (1 << 0)
+
+/* OTG registers definition */
+#define OTGSTS 0x4
+#define OTGREFCLK 0xc
+
+/* Register bits definition */
+/* OTGSTS */
+#define OTG_NRDY (1 << 11)
+/* OTGREFCLK */
+#define OTG_STB_CLK_SWITCH_EN (1 << 31)
+
+/* xHCI registers definition */
+#define XECP_PORT_CAP_REG 0x8000
+#define XECP_PM_PMCSR 0x8018
+#define XECP_AUX_CTRL_REG1 0x8120
+
+/* Register bits definition */
+/* XECP_PORT_CAP_REG */
+#define LPM_2_STB_SWITCH_EN (1 << 25)
+
+/* XECP_AUX_CTRL_REG1 */
+#define CFG_RXDET_P3_EN (1 << 15)
+
+/* XECP_PM_PMCSR */
+#define PS_MASK (3 << 0)
+#define PS_D0 0
+#define PS_D1 (1 << 0)
+#endif /* __DRIVERS_USB_CDNS3_NXP_H */
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
new file mode 100644
index 000000000000..b8a183f03af7
--- /dev/null
+++ b/drivers/usb/cdns3/core.c
@@ -0,0 +1,1009 @@
+/**
+ * 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;
+ void __iomem *xhci_regs = cdns->xhci_regs;
+
+ 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");
+
+ 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 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_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;
+ } else if (cdns->role == CDNS3_ROLE_END) {
+ return;
+ }
+
+ if (suspend) {
+ if (cdns3_role(cdns)->suspend)
+ cdns3_role(cdns)->suspend(cdns, wakeup);
+
+ /* 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");
+ }
+}
+
+static void cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup)
+{
+ disable_irq(cdns->irq);
+ cdns3_enter_suspend(cdns, true, wakeup);
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns->in_lpm = true;
+ 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);
+
+ dev_dbg(dev, "at %s\n", __func__);
+
+ if (pm_runtime_status_suspended(dev))
+ pm_runtime_resume(dev);
+
+ cdns3_controller_suspend(cdns, wakeup);
+ cdns3_disable_unprepare_clks(dev);
+ if (wakeup)
+ enable_irq_wake(cdns->irq);
+
+ 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_controller_suspend(cdns, true);
+ cdns3_disable_unprepare_clks(dev);
+
+ 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:cdns-usb3");
+MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
new file mode 100644
index 000000000000..738dbfa836cf
--- /dev/null
+++ b/drivers/usb/cdns3/core.h
@@ -0,0 +1,131 @@
+/**
+ * core.h - Cadence USB3 DRD Controller Core header 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/>.
+ */
+
+#ifndef __DRIVERS_USB_CDNS3_CORE_H
+#define __DRIVERS_USB_CDNS3_CORE_H
+
+struct cdns3;
+enum cdns3_roles {
+ CDNS3_ROLE_HOST = 0,
+ CDNS3_ROLE_GADGET,
+ CDNS3_ROLE_END,
+};
+
+/**
+ * struct cdns3_role_driver - host/gadget role driver
+ * @start: start this role
+ * @stop: stop this role
+ * @suspend: suspend callback for this role
+ * @resume: resume callback for this role
+ * @irq: irq handler for this role
+ * @name: role name string (host/gadget)
+ */
+struct cdns3_role_driver {
+ int (*start)(struct cdns3 *);
+ void (*stop)(struct cdns3 *);
+ int (*suspend)(struct cdns3 *, bool do_wakeup);
+ int (*resume)(struct cdns3 *, bool hibernated);
+ irqreturn_t (*irq)(struct cdns3 *);
+ const char *name;
+};
+
+#define CDNS3_NUM_OF_CLKS 5
+/**
+ * struct cdns3 - Representation of Cadence USB3 DRD controller.
+ * @dev: pointer to Cadence device struct
+ * @xhci_regs: pointer to base of xhci registers
+ * @xhci_res: the resource for xhci
+ * @dev_regs: pointer to base of dev registers
+ * @none_core_regs: pointer to base of nxp wrapper registers
+ * @phy_regs: pointer to base of phy registers
+ * @otg_regs: pointer to base of otg registers
+ * @irq: irq number for controller
+ * @roles: array of supported roles for this controller
+ * @role: current role
+ * @host_dev: the child host device pointer for cdns3 core
+ * @gadget_dev: the child gadget device pointer for cdns3 core
+ * @usbphy: usbphy for this controller
+ * @cdns3_clks: Clock pointer array for cdns3 core
+ * @extcon: Type-C extern connector
+ * @extcon_nb: notifier block for Type-C extern connector
+ * @role_switch_wq: work queue item for role switch
+ * @in_lpm: the controller in low power mode
+ * @wakeup_int: the wakeup interrupt
+ * @mutex: the mutex for concurrent code at driver
+ */
+struct cdns3 {
+ struct device *dev;
+ void __iomem *xhci_regs;
+ struct resource *xhci_res;
+ struct usbss_dev_register_block_type __iomem *dev_regs;
+ void __iomem *none_core_regs;
+ void __iomem *phy_regs;
+ void __iomem *otg_regs;
+ int irq;
+ struct cdns3_role_driver *roles[CDNS3_ROLE_END];
+ enum cdns3_roles role;
+ struct device *host_dev;
+ struct device *gadget_dev;
+ struct usb_phy *usbphy;
+ struct clk *cdns3_clks[CDNS3_NUM_OF_CLKS];
+ struct extcon_dev *extcon;
+ struct notifier_block extcon_nb;
+ struct work_struct role_switch_wq;
+ bool in_lpm;
+ bool wakeup_int;
+ struct mutex mutex;
+};
+
+static inline struct cdns3_role_driver *cdns3_role(struct cdns3 *cdns)
+{
+ WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
+ return cdns->roles[cdns->role];
+}
+
+static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
+{
+ int ret;
+ if (role >= CDNS3_ROLE_END)
+ return 0;
+
+ if (!cdns->roles[role])
+ return -ENXIO;
+
+ mutex_lock(&cdns->mutex);
+ cdns->role = role;
+ ret = cdns->roles[role]->start(cdns);
+ mutex_unlock(&cdns->mutex);
+ return ret;
+}
+
+static inline void cdns3_role_stop(struct cdns3 *cdns)
+{
+ enum cdns3_roles role = cdns->role;
+
+ if (role == CDNS3_ROLE_END)
+ return;
+
+ mutex_lock(&cdns->mutex);
+ cdns->roles[role]->stop(cdns);
+ cdns->role = CDNS3_ROLE_END;
+ mutex_unlock(&cdns->mutex);
+}
+
+#endif /* __DRIVERS_USB_CDNS3_CORE_H */
diff --git a/drivers/usb/cdns3/dev-regs-macro.h b/drivers/usb/cdns3/dev-regs-macro.h
new file mode 100644
index 000000000000..5b307623afec
--- /dev/null
+++ b/drivers/usb/cdns3/dev-regs-macro.h
@@ -0,0 +1,894 @@
+/**
+ * dev-regs-macro.h - Cadence USB3 Device register definition
+ *
+ * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __REG_USBSS_DEV_ADDR_MAP_MACRO_H__
+#define __REG_USBSS_DEV_ADDR_MAP_MACRO_H__
+
+
+/* macros for BlueprintGlobalNameSpace::USB_CONF */
+#ifndef __USB_CONF_MACRO__
+#define __USB_CONF_MACRO__
+
+/* macros for field CFGRST */
+#define USB_CONF__CFGRST__MASK 0x00000001U
+#define USB_CONF__CFGSET__MASK 0x00000002U
+#define USB_CONF__USB3DIS__MASK 0x00000008U
+#define USB_CONF__DEVEN__MASK 0x00004000U
+#define USB_CONF__DEVDS__MASK 0x00008000U
+#define USB_CONF__L1EN__MASK 0x00010000U
+#define USB_CONF__L1DS__MASK 0x00020000U
+#define USB_CONF__CLK2OFFDS__MASK 0x00080000U
+#define USB_CONF__U1EN__MASK 0x01000000U
+#define USB_CONF__U1DS__MASK 0x02000000U
+#define USB_CONF__U2EN__MASK 0x04000000U
+#define USB_CONF__U2DS__MASK 0x08000000U
+#endif /* __USB_CONF_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_conf */
+#ifndef __USB_STS_MACRO__
+#define __USB_STS_MACRO__
+
+/* macros for field CFGSTS */
+#define USB_STS__CFGSTS__MASK 0x00000001U
+#define USB_STS__USBSPEED__READ(src) (((uint32_t)(src) & 0x00000070U) >> 4)
+
+/* macros for field ENDIAN_MIRROR */
+#define USB_STS__LPMST__READ(src) (((uint32_t)(src) & 0x000c0000U) >> 18)
+
+/* macros for field USB2CONS */
+#define USB_STS__U1ENS__MASK 0x01000000U
+#define USB_STS__U2ENS__MASK 0x02000000U
+#define USB_STS__LST__READ(src) (((uint32_t)(src) & 0x3c000000U) >> 26)
+
+/* macros for field DMAOFF */
+#endif /* __USB_STS_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_sts */
+#ifndef __USB_CMD_MACRO__
+#define __USB_CMD_MACRO__
+
+/* macros for field SET_ADDR */
+#define USB_CMD__SET_ADDR__MASK 0x00000001U
+#define USB_CMD__STMODE 0x00000200U
+#define USB_CMD__TMODE_SEL(x) (x << 10)
+#define USB_CMD__FADDR__WRITE(src) (((uint32_t)(src) << 1) & 0x000000feU)
+#endif /* __USB_CMD_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cmd */
+#ifndef __USB_ITPN_MACRO__
+#define __USB_ITPN_MACRO__
+
+/* macros for field ITPN */
+#endif /* __USB_ITPN_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_iptn */
+#ifndef __USB_LPM_MACRO__
+#define __USB_LPM_MACRO__
+
+/* macros for field HIRD */
+#endif /* __USB_LPM_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_lpm */
+#ifndef __USB_IEN_MACRO__
+#define __USB_IEN_MACRO__
+
+/* macros for field CONIEN */
+#define USB_IEN__CONIEN__MASK 0x00000001U
+#define USB_IEN__DISIEN__MASK 0x00000002U
+#define USB_IEN__UWRESIEN__MASK 0x00000004U
+#define USB_IEN__UHRESIEN__MASK 0x00000008U
+#define USB_IEN__U3EXTIEN__MASK 0x00000020U
+#define USB_IEN__CON2IEN__MASK 0x00010000U
+#define USB_IEN__U2RESIEN__MASK 0x00040000U
+#define USB_IEN__L2ENTIEN__MASK 0x00100000U
+#define USB_IEN__L2EXTIEN__MASK 0x00200000U
+#endif /* __USB_IEN_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_ien */
+#ifndef __USB_ISTS_MACRO__
+#define __USB_ISTS_MACRO__
+
+/* macros for field CONI */
+#define USB_ISTS__CONI__SHIFT 0
+#define USB_ISTS__DISI__SHIFT 1
+#define USB_ISTS__UWRESI__SHIFT 2
+#define USB_ISTS__UHRESI__SHIFT 3
+#define USB_ISTS__U3EXTI__SHIFT 5
+#define USB_ISTS__CON2I__SHIFT 16
+#define USB_ISTS__DIS2I__SHIFT 17
+#define USB_ISTS__DIS2I__MASK 0x00020000U
+#define USB_ISTS__U2RESI__SHIFT 18
+#define USB_ISTS__L2ENTI__SHIFT 20
+#define USB_ISTS__L2EXTI__SHIFT 21
+#endif /* __USB_ISTS_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_ists */
+#ifndef __EP_SEL_MACRO__
+#define __EP_SEL_MACRO__
+
+/* macros for field EPNO */
+#endif /* __EP_SEL_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_sel */
+#ifndef __EP_TRADDR_MACRO__
+#define __EP_TRADDR_MACRO__
+
+/* macros for field TRADDR */
+#define EP_TRADDR__TRADDR__WRITE(src) ((uint32_t)(src) & 0xffffffffU)
+#endif /* __EP_TRADDR_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_traddr */
+#ifndef __EP_CFG_MACRO__
+#define __EP_CFG_MACRO__
+
+/* macros for field ENABLE */
+#define EP_CFG__ENABLE__MASK 0x00000001U
+#define EP_CFG__EPTYPE__WRITE(src) (((uint32_t)(src) << 1) & 0x00000006U)
+#define EP_CFG__MAXBURST__WRITE(src) (((uint32_t)(src) << 8) & 0x00000f00U)
+#define EP_CFG__MAXPKTSIZE__WRITE(src) (((uint32_t)(src) << 16) & 0x07ff0000U)
+#define EP_CFG__BUFFERING__WRITE(src) (((uint32_t)(src) << 27) & 0xf8000000U)
+#endif /* __EP_CFG_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_cfg */
+#ifndef __EP_CMD_MACRO__
+#define __EP_CMD_MACRO__
+
+/* macros for field EPRST */
+#define EP_CMD__EPRST__MASK 0x00000001U
+#define EP_CMD__SSTALL__MASK 0x00000002U
+#define EP_CMD__CSTALL__MASK 0x00000004U
+#define EP_CMD__ERDY__MASK 0x00000008U
+#define EP_CMD__REQ_CMPL__MASK 0x00000020U
+#define EP_CMD__DRDY__MASK 0x00000040U
+#define EP_CMD__DFLUSH__MASK 0x00000080U
+#endif /* __EP_CMD_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_cmd */
+#ifndef __EP_STS_MACRO__
+#define __EP_STS_MACRO__
+
+/* macros for field SETUP */
+#define EP_STS__SETUP__MASK 0x00000001U
+#define EP_STS__STALL__MASK 0x00000002U
+#define EP_STS__IOC__MASK 0x00000004U
+#define EP_STS__ISP__MASK 0x00000008U
+#define EP_STS__DESCMIS__MASK 0x00000010U
+#define EP_STS__TRBERR__MASK 0x00000080U
+#define EP_STS__NRDY__MASK 0x00000100U
+#define EP_STS__DBUSY__MASK 0x00000200U
+#define EP_STS__OUTSMM__MASK 0x00004000U
+#define EP_STS__ISOERR__MASK 0x00008000U
+#endif /* __EP_STS_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_sts */
+#ifndef __EP_STS_SID_MACRO__
+#define __EP_STS_SID_MACRO__
+
+/* macros for field SID */
+#endif /* __EP_STS_SID_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_sts_sid */
+#ifndef __EP_STS_EN_MACRO__
+#define __EP_STS_EN_MACRO__
+
+/* macros for field SETUPEN */
+#define EP_STS_EN__SETUPEN__MASK 0x00000001U
+#define EP_STS_EN__DESCMISEN__MASK 0x00000010U
+#define EP_STS_EN__TRBERREN__MASK 0x00000080U
+#endif /* __EP_STS_EN_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_sts_en */
+#ifndef __DRBL_MACRO__
+#define __DRBL_MACRO__
+
+/* macros for field DRBL0O */
+#endif /* __DRBL_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.drbl */
+#ifndef __EP_IEN_MACRO__
+#define __EP_IEN_MACRO__
+
+/* macros for field EOUTEN0 */
+#define EP_IEN__EOUTEN0__MASK 0x00000001U
+#define EP_IEN__EINEN0__MASK 0x00010000U
+#endif /* __EP_IEN_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_ien */
+#ifndef __EP_ISTS_MACRO__
+#define __EP_ISTS_MACRO__
+
+/* macros for field EOUT0 */
+#define EP_ISTS__EOUT0__MASK 0x00000001U
+#define EP_ISTS__EIN0__MASK 0x00010000U
+#endif /* __EP_ISTS_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.ep_ists */
+#ifndef __USB_PWR_MACRO__
+#define __USB_PWR_MACRO__
+
+/* macros for field PSO_EN */
+#endif /* __USB_PWR_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_pwr */
+#ifndef __USB_CONF2_MACRO__
+#define __USB_CONF2_MACRO__
+
+/* macros for field AHB_RETRY_EN */
+#endif /* __USB_CONF2_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_conf2 */
+#ifndef __USB_CAP1_MACRO__
+#define __USB_CAP1_MACRO__
+
+/* macros for field SFR_TYPE */
+#endif /* __USB_CAP1_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap1 */
+#ifndef __USB_CAP2_MACRO__
+#define __USB_CAP2_MACRO__
+
+/* macros for field ACTUAL_MEM_SIZE */
+#endif /* __USB_CAP2_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap2 */
+#ifndef __USB_CAP3_MACRO__
+#define __USB_CAP3_MACRO__
+
+/* macros for field EPOUT_N */
+#endif /* __USB_CAP3_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap3 */
+#ifndef __USB_CAP4_MACRO__
+#define __USB_CAP4_MACRO__
+
+/* macros for field EPOUTI_N */
+#endif /* __USB_CAP4_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap4 */
+#ifndef __USB_CAP5_MACRO__
+#define __USB_CAP5_MACRO__
+
+/* macros for field EPOUTI_N */
+#endif /* __USB_CAP5_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap5 */
+#ifndef __USB_CAP6_MACRO__
+#define __USB_CAP6_MACRO__
+
+/* macros for field VERSION */
+#endif /* __USB_CAP6_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cap6 */
+#ifndef __USB_CPKT1_MACRO__
+#define __USB_CPKT1_MACRO__
+
+/* macros for field CPKT1 */
+#endif /* __USB_CPKT1_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cpkt1 */
+#ifndef __USB_CPKT2_MACRO__
+#define __USB_CPKT2_MACRO__
+
+/* macros for field CPKT2 */
+#endif /* __USB_CPKT2_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cpkt2 */
+#ifndef __USB_CPKT3_MACRO__
+#define __USB_CPKT3_MACRO__
+
+/* macros for field CPKT3 */
+#endif /* __USB_CPKT3_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.usb_cpkt3 */
+#ifndef __CFG_REG1_MACRO__
+#define __CFG_REG1_MACRO__
+
+/* macros for field DEBOUNCER_CNT */
+#endif /* __CFG_REG1_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg1 */
+#ifndef __DBG_LINK1_MACRO__
+#define __DBG_LINK1_MACRO__
+
+/* macros for field LFPS_MIN_DET_U1_EXIT */
+#define DBG_LINK1__LFPS_MIN_GEN_U1_EXIT__WRITE(src) \
+ (((uint32_t)(src)\
+ << 8) & 0x0000ff00U)
+#define DBG_LINK1__LFPS_MIN_GEN_U1_EXIT_SET__MASK 0x02000000U
+#endif /* __DBG_LINK1_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dbg_link1 */
+#ifndef __DBG_LINK2_MACRO__
+#define __DBG_LINK2_MACRO__
+
+/* macros for field RXEQTR_AVAL */
+#endif /* __DBG_LINK2_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dbg_link2 */
+#ifndef __CFG_REG4_MACRO__
+#define __CFG_REG4_MACRO__
+
+/* macros for field RXDETECT_QUIET_TIMEOUT */
+#endif /* __CFG_REG4_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg4 */
+#ifndef __CFG_REG5_MACRO__
+#define __CFG_REG5_MACRO__
+
+/* macros for field U3_HDSK_FAIL_TIMEOUT */
+#endif /* __CFG_REG5_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg5 */
+#ifndef __CFG_REG6_MACRO__
+#define __CFG_REG6_MACRO__
+
+/* macros for field SSINACTIVE_QUIET_TIMEOUT */
+#endif /* __CFG_REG6_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg6 */
+#ifndef __CFG_REG7_MACRO__
+#define __CFG_REG7_MACRO__
+
+/* macros for field POLLING_LFPS_TIMEOUT */
+#endif /* __CFG_REG7_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg7 */
+#ifndef __CFG_REG8_MACRO__
+#define __CFG_REG8_MACRO__
+
+/* macros for field POLLING_ACTIVE_TIMEOUT */
+#endif /* __CFG_REG8_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg8 */
+#ifndef __CFG_REG9_MACRO__
+#define __CFG_REG9_MACRO__
+
+/* macros for field POLLING_IDLE_TIMEOUT */
+#endif /* __CFG_REG9_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg9 */
+#ifndef __CFG_REG10_MACRO__
+#define __CFG_REG10_MACRO__
+
+/* macros for field POLLING_CONF_TIMEOUT */
+#endif /* __CFG_REG10_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg10 */
+#ifndef __CFG_REG11_MACRO__
+#define __CFG_REG11_MACRO__
+
+/* macros for field RECOVERY_ACTIVE_TIMEOUT */
+#endif /* __CFG_REG11_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg11 */
+#ifndef __CFG_REG12_MACRO__
+#define __CFG_REG12_MACRO__
+
+/* macros for field RECOVERY_CONF_TIMEOUT */
+#endif /* __CFG_REG12_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg12 */
+#ifndef __CFG_REG13_MACRO__
+#define __CFG_REG13_MACRO__
+
+/* macros for field RECOVERY_IDLE_TIMEOUT */
+#endif /* __CFG_REG13_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg13 */
+#ifndef __CFG_REG14_MACRO__
+#define __CFG_REG14_MACRO__
+
+/* macros for field HOTRESET_ACTIVE_TIMEOUT */
+#endif /* __CFG_REG14_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg14 */
+#ifndef __CFG_REG15_MACRO__
+#define __CFG_REG15_MACRO__
+
+/* macros for field HOTRESET_EXIT_TIMEOUT */
+#endif /* __CFG_REG15_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg15 */
+#ifndef __CFG_REG16_MACRO__
+#define __CFG_REG16_MACRO__
+
+/* macros for field LFPS_PING_REPEAT */
+#endif /* __CFG_REG16_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg16 */
+#ifndef __CFG_REG17_MACRO__
+#define __CFG_REG17_MACRO__
+
+/* macros for field PENDING_HP_TIMEOUT */
+#endif /* __CFG_REG17_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg17 */
+#ifndef __CFG_REG18_MACRO__
+#define __CFG_REG18_MACRO__
+
+/* macros for field CREDIT_HP_TIMEOUT */
+#endif /* __CFG_REG18_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg18 */
+#ifndef __CFG_REG19_MACRO__
+#define __CFG_REG19_MACRO__
+
+/* macros for field LUP_TIMEOUT */
+#endif /* __CFG_REG19_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg19 */
+#ifndef __CFG_REG20_MACRO__
+#define __CFG_REG20_MACRO__
+
+/* macros for field LDN_TIMEOUT */
+#endif /* __CFG_REG20_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg20 */
+#ifndef __CFG_REG21_MACRO__
+#define __CFG_REG21_MACRO__
+
+/* macros for field PM_LC_TIMEOUT */
+#endif /* __CFG_REG21_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg21 */
+#ifndef __CFG_REG22_MACRO__
+#define __CFG_REG22_MACRO__
+
+/* macros for field PM_ENTRY_TIMEOUT */
+#endif /* __CFG_REG22_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg22 */
+#ifndef __CFG_REG23_MACRO__
+#define __CFG_REG23_MACRO__
+
+/* macros for field UX_EXIT_TIMEOUT */
+#endif /* __CFG_REG23_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg23 */
+#ifndef __CFG_REG24_MACRO__
+#define __CFG_REG24_MACRO__
+
+/* macros for field LFPS_DET_RESET_MIN */
+#endif /* __CFG_REG24_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg24 */
+#ifndef __CFG_REG25_MACRO__
+#define __CFG_REG25_MACRO__
+
+/* macros for field LFPS_DET_RESET_MAX */
+#endif /* __CFG_REG25_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg25 */
+#ifndef __CFG_REG26_MACRO__
+#define __CFG_REG26_MACRO__
+
+/* macros for field LFPS_DET_POLLING_MIN */
+#endif /* __CFG_REG26_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg26 */
+#ifndef __CFG_REG27_MACRO__
+#define __CFG_REG27_MACRO__
+
+/* macros for field LFPS_DET_POLLING_MAX */
+#endif /* __CFG_REG27_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg27 */
+#ifndef __CFG_REG28_MACRO__
+#define __CFG_REG28_MACRO__
+
+/* macros for field LFPS_DET_PING_MIN */
+#endif /* __CFG_REG28_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg28 */
+#ifndef __CFG_REG29_MACRO__
+#define __CFG_REG29_MACRO__
+
+/* macros for field LFPS_DET_PING_MAX */
+#endif /* __CFG_REG29_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg29 */
+#ifndef __CFG_REG30_MACRO__
+#define __CFG_REG30_MACRO__
+
+/* macros for field LFPS_DET_U1EXIT_MIN */
+#endif /* __CFG_REG30_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg30 */
+#ifndef __CFG_REG31_MACRO__
+#define __CFG_REG31_MACRO__
+
+/* macros for field LFPS_DET_U1EXIT_MAX */
+#endif /* __CFG_REG31_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg31 */
+#ifndef __CFG_REG32_MACRO__
+#define __CFG_REG32_MACRO__
+
+/* macros for field LFPS_DET_U2EXIT_MIN */
+#endif /* __CFG_REG32_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg32 */
+#ifndef __CFG_REG33_MACRO__
+#define __CFG_REG33_MACRO__
+
+/* macros for field LFPS_DET_U2EXIT_MAX */
+#endif /* __CFG_REG33_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg33 */
+#ifndef __CFG_REG34_MACRO__
+#define __CFG_REG34_MACRO__
+
+/* macros for field LFPS_DET_U3EXIT_MIN */
+#endif /* __CFG_REG34_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg34 */
+#ifndef __CFG_REG35_MACRO__
+#define __CFG_REG35_MACRO__
+
+/* macros for field LFPS_DET_U3EXIT_MAX */
+#endif /* __CFG_REG35_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg35 */
+#ifndef __CFG_REG36_MACRO__
+#define __CFG_REG36_MACRO__
+
+/* macros for field LFPS_GEN_PING */
+#endif /* __CFG_REG36_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg36 */
+#ifndef __CFG_REG37_MACRO__
+#define __CFG_REG37_MACRO__
+
+/* macros for field LFPS_GEN_POLLING */
+#endif /* __CFG_REG37_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg37 */
+#ifndef __CFG_REG38_MACRO__
+#define __CFG_REG38_MACRO__
+
+/* macros for field LFPS_GEN_U1EXIT */
+#endif /* __CFG_REG38_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg38 */
+#ifndef __CFG_REG39_MACRO__
+#define __CFG_REG39_MACRO__
+
+/* macros for field LFPS_GEN_U3EXIT */
+#endif /* __CFG_REG39_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg39 */
+#ifndef __CFG_REG40_MACRO__
+#define __CFG_REG40_MACRO__
+
+/* macros for field LFPS_MIN_GEN_U1EXIT */
+#endif /* __CFG_REG40_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg40 */
+#ifndef __CFG_REG41_MACRO__
+#define __CFG_REG41_MACRO__
+
+/* macros for field LFPS_MIN_GEN_U2EXIT */
+#endif /* __CFG_REG41_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg41 */
+#ifndef __CFG_REG42_MACRO__
+#define __CFG_REG42_MACRO__
+
+/* macros for field LFPS_POLLING_REPEAT */
+#endif /* __CFG_REG42_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg42 */
+#ifndef __CFG_REG43_MACRO__
+#define __CFG_REG43_MACRO__
+
+/* macros for field LFPS_POLLING_MAX_TREPEAT */
+#endif /* __CFG_REG43_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg43 */
+#ifndef __CFG_REG44_MACRO__
+#define __CFG_REG44_MACRO__
+
+/* macros for field LFPS_POLLING_MIN_TREPEAT */
+#endif /* __CFG_REG44_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg44 */
+#ifndef __CFG_REG45_MACRO__
+#define __CFG_REG45_MACRO__
+
+/* macros for field ITP_WAKEUP_TIMEOUT */
+#endif /* __CFG_REG45_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg45 */
+#ifndef __CFG_REG46_MACRO__
+#define __CFG_REG46_MACRO__
+
+/* macros for field TSEQ_QUANTITY */
+#endif /* __CFG_REG46_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg46 */
+#ifndef __CFG_REG47_MACRO__
+#define __CFG_REG47_MACRO__
+
+/* macros for field ERDY_TIMEOUT_CNT */
+#endif /* __CFG_REG47_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg47 */
+#ifndef __CFG_REG48_MACRO__
+#define __CFG_REG48_MACRO__
+
+/* macros for field TWTRSTFS_J_CNT */
+#endif /* __CFG_REG48_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg48 */
+#ifndef __CFG_REG49_MACRO__
+#define __CFG_REG49_MACRO__
+
+/* macros for field TUCH_CNT */
+#endif /* __CFG_REG49_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg49 */
+#ifndef __CFG_REG50_MACRO__
+#define __CFG_REG50_MACRO__
+
+/* macros for field TWAITCHK_CNT */
+#endif /* __CFG_REG50_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg50 */
+#ifndef __CFG_REG51_MACRO__
+#define __CFG_REG51_MACRO__
+
+/* macros for field TWTFS_CNT */
+#endif /* __CFG_REG51_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg51 */
+#ifndef __CFG_REG52_MACRO__
+#define __CFG_REG52_MACRO__
+
+/* macros for field TWTREV_CNT */
+#endif /* __CFG_REG52_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg52 */
+#ifndef __CFG_REG53_MACRO__
+#define __CFG_REG53_MACRO__
+
+/* macros for field TWTRSTHS_CNT */
+#endif /* __CFG_REG53_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg53 */
+#ifndef __CFG_REG54_MACRO__
+#define __CFG_REG54_MACRO__
+
+/* macros for field TWTRSM_CNT */
+#endif /* __CFG_REG54_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg54 */
+#ifndef __CFG_REG55_MACRO__
+#define __CFG_REG55_MACRO__
+
+/* macros for field TDRSMUP_CNT */
+#endif /* __CFG_REG55_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg55 */
+#ifndef __CFG_REG56_MACRO__
+#define __CFG_REG56_MACRO__
+
+/* macros for field TOUTHS_CNT */
+#endif /* __CFG_REG56_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg56 */
+#ifndef __CFG_REG57_MACRO__
+#define __CFG_REG57_MACRO__
+
+/* macros for field LFPS_DEB_WIDTH */
+#endif /* __CFG_REG57_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg57 */
+#ifndef __CFG_REG58_MACRO__
+#define __CFG_REG58_MACRO__
+
+/* macros for field LFPS_GEN_U2EXIT */
+#endif /* __CFG_REG58_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg58 */
+#ifndef __CFG_REG59_MACRO__
+#define __CFG_REG59_MACRO__
+
+/* macros for field LFPS_MIN_GEN_U3EXIT */
+#endif /* __CFG_REG59_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg59 */
+#ifndef __CFG_REG60_MACRO__
+#define __CFG_REG60_MACRO__
+
+/* macros for field PORT_CONFIG_TIMEOUT */
+#endif /* __CFG_REG60_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg60 */
+#ifndef __CFG_REG61_MACRO__
+#define __CFG_REG61_MACRO__
+
+/* macros for field LFPS_POL_LFPS_TO_RXEQ */
+#endif /* __CFG_REG61_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg61 */
+#ifndef __CFG_REG62_MACRO__
+#define __CFG_REG62_MACRO__
+
+/* macros for field PHY_TX_LATENCY */
+#endif /* __CFG_REG62_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg62 */
+#ifndef __CFG_REG63_MACRO__
+#define __CFG_REG63_MACRO__
+
+/* macros for field U2_INACTIVITY_TMOUT */
+#endif /* __CFG_REG63_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg63 */
+#ifndef __CFG_REG64_MACRO__
+#define __CFG_REG64_MACRO__
+
+/* macros for field TFILTSE0 */
+#endif /* __CFG_REG64_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg64 */
+#ifndef __CFG_REG65_MACRO__
+#define __CFG_REG65_MACRO__
+
+/* macros for field TFILT */
+#endif /* __CFG_REG65_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg65 */
+#ifndef __CFG_REG66_MACRO__
+#define __CFG_REG66_MACRO__
+
+/* macros for field TWTRSTFS_SE0 */
+#endif /* __CFG_REG66_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.cfg_reg66 */
+#ifndef __DMA_AXI_CTRL_MACRO__
+#define __DMA_AXI_CTRL_MACRO__
+
+/* macros for field MAWPROT */
+#endif /* __DMA_AXI_CTRL_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dma_axi_ctrl */
+#ifndef __DMA_AXI_ID_MACRO__
+#define __DMA_AXI_ID_MACRO__
+
+/* macros for field MAW_ID */
+#endif /* __DMA_AXI_ID_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dma_axi_id */
+#ifndef __DMA_AXI_CAP_MACRO__
+#define __DMA_AXI_CAP_MACRO__
+
+/* macros for field RESERVED0 */
+#endif /* __DMA_AXI_CAP_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dma_axi_cap */
+#ifndef __DMA_AXI_CTRL0_MACRO__
+#define __DMA_AXI_CTRL0_MACRO__
+
+/* macros for field B_MAX */
+#endif /* __DMA_AXI_CTRL0_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dma_axi_ctrl0 */
+#ifndef __DMA_AXI_CTRL1_MACRO__
+#define __DMA_AXI_CTRL1_MACRO__
+
+/* macros for field ROT */
+#endif /* __DMA_AXI_CTRL1_MACRO__ */
+
+
+/* macros for usbss_dev_register_block.dma_axi_ctrl1 */
+#endif /* __REG_USBSS_DEV_ADDR_MAP_MACRO_H__ */
diff --git a/drivers/usb/cdns3/dev-regs-map.h b/drivers/usb/cdns3/dev-regs-map.h
new file mode 100644
index 000000000000..ef9cfe2ff342
--- /dev/null
+++ b/drivers/usb/cdns3/dev-regs-map.h
@@ -0,0 +1,126 @@
+/**
+ * dev-regs-map.h - Cadence USB3 Device register map definition
+ *
+ * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+
+#ifndef __REG_USBSS_DEV_ADDR_MAP_H__
+#define __REG_USBSS_DEV_ADDR_MAP_H__
+
+#include "dev-regs-macro.h"
+
+struct usbss_dev_register_block_type {
+ uint32_t usb_conf; /* 0x0 - 0x4 */
+ uint32_t usb_sts; /* 0x4 - 0x8 */
+ uint32_t usb_cmd; /* 0x8 - 0xc */
+ uint32_t usb_iptn; /* 0xc - 0x10 */
+ uint32_t usb_lpm; /* 0x10 - 0x14 */
+ uint32_t usb_ien; /* 0x14 - 0x18 */
+ uint32_t usb_ists; /* 0x18 - 0x1c */
+ uint32_t ep_sel; /* 0x1c - 0x20 */
+ uint32_t ep_traddr; /* 0x20 - 0x24 */
+ uint32_t ep_cfg; /* 0x24 - 0x28 */
+ uint32_t ep_cmd; /* 0x28 - 0x2c */
+ uint32_t ep_sts; /* 0x2c - 0x30 */
+ uint32_t ep_sts_sid; /* 0x30 - 0x34 */
+ uint32_t ep_sts_en; /* 0x34 - 0x38 */
+ uint32_t drbl; /* 0x38 - 0x3c */
+ uint32_t ep_ien; /* 0x3c - 0x40 */
+ uint32_t ep_ists; /* 0x40 - 0x44 */
+ uint32_t usb_pwr; /* 0x44 - 0x48 */
+ uint32_t usb_conf2; /* 0x48 - 0x4c */
+ uint32_t usb_cap1; /* 0x4c - 0x50 */
+ uint32_t usb_cap2; /* 0x50 - 0x54 */
+ uint32_t usb_cap3; /* 0x54 - 0x58 */
+ uint32_t usb_cap4; /* 0x58 - 0x5c */
+ uint32_t usb_cap5; /* 0x5c - 0x60 */
+ uint32_t PAD2_73; /* 0x60 - 0x64 */
+ uint32_t usb_cpkt1; /* 0x64 - 0x68 */
+ uint32_t usb_cpkt2; /* 0x68 - 0x6c */
+ uint32_t usb_cpkt3; /* 0x6c - 0x70 */
+ char pad__0[0x90]; /* 0x70 - 0x100 */
+ uint32_t PAD2_78; /* 0x100 - 0x104 */
+ uint32_t dbg_link1; /* 0x104 - 0x108 */
+ uint32_t PAD2_80; /* 0x108 - 0x10c */
+ uint32_t PAD2_81; /* 0x10c - 0x110 */
+ uint32_t PAD2_82; /* 0x110 - 0x114 */
+ uint32_t PAD2_83; /* 0x114 - 0x118 */
+ uint32_t PAD2_84; /* 0x118 - 0x11c */
+ uint32_t PAD2_85; /* 0x11c - 0x120 */
+ uint32_t PAD2_86; /* 0x120 - 0x124 */
+ uint32_t PAD2_87; /* 0x124 - 0x128 */
+ uint32_t PAD2_88; /* 0x128 - 0x12c */
+ uint32_t PAD2_89; /* 0x12c - 0x130 */
+ uint32_t PAD2_90; /* 0x130 - 0x134 */
+ uint32_t PAD2_91; /* 0x134 - 0x138 */
+ uint32_t PAD2_92; /* 0x138 - 0x13c */
+ uint32_t PAD2_93; /* 0x13c - 0x140 */
+ uint32_t PAD2_94; /* 0x140 - 0x144 */
+ uint32_t PAD2_95; /* 0x144 - 0x148 */
+ uint32_t PAD2_96; /* 0x148 - 0x14c */
+ uint32_t PAD2_97; /* 0x14c - 0x150 */
+ uint32_t PAD2_98; /* 0x150 - 0x154 */
+ uint32_t PAD2_99; /* 0x154 - 0x158 */
+ uint32_t PAD2_100; /* 0x158 - 0x15c */
+ uint32_t PAD2_101; /* 0x15c - 0x160 */
+ uint32_t PAD2_102; /* 0x160 - 0x164 */
+ uint32_t PAD2_103; /* 0x164 - 0x168 */
+ uint32_t PAD2_104; /* 0x168 - 0x16c */
+ uint32_t PAD2_105; /* 0x16c - 0x170 */
+ uint32_t PAD2_106; /* 0x170 - 0x174 */
+ uint32_t PAD2_107; /* 0x174 - 0x178 */
+ uint32_t PAD2_108; /* 0x178 - 0x17c */
+ uint32_t PAD2_109; /* 0x17c - 0x180 */
+ uint32_t PAD2_110; /* 0x180 - 0x184 */
+ uint32_t PAD2_111; /* 0x184 - 0x188 */
+ uint32_t PAD2_112; /* 0x188 - 0x18c */
+ char pad__1[0x20]; /* 0x18c - 0x1ac */
+ uint32_t PAD2_114; /* 0x1ac - 0x1b0 */
+ uint32_t PAD2_115; /* 0x1b0 - 0x1b4 */
+ uint32_t PAD2_116; /* 0x1b4 - 0x1b8 */
+ uint32_t PAD2_117; /* 0x1b8 - 0x1bc */
+ uint32_t PAD2_118; /* 0x1bc - 0x1c0 */
+ uint32_t PAD2_119; /* 0x1c0 - 0x1c4 */
+ uint32_t PAD2_120; /* 0x1c4 - 0x1c8 */
+ uint32_t PAD2_121; /* 0x1c8 - 0x1cc */
+ uint32_t PAD2_122; /* 0x1cc - 0x1d0 */
+ uint32_t PAD2_123; /* 0x1d0 - 0x1d4 */
+ uint32_t PAD2_124; /* 0x1d4 - 0x1d8 */
+ uint32_t PAD2_125; /* 0x1d8 - 0x1dc */
+ uint32_t PAD2_126; /* 0x1dc - 0x1e0 */
+ uint32_t PAD2_127; /* 0x1e0 - 0x1e4 */
+ uint32_t PAD2_128; /* 0x1e4 - 0x1e8 */
+ uint32_t PAD2_129; /* 0x1e8 - 0x1ec */
+ uint32_t PAD2_130; /* 0x1ec - 0x1f0 */
+ uint32_t PAD2_131; /* 0x1f0 - 0x1f4 */
+ uint32_t PAD2_132; /* 0x1f4 - 0x1f8 */
+ uint32_t PAD2_133; /* 0x1f8 - 0x1fc */
+ uint32_t PAD2_134; /* 0x1fc - 0x200 */
+ uint32_t PAD2_135; /* 0x200 - 0x204 */
+ uint32_t PAD2_136; /* 0x204 - 0x208 */
+ uint32_t PAD2_137; /* 0x208 - 0x20c */
+ uint32_t PAD2_138; /* 0x20c - 0x210 */
+ uint32_t PAD2_139; /* 0x210 - 0x214 */
+ uint32_t PAD2_140; /* 0x214 - 0x218 */
+ uint32_t PAD2_141; /* 0x218 - 0x21c */
+ uint32_t PAD2_142; /* 0x21c - 0x220 */
+ uint32_t PAD2_143; /* 0x220 - 0x224 */
+ uint32_t PAD2_144; /* 0x224 - 0x228 */
+ char pad__2[0xd8]; /* 0x228 - 0x300 */
+ uint32_t dma_axi_ctrl; /* 0x300 - 0x304 */
+ uint32_t PAD2_147; /* 0x304 - 0x308 */
+ uint32_t PAD2_148; /* 0x308 - 0x30c */
+ uint32_t PAD2_149; /* 0x30c - 0x310 */
+ uint32_t PAD2_150; /* 0x310 - 0x314 */
+};
+
+#endif /* __REG_USBSS_DEV_ADDR_MAP_H__ */
diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h
new file mode 100644
index 000000000000..e085ed38ea05
--- /dev/null
+++ b/drivers/usb/cdns3/gadget-export.h
@@ -0,0 +1,36 @@
+/*
+ * gadget-export.h - Gadget Export APIs
+ *
+ * Copyright 2017 NXP
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __CDNS3_GADGET_EXPORT_H
+#define __CDNS3_GADGET_EXPORT_H
+
+#ifdef CONFIG_USB_CDNS3_GADGET
+
+int cdns3_gadget_init(struct cdns3 *cdns);
+void cdns3_gadget_remove(struct cdns3 *cdns);
+#else
+
+static inline int cdns3_gadget_init(struct cdns3 *cdns)
+{
+ return -ENXIO;
+}
+
+static inline void cdns3_gadget_remove(struct cdns3 *cdns)
+{
+
+}
+
+#endif
+
+#endif /* __CDNS3_GADGET_EXPORT_H */
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
new file mode 100644
index 000000000000..a15a89a64a13
--- /dev/null
+++ b/drivers/usb/cdns3/gadget.c
@@ -0,0 +1,2555 @@
+/**
+ * gadget.c - Cadence USB3 Device Core file
+ *
+ * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com
+ * Copyright 2017 NXP
+ *
+ * Authors: Pawel Jez <pjez@cadence.com>,
+ * Konrad Kociolek <konrad@cadence.com>
+ * Peter Chen <peter.chen@nxp.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/composite.h>
+#include <linux/of_platform.h>
+#include <linux/usb/gadget.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/byteorder/generic.h>
+#include <linux/ctype.h>
+
+#include "core.h"
+#include "gadget-export.h"
+#include "gadget.h"
+#include "io.h"
+
+/*-------------------------------------------------------------------------*/
+/* Function declarations */
+
+static void select_ep(struct usb_ss_dev *usb_ss, u32 ep);
+static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep);
+static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep);
+static void cdns_ep0_config(struct usb_ss_dev *usb_ss);
+static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss);
+static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss,
+ dma_addr_t dma_addr, unsigned int length, int erdy);
+static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep);
+static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req, int set);
+static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req);
+static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss);
+static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep);
+static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss,
+ int dir);
+static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss,
+ u32 usb_ists);
+static int usb_ss_gadget_ep0_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc);
+static int usb_ss_gadget_ep0_disable(struct usb_ep *ep);
+static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value);
+static int usb_ss_gadget_ep0_queue(struct usb_ep *ep,
+ struct usb_request *request, gfp_t gfp_flags);
+static int usb_ss_gadget_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc);
+static int usb_ss_gadget_ep_disable(struct usb_ep *ep);
+static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep,
+ gfp_t gfp_flags);
+static void usb_ss_gadget_ep_free_request(struct usb_ep *ep,
+ struct usb_request *request);
+static int usb_ss_gadget_ep_queue(struct usb_ep *ep,
+ struct usb_request *request, gfp_t gfp_flags);
+static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep,
+ struct usb_request *request);
+static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value);
+static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep);
+static int usb_ss_gadget_get_frame(struct usb_gadget *gadget);
+static int usb_ss_gadget_wakeup(struct usb_gadget *gadget);
+static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget,
+ int is_selfpowered);
+static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on);
+static int usb_ss_gadget_udc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver);
+static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget);
+static int usb_ss_init_ep(struct usb_ss_dev *usb_ss);
+static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss);
+static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss);
+static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss);
+static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep);
+static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable);
+static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss);
+static void cdns_enable_u1(struct usb_ss_dev *usb_ss, int enable);
+static void cdns_enable_u2(struct usb_ss_dev *usb_ss, int enable);
+
+static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
+};
+
+static u32 gadget_readl(struct usb_ss_dev *usb_ss, uint32_t __iomem *reg)
+{
+ return cdns_readl(reg);
+}
+
+static void gadget_writel(struct usb_ss_dev *usb_ss,
+ uint32_t __iomem *reg, u32 value)
+{
+ cdns_writel(reg, value);
+}
+
+/**
+ * next_request - returns next request from list
+ * @list: list containing requests
+ *
+ * Returns request or NULL if no requests in list
+ */
+static struct usb_request *next_request(struct list_head *list)
+{
+ if (list_empty(list))
+ return NULL;
+ return list_first_entry(list, struct usb_request, list);
+}
+
+/**
+ * wait_reg_bit - Read reg and compare until equal to specific value
+ * @reg: the register address to read
+ * @value: the value to compare
+ * @wait_value: 0 or 1
+ * @timeout_ms: timeout value in milliseconds, must be larger than 1
+ *
+ * Returns -ETIMEDOUT if timeout occurs
+ */
+static int wait_reg_bit(struct usb_ss_dev *usb_ss, u32 __iomem *reg,
+ u32 value, int wait_value, int timeout_ms)
+{
+ u32 temp;
+
+ WARN_ON(timeout_ms <= 0);
+ timeout_ms *= 100;
+ temp = cdns_readl(reg);
+ while (timeout_ms-- > 0) {
+ if (!!(temp & value) == wait_value)
+ return 0;
+ temp = cdns_readl(reg);
+ udelay(10);
+ }
+
+ dev_err(&usb_ss->dev, "wait register timeout %s\n", __func__);
+ return -ETIMEDOUT;
+}
+
+static int wait_reg_bit_set(struct usb_ss_dev *usb_ss, u32 __iomem *reg,
+ u32 value, int timeout_ms)
+{
+ return wait_reg_bit(usb_ss, reg, value, 1, timeout_ms);
+}
+
+static int wait_reg_bit_clear(struct usb_ss_dev *usb_ss, u32 __iomem *reg,
+ u32 value, int timeout_ms)
+{
+ return wait_reg_bit(usb_ss, reg, value, 0, timeout_ms);
+}
+
+/**
+ * select_ep - selects endpoint
+ * @usb_ss: extended gadget object
+ * @ep: endpoint address
+ */
+static void select_ep(struct usb_ss_dev *usb_ss, u32 ep)
+{
+ if (!usb_ss || !usb_ss->regs) {
+ dev_err(&usb_ss->dev, "Failed to select endpoint!\n");
+ return;
+ }
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sel, ep);
+}
+
+/**
+ * usb_ss_allocate_trb_pool - Allocates TRB's pool for selected endpoint
+ * @usb_ss_ep: extended endpoint object
+ *
+ * Function will return 0 on success or -ENOMEM on allocation error
+ */
+static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ if (!usb_ss_ep->trb_pool) {
+ usb_ss_ep->trb_pool = dma_zalloc_coherent(usb_ss->sysdev,
+ sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM,
+ &usb_ss_ep->trb_pool_dma, GFP_DMA);
+ if (!usb_ss_ep->trb_pool)
+ return -ENOMEM;
+ }
+
+ if (!usb_ss_ep->cpu_addr) {
+ usb_ss_ep->cpu_addr = dma_alloc_coherent(usb_ss->sysdev,
+ CDNS3_UNALIGNED_BUF_SIZE,
+ &usb_ss_ep->dma_addr, GFP_DMA);
+ if (!usb_ss_ep->cpu_addr)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/**
+ * cdns_data_flush - do flush data at onchip buffer
+ * @usb_ss_ep: extended endpoint object
+ *
+ * Endpoint must be selected before call to this function
+ *
+ * Returns zero on success or negative value on failure
+ */
+static int cdns_data_flush(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DFLUSH__MASK);
+ /* wait for DFLUSH cleared */
+ return wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DFLUSH__MASK, 100);
+}
+
+/**
+ * cdns_ep_stall_flush - Stalls and flushes selected endpoint
+ * @usb_ss_ep: extended endpoint object
+ *
+ * Endpoint must be selected before call to this function
+ */
+static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DFLUSH__MASK | EP_CMD__ERDY__MASK |
+ EP_CMD__SSTALL__MASK);
+
+ /* wait for DFLUSH cleared */
+ wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DFLUSH__MASK, 100);
+ usb_ss_ep->stalled_flag = 1;
+}
+
+/**
+ * cdns_ep0_config - Configures default endpoint
+ * @usb_ss: extended gadget object
+ *
+ * Functions sets parameters: maximal packet size and enables interrupts
+ */
+static void cdns_ep0_config(struct usb_ss_dev *usb_ss)
+{
+ u32 reg, max_packet_size = 0;
+
+ switch (usb_ss->gadget.speed) {
+ case USB_SPEED_UNKNOWN:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_0;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(0);
+ break;
+
+ case USB_SPEED_LOW:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_8;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8);
+ break;
+
+ case USB_SPEED_FULL:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+
+ case USB_SPEED_HIGH:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+
+ case USB_SPEED_WIRELESS:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+
+ case USB_SPEED_SUPER:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512;
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+ break;
+
+ case USB_SPEED_SUPER_PLUS:
+ dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n");
+ usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512;
+ cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512;
+ break;
+ }
+
+ /* init ep out */
+ select_ep(usb_ss, USB_DIR_OUT);
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cfg,
+ EP_CFG__ENABLE__MASK |
+ EP_CFG__MAXPKTSIZE__WRITE(max_packet_size));
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en,
+ EP_STS_EN__SETUPEN__MASK |
+ EP_STS_EN__DESCMISEN__MASK |
+ EP_STS_EN__TRBERREN__MASK);
+
+ /* init ep in */
+ select_ep(usb_ss, USB_DIR_IN);
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cfg,
+ EP_CFG__ENABLE__MASK |
+ EP_CFG__MAXPKTSIZE__WRITE(max_packet_size));
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en,
+ EP_STS_EN__SETUPEN__MASK |
+ EP_STS_EN__TRBERREN__MASK);
+
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf);
+ reg |= USB_CONF__U1DS__MASK | USB_CONF__U2DS__MASK;
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg);
+
+ cdns_prepare_setup_packet(usb_ss);
+}
+
+/**
+ * cdns_gadget_unconfig - Unconfigures device controller
+ * @usb_ss: extended gadget object
+ */
+static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss)
+{
+ /* RESET CONFIGURATION */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__CFGRST__MASK);
+
+ cdns_enable_l1(usb_ss, 0);
+ usb_ss->hw_configured_flag = 0;
+ usb_ss->onchip_mem_allocated_size = 0;
+ usb_ss->out_mem_is_allocated = 0;
+}
+
+/**
+ * cdns_ep0_run_transfer - Do transfer on default endpoint hardware
+ * @usb_ss: extended gadget object
+ * @dma_addr: physical address where data is/will be stored
+ * @length: data length
+ * @erdy: set it to 1 when ERDY packet should be sent -
+ * exit from flow control state
+ */
+static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss,
+ dma_addr_t dma_addr, unsigned int length, int erdy)
+{
+ usb_ss->trb_ep0[0] = TRB_SET_DATA_BUFFER_POINTER(dma_addr);
+ usb_ss->trb_ep0[1] = TRB_SET_TRANSFER_LENGTH((u32)length);
+ usb_ss->trb_ep0[2] = TRB_SET_CYCLE_BIT |
+ TRB_SET_INT_ON_COMPLETION | TRB_TYPE_NORMAL;
+
+ dev_dbg(&usb_ss->dev, "DRBL(%02X)\n",
+ usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT);
+
+ select_ep(usb_ss, usb_ss->ep0_data_dir
+ ? USB_DIR_IN : USB_DIR_OUT);
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_traddr,
+ EP_TRADDR__TRADDR__WRITE(usb_ss->trb_ep0_dma));
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DRDY__MASK); /* drbl */
+
+ if (erdy)
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK);
+}
+
+/**
+ * cdns_ep_run_transfer - Do transfer on no-default endpoint hardware
+ * @usb_ss_ep: extended endpoint object
+ *
+ * Returns zero on success or negative value on failure
+ */
+static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep)
+{
+ dma_addr_t trb_dma;
+ struct usb_request *request = next_request(&usb_ss_ep->request_list);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ int sg_iter = 0;
+ struct usb_ss_trb *trb;
+
+ if (request == NULL)
+ return -EINVAL;
+
+ if (request->num_sgs > USB_SS_TRBS_NUM)
+ return -EINVAL;
+
+ dev_dbg(&usb_ss->dev, "DRBL(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+
+ usb_ss_ep->hw_pending_flag = 1;
+ trb_dma = request->dma;
+
+ /* must allocate buffer aligned to 8 */
+ if ((request->dma % ADDR_MODULO_8)) {
+ if (request->length <= CDNS3_UNALIGNED_BUF_SIZE) {
+ memcpy(usb_ss_ep->cpu_addr, request->buf,
+ request->length);
+ trb_dma = usb_ss_ep->dma_addr;
+ } else {
+ return -ENOMEM;
+ }
+ }
+
+ trb = usb_ss_ep->trb_pool;
+
+ do {
+ /* fill TRB */
+ trb->offset0 = TRB_SET_DATA_BUFFER_POINTER(request->num_sgs == 0
+ ? trb_dma : request->sg[sg_iter].dma_address);
+
+ trb->offset4 = TRB_SET_BURST_LENGTH(16) |
+ TRB_SET_TRANSFER_LENGTH(request->num_sgs == 0 ?
+ request->length : request->sg[sg_iter].length);
+
+ trb->offset8 = TRB_SET_CYCLE_BIT
+ | TRB_SET_INT_ON_COMPLETION
+ | TRB_SET_INT_ON_SHORT_PACKET
+ | TRB_TYPE_NORMAL;
+
+ ++sg_iter;
+ ++trb;
+
+ } while (sg_iter < request->num_sgs);
+
+ /* arm transfer on selected endpoint */
+ select_ep(usb_ss_ep->usb_ss,
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_traddr,
+ EP_TRADDR__TRADDR__WRITE(usb_ss_ep->trb_pool_dma));
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__DRDY__MASK); /* DRDY */
+ return 0;
+}
+
+/**
+ * cdns_get_setup_ret - Returns status of handling setup packet
+ * Setup is handled by gadget driver
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns zero on success or negative value on failure
+ */
+static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ int ret;
+
+ spin_unlock(&usb_ss->lock);
+ usb_ss->setup_pending = 1;
+ ret = usb_ss->gadget_driver->setup(&usb_ss->gadget, ctrl_req);
+ usb_ss->setup_pending = 0;
+ spin_lock(&usb_ss->lock);
+ return ret;
+}
+
+static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss)
+{
+ usb_ss->ep0_data_dir = 0;
+ cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 8, 0);
+}
+
+/**
+ * cdns_req_ep0_set_address - Handling of SET_ADDRESS standard USB request
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ enum usb_device_state device_state = usb_ss->gadget.state;
+ u32 reg;
+ u32 addr;
+
+ addr = le16_to_cpu(ctrl_req->wValue);
+
+ if (addr > DEVICE_ADDRESS_MAX) {
+ dev_err(&usb_ss->dev,
+ "Device address (%d) cannot be greater than %d\n",
+ addr, DEVICE_ADDRESS_MAX);
+ return -EINVAL;
+ }
+
+ if (device_state == USB_STATE_CONFIGURED) {
+ dev_err(&usb_ss->dev, "USB device already configured\n");
+ return -EINVAL;
+ }
+
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_cmd);
+
+ gadget_writel(usb_ss, &usb_ss->regs->usb_cmd, reg
+ | USB_CMD__FADDR__WRITE(addr)
+ | USB_CMD__SET_ADDR__MASK);
+
+ usb_gadget_set_state(&usb_ss->gadget,
+ (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT));
+
+ cdns_prepare_setup_packet(usb_ss);
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+ return 0;
+}
+
+/**
+ * cdns_req_ep0_get_status - Handling of GET_STATUS standard USB request
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ u16 usb_status = 0;
+ unsigned int length = 2;
+ u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK;
+ u32 reg;
+
+ switch (recip) {
+
+ case USB_RECIP_DEVICE:
+ /* handling otg features */
+ if (ctrl_req->wIndex == OTG_STS_SELECTOR) {
+ length = 1;
+ usb_status = usb_ss->gadget.host_request_flag;
+ } else {
+
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_sts);
+
+ if (reg & USB_STS__U1ENS__MASK)
+ usb_status |= 1uL << USB_DEV_STAT_U1_ENABLED;
+
+ if (reg & USB_STS__U2ENS__MASK)
+ usb_status |= 1uL << USB_DEV_STAT_U2_ENABLED;
+
+ if (usb_ss->wake_up_flag)
+ usb_status |= 1uL << USB_DEVICE_REMOTE_WAKEUP;
+
+ /* self powered */
+ usb_status |= usb_ss->gadget.is_selfpowered;
+ }
+ break;
+
+ case USB_RECIP_INTERFACE:
+ return cdns_get_setup_ret(usb_ss, ctrl_req);
+
+ case USB_RECIP_ENDPOINT:
+ /* check if endpoint is stalled */
+ select_ep(usb_ss, ctrl_req->wIndex);
+ if (gadget_readl(usb_ss, &usb_ss->regs->ep_sts)
+ & EP_STS__STALL__MASK)
+ usb_status = 1;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ *(u16 *)usb_ss->setup = cpu_to_le16(usb_status);
+
+ usb_ss->actual_ep0_request = NULL;
+ cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, length, 1);
+ return 0;
+}
+
+/**
+ * cdns_req_ep0_handle_feature -
+ * Handling of GET/SET_FEATURE standard USB request
+ *
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ * @set: must be set to 1 for SET_FEATURE request
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req, int set)
+{
+ u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK;
+ struct usb_ss_endpoint *usb_ss_ep;
+ u32 reg;
+ u8 tmode = 0;
+ int ret = 0;
+
+ switch (recip) {
+ case USB_RECIP_DEVICE:
+ switch (ctrl_req->wValue) {
+ case USB_DEVICE_U1_ENABLE:
+ if (usb_ss->gadget.state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+ if (usb_ss->gadget.speed != USB_SPEED_SUPER)
+ return -EINVAL;
+
+ if (set)
+ /* set U1EN */
+ cdns_enable_u1(usb_ss, 1);
+ else
+ /* set U1 disable */
+ cdns_enable_u1(usb_ss, 0);
+ break;
+ case USB_DEVICE_U2_ENABLE:
+ if (usb_ss->gadget.state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+ if (usb_ss->gadget.speed != USB_SPEED_SUPER)
+ return -EINVAL;
+
+ if (set)
+ /* set U2EN */
+ cdns_enable_u2(usb_ss, 1);
+ else
+ /* set U2 disable */
+ cdns_enable_u2(usb_ss, 0);
+ break;
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ break;
+ case USB_DEVICE_A_HNP_SUPPORT:
+ break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ if (!usb_ss->gadget.b_hnp_enable && set)
+ usb_ss->gadget.b_hnp_enable = 1;
+ break;
+ case USB_DEVICE_REMOTE_WAKEUP:
+ usb_ss->wake_up_flag = !!set;
+ break;
+ case USB_DEVICE_TEST_MODE:
+ if (usb_ss->gadget.state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+ if (usb_ss->gadget.speed != USB_SPEED_HIGH &&
+ usb_ss->gadget.speed != USB_SPEED_FULL)
+ return -EINVAL;
+ if (ctrl_req->wLength != 0 ||
+ ctrl_req->bRequestType & USB_DIR_IN) {
+ dev_err(&usb_ss->dev, "req is error\n");
+ return -EINVAL;
+ }
+ tmode = le16_to_cpu(ctrl_req->wIndex) >> 8;
+ switch (tmode) {
+ case TEST_J:
+ case TEST_K:
+ case TEST_SE0_NAK:
+ case TEST_PACKET:
+ reg = gadget_readl(usb_ss,
+ &usb_ss->regs->usb_cmd);
+ tmode -= 1;
+ reg |= USB_CMD__STMODE |
+ USB_CMD__TMODE_SEL(tmode);
+ gadget_writel(usb_ss, &usb_ss->regs->usb_cmd,
+ reg);
+ dev_info(&usb_ss->dev,
+ "set test mode, val=0x%x", reg);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ break;
+ case USB_RECIP_INTERFACE:
+ return cdns_get_setup_ret(usb_ss, ctrl_req);
+ case USB_RECIP_ENDPOINT:
+ select_ep(usb_ss, ctrl_req->wIndex);
+ if (set) {
+ /* set stall */
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__SSTALL__MASK);
+
+ /* handle non zero endpoint software endpoint */
+ if (ctrl_req->wIndex & 0x7F) {
+ usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX(
+ ctrl_req->wIndex)];
+ usb_ss_ep->stalled_flag = 1;
+ }
+ } else {
+ struct usb_request *request;
+
+ if (ctrl_req->wIndex & 0x7F) {
+ if (usb_ss->eps[CAST_EP_ADDR_TO_INDEX(
+ ctrl_req->wIndex)]->wedge_flag)
+ goto jmp_wedge;
+ }
+
+ /* clear stall */
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK);
+ /* wait for EPRST cleared */
+ ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK, 100);
+
+ /* handle non zero endpoint software endpoint */
+ if (ctrl_req->wIndex & 0x7F) {
+ usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX(
+ ctrl_req->wIndex)];
+ usb_ss_ep->stalled_flag = 0;
+
+ request = next_request(
+ &usb_ss_ep->request_list);
+ if (request)
+ cdns_ep_run_transfer(usb_ss_ep);
+ }
+ }
+jmp_wedge:
+ select_ep(usb_ss, 0x00);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+ return ret;
+}
+
+/**
+ * cdns_req_ep0_set_sel - Handling of SET_SEL standard USB request
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ if (usb_ss->gadget.state < USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ if (ctrl_req->wLength != 6) {
+ dev_err(&usb_ss->dev, "Set SEL should be 6 bytes, got %d\n",
+ ctrl_req->wLength);
+ return -EINVAL;
+ }
+
+ usb_ss->ep0_data_dir = 0;
+ usb_ss->actual_ep0_request = NULL;
+ cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 6, 1);
+ return 0;
+}
+
+/**
+ * cdns_req_ep0_set_isoch_delay -
+ * Handling of GET_ISOCH_DELAY standard USB request
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ if (ctrl_req->wIndex || ctrl_req->wLength)
+ return -EINVAL;
+
+ usb_ss->isoch_delay = ctrl_req->wValue;
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+ return 0;
+}
+
+static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable)
+{
+ if (enable)
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__L1EN__MASK);
+ else
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__L1DS__MASK);
+}
+
+static void cdns_enable_u1(struct usb_ss_dev *usb_ss, int enable)
+{
+ if (enable)
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__U1EN__MASK);
+ else
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__U1DS__MASK);
+}
+
+static void cdns_enable_u2(struct usb_ss_dev *usb_ss, int enable)
+{
+ if (enable)
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__U2EN__MASK);
+ else
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__U2DS__MASK);
+}
+
+/**
+ * cdns_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, 0x7FFF on deferred status stage, error code on error
+ */
+static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ enum usb_device_state device_state = usb_ss->gadget.state;
+ u32 config = le16_to_cpu(ctrl_req->wValue);
+ struct usb_ep *ep;
+ struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep;
+ int i, result = 0;
+
+ switch (device_state) {
+ case USB_STATE_ADDRESS:
+ /* Configure non-control EPs */
+ list_for_each_entry_safe(usb_ss_ep, temp_ss_ep,
+ &usb_ss->ep_match_list, ep_match_pending_list)
+ cdns_ep_config(usb_ss_ep);
+
+ result = cdns_get_setup_ret(usb_ss, ctrl_req);
+
+ if (result != 0)
+ return result;
+
+ if (config) {
+ if (!usb_ss->hw_configured_flag) {
+ /* SET CONFIGURATION */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__CFGSET__MASK);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK |
+ EP_CMD__REQ_CMPL__MASK);
+ /* wait until configuration set */
+ result = wait_reg_bit_set(usb_ss,
+ &usb_ss->regs->usb_sts,
+ USB_STS__CFGSTS__MASK, 100);
+ usb_ss->hw_configured_flag = 1;
+ cdns_enable_l1(usb_ss, 1);
+ if (usb_ss->gadget.speed == USB_SPEED_SUPER) {
+ cdns_enable_u1(usb_ss, 1);
+ cdns_enable_u2(usb_ss, 1);
+ }
+
+ list_for_each_entry(ep,
+ &usb_ss->gadget.ep_list,
+ ep_list) {
+ if (ep->enabled)
+ cdns_ep_run_transfer(
+ to_usb_ss_ep(ep));
+ }
+ }
+ } else {
+ cdns_gadget_unconfig(usb_ss);
+ for (i = 0; i < usb_ss->ep_nums; i++)
+ usb_ss->eps[i]->endpoint.enabled = 0;
+ usb_gadget_set_state(&usb_ss->gadget,
+ USB_STATE_ADDRESS);
+ }
+ break;
+ case USB_STATE_CONFIGURED:
+ result = cdns_get_setup_ret(usb_ss, ctrl_req);
+ if (!config && !result) {
+ cdns_gadget_unconfig(usb_ss);
+ for (i = 0; i < usb_ss->ep_nums; i++)
+ usb_ss->eps[i]->endpoint.enabled = 0;
+ usb_gadget_set_state(&usb_ss->gadget,
+ USB_STATE_ADDRESS);
+ }
+ break;
+ default:
+ result = -EINVAL;
+ }
+
+ return result;
+}
+
+/**
+ * cdns_ep0_standard_request - Handling standard USB requests
+ * @usb_ss: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss,
+ struct usb_ctrlrequest *ctrl_req)
+{
+ switch (ctrl_req->bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ return cdns_req_ep0_set_address(usb_ss, ctrl_req);
+ case USB_REQ_SET_CONFIGURATION:
+ return cdns_req_ep0_set_configuration(usb_ss, ctrl_req);
+ case USB_REQ_GET_STATUS:
+ return cdns_req_ep0_get_status(usb_ss, ctrl_req);
+ case USB_REQ_CLEAR_FEATURE:
+ return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 0);
+ case USB_REQ_SET_FEATURE:
+ return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 1);
+ case USB_REQ_SET_SEL:
+ return cdns_req_ep0_set_sel(usb_ss, ctrl_req);
+ case USB_REQ_SET_ISOCH_DELAY:
+ return cdns_req_ep0_set_isoch_delay(usb_ss, ctrl_req);
+ default:
+ return cdns_get_setup_ret(usb_ss, ctrl_req);
+ }
+}
+
+/**
+ * cdns_ep0_setup_phase - Handling setup USB requests
+ * @usb_ss: extended gadget object
+ */
+static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss)
+{
+ int result;
+ struct usb_ctrlrequest *ctrl_req =
+ (struct usb_ctrlrequest *)usb_ss->setup;
+
+ if ((ctrl_req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
+ result = cdns_ep0_standard_request(usb_ss, ctrl_req);
+ else
+ result = cdns_get_setup_ret(usb_ss, ctrl_req);
+
+ if (result != 0 && result != USB_GADGET_DELAYED_STATUS) {
+ dev_dbg(&usb_ss->dev, "STALL(00) %d\n", result);
+ /* set_stall on ep0 */
+ select_ep(usb_ss, 0x00);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__SSTALL__MASK);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+ }
+}
+
+/**
+ * cdns_check_ep_interrupt_proceed - Processes interrupt related to endpoint
+ * @usb_ss_ep: extended endpoint object
+ *
+ * Returns 0
+ */
+static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ struct usb_request *request;
+ u32 ep_sts_reg;
+
+ select_ep(usb_ss, usb_ss_ep->endpoint.address);
+ ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts);
+
+ dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg);
+
+ if (ep_sts_reg & EP_STS__TRBERR__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK);
+
+ dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ }
+
+ if (ep_sts_reg & EP_STS__ISOERR__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__ISOERR__MASK);
+ dev_dbg(&usb_ss->dev, "ISOERR(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ }
+
+ if (ep_sts_reg & EP_STS__OUTSMM__MASK) {
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts,
+ EP_STS__OUTSMM__MASK);
+ dev_dbg(&usb_ss->dev, "OUTSMM(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ }
+
+ if (ep_sts_reg & EP_STS__NRDY__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__NRDY__MASK);
+ dev_dbg(&usb_ss->dev, "NRDY(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ }
+
+ if ((ep_sts_reg & EP_STS__IOC__MASK)
+ || (ep_sts_reg & EP_STS__ISP__MASK)) {
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts,
+ EP_STS__IOC__MASK | EP_STS__ISP__MASK);
+
+ /* get just completed request */
+ request = next_request(&usb_ss_ep->request_list);
+ if (!request)
+ return 0;
+
+ if ((request->dma % ADDR_MODULO_8) &&
+ (usb_ss_ep->dir == USB_DIR_OUT))
+ memcpy(request->buf, usb_ss_ep->cpu_addr,
+ request->length);
+
+ usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request,
+ usb_ss_ep->endpoint.desc->bEndpointAddress
+ & ENDPOINT_DIR_MASK);
+
+ request->status = 0;
+ request->actual =
+ le32_to_cpu(((u32 *) usb_ss_ep->trb_pool)[1])
+ & ACTUAL_TRANSFERRED_BYTES_MASK;
+
+ dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress,
+ request->actual);
+
+ list_del(&request->list);
+
+ usb_ss_ep->hw_pending_flag = 0;
+ if (request->complete) {
+ spin_unlock(&usb_ss->lock);
+ usb_gadget_giveback_request(&usb_ss_ep->endpoint,
+ request);
+ spin_lock(&usb_ss->lock);
+ }
+
+ if (request->buf == usb_ss->zlp_buf)
+ kfree(request);
+
+ /* handle deferred STALL */
+ if (usb_ss_ep->stalled_flag) {
+ cdns_ep_stall_flush(usb_ss_ep);
+ return 0;
+ }
+
+ /* exit if hardware transfer already started */
+ if (usb_ss_ep->hw_pending_flag)
+ return 0;
+
+ /* if any request queued run it! */
+ if (!list_empty(&usb_ss_ep->request_list))
+ cdns_ep_run_transfer(usb_ss_ep);
+ }
+
+ if (ep_sts_reg & EP_STS__DESCMIS__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK);
+ dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n",
+ usb_ss_ep->endpoint.desc->bEndpointAddress);
+ }
+
+ return 0;
+}
+
+/**
+ * cdns_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
+ * @usb_ss: extended gadget object
+ * @dir: 1 for IN direction, 0 for OUT direction
+ */
+static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss, int dir)
+{
+ u32 ep_sts_reg;
+ int i;
+
+ select_ep(usb_ss, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
+ ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts);
+
+ dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg);
+
+ __pending_setup_status_handler(usb_ss);
+
+ if ((ep_sts_reg & EP_STS__SETUP__MASK) && (dir == 0)) {
+ dev_dbg(&usb_ss->dev, "SETUP(%02X)\n", 0x00);
+
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts,
+ EP_STS__SETUP__MASK |
+ EP_STS__IOC__MASK | EP_STS__ISP__MASK);
+
+ dev_dbg(&usb_ss->dev, "SETUP: ");
+ for (i = 0; i < 8; i++)
+ dev_dbg(&usb_ss->dev, "%02X ", usb_ss->setup[i]);
+ dev_dbg(&usb_ss->dev, "\nSTATE: %d\n", usb_ss->gadget.state);
+ usb_ss->ep0_data_dir = usb_ss->setup[0] & USB_DIR_IN;
+ cdns_ep0_setup_phase(usb_ss);
+ ep_sts_reg &= ~(EP_STS__SETUP__MASK |
+ EP_STS__IOC__MASK |
+ EP_STS__ISP__MASK);
+ }
+
+ if (ep_sts_reg & EP_STS__TRBERR__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK);
+ dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n",
+ dir ? USB_DIR_IN : USB_DIR_OUT);
+ }
+
+ if (ep_sts_reg & EP_STS__DESCMIS__MASK) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK);
+
+ dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n",
+ dir ? USB_DIR_IN : USB_DIR_OUT);
+
+ if (dir == 0 && !usb_ss->setup_pending) {
+ usb_ss->ep0_data_dir = 0;
+ cdns_ep0_run_transfer(usb_ss,
+ usb_ss->setup_dma, 8, 0);
+ }
+ }
+
+ if ((ep_sts_reg & EP_STS__IOC__MASK)
+ || (ep_sts_reg & EP_STS__ISP__MASK)) {
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_sts, EP_STS__IOC__MASK);
+ if (usb_ss->actual_ep0_request) {
+ usb_gadget_unmap_request_by_dev(usb_ss->sysdev,
+ usb_ss->actual_ep0_request,
+ usb_ss->ep0_data_dir);
+
+ usb_ss->actual_ep0_request->actual =
+ le32_to_cpu((usb_ss->trb_ep0)[1])
+ & ACTUAL_TRANSFERRED_BYTES_MASK;
+
+ dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n",
+ dir ? USB_DIR_IN : USB_DIR_OUT,
+ usb_ss->actual_ep0_request->actual);
+ list_del_init(&usb_ss->actual_ep0_request->list);
+ }
+
+ if (usb_ss->actual_ep0_request
+ && usb_ss->actual_ep0_request->complete) {
+ spin_unlock(&usb_ss->lock);
+ usb_ss->actual_ep0_request->complete(usb_ss->gadget.ep0,
+ usb_ss->actual_ep0_request);
+ spin_lock(&usb_ss->lock);
+ }
+ cdns_prepare_setup_packet(usb_ss);
+ gadget_writel(usb_ss,
+ &usb_ss->regs->ep_cmd, EP_CMD__REQ_CMPL__MASK);
+ }
+}
+
+/**
+ * cdns_check_usb_interrupt_proceed - Processes interrupt related to device
+ * @usb_ss: extended gadget object
+ * @usb_ists: bitmap representation of device's reported interrupts
+ * (usb_ists register value)
+ */
+static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss,
+ u32 usb_ists)
+{
+ int interrupt_bit = ffs(usb_ists) - 1;
+ int speed;
+ u32 val;
+
+ dev_dbg(&usb_ss->dev, "USB interrupt detected\n");
+
+ switch (interrupt_bit) {
+ case USB_ISTS__CON2I__SHIFT:
+ /* FS/HS Connection detected */
+ dev_dbg(&usb_ss->dev,
+ "[Interrupt] FS/HS Connection detected\n");
+ val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts);
+ speed = USB_STS__USBSPEED__READ(val);
+ if (speed == USB_SPEED_WIRELESS)
+ speed = USB_SPEED_SUPER;
+ dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n",
+ usb_speed_string(speed), speed, val);
+ usb_ss->gadget.speed = speed;
+ usb_ss->is_connected = 1;
+ usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED);
+ cdns_ep0_config(usb_ss);
+ break;
+ case USB_ISTS__CONI__SHIFT:
+ /* SS Connection detected */
+ dev_dbg(&usb_ss->dev, "[Interrupt] SS Connection detected\n");
+ val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts);
+ speed = USB_STS__USBSPEED__READ(val);
+ if (speed == USB_SPEED_WIRELESS)
+ speed = USB_SPEED_SUPER;
+ dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n",
+ usb_speed_string(speed), speed, val);
+ usb_ss->gadget.speed = speed;
+ usb_ss->is_connected = 1;
+ usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED);
+ cdns_ep0_config(usb_ss);
+ break;
+ case USB_ISTS__DIS2I__SHIFT:
+ case USB_ISTS__DISI__SHIFT:
+ /* SS Disconnection detected */
+ val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts);
+ dev_dbg(&usb_ss->dev,
+ "[Interrupt] Disconnection detected: usbsts:0x%x\n",
+ val);
+ if (usb_ss->gadget_driver
+ && usb_ss->gadget_driver->disconnect) {
+
+ spin_unlock(&usb_ss->lock);
+ usb_ss->gadget_driver->disconnect(&usb_ss->gadget);
+ spin_lock(&usb_ss->lock);
+ }
+ usb_ss->gadget.speed = USB_SPEED_UNKNOWN;
+ usb_gadget_set_state(&usb_ss->gadget, USB_STATE_NOTATTACHED);
+ usb_ss->is_connected = 0;
+ cdns_gadget_unconfig(usb_ss);
+ break;
+ case USB_ISTS__L2ENTI__SHIFT:
+ dev_dbg(&usb_ss->dev,
+ "[Interrupt] Device suspended\n");
+ break;
+ case USB_ISTS__L2EXTI__SHIFT:
+ dev_dbg(&usb_ss->dev, "[Interrupt] L2 exit detected\n");
+ /*
+ * Exit from standby mode
+ * on L2 exit (Suspend in HS/FS or SS)
+ */
+ break;
+ case USB_ISTS__U3EXTI__SHIFT:
+ /*
+ * Exit from standby mode
+ * on U3 exit (Suspend in HS/FS or SS)
+ */
+ dev_dbg(&usb_ss->dev, "[Interrupt] U3 exit detected\n");
+ break;
+
+ /* resets cases */
+ case USB_ISTS__UWRESI__SHIFT:
+ case USB_ISTS__UHRESI__SHIFT:
+ case USB_ISTS__U2RESI__SHIFT:
+ dev_dbg(&usb_ss->dev, "[Interrupt] Reset detected\n");
+ speed = USB_STS__USBSPEED__READ(
+ gadget_readl(usb_ss, &usb_ss->regs->usb_sts));
+ if (speed == USB_SPEED_WIRELESS)
+ speed = USB_SPEED_SUPER;
+ usb_gadget_set_state(&usb_ss->gadget, USB_STATE_DEFAULT);
+ usb_ss->gadget.speed = speed;
+ cdns_gadget_unconfig(usb_ss);
+ cdns_ep0_config(usb_ss);
+ break;
+ default:
+ break;
+ }
+
+ /* Clear interrupt bit */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_ists, (1uL << interrupt_bit));
+}
+
+/**
+ * cdns_irq_handler - irq line interrupt handler
+ * @cdns: cdns3 instance
+ *
+ * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV,
+ * IRQ_NONE when interrupt raised by other device connected
+ * to the irq line
+ */
+static irqreturn_t cdns_irq_handler_thread(struct cdns3 *cdns)
+{
+ struct usb_ss_dev *usb_ss =
+ container_of(cdns->gadget_dev, struct usb_ss_dev, dev);
+ u32 reg;
+ enum irqreturn ret = IRQ_NONE;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+
+ /* check USB device interrupt */
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_ists);
+ if (reg) {
+ dev_dbg(&usb_ss->dev, "usb_ists: %08X\n", reg);
+ cdns_check_usb_interrupt_proceed(usb_ss, reg);
+ ret = IRQ_HANDLED;
+ }
+
+ /* check endpoint interrupt */
+ reg = gadget_readl(usb_ss, &usb_ss->regs->ep_ists);
+ if (reg != 0) {
+ dev_dbg(&usb_ss->dev, "ep_ists: %08X\n", reg);
+ } else {
+ if (gadget_readl(usb_ss, &usb_ss->regs->usb_sts) &
+ USB_STS__CFGSTS__MASK)
+ ret = IRQ_HANDLED;
+ goto irqend;
+ }
+
+ /* handle default endpoint OUT */
+ if (reg & EP_ISTS__EOUT0__MASK) {
+ cdns_check_ep0_interrupt_proceed(usb_ss, 0);
+ ret = IRQ_HANDLED;
+ }
+
+ /* handle default endpoint IN */
+ if (reg & EP_ISTS__EIN0__MASK) {
+ cdns_check_ep0_interrupt_proceed(usb_ss, 1);
+ ret = IRQ_HANDLED;
+ }
+
+ /* check if interrupt from non default endpoint, if no exit */
+ reg &= ~(EP_ISTS__EOUT0__MASK | EP_ISTS__EIN0__MASK);
+ if (!reg)
+ goto irqend;
+
+ do {
+ unsigned int bit_pos = ffs(reg);
+ u32 bit_mask = 1 << (bit_pos - 1);
+
+ dev_dbg(&usb_ss->dev, "Interrupt on index: %d bitmask %08X\n",
+ CAST_EP_REG_POS_TO_INDEX(bit_pos), bit_mask);
+ cdns_check_ep_interrupt_proceed(
+ usb_ss->eps[CAST_EP_REG_POS_TO_INDEX(bit_pos)]);
+ reg &= ~bit_mask;
+ ret = IRQ_HANDLED;
+ } while (reg);
+
+irqend:
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return ret;
+}
+
+/**
+ * usb_ss_gadget_ep0_enable
+ * Function shouldn't be called by gadget driver,
+ * endpoint 0 is allways active
+ */
+static int usb_ss_gadget_ep0_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ return -EINVAL;
+}
+
+/**
+ * usb_ss_gadget_ep0_disable
+ * Function shouldn't be called by gadget driver,
+ * endpoint 0 is allways active
+ */
+static int usb_ss_gadget_ep0_disable(struct usb_ep *ep)
+{
+ return -EINVAL;
+}
+
+/**
+ * usb_ss_gadget_ep0_set_halt
+ * @ep: pointer to endpoint zero object
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Returns 0
+ */
+static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value)
+{
+ /* TODO */
+ return 0;
+}
+
+static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss)
+{
+ struct usb_request *request = usb_ss->pending_status_request;
+
+ if (usb_ss->status_completion_no_call && request && request->complete) {
+ request->complete(usb_ss->gadget.ep0, request);
+ usb_ss->status_completion_no_call = 0;
+ }
+}
+
+static void pending_setup_status_handler(struct work_struct *work)
+{
+ struct usb_ss_dev *usb_ss = container_of(work, struct usb_ss_dev,
+ pending_status_wq);
+ unsigned long flags;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ __pending_setup_status_handler(usb_ss);
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+}
+
+/**
+ * usb_ss_gadget_ep0_queue Transfer data on endpoint zero
+ * @ep: pointer to endpoint zero object
+ * @request: pointer to request object
+ * @gfp_flags: gfp flags
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_ep0_queue(struct usb_ep *ep,
+ struct usb_request *request, gfp_t gfp_flags)
+{
+ int ret = 0;
+ unsigned long flags;
+ int erdy_sent = 0;
+ /* get extended endpoint */
+ struct usb_ss_endpoint *usb_ss_ep =
+ to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n",
+ usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT,
+ request->length);
+
+ /* send STATUS stage */
+ if (request->length == 0 && request->zero == 0) {
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ select_ep(usb_ss, 0x00);
+ if (!usb_ss->hw_configured_flag) {
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__CFGSET__MASK); /* SET CONFIGURATION */
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+ /* wait until configuration set */
+ ret = wait_reg_bit_set(usb_ss, &usb_ss->regs->usb_sts,
+ USB_STS__CFGSTS__MASK, 100);
+ erdy_sent = 1;
+ usb_ss->hw_configured_flag = 1;
+ cdns_enable_l1(usb_ss, 1);
+ /* Enable U1/U2 at Configuration state */
+ if (usb_ss->gadget.speed == USB_SPEED_SUPER) {
+ cdns_enable_u1(usb_ss, 1);
+ cdns_enable_u2(usb_ss, 1);
+ }
+
+ list_for_each_entry(ep,
+ &usb_ss->gadget.ep_list,
+ ep_list) {
+
+ if (ep->enabled)
+ cdns_ep_run_transfer(
+ to_usb_ss_ep(ep));
+ }
+ }
+ if (!erdy_sent)
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK);
+
+ cdns_prepare_setup_packet(usb_ss);
+ request->actual = 0;
+ usb_ss->status_completion_no_call = true;
+ usb_ss->pending_status_request = request;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ /*
+ * Since there is no completion interrupt for status stage,
+ * it needs to call ->completion in software after
+ * ep0_queue is back.
+ */
+ queue_work(system_freezable_wq, &usb_ss->pending_status_wq);
+ return 0;
+ }
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ if (!list_empty(&usb_ss_ep->request_list)) {
+ dev_err(&usb_ss->dev,
+ "can't handle multiple requests for ep0\n");
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return -EOPNOTSUPP;
+ }
+
+ ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request,
+ usb_ss->ep0_data_dir);
+ if (ret) {
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ dev_err(&usb_ss->dev, "failed to map request\n");
+ return -EINVAL;
+ }
+
+ usb_ss->actual_ep0_request = request;
+ cdns_ep0_run_transfer(usb_ss, request->dma, request->length, 1);
+ list_add_tail(&request->list, &usb_ss_ep->request_list);
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return ret;
+}
+/**
+ * ep_onchip_buffer_alloc - Try to allocate onchip buf for EP
+ *
+ * The real allocation will occur during write to EP_CFG register,
+ * this function is used to check if the 'size' allocation is allowed.
+ *
+ * @usb_ss: extended gadget object
+ * @size: the size (KB) for EP would like to allocate
+ * @is_in: the direction for EP
+ *
+ * Return 0 if the later allocation is allowed or negative value on failure
+ */
+
+static int ep_onchip_buffer_alloc(struct usb_ss_dev *usb_ss,
+ int size, int is_in)
+{
+ if (is_in) {
+ usb_ss->onchip_mem_allocated_size += size;
+ } else if (!usb_ss->out_mem_is_allocated) {
+ /* ALL OUT EPs are shared the same chunk onchip memory */
+ usb_ss->onchip_mem_allocated_size += size;
+ usb_ss->out_mem_is_allocated = 1;
+ }
+
+ if (usb_ss->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) {
+ usb_ss->onchip_mem_allocated_size -= size;
+ return -EPERM;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * cdns_ep_config Configure hardware endpoint
+ * @usb_ss_ep: extended endpoint object
+ */
+static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ u32 ep_cfg = 0;
+ u32 max_packet_size = 0;
+ u32 bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir;
+ u32 interrupt_mask = 0;
+ int is_in = !!usb_ss_ep->dir;
+ bool is_iso_ep = (usb_ss_ep->type == USB_ENDPOINT_XFER_ISOC);
+ int default_buf_size = CDNS3_EP_BUF_SIZE;
+
+ dev_dbg(&usb_ss->dev, "%s: %s addr=0x%x\n", __func__,
+ usb_ss_ep->name, bEndpointAddress);
+
+ if (is_iso_ep) {
+ ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_ISOC);
+ interrupt_mask = INTERRUPT_MASK;
+ } else {
+ ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_BULK);
+ }
+
+ switch (usb_ss->gadget.speed) {
+ case USB_SPEED_UNKNOWN:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0;
+ break;
+ case USB_SPEED_LOW:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8;
+ break;
+ case USB_SPEED_FULL:
+ max_packet_size = (is_iso_ep ?
+ ENDPOINT_MAX_PACKET_SIZE_1023 :
+ ENDPOINT_MAX_PACKET_SIZE_64);
+ break;
+ case USB_SPEED_HIGH:
+ max_packet_size = (is_iso_ep ?
+ ENDPOINT_MAX_PACKET_SIZE_1024 :
+ ENDPOINT_MAX_PACKET_SIZE_512);
+ break;
+ case USB_SPEED_WIRELESS:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512;
+ break;
+ case USB_SPEED_SUPER:
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024;
+ break;
+ case USB_SPEED_SUPER_PLUS:
+ dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n");
+ max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024;
+ break;
+ }
+
+ if (ep_onchip_buffer_alloc(usb_ss, default_buf_size, is_in)) {
+ dev_err(&usb_ss->dev, "onchip mem is full, ep is invalid\n");
+ return;
+ }
+
+ ep_cfg |= EP_CFG__MAXPKTSIZE__WRITE(max_packet_size) |
+ EP_CFG__BUFFERING__WRITE(default_buf_size - 1) |
+ EP_CFG__MAXBURST__WRITE(usb_ss_ep->endpoint.maxburst);
+
+ select_ep(usb_ss, bEndpointAddress);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en,
+ EP_STS_EN__TRBERREN__MASK | interrupt_mask);
+
+ /* enable interrupt for selected endpoint */
+ ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_ien);
+ ep_cfg |= CAST_EP_ADDR_TO_BIT_POS(bEndpointAddress);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_ien, ep_cfg);
+}
+
+/**
+ * usb_ss_gadget_ep_enable Enable endpoint
+ * @ep: endpoint object
+ * @desc: endpoint descriptor
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct usb_ss_endpoint *usb_ss_ep;
+ struct usb_ss_dev *usb_ss;
+ unsigned long flags;
+ int ret;
+ u32 ep_cfg;
+
+ usb_ss_ep = to_usb_ss_ep(ep);
+ usb_ss = usb_ss_ep->usb_ss;
+
+ if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) {
+ dev_err(&usb_ss->dev, "usb-ss: invalid parameters\n");
+ return -EINVAL;
+ }
+
+ if (!desc->wMaxPacketSize) {
+ dev_err(&usb_ss->dev, "usb-ss: missing wMaxPacketSize\n");
+ return -EINVAL;
+ }
+
+ ret = usb_ss_allocate_trb_pool(usb_ss_ep);
+ if (ret)
+ return ret;
+
+ dev_dbg(&usb_ss->dev, "Enabling endpoint: %s, addr=0x%x\n",
+ ep->name, desc->bEndpointAddress);
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ select_ep(usb_ss, desc->bEndpointAddress);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK);
+ ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK, 100);
+ ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg);
+ ep_cfg |= EP_CFG__ENABLE__MASK;
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg);
+
+ ep->enabled = 1;
+ ep->desc = desc;
+ usb_ss_ep->hw_pending_flag = 0;
+ usb_ss_ep->stalled_flag = 0;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+}
+
+/* Find correct direction for HW endpoint according to description */
+static int ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
+ struct usb_ss_endpoint *usb_ss_ep)
+{
+ return (usb_ss_ep->endpoint.caps.dir_in &&
+ !!(desc->bEndpointAddress & USB_DIR_IN))
+ || (usb_ss_ep->endpoint.caps.dir_out
+ && ((desc->bEndpointAddress & 0x80) == USB_DIR_OUT));
+}
+
+static struct usb_ss_endpoint *find_available_ss_ep(
+ struct usb_ss_dev *usb_ss,
+ struct usb_endpoint_descriptor *desc)
+{
+ struct usb_ep *ep;
+ struct usb_ss_endpoint *usb_ss_ep;
+
+ list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) {
+ unsigned long num;
+ int ret;
+ /* ep name pattern likes epXin or epXout */
+ char c[2] = {ep->name[2], '\0'};
+
+ ret = kstrtoul(c, 10, &num);
+ if (ret)
+ return ERR_PTR(ret);
+
+ usb_ss_ep = to_usb_ss_ep(ep);
+ if (ep_dir_is_correct(desc, usb_ss_ep)) {
+ if (!usb_ss_ep->used) {
+ usb_ss_ep->num = num;
+ usb_ss_ep->used = true;
+ return usb_ss_ep;
+ }
+ }
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+static struct usb_ep *usb_ss_gadget_match_ep(struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc,
+ struct usb_ss_ep_comp_descriptor *comp_desc)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+ struct usb_ss_endpoint *usb_ss_ep;
+ unsigned long flags;
+
+ usb_ss_ep = find_available_ss_ep(usb_ss, desc);
+ if (IS_ERR(usb_ss_ep)) {
+ dev_err(&usb_ss->dev, "no available ep\n");
+ return NULL;
+ }
+
+ dev_dbg(&usb_ss->dev, "match endpoint: %s\n", usb_ss_ep->name);
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ usb_ss_ep->endpoint.desc = desc;
+ usb_ss_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
+ usb_ss_ep->type = usb_endpoint_type(desc);
+
+ list_add_tail(&usb_ss_ep->ep_match_pending_list,
+ &usb_ss->ep_match_list);
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return &usb_ss_ep->endpoint;
+}
+
+static void usb_ss_free_trb_pool(struct usb_ss_endpoint *usb_ss_ep)
+{
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ if (usb_ss_ep->trb_pool) {
+ dma_free_coherent(usb_ss->sysdev,
+ sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM,
+ usb_ss_ep->trb_pool, usb_ss_ep->trb_pool_dma);
+ usb_ss_ep->trb_pool = NULL;
+ }
+
+ if (usb_ss_ep->cpu_addr) {
+ dma_free_coherent(usb_ss->sysdev, CDNS3_UNALIGNED_BUF_SIZE,
+ usb_ss_ep->cpu_addr, usb_ss_ep->dma_addr);
+ usb_ss_ep->cpu_addr = NULL;
+ }
+}
+
+/**
+ * usb_ss_gadget_ep_disable Disable endpoint
+ * @ep: endpoint object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_ep_disable(struct usb_ep *ep)
+{
+ struct usb_ss_endpoint *usb_ss_ep;
+ struct usb_ss_dev *usb_ss;
+ unsigned long flags;
+ int ret = 0;
+ struct usb_request *request;
+ u32 ep_cfg;
+
+ if (!ep) {
+ pr_debug("usb-ss: invalid parameters\n");
+ return -EINVAL;
+ }
+
+ usb_ss_ep = to_usb_ss_ep(ep);
+ usb_ss = usb_ss_ep->usb_ss;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ if (!usb_ss->start_gadget) {
+ dev_dbg(&usb_ss->dev,
+ "Disabling endpoint at disconnection: %s\n", ep->name);
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+ }
+
+ dev_dbg(&usb_ss->dev,
+ "Disabling endpoint: %s\n", ep->name);
+
+ select_ep(usb_ss, ep->desc->bEndpointAddress);
+ ret = cdns_data_flush(usb_ss_ep);
+ while (!list_empty(&usb_ss_ep->request_list)) {
+
+ request = next_request(&usb_ss_ep->request_list);
+ usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request,
+ ep->desc->bEndpointAddress & USB_DIR_IN);
+ request->status = -ESHUTDOWN;
+ list_del(&request->list);
+ spin_unlock(&usb_ss->lock);
+ usb_gadget_giveback_request(ep, request);
+ spin_lock(&usb_ss->lock);
+ }
+
+ ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg);
+ ep_cfg &= ~EP_CFG__ENABLE__MASK;
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg);
+ ep->desc = NULL;
+ ep->enabled = 0;
+
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+
+ return ret;
+}
+
+/**
+ * usb_ss_gadget_ep_alloc_request Allocates request
+ * @ep: endpoint object associated with request
+ * @gfp_flags: gfp flags
+ *
+ * Returns allocated request address, NULL on allocation error
+ */
+static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep,
+ gfp_t gfp_flags)
+{
+ struct usb_request *request;
+
+ request = kzalloc(sizeof(struct usb_request), gfp_flags);
+ if (!request)
+ return NULL;
+
+ return request;
+}
+
+/**
+ * usb_ss_gadget_ep_free_request Free memory occupied by request
+ * @ep: endpoint object associated with request
+ * @request: request to free memory
+ */
+static void usb_ss_gadget_ep_free_request(struct usb_ep *ep,
+ struct usb_request *request)
+{
+ kfree(request);
+ request = NULL;
+}
+
+/**
+ * usb_ss_gadget_ep_queue Transfer data on endpoint
+ * @ep: endpoint object
+ * @request: request object
+ * @gfp_flags: gfp flags
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int __usb_ss_gadget_ep_queue(struct usb_ep *ep,
+ struct usb_request *request, gfp_t gfp_flags)
+{
+ struct usb_ss_endpoint *usb_ss_ep =
+ to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ int ret = 0;
+ int empty_list = 0;
+
+ request->actual = 0;
+ request->status = -EINPROGRESS;
+
+ dev_dbg(&usb_ss->dev,
+ "Queuing endpoint: %s\n", usb_ss_ep->name);
+
+ dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n",
+ ep->desc->bEndpointAddress, request->length);
+
+ ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request,
+ ep->desc->bEndpointAddress & USB_DIR_IN);
+
+ if (ret)
+ return ret;
+
+ empty_list = list_empty(&usb_ss_ep->request_list);
+ list_add_tail(&request->list, &usb_ss_ep->request_list);
+
+ if (!usb_ss->hw_configured_flag)
+ return 0;
+
+ if (empty_list) {
+ if (!usb_ss_ep->stalled_flag)
+ ret = cdns_ep_run_transfer(usb_ss_ep);
+ }
+
+ return ret;
+}
+
+static int usb_ss_gadget_ep_queue(struct usb_ep *ep,
+ struct usb_request *request, gfp_t gfp_flags)
+{
+ struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ struct usb_request *zlp_request;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+
+ ret = __usb_ss_gadget_ep_queue(ep, request, gfp_flags);
+ if (ret == 0 && request->zero && request->length &&
+ (request->length % ep->maxpacket == 0)) {
+ zlp_request = usb_ss_gadget_ep_alloc_request(ep, GFP_ATOMIC);
+ zlp_request->length = 0;
+ zlp_request->buf = usb_ss->zlp_buf;
+
+ dev_dbg(&usb_ss->dev, "Queuing ZLP for endpoint: %s\n",
+ usb_ss_ep->name);
+ ret = __usb_ss_gadget_ep_queue(ep, zlp_request, gfp_flags);
+ }
+
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return ret;
+}
+
+/**
+ * usb_ss_gadget_ep_dequeue Remove request from transfer queue
+ * @ep: endpoint object associated with request
+ * @request: request object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep,
+ struct usb_request *request)
+{
+ struct usb_ss_endpoint *usb_ss_ep =
+ to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ unsigned long flags;
+ struct usb_request *req, *req_temp;
+ int ret = 0;
+
+ if (ep == NULL || request == NULL || ep->desc == NULL)
+ return -EINVAL;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ dev_dbg(&usb_ss->dev, "DEQUEUE(%02X) %d\n",
+ ep->address, request->length);
+
+ select_ep(usb_ss, ep->desc->bEndpointAddress);
+ if (usb_ss->start_gadget)
+ ret = cdns_data_flush(usb_ss_ep);
+
+ list_for_each_entry_safe(req, req_temp,
+ &usb_ss_ep->request_list, list) {
+ if (request == req) {
+ request->status = -ECONNRESET;
+ usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request,
+ ep->address & USB_DIR_IN);
+ list_del_init(&request->list);
+ if (request->complete) {
+ spin_unlock(&usb_ss->lock);
+ usb_gadget_giveback_request
+ (&usb_ss_ep->endpoint, request);
+ spin_lock(&usb_ss->lock);
+ }
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ if (&usb_ss_ep->endpoint == usb_ss->gadget.ep0)
+ flush_work(&usb_ss->pending_status_wq);
+
+ return ret;
+}
+
+/**
+ * usb_ss_gadget_ep_set_halt Sets/clears stall on selected endpoint
+ * @ep: endpoint object to set/clear stall on
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value)
+{
+ struct usb_ss_endpoint *usb_ss_ep =
+ to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+ unsigned long flags;
+ int ret = 0;
+
+ /* return error when endpoint disabled */
+ if (!ep->enabled)
+ return -EPERM;
+
+ /* if actual transfer is pending defer setting stall on this endpoint */
+ if (usb_ss_ep->hw_pending_flag && value) {
+ usb_ss_ep->stalled_flag = 1;
+ return 0;
+ }
+
+ dev_dbg(&usb_ss->dev, "HALT(%02X) %d\n", ep->address, value);
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+
+ select_ep(usb_ss, ep->desc->bEndpointAddress);
+ if (value) {
+ cdns_ep_stall_flush(usb_ss_ep);
+ } else {
+ /*
+ * TODO:
+ * epp->wedgeFlag = 0;
+ */
+ usb_ss_ep->wedge_flag = 0;
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK);
+ /* wait for EPRST cleared */
+ ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK, 100);
+ usb_ss_ep->stalled_flag = 0;
+ }
+ usb_ss_ep->hw_pending_flag = 0;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+
+ return ret;
+}
+
+/**
+ * usb_ss_gadget_ep_set_wedge Set wedge on selected endpoint
+ * @ep: endpoint object
+ *
+ * Returns 0
+ */
+static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep)
+{
+ struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep);
+ struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss;
+
+ dev_dbg(&usb_ss->dev, "WEDGE(%02X)\n", ep->address);
+ usb_ss_gadget_ep_set_halt(ep, 1);
+ usb_ss_ep->wedge_flag = 1;
+ return 0;
+}
+
+static const struct usb_ep_ops usb_ss_gadget_ep0_ops = {
+ .enable = usb_ss_gadget_ep0_enable,
+ .disable = usb_ss_gadget_ep0_disable,
+ .alloc_request = usb_ss_gadget_ep_alloc_request,
+ .free_request = usb_ss_gadget_ep_free_request,
+ .queue = usb_ss_gadget_ep0_queue,
+ .dequeue = usb_ss_gadget_ep_dequeue,
+ .set_halt = usb_ss_gadget_ep0_set_halt,
+ .set_wedge = usb_ss_gadget_ep_set_wedge,
+};
+
+static const struct usb_ep_ops usb_ss_gadget_ep_ops = {
+ .enable = usb_ss_gadget_ep_enable,
+ .disable = usb_ss_gadget_ep_disable,
+ .alloc_request = usb_ss_gadget_ep_alloc_request,
+ .free_request = usb_ss_gadget_ep_free_request,
+ .queue = usb_ss_gadget_ep_queue,
+ .dequeue = usb_ss_gadget_ep_dequeue,
+ .set_halt = usb_ss_gadget_ep_set_halt,
+ .set_wedge = usb_ss_gadget_ep_set_wedge,
+};
+
+/**
+ * usb_ss_gadget_get_frame Returns number of actual ITP frame
+ * @gadget: gadget object
+ *
+ * Returns number of actual ITP frame
+ */
+static int usb_ss_gadget_get_frame(struct usb_gadget *gadget)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+
+ dev_dbg(&usb_ss->dev, "usb_ss_gadget_get_frame\n");
+ return gadget_readl(usb_ss, &usb_ss->regs->usb_iptn);
+}
+
+static int usb_ss_gadget_wakeup(struct usb_gadget *gadget)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+
+ dev_dbg(&usb_ss->dev, "usb_ss_gadget_wakeup\n");
+ return 0;
+}
+
+static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget,
+ int is_selfpowered)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+ unsigned long flags;
+
+ dev_dbg(&usb_ss->dev, "usb_ss_gadget_set_selfpowered: %d\n",
+ is_selfpowered);
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ gadget->is_selfpowered = !!is_selfpowered;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+}
+
+static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+
+ if (!usb_ss->start_gadget)
+ return 0;
+
+ dev_dbg(&usb_ss->dev, "usb_ss_gadget_pullup: %d\n", is_on);
+
+ if (is_on)
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__DEVEN__MASK);
+ else
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__DEVDS__MASK);
+
+ return 0;
+}
+
+/**
+ * usb_ss_gadget_udc_start Gadget start
+ * @gadget: gadget object
+ * @driver: driver which operates on this gadget
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_gadget_udc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+ unsigned long flags;
+
+ if (usb_ss->gadget_driver) {
+ dev_err(&usb_ss->dev, "%s is already bound to %s\n",
+ usb_ss->gadget.name,
+ usb_ss->gadget_driver->driver.name);
+ return -EBUSY;
+ }
+
+ dev_dbg(&usb_ss->dev, "%s begins\n", __func__);
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ usb_ss->gadget_driver = driver;
+ if (!usb_ss->start_gadget) {
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+ }
+
+ __cdns3_gadget_start(usb_ss);
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ dev_dbg(&usb_ss->dev, "%s ends\n", __func__);
+ return 0;
+}
+
+/**
+ * usb_ss_gadget_udc_stop Stops gadget
+ * @gadget: gadget object
+ *
+ * Returns 0
+ */
+static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget)
+{
+ struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget);
+ struct usb_ep *ep;
+ struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep;
+ int i;
+ u32 bEndpointAddress;
+ int ret = 0;
+
+ usb_ss->gadget_driver = NULL;
+ usb_ss->status_completion_no_call = 0;
+ list_for_each_entry_safe(usb_ss_ep, temp_ss_ep,
+ &usb_ss->ep_match_list, ep_match_pending_list) {
+ list_del(&usb_ss_ep->ep_match_pending_list);
+ usb_ss_ep->used = false;
+ }
+
+ usb_ss->onchip_mem_allocated_size = 0;
+ usb_ss->out_mem_is_allocated = 0;
+ usb_ss->gadget.speed = USB_SPEED_UNKNOWN;
+ for (i = 0; i < usb_ss->ep_nums ; i++)
+ usb_ss_free_trb_pool(usb_ss->eps[i]);
+
+ if (!usb_ss->start_gadget)
+ return 0;
+
+ list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) {
+ usb_ss_ep = to_usb_ss_ep(ep);
+ bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir;
+ select_ep(usb_ss, bEndpointAddress);
+ gadget_writel(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK);
+ ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd,
+ EP_CMD__EPRST__MASK, 100);
+ }
+
+ /* disable interrupt for device */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0);
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK);
+
+ return ret;
+}
+
+static const struct usb_gadget_ops usb_ss_gadget_ops = {
+ .get_frame = usb_ss_gadget_get_frame,
+ .wakeup = usb_ss_gadget_wakeup,
+ .set_selfpowered = usb_ss_gadget_set_selfpowered,
+ .pullup = usb_ss_gadget_pullup,
+ .udc_start = usb_ss_gadget_udc_start,
+ .udc_stop = usb_ss_gadget_udc_stop,
+ .match_ep = usb_ss_gadget_match_ep,
+};
+
+/**
+ * usb_ss_init_ep Initializes software endpoints of gadget
+ * @usb_ss: extended gadget object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_init_ep(struct usb_ss_dev *usb_ss)
+{
+ struct usb_ss_endpoint *usb_ss_ep;
+ u32 ep_enabled_reg, iso_ep_reg, bulk_ep_reg;
+ int i;
+ int ep_reg_pos, ep_dir, ep_number;
+ int found_endpoints = 0;
+
+ /* Read it from USB_CAP3 to USB_CAP5 */
+ ep_enabled_reg = 0x00ff00ff;
+ iso_ep_reg = 0x00fe00fe;
+ bulk_ep_reg = 0x00fe00fe;
+
+ dev_dbg(&usb_ss->dev, "Initializing non-zero endpoints\n");
+
+ for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) {
+ ep_number = (i / 2) + 1;
+ ep_dir = i % 2;
+ ep_reg_pos = (16 * ep_dir) + ep_number;
+
+ if (!(ep_enabled_reg & (1uL << ep_reg_pos)))
+ continue;
+
+ /* create empty endpoint object */
+ usb_ss_ep = devm_kzalloc(&usb_ss->dev, sizeof(*usb_ss_ep),
+ GFP_KERNEL);
+ if (!usb_ss_ep)
+ return -ENOMEM;
+
+ /* set parent of endpoint object */
+ usb_ss_ep->usb_ss = usb_ss;
+
+ /* set index of endpoint in endpoints container */
+ usb_ss->eps[found_endpoints++] = usb_ss_ep;
+
+ /* set name of endpoint */
+ snprintf(usb_ss_ep->name, sizeof(usb_ss_ep->name), "ep%d%s",
+ ep_number, !!ep_dir ? "in" : "out");
+ usb_ss_ep->endpoint.name = usb_ss_ep->name;
+ dev_dbg(&usb_ss->dev, "Initializing endpoint: %s\n",
+ usb_ss_ep->name);
+
+ usb_ep_set_maxpacket_limit(&usb_ss_ep->endpoint,
+ ENDPOINT_MAX_PACKET_LIMIT);
+ usb_ss_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
+ usb_ss_ep->endpoint.ops = &usb_ss_gadget_ep_ops;
+ if (ep_dir)
+ usb_ss_ep->endpoint.caps.dir_in = 1;
+ else
+ usb_ss_ep->endpoint.caps.dir_out = 1;
+
+ /* check endpoint type */
+ if (iso_ep_reg & (1uL << ep_reg_pos))
+ usb_ss_ep->endpoint.caps.type_iso = 1;
+
+ if (bulk_ep_reg & (1uL << ep_reg_pos)) {
+ usb_ss_ep->endpoint.caps.type_bulk = 1;
+ usb_ss_ep->endpoint.caps.type_int = 1;
+ usb_ss_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1;
+ }
+
+ list_add_tail(&usb_ss_ep->endpoint.ep_list,
+ &usb_ss->gadget.ep_list);
+ INIT_LIST_HEAD(&usb_ss_ep->request_list);
+ INIT_LIST_HEAD(&usb_ss_ep->ep_match_pending_list);
+ }
+
+ usb_ss->ep_nums = found_endpoints;
+ return 0;
+}
+
+/**
+ * usb_ss_init_ep0 Initializes software endpoint 0 of gadget
+ * @usb_ss: extended gadget object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss)
+{
+ struct usb_ss_endpoint *ep0;
+
+ dev_dbg(&usb_ss->dev, "Initializing EP0\n");
+ ep0 = devm_kzalloc(&usb_ss->dev, sizeof(struct usb_ss_endpoint),
+ GFP_KERNEL);
+
+ if (!ep0)
+ return -ENOMEM;
+
+ /* fill CDNS fields */
+ ep0->usb_ss = usb_ss;
+ sprintf(ep0->name, "ep0");
+
+ /* fill linux fields */
+ ep0->endpoint.ops = &usb_ss_gadget_ep0_ops;
+ ep0->endpoint.maxburst = 1;
+ usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
+ ep0->endpoint.address = 0;
+ ep0->endpoint.enabled = 1;
+ ep0->endpoint.caps.type_control = 1;
+ ep0->endpoint.caps.dir_in = 1;
+ ep0->endpoint.caps.dir_out = 1;
+ ep0->endpoint.name = ep0->name;
+ ep0->endpoint.desc = &cdns3_gadget_ep0_desc;
+ usb_ss->gadget.ep0 = &ep0->endpoint;
+ INIT_LIST_HEAD(&ep0->request_list);
+
+ return 0;
+}
+
+static void cdns3_gadget_release(struct device *dev)
+{
+ struct usb_ss_dev *usb_ss = container_of(dev, struct usb_ss_dev, dev);
+
+ dev_dbg(dev, "releasing '%s'\n", dev_name(dev));
+ kfree(usb_ss);
+}
+
+static int __cdns3_gadget_init(struct cdns3 *cdns)
+{
+ struct usb_ss_dev *usb_ss;
+ int ret;
+ struct device *dev;
+
+ usb_ss = kzalloc(sizeof(*usb_ss), GFP_KERNEL);
+ if (!usb_ss)
+ return -ENOMEM;
+
+ dev = &usb_ss->dev;
+ dev->release = cdns3_gadget_release;
+ dev->parent = cdns->dev;
+ dev_set_name(dev, "gadget-cdns3");
+ cdns->gadget_dev = dev;
+ usb_ss->sysdev = cdns->dev;
+ ret = device_register(dev);
+ if (ret)
+ goto err1;
+
+ usb_ss->regs = cdns->dev_regs;
+
+ /* fill gadget fields */
+ usb_ss->gadget.ops = &usb_ss_gadget_ops;
+ usb_ss->gadget.max_speed = USB_SPEED_SUPER;
+ usb_ss->gadget.speed = USB_SPEED_UNKNOWN;
+ usb_ss->gadget.name = "usb-ss-gadget";
+ usb_ss->gadget.sg_supported = 1;
+ usb_ss->is_connected = 0;
+ spin_lock_init(&usb_ss->lock);
+ INIT_WORK(&usb_ss->pending_status_wq, pending_setup_status_handler);
+
+ usb_ss->in_standby_mode = 1;
+
+ /* initialize endpoint container */
+ INIT_LIST_HEAD(&usb_ss->gadget.ep_list);
+ INIT_LIST_HEAD(&usb_ss->ep_match_list);
+ ret = usb_ss_init_ep0(usb_ss);
+ if (ret) {
+ dev_err(dev, "Failed to create endpoint 0\n");
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ ret = usb_ss_init_ep(usb_ss);
+ if (ret) {
+ dev_err(dev, "Failed to create non zero endpoints\n");
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ /* allocate memory for default endpoint TRB */
+ usb_ss->trb_ep0 = (u32 *)dma_alloc_coherent(usb_ss->sysdev, 20,
+ &usb_ss->trb_ep0_dma, GFP_DMA);
+ if (!usb_ss->trb_ep0) {
+ dev_err(dev, "Failed to allocate memory for ep0 TRB\n");
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ /* allocate memory for setup packet buffer */
+ usb_ss->setup = (u8 *)dma_alloc_coherent(usb_ss->sysdev, 8,
+ &usb_ss->setup_dma,
+ GFP_DMA);
+ if (!usb_ss->setup) {
+ dev_err(dev, "Failed to allocate memory for SETUP buffer\n");
+ ret = -ENOMEM;
+ goto err3;
+ }
+
+ /* add USB gadget device */
+ ret = usb_add_gadget_udc(&usb_ss->dev, &usb_ss->gadget);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register USB device controller\n");
+ goto err4;
+ }
+
+ usb_ss->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL);
+ if (!usb_ss->zlp_buf) {
+ ret = -ENOMEM;
+ goto err4;
+ }
+
+ return 0;
+err4:
+ dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup,
+ usb_ss->setup_dma);
+err3:
+ dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0,
+ usb_ss->trb_ep0_dma);
+err2:
+ device_del(dev);
+err1:
+ put_device(dev);
+ cdns->gadget_dev = NULL;
+ return ret;
+}
+
+/**
+ * cdns3_gadget_remove: parent must call this to remove UDC
+ *
+ * cdns: cdns3 instance
+ *
+ */
+void cdns3_gadget_remove(struct cdns3 *cdns)
+{
+ struct usb_ss_dev *usb_ss;
+
+ if (!cdns->roles[CDNS3_ROLE_GADGET])
+ return;
+
+ usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev);
+ usb_del_gadget_udc(&usb_ss->gadget);
+ dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup, usb_ss->setup_dma);
+ dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0,
+ usb_ss->trb_ep0_dma);
+ kfree(usb_ss->zlp_buf);
+ device_unregister(cdns->gadget_dev);
+ cdns->gadget_dev = NULL;
+}
+
+static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss)
+{
+
+ /* configure endpoint 0 hardware */
+ cdns_ep0_config(usb_ss);
+
+ /* enable interrupts for endpoint 0 (in and out) */
+ gadget_writel(usb_ss, &usb_ss->regs->ep_ien,
+ EP_IEN__EOUTEN0__MASK | EP_IEN__EINEN0__MASK);
+
+ /* enable interrupt for device */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_ien,
+ USB_IEN__U2RESIEN__MASK
+ | USB_ISTS__DIS2I__MASK
+ | USB_IEN__CON2IEN__MASK
+ | USB_IEN__UHRESIEN__MASK
+ | USB_IEN__UWRESIEN__MASK
+ | USB_IEN__DISIEN__MASK
+ | USB_IEN__CONIEN__MASK
+ | USB_IEN__U3EXTIEN__MASK
+ | USB_IEN__L2ENTIEN__MASK
+ | USB_IEN__L2EXTIEN__MASK);
+
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__CLK2OFFDS__MASK
+ /* | USB_CONF__USB3DIS__MASK */
+ | USB_CONF__L1DS__MASK);
+
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf,
+ USB_CONF__U1DS__MASK
+ | USB_CONF__U2DS__MASK
+ );
+
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVEN__MASK);
+}
+
+static int cdns3_gadget_start(struct cdns3 *cdns)
+{
+ struct usb_ss_dev *usb_ss = container_of(cdns->gadget_dev,
+ struct usb_ss_dev, dev);
+ unsigned long flags;
+
+ dev_dbg(&usb_ss->dev, "%s begins\n", __func__);
+
+ pm_runtime_get_sync(cdns->dev);
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ usb_ss->start_gadget = 1;
+ if (!usb_ss->gadget_driver) {
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+ }
+
+ __cdns3_gadget_start(usb_ss);
+ usb_ss->in_standby_mode = 0;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ dev_dbg(&usb_ss->dev, "%s ends\n", __func__);
+ return 0;
+}
+
+static void __cdns3_gadget_stop(struct cdns3 *cdns)
+{
+ struct usb_ss_dev *usb_ss;
+ unsigned long flags;
+
+ usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev);
+ if (usb_ss->gadget_driver)
+ usb_ss->gadget_driver->disconnect(&usb_ss->gadget);
+ usb_gadget_disconnect(&usb_ss->gadget);
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ usb_ss->gadget.speed = USB_SPEED_UNKNOWN;
+ usb_gadget_set_state(&usb_ss->gadget, USB_STATE_NOTATTACHED);
+ /* disable interrupt for device */
+ gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0);
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK);
+ usb_ss->start_gadget = 0;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+}
+
+static void cdns3_gadget_stop(struct cdns3 *cdns)
+{
+ if (cdns->role == CDNS3_ROLE_GADGET)
+ __cdns3_gadget_stop(cdns);
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+}
+
+static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup)
+{
+ __cdns3_gadget_stop(cdns);
+ return 0;
+}
+
+static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated)
+{
+ struct usb_ss_dev *usb_ss = container_of(cdns->gadget_dev,
+ struct usb_ss_dev, dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&usb_ss->lock, flags);
+ usb_ss->start_gadget = 1;
+ if (!usb_ss->gadget_driver) {
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+ }
+
+ __cdns3_gadget_start(usb_ss);
+ usb_ss->in_standby_mode = 0;
+ spin_unlock_irqrestore(&usb_ss->lock, flags);
+ return 0;
+}
+
+/**
+ * cdns3_gadget_init - initialize device structure
+ *
+ * cdns: cdns3 instance
+ *
+ * This function initializes the gadget.
+ */
+int cdns3_gadget_init(struct cdns3 *cdns)
+{
+ struct cdns3_role_driver *rdrv;
+
+ rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
+ if (!rdrv)
+ return -ENOMEM;
+
+ rdrv->start = cdns3_gadget_start;
+ rdrv->stop = cdns3_gadget_stop;
+ rdrv->suspend = cdns3_gadget_suspend;
+ rdrv->resume = cdns3_gadget_resume;
+ rdrv->irq = cdns_irq_handler_thread;
+ rdrv->name = "gadget";
+ cdns->roles[CDNS3_ROLE_GADGET] = rdrv;
+ return __cdns3_gadget_init(cdns);
+}
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
new file mode 100644
index 000000000000..f0a576d8a4ea
--- /dev/null
+++ b/drivers/usb/cdns3/gadget.h
@@ -0,0 +1,225 @@
+/**
+ * gadget.h - Cadence USB3 device Controller Core file
+ *
+ * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com
+ * Copyright 2017 NXP
+ *
+ * Authors: Pawel Jez <pjez@cadence.com>,
+ * Konrad Kociolek <konrad@cadence.com>,
+ * Peter Chen <peter.chen@nxp.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __DRIVERS_CDNS3_GADGET
+#define __DRIVERS_CDNS3_GADGET
+
+#include "dev-regs-map.h"
+
+#if IS_ENABLED(CONFIG_USB_CDNS_MISC)
+#include "cdns_misc.h"
+#endif
+
+#define gadget_to_usb_ss(g) \
+ (container_of(g, struct usb_ss_dev, gadget))
+
+#define to_usb_ss_ep(ep) \
+ (container_of(ep, struct usb_ss_endpoint, endpoint))
+
+#define ep_to_usb_ss_ep(ep) \
+ (container_of(ep, struct usb_ss_endpoint, endpoint))
+
+/*-------------------------------------------------------------------------*/
+/* TRB macros */
+
+/* Common TRB fields */
+#define TRB_SET_CYCLE_BIT 1uL
+#define TRB_SET_CHAIN_BIT 0x10
+
+/* offset 0 */
+#define TRB_DATA_BUFFER_POINTER_MASK 0xFFFFFFFF
+#define TRB_SET_DATA_BUFFER_POINTER(p) (p & TRB_DATA_BUFFER_POINTER_MASK)
+
+/* offset 4 */
+#define TRB_TRANSFER_LENGTH_MASK 0x1FFFF
+#define TRB_SET_TRANSFER_LENGTH(l) (l & TRB_TRANSFER_LENGTH_MASK)
+
+#define TRB_BURST_LENGTH_MASK 0xFF
+#define TRB_SET_BURST_LENGTH(l) ((l & TRB_BURST_LENGTH_MASK) << 24)
+
+/* offset 8 */
+#define TRB_SET_INT_ON_SHORT_PACKET 0x04
+#define TRB_SET_FIFO_MODE 0x08
+#define TRB_SET_INT_ON_COMPLETION 0x20
+
+#define TRB_TYPE_NORMAL 0x400
+
+#define TRB_STREAM_ID_MASK 0xFFFF
+#define TRB_SET_STREAM_ID(sid) ((sid & TRB_STREAM_ID_MASK) << 16)
+
+/*-------------------------------------------------------------------------*/
+/* Driver numeric constants */
+
+
+#define DEVICE_ADDRESS_MAX 127
+
+/* Endpoint init values */
+#define ENDPOINT_MAX_PACKET_LIMIT 1024
+#define ENDPOINT_MAX_STREAMS 15
+
+#define ENDPOINT0_MAX_PACKET_LIMIT 512
+
+/* All endpoints except EP0 */
+#define USB_SS_ENDPOINTS_MAX_COUNT 30
+
+#define USB_SS_TRBS_NUM 32
+
+/* Standby mode */
+#define STB_CLK_SWITCH_DONE_MASK 0x200
+#define STB_CLK_SWITCH_EN_MASK 0x100
+#define STB_CLK_SWITCH_EN_SHIFT 8
+
+#define ENDPOINT_MAX_PACKET_SIZE_0 0
+#define ENDPOINT_MAX_PACKET_SIZE_8 8
+#define ENDPOINT_MAX_PACKET_SIZE_64 64
+#define ENDPOINT_MAX_PACKET_SIZE_512 512
+#define ENDPOINT_MAX_PACKET_SIZE_1023 1023
+#define ENDPOINT_MAX_PACKET_SIZE_1024 1024
+
+#define SS_LINK_STATE_U3 3
+#define FSHS_LPM_STATE_L2 2
+
+#define ADDR_MODULO_8 8
+
+#define INTERRUPT_MASK 0xFFFFFFFF
+
+#define ACTUAL_TRANSFERRED_BYTES_MASK 0x1FFFF
+
+#define ENDPOINT_DIR_MASK 0x80
+
+#define ENDPOINT_ZLP_BUF_SIZE 1024
+/*-------------------------------------------------------------------------*/
+
+/**
+ * IS_REG_REQUIRING_ACTIVE_REF_CLOCK - Macro checks if desired
+ * register requires active clock, it involves such registers as:
+ * EP_CFG, EP_TR_ADDR, EP_CMD, EP_SEL, USB_CONF
+ * @usb_ss: extended gadget object
+ * @reg: register address
+ */
+#define IS_REG_REQUIRING_ACTIVE_REF_CLOCK(usb_ss, reg) (!reg || \
+ (reg >= &usb_ss->regs->ep_sel && reg <= &usb_ss->regs->ep_cmd))
+
+/**
+ * CAST_EP_REG_POS_TO_INDEX - Macro converts bit position of ep_ists register to
+ * index of endpoint object in usb_ss_dev.eps[] container
+ * @i: bit position of endpoint for which endpoint object is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+#define CAST_EP_REG_POS_TO_INDEX(i) (((i) / 16) + ((((i) % 16) - 2) * 2))
+
+/**
+ * CAST_EP_ADDR_TO_INDEX - Macro converts endpoint address to
+ * index of endpoint object in usb_ss_dev.eps[] container
+ * @ep_addr: endpoint address for which endpoint object is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+#define CAST_EP_ADDR_TO_INDEX(ep_addr) \
+ (((ep_addr & 0x7F) - 1) + ((ep_addr & 0x80) ? 1 : 0))
+
+/**
+ * CAST_EP_ADDR_TO_BIT_POS - Macro converts endpoint address to
+ * bit position in ep_ists register
+ * @ep_addr: endpoint address for which bit position is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+#define CAST_EP_ADDR_TO_BIT_POS(ep_addr) \
+ (((uint32_t)1 << (ep_addr & 0x7F)) << ((ep_addr & 0x80) ? 16 : 0))
+
+
+#define CAST_INDEX_TO_EP_ADDR(index) \
+ ((index / 2 + 1) | ((index % 2) ? 0x80 : 0x00))
+
+/* 18KB is the total size, and 2KB is used for EP0 and configuration */
+#define CDNS3_ONCHIP_BUF_SIZE 16 /* KB */
+#define CDNS3_EP_BUF_SIZE 2 /* KB */
+#define CDNS3_UNALIGNED_BUF_SIZE 16384 /* Bytes */
+/*-------------------------------------------------------------------------*/
+/* Used structs */
+
+struct usb_ss_trb {
+ u32 offset0;
+ u32 offset4;
+ u32 offset8;
+};
+
+struct usb_ss_dev;
+
+struct usb_ss_endpoint {
+ struct usb_ep endpoint;
+ struct list_head request_list;
+ struct list_head ep_match_pending_list;
+
+ struct usb_ss_trb *trb_pool;
+ dma_addr_t trb_pool_dma;
+
+ struct usb_ss_dev *usb_ss;
+ char name[20];
+ int hw_pending_flag;
+ int stalled_flag;
+ int wedge_flag;
+ void *cpu_addr;
+ dma_addr_t dma_addr;
+ u8 dir;
+ u8 num;
+ u8 type;
+ bool used;
+};
+
+struct usb_ss_dev {
+ struct device dev;
+ struct usbss_dev_register_block_type __iomem *regs;
+
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *gadget_driver;
+
+ dma_addr_t setup_dma;
+ dma_addr_t trb_ep0_dma;
+ u32 *trb_ep0;
+ u8 *setup;
+ void *zlp_buf;
+
+ struct usb_ss_endpoint *eps[USB_SS_ENDPOINTS_MAX_COUNT];
+ int ep_nums;
+ struct usb_request *actual_ep0_request;
+ int ep0_data_dir;
+ int hw_configured_flag;
+ int wake_up_flag;
+ u16 isoch_delay;
+ spinlock_t lock;
+
+ unsigned is_connected:1;
+ unsigned in_standby_mode:1;
+ unsigned status_completion_no_call:1;
+
+ u32 usb_ien;
+ u32 ep_ien;
+ int setup_pending;
+ struct device *sysdev;
+ bool start_gadget; /* The device mode is enabled */
+ struct list_head ep_match_list;
+ int onchip_mem_allocated_size; /* KB */
+ /* Memory is allocated for OUT */
+ int out_mem_is_allocated:1;
+ struct work_struct pending_status_wq;
+ struct usb_request *pending_status_request;
+};
+
+#endif /* __DRIVERS_CDNS3_GADGET */
diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
new file mode 100644
index 000000000000..a981d5cf3658
--- /dev/null
+++ b/drivers/usb/cdns3/host-export.h
@@ -0,0 +1,43 @@
+/*
+ * host-export.h - Host Export APIs
+ *
+ * Copyright 2017 NXP
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __DRIVERS_USB_CDNS3_HOST_H
+#define __DRIVERS_USB_CDNS3_HOST_H
+
+#ifdef CONFIG_USB_CDNS3_HOST
+
+int cdns3_host_init(struct cdns3 *cdns);
+void cdns3_host_remove(struct cdns3 *cdns);
+void cdns3_host_driver_init(void);
+
+#else
+
+static inline int cdns3_host_init(struct cdns3 *cdns)
+{
+ return -ENXIO;
+}
+
+static inline void cdns3_host_remove(struct cdns3 *cdns)
+{
+
+}
+
+static inline void cdns3_host_driver_init(void)
+{
+
+}
+
+#endif /* CONFIG_USB_CDNS3_HOST */
+
+#endif /* __DRIVERS_USB_CDNS3_HOST_H */
diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
new file mode 100644
index 000000000000..37bbae183ef0
--- /dev/null
+++ b/drivers/usb/cdns3/host.c
@@ -0,0 +1,286 @@
+/*
+ * host.c - Cadence USB3 host controller driver
+ *
+ * Copyright 2017 NXP
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/of.h>
+
+#include "../host/xhci.h"
+
+#include "core.h"
+#include "host-export.h"
+#include "cdns3-nxp-reg-def.h"
+
+static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
+
+static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
+{
+ /*
+ * As of now platform drivers don't provide MSI support so we ensure
+ * here that the generic code does not try to make a pci_dev from our
+ * dev struct in order to setup MSI
+ */
+ xhci->quirks |= (XHCI_PLAT | XHCI_CDNS_HOST);
+}
+
+static int xhci_cdns3_setup(struct usb_hcd *hcd)
+{
+ int ret;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ u32 command;
+
+ ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
+ if (ret)
+ return ret;
+ /* set usbcmd.EU3S */
+ command = readl(&xhci->op_regs->command);
+ command |= CMD_PM_INDEX;
+ writel(command, &xhci->op_regs->command);
+
+ return 0;
+}
+
+struct cdns3_host {
+ struct device dev;
+ struct usb_hcd *hcd;
+ struct cdns3 *cdns;
+};
+
+static int xhci_cdns3_bus_suspend(struct usb_hcd *hcd)
+{
+ struct device *dev = hcd->self.controller;
+ struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
+ struct cdns3 *cdns = host->cdns;
+ void __iomem *xhci_regs = cdns->xhci_regs;
+ u32 value;
+ int ret;
+
+ ret = xhci_bus_suspend(hcd);
+ if (ret)
+ return ret;
+
+ value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
+ value |= CFG_RXDET_P3_EN;
+ writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
+
+ return 0;
+}
+
+static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
+ .extra_priv_size = sizeof(struct xhci_hcd),
+ .reset = xhci_cdns3_setup,
+ .bus_suspend = xhci_cdns3_bus_suspend,
+};
+
+static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
+{
+ struct device *dev = cdns->host_dev;
+ struct usb_hcd *hcd;
+
+ if (dev)
+ hcd = dev_get_drvdata(dev);
+ else
+ return IRQ_NONE;
+
+ if (hcd)
+ return usb_hcd_irq(cdns->irq, hcd);
+ else
+ return IRQ_NONE;
+}
+
+static void cdns3_host_release(struct device *dev)
+{
+ struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
+
+ dev_dbg(dev, "releasing '%s'\n", dev_name(dev));
+ kfree(host);
+}
+
+static int cdns3_host_start(struct cdns3 *cdns)
+{
+ struct cdns3_host *host;
+ struct device *dev;
+ struct device *sysdev;
+ struct xhci_hcd *xhci;
+ int ret;
+
+ host = kzalloc(sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ dev = &host->dev;
+ dev->release = cdns3_host_release;
+ dev->parent = cdns->dev;
+ dev_set_name(dev, "xhci-cdns3");
+ cdns->host_dev = dev;
+ host->cdns = cdns;
+ ret = device_register(dev);
+ if (ret)
+ goto err1;
+
+ sysdev = cdns->dev;
+ /* Try to set 64-bit DMA first */
+ if (WARN_ON(!sysdev->dma_mask))
+ /* Platform did not initialize dma_mask */
+ ret = dma_coerce_mask_and_coherent(sysdev,
+ DMA_BIT_MASK(64));
+ else
+ ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
+
+ /* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
+ if (ret) {
+ ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+ }
+ pm_runtime_set_active(dev);
+ pm_runtime_no_callbacks(dev);
+ pm_runtime_enable(dev);
+
+ host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
+ dev_name(dev), NULL);
+ if (!host->hcd) {
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ host->hcd->regs = cdns->xhci_regs;
+ host->hcd->rsrc_start = cdns->xhci_res->start;
+ host->hcd->rsrc_len = resource_size(cdns->xhci_res);
+
+ device_wakeup_enable(host->hcd->self.controller);
+
+ xhci = hcd_to_xhci(host->hcd);
+
+ xhci->quirks = XHCI_SKIP_ACCESS_RESERVED_REG;
+ xhci->main_hcd = host->hcd;
+ xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
+ dev_name(dev), host->hcd);
+ if (!xhci->shared_hcd) {
+ ret = -ENOMEM;
+ goto err3;
+ }
+ host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
+ xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
+
+ ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
+ if (ret)
+ goto err4;
+
+ ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
+ if (ret)
+ goto err5;
+
+ device_set_wakeup_capable(dev, true);
+ dev_dbg(dev, "%s ends\n", __func__);
+
+ return 0;
+
+err5:
+ usb_remove_hcd(host->hcd);
+err4:
+ usb_put_hcd(xhci->shared_hcd);
+err3:
+ usb_put_hcd(host->hcd);
+err2:
+ device_del(dev);
+err1:
+ put_device(dev);
+ cdns->host_dev = NULL;
+ return ret;
+}
+
+static void cdns3_host_stop(struct cdns3 *cdns)
+{
+ struct device *dev = cdns->host_dev;
+ struct usb_hcd *hcd, *shared_hcd;
+ struct xhci_hcd *xhci;
+
+ if (dev) {
+ hcd = dev_get_drvdata(dev);
+ xhci = hcd_to_xhci(hcd);
+ shared_hcd = xhci->shared_hcd;
+ xhci->xhc_state |= XHCI_STATE_REMOVING;
+ usb_remove_hcd(shared_hcd);
+ xhci->shared_hcd = NULL;
+ usb_remove_hcd(hcd);
+ synchronize_irq(cdns->irq);
+ usb_put_hcd(shared_hcd);
+ usb_put_hcd(hcd);
+ cdns->host_dev = NULL;
+ pm_runtime_set_suspended(dev);
+ pm_runtime_disable(dev);
+ device_del(dev);
+ put_device(dev);
+ }
+}
+
+static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
+{
+ struct device *dev = cdns->host_dev;
+ struct xhci_hcd *xhci;
+
+ if (!dev)
+ return 0;
+
+ xhci = hcd_to_xhci(dev_get_drvdata(dev));
+ return xhci_suspend(xhci, do_wakeup);
+}
+
+static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
+{
+ struct device *dev = cdns->host_dev;
+ struct xhci_hcd *xhci;
+
+ if (!dev)
+ return 0;
+
+ xhci = hcd_to_xhci(dev_get_drvdata(dev));
+ return xhci_resume(xhci, hibernated);
+}
+
+int cdns3_host_init(struct cdns3 *cdns)
+{
+ struct cdns3_role_driver *rdrv;
+
+ rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
+ if (!rdrv)
+ return -ENOMEM;
+
+ rdrv->start = cdns3_host_start;
+ rdrv->stop = cdns3_host_stop;
+ rdrv->irq = cdns3_host_irq;
+ rdrv->suspend = cdns3_host_suspend;
+ rdrv->resume = cdns3_host_resume;
+ rdrv->name = "host";
+ cdns->roles[CDNS3_ROLE_HOST] = rdrv;
+
+ return 0;
+}
+
+void cdns3_host_remove(struct cdns3 *cdns)
+{
+ cdns3_host_stop(cdns);
+}
+
+void __init cdns3_host_driver_init(void)
+{
+ xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
+}
diff --git a/drivers/usb/cdns3/io.h b/drivers/usb/cdns3/io.h
new file mode 100644
index 000000000000..c16edbf54b8b
--- /dev/null
+++ b/drivers/usb/cdns3/io.h
@@ -0,0 +1,35 @@
+/**
+ * io.h - Cadence USB3 IO Header
+ *
+ * Copyright (C) 2016 Cadence Design Systems - https://www.cadence.com/
+ *
+ * Authors: Rafal Ozieblo <rafalo@cadence.com>,
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __DRIVERS_USB_CDNS_IO_H
+#define __DRIVERS_USB_CDNS_IO_H
+
+#include <linux/io.h>
+
+static inline u32 cdns_readl(uint32_t __iomem *reg)
+{
+ u32 value = 0;
+
+ value = readl(reg);
+ return value;
+}
+
+static inline void cdns_writel(uint32_t __iomem *reg, u32 value)
+{
+ writel(value, reg);
+}
+
+
+#endif /* __DRIVERS_USB_CDNS_IO_H */
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 6743f85b1b7a..fbd600e25545 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -128,12 +128,16 @@ enum ci_revision {
* @start: start this role
* @stop: stop this role
* @irq: irq handler for this role
+ * @suspend: system suspend handler for this role
+ * @resume: system resume handler for this role
* @name: role name string (host/gadget)
*/
struct ci_role_driver {
int (*start)(struct ci_hdrc *);
void (*stop)(struct ci_hdrc *);
irqreturn_t (*irq)(struct ci_hdrc *);
+ void (*suspend)(struct ci_hdrc *);
+ void (*resume)(struct ci_hdrc *, bool power_lost);
const char *name;
};
@@ -199,12 +203,16 @@ struct hw_bank {
* @debugfs: root dentry for this controller in debugfs
* @id_event: indicates there is an id event, and handled at ci_otg_work
* @b_sess_valid_event: indicates there is a vbus event, and handled
+ * @vbus_glitch_check_event: check if vbus change is a glitch
* at ci_otg_work
* @imx28_write_fix: Freescale imx28 needs swp instruction for writing
* @supports_runtime_pm: if runtime pm is supported
* @in_lpm: if the core in low power mode
* @wakeup_int: if wakeup interrupt occur
* @rev: The revision number for controller
+ * @mutex: protect code from concorrent running
+ * @power_lost_work: work item when controller power is lost
+ * @power_lost_wq: work queue for controller power is lost
*/
struct ci_hdrc {
struct device *dev;
@@ -254,11 +262,26 @@ struct ci_hdrc {
struct dentry *debugfs;
bool id_event;
bool b_sess_valid_event;
+ bool vbus_glitch_check_event;
bool imx28_write_fix;
bool supports_runtime_pm;
bool in_lpm;
bool wakeup_int;
enum ci_revision rev;
+ /* register save area for suspend&resume */
+ u32 pm_command;
+ u32 pm_status;
+ u32 pm_intr_enable;
+ u32 pm_frame_index;
+ u32 pm_segment;
+ u32 pm_frame_list;
+ u32 pm_async_next;
+ u32 pm_configured_flag;
+ u32 pm_portsc;
+ u32 pm_usbmode;
+ struct work_struct power_lost_work;
+ struct workqueue_struct *power_lost_wq;
+ struct mutex mutex;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
@@ -278,9 +301,21 @@ static inline int ci_role_start(struct ci_hdrc *ci, enum ci_role role)
return -ENXIO;
ret = ci->roles[role]->start(ci);
- if (!ret)
- ci->role = role;
- return ret;
+ if (ret)
+ return ret;
+
+ ci->role = role;
+
+ if (ci->usb_phy) {
+ if (role == CI_ROLE_HOST)
+ usb_phy_set_mode(ci->usb_phy,
+ USB_MODE_HOST);
+ else
+ usb_phy_set_mode(ci->usb_phy,
+ USB_MODE_DEVICE);
+ }
+
+ return 0;
}
static inline void ci_role_stop(struct ci_hdrc *ci)
@@ -293,6 +328,9 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
ci->role = CI_ROLE_END;
ci->roles[role]->stop(ci);
+
+ if (ci->usb_phy)
+ usb_phy_set_mode(ci->usb_phy, USB_MODE_NONE);
}
/**
@@ -452,6 +490,7 @@ u8 hw_port_test_get(struct ci_hdrc *ci);
void hw_phymode_configure(struct ci_hdrc *ci);
void ci_platform_configure(struct ci_hdrc *ci);
+int hw_controller_reset(struct ci_hdrc *ci);
int dbg_create_files(struct ci_hdrc *ci);
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 5f4a8157fad8..483e89ce1800 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -1,5 +1,6 @@
/*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*
@@ -20,6 +21,13 @@
#include <linux/usb/chipidea.h>
#include <linux/usb/of.h>
#include <linux/clk.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regulator/consumer.h>
+#include <linux/busfreq-imx.h>
+#include <linux/pm_qos.h>
+#include <linux/usb/of.h>
#include "ci.h"
#include "ci_hdrc_imx.h"
@@ -41,25 +49,29 @@ static const struct ci_hdrc_imx_platform_flag imx27_usb_data = {
static const struct ci_hdrc_imx_platform_flag imx28_usb_data = {
.flags = CI_HDRC_IMX28_WRITE_FIX |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_STREAMING,
+ CI_HDRC_DISABLE_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_STREAMING,
+ CI_HDRC_DISABLE_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_HOST_STREAMING,
+ CI_HDRC_DISABLE_HOST_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_HOST_STREAMING,
+ CI_HDRC_DISABLE_HOST_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = {
@@ -71,6 +83,16 @@ static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM,
};
+static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
+ CI_HDRC_IMX_EHCI_QUIRK |
+ CI_HDRC_PMQOS,
+};
+
+static const struct ci_hdrc_imx_platform_flag imx8qm_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM,
+};
+
static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx23-usb", .data = &imx23_usb_data},
{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data},
@@ -80,6 +102,8 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data},
{ .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data},
{ .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data},
+ { .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data},
+ { .compatible = "fsl,imx8qm-usb", .data = &imx8qm_usb_data},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
@@ -91,12 +115,18 @@ struct ci_hdrc_imx_data {
struct imx_usbmisc_data *usbmisc_data;
bool supports_runtime_pm;
bool in_lpm;
+ struct regmap *anatop;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_hsic_active;
+ struct regulator *hsic_pad_regulator;
+ const struct ci_hdrc_imx_platform_flag *data;
/* SoC before i.mx6 (except imx23/imx28) needs three clks */
bool need_three_clks;
struct clk *clk_ipg;
struct clk *clk_ahb;
struct clk *clk_per;
/* --------------------------------- */
+ struct pm_qos_request pm_qos_req;
};
/* Common functions shared by usbmisc drivers */
@@ -144,12 +174,40 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev)
if (of_find_property(np, "over-current-active-high", NULL))
data->oc_polarity = 1;
+ if (of_find_property(np, "power-polarity-active-high", NULL))
+ data->pwr_polarity = 1;
+
if (of_find_property(np, "external-vbus-divider", NULL))
data->evdo = 1;
if (of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI)
data->ulpi = 1;
+ if (of_find_property(np, "osc-clkgate-delay", NULL)) {
+ ret = of_property_read_u32(np, "osc-clkgate-delay",
+ &data->osc_clkgate_delay);
+ if (ret) {
+ dev_err(dev,
+ "failed to get osc-clkgate-delay value\n");
+ return ERR_PTR(ret);
+ }
+ /*
+ * 0 <= osc_clkgate_delay <=7
+ * - 0x0 (default) is 0.5ms,
+ * - 0x1-0x7: 1-7ms
+ */
+ if (data->osc_clkgate_delay > 7) {
+ dev_err(dev,
+ "value of osc-clkgate-delay is incorrect\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ of_property_read_u32(np, "picophy,pre-emp-curr-control",
+ &data->emp_curr_control);
+ of_property_read_u32(np, "picophy,dc-vol-level-adjust",
+ &data->dc_vol_level_adjust);
+
return data;
}
@@ -251,41 +309,135 @@ static void imx_disable_unprepare_clks(struct device *dev)
}
}
+static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned event)
+{
+ struct device *dev = ci->dev->parent;
+ struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret = 0;
+ struct imx_usbmisc_data *mdata = data->usbmisc_data;
+
+ switch (event) {
+ case CI_HDRC_CONTROLLER_VBUS_EVENT:
+ if (ci->vbus_active)
+ ret = imx_usbmisc_charger_detection(mdata, true);
+ else
+ ret = imx_usbmisc_charger_detection(mdata, false);
+ break;
+ case CI_HDRC_IMX_HSIC_ACTIVE_EVENT:
+ if (!IS_ERR(data->pinctrl) &&
+ !IS_ERR(data->pinctrl_hsic_active)) {
+ ret = pinctrl_select_state(data->pinctrl,
+ data->pinctrl_hsic_active);
+ if (ret)
+ dev_err(dev,
+ "hsic_active select failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ break;
+ case CI_HDRC_IMX_HSIC_SUSPEND_EVENT:
+ if (data->usbmisc_data) {
+ ret = imx_usbmisc_hsic_set_connect(data->usbmisc_data);
+ if (ret)
+ dev_err(dev,
+ "hsic_set_connect failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ break;
+ case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_FS:
+ if (data->usbmisc_data)
+ return imx_usbmisc_term_select_override(
+ data->usbmisc_data, true, 1);
+ break;
+ case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF:
+ if (data->usbmisc_data)
+ return imx_usbmisc_term_select_override(
+ data->usbmisc_data, false, 0);
+ break;
+ default:
+ dev_dbg(dev, "unknown event\n");
+ }
+
+ return ret;
+}
+
static int ci_hdrc_imx_probe(struct platform_device *pdev)
{
struct ci_hdrc_imx_data *data;
struct ci_hdrc_platform_data pdata = {
.name = dev_name(&pdev->dev),
.capoffset = DEF_CAPOFFSET,
+ .notify_event = ci_hdrc_imx_notify_event,
};
int ret;
const struct of_device_id *of_id;
const struct ci_hdrc_imx_platform_flag *imx_platform_flag;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct pinctrl_state *pinctrl_hsic_idle;
- of_id = of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev);
+ of_id = of_match_device(ci_hdrc_imx_dt_ids, dev);
if (!of_id)
return -ENODEV;
imx_platform_flag = of_id->data;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
- data->usbmisc_data = usbmisc_get_init_data(&pdev->dev);
+
+ data->data = imx_platform_flag;
+ pdata.flags |= imx_platform_flag->flags;
+ data->usbmisc_data = usbmisc_get_init_data(dev);
if (IS_ERR(data->usbmisc_data))
return PTR_ERR(data->usbmisc_data);
- ret = imx_get_clks(&pdev->dev);
+ data->pinctrl = devm_pinctrl_get(dev);
+ if (IS_ERR(data->pinctrl)) {
+ dev_dbg(dev, "pinctrl get failed, err=%ld\n",
+ PTR_ERR(data->pinctrl));
+ } else {
+ pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle");
+ if (IS_ERR(pinctrl_hsic_idle)) {
+ dev_dbg(dev,
+ "pinctrl_hsic_idle lookup failed, err=%ld\n",
+ PTR_ERR(pinctrl_hsic_idle));
+ } else {
+ ret = pinctrl_select_state(data->pinctrl,
+ pinctrl_hsic_idle);
+ if (ret) {
+ dev_err(dev,
+ "hsic_idle select failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ data->pinctrl_hsic_active = pinctrl_lookup_state(data->pinctrl,
+ "active");
+ if (IS_ERR(data->pinctrl_hsic_active))
+ dev_dbg(dev,
+ "pinctrl_hsic_active lookup failed, err=%ld\n",
+ PTR_ERR(data->pinctrl_hsic_active));
+ }
+
+ ret = imx_get_clks(dev);
if (ret)
return ret;
- ret = imx_prepare_enable_clks(&pdev->dev);
+ request_bus_freq(BUS_FREQ_HIGH);
+ if (pdata.flags & CI_HDRC_PMQOS)
+ pm_qos_add_request(&data->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+
+ ret = imx_prepare_enable_clks(dev);
if (ret)
- return ret;
+ goto err_bus_freq;
- data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0);
+ data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
if (IS_ERR(data->phy)) {
ret = PTR_ERR(data->phy);
/* Return -EINVAL if no usbphy is available */
@@ -295,46 +447,107 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
}
pdata.usb_phy = data->phy;
- pdata.flags |= imx_platform_flag->flags;
+ data->usbmisc_data->usb_phy = data->phy;
if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
data->supports_runtime_pm = true;
+ if (of_find_property(np, "ci-disable-lpm", NULL)) {
+ data->supports_runtime_pm = false;
+ pdata.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM;
+ }
+
+ if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) {
+ pdata.flags |= CI_HDRC_IMX_IS_HSIC;
+ data->usbmisc_data->hsic = 1;
+ data->hsic_pad_regulator = devm_regulator_get(dev, "pad");
+ if (PTR_ERR(data->hsic_pad_regulator) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_clk;
+ } else if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) {
+ /* no pad regualator is needed */
+ data->hsic_pad_regulator = NULL;
+ } else if (IS_ERR(data->hsic_pad_regulator)) {
+ dev_err(dev, "Get hsic pad regulator error: %ld\n",
+ PTR_ERR(data->hsic_pad_regulator));
+ ret = PTR_ERR(data->hsic_pad_regulator);
+ goto err_clk;
+ }
+
+ if (data->hsic_pad_regulator) {
+ ret = regulator_enable(data->hsic_pad_regulator);
+ if (ret) {
+ dev_err(dev,
+ "Fail to enable hsic pad regulator\n");
+ goto err_clk;
+ }
+ }
+ }
+
+ if (of_find_property(np, "fsl,anatop", NULL) && data->usbmisc_data) {
+ data->anatop = syscon_regmap_lookup_by_phandle(np,
+ "fsl,anatop");
+ if (IS_ERR(data->anatop)) {
+ dev_dbg(dev, "failed to find regmap for anatop\n");
+ ret = PTR_ERR(data->anatop);
+ goto disable_hsic_regulator;
+ }
+ data->usbmisc_data->anatop = data->anatop;
+ }
+
ret = imx_usbmisc_init(data->usbmisc_data);
if (ret) {
- dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", ret);
- goto err_clk;
+ dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
+ goto disable_hsic_regulator;
}
- data->ci_pdev = ci_hdrc_add_device(&pdev->dev,
+ data->ci_pdev = ci_hdrc_add_device(dev,
pdev->resource, pdev->num_resources,
&pdata);
if (IS_ERR(data->ci_pdev)) {
ret = PTR_ERR(data->ci_pdev);
if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
+ dev_err(dev,
"ci_hdrc_add_device failed, err=%d\n", ret);
- goto err_clk;
+ goto disable_hsic_regulator;
}
ret = imx_usbmisc_init_post(data->usbmisc_data);
if (ret) {
- dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", ret);
+ dev_err(dev, "usbmisc post failed, ret=%d\n", ret);
goto disable_device;
}
+ ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
+ if (ret) {
+ dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
+ goto disable_device;
+ }
+
+ /* usbmisc needs to know dr mode to choose wakeup setting */
+ if (data->usbmisc_data)
+ data->usbmisc_data->available_role =
+ ci_hdrc_query_available_role(data->ci_pdev);
+
if (data->supports_runtime_pm) {
- pm_runtime_set_active(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
}
- device_set_wakeup_capable(&pdev->dev, true);
+ device_set_wakeup_capable(dev, true);
return 0;
disable_device:
ci_hdrc_remove_device(data->ci_pdev);
+disable_hsic_regulator:
+ if (data->hsic_pad_regulator)
+ ret = regulator_disable(data->hsic_pad_regulator);
err_clk:
imx_disable_unprepare_clks(&pdev->dev);
+err_bus_freq:
+ if (pdata.flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
return ret;
}
@@ -349,6 +562,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)
}
ci_hdrc_remove_device(data->ci_pdev);
imx_disable_unprepare_clks(&pdev->dev);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
+ if (data->hsic_pad_regulator)
+ regulator_disable(data->hsic_pad_regulator);
return 0;
}
@@ -362,10 +580,23 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev)
static int imx_controller_suspend(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret;
dev_dbg(dev, "at %s\n", __func__);
+ if (data->usbmisc_data) {
+ ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, false);
+ if (ret) {
+ dev_err(dev,
+ "usbmisc hsic_set_clk failed, ret=%d\n", ret);
+ return ret;
+ }
+ }
+
imx_disable_unprepare_clks(dev);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
data->in_lpm = true;
return 0;
@@ -378,27 +609,51 @@ static int imx_controller_resume(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- if (!data->in_lpm) {
- WARN_ON(1);
+ if (!data->in_lpm)
return 0;
- }
+ request_bus_freq(BUS_FREQ_HIGH);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_add_request(&data->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
ret = imx_prepare_enable_clks(dev);
if (ret)
- return ret;
+ goto err_bus_freq;
data->in_lpm = false;
+ ret = imx_usbmisc_power_lost_check(data->usbmisc_data);
+ /* re-init if resume from power lost */
+ if (ret > 0) {
+ ret = imx_usbmisc_init(data->usbmisc_data);
+ if (ret) {
+ dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
+ goto clk_disable;
+ }
+ }
+
ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
if (ret) {
dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
goto clk_disable;
}
+ ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, true);
+ if (ret) {
+ dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret);
+ goto hsic_set_clk_fail;
+ }
+
return 0;
+hsic_set_clk_fail:
+ imx_usbmisc_set_wakeup(data->usbmisc_data, true);
clk_disable:
imx_disable_unprepare_clks(dev);
+err_bus_freq:
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
return ret;
}
@@ -422,7 +677,12 @@ static int ci_hdrc_imx_suspend(struct device *dev)
}
}
- return imx_controller_suspend(dev);
+ ret = imx_controller_suspend(dev);
+ if (ret)
+ return ret;
+
+ pinctrl_pm_select_sleep_state(dev);
+ return ret;
}
static int ci_hdrc_imx_resume(struct device *dev)
@@ -430,6 +690,7 @@ static int ci_hdrc_imx_resume(struct device *dev)
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret;
+ pinctrl_pm_select_default_state(dev);
ret = imx_controller_resume(dev);
if (!ret && data->supports_runtime_pm) {
pm_runtime_disable(dev);
@@ -446,10 +707,8 @@ static int ci_hdrc_imx_runtime_suspend(struct device *dev)
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret;
- if (data->in_lpm) {
- WARN_ON(1);
+ if (data->in_lpm)
return 0;
- }
ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
if (ret) {
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h
index d666c9f036ba..76512e5d27ad 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.h
+++ b/drivers/usb/chipidea/ci_hdrc_imx.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2015 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
@@ -11,19 +11,39 @@
#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
+#include <linux/usb/otg.h>
+#include <linux/usb/phy.h>
struct imx_usbmisc_data {
struct device *dev;
int index;
+ struct regmap *anatop;
+ struct usb_phy *usb_phy;
unsigned int disable_oc:1; /* over current detect disabled */
unsigned int oc_polarity:1; /* over current polarity if oc enabled */
+ unsigned int pwr_polarity:1; /* polarity of enable vbus from pmic */
unsigned int evdo:1; /* set external vbus divider option */
unsigned int ulpi:1; /* connected to an ULPI phy */
+ unsigned int hsic:1; /* HSIC controlller */
+ /*
+ * Specifies the delay between powering up the xtal 24MHz clock
+ * and release the clock to the digital logic inside the analog block
+ */
+ unsigned int osc_clkgate_delay;
+ enum usb_dr_mode available_role;
+ int emp_curr_control;
+ int dc_vol_level_adjust;
};
int imx_usbmisc_init(struct imx_usbmisc_data *);
int imx_usbmisc_init_post(struct imx_usbmisc_data *);
int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *, bool);
+int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect);
+int imx_usbmisc_power_lost_check(struct imx_usbmisc_data *);
+int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *);
+int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *, bool);
+int imx_usbmisc_term_select_override(struct imx_usbmisc_data *data,
+ bool enable, int val);
#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 70306ae039c0..09d50c2b02e2 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -477,7 +477,7 @@ void ci_platform_configure(struct ci_hdrc *ci)
*
* This function returns an error code
*/
-static int hw_controller_reset(struct ci_hdrc *ci)
+int hw_controller_reset(struct ci_hdrc *ci)
{
int count = 0;
@@ -574,7 +574,7 @@ static irqreturn_t ci_irq(int irq, void *data)
* and disconnection events.
*/
if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
- ci->b_sess_valid_event = true;
+ ci->vbus_glitch_check_event = true;
/* Clear BSV irq */
hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
ci_otg_queue_work(ci);
@@ -690,6 +690,12 @@ static int ci_get_platdata(struct device *dev,
if (of_find_property(dev->of_node, "non-zero-ttctrl-ttha", NULL))
platdata->flags |= CI_HDRC_SET_NON_ZERO_TTHA;
+ /* "imx-usb-charger-detection is legacy compatible */
+ if (of_find_property(dev->of_node, "phy-charger-detection", NULL) ||
+ of_find_property(dev->of_node, "imx-usb-charger-detection",
+ NULL))
+ platdata->flags |= CI_HDRC_PHY_CHARGER_DETECTION;
+
ext_id = ERR_PTR(-ENODEV);
ext_vbus = ERR_PTR(-ENODEV);
if (of_property_read_bool(dev->of_node, "extcon")) {
@@ -814,12 +820,39 @@ void ci_hdrc_remove_device(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(ci_hdrc_remove_device);
+/**
+ * ci_hdrc_query_available_role: get runtime available operation mode
+ *
+ * The glue layer can get current operation mode (host/peripheral/otg)
+ * This function should be called after ci core device has created.
+ *
+ * @pdev: the platform device of ci core.
+ *
+ * Return USB_DR_MODE_XXX.
+ */
+enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev)
+{
+ struct ci_hdrc *ci = platform_get_drvdata(pdev);
+
+ if (!ci)
+ return USB_DR_MODE_UNKNOWN;
+ if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])
+ return USB_DR_MODE_OTG;
+ else if (ci->roles[CI_ROLE_HOST])
+ return USB_DR_MODE_HOST;
+ else if (ci->roles[CI_ROLE_GADGET])
+ return USB_DR_MODE_PERIPHERAL;
+ else
+ return USB_DR_MODE_UNKNOWN;
+}
+EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role);
+
static inline void ci_role_destroy(struct ci_hdrc *ci)
{
- ci_hdrc_gadget_destroy(ci);
- ci_hdrc_host_destroy(ci);
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
ci_hdrc_otg_destroy(ci);
+ ci_hdrc_gadget_destroy(ci);
+ ci_hdrc_host_destroy(ci);
}
static void ci_get_otg_capable(struct ci_hdrc *ci)
@@ -891,6 +924,55 @@ static const struct attribute_group ci_attr_group = {
.attrs = ci_attrs,
};
+static enum ci_role ci_get_role(struct ci_hdrc *ci)
+{
+ if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
+ if (ci->is_otg) {
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
+ return ci_otg_role(ci);
+ } else {
+ /*
+ * If the controller is not OTG capable, but support
+ * role switch, the defalt role is gadget, and the
+ * user can switch it through debugfs.
+ */
+ return CI_ROLE_GADGET;
+ }
+ } else {
+ return ci->roles[CI_ROLE_HOST]
+ ? CI_ROLE_HOST
+ : CI_ROLE_GADGET;
+ }
+}
+
+static void ci_start_new_role(struct ci_hdrc *ci)
+{
+ enum ci_role role = ci_get_role(ci);
+
+ if (ci->role != role) {
+ ci_handle_id_switch(ci);
+ } else if (role == CI_ROLE_GADGET) {
+ if (ci->vbus_active)
+ usb_gadget_vbus_disconnect(&ci->gadget);
+ ci_handle_vbus_connected(ci);
+ }
+}
+
+static void ci_power_lost_work(struct work_struct *work)
+{
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc,
+ power_lost_work);
+
+ disable_irq_nosync(ci->irq);
+ pm_runtime_get_sync(ci->dev);
+ if (!ci_otg_is_fsm_mode(ci))
+ ci_start_new_role(ci);
+ else
+ ci_hdrc_otg_fsm_restart(ci);
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+}
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1019,30 +1101,15 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
}
- if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
- if (ci->is_otg) {
- ci->role = ci_otg_role(ci);
- /* Enable ID change irq */
- hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
- } else {
- /*
- * If the controller is not OTG capable, but support
- * role switch, the defalt role is gadget, and the
- * user can switch it through debugfs.
- */
- ci->role = CI_ROLE_GADGET;
- }
- } else {
- ci->role = ci->roles[CI_ROLE_HOST]
- ? CI_ROLE_HOST
- : CI_ROLE_GADGET;
+ ci->role = ci_get_role(ci);
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET) {
+ /* Let DP pull down if it isn't currently */
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+ ci_handle_vbus_connected(ci);
}
if (!ci_otg_is_fsm_mode(ci)) {
- /* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET)
- ci_handle_vbus_change(ci);
-
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
@@ -1072,6 +1139,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci_hdrc_otg_fsm_start(ci);
device_set_wakeup_capable(&pdev->dev, true);
+
+ mutex_init(&ci->mutex);
+
ret = dbg_create_files(ci);
if (ret)
goto stop;
@@ -1080,8 +1150,19 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret)
goto remove_debug;
+ /* Init workqueue for controller power lost handling */
+ ci->power_lost_wq = create_freezable_workqueue("ci_power_lost");
+ if (!ci->power_lost_wq) {
+ dev_err(ci->dev, "can't create power_lost workqueue\n");
+ goto remove_sys_group;
+ }
+
+ INIT_WORK(&ci->power_lost_work, ci_power_lost_work);
+
return 0;
+remove_sys_group:
+ sysfs_remove_group(&ci->dev->kobj, &ci_attr_group);
remove_debug:
dbg_remove_files(ci);
stop:
@@ -1109,6 +1190,8 @@ static int ci_hdrc_remove(struct platform_device *pdev)
pm_runtime_put_noidle(&pdev->dev);
}
+ flush_workqueue(ci->power_lost_wq);
+ destroy_workqueue(ci->power_lost_wq);
dbg_remove_files(ci);
sysfs_remove_group(&ci->dev->kobj, &ci_attr_group);
ci_role_destroy(ci);
@@ -1137,13 +1220,10 @@ static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci)
{
if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) &&
(ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) {
- if (!hw_read_otgsc(ci, OTGSC_ID)) {
- ci->fsm.a_srp_det = 1;
- ci->fsm.a_bus_drop = 0;
- } else {
+ if (!hw_read_otgsc(ci, OTGSC_ID))
+ otg_add_timer(&ci->fsm, A_DP_END);
+ else
ci->fsm.id = 1;
- }
- ci_otg_queue_work(ci);
}
}
@@ -1159,6 +1239,29 @@ static void ci_controller_suspend(struct ci_hdrc *ci)
enable_irq(ci->irq);
}
+/*
+ * Handle the wakeup interrupt triggered by extcon connector
+ * We need to call ci_irq again for extcon since the first
+ * interrupt (wakeup int) only let the controller be out of
+ * low power mode, but not handle any interrupts.
+ */
+static void ci_extcon_wakeup_int(struct ci_hdrc *ci)
+{
+ struct ci_hdrc_cable *cable_id, *cable_vbus;
+ u32 otgsc = hw_read_otgsc(ci, ~0);
+
+ cable_id = &ci->platdata->id_extcon;
+ cable_vbus = &ci->platdata->vbus_extcon;
+
+ if (!IS_ERR(cable_id->edev) && ci->is_otg &&
+ (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS))
+ ci_irq(ci->irq, ci);
+
+ if (!IS_ERR(cable_vbus->edev) && ci->is_otg &&
+ (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS))
+ ci_irq(ci->irq, ci);
+}
+
static int ci_controller_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
@@ -1166,10 +1269,8 @@ static int ci_controller_resume(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- if (!ci->in_lpm) {
- WARN_ON(1);
+ if (!ci->in_lpm)
return 0;
- }
ci_hdrc_enter_lpm(ci, false);
@@ -1191,6 +1292,7 @@ static int ci_controller_resume(struct device *dev)
enable_irq(ci->irq);
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_wakeup_by_srp(ci);
+ ci_extcon_wakeup_int(ci);
}
return 0;
@@ -1201,6 +1303,7 @@ static int ci_suspend(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
+ flush_workqueue(ci->power_lost_wq);
if (ci->wq)
flush_workqueue(ci->wq);
/*
@@ -1217,6 +1320,10 @@ static int ci_suspend(struct device *dev)
return 0;
}
+ /* Extra routine per role before system suspend */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->suspend)
+ ci_role(ci)->suspend(ci);
+
if (device_may_wakeup(dev)) {
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);
@@ -1233,8 +1340,18 @@ static int ci_suspend(struct device *dev)
static int ci_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
+ bool power_lost = false;
+ u32 sample_reg_val;
int ret;
+ /* Check if controller resume from power lost */
+ sample_reg_val = hw_read(ci, OP_ENDPTLISTADDR, ~0);
+ if (sample_reg_val == 0)
+ power_lost = true;
+ else if (sample_reg_val == 0xFFFFFFFF)
+ /* Restore value 0 if it was set for power lost check */
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, 0);
+
if (device_may_wakeup(dev))
disable_irq_wake(ci->irq);
@@ -1242,6 +1359,19 @@ static int ci_resume(struct device *dev)
if (ret)
return ret;
+ if (power_lost) {
+ /* shutdown and re-init for phy */
+ ci_usb_phy_exit(ci);
+ ci_usb_phy_init(ci);
+ }
+
+ /* Extra routine per role after system resume */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->resume)
+ ci_role(ci)->resume(ci, power_lost);
+
+ if (power_lost)
+ queue_work(ci->power_lost_wq, &ci->power_lost_work);
+
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -1258,10 +1388,8 @@ static int ci_runtime_suspend(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- if (ci->in_lpm) {
- WARN_ON(1);
+ if (ci->in_lpm)
return 0;
- }
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index 18cb8e46262d..37367a395cd1 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -25,6 +25,7 @@
#include <linux/usb/hcd.h>
#include <linux/usb/chipidea.h>
#include <linux/regulator/consumer.h>
+#include <linux/imx_gpc.h>
#include "../host/ehci.h"
@@ -34,11 +35,29 @@
static struct hc_driver __read_mostly ci_ehci_hc_driver;
static int (*orig_bus_suspend)(struct usb_hcd *hcd);
+static int (*orig_bus_resume)(struct usb_hcd *hcd);
+static int (*orig_hub_control)(struct usb_hcd *hcd,
+ u16 typeReq, u16 wValue, u16 wIndex,
+ char *buf, u16 wLength);
struct ehci_ci_priv {
struct regulator *reg_vbus;
};
+/* This function is used to override WKCN, WKDN, and WKOC */
+static void ci_ehci_override_wakeup_flag(struct ehci_hcd *ehci,
+ u32 __iomem *reg, u32 flags, bool set)
+{
+ u32 val = ehci_readl(ehci, reg);
+
+ if (set)
+ val |= flags;
+ else
+ val &= ~flags;
+
+ ehci_writel(ehci, val, reg);
+}
+
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
@@ -108,9 +127,164 @@ static const struct ehci_driver_overrides ehci_ci_overrides = {
.reset = ehci_ci_reset,
};
+static int ci_imx_ehci_bus_resume(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ int port;
+
+ int ret = orig_bus_resume(hcd);
+
+ if (ret)
+ return ret;
+
+ port = HCS_N_PORTS(ehci->hcs_params);
+ while (port--) {
+ u32 __iomem *reg = &ehci->regs->port_status[port];
+ u32 portsc = ehci_readl(ehci, reg);
+ /*
+ * Notify PHY after resume signal has finished, it is
+ * for global suspend case.
+ */
+ if (hcd->usb_phy
+ && test_bit(port, &ehci->bus_suspended)
+ && (portsc & PORT_CONNECT)
+ && (ehci_port_speed(ehci, portsc) ==
+ USB_PORT_STAT_HIGH_SPEED))
+ /* notify the USB PHY */
+ usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_USB_OTG
+
+static int ci_start_port_reset(struct usb_hcd *hcd, unsigned port)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 __iomem *reg;
+ u32 status;
+
+ if (!port)
+ return -EINVAL;
+ port--;
+ /* start port reset before HNP protocol time out */
+ reg = &ehci->regs->port_status[port];
+ status = ehci_readl(ehci, reg);
+ if (!(status & PORT_CONNECT))
+ return -ENODEV;
+
+ /* khubd will finish the reset later */
+ if (ehci_is_TDI(ehci))
+ ehci_writel(ehci, status | (PORT_RESET & ~PORT_RWC_BITS), reg);
+ else
+ ehci_writel(ehci, status | PORT_RESET, reg);
+
+ return 0;
+}
+
+#else
+
+#define ci_start_port_reset NULL
+
+#endif
+
+/* The below code is based on tegra ehci driver */
+static int ci_imx_ehci_hub_control(
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 __iomem *status_reg;
+ u32 temp;
+ unsigned long flags;
+ int retval = 0;
+ struct device *dev = hcd->self.controller;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
+
+ spin_lock_irqsave(&ehci->lock, flags);
+
+ if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
+ temp = ehci_readl(ehci, status_reg);
+ if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
+ retval = -EPIPE;
+ goto done;
+ }
+
+ temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E);
+ temp |= PORT_WKDISC_E | PORT_WKOC_E;
+ ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
+
+ /*
+ * If a transaction is in progress, there may be a delay in
+ * suspending the port. Poll until the port is suspended.
+ */
+ if (ehci_handshake(ehci, status_reg, PORT_SUSPEND,
+ PORT_SUSPEND, 5000))
+ ehci_err(ehci, "timeout waiting for SUSPEND\n");
+
+ if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
+ if (ci->platdata->notify_event)
+ ci->platdata->notify_event
+ (ci, CI_HDRC_IMX_HSIC_SUSPEND_EVENT);
+ ci_ehci_override_wakeup_flag(ehci, status_reg,
+ PORT_WKDISC_E | PORT_WKCONN_E, false);
+ }
+
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ if (ehci_port_speed(ehci, temp) ==
+ USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) {
+ /* notify the USB PHY */
+ usb_phy_notify_suspend(hcd->usb_phy, USB_SPEED_HIGH);
+ }
+ spin_lock_irqsave(&ehci->lock, flags);
+
+ set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
+ goto done;
+ }
+
+ /*
+ * After resume has finished, it needs do some post resume
+ * operation for some SoCs.
+ */
+ else if (typeReq == ClearPortFeature &&
+ wValue == USB_PORT_FEAT_C_SUSPEND) {
+
+ /* Make sure the resume has finished, it should be finished */
+ if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000))
+ ehci_err(ehci, "timeout waiting for resume\n");
+
+ temp = ehci_readl(ehci, status_reg);
+
+ if (ehci_port_speed(ehci, temp) ==
+ USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) {
+ /* notify the USB PHY */
+ usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH);
+ }
+ }
+
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /* Handle the hub control events here */
+ return orig_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+done:
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return retval;
+}
+
static irqreturn_t host_irq(struct ci_hdrc *ci)
{
- return usb_hcd_irq(ci->irq, ci->hcd);
+ if (ci->hcd)
+ return usb_hcd_irq(ci->irq, ci->hcd);
+ else
+ return IRQ_NONE;
}
static int host_start(struct ci_hdrc *ci)
@@ -164,6 +338,13 @@ static int host_start(struct ci_hdrc *ci)
}
}
+ if (ci_otg_is_fsm_mode(ci)) {
+ if (ci->fsm.id && ci->fsm.otg->state <= OTG_STATE_B_HOST)
+ hcd->self.is_b_host = 1;
+ else
+ hcd->self.is_b_host = 0;
+ }
+
ret = usb_add_hcd(hcd, 0, 0);
if (ret) {
goto disable_reg;
@@ -173,11 +354,17 @@ static int host_start(struct ci_hdrc *ci)
ci->hcd = hcd;
if (ci_otg_is_fsm_mode(ci)) {
+ hcd->self.otg_fsm = &ci->fsm;
otg->host = &hcd->self;
hcd->self.otg_port = 1;
}
}
+ if (ci->platdata->notify_event &&
+ (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC))
+ ci->platdata->notify_event
+ (ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT);
+
return ret;
disable_reg:
@@ -205,16 +392,123 @@ static void host_stop(struct ci_hdrc *ci)
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
(ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
regulator_disable(ci->platdata->reg_vbus);
+ if (hcd->self.is_b_host)
+ hcd->self.is_b_host = 0;
}
ci->hcd = NULL;
ci->otg.host = NULL;
}
+bool ci_hdrc_host_has_device(struct ci_hdrc *ci)
+{
+ struct usb_device *roothub;
+ int i;
+
+ if ((ci->role == CI_ROLE_HOST) && ci->hcd) {
+ roothub = ci->hcd->self.root_hub;
+ for (i = 0; i < roothub->maxchild; ++i) {
+ if (usb_hub_find_child(roothub, (i + 1)))
+ return true;
+ }
+ }
+ return false;
+}
+
+static void ci_hdrc_host_save_for_power_lost(struct ci_hdrc *ci)
+{
+ struct ehci_hcd *ehci;
+
+ if (!ci->hcd)
+ return;
+
+ ehci = hcd_to_ehci(ci->hcd);
+ /* save EHCI registers */
+ ci->pm_usbmode = ehci_readl(ehci, &ehci->regs->usbmode);
+ ci->pm_command = ehci_readl(ehci, &ehci->regs->command);
+ ci->pm_command &= ~CMD_RUN;
+ ci->pm_status = ehci_readl(ehci, &ehci->regs->status);
+ ci->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable);
+ ci->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index);
+ ci->pm_segment = ehci_readl(ehci, &ehci->regs->segment);
+ ci->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list);
+ ci->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next);
+ ci->pm_configured_flag =
+ ehci_readl(ehci, &ehci->regs->configured_flag);
+ ci->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]);
+}
+
+static void ci_hdrc_host_restore_from_power_lost(struct ci_hdrc *ci)
+{
+ struct ehci_hcd *ehci;
+ unsigned long flags;
+ u32 tmp;
+ int step_ms;
+ /*
+ * If the vbus is off during system suspend, most of devices will pull
+ * DP up within 200ms when they see vbus, set 1000ms for safety.
+ */
+ int timeout_ms = 1000;
+
+ if (!ci->hcd)
+ return;
+
+ hw_controller_reset(ci);
+
+ ehci = hcd_to_ehci(ci->hcd);
+ spin_lock_irqsave(&ehci->lock, flags);
+ /* Restore EHCI registers */
+ ehci_writel(ehci, ci->pm_usbmode, &ehci->regs->usbmode);
+ ehci_writel(ehci, ci->pm_portsc, &ehci->regs->port_status[0]);
+ ehci_writel(ehci, ci->pm_command, &ehci->regs->command);
+ ehci_writel(ehci, ci->pm_intr_enable, &ehci->regs->intr_enable);
+ ehci_writel(ehci, ci->pm_frame_index, &ehci->regs->frame_index);
+ ehci_writel(ehci, ci->pm_segment, &ehci->regs->segment);
+ ehci_writel(ehci, ci->pm_frame_list, &ehci->regs->frame_list);
+ ehci_writel(ehci, ci->pm_async_next, &ehci->regs->async_next);
+ ehci_writel(ehci, ci->pm_configured_flag,
+ &ehci->regs->configured_flag);
+ /* Restore the PHY's connect notifier setting */
+ if (ci->pm_portsc & PORTSC_HSP)
+ usb_phy_notify_connect(ci->usb_phy, USB_SPEED_HIGH);
+
+ tmp = ehci_readl(ehci, &ehci->regs->command);
+ tmp |= CMD_RUN;
+ ehci_writel(ehci, tmp, &ehci->regs->command);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ if (!(ci->pm_portsc & PORTSC_CCS))
+ return;
+
+ for (step_ms = 0; step_ms < timeout_ms; step_ms += 25) {
+ if (ehci_readl(ehci, &ehci->regs->port_status[0]) & PORTSC_CCS)
+ break;
+ msleep(25);
+ }
+}
+
+static void ci_hdrc_host_suspend(struct ci_hdrc *ci)
+{
+ if (ci_hdrc_host_has_device(ci))
+ imx_gpc_mf_request_on(ci->irq, 1);
+
+ ci_hdrc_host_save_for_power_lost(ci);
+}
+
+static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost)
+{
+ imx_gpc_mf_request_on(ci->irq, 0);
+
+ if (power_lost)
+ ci_hdrc_host_restore_from_power_lost(ci);
+}
void ci_hdrc_host_destroy(struct ci_hdrc *ci)
{
- if (ci->role == CI_ROLE_HOST && ci->hcd)
+ if (ci->role == CI_ROLE_HOST && ci->hcd) {
+ disable_irq_nosync(ci->irq);
host_stop(ci);
+ enable_irq(ci->irq);
+ }
}
static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
@@ -222,6 +516,8 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
int port;
u32 tmp;
+ struct device *dev = hcd->self.controller;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret = orig_bus_suspend(hcd);
@@ -251,6 +547,30 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
* It needs a short delay between set RS bit and PHCD.
*/
usleep_range(150, 200);
+
+ /*
+ * If a transaction is in progress, there may be
+ * a delay in suspending the port. Poll until the
+ * port is suspended.
+ */
+ if (test_bit(port, &ehci->bus_suspended) &&
+ ehci_handshake(ehci, reg, PORT_SUSPEND,
+ PORT_SUSPEND, 5000))
+ ehci_err(ehci, "timeout waiting for SUSPEND\n");
+
+ if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC)
+ ci_ehci_override_wakeup_flag(ehci, reg,
+ PORT_WKDISC_E | PORT_WKCONN_E, false);
+
+ if (hcd->usb_phy && test_bit(port, &ehci->bus_suspended)
+ && (ehci_port_speed(ehci, portsc) ==
+ USB_PORT_STAT_HIGH_SPEED))
+ /*
+ * notify the USB PHY, it is for global
+ * suspend case.
+ */
+ usb_phy_notify_suspend(hcd->usb_phy,
+ USB_SPEED_HIGH);
break;
}
}
@@ -272,6 +592,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci)
rdrv->start = host_start;
rdrv->stop = host_stop;
rdrv->irq = host_irq;
+ rdrv->suspend = ci_hdrc_host_suspend;
+ rdrv->resume = ci_hdrc_host_resume;
rdrv->name = "host";
ci->roles[CI_ROLE_HOST] = rdrv;
@@ -282,5 +604,11 @@ void ci_hdrc_host_driver_init(void)
{
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
+ orig_bus_resume = ci_ehci_hc_driver.bus_resume;
+ orig_hub_control = ci_ehci_hc_driver.hub_control;
+
ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
+ ci_ehci_hc_driver.bus_resume = ci_imx_ehci_bus_resume;
+ ci_ehci_hc_driver.hub_control = ci_imx_ehci_hub_control;
+ ci_ehci_hc_driver.start_port_reset = ci_start_port_reset;
}
diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h
index 70112cf0f195..448df8894e26 100644
--- a/drivers/usb/chipidea/host.h
+++ b/drivers/usb/chipidea/host.h
@@ -7,6 +7,7 @@
int ci_hdrc_host_init(struct ci_hdrc *ci);
void ci_hdrc_host_destroy(struct ci_hdrc *ci);
void ci_hdrc_host_driver_init(void);
+bool ci_hdrc_host_has_device(struct ci_hdrc *ci);
#else
@@ -20,11 +21,16 @@ static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci)
}
-static void ci_hdrc_host_driver_init(void)
+static inline void ci_hdrc_host_driver_init(void)
{
}
+static inline bool ci_hdrc_host_has_device(struct ci_hdrc *ci)
+{
+ return false;
+}
+
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 8bf4032226ed..412232d84ff1 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -1,7 +1,8 @@
/*
* otg.c - ChipIdea USB IP core OTG driver
*
- * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* Author: Peter Chen
*
@@ -23,6 +24,7 @@
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"
+#include "host.h"
/**
* hw_read_otgsc returns otgsc register bits value.
@@ -129,6 +131,46 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
return role;
}
+/*
+ * Handling vbus glitch
+ * We only need to consider glitch for without usb connection,
+ * With usb connection, we consider it as real disconnection.
+ *
+ * If the vbus can't be kept above B session valid for timeout value,
+ * we think it is a vbus glitch, otherwise it's a valid vbus.
+ */
+#define CI_VBUS_CONNECT_TIMEOUT_MS 300
+static int ci_is_vbus_glitch(struct ci_hdrc *ci)
+{
+ int i;
+
+ for (i = 0; i < CI_VBUS_CONNECT_TIMEOUT_MS/20; i++) {
+ if (hw_read_otgsc(ci, OTGSC_AVV)) {
+ return 0;
+ } else if (!hw_read_otgsc(ci, OTGSC_BSV)) {
+ dev_warn(ci->dev, "there is a vbus glitch\n");
+ return 1;
+ }
+ msleep(20);
+ }
+
+ return 0;
+}
+
+void ci_handle_vbus_connected(struct ci_hdrc *ci)
+{
+ /*
+ * TODO: if the platform does not supply 5v to udc, or use other way
+ * to supply 5v, it needs to use other conditions to call
+ * usb_gadget_vbus_connect.
+ */
+ if (!ci->is_otg)
+ return;
+
+ if (hw_read_otgsc(ci, OTGSC_BSV) && !ci_is_vbus_glitch(ci))
+ usb_gadget_vbus_connect(&ci->gadget);
+}
+
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
if (!ci->is_otg)
@@ -165,10 +207,13 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
return 0;
}
-static void ci_handle_id_switch(struct ci_hdrc *ci)
+void ci_handle_id_switch(struct ci_hdrc *ci)
{
- enum ci_role role = ci_otg_role(ci);
+ enum ci_role role;
+ int ret = 0;
+ mutex_lock(&ci->mutex);
+ role = ci_otg_role(ci);
if (role != ci->role) {
dev_dbg(ci->dev, "switching from %s to %s\n",
ci_role(ci)->name, ci->roles[role]->name);
@@ -184,14 +229,57 @@ static void ci_handle_id_switch(struct ci_hdrc *ci)
* care vbus on the board, since it will not affect
* external connector status.
*/
- hw_wait_vbus_lower_bsv(ci);
+ ret = hw_wait_vbus_lower_bsv(ci);
+ else if (ci->vbus_active)
+ /*
+ * If the role switch happens(e.g. during
+ * system sleep), and we lose vbus drop
+ * event, disconnect gadget for it before
+ * start host.
+ */
+ usb_gadget_vbus_disconnect(&ci->gadget);
ci_role_start(ci, role);
/* vbus change may have already occurred */
if (role == CI_ROLE_GADGET)
ci_handle_vbus_change(ci);
+
+ /*
+ * If the role switch happens(e.g. during system
+ * sleep) and vbus keeps on afterwards, we connect
+ * gadget as vbus connect event lost.
+ */
+ if (ret == -ETIMEDOUT)
+ usb_gadget_vbus_connect(&ci->gadget);
+ }
+ mutex_unlock(&ci->mutex);
+}
+
+static void ci_handle_vbus_glitch(struct ci_hdrc *ci)
+{
+ bool valid_vbus_change = false;
+
+ if (hw_read_otgsc(ci, OTGSC_BSV)) {
+ if (!ci_is_vbus_glitch(ci)) {
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->fsm.b_sess_vld = 1;
+ ci->fsm.b_ssend_srp = 0;
+ otg_del_timer(&ci->fsm, B_SSEND_SRP);
+ otg_del_timer(&ci->fsm, B_SRP_FAIL);
+ }
+ valid_vbus_change = true;
+ }
+ } else {
+ if (ci->vbus_active && !ci_otg_is_fsm_mode(ci))
+ valid_vbus_change = true;
+ }
+
+ if (valid_vbus_change) {
+ ci->b_sess_valid_event = true;
+ ci_otg_queue_work(ci);
}
}
+
/**
* ci_otg_work - perform otg (vbus/id) event handle
* @work: work struct
@@ -200,6 +288,15 @@ static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
+ if (ci->vbus_glitch_check_event) {
+ ci->vbus_glitch_check_event = false;
+ pm_runtime_get_sync(ci->dev);
+ ci_handle_vbus_glitch(ci);
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+ return;
+ }
+
if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
enable_irq(ci->irq);
return;
@@ -248,13 +345,14 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
*/
void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
{
+ /* Disable all OTG irq and clear status */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
if (ci->wq) {
flush_workqueue(ci->wq);
destroy_workqueue(ci->wq);
+ ci->wq = NULL;
}
- /* Disable all OTG irq and clear status */
- hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
- OTGSC_INT_STATUS_BITS);
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_remove(ci);
}
diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
index a5557c70034a..95fa6c2b675a 100644
--- a/drivers/usb/chipidea/otg.h
+++ b/drivers/usb/chipidea/otg.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2015 Freescale Semiconductor, Inc.
*
* Author: Peter Chen
*
@@ -17,11 +17,17 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
void ci_handle_vbus_change(struct ci_hdrc *ci);
+void ci_handle_id_switch(struct ci_hdrc *ci);
+void ci_handle_vbus_connected(struct ci_hdrc *ci);
static inline void ci_otg_queue_work(struct ci_hdrc *ci)
{
- disable_irq_nosync(ci->irq);
- if (queue_work(ci->wq, &ci->work) == false)
- enable_irq(ci->irq);
+ if (ci->wq) {
+ disable_irq_nosync(ci->irq);
+ if (!queue_work(ci->wq, &ci->work))
+ enable_irq(ci->irq);
+ } else {
+ WARN_ON(!ci->wq);
+ }
}
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 5ea0246f650d..b5d079a54aa8 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -28,7 +28,10 @@
#include "ci.h"
#include "bits.h"
#include "otg.h"
+#include "udc.h"
#include "otg_fsm.h"
+#include "udc.h"
+#include "host.h"
/* Add for otg: interact with user space app */
static ssize_t
@@ -215,6 +218,11 @@ static unsigned otg_timer_ms[] = {
0,
TB_DATA_PLS,
TB_SSEND_SRP,
+ TA_DP_END,
+ TA_TST_MAINT,
+ TB_SRP_REQD,
+ TB_TST_SUSP,
+ 0,
};
/*
@@ -300,6 +308,7 @@ static int a_wait_vfall_tmout(struct ci_hdrc *ci)
static int a_wait_bcon_tmout(struct ci_hdrc *ci)
{
ci->fsm.a_wait_bcon_tmout = 1;
+ dev_warn(ci->dev, "Device No Response\n");
return 0;
}
@@ -312,6 +321,7 @@ static int a_aidl_bdis_tmout(struct ci_hdrc *ci)
static int b_ase0_brst_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_ase0_brst_tmout = 1;
+ dev_warn(ci->dev, "Device No Response\n");
return 0;
}
@@ -336,6 +346,7 @@ static int b_se0_srp_tmout(struct ci_hdrc *ci)
static int b_srp_fail_tmout(struct ci_hdrc *ci)
{
ci->fsm.b_srp_done = 1;
+ dev_warn(ci->dev, "Device No Response\n");
return 1;
}
@@ -360,6 +371,57 @@ static int b_ssend_srp_tmout(struct ci_hdrc *ci)
return 1;
}
+static int a_dp_end_tmout(struct ci_hdrc *ci)
+{
+ ci->fsm.a_bus_drop = 0;
+ ci->fsm.a_srp_det = 1;
+ return 0;
+}
+
+static int a_tst_maint_tmout(struct ci_hdrc *ci)
+{
+ ci->fsm.tst_maint = 0;
+ if (ci->fsm.otg_vbus_off) {
+ ci->fsm.otg_vbus_off = 0;
+ dev_dbg(ci->dev,
+ "test device does not disconnect, end the session!\n");
+ }
+
+ /* End the session */
+ ci->fsm.a_bus_req = 0;
+ ci->fsm.a_bus_drop = 1;
+ return 0;
+}
+
+/*
+ * otg_srp_reqd feature
+ * After A(PET) turn off vbus, B(UUT) should start this timer to do SRP
+ * when the timer expires.
+ */
+static int b_srp_reqd_tmout(struct ci_hdrc *ci)
+{
+ ci->fsm.otg_srp_reqd = 0;
+ if (ci->fsm.otg->state == OTG_STATE_B_IDLE) {
+ ci->fsm.b_bus_req = 1;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * otg_hnp_reqd feature
+ * After B(UUT) switch to host, B should hand host role back
+ * to A(PET) within TB_TST_SUSP after setting configuration.
+ */
+static int b_tst_susp_tmout(struct ci_hdrc *ci)
+{
+ if (ci->fsm.otg->state == OTG_STATE_B_HOST) {
+ ci->fsm.b_bus_req = 0;
+ return 0;
+ }
+ return 1;
+}
+
/*
* Keep this list in the same order as timers indexed
* by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
@@ -377,6 +439,11 @@ static int (*otg_timer_handlers[])(struct ci_hdrc *) = {
NULL, /* A_WAIT_ENUM */
b_data_pls_tmout, /* B_DATA_PLS */
b_ssend_srp_tmout, /* B_SSEND_SRP */
+ a_dp_end_tmout, /* A_DP_END */
+ a_tst_maint_tmout, /* A_TST_MAINT */
+ b_srp_reqd_tmout, /* B_SRP_REQD */
+ b_tst_susp_tmout, /* B_TST_SUSP */
+ NULL, /* HNP_POLLING */
};
/*
@@ -463,6 +530,9 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
if (on) {
+ ci->platdata->notify_event(ci,
+ CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF);
+
/* Enable power power */
hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
PORTSC_PP);
@@ -486,6 +556,7 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
fsm->a_bus_drop = 1;
fsm->a_bus_req = 0;
+ fsm->b_conn = 0;
}
}
@@ -562,11 +633,16 @@ static int ci_otg_start_host(struct otg_fsm *fsm, int on)
static int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
{
struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+ unsigned long flags;
+ int gadget_ready = 0;
- if (on)
- usb_gadget_vbus_connect(&ci->gadget);
- else
- usb_gadget_vbus_disconnect(&ci->gadget);
+ spin_lock_irqsave(&ci->lock, flags);
+ ci->vbus_active = on;
+ if (ci->driver)
+ gadget_ready = 1;
+ spin_unlock_irqrestore(&ci->lock, flags);
+ if (gadget_ready)
+ ci_hdrc_gadget_connect(&ci->gadget, on);
return 0;
}
@@ -584,13 +660,26 @@ static struct otg_fsm_ops ci_otg_ops = {
int ci_otg_fsm_work(struct ci_hdrc *ci)
{
- /*
- * Don't do fsm transition for B device
- * when there is no gadget class driver
- */
- if (ci->fsm.id && !(ci->driver) &&
- ci->fsm.otg->state < OTG_STATE_A_IDLE)
- return 0;
+ if (ci->fsm.id && ci->fsm.otg->state < OTG_STATE_A_IDLE) {
+ unsigned long flags;
+
+ /* Charger detection */
+ spin_lock_irqsave(&ci->lock, flags);
+ if (ci->b_sess_valid_event) {
+ ci->b_sess_valid_event = false;
+ ci->vbus_active = ci->fsm.b_sess_vld;
+ spin_unlock_irqrestore(&ci->lock, flags);
+ ci_usb_charger_connect(ci, ci->fsm.b_sess_vld);
+ spin_lock_irqsave(&ci->lock, flags);
+ }
+ spin_unlock_irqrestore(&ci->lock, flags);
+ /*
+ * Don't do fsm transition for B device if gadget
+ * driver is not binded.
+ */
+ if (!ci->driver)
+ return 0;
+ }
pm_runtime_get_sync(ci->dev);
if (otg_statemachine(&ci->fsm)) {
@@ -612,10 +701,14 @@ int ci_otg_fsm_work(struct ci_hdrc *ci)
PORTSC_PP, 0);
hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
+ /* FS termination override if needed */
+ ci->platdata->notify_event(ci,
+ CI_HDRC_IMX_TERM_SELECT_OVERRIDE_FS);
}
if (ci->id_event)
ci->id_event = false;
} else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) {
+ ci->fsm.b_sess_vld = hw_read_otgsc(ci, OTGSC_BSV);
if (ci->fsm.b_sess_vld) {
ci->fsm.power_up = 0;
/*
@@ -624,7 +717,8 @@ int ci_otg_fsm_work(struct ci_hdrc *ci)
*/
ci_otg_queue_work(ci);
}
- } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) {
+ } else if (ci->fsm.otg->state == OTG_STATE_A_HOST ||
+ ci->fsm.otg->state == OTG_STATE_A_WAIT_VFALL) {
pm_runtime_mark_last_busy(ci->dev);
pm_runtime_put_autosuspend(ci->dev);
return 0;
@@ -678,25 +772,16 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci)
}
break;
case OTG_STATE_A_PERIPHERAL:
- if (intr_sts & USBi_SLI) {
- fsm->b_bus_suspend = 1;
+ if (intr_sts & USBi_SLI)
/*
* Init a timer to know how long this suspend
* will continue, if time out, indicates B no longer
* wants to be host role
*/
ci_otg_add_timer(ci, A_BIDL_ADIS);
- }
- if (intr_sts & USBi_URI)
+ if (intr_sts & (USBi_URI | USBi_PCI))
ci_otg_del_timer(ci, A_BIDL_ADIS);
-
- if (intr_sts & USBi_PCI) {
- if (fsm->b_bus_suspend == 1) {
- ci_otg_del_timer(ci, A_BIDL_ADIS);
- fsm->b_bus_suspend = 0;
- }
- }
break;
case OTG_STATE_A_SUSPEND:
if ((intr_sts & USBi_PCI) && !port_conn) {
@@ -713,6 +798,15 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci)
case OTG_STATE_A_HOST:
if ((intr_sts & USBi_PCI) && !port_conn) {
fsm->b_conn = 0;
+ if (fsm->tst_maint) {
+ ci_otg_del_timer(ci, A_TST_MAINT);
+ if (fsm->otg_vbus_off) {
+ fsm->a_bus_req = 0;
+ fsm->a_bus_drop = 1;
+ fsm->otg_vbus_off = 0;
+ }
+ fsm->tst_maint = 0;
+ }
ci_otg_queue_work(ci);
}
break;
@@ -746,26 +840,36 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
if (otg_int_src) {
if (otg_int_src & OTGSC_DPIS) {
hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
- fsm->a_srp_det = 1;
- fsm->a_bus_drop = 0;
+ ci->platdata->notify_event(ci,
+ CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF);
+ ci_otg_add_timer(ci, A_DP_END);
} else if (otg_int_src & OTGSC_IDIS) {
hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
if (fsm->id == 0) {
fsm->a_bus_drop = 0;
fsm->a_bus_req = 1;
ci->id_event = true;
+ } else {
+ /*
+ * Disable term select override and data pulse
+ * for B device.
+ */
+ ci->platdata->notify_event(ci,
+ CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF);
}
} else if (otg_int_src & OTGSC_BSVIS) {
hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
- if (otgsc & OTGSC_BSV) {
- fsm->b_sess_vld = 1;
- ci_otg_del_timer(ci, B_SSEND_SRP);
- ci_otg_del_timer(ci, B_SRP_FAIL);
- fsm->b_ssend_srp = 0;
- } else {
+ if (!(otgsc & OTGSC_BSV) && fsm->b_sess_vld) {
+ ci->b_sess_valid_event = true;
fsm->b_sess_vld = 0;
if (fsm->id)
ci_otg_add_timer(ci, B_SSEND_SRP);
+ if (fsm->b_bus_req)
+ fsm->b_bus_req = 0;
+ if (fsm->otg_srp_reqd)
+ ci_otg_add_timer(ci, B_SRP_REQD);
+ } else {
+ ci->vbus_glitch_check_event = true;
}
} else if (otg_int_src & OTGSC_AVVIS) {
hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS);
@@ -802,6 +906,7 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
ci->otg.gadget = &ci->gadget;
ci->fsm.otg = &ci->otg;
ci->fsm.power_up = 1;
+ ci->fsm.hnp_polling = 1;
ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
ci->fsm.otg->state = OTG_STATE_UNDEFINED;
ci->fsm.ops = &ci_otg_ops;
@@ -844,5 +949,56 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
{
+ enum otg_fsm_timer i;
+
+ mutex_lock(&ci->fsm.lock);
+ ci->fsm.otg->state = OTG_STATE_UNDEFINED;
+ mutex_unlock(&ci->fsm.lock);
+
+ for (i = 0; i < NUM_OTG_FSM_TIMERS; i++)
+ otg_del_timer(&ci->fsm, i);
+
+ ci->enabled_otg_timer_bits = 0;
+
+ /* Turn off vbus if vbus is on */
+ if (ci->fsm.drv_vbus)
+ otg_drv_vbus(&ci->fsm, 0);
+
sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group);
}
+
+/* Restart OTG fsm if resume from power lost */
+void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci)
+{
+ struct otg_fsm *fsm = &ci->fsm;
+ int id_status = fsm->id;
+
+ /* Update fsm if power lost in peripheral state */
+ if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) {
+ fsm->b_sess_vld = 0;
+ otg_statemachine(fsm);
+ }
+
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
+ hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE);
+
+ /* Update fsm variables for restart */
+ fsm->id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
+ if (fsm->id) {
+ fsm->b_ssend_srp =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1;
+ fsm->b_sess_vld =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
+ } else if (fsm->id != id_status) {
+ /* ID changes to be 0 */
+ fsm->a_bus_drop = 0;
+ fsm->a_bus_req = 1;
+ ci->id_event = true;
+ }
+
+ if (ci_hdrc_host_has_device(ci) &&
+ !hw_read(ci, OP_PORTSC, PORTSC_CCS))
+ fsm->b_conn = 0;
+
+ ci_otg_fsm_work(ci);
+}
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
index 6366fe398ba6..ff77d2e1bb64 100644
--- a/drivers/usb/chipidea/otg_fsm.h
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
*
* Author: Jun Li
*
@@ -25,7 +25,7 @@
* ->DC Electrical Timing
*/
/* Wait for VBUS Fall */
-#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7
+#define TA_WAIT_VFALL (500) /* a_wait_vfall: section 7.1.7
* a_wait_vfall_tmr: section: 7.4.5.2
*/
/* Wait for B-Connect */
@@ -38,9 +38,13 @@
* TA_AIDL_BDIS: section 5.5, Table 5-1
*/
/* B-Idle to A-Disconnect */
-#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1
- * 500ms is used for B switch to host
- * for safe
+#define TA_BIDL_ADIS (160) /* TA_BIDL_ADIS: section 5.2.1
+ * 155ms ~ 200 ms
+ */
+
+#define TA_DP_END (200)
+#define TA_TST_MAINT (9900) /* OTG test device session maintain
+ * timer, 9.9s~10.1s
*/
/*
@@ -63,6 +67,14 @@
#define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */
#define TB_AIDL_BDIS (20) /* 4ms ~ 150ms, section 5.2.1 */
+#define TB_SRP_REQD (2000) /* For otg_srp_reqd to start data
+ * pulse after A(PET) turn off v-bus
+ */
+
+#define TB_TST_SUSP (20) /* B-dev hand host role back to A-dev
+ * via suspend bus after set config.
+ * max: 100ms
+ */
#if IS_ENABLED(CONFIG_USB_OTG_FSM)
@@ -71,6 +83,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci);
irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci);
void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci);
void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci);
#else
@@ -99,6 +112,11 @@ static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
}
+static inline void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci)
+{
+
+}
+
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index e3a44bea7bb7..6afb31d99b0a 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -752,6 +752,11 @@ __acquires(ci->lock)
{
int retval;
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->fsm.otg_srp_reqd = 0;
+ ci->fsm.otg_hnp_reqd = 0;
+ }
+
spin_unlock(&ci->lock);
if (ci->gadget.speed != USB_SPEED_UNKNOWN)
usb_gadget_udc_reset(&ci->gadget, ci->driver);
@@ -875,7 +880,10 @@ __acquires(hwep->lock)
return -ENOMEM;
req->complete = isr_get_status_complete;
- req->length = 2;
+ if (setup->wIndex == OTG_STS_SELECTOR)
+ req->length = 1;
+ else
+ req->length = 2;
req->buf = kzalloc(req->length, gfp_flags);
if (req->buf == NULL) {
retval = -ENOMEM;
@@ -883,8 +891,16 @@ __acquires(hwep->lock)
}
if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
- *(u16 *)req->buf = (ci->remote_wakeup << 1) |
- ci->gadget.is_selfpowered;
+ if ((setup->wIndex == OTG_STS_SELECTOR) &&
+ ci_otg_is_fsm_mode(ci)) {
+ if (ci->gadget.host_request_flag)
+ *(u8 *)req->buf = HOST_REQUEST_FLAG;
+ else
+ *(u8 *)req->buf = 0;
+ } else {
+ *(u16 *)req->buf = (ci->remote_wakeup << 1) |
+ ci->gadget.is_selfpowered;
+ }
} else if ((setup->bRequestType & USB_RECIP_MASK) \
== USB_RECIP_ENDPOINT) {
dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ?
@@ -1006,6 +1022,28 @@ static int otg_a_alt_hnp_support(struct ci_hdrc *ci)
return isr_setup_status_phase(ci);
}
+static int otg_srp_reqd(struct ci_hdrc *ci)
+{
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->fsm.otg_srp_reqd = 1;
+ return isr_setup_status_phase(ci);
+ } else {
+ return -ENOTSUPP;
+ }
+}
+
+static int otg_hnp_reqd(struct ci_hdrc *ci)
+{
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->fsm.otg_hnp_reqd = 1;
+ ci->fsm.b_bus_req = 1;
+ ci->gadget.host_request_flag = 1;
+ return isr_setup_status_phase(ci);
+ } else {
+ return -ENOTSUPP;
+ }
+}
+
/**
* isr_setup_packet_handler: setup packet handler
* @ci: UDC descriptor
@@ -1076,8 +1114,9 @@ __acquires(ci->lock)
type != (USB_DIR_IN|USB_RECIP_ENDPOINT) &&
type != (USB_DIR_IN|USB_RECIP_INTERFACE))
goto delegate;
- if (le16_to_cpu(req.wLength) != 2 ||
- le16_to_cpu(req.wValue) != 0)
+ if ((le16_to_cpu(req.wLength) != 2 &&
+ le16_to_cpu(req.wLength) != 1) ||
+ le16_to_cpu(req.wValue) != 0)
break;
err = isr_get_status_response(ci, &req);
break;
@@ -1128,6 +1167,12 @@ __acquires(ci->lock)
err = isr_setup_status_phase(
ci);
break;
+ case TEST_OTG_SRP_REQD:
+ err = otg_srp_reqd(ci);
+ break;
+ case TEST_OTG_HNP_REQD:
+ err = otg_hnp_reqd(ci);
+ break;
default:
break;
}
@@ -1538,27 +1583,19 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
gadget_ready = 1;
spin_unlock_irqrestore(&ci->lock, flags);
- if (gadget_ready) {
- if (is_active) {
- pm_runtime_get_sync(&_gadget->dev);
- hw_device_reset(ci);
- hw_device_state(ci, ci->ep0out->qh.dma);
- usb_gadget_set_state(_gadget, USB_STATE_POWERED);
- usb_udc_vbus_handler(_gadget, true);
- } else {
- usb_udc_vbus_handler(_gadget, false);
- if (ci->driver)
- ci->driver->disconnect(&ci->gadget);
- hw_device_state(ci, 0);
- if (ci->platdata->notify_event)
- ci->platdata->notify_event(ci,
- CI_HDRC_CONTROLLER_STOPPED_EVENT);
- _gadget_stop_activity(&ci->gadget);
- pm_runtime_put_sync(&_gadget->dev);
- usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);
- }
+ if (ci->usb_phy) {
+ /* Charger Detection */
+ ci_usb_charger_connect(ci, is_active);
+
+ if (is_active)
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS);
+ else
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE);
}
+ if (gadget_ready)
+ ci_hdrc_gadget_connect(_gadget, is_active);
+
return 0;
}
@@ -1623,12 +1660,12 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST)
return 0;
- pm_runtime_get_sync(&ci->gadget.dev);
+ pm_runtime_get_sync(ci->dev);
if (is_on)
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
else
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
- pm_runtime_put_sync(&ci->gadget.dev);
+ pm_runtime_put_sync(ci->dev);
return 0;
}
@@ -1779,23 +1816,14 @@ static int ci_udc_start(struct usb_gadget *gadget,
ci->driver = driver;
/* Start otg fsm for B-device */
- if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) {
- ci_hdrc_otg_fsm_start(ci);
+ if (ci_otg_is_fsm_mode(ci)) {
+ if (ci->fsm.id)
+ ci_hdrc_otg_fsm_start(ci);
return retval;
}
- pm_runtime_get_sync(&ci->gadget.dev);
- if (ci->vbus_active) {
- hw_device_reset(ci);
- } else {
- usb_udc_vbus_handler(&ci->gadget, false);
- pm_runtime_put_sync(&ci->gadget.dev);
- return retval;
- }
-
- retval = hw_device_state(ci, ci->ep0out->qh.dma);
- if (retval)
- pm_runtime_put_sync(&ci->gadget.dev);
+ if (ci->vbus_active)
+ ci_hdrc_gadget_connect(&ci->gadget, 1);
return retval;
}
@@ -1834,7 +1862,7 @@ static int ci_udc_stop(struct usb_gadget *gadget)
CI_HDRC_CONTROLLER_STOPPED_EVENT);
_gadget_stop_activity(&ci->gadget);
spin_lock_irqsave(&ci->lock, flags);
- pm_runtime_put(&ci->gadget.dev);
+ pm_runtime_put(ci->dev);
}
ci->driver = NULL;
@@ -1880,6 +1908,9 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
if (USBi_PCI & intr) {
ci->gadget.speed = hw_port_is_high_speed(ci) ?
USB_SPEED_HIGH : USB_SPEED_FULL;
+ if (ci->usb_phy)
+ usb_phy_set_event(ci->usb_phy,
+ USB_EVENT_ENUMERATED);
if (ci->suspended) {
if (ci->driver->resume) {
spin_unlock(&ci->lock);
@@ -1966,9 +1997,6 @@ static int udc_start(struct ci_hdrc *ci)
if (retval)
goto destroy_eps;
- pm_runtime_no_callbacks(&ci->gadget.dev);
- pm_runtime_enable(&ci->gadget.dev);
-
return retval;
destroy_eps:
@@ -1998,10 +2026,67 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
dma_pool_destroy(ci->qh_pool);
}
+int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active)
+{
+ int ret = 0;
+
+ if (!(ci->platdata->flags & CI_HDRC_PHY_CHARGER_DETECTION))
+ return 0;
+
+ pm_runtime_get_sync(ci->dev);
+
+ if (ci->usb_phy->charger_detect) {
+ usb_phy_set_charger_state(ci->usb_phy, is_active ?
+ USB_CHARGER_PRESENT : USB_CHARGER_ABSENT);
+ } else if (ci->platdata->notify_event) {
+ ret = ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_VBUS_EVENT);
+ schedule_work(&ci->usb_phy->chg_work);
+ }
+
+ pm_runtime_put_sync(ci->dev);
+
+ return ret;
+}
+
+/**
+ * ci_hdrc_gadget_connect: caller make sure gadget driver is binded
+ */
+void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active)
+{
+ struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
+
+ if (is_active) {
+ pm_runtime_get_sync(ci->dev);
+ hw_device_reset(ci);
+ hw_device_state(ci, ci->ep0out->qh.dma);
+ usb_gadget_set_state(gadget, USB_STATE_POWERED);
+ usb_udc_vbus_handler(gadget, true);
+ } else {
+ usb_udc_vbus_handler(gadget, false);
+ if (ci->driver)
+ ci->driver->disconnect(gadget);
+ hw_device_state(ci, 0);
+ if (ci->platdata->notify_event)
+ ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_STOPPED_EVENT);
+ _gadget_stop_activity(gadget);
+ pm_runtime_put_sync(ci->dev);
+ usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
+ }
+}
+
static int udc_id_switch_for_device(struct ci_hdrc *ci)
{
- if (ci->is_otg)
- /* Clear and enable BSV irq */
+ if (!ci->is_otg)
+ return 0;
+
+ /*
+ * Clear and enable BSV irq for A-device switch to B-device
+ * (in otg fsm mode, means A_IDLE->B_DILE) due to ID change.
+ */
+ if (!ci_otg_is_fsm_mode(ci) ||
+ ci->fsm.otg->state == OTG_STATE_A_IDLE)
hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
OTGSC_BSVIS | OTGSC_BSVIE);
@@ -2010,16 +2095,59 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci)
static void udc_id_switch_for_host(struct ci_hdrc *ci)
{
+ if (!ci->is_otg)
+ return;
+
/*
- * host doesn't care B_SESSION_VALID event
- * so clear and disbale BSV irq
+ * Clear and disbale BSV irq for B-device switch to A-device
+ * (in otg fsm mode, means B_IDLE->A_IDLE) due to ID change.
*/
- if (ci->is_otg)
+ if (!ci_otg_is_fsm_mode(ci) ||
+ ci->fsm.otg->state == OTG_STATE_B_IDLE ||
+ ci->fsm.otg->state == OTG_STATE_UNDEFINED)
hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
ci->vbus_active = 0;
}
+static void udc_suspend_for_power_lost(struct ci_hdrc *ci)
+{
+ /*
+ * Set OP_ENDPTLISTADDR to be non-zero for
+ * checking if controller resume from power lost
+ * in non-host mode.
+ */
+ if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0)
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0);
+}
+
+/* Power lost with device mode */
+static void udc_resume_from_power_lost(struct ci_hdrc *ci)
+{
+ if (ci->is_otg)
+ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
+ OTGSC_BSVIS | OTGSC_BSVIE);
+}
+
+static void udc_suspend(struct ci_hdrc *ci)
+{
+ udc_suspend_for_power_lost(ci);
+
+ if (ci->driver && ci->vbus_active &&
+ (ci->gadget.state != USB_STATE_SUSPENDED))
+ usb_gadget_disconnect(&ci->gadget);
+}
+
+static void udc_resume(struct ci_hdrc *ci, bool power_lost)
+{
+ if (power_lost) {
+ udc_resume_from_power_lost(ci);
+ } else {
+ if (ci->driver && ci->vbus_active)
+ usb_gadget_connect(&ci->gadget);
+ }
+}
+
/**
* ci_hdrc_gadget_init - initialize device related bits
* ci: the controller
@@ -2041,6 +2169,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
rdrv->start = udc_id_switch_for_device;
rdrv->stop = udc_id_switch_for_host;
rdrv->irq = udc_irq;
+ rdrv->suspend = udc_suspend;
+ rdrv->resume = udc_resume;
rdrv->name = "gadget";
ret = udc_start(ci);
diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h
index 2ecd1174d66c..7b7622a68b5f 100644
--- a/drivers/usb/chipidea/udc.h
+++ b/drivers/usb/chipidea/udc.h
@@ -85,6 +85,8 @@ struct ci_hw_req {
int ci_hdrc_gadget_init(struct ci_hdrc *ci);
void ci_hdrc_gadget_destroy(struct ci_hdrc *ci);
+int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active);
+void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active);
#else
@@ -98,6 +100,17 @@ static inline void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
}
+static inline int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active)
+{
+ return 0;
+}
+
+static inline void ci_hdrc_gadget_connect(struct usb_gadget *gadget,
+ int is_active)
+{
+
+}
+
#endif
#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index b7477fd4443a..bb45161f5c05 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -1,5 +1,6 @@
/*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
@@ -14,6 +15,8 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include "ci_hdrc_imx.h"
@@ -69,11 +72,24 @@
#define MX6_BM_NON_BURST_SETTING BIT(1)
#define MX6_BM_OVER_CUR_DIS BIT(7)
#define MX6_BM_OVER_CUR_POLARITY BIT(8)
+#define MX6_BM_PRW_POLARITY BIT(9)
#define MX6_BM_WAKEUP_ENABLE BIT(10)
+#define MX6_BM_UTMI_ON_CLOCK BIT(13)
#define MX6_BM_ID_WAKEUP BIT(16)
#define MX6_BM_VBUS_WAKEUP BIT(17)
#define MX6SX_BM_DPDM_WAKEUP_EN BIT(29)
#define MX6_BM_WAKEUP_INTR BIT(31)
+
+#define MX6_USB_HSIC_CTRL_OFFSET 0x10
+/* Send resume signal without 480Mhz PHY clock */
+#define MX6SX_BM_HSIC_AUTO_RESUME BIT(23)
+/* set before portsc.suspendM = 1 */
+#define MX6_BM_HSIC_DEV_CONN BIT(21)
+/* HSIC enable */
+#define MX6_BM_HSIC_EN BIT(12)
+/* Force HSIC module 480M clock on, even when in Host is in suspend mode */
+#define MX6_BM_HSIC_CLK_ON BIT(11)
+
#define MX6_USB_OTG1_PHY_CTRL 0x18
/* For imx6dql, it is host-only controller, for later imx6, it is otg's */
#define MX6_USB_OTG2_PHY_CTRL 0x1c
@@ -86,12 +102,65 @@
#define VF610_OVER_CUR_DIS BIT(7)
#define MX7D_USBNC_USB_CTRL2 0x4
+/* The default DM/DP value is pull-down */
+#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_EN BIT(15)
+#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_VAL BIT(14)
+#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_EN BIT(13)
+#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_VAL BIT(12)
+#define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \
+ BIT(14) | BIT(15))
+#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN BIT(8)
+#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK (BIT(7) | BIT(6))
+#define MX7D_USBNC_USB_CTRL2_OPMODE(v) (v << 6)
+#define MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING MX7D_USBNC_USB_CTRL2_OPMODE(1)
+#define MX7D_USBNC_USB_CTRL2_TERMSEL_OVERRIDE_EN BIT(5)
+#define MX7D_USBNC_USB_CTRL2_TERMSEL_OVERRIDE_VAL BIT(4)
+#define MX7D_USBNC_AUTO_RESUME BIT(2)
+
#define MX7D_USB_VBUS_WAKEUP_SOURCE_MASK 0x3
#define MX7D_USB_VBUS_WAKEUP_SOURCE(v) (v << 0)
#define MX7D_USB_VBUS_WAKEUP_SOURCE_VBUS MX7D_USB_VBUS_WAKEUP_SOURCE(0)
#define MX7D_USB_VBUS_WAKEUP_SOURCE_AVALID MX7D_USB_VBUS_WAKEUP_SOURCE(1)
#define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID MX7D_USB_VBUS_WAKEUP_SOURCE(2)
#define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END MX7D_USB_VBUS_WAKEUP_SOURCE(3)
+#define MX7D_USB_TERMSEL_OVERRIDE BIT(4)
+#define MX7D_USB_TERMSEL_OVERRIDE_EN BIT(5)
+
+#define MX7D_USB_OTG_PHY_CFG1 0x30
+#define TXPREEMPAMPTUNE0_BIT 28
+#define TXPREEMPAMPTUNE0_MASK (3 << 28)
+#define TXVREFTUNE0_BIT 20
+#define TXVREFTUNE0_MASK (0xf << 20)
+
+#define MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB BIT(3)
+#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2)
+#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1)
+#define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0)
+#define MX7D_USB_OTG_PHY_CFG2 0x34
+
+#define MX7D_USB_OTG_PHY_STATUS 0x3c
+#define MX7D_USB_OTG_PHY_STATUS_CHRGDET BIT(29)
+#define MX7D_USB_OTG_PHY_STATUS_VBUS_VLD BIT(3)
+#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE1 BIT(1)
+#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE0 BIT(0)
+
+#define ANADIG_ANA_MISC0 0x150
+#define ANADIG_ANA_MISC0_SET 0x154
+#define ANADIG_ANA_MISC0_CLK_DELAY(x) ((x >> 26) & 0x7)
+
+#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4
+#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8
+#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18)
+
+#define ANADIG_USB1_VBUS_DET_STAT 0x1c0
+#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
+
+#define ANADIG_USB1_CHRG_DET_STAT 0x1d0
+#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2)
+#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1)
+#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0)
struct usbmisc_ops {
/* It's called once when probe a usb device */
@@ -100,14 +169,28 @@ struct usbmisc_ops {
int (*post)(struct imx_usbmisc_data *data);
/* It's called when we need to enable/disable usb wakeup */
int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled);
+ /* usb charger detection */
+ int (*charger_detection)(struct imx_usbmisc_data *data);
+ /* It's called when system resume from usb power lost */
+ int (*power_lost_check)(struct imx_usbmisc_data *data);
+ /* It's called before setting portsc.suspendM */
+ int (*hsic_set_connect)(struct imx_usbmisc_data *data);
+ /* It's called during suspend/resume */
+ int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled);
+ /* override UTMI termination select */
+ int (*term_select_override)(struct imx_usbmisc_data *data,
+ bool enable, int val);
};
struct imx_usbmisc {
void __iomem *base;
spinlock_t lock;
const struct usbmisc_ops *ops;
+ struct mutex mutex;
};
+static struct regulator *vbus_wakeup_reg;
+
static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data);
static int usbmisc_imx25_init(struct imx_usbmisc_data *data)
@@ -297,15 +380,86 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
return 0;
}
+static int usbmisc_imx6_hsic_set_connect(struct imx_usbmisc_data *data)
+{
+ unsigned long flags;
+ u32 val, offset;
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ int ret = 0;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ if (data->index == 2 || data->index == 3) {
+ offset = (data->index - 2) * 4;
+ } else if (data->index == 0) {
+ /*
+ * For controllers later than imx7d (imx7d is included),
+ * each controller has its own non core register region.
+ * And the controllers before than imx7d, the 1st controller
+ * is not HSIC controller.
+ */
+ offset = 0;
+ } else {
+ dev_err(data->dev, "index is error for usbmisc\n");
+ offset = 0;
+ ret = -EINVAL;
+ }
+
+ val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset);
+ if (!(val & MX6_BM_HSIC_DEV_CONN))
+ writel(val | MX6_BM_HSIC_DEV_CONN,
+ usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return ret;
+}
+
+static int usbmisc_imx6_hsic_set_clk(struct imx_usbmisc_data *data, bool on)
+{
+ unsigned long flags;
+ u32 val, offset;
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ int ret = 0;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ if (data->index == 2 || data->index == 3) {
+ offset = (data->index - 2) * 4;
+ } else if (data->index == 0) {
+ offset = 0;
+ } else {
+ dev_err(data->dev, "index is error for usbmisc\n");
+ offset = 0;
+ ret = -EINVAL;
+ }
+
+ val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset);
+ val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON;
+ if (on)
+ val |= MX6_BM_HSIC_CLK_ON;
+ else
+ val &= ~MX6_BM_HSIC_CLK_ON;
+ writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return 0;
+}
+
+static u32 imx6q_finalize_wakeup_setting(struct imx_usbmisc_data *data)
+{
+ if (data->available_role == USB_DR_MODE_PERIPHERAL)
+ return MX6_BM_VBUS_WAKEUP;
+ else if (data->available_role == USB_DR_MODE_OTG)
+ return MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP;
+
+ return 0;
+}
+
static int usbmisc_imx6q_set_wakeup
(struct imx_usbmisc_data *data, bool enabled)
{
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
unsigned long flags;
- u32 val;
- u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE |
- MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP);
int ret = 0;
+ u32 val, wakeup_setting = MX6_BM_WAKEUP_ENABLE;
if (data->index > 3)
return -EINVAL;
@@ -313,15 +467,20 @@ static int usbmisc_imx6q_set_wakeup
spin_lock_irqsave(&usbmisc->lock, flags);
val = readl(usbmisc->base + data->index * 4);
if (enabled) {
- val |= wakeup_setting;
- writel(val, usbmisc->base + data->index * 4);
+ wakeup_setting |= imx6q_finalize_wakeup_setting(data);
+ writel(val | wakeup_setting, usbmisc->base + data->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ if (vbus_wakeup_reg)
+ ret = regulator_enable(vbus_wakeup_reg);
} else {
if (val & MX6_BM_WAKEUP_INTR)
pr_debug("wakeup int at ci_hdrc.%d\n", data->index);
- val &= ~wakeup_setting;
- writel(val, usbmisc->base + data->index * 4);
+ wakeup_setting |= MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP;
+ writel(val & ~wakeup_setting, usbmisc->base + data->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ if (vbus_wakeup_reg && regulator_is_enabled(vbus_wakeup_reg))
+ regulator_disable(vbus_wakeup_reg);
}
- spin_unlock_irqrestore(&usbmisc->lock, flags);
return ret;
}
@@ -330,7 +489,7 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
{
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
unsigned long flags;
- u32 reg;
+ u32 reg, val;
if (data->index > 3)
return -EINVAL;
@@ -353,6 +512,27 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
writel(reg | MX6_BM_NON_BURST_SETTING,
usbmisc->base + data->index * 4);
+ /* For HSIC controller */
+ if (data->index == 2 || data->index == 3) {
+ val = readl(usbmisc->base + data->index * 4);
+ writel(val | MX6_BM_UTMI_ON_CLOCK,
+ usbmisc->base + data->index * 4);
+ val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET
+ + (data->index - 2) * 4);
+ val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON;
+ writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET
+ + (data->index - 2) * 4);
+
+ /*
+ * Need to add delay to wait 24M OSC to be stable,
+ * It is board specific.
+ */
+ regmap_read(data->anatop, ANADIG_ANA_MISC0, &val);
+ /* 0 <= data->osc_clkgate_delay <= 7 */
+ if (data->osc_clkgate_delay > ANADIG_ANA_MISC0_CLK_DELAY(val))
+ regmap_write(data->anatop, ANADIG_ANA_MISC0_SET,
+ (data->osc_clkgate_delay) << 26);
+ }
spin_unlock_irqrestore(&usbmisc->lock, flags);
usbmisc_imx6q_set_wakeup(data, false);
@@ -369,9 +549,9 @@ static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data)
usbmisc_imx6q_init(data);
+ spin_lock_irqsave(&usbmisc->lock, flags);
if (data->index == 0 || data->index == 1) {
reg = usbmisc->base + MX6_USB_OTG1_PHY_CTRL + data->index * 4;
- spin_lock_irqsave(&usbmisc->lock, flags);
/* Set vbus wakeup source as bvalid */
val = readl(reg);
writel(val | MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID, reg);
@@ -382,9 +562,18 @@ static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data)
val = readl(usbmisc->base + data->index * 4);
writel(val & ~MX6SX_BM_DPDM_WAKEUP_EN,
usbmisc->base + data->index * 4);
- spin_unlock_irqrestore(&usbmisc->lock, flags);
}
+ /* For HSIC controller */
+ if (data->index == 2) {
+ val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET
+ + (data->index - 2) * 4);
+ val |= MX6SX_BM_HSIC_AUTO_RESUME;
+ writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET
+ + (data->index - 2) * 4);
+ }
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
return 0;
}
@@ -414,16 +603,17 @@ static int usbmisc_imx7d_set_wakeup
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
unsigned long flags;
u32 val;
- u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE |
- MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP);
+ u32 wakeup_setting = MX6_BM_WAKEUP_ENABLE;
spin_lock_irqsave(&usbmisc->lock, flags);
val = readl(usbmisc->base);
if (enabled) {
+ wakeup_setting |= imx6q_finalize_wakeup_setting(data);
writel(val | wakeup_setting, usbmisc->base);
} else {
if (val & MX6_BM_WAKEUP_INTR)
dev_dbg(data->dev, "wakeup int\n");
+ wakeup_setting |= MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP;
writel(val & ~wakeup_setting, usbmisc->base);
}
spin_unlock_irqrestore(&usbmisc->lock, flags);
@@ -448,12 +638,336 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data)
/* High active */
reg &= ~(MX6_BM_OVER_CUR_DIS | MX6_BM_OVER_CUR_POLARITY);
}
+
+ if (data->pwr_polarity)
+ reg |= MX6_BM_PRW_POLARITY;
+
writel(reg, usbmisc->base);
+ /* SoC non-burst setting */
+ reg = readl(usbmisc->base);
+ writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base);
+
+ if (!data->hsic) {
+ reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK;
+ writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID
+ | MX7D_USBNC_AUTO_RESUME,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ /* PHY tuning for signal quality */
+ reg = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG1);
+ if (data->emp_curr_control && data->emp_curr_control <=
+ (TXPREEMPAMPTUNE0_MASK >> TXPREEMPAMPTUNE0_BIT)) {
+ reg &= ~TXPREEMPAMPTUNE0_MASK;
+ reg |= (data->emp_curr_control << TXPREEMPAMPTUNE0_BIT);
+ }
+
+ if (data->dc_vol_level_adjust && data->dc_vol_level_adjust <=
+ (TXVREFTUNE0_MASK >> TXVREFTUNE0_BIT)) {
+ reg &= ~TXVREFTUNE0_MASK;
+ reg |= (data->dc_vol_level_adjust << TXVREFTUNE0_BIT);
+ }
+
+ writel(reg, usbmisc->base + MX7D_USB_OTG_PHY_CFG1);
+ }
+
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ usbmisc_imx7d_set_wakeup(data, false);
+
+ return 0;
+}
+
+static int usbmisc_imx7d_power_lost_check(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ /*
+ * Here use a power on reset value to judge
+ * if the controller experienced a power lost
+ */
+ if (val == 0x30001000)
+ return 1;
+ else
+ return 0;
+}
+
+
+static int usbmisc_imx6sx_power_lost_check(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base + data->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ /*
+ * Here use a power on reset value to judge
+ * if the controller experienced a power lost
+ */
+ if (val == 0x30001000)
+ return 1;
+ else
+ return 0;
+}
+
+static int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ struct usb_phy *usb_phy = data->usb_phy;
+ int val, bak_val;
+
+ /* Pull up DP */
+ val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ bak_val = val;
+ val &= ~MX7D_USBNC_USB_CTRL2_DP_DM_MASK;
+ val |= MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_EN |
+ MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_EN;
+ val |= MX7D_USBNC_USB_CTRL2_TERMSEL_OVERRIDE_EN |
+ MX7D_USBNC_USB_CTRL2_TERMSEL_OVERRIDE_VAL;
+ writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ msleep(80);
+
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
+ if (val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE1) {
+ dev_dbg(data->dev, "It is a dedicate charging port\n");
+ usb_phy->chg_type = DCP_TYPE;
+ } else {
+ dev_dbg(data->dev, "It is a charging downstream port\n");
+ usb_phy->chg_type = CDP_TYPE;
+ }
+ writel(bak_val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ return 0;
+}
+
+static void imx7_disable_charger_detector(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ val &= ~(MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB |
+ MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 |
+ MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 |
+ MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL);
+ writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+
+ /* Set OPMODE to be 2'b00 and disable its override */
+ val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK;
+ writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ writel(val & ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+}
+
+static int imx7d_charger_data_contact_detect(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+ int i, data_pin_contact_count = 0;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+
+ /* check if vbus is valid */
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
+ if (!(val & MX7D_USB_OTG_PHY_STATUS_VBUS_VLD)) {
+ dev_err(data->dev, "vbus is error\n");
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ return -EINVAL;
+ }
+
+ /*
+ * - Do not check whether a charger is connected to the USB port
+ * - Check whether the USB plug has been in contact with each other
+ */
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB,
+ usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ /* Check if plug is connected */
+ for (i = 0; i < 100; i = i + 1) {
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
+ if (!(val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE0)) {
+ if (data_pin_contact_count++ > 5)
+ /* Data pin makes contact */
+ break;
+ else
+ usleep_range(5000, 10000);
+ } else {
+ data_pin_contact_count = 0;
+ usleep_range(5000, 6000);
+ }
+ }
+
+ if (i == 100) {
+ dev_err(data->dev,
+ "VBUS is coming from a dedicated power supply.\n");
+ imx7_disable_charger_detector(data);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int imx7d_charger_primary_detection(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ struct usb_phy *usb_phy = data->usb_phy;
+ unsigned long flags;
+ u32 val;
+ int ret;
+
+ ret = imx7d_charger_data_contact_detect(data);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ /* Set OPMODE to be non-driving mode */
+ val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK;
+ val |= MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING;
+ writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ writel(val | MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ /*
+ * - Do check whether a charger is connected to the USB port
+ * - Do not Check whether the USB plug has been in contact with
+ * each other
+ */
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 |
+ MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0,
+ usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ usleep_range(1000, 2000);
+
+ /* Check if it is a charger */
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
+ if (!(val & MX7D_USB_OTG_PHY_STATUS_CHRGDET)) {
+ dev_dbg(data->dev, "It is a stardard downstream port\n");
+ usb_phy->chg_type = SDP_TYPE;
+ }
+
+ imx7_disable_charger_detector(data);
+
+ return 0;
+}
+
+static int imx7d_charger_detection(struct imx_usbmisc_data *data)
+{
+ struct usb_phy *usb_phy = data->usb_phy;
+ int ret;
+
+ ret = imx7d_charger_primary_detection(data);
+ if (ret)
+ return ret;
+
+ if (usb_phy->chg_type != SDP_TYPE)
+ ret = imx7d_charger_secondary_detection(data);
+
+ return ret;
+}
+
+static int usbmisc_term_select_override(struct imx_usbmisc_data *data,
+ bool enable, int val)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+
reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
- reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK;
- writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID,
- usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ if (enable) {
+ if (val)
+ writel(reg | MX7D_USB_TERMSEL_OVERRIDE,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ else
+ writel(reg & ~MX7D_USB_TERMSEL_OVERRIDE,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+
+ reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ writel(reg | MX7D_USB_TERMSEL_OVERRIDE_EN,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ } else {
+ writel(reg & ~MX7D_USB_TERMSEL_OVERRIDE_EN,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ }
+
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return 0;
+}
+
+static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 reg;
+
+ if (data->index >= 1)
+ return -EINVAL;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ reg = readl(usbmisc->base);
+ if (data->disable_oc) {
+ reg |= MX6_BM_OVER_CUR_DIS;
+ } else if (data->oc_polarity == 1) {
+ /* High active */
+ reg &= ~(MX6_BM_OVER_CUR_DIS | MX6_BM_OVER_CUR_POLARITY);
+ }
+
+ if (data->pwr_polarity)
+ reg |= MX6_BM_PRW_POLARITY;
+
+ writel(reg, usbmisc->base);
+
+ /* SoC non-burst setting */
+ reg = readl(usbmisc->base);
+ writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base);
+
+ if (data->hsic) {
+ reg = readl(usbmisc->base);
+ writel(reg | MX6_BM_UTMI_ON_CLOCK, usbmisc->base);
+
+ reg = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET);
+ reg |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON;
+ writel(reg, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET);
+
+ /*
+ * For non-HSIC controller, the autoresume is enabled
+ * at MXS PHY driver (usbphy_ctrl bit18).
+ */
+ reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ writel(reg | MX7D_USBNC_AUTO_RESUME,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ } else {
+ reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK;
+ writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ }
+
spin_unlock_irqrestore(&usbmisc->lock, flags);
usbmisc_imx7d_set_wakeup(data, false);
@@ -481,6 +995,8 @@ static const struct usbmisc_ops imx53_usbmisc_ops = {
static const struct usbmisc_ops imx6q_usbmisc_ops = {
.set_wakeup = usbmisc_imx6q_set_wakeup,
.init = usbmisc_imx6q_init,
+ .hsic_set_connect = usbmisc_imx6_hsic_set_connect,
+ .hsic_set_clk = usbmisc_imx6_hsic_set_clk,
};
static const struct usbmisc_ops vf610_usbmisc_ops = {
@@ -490,11 +1006,25 @@ static const struct usbmisc_ops vf610_usbmisc_ops = {
static const struct usbmisc_ops imx6sx_usbmisc_ops = {
.set_wakeup = usbmisc_imx6q_set_wakeup,
.init = usbmisc_imx6sx_init,
+ .power_lost_check = usbmisc_imx6sx_power_lost_check,
+ .hsic_set_connect = usbmisc_imx6_hsic_set_connect,
+ .hsic_set_clk = usbmisc_imx6_hsic_set_clk,
};
static const struct usbmisc_ops imx7d_usbmisc_ops = {
.init = usbmisc_imx7d_init,
.set_wakeup = usbmisc_imx7d_set_wakeup,
+ .power_lost_check = usbmisc_imx7d_power_lost_check,
+ .charger_detection = imx7d_charger_detection,
+ .term_select_override = usbmisc_term_select_override,
+};
+
+static const struct usbmisc_ops imx7ulp_usbmisc_ops = {
+ .init = usbmisc_imx7ulp_init,
+ .set_wakeup = usbmisc_imx7d_set_wakeup,
+ .power_lost_check = usbmisc_imx7d_power_lost_check,
+ .hsic_set_connect = usbmisc_imx6_hsic_set_connect,
+ .hsic_set_clk = usbmisc_imx6_hsic_set_clk,
};
static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data)
@@ -546,6 +1076,97 @@ int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled)
}
EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup);
+int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect)
+{
+ struct imx_usbmisc *usbmisc;
+ struct usb_phy *usb_phy;
+ int ret = 0;
+
+ if (!data)
+ return -EINVAL;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ usb_phy = data->usb_phy;
+ if (!usbmisc->ops->charger_detection)
+ return -ENOTSUPP;
+
+ mutex_lock(&usbmisc->mutex);
+ if (connect) {
+ ret = usbmisc->ops->charger_detection(data);
+ if (ret) {
+ dev_err(data->dev,
+ "Error occurs during detection: %d\n",
+ ret);
+ usb_phy->chg_state = USB_CHARGER_ABSENT;
+ } else {
+ usb_phy->chg_state = USB_CHARGER_PRESENT;
+ }
+ } else {
+ usb_phy->chg_state = USB_CHARGER_ABSENT;
+ usb_phy->chg_type = UNKNOWN_TYPE;
+ }
+ mutex_unlock(&usbmisc->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection);
+
+int imx_usbmisc_power_lost_check(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->power_lost_check)
+ return 0;
+ return usbmisc->ops->power_lost_check(data);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_power_lost_check);
+
+int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->hsic_set_connect || !data->hsic)
+ return 0;
+ return usbmisc->ops->hsic_set_connect(data);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect);
+
+int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->hsic_set_clk || !data->hsic)
+ return 0;
+ return usbmisc->ops->hsic_set_clk(data, on);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk);
+
+int imx_usbmisc_term_select_override(struct imx_usbmisc_data *data,
+ bool enable, int val)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->term_select_override)
+ return 0;
+ return usbmisc->ops->term_select_override(data, enable, val);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_term_select_override);
+
static const struct of_device_id usbmisc_imx_dt_ids[] = {
{
.compatible = "fsl,imx25-usbmisc",
@@ -587,6 +1208,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
.compatible = "fsl,imx7d-usbmisc",
.data = &imx7d_usbmisc_ops,
},
+ {
+ .compatible = "fsl,imx7ulp-usbmisc",
+ .data = &imx7ulp_usbmisc_ops,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids);
@@ -606,6 +1231,7 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
return -ENOMEM;
spin_lock_init(&data->lock);
+ mutex_init(&data->mutex);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->base = devm_ioremap_resource(&pdev->dev, res);
@@ -615,6 +1241,18 @@ static int usbmisc_imx_probe(struct platform_device *pdev)
data->ops = (const struct usbmisc_ops *)of_id->data;
platform_set_drvdata(pdev, data);
+ vbus_wakeup_reg = devm_regulator_get(&pdev->dev, "vbus-wakeup");
+ if (PTR_ERR(vbus_wakeup_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ else if (PTR_ERR(vbus_wakeup_reg) == -ENODEV)
+ /* no vbus regualator is needed */
+ vbus_wakeup_reg = NULL;
+ else if (IS_ERR(vbus_wakeup_reg)) {
+ dev_err(&pdev->dev, "Getting regulator error: %ld\n",
+ PTR_ERR(vbus_wakeup_reg));
+ return PTR_ERR(vbus_wakeup_reg);
+ }
+
return 0;
}
diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c
index b8fe31e409a5..1476c10c5a7c 100644
--- a/drivers/usb/common/usb-otg-fsm.c
+++ b/drivers/usb/common/usb-otg-fsm.c
@@ -75,6 +75,7 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
switch (old_state) {
case OTG_STATE_B_IDLE:
otg_del_timer(fsm, B_SE0_SRP);
+ otg_del_timer(fsm, B_SRP_FAIL);
fsm->b_se0_srp = 0;
fsm->adp_sns = 0;
fsm->adp_prb = 0;
@@ -92,6 +93,11 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
fsm->b_ase0_brst_tmout = 0;
break;
case OTG_STATE_B_HOST:
+ if (fsm->otg_hnp_reqd) {
+ fsm->otg_hnp_reqd = 0;
+ fsm->b_bus_req = 0;
+ }
+ fsm->a_conn = 0;
break;
case OTG_STATE_A_IDLE:
fsm->adp_prb = 0;
@@ -138,8 +144,10 @@ static void otg_hnp_polling_work(struct work_struct *work)
enum usb_otg_state state = fsm->otg->state;
u8 flag;
int retval;
+ struct usb_otg_descriptor *desc = NULL;
- if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST)
+ if ((state != OTG_STATE_A_HOST || !fsm->b_hnp_enable) &&
+ state != OTG_STATE_B_HOST)
return;
udev = usb_hub_find_child(fsm->otg->host->root_hub, 1);
@@ -149,6 +157,31 @@ static void otg_hnp_polling_work(struct work_struct *work)
return;
}
+ if (udev->state != USB_STATE_CONFIGURED) {
+ dev_dbg(&udev->dev, "the B dev is not resumed!\n");
+ schedule_delayed_work(&fsm->hnp_polling_work,
+ msecs_to_jiffies(T_HOST_REQ_POLL));
+ return;
+ }
+
+ /*
+ * Legacy otg test device does not support HNP polling,
+ * start HNP directly for legacy otg test device.
+ */
+ if (fsm->tst_maint &&
+ (__usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc) == 0)) {
+ /* shorter bLength of OTG 1.3 or earlier */
+ if (desc->bLength < 5) {
+ fsm->a_bus_req = 0;
+ fsm->tst_maint = 0;
+ otg_del_timer(fsm, A_TST_MAINT);
+ *fsm->host_req_flag = HOST_REQUEST_FLAG;
+ return;
+ }
+ }
+
*fsm->host_req_flag = 0;
/* Get host request flag from connected USB device */
retval = usb_control_msg(udev,
@@ -190,6 +223,11 @@ static void otg_hnp_polling_work(struct work_struct *work)
fsm->otg->host->b_hnp_enable = 1;
}
fsm->a_bus_req = 0;
+ if (fsm->tst_maint) {
+ fsm->tst_maint = 0;
+ fsm->otg_vbus_off = 0;
+ otg_del_timer(fsm, A_TST_MAINT);
+ }
} else if (state == OTG_STATE_B_HOST) {
fsm->b_bus_req = 0;
}
@@ -231,6 +269,10 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_start_adp_sns(fsm);
otg_set_protocol(fsm, PROTO_UNDEF);
otg_add_timer(fsm, B_SE0_SRP);
+ if (fsm->otg_hnp_reqd) {
+ fsm->otg_hnp_reqd = 0;
+ fsm->b_bus_req = 0;
+ }
break;
case OTG_STATE_B_SRP_INIT:
otg_start_pulse(fsm);
@@ -243,6 +285,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_GADGET);
otg_loc_conn(fsm, 1);
+ fsm->b_bus_req = 0;
break;
case OTG_STATE_B_WAIT_ACON:
otg_chrg_vbus(fsm, 0);
@@ -257,8 +300,6 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 1);
otg_set_protocol(fsm, PROTO_HOST);
- usb_bus_start_enum(fsm->otg->host,
- fsm->otg->host->otg_port);
otg_start_hnp_polling(fsm);
break;
case OTG_STATE_A_IDLE:
@@ -413,8 +454,7 @@ int otg_statemachine(struct otg_fsm *fsm)
case OTG_STATE_A_HOST:
if (fsm->id || fsm->a_bus_drop)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
- else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) &&
- fsm->otg->host->b_hnp_enable)
+ else if (!fsm->a_bus_req || fsm->a_suspend_req_inf)
otg_set_state(fsm, OTG_STATE_A_SUSPEND);
else if (!fsm->b_conn)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
@@ -422,9 +462,9 @@ int otg_statemachine(struct otg_fsm *fsm)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
break;
case OTG_STATE_A_SUSPEND:
- if (!fsm->b_conn && fsm->otg->host->b_hnp_enable)
+ if (!fsm->b_conn && fsm->a_set_b_hnp_en)
otg_set_state(fsm, OTG_STATE_A_PERIPHERAL);
- else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable)
+ else if (!fsm->b_conn && !fsm->a_set_b_hnp_en)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
else if (fsm->a_bus_req || fsm->b_bus_resume)
otg_set_state(fsm, OTG_STATE_A_HOST);
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 5fcea1114e2f..745698feef07 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2243,6 +2243,140 @@ int usb_hcd_get_frame_number (struct usb_device *udev)
}
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_USB_HCD_TEST_MODE
+
+static void usb_ehset_completion(struct urb *urb)
+{
+ struct completion *done = urb->context;
+
+ complete(done);
+}
+/*
+ * Allocate and initialize a control URB. This request will be used by the
+ * EHSET SINGLE_STEP_SET_FEATURE test in which the DATA and STATUS stages
+ * of the GetDescriptor request are sent 15 seconds after the SETUP stage.
+ * Return NULL if failed.
+ */
+static struct urb *request_single_step_set_feature_urb(
+ struct usb_device *udev,
+ void *dr,
+ void *buf,
+ struct completion *done
+) {
+ struct urb *urb;
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ struct usb_host_endpoint *ep;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return NULL;
+
+ urb->pipe = usb_rcvctrlpipe(udev, 0);
+ ep = (usb_pipein(urb->pipe) ? udev->ep_in : udev->ep_out)
+ [usb_pipeendpoint(urb->pipe)];
+ if (!ep) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+
+ urb->ep = ep;
+ urb->dev = udev;
+ urb->setup_packet = (void *)dr;
+ urb->transfer_buffer = buf;
+ urb->transfer_buffer_length = USB_DT_DEVICE_SIZE;
+ urb->complete = usb_ehset_completion;
+ urb->status = -EINPROGRESS;
+ urb->actual_length = 0;
+ urb->transfer_flags = URB_DIR_IN;
+ usb_get_urb(urb);
+ atomic_inc(&urb->use_count);
+ atomic_inc(&urb->dev->urbnum);
+ urb->setup_dma = dma_map_single(
+ hcd->self.sysdev,
+ urb->setup_packet,
+ sizeof(struct usb_ctrlrequest),
+ DMA_TO_DEVICE);
+ urb->transfer_dma = dma_map_single(
+ hcd->self.sysdev,
+ urb->transfer_buffer,
+ urb->transfer_buffer_length,
+ DMA_FROM_DEVICE);
+ urb->context = done;
+ return urb;
+}
+
+int ehset_single_step_set_feature(struct usb_hcd *hcd, int port)
+{
+ int retval = -ENOMEM;
+ struct usb_ctrlrequest *dr;
+ struct urb *urb;
+ struct usb_device *udev;
+ struct usb_device_descriptor *buf;
+ DECLARE_COMPLETION_ONSTACK(done);
+
+ /* Obtain udev of the rhub's child port */
+ udev = usb_hub_find_child(hcd->self.root_hub, port);
+ if (!udev) {
+ dev_err(hcd->self.controller, "No device attached to the RootHub\n");
+ return -ENODEV;
+ }
+ buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+ if (!dr) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* Fill Setup packet for GetDescriptor */
+ dr->bRequestType = USB_DIR_IN;
+ dr->bRequest = USB_REQ_GET_DESCRIPTOR;
+ dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8);
+ dr->wIndex = 0;
+ dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE);
+ urb = request_single_step_set_feature_urb(udev, dr, buf, &done);
+ if (!urb)
+ goto cleanup;
+
+ /* Submit just the SETUP stage */
+ retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 1);
+ if (retval)
+ goto out1;
+ if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) {
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ dev_err(hcd->self.controller,
+ "%s SETUP stage timed out on ep0\n", __func__);
+ goto out1;
+ }
+ msleep(15 * 1000);
+
+ /* Complete remaining DATA and STATUS stages using the same URB */
+ urb->status = -EINPROGRESS;
+ usb_get_urb(urb);
+ atomic_inc(&urb->use_count);
+ atomic_inc(&urb->dev->urbnum);
+ retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 0);
+ if (!retval && !wait_for_completion_timeout(&done,
+ msecs_to_jiffies(2000))) {
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ dev_err(hcd->self.controller,
+ "%s IN stage timed out on ep0\n", __func__);
+ }
+out1:
+ usb_free_urb(urb);
+cleanup:
+ kfree(dr);
+ kfree(buf);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(ehset_single_step_set_feature);
+#endif /* CONFIG_USB_HCD_TEST_MODE */
+
+/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PM
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 4efccf8bf99f..d80ba41185a0 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -23,6 +23,7 @@
#include <linux/usbdevice_fs.h>
#include <linux/usb/hcd.h>
#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
#include <linux/usb/quirks.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
@@ -1132,6 +1133,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
need_debounce_delay = true;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
+#ifdef CONFIG_USB_OTG
+ if (hdev->bus->is_b_host)
+ usb_bus_start_enum(hdev->bus, port1);
+#endif
}
if (portchange & USB_PORT_STAT_C_ENABLE) {
need_debounce_delay = true;
@@ -2230,9 +2235,9 @@ static inline void announce_device(struct usb_device *udev) { }
*/
static int usb_enumerate_device_otg(struct usb_device *udev)
{
+#ifdef CONFIG_USB_OTG
int err = 0;
-#ifdef CONFIG_USB_OTG
/*
* OTG-aware devices on OTG-capable root hubs may be able to use SRP,
* to wake us after we've powered off VBUS; and HNP, switching roles
@@ -2273,6 +2278,12 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
err);
bus->b_hnp_enable = 0;
}
+
+ if (bus->otg_fsm) {
+ bus->otg_fsm->b_hnp_enable = 1;
+ if (bus->b_hnp_enable)
+ bus->otg_fsm->a_set_b_hnp_en = 1;
+ }
} else if (desc->bLength == sizeof
(struct usb_otg_descriptor)) {
/* Set a_alt_hnp_support for legacy otg device */
@@ -2289,7 +2300,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
}
}
#endif
- return err;
+ return 0;
}
@@ -2311,6 +2322,7 @@ static int usb_enumerate_device(struct usb_device *udev)
{
int err;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ struct otg_fsm *fsm = udev->bus->otg_fsm;
if (udev->config == NULL) {
err = usb_get_configuration(udev);
@@ -2342,8 +2354,10 @@ static int usb_enumerate_device(struct usb_device *udev)
err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND);
if (err < 0)
dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ return -ENOTSUPP;
+ } else if (!fsm || !fsm->b_hnp_enable || !fsm->hnp_polling) {
+ return -ENOTSUPP;
}
- return -ENOTSUPP;
}
usb_detect_interface_quirks(udev);
@@ -4570,7 +4584,8 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
}
if (r) {
if (r != -ENODEV)
- dev_err(&udev->dev, "device descriptor read/64, error %d\n",
+ dev_err(&udev->dev,
+ "device no response, device descriptor read/64, error %d\n",
r);
retval = -EMSGSIZE;
continue;
diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h
index 085049d37d7a..68b4f7e80b47 100644
--- a/drivers/usb/core/otg_whitelist.h
+++ b/drivers/usb/core/otg_whitelist.h
@@ -17,35 +17,64 @@
*/
static struct usb_device_id whitelist_table[] = {
-
-/* hubs are optional in OTG, but very handy ... */
-{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), },
-{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), },
-
-#ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */
-/* FIXME actually, printers are NOT supposed to use device classes;
- * they're supposed to use interface classes...
- */
-{ USB_DEVICE_INFO(7, 1, 1) },
-{ USB_DEVICE_INFO(7, 1, 2) },
-{ USB_DEVICE_INFO(7, 1, 3) },
+/* Add FSL i.mx whitelist, the default list is for USB Compliance Test */
+#if defined(CONFIG_USB_EHSET_TEST_FIXTURE) \
+ || defined(CONFIG_USB_EHSET_TEST_FIXTURE_MODULE)
+#define TEST_SE0_NAK_PID 0x0101
+#define TEST_J_PID 0x0102
+#define TEST_K_PID 0x0103
+#define TEST_PACKET_PID 0x0104
+#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106
+#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107
+#define TEST_SINGLE_STEP_SET_FEATURE 0x0108
+#define TEST_OTG_TEST_DEVICE_SUPPORT 0x0200
+{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
+{ USB_DEVICE(0x1a0a, TEST_J_PID) },
+{ USB_DEVICE(0x1a0a, TEST_K_PID) },
+{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
+{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
+{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
+{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
+{ USB_DEVICE(0x1a0a, TEST_OTG_TEST_DEVICE_SUPPORT) },
#endif
-#ifdef CONFIG_USB_NET_CDCETHER
-/* Linux-USB CDC Ethernet gadget */
-{ USB_DEVICE(0x0525, 0xa4a1), },
-/* Linux-USB CDC Ethernet + RNDIS gadget */
-{ USB_DEVICE(0x0525, 0xa4a2), },
-#endif
+#define USB_INTERFACE_CLASS_INFO(cl) \
+ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, \
+ .bInterfaceClass = (cl)
-#if IS_ENABLED(CONFIG_USB_TEST)
-/* gadget zero, for testing */
-{ USB_DEVICE(0x0525, 0xa4a0), },
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_HUB) },
+#if defined(CONFIG_USB_STORAGE) || defined(CONFIG_USB_STORAGE_MODULE)
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_MASS_STORAGE) },
+#endif
+#if defined(CONFIG_USB_HID) || defined(CONFIG_USB_HID_MODULE)
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_HID) },
#endif
{ } /* Terminating entry */
};
+static bool match_int_class(struct usb_device_id *id, struct usb_device *udev)
+{
+ struct usb_host_config *c;
+ int num_configs, i;
+
+ /* Copy the code from generic.c */
+ c = udev->config;
+ num_configs = udev->descriptor.bNumConfigurations;
+ for (i = 0; i < num_configs; (i++, c++)) {
+ struct usb_interface_descriptor *desc = NULL;
+
+ /* It's possible that a config has no interfaces! */
+ if (c->desc.bNumInterfaces > 0)
+ desc = &c->intf_cache[0]->altsetting->desc;
+
+ if (desc && (desc->bInterfaceClass == id->bInterfaceClass))
+ return true;
+ }
+
+ return false;
+}
+
static int is_targeted(struct usb_device *dev)
{
struct usb_device_id *id = whitelist_table;
@@ -60,6 +89,19 @@ static int is_targeted(struct usb_device *dev)
le16_to_cpu(dev->descriptor.idProduct) == 0x0200))
return 1;
+ /* Unknown Device Not Supporting HNP */
+ if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x0201)) {
+ dev_warn(&dev->dev, "Unsupported Device\n");
+ return 0;
+ }
+ /* Unknown Device Supporting HNP */
+ if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x0202)) {
+ dev_warn(&dev->dev, "Device no Responding\n");
+ return 0;
+ }
+
/* NOTE: can't use usb_match_id() since interface caches
* aren't set up yet. this is cut/paste from that code.
*/
@@ -94,6 +136,10 @@ static int is_targeted(struct usb_device *dev)
(id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
continue;
+ if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
+ (!match_int_class(id, dev)))
+ continue;
+
return 1;
}
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index a497b878c3e2..06839e02ebff 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -192,6 +192,30 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
queue_work(system_freezable_wq, &dwc->drd_work);
}
+static int dwc3_set_suspend_clk(struct dwc3 *dwc)
+{
+ u32 reg, scale;
+
+ /*
+ * DWC3_GCTL.PWRDNSCALE: The USB3 suspend_clk input replaces
+ * pipe3_rx_pclk as a clock source to a small part of the USB3
+ * core that operates when the SS PHY is in its lowest power
+ * (P3) state, and therefore does not provide a clock.
+ * The Power Down Scale field specifies how many suspend_clk
+ * periods fit into a 16 kHz clock period. When performing the
+ * division, round up the remainder.
+ */
+ if (!device_property_read_u32(dwc->dev, "snps,power-down-scale",
+ &scale)) {
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK);
+ reg |= DWC3_GCTL_PWRDNSCALE(scale);
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+ }
+
+ return 0;
+}
+
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
{
struct dwc3 *dwc = dep->dwc;
@@ -806,6 +830,9 @@ static int dwc3_core_init(struct dwc3 *dwc)
dwc->ulpi_ready = true;
}
+ /* Set suspend_clk */
+ dwc3_set_suspend_clk(dwc);
+
if (!dwc->phys_ready) {
ret = dwc3_core_get_phy(dwc);
if (ret)
@@ -969,6 +996,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
+ dwc->current_dr_role = DWC3_GCTL_PRTCAP_DEVICE;
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
if (dwc->usb2_phy)
@@ -984,6 +1012,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_HOST:
+ dwc->current_dr_role = DWC3_GCTL_PRTCAP_HOST;
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
if (dwc->usb2_phy)
@@ -1229,6 +1258,19 @@ static int dwc3_probe(struct platform_device *pdev)
dwc3_get_properties(dwc);
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ dwc->otg_caps.otg_rev = 0x0300;
+ dwc->otg_caps.hnp_support = true;
+ dwc->otg_caps.srp_support = true;
+ dwc->otg_caps.adp_support = true;
+
+ /* Update otg capabilities by DT properties */
+ ret = of_usb_update_otg_caps(dev->of_node,
+ &dwc->otg_caps);
+ if (ret)
+ goto err0;
+ }
+
platform_set_drvdata(pdev, dwc);
dwc3_cache_hwparams(dwc);
@@ -1339,21 +1381,19 @@ static int dwc3_suspend_common(struct dwc3 *dwc)
{
unsigned long flags;
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_DEVICE:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_suspend(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
+ dwc3_core_exit(dwc);
break;
- case USB_DR_MODE_HOST:
+ case DWC3_GCTL_PRTCAP_HOST:
default:
/* do nothing */
break;
}
- dwc3_core_exit(dwc);
-
return 0;
}
@@ -1362,18 +1402,17 @@ static int dwc3_resume_common(struct dwc3 *dwc)
unsigned long flags;
int ret;
- ret = dwc3_core_init(dwc);
- if (ret)
- return ret;
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ ret = dwc3_core_init(dwc);
+ if (ret)
+ return ret;
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_resume(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
- /* FALLTHROUGH */
- case USB_DR_MODE_HOST:
+ break;
+ case DWC3_GCTL_PRTCAP_HOST:
default:
/* do nothing */
break;
@@ -1384,7 +1423,7 @@ static int dwc3_resume_common(struct dwc3 *dwc)
static int dwc3_runtime_checks(struct dwc3 *dwc)
{
- switch (dwc->dr_mode) {
+ switch (dwc->current_dr_role) {
case USB_DR_MODE_PERIPHERAL:
case USB_DR_MODE_OTG:
if (dwc->connected)
@@ -1427,12 +1466,11 @@ static int dwc3_runtime_resume(struct device *dev)
if (ret)
return ret;
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_DEVICE:
dwc3_gadget_process_pending_events(dwc);
break;
- case USB_DR_MODE_HOST:
+ case DWC3_GCTL_PRTCAP_HOST:
default:
/* do nothing */
break;
@@ -1448,13 +1486,12 @@ static int dwc3_runtime_idle(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_DEVICE:
if (dwc3_runtime_checks(dwc))
return -EBUSY;
break;
- case USB_DR_MODE_HOST:
+ case DWC3_GCTL_PRTCAP_HOST:
default:
/* do nothing */
break;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 40bf0e0768d9..354bddf5ed44 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -183,6 +183,7 @@
/* Global Configuration Register */
#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19)
+#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff)
#define DWC3_GCTL_U2RSTECN BIT(16)
#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6)
#define DWC3_GCTL_CLK_BUS (0)
@@ -872,6 +873,7 @@ struct dwc3_scratchpad_array {
* @dis_metastability_quirk: set to disable metastability quirk.
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
+ * @otg_caps: the OTG capabilities from hardware point
*/
struct dwc3 {
struct work_struct drd_work;
@@ -1029,6 +1031,8 @@ struct dwc3 {
unsigned dis_metastability_quirk:1;
u16 imod_interval;
+
+ struct usb_otg_caps otg_caps;
};
#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work))
diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c
index acf41ba3638d..216ae3886186 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -181,6 +181,7 @@ static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "xlnx,zynqmp-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ .compatible = "sprd,sc9860-dwc3" },
+ { .compatible = "fsl, imx8mq-dwc3" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index e96b22d6fa52..707646b95d25 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -26,6 +26,7 @@
#include <linux/io.h>
#include <linux/list.h>
#include <linux/dma-mapping.h>
+#include <linux/busfreq-imx.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -276,7 +277,11 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
{
const struct usb_endpoint_descriptor *desc = dep->endpoint.desc;
struct dwc3 *dwc = dep->dwc;
- u32 timeout = 1000;
+ /*
+ * FIXME need check why 500 times check
+ * is not enough.
+ */
+ u32 timeout = 20000;
u32 saved_config = 0;
u32 reg;
@@ -2675,14 +2680,20 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
dwc->setup_packet_pending = false;
usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);
- dwc->connected = false;
+ if (dwc->connected) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ dwc->connected = false;
+ }
}
static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
{
u32 reg;
- dwc->connected = true;
+ if (!dwc->connected) {
+ request_bus_freq(BUS_FREQ_HIGH);
+ dwc->connected = true;
+ }
/*
* WORKAROUND: DWC3 revisions <1.88a have an issue which
@@ -3257,7 +3268,10 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc->gadget.speed = USB_SPEED_UNKNOWN;
dwc->gadget.sg_supported = true;
dwc->gadget.name = "dwc3-gadget";
- dwc->gadget.is_otg = dwc->dr_mode == USB_DR_MODE_OTG;
+ dwc->gadget.is_otg = (dwc->dr_mode == USB_DR_MODE_OTG) &&
+ (dwc->otg_caps.hnp_support ||
+ dwc->otg_caps.srp_support ||
+ dwc->otg_caps.adp_support);
/*
* FIXME We might be setting max_speed to <SUPER, however versions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 31cce7805eb2..53114b126f42 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -343,6 +343,12 @@ config USB_CONFIGFS_MASS_STORAGE
device (in much the same way as the "loop" device driver),
specified as a module parameter or sysfs option.
+config FSL_UTP
+ bool "UTP over Storage Gadget"
+ depends on USB_CONFIGFS_MASS_STORAGE
+ help
+ Freescale's extension to MSC protocol
+
config USB_CONFIGFS_F_LB_SS
bool "Loopback and sourcesink function (for testing)"
depends on USB_CONFIGFS
diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c
index 41b5baa1f43b..5ec48dbaff6b 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -333,8 +333,15 @@ struct fsg_dev {
struct usb_ep *bulk_in;
struct usb_ep *bulk_out;
+#ifdef CONFIG_FSL_UTP
+ void *utp;
+#endif
};
+#ifdef CONFIG_FSL_UTP
+#include "fsl_updater.h"
+#endif
+
static inline int __fsg_is_set(struct fsg_common *common,
const char *func, unsigned line)
{
@@ -1118,6 +1125,15 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
}
#endif
+#ifdef CONFIG_FSL_UTP
+ if (is_utp_device(common->fsg) &&
+ utp_get_sense(common->fsg) == 0) {
+ /* got the sense from the UTP */
+ sd = UTP_CTX(common->fsg)->sd;
+ sdinfo = UTP_CTX(common->fsg)->sdinfo;
+ valid = 0;
+ } else
+#endif
if (!curlun) { /* Unsupported LUNs are okay */
common->bad_lun_okay = 1;
sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
@@ -1139,6 +1155,10 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
buf[7] = 18 - 8; /* Additional sense length */
buf[12] = ASC(sd);
buf[13] = ASCQ(sd);
+#ifdef CONFIG_FSL_UTP
+ if (is_utp_device(common->fsg))
+ put_unaligned_be32(UTP_CTX(common->fsg)->sdinfo_h, &buf[8]);
+#endif
return 18;
}
@@ -1625,7 +1645,20 @@ static void send_status(struct fsg_common *common)
sd = SS_INVALID_COMMAND;
} else if (sd != SS_NO_SENSE) {
DBG(common, "sending command-failure status\n");
- status = US_BULK_STAT_FAIL;
+#ifdef CONFIG_FSL_UTP
+ /*
+ * mfgtool host frequently reset bus during transfer
+ * - the response in csw to request sense will be 1
+ * due to UTP change some storage information
+ * - host will reset the bus if response to request sense is 1
+ * - change the response to 0 if CONFIG_FSL_UTP is defined
+ */
+ if (is_utp_device(common->fsg))
+ status = US_BULK_STAT_OK;
+ else
+#endif
+ status = US_BULK_STAT_FAIL;
+
VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;"
" info x%x\n",
SK(sd), ASC(sd), ASCQ(sd), sdinfo);
@@ -1815,6 +1848,14 @@ static int do_scsi_command(struct fsg_common *common)
common->phase_error = 0;
common->short_packet_received = 0;
+#ifdef CONFIG_FSL_UTP
+ if (is_utp_device(common->fsg))
+ reply = utp_handle_message(common->fsg, common->cmnd, reply);
+
+ if (reply != -EINVAL)
+ return reply;
+#endif
+
down_read(&common->filesem); /* We're using the backing file */
switch (common->cmnd[0]) {
@@ -2461,6 +2502,18 @@ static int fsg_main_thread(void *common_)
/* Allow the thread to be frozen */
set_freezable();
+ /*
+ * Arrange for userspace references to be interpreted as kernel
+ * pointers. That way we can pass a kernel pointer to a routine
+ * that expects a __user pointer and it will work okay.
+ */
+#ifdef CONFIG_FSL_UTP
+ if (!is_utp_device(common->fsg))
+ set_fs(get_ds());
+#else
+ set_fs(get_ds());
+#endif
+
/* The main loop */
while (common->state != FSG_STATE_TERMINATED) {
if (exception_in_progress(common) || signal_pending(current)) {
@@ -2909,6 +2962,10 @@ static void fsg_common_release(struct kref *ref)
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_FSL_UTP
+#include "fsl_updater.c"
+#endif
+
static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
{
struct fsg_dev *fsg = fsg_from_func(f);
@@ -2960,6 +3017,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
fsg_intf_desc.bInterfaceNumber = i;
fsg->interface_number = i;
+#ifdef CONFIG_FSL_UTP
+ if (is_utp_device(fsg))
+ utp_init(fsg);
+#endif
+
/* Find all the endpoints we will use */
ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
if (!ep)
@@ -3022,6 +3084,12 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
}
usb_free_all_descriptors(&fsg->function);
+
+#ifdef CONFIG_FSL_UTP
+ if (is_utp_device(common->fsg))
+ utp_exit(fsg);
+#endif
+
}
static inline struct fsg_lun_opts *to_fsg_lun_opts(struct config_item *item)
diff --git a/drivers/usb/gadget/function/fsl_updater.c b/drivers/usb/gadget/function/fsl_updater.c
new file mode 100644
index 000000000000..eec5bf1f8f1e
--- /dev/null
+++ b/drivers/usb/gadget/function/fsl_updater.c
@@ -0,0 +1,644 @@
+/*
+ * Freescale UUT driver
+ *
+ * Copyright 2008-2016 Freescale Semiconductor, Inc.
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ * Copyright 2017 NXP
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+static bool is_utp_device(struct fsg_dev *fsg)
+{
+ struct usb_device_descriptor *pdesc;
+
+ if (!fsg || !fsg->common || !fsg->common->cdev)
+ return false;
+
+ pdesc = &fsg->common->cdev->desc;
+ if (pdesc->idVendor == UTP_IDVENDOR &&
+ pdesc->idProduct == UTP_IDPRODUCT)
+ return true;
+
+ return false;
+}
+
+static u64 get_be64(u8 *buf)
+{
+ return ((u64)get_unaligned_be32(buf) << 32) |
+ get_unaligned_be32(buf + 4);
+}
+
+static DEFINE_IDA(utp_ida);
+
+static int utp_init(struct fsg_dev *fsg)
+{
+ struct utp_context *utp;
+ int index;
+
+ utp = vzalloc(sizeof(struct utp_context));
+ if (!utp)
+ return -EIO;
+
+ init_waitqueue_head(&utp->wq);
+ init_waitqueue_head(&utp->list_full_wq);
+
+ INIT_LIST_HEAD(&utp->read);
+ INIT_LIST_HEAD(&utp->write);
+ mutex_init(&utp->lock);
+
+ /* the max message is 64KB */
+ utp->buffer = vmalloc(0x10000);
+ if (!utp->buffer) {
+ vfree(utp);
+ return -EIO;
+ }
+
+ utp->utp_version = 0x1ull;
+ fsg->utp = utp;
+ utp->utp_dev.minor = MISC_DYNAMIC_MINOR;
+ utp->utp_dev.fops = &utp_fops;
+
+ index = ida_simple_get(&utp_ida, 0, 0, GFP_KERNEL);
+ if (index == 0)
+ snprintf(utp->utp_name, 8, "utp");
+ else
+ snprintf(utp->utp_name, 8, "utp%d", index);
+
+ utp->utp_dev.name = utp->utp_name;
+ return misc_register(&utp->utp_dev);
+}
+
+static void utp_exit(struct fsg_dev *fsg)
+{
+ struct utp_context *utp;
+ utp = UTP_CTX(fsg);
+
+ misc_deregister(&utp->utp_dev);
+
+ vfree(utp->buffer);
+ vfree(utp);
+}
+
+static struct utp_user_data *utp_user_data_alloc(size_t size)
+{
+ struct utp_user_data *uud;
+
+ uud = vmalloc(size + sizeof(*uud));
+ if (!uud)
+ return uud;
+ memset(uud, 0, size + sizeof(*uud));
+ uud->data.size = size + sizeof(uud->data);
+ INIT_LIST_HEAD(&uud->link);
+ return uud;
+}
+
+static void utp_user_data_free(struct utp_context *utp, struct utp_user_data *uud)
+{
+ mutex_lock(&utp->lock);
+ list_del(&uud->link);
+ mutex_unlock(&utp->lock);
+ vfree(uud);
+}
+
+/* Get the number of element for list */
+static u32 count_list(struct utp_context *utp, struct list_head *l)
+{
+ u32 count = 0;
+ struct list_head *tmp;
+
+ mutex_lock(&utp->lock);
+ list_for_each(tmp, l) {
+ count++;
+ }
+ mutex_unlock(&utp->lock);
+
+ return count;
+}
+
+/* The routine will not go on if utp_context.queue is empty */
+#define WAIT_ACTIVITY(utp, queue) \
+ wait_event_interruptible(utp->wq, !list_empty(&utp->queue))
+
+/* Called by userspace program (uuc) */
+static ssize_t utp_file_read(struct file *file,
+ char __user *buf,
+ size_t size,
+ loff_t *off)
+{
+ struct utp_user_data *uud;
+ size_t size_to_put;
+ int free = 0;
+
+ struct miscdevice *misc = (struct miscdevice *)file->private_data;
+ struct utp_context *utp = container_of(misc, struct utp_context, utp_dev);
+
+ WAIT_ACTIVITY(utp, read);
+
+ mutex_lock(&utp->lock);
+ uud = list_first_entry(&utp->read, struct utp_user_data, link);
+ mutex_unlock(&utp->lock);
+ size_to_put = uud->data.size;
+
+ if (size >= size_to_put)
+ free = !0;
+ if (copy_to_user(buf, &uud->data, size_to_put)) {
+ printk(KERN_INFO "[ %s ] copy error\n", __func__);
+ return -EACCES;
+ }
+ if (free)
+ utp_user_data_free(utp, uud);
+ else {
+ pr_info("sizeof = %zd, size = %zd\n",
+ sizeof(uud->data),
+ uud->data.size);
+
+ pr_err("Will not free utp_user_data, because buffer size = %zd need to put %zd\n",
+ size, size_to_put);
+ }
+
+ /*
+ * The user program has already finished data process,
+ * go on getting data from the host
+ */
+ wake_up(&utp->list_full_wq);
+
+ return size_to_put;
+}
+
+static ssize_t utp_file_write(struct file *file, const char __user *buf,
+ size_t size, loff_t *off)
+{
+ struct utp_user_data *uud;
+
+ struct miscdevice *misc = (struct miscdevice *)file->private_data;
+ struct utp_context *utp = container_of(misc, struct utp_context, utp_dev);
+
+ if (size < sizeof(uud->data))
+ return -EINVAL;
+ uud = utp_user_data_alloc(size);
+ if (uud == NULL)
+ return -ENOMEM;
+ if (copy_from_user(&uud->data, buf, size)) {
+ printk(KERN_INFO "[ %s ] copy error!\n", __func__);
+ vfree(uud);
+ return -EACCES;
+ }
+ mutex_lock(&utp->lock);
+ list_add_tail(&uud->link, &utp->write);
+ /* Go on EXEC routine process */
+ wake_up(&utp->wq);
+ mutex_unlock(&utp->lock);
+ return size;
+}
+
+/*
+ * uuc should change to use soc bus infrastructure to soc information
+ * /sys/devices/soc0/soc_id
+ * this function can be removed.
+ */
+static long
+utp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int cpu_id = 0;
+
+ switch (cmd) {
+ case UTP_GET_CPU_ID:
+ return put_user(cpu_id, (int __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+/* Will be called when the host wants to get the sense data */
+static int utp_get_sense(struct fsg_dev *fsg)
+{
+ if (UTP_CTX(fsg)->processed == 0)
+ return -1;
+
+ UTP_CTX(fsg)->processed = 0;
+ return 0;
+}
+
+static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size)
+{
+ struct fsg_buffhd *bh;
+ int rc;
+ u32 amount_left;
+ unsigned int amount;
+
+ /* Get the starting Logical Block Address and check that it's
+ * not too big */
+
+ amount_left = size;
+ if (unlikely(amount_left == 0))
+ return -EIO; /* No default reply*/
+
+ pr_debug("%s: sending %zd\n", __func__, size);
+ for (;;) {
+ /* Figure out how much we need to read:
+ * Try to read the remaining amount.
+ * But don't read more than the buffer size.
+ * And don't try to read past the end of the file.
+ * Finally, if we're not at a page boundary, don't read past
+ * the next page.
+ * If this means reading 0 then we were asked to read past
+ * the end of file. */
+ amount = min((unsigned int) amount_left, FSG_BUFLEN);
+
+ /* Wait for the next buffer to become available */
+ bh = fsg->common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg->common, true, bh);
+ if (rc)
+ return rc;
+ }
+
+ /* If we were asked to read past the end of file,
+ * end with an empty buffer. */
+ if (amount == 0) {
+ bh->inreq->length = 0;
+ bh->state = BUF_STATE_FULL;
+ break;
+ }
+
+ /* Perform the read */
+ pr_info("Copied to %p, %d bytes started from %zd\n",
+ bh->buf, amount, size - amount_left);
+ /* from upt buffer to file_storeage buffer */
+ memcpy(bh->buf, data + size - amount_left, amount);
+ amount_left -= amount;
+ fsg->common->residue -= amount;
+
+ bh->inreq->length = amount;
+ bh->state = BUF_STATE_FULL;
+
+ /* Send this buffer and go read some more */
+ bh->inreq->zero = 0;
+
+ /* USB Physical transfer: Data from device to host */
+ start_transfer(fsg, fsg->bulk_in, bh->inreq);
+
+ fsg->common->next_buffhd_to_fill = bh->next;
+
+ if (amount_left <= 0)
+ break;
+ }
+
+ return size - amount_left;
+}
+
+static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size)
+{
+ struct fsg_buffhd *bh;
+ int get_some_more;
+ u32 amount_left_to_req, amount_left_to_write;
+ unsigned int amount;
+ int rc;
+ loff_t offset;
+
+ /* Carry out the file writes */
+ get_some_more = 1;
+ amount_left_to_req = amount_left_to_write = size;
+
+ if (unlikely(amount_left_to_write == 0))
+ return -EIO;
+
+ offset = 0;
+ while (amount_left_to_write > 0) {
+
+ /* Queue a request for more data from the host */
+ bh = fsg->common->next_buffhd_to_fill;
+ if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+ /* Figure out how much we want to get:
+ * Try to get the remaining amount.
+ * But don't get more than the buffer size.
+ * And don't try to go past the end of the file.
+ * If we're not at a page boundary,
+ * don't go past the next page.
+ * If this means getting 0, then we were asked
+ * to write past the end of file.
+ * Finally, round down to a block boundary. */
+ amount = min(amount_left_to_req, FSG_BUFLEN);
+
+ if (amount == 0) {
+ get_some_more = 0;
+ /* cry now */
+ continue;
+ }
+
+ /* Get the next buffer */
+ amount_left_to_req -= amount;
+ if (amount_left_to_req == 0)
+ get_some_more = 0;
+
+ /* amount is always divisible by 512, hence by
+ * the bulk-out maxpacket size */
+ set_bulk_out_req_length(fsg->common, bh, amount);
+ bh->outreq->short_not_ok = 1;
+ start_transfer(fsg, fsg->bulk_out, bh->outreq);
+ fsg->common->next_buffhd_to_fill = bh->next;
+ continue;
+ }
+
+ /* Write the received data to the backing file */
+ bh = fsg->common->next_buffhd_to_drain;
+ if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+ break; /* We stopped early */
+ if (bh->state == BUF_STATE_FULL) {
+ smp_rmb();
+ fsg->common->next_buffhd_to_drain = bh->next;
+ bh->state = BUF_STATE_EMPTY;
+
+ /* Did something go wrong with the transfer? */
+ if (bh->outreq->status != 0)
+ /* cry again, COMMUNICATION_FAILURE */
+ break;
+
+ amount = bh->outreq->actual;
+
+ /* Perform the write */
+ memcpy(data + offset, bh->buf, amount);
+
+ offset += amount;
+ if (signal_pending(current))
+ return -EINTR; /* Interrupted!*/
+ amount_left_to_write -= amount;
+ fsg->common->residue -= amount;
+
+ /* Did the host decide to stop early? */
+ if (bh->outreq->actual != bh->outreq->length) {
+ fsg->common->short_packet_received = 1;
+ break;
+ }
+ continue;
+ }
+
+ /* Wait for something to happen */
+ rc = sleep_thread(fsg->common, true, bh);
+ if (rc)
+ return rc;
+ }
+
+ return -EIO;
+}
+
+static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply)
+{
+ UTP_CTX(fsg)->processed = true;
+ UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF;
+ UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF;
+ UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code;
+}
+
+static void utp_poll(struct fsg_dev *fsg)
+{
+ struct utp_context *ctx = UTP_CTX(fsg);
+ struct utp_user_data *uud = NULL;
+
+ mutex_lock(&ctx->lock);
+ if (!list_empty(&ctx->write))
+ uud = list_first_entry(&ctx->write, struct utp_user_data, link);
+ mutex_unlock(&ctx->lock);
+
+ if (uud) {
+ if (uud->data.flags & UTP_FLAG_STATUS) {
+ printk(KERN_WARNING "%s: exit with status %d\n",
+ __func__, uud->data.status);
+ UTP_SS_EXIT(fsg, uud->data.status);
+ } else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) {
+ UTP_SS_BUSY(fsg, --ctx->counter);
+ } else {
+ printk("%s: pass returned.\n", __func__);
+ UTP_SS_PASS(fsg);
+ }
+ utp_user_data_free(ctx, uud);
+ } else {
+ if (ctx->cur_state & UTP_FLAG_DATA) {
+ if (count_list(ctx, &ctx->read) < 7) {
+ pr_debug("%s: pass returned in POLL stage. \n", __func__);
+ UTP_SS_PASS(fsg);
+ ctx->cur_state = 0;
+ return;
+ }
+ }
+ UTP_SS_BUSY(fsg, --ctx->counter);
+ }
+}
+
+static int utp_exec(struct fsg_dev *fsg,
+ char *command,
+ int cmdsize,
+ unsigned long long payload)
+{
+ struct utp_user_data *uud = NULL, *uud2r;
+ struct utp_context *ctx = UTP_CTX(fsg);
+
+ ctx->counter = 0xFFFF;
+ uud2r = utp_user_data_alloc(cmdsize + 1);
+ if (!uud2r)
+ return -ENOMEM;
+ uud2r->data.flags = UTP_FLAG_COMMAND;
+ uud2r->data.payload = payload;
+ strncpy(uud2r->data.command, command, cmdsize);
+
+ mutex_lock(&ctx->lock);
+ list_add_tail(&uud2r->link, &ctx->read);
+ mutex_unlock(&ctx->lock);
+ /* wake up the read routine */
+ wake_up(&ctx->wq);
+
+ if (command[0] == '!') /* there will be no response */
+ return 0;
+
+ /*
+ * the user program (uuc) will return utp_message
+ * and add list to write list
+ */
+ WAIT_ACTIVITY(ctx, write);
+
+ mutex_lock(&ctx->lock);
+ if (!list_empty(&ctx->write)) {
+ uud = list_first_entry(&ctx->write, struct utp_user_data, link);
+#ifdef DEBUG
+ pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags);
+ if (uud->data.flags & UTP_FLAG_DATA) {
+ pr_info("\tbufsize = %d\n", uud->data.bufsize);
+ print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE,
+ 16, 2, uud->data.data, uud->data.bufsize, true);
+ }
+ if (uud->data.flags & UTP_FLAG_REPORT_BUSY)
+ pr_info("\tBUSY\n");
+#endif
+ } else {
+ pr_err("UTP write list is empty.\n");
+ mutex_unlock(&ctx->lock);
+ return 0;
+ }
+ mutex_unlock(&ctx->lock);
+
+ if (uud->data.flags & UTP_FLAG_DATA) {
+ memcpy(ctx->buffer, uud->data.data, uud->data.bufsize);
+ UTP_SS_SIZE(fsg, uud->data.bufsize);
+ } else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) {
+ UTP_SS_BUSY(fsg, ctx->counter);
+ } else if (uud->data.flags & UTP_FLAG_STATUS) {
+ printk(KERN_WARNING "%s: exit with status %d\n", __func__,
+ uud->data.status);
+ UTP_SS_EXIT(fsg, uud->data.status);
+ } else {
+ pr_debug("%s: pass returned in EXEC stage. \n", __func__);
+ UTP_SS_PASS(fsg);
+ }
+ utp_user_data_free(ctx, uud);
+ return 0;
+}
+
+static int utp_send_status(struct fsg_dev *fsg)
+{
+ struct fsg_buffhd *bh;
+ u8 status = US_BULK_STAT_OK;
+ struct bulk_cs_wrap *csw;
+ int rc;
+
+ /* Wait for the next buffer to become available */
+ bh = fsg->common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg->common, true, bh);
+ if (rc)
+ return rc;
+ }
+
+ if (fsg->common->phase_error) {
+ DBG(fsg, "sending phase-error status\n");
+ status = US_BULK_STAT_PHASE;
+
+ } else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) {
+ status = US_BULK_STAT_FAIL;
+ }
+
+ csw = bh->buf;
+
+ /* Store and send the Bulk-only CSW */
+ csw->Signature = __constant_cpu_to_le32(US_BULK_CS_SIGN);
+ csw->Tag = fsg->common->tag;
+ csw->Residue = cpu_to_le32(fsg->common->residue);
+ csw->Status = status;
+
+ bh->inreq->length = US_BULK_CS_WRAP_LEN;
+ bh->inreq->zero = 0;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq);
+ fsg->common->next_buffhd_to_fill = bh->next;
+ return 0;
+}
+
+static int utp_handle_message(struct fsg_dev *fsg,
+ char *cdb_data,
+ int default_reply)
+{
+ struct utp_msg *m = (struct utp_msg *)cdb_data;
+ void *data = NULL;
+ int r;
+ struct utp_user_data *uud2r;
+ unsigned long long param;
+ unsigned long tag;
+
+ if (m->f0 != 0xF0)
+ return default_reply;
+
+ tag = get_unaligned_be32((void *)&m->utp_msg_tag);
+ param = get_be64((void *)&m->param);
+ pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n",
+ m->utp_msg_type, tag, param);
+
+ switch ((enum utp_msg_type)m->utp_msg_type) {
+
+ case UTP_POLL:
+ if (get_be64((void *)&m->param) == 1) {
+ pr_debug("%s: version request\n", __func__);
+ UTP_SS_EXIT(fsg, UTP_CTX(fsg)->utp_version);
+ break;
+ }
+ utp_poll(fsg);
+ break;
+ case UTP_EXEC:
+ pr_debug("%s: EXEC\n", __func__);
+ data = vmalloc(fsg->common->data_size);
+ memset(data, 0, fsg->common->data_size);
+ /* copy data from usb buffer to utp buffer */
+ utp_do_write(fsg, data, fsg->common->data_size);
+ utp_exec(fsg, data, fsg->common->data_size, param);
+ vfree(data);
+ break;
+ case UTP_GET: /* data from device to host */
+ pr_debug("%s: GET, %d bytes\n", __func__,
+ fsg->common->data_size);
+ r = utp_do_read(fsg, UTP_CTX(fsg)->buffer,
+ fsg->common->data_size);
+ UTP_SS_PASS(fsg);
+ break;
+ case UTP_PUT:
+ UTP_CTX(fsg)->cur_state = UTP_FLAG_DATA;
+ pr_debug("%s: PUT, Received %d bytes\n", __func__, fsg->common->data_size);/* data from host to device */
+ uud2r = utp_user_data_alloc(fsg->common->data_size);
+ if (!uud2r)
+ return -ENOMEM;
+ uud2r->data.bufsize = fsg->common->data_size;
+ uud2r->data.flags = UTP_FLAG_DATA;
+ utp_do_write(fsg, uud2r->data.data, fsg->common->data_size);
+ /* don't know what will be written */
+ mutex_lock(&UTP_CTX(fsg)->lock);
+ list_add_tail(&uud2r->link, &UTP_CTX(fsg)->read);
+ mutex_unlock(&UTP_CTX(fsg)->lock);
+ wake_up(&UTP_CTX(fsg)->wq);
+ /*
+ * Return PASS or FAIL according to uuc's status
+ * Please open it if need to check uuc's status
+ * and use another version uuc
+ */
+#if 0
+ struct utp_user_data *uud = NULL;
+ struct utp_context *ctx;
+ WAIT_ACTIVITY(write);
+ ctx = UTP_CTX(fsg);
+ mutex_lock(&ctx->lock);
+
+ if (!list_empty(&ctx->write))
+ uud = list_first_entry(&ctx->write,
+ struct utp_user_data, link);
+
+ mutex_unlock(&ctx->lock);
+ if (uud) {
+ if (uud->data.flags & UTP_FLAG_STATUS) {
+ printk(KERN_WARNING "%s: exit with status %d\n",
+ __func__, uud->data.status);
+ UTP_SS_EXIT(fsg, uud->data.status);
+ } else {
+ pr_debug("%s: pass\n", __func__);
+ UTP_SS_PASS(fsg);
+ }
+ utp_user_data_free(uud);
+ } else{
+ UTP_SS_PASS(fsg);
+ }
+#endif
+ if (count_list(UTP_CTX(fsg), &UTP_CTX(fsg)->read) < 7) {
+ UTP_CTX(fsg)->cur_state = 0;
+ UTP_SS_PASS(fsg);
+ } else
+ UTP_SS_BUSY(fsg, UTP_CTX(fsg)->counter);
+
+ break;
+ }
+
+ utp_send_status(fsg);
+ return -1;
+}
diff --git a/drivers/usb/gadget/function/fsl_updater.h b/drivers/usb/gadget/function/fsl_updater.h
new file mode 100644
index 000000000000..044beb0e2114
--- /dev/null
+++ b/drivers/usb/gadget/function/fsl_updater.h
@@ -0,0 +1,152 @@
+/*
+ * Freescale UUT driver
+ *
+ * Copyright 2008-2016 Freescale Semiconductor, Inc.
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ * Copyright 2017 NXP
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __FSL_UPDATER_H
+#define __FSL_UPDATER_H
+
+#include <linux/miscdevice.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+#include <linux/ioctl.h>
+/* #include <mach/hardware.h> */
+struct utp_context;
+
+static int utp_init(struct fsg_dev *fsg);
+static void utp_exit(struct fsg_dev *fsg);
+static ssize_t utp_file_read(struct file *file,
+ char __user *buf,
+ size_t size,
+ loff_t *off);
+
+static ssize_t utp_file_write(struct file *file,
+ const char __user *buf,
+ size_t size,
+ loff_t *off);
+
+static bool is_utp_device(struct fsg_dev *fsg);
+static long utp_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg);
+static struct utp_user_data *utp_user_data_alloc(size_t size);
+static void utp_user_data_free(struct utp_context *utp, struct utp_user_data *uud);
+static int utp_get_sense(struct fsg_dev *fsg);
+static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size);
+static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size);
+static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply);
+static int utp_handle_message(struct fsg_dev *fsg,
+ char *cdb_data,
+ int default_reply);
+
+#define UTP_REPLY_PASS 0
+#define UTP_REPLY_EXIT 0x8001
+#define UTP_REPLY_BUSY 0x8002
+#define UTP_REPLY_SIZE 0x8003
+#define UTP_SENSE_KEY 9
+
+#define UTP_MINOR 222
+/* MISC_DYNAMIC_MINOR would be better, but... */
+
+#define UTP_IDVENDOR 0x066F
+#define UTP_IDPRODUCT 0x37FF
+
+#define UTP_COMMAND_SIZE 80
+
+#define UTP_SS_EXIT(fsg, r) utp_set_sense(fsg, UTP_REPLY_EXIT, (u64)r)
+#define UTP_SS_PASS(fsg) utp_set_sense(fsg, UTP_REPLY_PASS, 0)
+#define UTP_SS_BUSY(fsg, r) utp_set_sense(fsg, UTP_REPLY_BUSY, (u64)r)
+#define UTP_SS_SIZE(fsg, r) utp_set_sense(fsg, UTP_REPLY_SIZE, (u64)r)
+
+#define UTP_IOCTL_BASE 'U'
+#define UTP_GET_CPU_ID _IOR(UTP_IOCTL_BASE, 0, int)
+/* the structure of utp message which is mapped to 16-byte SCSI CBW's CDB */
+#pragma pack(1)
+struct utp_msg {
+ u8 f0;
+ u8 utp_msg_type;
+ u32 utp_msg_tag;
+ union {
+ struct {
+ u32 param_lsb;
+ u32 param_msb;
+ };
+ u64 param;
+ };
+};
+
+enum utp_msg_type {
+ UTP_POLL = 0,
+ UTP_EXEC,
+ UTP_GET,
+ UTP_PUT,
+};
+
+struct utp_context {
+ wait_queue_head_t wq;
+ wait_queue_head_t list_full_wq;
+ struct mutex lock;
+ struct list_head read;
+ struct list_head write;
+ u32 sd, sdinfo, sdinfo_h; /* sense data */
+ int processed;
+ u8 *buffer;
+ u32 counter;
+ u64 utp_version;
+ u32 cur_state;
+ struct miscdevice utp_dev;
+ char utp_name[8];
+};
+
+static const struct file_operations utp_fops = {
+ .open = nonseekable_open,
+ .read = utp_file_read,
+ .write = utp_file_write,
+ /* .ioctl = utp_ioctl, */
+ .unlocked_ioctl = utp_ioctl,
+};
+
+#define UTP_FLAG_COMMAND 0x00000001
+#define UTP_FLAG_DATA 0x00000002
+#define UTP_FLAG_STATUS 0x00000004
+#define UTP_FLAG_REPORT_BUSY 0x10000000
+struct utp_message {
+ u32 flags;
+ size_t size;
+ union {
+ struct {
+ u64 payload;
+ char command[1];
+ };
+ struct {
+ size_t bufsize;
+ u8 data[1];
+ };
+ u32 status;
+ };
+};
+
+struct utp_user_data {
+ struct list_head link;
+ struct utp_message data;
+};
+#pragma pack()
+
+static inline struct utp_context *UTP_CTX(struct fsg_dev *fsg)
+{
+ return (struct utp_context *)fsg->utp;
+}
+
+#endif /* __FSL_UPDATER_H */
+
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 6e834b83a104..5ad03525e7f7 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -93,7 +93,7 @@ module_param (log2_irq_thresh, int, S_IRUGO);
MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes");
/* initial park setting: slower than hw default */
-static unsigned park = 0;
+static unsigned park = 3;
module_param (park, uint, S_IRUGO);
MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets");
@@ -1244,6 +1244,10 @@ static const struct hc_driver ehci_hc_driver = {
* device support
*/
.free_dev = ehci_remove_device,
+#ifdef CONFIG_USB_HCD_TEST_MODE
+ /* EH SINGLE_STEP_SET_FEATURE test support */
+ .submit_single_step_set_feature = ehci_submit_single_step_set_feature,
+#endif
};
void ehci_init_driver(struct hc_driver *drv,
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 37ef2ac9cdae..ab4788cfcc1d 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -730,145 +730,6 @@ ehci_hub_descriptor (
}
/*-------------------------------------------------------------------------*/
-#ifdef CONFIG_USB_HCD_TEST_MODE
-
-#define EHSET_TEST_SINGLE_STEP_SET_FEATURE 0x06
-
-static void usb_ehset_completion(struct urb *urb)
-{
- struct completion *done = urb->context;
-
- complete(done);
-}
-static int submit_single_step_set_feature(
- struct usb_hcd *hcd,
- struct urb *urb,
- int is_setup
-);
-
-/*
- * Allocate and initialize a control URB. This request will be used by the
- * EHSET SINGLE_STEP_SET_FEATURE test in which the DATA and STATUS stages
- * of the GetDescriptor request are sent 15 seconds after the SETUP stage.
- * Return NULL if failed.
- */
-static struct urb *request_single_step_set_feature_urb(
- struct usb_device *udev,
- void *dr,
- void *buf,
- struct completion *done
-) {
- struct urb *urb;
- struct usb_hcd *hcd = bus_to_hcd(udev->bus);
- struct usb_host_endpoint *ep;
-
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb)
- return NULL;
-
- urb->pipe = usb_rcvctrlpipe(udev, 0);
- ep = (usb_pipein(urb->pipe) ? udev->ep_in : udev->ep_out)
- [usb_pipeendpoint(urb->pipe)];
- if (!ep) {
- usb_free_urb(urb);
- return NULL;
- }
-
- urb->ep = ep;
- urb->dev = udev;
- urb->setup_packet = (void *)dr;
- urb->transfer_buffer = buf;
- urb->transfer_buffer_length = USB_DT_DEVICE_SIZE;
- urb->complete = usb_ehset_completion;
- urb->status = -EINPROGRESS;
- urb->actual_length = 0;
- urb->transfer_flags = URB_DIR_IN;
- usb_get_urb(urb);
- atomic_inc(&urb->use_count);
- atomic_inc(&urb->dev->urbnum);
- urb->setup_dma = dma_map_single(
- hcd->self.sysdev,
- urb->setup_packet,
- sizeof(struct usb_ctrlrequest),
- DMA_TO_DEVICE);
- urb->transfer_dma = dma_map_single(
- hcd->self.sysdev,
- urb->transfer_buffer,
- urb->transfer_buffer_length,
- DMA_FROM_DEVICE);
- urb->context = done;
- return urb;
-}
-
-static int ehset_single_step_set_feature(struct usb_hcd *hcd, int port)
-{
- int retval = -ENOMEM;
- struct usb_ctrlrequest *dr;
- struct urb *urb;
- struct usb_device *udev;
- struct ehci_hcd *ehci = hcd_to_ehci(hcd);
- struct usb_device_descriptor *buf;
- DECLARE_COMPLETION_ONSTACK(done);
-
- /* Obtain udev of the rhub's child port */
- udev = usb_hub_find_child(hcd->self.root_hub, port);
- if (!udev) {
- ehci_err(ehci, "No device attached to the RootHub\n");
- return -ENODEV;
- }
- buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
- if (!dr) {
- kfree(buf);
- return -ENOMEM;
- }
-
- /* Fill Setup packet for GetDescriptor */
- dr->bRequestType = USB_DIR_IN;
- dr->bRequest = USB_REQ_GET_DESCRIPTOR;
- dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8);
- dr->wIndex = 0;
- dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE);
- urb = request_single_step_set_feature_urb(udev, dr, buf, &done);
- if (!urb)
- goto cleanup;
-
- /* Submit just the SETUP stage */
- retval = submit_single_step_set_feature(hcd, urb, 1);
- if (retval)
- goto out1;
- if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) {
- usb_kill_urb(urb);
- retval = -ETIMEDOUT;
- ehci_err(ehci, "%s SETUP stage timed out on ep0\n", __func__);
- goto out1;
- }
- msleep(15 * 1000);
-
- /* Complete remaining DATA and STATUS stages using the same URB */
- urb->status = -EINPROGRESS;
- usb_get_urb(urb);
- atomic_inc(&urb->use_count);
- atomic_inc(&urb->dev->urbnum);
- retval = submit_single_step_set_feature(hcd, urb, 0);
- if (!retval && !wait_for_completion_timeout(&done,
- msecs_to_jiffies(2000))) {
- usb_kill_urb(urb);
- retval = -ETIMEDOUT;
- ehci_err(ehci, "%s IN stage timed out on ep0\n", __func__);
- }
-out1:
- usb_free_urb(urb);
-cleanup:
- kfree(dr);
- kfree(buf);
- return retval;
-}
-#endif /* CONFIG_USB_HCD_TEST_MODE */
-/*-------------------------------------------------------------------------*/
int ehci_hub_control(
struct usb_hcd *hcd,
@@ -943,7 +804,7 @@ int ehci_hub_control(
break;
#ifdef CONFIG_USB_OTG
if ((hcd->self.otg_port == (wIndex + 1))
- && hcd->self.b_hnp_enable) {
+ && hcd->self.b_hnp_enable && !hcd->self.otg_fsm) {
otg_start_hnp(hcd->usb_phy->otg);
break;
}
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 8f3f055c05fa..a33c49cae3de 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -1167,7 +1167,7 @@ submit_async (
* performed; TRUE - SETUP and FALSE - IN+STATUS
* Returns 0 if success
*/
-static int submit_single_step_set_feature(
+static int ehci_submit_single_step_set_feature(
struct usb_hcd *hcd,
struct urb *urb,
int is_setup
@@ -1201,10 +1201,10 @@ static int submit_single_step_set_feature(
* 15 secs after the setup
*/
if (is_setup) {
- /* SETUP pid */
+ /* SETUP pid, and interrupt after SETUP completion */
qtd_fill(ehci, qtd, urb->setup_dma,
sizeof(struct usb_ctrlrequest),
- token | (2 /* "setup" */ << 8), 8);
+ QTD_IOC | token | (2 /* "setup" */ << 8), 8);
submit_async(ehci, urb, &qtd_list, GFP_ATOMIC);
return 0; /*Return now; we shall come back after 15 seconds*/
@@ -1241,12 +1241,8 @@ static int submit_single_step_set_feature(
qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
list_add_tail(&qtd->qtd_list, head);
- /* dont fill any data in such packets */
- qtd_fill(ehci, qtd, 0, 0, token, 0);
-
- /* by default, enable interrupt on urb completion */
- if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT)))
- qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC);
+ /* Interrupt after STATUS completion */
+ qtd_fill(ehci, qtd, 0, 0, token | QTD_IOC, 0);
submit_async(ehci, urb, &qtd_list, GFP_KERNEL);
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index f5f2c83a2c66..60f0d515a26b 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -1274,23 +1274,3 @@ static void quirk_usb_early_handoff(struct pci_dev *pdev)
}
DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID,
PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff);
-
-bool usb_xhci_needs_pci_reset(struct pci_dev *pdev)
-{
- /*
- * Our dear uPD72020{1,2} friend only partially resets when
- * asked to via the XHCI interface, and may end up doing DMA
- * at the wrong addresses, as it keeps the top 32bit of some
- * addresses from its previous programming under obscure
- * circumstances.
- * Give it a good wack at probe time. Unfortunately, this
- * needs to happen before we've had a chance to discover any
- * quirk, or the system will be in a rather bad state.
- */
- if (pdev->vendor == PCI_VENDOR_ID_RENESAS &&
- (pdev->device == 0x0014 || pdev->device == 0x0015))
- return true;
-
- return false;
-}
-EXPORT_SYMBOL_GPL(usb_xhci_needs_pci_reset);
diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h
index 4ca0d9b7e463..63c633077d9e 100644
--- a/drivers/usb/host/pci-quirks.h
+++ b/drivers/usb/host/pci-quirks.h
@@ -16,7 +16,6 @@ void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
void sb800_prefetch(struct device *dev, int on);
-bool usb_xhci_needs_pci_reset(struct pci_dev *pdev);
bool usb_amd_pt_check_port(struct device *device, int port);
#else
struct pci_dev;
diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c
index 2c83b37ae8f2..0bd8e6b260a0 100644
--- a/drivers/usb/host/xhci-dbg.c
+++ b/drivers/usb/host/xhci-dbg.c
@@ -238,6 +238,9 @@ void xhci_print_run_regs(struct xhci_hcd *xhci)
xhci_dbg(xhci, " %p: Microframe index = 0x%x\n",
&xhci->run_regs->microframe_index,
(unsigned int) temp);
+ if (xhci->quirks & XHCI_SKIP_ACCESS_RESERVED_REG)
+ return;
+
for (i = 0; i < 7; i++) {
temp = readl(&xhci->run_regs->rsvd[i]);
if (temp != XHCI_INIT_VALUE)
@@ -251,7 +254,8 @@ void xhci_print_registers(struct xhci_hcd *xhci)
{
xhci_print_cap_regs(xhci);
xhci_print_op_regs(xhci);
- xhci_print_ports(xhci);
+ if (!(xhci->quirks & XHCI_SKIP_ACCESS_RESERVED_REG))
+ xhci_print_ports(xhci);
}
void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst)
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 95503bb9b067..6bd3eaa30688 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1321,6 +1321,15 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
/* 4.19.6 Port Test Modes (USB2 Test Mode) */
if (hcd->speed != HCD_USB2)
goto error;
+#ifdef CONFIG_USB_HCD_TEST_MODE
+ if (test_mode == EHSET_TEST_SINGLE_STEP_SET_FEATURE) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ retval = ehset_single_step_set_feature(hcd,
+ wIndex + 1);
+ spin_lock_irqsave(&xhci->lock, flags);
+ break;
+ }
+#endif
if (test_mode > TEST_FORCE_EN || test_mode < TEST_J)
goto error;
retval = xhci_enter_test_mode(xhci, test_mode, wIndex,
@@ -1583,6 +1592,7 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
spin_unlock_irqrestore(&xhci->lock, flags);
return 0;
}
+EXPORT_SYMBOL(xhci_bus_suspend);
/*
* Workaround for missing Cold Attach Status (CAS) if device re-plugged in S3.
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 021a2d320acc..4f51feeec6fa 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -303,13 +303,6 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
driver = (struct hc_driver *)id->driver_data;
- /* For some HW implementation, a XHCI reset is just not enough... */
- if (usb_xhci_needs_pci_reset(dev)) {
- dev_info(&dev->dev, "Resetting\n");
- if (pci_reset_function_locked(dev))
- dev_warn(&dev->dev, "Reset failed");
- }
-
/* Prevent runtime suspending between USB-2 and USB-3 initialization */
pm_runtime_get_noresume(&dev->dev);
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 108a212294bf..eaed46b1c846 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -20,6 +20,8 @@
#include <linux/usb/phy.h>
#include <linux/slab.h>
#include <linux/acpi.h>
+#include <linux/busfreq-imx.h>
+#include <linux/usb/of.h>
#include "xhci.h"
#include "xhci-plat.h"
@@ -266,6 +268,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (device_property_read_bool(sysdev, "usb3-lpm-capable"))
xhci->quirks |= XHCI_LPM_SUPPORT;
+ if (device_property_read_bool(sysdev, "usb3-resume-missing-cas"))
+ xhci->quirks |= XHCI_MISSING_CAS;
+
if (device_property_read_bool(&pdev->dev, "quirk-broken-port-ped"))
xhci->quirks |= XHCI_BROKEN_PORT_PED;
@@ -281,6 +286,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_usb3_hcd;
}
+ request_bus_freq(BUS_FREQ_HIGH);
+ hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
+ xhci->shared_hcd->tpl_support = hcd->tpl_support;
+
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
if (ret)
goto disable_usb_phy;
@@ -295,12 +304,6 @@ static int xhci_plat_probe(struct platform_device *pdev)
device_enable_async_suspend(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
- /*
- * Prevent runtime pm from being on as default, users should enable
- * runtime pm using power/control in sysfs.
- */
- pm_runtime_forbid(&pdev->dev);
-
return 0;
@@ -309,6 +312,7 @@ dealloc_usb2_hcd:
disable_usb_phy:
usb_phy_shutdown(hcd->usb_phy);
+ release_bus_freq(BUS_FREQ_HIGH);
put_usb3_hcd:
usb_put_hcd(xhci->shared_hcd);
@@ -347,6 +351,9 @@ static int xhci_plat_remove(struct platform_device *dev)
clk_disable_unprepare(clk);
usb_put_hcd(hcd);
+ if (!pm_runtime_suspended(&dev->dev))
+ release_bus_freq(BUS_FREQ_HIGH);
+
pm_runtime_set_suspended(&dev->dev);
pm_runtime_disable(&dev->dev);
@@ -384,18 +391,14 @@ static int __maybe_unused xhci_plat_resume(struct device *dev)
static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-
- return xhci_suspend(xhci, true);
+ release_bus_freq(BUS_FREQ_HIGH);
+ return 0;
}
static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-
- return xhci_resume(xhci, 0);
+ request_bus_freq(BUS_FREQ_HIGH);
+ return 0;
}
static const struct dev_pm_ops xhci_plat_pm_ops = {
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 89af395cd89c..d70aae05769b 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1640,6 +1640,11 @@ static void handle_port_status(struct xhci_hcd *xhci,
if ((major_revision == 0x03) != (hcd->speed >= HCD_USB3))
hcd = xhci->shared_hcd;
+ if (!hcd) {
+ bogus_port_status = true;
+ goto cleanup;
+ }
+
if (major_revision == 0) {
xhci_warn(xhci, "Event for port %u not in "
"Extended Capabilities, ignoring.\n",
@@ -2063,12 +2068,9 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
switch (trb_comp_code) {
case COMP_SUCCESS:
- if (trb_type != TRB_STATUS) {
- xhci_warn(xhci, "WARN: Success on ctrl %s TRB without IOC set?\n",
+ if (trb_type != TRB_STATUS)
+ xhci_dbg(xhci, "Success on ctrl %s TRB without IOC set?\n",
(trb_type == TRB_DATA) ? "data" : "setup");
- *status = -ESHUTDOWN;
- break;
- }
*status = 0;
break;
case COMP_SHORT_PACKET:
@@ -3524,6 +3526,129 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
return 0;
}
+#ifdef CONFIG_USB_HCD_TEST_MODE
+/*
+ * This function prepare TRBs and submits them for the
+ * SINGLE_STEP_SET_FEATURE Test.
+ * This is done in two parts: first SETUP req for GetDesc is sent then
+ * 15 seconds later, the IN stage for GetDesc starts to req data from dev
+ *
+ * is_setup : argument decides which of the two stage needs to be
+ * performed; TRUE - SETUP and FALSE - IN+STATUS
+ * Returns 0 if success
+ */
+int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup)
+{
+ int slot_id;
+ unsigned int ep_index;
+ struct xhci_ring *ep_ring;
+ int ret;
+ struct usb_ctrlrequest *setup;
+ struct xhci_generic_trb *start_trb;
+ int start_cycle;
+ u32 field, length_field, remainder;
+ struct urb_priv *urb_priv;
+ struct xhci_td *td;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ /* urb_priv will be free after transcation has completed */
+ urb_priv = kzalloc(sizeof(struct urb_priv) +
+ sizeof(struct xhci_td), GFP_KERNEL);
+ if (!urb_priv)
+ return -ENOMEM;
+
+ td = &urb_priv->td[0];
+ urb_priv->num_tds = 1;
+ urb_priv->num_tds_done = 0;
+ urb->hcpriv = urb_priv;
+
+ ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
+ if (!ep_ring) {
+ ret = -EINVAL;
+ goto free_priv;
+ }
+
+ slot_id = urb->dev->slot_id;
+ ep_index = xhci_get_endpoint_index(&urb->ep->desc);
+
+ setup = (struct usb_ctrlrequest *) urb->setup_packet;
+ if (is_setup) {
+ ret = prepare_transfer(xhci, xhci->devs[slot_id],
+ ep_index, urb->stream_id,
+ 1, urb, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_priv;
+
+ start_trb = &ep_ring->enqueue->generic;
+ start_cycle = ep_ring->cycle_state;
+ /* Save the DMA address of the last TRB in the TD */
+ td->last_trb = ep_ring->enqueue;
+ field = TRB_IOC | TRB_IDT | TRB_TYPE(TRB_SETUP) | start_cycle;
+ /* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */
+ if ((xhci->hci_version >= 0x100) ||
+ (xhci->quirks & XHCI_MTK_HOST))
+ field |= TRB_TX_TYPE(TRB_DATA_IN);
+
+ queue_trb(xhci, ep_ring, false,
+ setup->bRequestType | setup->bRequest << 8 |
+ le16_to_cpu(setup->wValue) << 16,
+ le16_to_cpu(setup->wIndex) |
+ le16_to_cpu(setup->wLength) << 16,
+ TRB_LEN(8) | TRB_INTR_TARGET(0),
+ /* Immediate data in pointer */
+ field);
+ giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
+ start_cycle, start_trb);
+ return 0;
+ }
+
+ ret = prepare_transfer(xhci, xhci->devs[slot_id],
+ ep_index, urb->stream_id,
+ 2, urb, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_priv;
+
+ start_trb = &ep_ring->enqueue->generic;
+ start_cycle = ep_ring->cycle_state;
+ field = TRB_ISP | TRB_TYPE(TRB_DATA);
+
+ remainder = xhci_td_remainder(xhci, 0,
+ urb->transfer_buffer_length,
+ urb->transfer_buffer_length,
+ urb, 1);
+
+ length_field = TRB_LEN(urb->transfer_buffer_length) |
+ TRB_TD_SIZE(remainder) |
+ TRB_INTR_TARGET(0);
+
+ if (urb->transfer_buffer_length > 0) {
+ field |= TRB_DIR_IN;
+ queue_trb(xhci, ep_ring, true,
+ lower_32_bits(urb->transfer_dma),
+ upper_32_bits(urb->transfer_dma),
+ length_field,
+ field | ep_ring->cycle_state);
+ }
+
+ td->last_trb = ep_ring->enqueue;
+ field = TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state;
+ queue_trb(xhci, ep_ring, false,
+ 0,
+ 0,
+ TRB_INTR_TARGET(0),
+ field);
+
+ giveback_first_trb(xhci, slot_id, ep_index, 0,
+ start_cycle, start_trb);
+
+ return 0;
+free_priv:
+ xhci_urb_free_priv(urb_priv);
+ return ret;
+}
+#endif /* CONFIG_USB_HCD_TEST_MODE */
+
/*
* The transfer burst count field of the isochronous TRB defines the number of
* bursts that are required to move all packets in this TD. Only SuperSpeed
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 6c0a0ca316d3..a161ccb829fa 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -203,7 +203,7 @@ int xhci_reset(struct xhci_hcd *xhci)
* Without this delay, the subsequent HC register access,
* may result in a system hang very rarely.
*/
- if (xhci->quirks & XHCI_INTEL_HOST)
+ if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_CDNS_HOST))
udelay(1000);
ret = xhci_handshake(&xhci->op_regs->command,
@@ -5063,6 +5063,7 @@ static const struct hc_driver xhci_hc_driver = {
.enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout,
.disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout,
.find_raw_port_number = xhci_find_raw_port_number,
+ .submit_single_step_set_feature = xhci_submit_single_step_set_feature,
};
void xhci_init_driver(struct hc_driver *drv,
@@ -5079,6 +5080,8 @@ void xhci_init_driver(struct hc_driver *drv,
drv->reset = over->reset;
if (over->start)
drv->start = over->start;
+ if (over->bus_suspend)
+ drv->bus_suspend = over->bus_suspend;
}
}
EXPORT_SYMBOL_GPL(xhci_init_driver);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index db1af99d53bd..6edb43a65f5a 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1840,6 +1840,8 @@ struct xhci_hcd {
#define XHCI_INTEL_USB_ROLE_SW BIT_ULL(31)
#define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34)
#define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35)
+#define XHCI_SKIP_ACCESS_RESERVED_REG (1 << 29)
+#define XHCI_CDNS_HOST (1 << 31)
unsigned int num_active_eps;
unsigned int limit_active_eps;
@@ -1880,6 +1882,7 @@ struct xhci_driver_overrides {
size_t extra_priv_size;
int (*reset)(struct usb_hcd *hcd);
int (*start)(struct usb_hcd *hcd);
+ int (*bus_suspend)(struct usb_hcd *hcd);
};
#define XHCI_CFC_DELAY 10
@@ -2097,6 +2100,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex,
int xhci_hub_status_data(struct usb_hcd *hcd, char *buf);
int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1);
void xhci_hc_died(struct xhci_hcd *xhci);
+#ifdef CONFIG_USB_HCD_TEST_MODE
+int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup);
+#else
+static inline int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup)
+{
+ return 0;
+}
+#endif
#ifdef CONFIG_PM
int xhci_bus_suspend(struct usb_hcd *hcd);
diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c
index c31b4a33e6bb..f54473cd25b3 100644
--- a/drivers/usb/misc/ehset.c
+++ b/drivers/usb/misc/ehset.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/ch11.h>
+#include <linux/usb/otg-fsm.h>
#define TEST_SE0_NAK_PID 0x0101
#define TEST_J_PID 0x0102
@@ -25,6 +26,7 @@
#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106
#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107
#define TEST_SINGLE_STEP_SET_FEATURE 0x0108
+#define TEST_OTG_TEST_DEVICE_SUPPORT 0x0200
static int ehset_probe(struct usb_interface *intf,
const struct usb_device_id *id)
@@ -35,6 +37,7 @@ static int ehset_probe(struct usb_interface *intf,
struct usb_device_descriptor *buf;
u8 portnum = dev->portnum;
u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
+ struct otg_fsm *fsm = dev->bus->otg_fsm;
switch (test_pid) {
case TEST_SE0_NAK_PID:
@@ -115,6 +118,27 @@ static int ehset_probe(struct usb_interface *intf,
NULL, 0, 60 * 1000);
break;
+ case TEST_OTG_TEST_DEVICE_SUPPORT:
+ if (!fsm)
+ return ret;
+
+ /* B host enumerate test device */
+ if (dev->bus->is_b_host) {
+ otg_add_timer(fsm, B_TST_SUSP);
+ ret = 0;
+ break;
+ }
+
+ mutex_lock(&fsm->lock);
+ fsm->tst_maint = 1;
+ if (le16_to_cpu(dev->descriptor.bcdDevice) & 0x1)
+ fsm->otg_vbus_off = 1;
+ else
+ fsm->otg_vbus_off = 0;
+ mutex_unlock(&fsm->lock);
+ otg_add_timer(fsm, A_TST_MAINT);
+ ret = 0;
+ break;
default:
dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
__func__, test_pid);
@@ -135,6 +159,7 @@ static const struct usb_device_id ehset_id_table[] = {
{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
+ { USB_DEVICE(0x1a0a, TEST_OTG_TEST_DEVICE_SUPPORT) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, ehset_id_table);
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 85a92d0813dd..2b64621ecb32 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -180,7 +180,7 @@ config USB_MV_OTG
config USB_MXS_PHY
tristate "Freescale MXS USB PHY support"
- depends on ARCH_MXC || ARCH_MXS
+ depends on ARCH_MXC || ARCH_MXS || ARCH_MXC_ARM64
select STMP_DEVICE
select USB_PHY
help
diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c
index 0e2f1a36d315..18e256ad7ed6 100644
--- a/drivers/usb/phy/phy-mxs-usb.c
+++ b/drivers/usb/phy/phy-mxs-usb.c
@@ -1,5 +1,6 @@
/*
- * Copyright 2012-2014 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*
@@ -23,9 +24,11 @@
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
+#include <linux/regulator/consumer.h>
#define DRIVER_NAME "mxs_phy"
+/* Register Macro */
#define HW_USBPHY_PWD 0x00
#define HW_USBPHY_TX 0x10
#define HW_USBPHY_CTRL 0x30
@@ -43,6 +46,11 @@
#define GM_USBPHY_TX_TXCAL45DN(x) (((x) & 0xf) << 8)
#define GM_USBPHY_TX_D_CAL(x) (((x) & 0xf) << 0)
+/* imx7ulp */
+#define HW_USBPHY_PLL_SIC 0xa4
+#define HW_USBPHY_PLL_SIC_SET 0xa4
+#define HW_USBPHY_PLL_SIC_CLR 0xa8
+
#define BM_USBPHY_CTRL_SFTRST BIT(31)
#define BM_USBPHY_CTRL_CLKGATE BIT(30)
#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27)
@@ -61,17 +69,44 @@
#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18))
#define BM_USBPHY_DEBUG_CLKGATE BIT(30)
+/* imx7ulp */
+#define BM_USBPHY_PLL_LOCK BIT(31)
+#define BM_USBPHY_PLL_REG_ENABLE BIT(21)
+#define BM_USBPHY_PLL_BYPASS BIT(16)
+#define BM_USBPHY_PLL_POWER BIT(12)
+#define BM_USBPHY_PLL_EN_USB_CLKS BIT(6)
/* Anatop Registers */
+#define ANADIG_PLL_USB2 0x20
+#define ANADIG_PLL_USB2_SET 0x24
+#define ANADIG_PLL_USB2_CLR 0x28
+#define ANADIG_REG_1P1_SET 0x114
+#define ANADIG_REG_1P1_CLR 0x118
+
#define ANADIG_ANA_MISC0 0x150
#define ANADIG_ANA_MISC0_SET 0x154
#define ANADIG_ANA_MISC0_CLR 0x158
+#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4
+#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8
+#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18)
+
#define ANADIG_USB1_VBUS_DET_STAT 0x1c0
+#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
+
+#define ANADIG_USB1_CHRG_DET_STAT 0x1d0
+#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2)
+#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1)
+#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0)
+
#define ANADIG_USB2_VBUS_DET_STAT 0x220
#define ANADIG_USB1_LOOPBACK_SET 0x1e4
#define ANADIG_USB1_LOOPBACK_CLR 0x1e8
+#define ANADIG_USB1_LOOPBACK_UTMI_TESTSTART BIT(0)
+
#define ANADIG_USB2_LOOPBACK_SET 0x244
#define ANADIG_USB2_LOOPBACK_CLR 0x248
@@ -94,6 +129,41 @@
#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29)
#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28)
+#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18)
+#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19)
+
+#define BM_ANADIG_PLL_USB2_HOLD_RING_OFF BIT(11)
+
+/* DCD module, the offset is 0x800 */
+#define DCD_CONTROL 0x800
+#define DCD_CLOCK (DCD_CONTROL + 0x4)
+#define DCD_STATUS (DCD_CONTROL + 0x8)
+
+#define DCD_CONTROL_SR BIT(25)
+#define DCD_CONTROL_START BIT(24)
+#define DCD_CONTROL_BC12 BIT(17)
+#define DCD_CONTROL_IE BIT(16)
+#define DCD_CONTROL_IF BIT(8)
+#define DCD_CONTROL_IACK BIT(0)
+
+#define DCD_CLOCK_MHZ BIT(0)
+
+#define DCD_STATUS_ACTIVE BIT(22)
+#define DCD_STATUS_TO BIT(21)
+#define DCD_STATUS_ERR BIT(20)
+#define DCD_STATUS_SEQ_STAT (BIT(18) | BIT(19))
+#define DCD_CHG_PORT BIT(19)
+#define DCD_CHG_DET (BIT(18) | BIT(19))
+#define DCD_CHG_DPIN BIT(18)
+#define DCD_STATUS_SEQ_RES (BIT(16) | BIT(17))
+#define DCD_SDP_PORT BIT(16)
+#define DCD_CDP_PORT BIT(17)
+#define DCD_DCP_PORT (BIT(16) | BIT(17))
+/* System Integration Module (SIM) Registers */
+#define SIM_GPR1 0x30
+
+#define USB_PHY_VLLS_WAKEUP_EN BIT(0)
+
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
/* Do disconnection between PHY and controller without vbus */
@@ -126,6 +196,19 @@
#define MXS_PHY_TX_D_CAL_MIN 79
#define MXS_PHY_TX_D_CAL_MAX 119
+/*
+ * At some versions, the PHY2's clock is controlled by hardware directly,
+ * eg, according to PHY's suspend status. In these PHYs, we only need to
+ * open the clock at the initialization and close it at its shutdown routine.
+ * It will be benefit for remote wakeup case which needs to send resume
+ * signal as soon as possible, and in this case, the resume signal can be sent
+ * out without software interfere.
+ */
+#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4)
+
+/* The MXS PHYs which have DCD module for charger detection */
+#define MXS_PHY_HAS_DCD BIT(5)
+
struct mxs_phy_data {
unsigned int flags;
};
@@ -137,12 +220,14 @@ static const struct mxs_phy_data imx23_phy_data = {
static const struct mxs_phy_data imx6q_phy_data = {
.flags = MXS_PHY_SENDING_SOF_TOO_FAST |
MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
- MXS_PHY_NEED_IP_FIX,
+ MXS_PHY_NEED_IP_FIX |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data imx6sl_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
- MXS_PHY_NEED_IP_FIX,
+ MXS_PHY_NEED_IP_FIX |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data vf610_phy_data = {
@@ -151,14 +236,22 @@ static const struct mxs_phy_data vf610_phy_data = {
};
static const struct mxs_phy_data imx6sx_phy_data = {
- .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data imx6ul_phy_data = {
- .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
+};
+
+static const struct mxs_phy_data imx7ulp_phy_data = {
+ .flags = MXS_PHY_HAS_DCD,
};
static const struct of_device_id mxs_phy_dt_ids[] = {
+ { .compatible = "fsl,imx7ulp-usbphy", .data = &imx7ulp_phy_data, },
+ { .compatible = "fsl,imx6ul-usbphy", .data = &imx6sx_phy_data, },
{ .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, },
{ .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
{ .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
@@ -174,9 +267,14 @@ struct mxs_phy {
struct clk *clk;
const struct mxs_phy_data *data;
struct regmap *regmap_anatop;
+ struct regmap *regmap_sim;
int port_id;
u32 tx_reg_set;
u32 tx_reg_mask;
+ struct regulator *phy_3p0;
+ bool hardware_control_phy2_clk;
+ enum usb_current_mode mode;
+ unsigned long clk_rate;
};
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
@@ -189,6 +287,16 @@ static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy)
return mxs_phy->data == &imx6sl_phy_data;
}
+static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx6ul_phy_data;
+}
+
+static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx7ulp_phy_data;
+}
+
/*
* PHY needs some 32K cycles to switch from 32K clock to
* bus (such as AHB/AXI, etc) clock.
@@ -212,14 +320,69 @@ static void mxs_phy_tx_init(struct mxs_phy *mxs_phy)
}
}
+static int wait_for_pll_lock(const void __iomem *base)
+{
+ int loop_count = 100;
+
+ /* Wait for PLL to lock */
+ do {
+ if (readl(base + HW_USBPHY_PLL_SIC) & BM_USBPHY_PLL_LOCK)
+ break;
+ usleep_range(100, 150);
+ } while (loop_count-- > 0);
+
+ return readl(base + HW_USBPHY_PLL_SIC) & BM_USBPHY_PLL_LOCK
+ ? 0 : -ETIMEDOUT;
+}
+
+static int mxs_phy_pll_enable(void __iomem *base, bool enable)
+{
+ int ret = 0;
+
+ if (enable) {
+ writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_SET);
+ writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_SET);
+ ret = wait_for_pll_lock(base);
+ if (ret)
+ return ret;
+ writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
+ HW_USBPHY_PLL_SIC_SET);
+ } else {
+ writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
+ HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_SET);
+ writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_CLR);
+ }
+
+ return ret;
+}
+
static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
{
int ret;
void __iomem *base = mxs_phy->phy.io_priv;
+ if (is_imx7ulp_phy(mxs_phy)) {
+ ret = mxs_phy_pll_enable(base, true);
+ if (ret)
+ return ret;
+ }
+
ret = stmp_reset_block(base + HW_USBPHY_CTRL);
if (ret)
- return ret;
+ goto disable_pll;
+
+ if (mxs_phy->phy_3p0) {
+ ret = regulator_enable(mxs_phy->phy_3p0);
+ if (ret) {
+ dev_err(mxs_phy->phy.dev,
+ "Failed to enable 3p0 regulator, ret=%d\n",
+ ret);
+ goto disable_pll;
+ }
+ }
/* Power up the PHY */
writel(0, base + HW_USBPHY_PWD);
@@ -244,6 +407,11 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
mxs_phy_tx_init(mxs_phy);
return 0;
+
+disable_pll:
+ if (is_imx7ulp_phy(mxs_phy))
+ mxs_phy_pll_enable(base, false);
+ return ret;
}
/* Return true if the vbus is there */
@@ -301,21 +469,10 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
usleep_range(500, 1000);
}
-static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy)
-{
- void __iomem *base = mxs_phy->phy.io_priv;
- u32 phyctrl = readl(base + HW_USBPHY_CTRL);
-
- if (IS_ENABLED(CONFIG_USB_OTG) &&
- !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE))
- return true;
-
- return false;
-}
-
static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
{
bool vbus_is_on = false;
+ enum usb_phy_events last_event = mxs_phy->phy.last_event;
/* If the SoCs don't need to disconnect line without vbus, quit */
if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
@@ -327,7 +484,8 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
- if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy))
+ if (on && ((!vbus_is_on && mxs_phy->mode != USB_MODE_HOST) ||
+ (last_event == USB_EVENT_VBUS)))
__mxs_phy_disconnect_line(mxs_phy, true);
else
__mxs_phy_disconnect_line(mxs_phy, false);
@@ -365,6 +523,9 @@ static void mxs_phy_shutdown(struct usb_phy *phy)
writel(BM_USBPHY_CTRL_CLKGATE,
phy->io_priv + HW_USBPHY_CTRL_SET);
+ if (mxs_phy->phy_3p0)
+ regulator_disable(mxs_phy->phy_3p0);
+
clk_disable_unprepare(mxs_phy->clk);
}
@@ -418,14 +579,49 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend)
} else {
writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
}
+
+ /*
+ * USB2 PLL use ring VCO, when the PLL power up, the ring
+ * VCO’s supply also ramp up. There is a possibility that
+ * the ring VCO start oscillation at multi nodes in this
+ * phase, especially for VCO which has many stages, then
+ * the multiwave will be kept until PLL power down. the bit
+ * hold_ring_off can force the VCO in one determined state
+ * to avoid the multiwave issue when VCO supply start ramp
+ * up.
+ */
+ if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop)
+ regmap_write(mxs_phy->regmap_anatop,
+ ANADIG_PLL_USB2_SET,
+ BM_ANADIG_PLL_USB2_HOLD_RING_OFF);
+
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_SET);
- clk_disable_unprepare(mxs_phy->clk);
+ if (!(mxs_phy->port_id == 1 &&
+ mxs_phy->hardware_control_phy2_clk))
+ clk_disable_unprepare(mxs_phy->clk);
} else {
mxs_phy_clock_switch_delay();
- ret = clk_prepare_enable(mxs_phy->clk);
- if (ret)
- return ret;
+ if (!(mxs_phy->port_id == 1 &&
+ mxs_phy->hardware_control_phy2_clk)) {
+ ret = clk_prepare_enable(mxs_phy->clk);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Per IC design's requirement, hold_ring_off bit can be
+ * cleared 25us after PLL power up and 25us before any USB
+ * TX/RX.
+ */
+ if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) {
+ udelay(25);
+ regmap_write(mxs_phy->regmap_anatop,
+ ANADIG_PLL_USB2_CLR,
+ BM_ANADIG_PLL_USB2_HOLD_RING_OFF);
+ udelay(25);
+ }
+
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_CLR);
writel(0, x->io_priv + HW_USBPHY_PWD);
@@ -479,6 +675,307 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy,
return 0;
}
+static int mxs_phy_on_suspend(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+
+ dev_dbg(phy->dev, "%s device has suspended\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ /* delay 4ms to wait bus entering idle */
+ usleep_range(4000, 5000);
+
+ if (mxs_phy->data->flags & MXS_PHY_ABNORMAL_IN_SUSPEND) {
+ writel_relaxed(0xffffffff, phy->io_priv + HW_USBPHY_PWD);
+ writel_relaxed(0, phy->io_priv + HW_USBPHY_PWD);
+ }
+
+ if (speed == USB_SPEED_HIGH)
+ writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ phy->io_priv + HW_USBPHY_CTRL_CLR);
+
+ return 0;
+}
+
+#define MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT 100
+static int mxs_charger_data_contact_detect(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ int i, stable_contact_count = 0;
+ u32 val;
+
+ /* Check if vbus is valid */
+ regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val);
+ if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
+ dev_err(x->phy.dev, "vbus is not valid\n");
+ return -EINVAL;
+ }
+
+ /* Enable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+ ANADIG_USB1_CHRG_DETECT_EN_B);
+ /*
+ * - Do not check whether a charger is connected to the USB port
+ * - Check whether the USB plug has been in contact with each other
+ */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ /* Check if plug is connected */
+ for (i = 0; i < MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT; i++) {
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
+ stable_contact_count++;
+ if (stable_contact_count > 5)
+ /* Data pin makes contact */
+ break;
+ else
+ usleep_range(5000, 10000);
+ } else {
+ stable_contact_count = 0;
+ usleep_range(5000, 6000);
+ }
+ }
+
+ if (i == MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT) {
+ dev_err(x->phy.dev,
+ "Data pin can't make good contact.\n");
+ /* Disable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+ return -ENXIO;
+ }
+ return 0;
+}
+
+/*
+ * The resume signal must be finished here.
+ */
+static int mxs_phy_on_resume(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ dev_dbg(phy->dev, "%s device has resumed\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ if (speed == USB_SPEED_HIGH) {
+ /* Make sure the device has switched to High-Speed mode */
+ udelay(500);
+ writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ phy->io_priv + HW_USBPHY_CTRL_SET);
+ }
+
+ return 0;
+}
+
+/*
+ * Set the usb current role for phy.
+ */
+static int mxs_phy_set_mode(struct usb_phy *phy,
+ enum usb_current_mode mode)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+
+ mxs_phy->mode = mode;
+
+ return 0;
+}
+
+static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ enum usb_charger_type chgr_type = UNKNOWN_TYPE;
+ u32 val;
+
+ /*
+ * - Do check whether a charger is connected to the USB port
+ * - Do not Check whether the USB plug has been in contact with
+ * each other
+ */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+ ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ msleep(100);
+
+ /* Check if it is a charger */
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
+ chgr_type = SDP_TYPE;
+ dev_dbg(x->phy.dev, "It is a stardard downstream port\n");
+ }
+
+ /* Disable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ return chgr_type;
+}
+
+/*
+ * It must be called after DP is pulled up, which is used to
+ * differentiate DCP and CDP.
+ */
+static enum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ int val;
+
+ msleep(80);
+
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
+ dev_dbg(x->phy.dev, "It is a dedicate charging port\n");
+ return DCP_TYPE;
+ } else {
+ dev_dbg(x->phy.dev, "It is a charging downstream port\n");
+ return CDP_TYPE;
+ }
+}
+
+static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+ struct regmap *regmap = mxs_phy->regmap_anatop;
+ void __iomem *base = phy->io_priv;
+ enum usb_charger_type chgr_type = UNKNOWN_TYPE;
+
+ if (mxs_charger_data_contact_detect(mxs_phy))
+ return chgr_type;
+
+ chgr_type = mxs_charger_primary_detection(mxs_phy);
+
+ if (chgr_type != SDP_TYPE) {
+ /* Pull up DP via test */
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_CLR);
+ regmap_write(regmap, ANADIG_USB1_LOOPBACK_SET,
+ ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
+
+ chgr_type = mxs_charger_secondary_detection(mxs_phy);
+
+ /* Stop the test */
+ regmap_write(regmap, ANADIG_USB1_LOOPBACK_CLR,
+ ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_SET);
+ }
+
+ return chgr_type;
+}
+
+static int mxs_phy_dcd_start(struct mxs_phy *mxs_phy)
+{
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 value;
+
+ value = readl(base + DCD_CONTROL);
+ writel(value | DCD_CONTROL_SR, base + DCD_CONTROL);
+
+ if (!mxs_phy->clk_rate)
+ return -EINVAL;
+
+ value = readl(base + DCD_CONTROL);
+ writel(((mxs_phy->clk_rate / 1000000) << 2) | DCD_CLOCK_MHZ,
+ base + DCD_CLOCK);
+
+ value = readl(base + DCD_CONTROL);
+ value &= ~DCD_CONTROL_IE;
+ writel(value | DCD_CONTROL_BC12, base + DCD_CONTROL);
+
+ value = readl(base + DCD_CONTROL);
+ writel(value | DCD_CONTROL_START, base + DCD_CONTROL);
+
+ return 0;
+}
+
+
+#define DCD_CHARGING_DURTION 1000 /* One second according to BC 1.2 */
+static enum usb_charger_type mxs_phy_dcd_flow(struct usb_phy *phy)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 value;
+ int i = 0;
+ enum usb_charger_type chgr_type;
+
+ if (mxs_phy_dcd_start(mxs_phy))
+ return UNKNOWN_TYPE;
+
+ while (i++ <= (DCD_CHARGING_DURTION / 50)) {
+ value = readl(base + DCD_CONTROL);
+ if (value & DCD_CONTROL_IF) {
+ value = readl(base + DCD_STATUS);
+ if (value & DCD_STATUS_ACTIVE) {
+ dev_err(phy->dev, "still detecting\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if (value & DCD_STATUS_TO) {
+ dev_err(phy->dev, "detect timeout\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if (value & DCD_STATUS_ERR) {
+ dev_err(phy->dev, "detect error\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_STAT) <= DCD_CHG_DPIN) {
+ dev_err(phy->dev, "error occurs\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ /* SDP */
+ if (((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_PORT) &&
+ ((value & DCD_STATUS_SEQ_RES)
+ == DCD_SDP_PORT)) {
+ dev_dbg(phy->dev, "SDP\n");
+ chgr_type = SDP_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_DET) {
+ if ((value & DCD_STATUS_SEQ_RES) ==
+ DCD_CDP_PORT) {
+ dev_dbg(phy->dev, "CDP\n");
+ chgr_type = CDP_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_RES) ==
+ DCD_DCP_PORT) {
+ dev_dbg(phy->dev, "DCP\n");
+ chgr_type = DCP_TYPE;
+ break;
+ }
+ }
+ dev_err(phy->dev, "unknown error occurs\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+ msleep(50);
+ }
+
+ if (i > 20) {
+ dev_err(phy->dev, "charger detecting timeout\n");
+ chgr_type = UNKNOWN_TYPE;
+ }
+
+ /* disable dcd module */
+ readl(base + DCD_STATUS);
+ writel(DCD_CONTROL_IACK, base + DCD_CONTROL);
+ writel(DCD_CONTROL_SR, base + DCD_CONTROL);
+ return chgr_type;
+}
+
static int mxs_phy_probe(struct platform_device *pdev)
{
struct resource *res;
@@ -510,6 +1007,7 @@ static int mxs_phy_probe(struct platform_device *pdev)
if (!mxs_phy)
return -ENOMEM;
+ mxs_phy->clk_rate = clk_get_rate(clk);
/* Some SoCs don't have anatop registers */
if (of_get_property(np, "fsl,anatop", NULL)) {
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
@@ -521,6 +1019,17 @@ static int mxs_phy_probe(struct platform_device *pdev)
}
}
+ /* Currently, only imx7ulp has SIM module */
+ if (of_get_property(np, "nxp,sim", NULL)) {
+ mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle
+ (np, "nxp,sim");
+ if (IS_ERR(mxs_phy->regmap_sim)) {
+ dev_dbg(&pdev->dev,
+ "failed to find regmap for sim\n");
+ return PTR_ERR(mxs_phy->regmap_sim);
+ }
+ }
+
/* Precompute which bits of the TX register are to be updated, if any */
if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) &&
val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) {
@@ -556,6 +1065,8 @@ static int mxs_phy_probe(struct platform_device *pdev)
if (ret < 0)
dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
mxs_phy->port_id = ret;
+ mxs_phy->clk = clk;
+ mxs_phy->data = of_id->data;
mxs_phy->phy.io_priv = base;
mxs_phy->phy.dev = &pdev->dev;
@@ -567,9 +1078,33 @@ static int mxs_phy_probe(struct platform_device *pdev)
mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect;
mxs_phy->phy.type = USB_PHY_TYPE_USB2;
mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup;
+ mxs_phy->phy.set_mode = mxs_phy_set_mode;
+ if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) {
+ mxs_phy->phy.notify_suspend = mxs_phy_on_suspend;
+ mxs_phy->phy.notify_resume = mxs_phy_on_resume;
+ }
- mxs_phy->clk = clk;
- mxs_phy->data = of_id->data;
+ if (mxs_phy->data->flags & MXS_PHY_HAS_DCD)
+ mxs_phy->phy.charger_detect = mxs_phy_dcd_flow;
+ else
+ mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
+
+ mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0");
+ if (PTR_ERR(mxs_phy->phy_3p0) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV) {
+ /* not exist */
+ mxs_phy->phy_3p0 = NULL;
+ } else if (IS_ERR(mxs_phy->phy_3p0)) {
+ dev_err(&pdev->dev, "Getting regulator error: %ld\n",
+ PTR_ERR(mxs_phy->phy_3p0));
+ return PTR_ERR(mxs_phy->phy_3p0);
+ }
+ if (mxs_phy->phy_3p0)
+ regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000);
+
+ if (mxs_phy->data->flags & MXS_PHY_HARDWARE_CONTROL_PHY2_CLK)
+ mxs_phy->hardware_control_phy2_clk = true;
platform_set_drvdata(pdev, mxs_phy);
@@ -588,28 +1123,58 @@ static int mxs_phy_remove(struct platform_device *pdev)
}
#ifdef CONFIG_PM_SLEEP
+static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on)
+{
+ u32 mask = USB_PHY_VLLS_WAKEUP_EN;
+
+ /* If the SoCs don't have SIM, quit */
+ if (!mxs_phy->regmap_sim)
+ return;
+
+ if (on) {
+ regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask);
+ udelay(500);
+ } else {
+ regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0);
+ }
+}
+
static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
{
- unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
+ unsigned int reg;
+ u32 value;
/* If the SoCs don't have anatop, quit */
if (!mxs_phy->regmap_anatop)
return;
- if (is_imx6q_phy(mxs_phy))
+ if (is_imx6q_phy(mxs_phy)) {
+ reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
regmap_write(mxs_phy->regmap_anatop, reg,
BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
- else if (is_imx6sl_phy(mxs_phy))
+ } else if (is_imx6sl_phy(mxs_phy)) {
+ reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
regmap_write(mxs_phy->regmap_anatop,
reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
+ } else if (is_imx6ul_phy(mxs_phy)) {
+ reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR;
+ value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG |
+ BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP;
+ if (mxs_phy_get_vbus_status(mxs_phy) && on)
+ regmap_write(mxs_phy->regmap_anatop, reg, value);
+ else if (!on)
+ regmap_write(mxs_phy->regmap_anatop, reg, value);
+ }
}
static int mxs_phy_system_suspend(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
- if (device_may_wakeup(dev))
+ if (device_may_wakeup(dev)) {
mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
+ mxs_phy_wakeup_enable(mxs_phy, true);
+ }
return 0;
}
@@ -618,8 +1183,10 @@ static int mxs_phy_system_resume(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
- if (device_may_wakeup(dev))
+ if (device_may_wakeup(dev)) {
mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
+ mxs_phy_wakeup_enable(mxs_phy, false);
+ }
return 0;
}
diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c
index 89f4ac4cd93e..26c1616f36eb 100644
--- a/drivers/usb/phy/phy.c
+++ b/drivers/usb/phy/phy.c
@@ -327,6 +327,14 @@ static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
return *phy == match_data;
}
+static void usb_charger_init(struct usb_phy *usb_phy)
+{
+ usb_phy->chg_type = UNKNOWN_TYPE;
+ usb_phy->chg_state = USB_CHARGER_DEFAULT;
+ usb_phy_set_default_current(usb_phy);
+ INIT_WORK(&usb_phy->chg_work, usb_phy_notify_charger_work);
+}
+
static int usb_add_extcon(struct usb_phy *x)
{
int ret;
@@ -410,10 +418,6 @@ static int usb_add_extcon(struct usb_phy *x)
}
}
- usb_phy_set_default_current(x);
- INIT_WORK(&x->chg_work, usb_phy_notify_charger_work);
- x->chg_type = UNKNOWN_TYPE;
- x->chg_state = USB_CHARGER_DEFAULT;
if (x->type_nb.notifier_call)
__usb_phy_get_charger_type(x);
@@ -708,6 +712,7 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type)
return -EINVAL;
}
+ usb_charger_init(x);
ret = usb_add_extcon(x);
if (ret)
return ret;
@@ -753,6 +758,7 @@ int usb_add_phy_dev(struct usb_phy *x)
return -EINVAL;
}
+ usb_charger_init(x);
ret = usb_add_extcon(x);
if (ret)
return ret;
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
index 24e355ba109d..5b8f5ac0bdd5 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/typec.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/of.h>
#include <linux/usb/typec.h>
struct typec_mode {
@@ -799,6 +800,12 @@ static const char * const typec_port_types[] = {
[TYPEC_PORT_DRP] = "dual",
};
+static const char *const typec_port_types_dt[] = {
+ [TYPEC_PORT_DFP] = "dfp",
+ [TYPEC_PORT_UFP] = "ufp",
+ [TYPEC_PORT_DRP] = "drp",
+};
+
static const char * const typec_port_types_drp[] = {
[TYPEC_PORT_DFP] = "dual [source] sink",
[TYPEC_PORT_UFP] = "dual source [sink]",
@@ -1150,6 +1157,47 @@ static const struct device_type typec_port_dev_type = {
.release = typec_release,
};
+static enum typec_port_type typec_get_port_type_from_string(const char *str)
+{
+ int ret;
+
+ ret = match_string(typec_port_types_dt, ARRAY_SIZE(typec_port_types_dt), str);
+ return (ret < 0) ? TYPEC_PORT_TYPE_UNKNOWN : ret;
+}
+
+enum typec_port_type typec_get_port_type(struct device *dev)
+{
+ const char *port_type;
+ int err;
+
+ err = device_property_read_string(dev, "port-type", &port_type);
+ if (err < 0)
+ return TYPEC_PORT_TYPE_UNKNOWN;
+
+ return typec_get_port_type_from_string(port_type);
+}
+EXPORT_SYMBOL_GPL(typec_get_port_type);
+
+static enum typec_role typec_get_power_role_from_string(const char *str)
+{
+ int ret;
+
+ ret = match_string(typec_roles, ARRAY_SIZE(typec_roles), str);
+ return (ret < 0) ? TYPEC_ROLE_UNKNOWN : ret;
+}
+
+enum typec_role typec_get_power_role(struct device *dev)
+{
+ const char *power_role;
+ int err;
+
+ err = device_property_read_string(dev, "default-role", &power_role);
+ if (err < 0)
+ return TYPEC_ROLE_UNKNOWN;
+
+ return typec_get_power_role_from_string(power_role);
+}
+EXPORT_SYMBOL_GPL(typec_get_power_role);
/* --------------------------------------- */
/* Driver callbacks to report role updates */