summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/Kconfig4
-rw-r--r--drivers/usb/Makefile3
-rw-r--r--drivers/usb/cdns3/Kconfig26
-rw-r--r--drivers/usb/cdns3/Makefile5
-rw-r--r--drivers/usb/cdns3/cdns3-nxp-reg-def.h172
-rw-r--r--drivers/usb/cdns3/core.c1014
-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.c2526
-rw-r--r--drivers/usb/cdns3/gadget.h225
-rw-r--r--drivers/usb/cdns3/host-export.h43
-rw-r--r--drivers/usb/cdns3/host.c259
-rw-r--r--drivers/usb/cdns3/io.h35
-rw-r--r--drivers/usb/chipidea/ci.h44
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c407
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.h45
-rw-r--r--drivers/usb/chipidea/ci_hdrc_msm.c4
-rw-r--r--drivers/usb/chipidea/core.c328
-rw-r--r--drivers/usb/chipidea/host.c335
-rw-r--r--drivers/usb/chipidea/host.h8
-rw-r--r--drivers/usb/chipidea/otg.c101
-rw-r--r--drivers/usb/chipidea/otg.h13
-rw-r--r--drivers/usb/chipidea/otg_fsm.c220
-rw-r--r--drivers/usb/chipidea/otg_fsm.h28
-rw-r--r--drivers/usb/chipidea/udc.c294
-rw-r--r--drivers/usb/chipidea/udc.h13
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c738
-rw-r--r--drivers/usb/common/usb-otg-fsm.c54
-rw-r--r--drivers/usb/core/buffer.c12
-rw-r--r--drivers/usb/core/hcd.c216
-rw-r--r--drivers/usb/core/hub.c40
-rw-r--r--drivers/usb/core/otg_whitelist.h88
-rw-r--r--drivers/usb/core/usb.c18
-rw-r--r--drivers/usb/dwc3/Kconfig1
-rw-r--r--drivers/usb/dwc3/Makefile4
-rw-r--r--drivers/usb/dwc3/core.c183
-rw-r--r--drivers/usb/dwc3/core.h36
-rw-r--r--drivers/usb/dwc3/debugfs.c14
-rw-r--r--drivers/usb/dwc3/drd.c85
-rw-r--r--drivers/usb/dwc3/dwc3-of-simple.c1
-rw-r--r--drivers/usb/dwc3/gadget.c180
-rw-r--r--drivers/usb/gadget/Kconfig6
-rw-r--r--drivers/usb/gadget/function/f_mass_storage.c63
-rw-r--r--drivers/usb/gadget/function/fsl_updater.c647
-rw-r--r--drivers/usb/gadget/function/fsl_updater.h152
-rw-r--r--drivers/usb/gadget/udc/core.c1
-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-mem.c12
-rw-r--r--drivers/usb/host/ehci-q.c14
-rw-r--r--drivers/usb/host/xhci-dbg.c6
-rw-r--r--drivers/usb/host/xhci-hub.c162
-rw-r--r--drivers/usb/host/xhci-mem.c32
-rw-r--r--drivers/usb/host/xhci-plat.c93
-rw-r--r--drivers/usb/host/xhci-ring.c138
-rw-r--r--drivers/usb/host/xhci.c75
-rw-r--r--drivers/usb/host/xhci.h18
-rw-r--r--drivers/usb/misc/ehset.c25
-rw-r--r--drivers/usb/phy/Kconfig2
-rw-r--r--drivers/usb/phy/phy-generic.c10
-rw-r--r--drivers/usb/phy/phy-mxs-usb.c289
-rw-r--r--drivers/usb/typec/Kconfig7
-rw-r--r--drivers/usb/typec/Makefile1
-rw-r--r--drivers/usb/typec/typec.c1310
66 files changed, 11497 insertions, 722 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 0103f777b97a..c055561e84aa 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -113,6 +113,8 @@ source "drivers/usb/chipidea/Kconfig"
source "drivers/usb/isp1760/Kconfig"
+source "drivers/usb/cdns3/Kconfig"
+
comment "USB port drivers"
if USB
@@ -158,6 +160,8 @@ source "drivers/usb/phy/Kconfig"
source "drivers/usb/gadget/Kconfig"
+source "drivers/usb/typec/Kconfig"
+
config USB_LED_TRIG
bool "USB LED Triggers"
depends on LEDS_CLASS && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index dca78565eb55..cbdfe47f011c 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -10,6 +10,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/
@@ -61,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/
+
+obj-$(CONFIG_TYPEC) += typec/
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..886632265e98
--- /dev/null
+++ b/drivers/usb/cdns3/cdns3-nxp-reg-def.h
@@ -0,0 +1,172 @@
+/**
+ * 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_D0 (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..df68728f1de3
--- /dev/null
+++ b/drivers/usb/cdns3/core.c
@@ -0,0 +1,1014 @@
+/**
+ * core.c - Cadence USB3 DRD Controller Core file
+ *
+ * Copyright 2017 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/usb/of.h>
+#include <linux/usb/phy.h>
+#include <linux/extcon.h>
+#include <linux/pm_runtime.h>
+
+#include "cdns3-nxp-reg-def.h"
+#include "core.h"
+#include "host-export.h"
+#include "gadget-export.h"
+
+static void cdns3_usb_phy_init(void __iomem *regs)
+{
+ u32 value;
+
+ pr_debug("begin of %s\n", __func__);
+
+ writel(0x0830, regs + PHY_PMA_CMN_CTRL1);
+ writel(0x10, regs + TB_ADDR_CMN_DIAG_HSCLK_SEL);
+ writel(0x00F0, regs + TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR);
+ writel(0x0018, regs + TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR);
+ writel(0x00D0, regs + TB_ADDR_CMN_PLL0_INTDIV);
+ writel(0x4aaa, regs + TB_ADDR_CMN_PLL0_FRACDIV);
+ writel(0x0034, regs + TB_ADDR_CMN_PLL0_HIGH_THR);
+ writel(0x1ee, regs + TB_ADDR_CMN_PLL0_SS_CTRL1);
+ writel(0x7F03, regs + TB_ADDR_CMN_PLL0_SS_CTRL2);
+ writel(0x0020, regs + TB_ADDR_CMN_PLL0_DSM_DIAG);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_OVRD);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD);
+ writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD);
+ writel(0x0007, regs + TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE);
+ writel(0x0027, regs + TB_ADDR_CMN_DIAG_PLL0_CP_TUNE);
+ writel(0x0008, regs + TB_ADDR_CMN_DIAG_PLL0_LF_PROG);
+ writel(0x0022, regs + TB_ADDR_CMN_DIAG_PLL0_TEST_MODE);
+ writel(0x000a, regs + TB_ADDR_CMN_PSM_CLK_CTRL);
+ writel(0x139, regs + TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR);
+ writel(0xbefc, regs + TB_ADDR_XCVR_PSM_RCTRL);
+
+ writel(0x7799, regs + TB_ADDR_TX_PSC_A0);
+ writel(0x7798, regs + TB_ADDR_TX_PSC_A1);
+ writel(0x509b, regs + TB_ADDR_TX_PSC_A2);
+ writel(0x3, regs + TB_ADDR_TX_DIAG_ECTRL_OVRD);
+ writel(0x509b, regs + TB_ADDR_TX_PSC_A3);
+ writel(0x2090, regs + TB_ADDR_TX_PSC_CAL);
+ writel(0x2090, regs + TB_ADDR_TX_PSC_RDY);
+
+ writel(0xA6FD, regs + TB_ADDR_RX_PSC_A0);
+ writel(0xA6FD, regs + TB_ADDR_RX_PSC_A1);
+ writel(0xA410, regs + TB_ADDR_RX_PSC_A2);
+ writel(0x2410, regs + TB_ADDR_RX_PSC_A3);
+
+ writel(0x23FF, regs + TB_ADDR_RX_PSC_CAL);
+ writel(0x2010, regs + TB_ADDR_RX_PSC_RDY);
+
+ writel(0x0020, regs + TB_ADDR_TX_TXCC_MGNLS_MULT_000);
+ writel(0x00ff, regs + TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY);
+ writel(0x0002, regs + TB_ADDR_RX_SLC_CU_ITER_TMR);
+ writel(0x0013, regs + TB_ADDR_RX_SIGDET_HL_FILT_TMR);
+ writel(0x0000, regs + TB_ADDR_RX_SAMP_DAC_CTRL);
+ writel(0x1004, regs + TB_ADDR_RX_DIAG_SIGDET_TUNE);
+ writel(0x4041, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE2);
+ writel(0x0480, regs + TB_ADDR_RX_DIAG_BS_TM);
+ writel(0x8006, regs + TB_ADDR_RX_DIAG_DFE_CTRL1);
+ writel(0x003f, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM4);
+ writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_E_TRIM0);
+ writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_IQ_TRIM0);
+ writel(0x0000, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM6);
+ writel(0x8000, regs + TB_ADDR_RX_DIAG_RXFE_TM3);
+ writel(0x0003, regs + TB_ADDR_RX_DIAG_RXFE_TM4);
+ writel(0x2408, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE);
+ writel(0x05ca, regs + TB_ADDR_RX_DIAG_DFE_CTRL3);
+ writel(0x0258, regs + TB_ADDR_RX_DIAG_SC2C_DELAY);
+ writel(0x1fff, regs + TB_ADDR_RX_REE_VGA_GAIN_NODFE);
+
+ writel(0x02c6, regs + TB_ADDR_XCVR_PSM_CAL_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0BYP_TMR);
+ writel(0x02c6, regs + TB_ADDR_XCVR_PSM_A0IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A1IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A2IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A3IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A4IN_TMR);
+ writel(0x0010, regs + TB_ADDR_XCVR_PSM_A5IN_TMR);
+
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A1OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A2OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A3OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A4OUT_TMR);
+ writel(0x0002, regs + TB_ADDR_XCVR_PSM_A5OUT_TMR);
+
+ /* Change rx detect parameter */
+ writel(0x960, regs + TB_ADDR_TX_RCVDET_EN_TMR);
+ writel(0x01e0, regs + TB_ADDR_TX_RCVDET_ST_TMR);
+ writel(0x0090, regs + TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR);
+
+ /* RXDET_IN_P3_32KHZ, Receiver detect slow clock enable */
+ value = readl(regs + TB_ADDR_TX_RCVDETSC_CTRL);
+ value |= RXDET_IN_P3_32KHZ;
+ writel(value, regs + TB_ADDR_TX_RCVDETSC_CTRL);
+
+ udelay(10);
+
+ pr_debug("end of %s\n", __func__);
+}
+
+static void cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role)
+{
+ u32 value;
+ int timeout_us = 100000;
+
+ if (role == CDNS3_ROLE_END)
+ return;
+
+ /* Wait clk value */
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ writel(value, cdns->none_core_regs + USB3_SSPHY_STATUS);
+ udelay(1);
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ while ((value & 0xf0000000) != 0xf0000000 && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
+ dev_dbg(cdns->dev, "clkvld:0x%x\n", value);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait clkvld timeout\n");
+
+ /* Set all Reset bits */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value |= ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ udelay(1);
+
+ if (role == CDNS3_ROLE_HOST) {
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value = (value & ~MODE_STRAP_MASK) | HOST_MODE | OC_DISABLE;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~PHYAHB_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ mdelay(1);
+ cdns3_usb_phy_init(cdns->phy_regs);
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ mdelay(1);
+
+ value = readl(cdns->none_core_regs + USB3_INT_REG);
+ value |= HOST_INT1_EN;
+ writel(value, cdns->none_core_regs + USB3_INT_REG);
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ dev_dbg(cdns->dev, "wait xhci_power_on_ready\n");
+
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
+
+ mdelay(1);
+
+ dev_dbg(cdns->dev, "switch to host role successfully\n");
+ } else { /* gadget mode */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value = (value & ~MODE_STRAP_MASK) | DEV_MODE;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~PHYAHB_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ cdns3_usb_phy_init(cdns->phy_regs);
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ value = readl(cdns->none_core_regs + USB3_INT_REG);
+ value |= DEV_INT_EN;
+ writel(value, cdns->none_core_regs + USB3_INT_REG);
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value &= ~ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+
+ dev_dbg(cdns->dev, "wait gadget_power_on_ready\n");
+
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & DEV_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev,
+ "wait gadget_power_on_ready timeout\n");
+
+ mdelay(1);
+
+ dev_dbg(cdns->dev, "switch to gadget role successfully\n");
+ }
+}
+
+static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
+{
+ if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
+ if (extcon_get_state(cdns->extcon, EXTCON_USB_HOST))
+ return CDNS3_ROLE_HOST;
+ else if (extcon_get_state(cdns->extcon, EXTCON_USB))
+ return CDNS3_ROLE_GADGET;
+ else
+ return CDNS3_ROLE_END;
+ } else {
+ return cdns->roles[CDNS3_ROLE_HOST]
+ ? CDNS3_ROLE_HOST
+ : CDNS3_ROLE_GADGET;
+ }
+}
+
+/**
+ * cdns3_core_init_role - initialize role of operation
+ * @cdns: Pointer to cdns3 structure
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_core_init_role(struct cdns3 *cdns)
+{
+ struct device *dev = cdns->dev;
+ enum usb_dr_mode dr_mode = usb_get_dr_mode(dev);
+
+ cdns->role = CDNS3_ROLE_END;
+ if (dr_mode == USB_DR_MODE_UNKNOWN)
+ dr_mode = USB_DR_MODE_OTG;
+
+ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
+ if (cdns3_host_init(cdns))
+ dev_info(dev, "doesn't support host\n");
+ }
+
+ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
+ if (cdns3_gadget_init(cdns))
+ dev_info(dev, "doesn't support gadget\n");
+ }
+
+ if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
+ dev_err(dev, "no supported roles\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * cdns3_irq - interrupt handler for cdns3 core device
+ *
+ * @irq: irq number for cdns3 core device
+ * @data: structure of cdns3
+ *
+ * Returns IRQ_HANDLED or IRQ_NONE
+ */
+static irqreturn_t cdns3_irq(int irq, void *data)
+{
+ struct cdns3 *cdns = data;
+ irqreturn_t ret = IRQ_NONE;
+
+ if (cdns->in_lpm) {
+ disable_irq_nosync(cdns->irq);
+ cdns->wakeup_int = true;
+ pm_runtime_get(cdns->dev);
+ return IRQ_HANDLED;
+ }
+
+ /* Handle device/host interrupt */
+ if (cdns->role != CDNS3_ROLE_END)
+ ret = cdns3_role(cdns)->irq(cdns);
+
+ return ret;
+}
+
+static int cdns3_get_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret = 0;
+
+ cdns->cdns3_clks[0] = devm_clk_get(dev, "usb3_lpm_clk");
+ if (IS_ERR(cdns->cdns3_clks[0])) {
+ ret = PTR_ERR(cdns->cdns3_clks[0]);
+ dev_err(dev, "Failed to get usb3_lpm_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[1] = devm_clk_get(dev, "usb3_bus_clk");
+ if (IS_ERR(cdns->cdns3_clks[1])) {
+ ret = PTR_ERR(cdns->cdns3_clks[1]);
+ dev_err(dev, "Failed to get usb3_bus_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[2] = devm_clk_get(dev, "usb3_aclk");
+ if (IS_ERR(cdns->cdns3_clks[2])) {
+ ret = PTR_ERR(cdns->cdns3_clks[2]);
+ dev_err(dev, "Failed to get usb3_aclk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[3] = devm_clk_get(dev, "usb3_ipg_clk");
+ if (IS_ERR(cdns->cdns3_clks[3])) {
+ ret = PTR_ERR(cdns->cdns3_clks[3]);
+ dev_err(dev, "Failed to get usb3_ipg_clk, err=%d\n", ret);
+ return ret;
+ }
+
+ cdns->cdns3_clks[4] = devm_clk_get(dev, "usb3_core_pclk");
+ if (IS_ERR(cdns->cdns3_clks[4])) {
+ ret = PTR_ERR(cdns->cdns3_clks[4]);
+ dev_err(dev, "Failed to get usb3_core_pclk, err=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cdns3_prepare_enable_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i, j, ret = 0;
+
+ for (i = 0; i < CDNS3_NUM_OF_CLKS; i++) {
+ ret = clk_prepare_enable(cdns->cdns3_clks[i]);
+ if (ret) {
+ dev_err(dev,
+ "Failed to prepare/enable cdns3 clk, err=%d\n",
+ ret);
+ goto err;
+ }
+ }
+
+ return ret;
+err:
+ for (j = i; j > 0; j--)
+ clk_disable_unprepare(cdns->cdns3_clks[j - 1]);
+
+ return ret;
+}
+
+static void cdns3_disable_unprepare_clks(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int i;
+
+ for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--)
+ clk_disable_unprepare(cdns->cdns3_clks[i]);
+}
+
+static void cdns3_remove_roles(struct cdns3 *cdns)
+{
+ cdns3_gadget_remove(cdns);
+ cdns3_host_remove(cdns);
+}
+
+static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
+{
+ int ret = 0;
+ enum cdns3_roles current_role;
+
+ dev_dbg(cdns->dev, "current role is %d, switch to %d\n",
+ cdns->role, role);
+
+ if (cdns->role == role)
+ return 0;
+
+ pm_runtime_get_sync(cdns->dev);
+ current_role = cdns->role;
+ cdns3_role_stop(cdns);
+ if (role == CDNS3_ROLE_END) {
+ /* Force B Session Valid as 0 */
+ writel(0x0040, cdns->phy_regs + 0x380a4);
+ pm_runtime_put_sync(cdns->dev);
+ return 0;
+ }
+
+ cdns_set_role(cdns, role);
+ ret = cdns3_role_start(cdns, role);
+ if (ret) {
+ /* Back to current role */
+ dev_err(cdns->dev, "set %d has failed, back to %d\n",
+ role, current_role);
+ cdns_set_role(cdns, current_role);
+ ret = cdns3_role_start(cdns, current_role);
+ }
+
+ pm_runtime_put_sync(cdns->dev);
+ return ret;
+}
+
+/**
+ * cdns3_role_switch - work queue handler for role switch
+ *
+ * @work: work queue item structure
+ *
+ * Handles below events:
+ * - Role switch for dual-role devices
+ * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
+ */
+static void cdns3_role_switch(struct work_struct *work)
+{
+ struct cdns3 *cdns = container_of(work, struct cdns3,
+ role_switch_wq);
+ bool device, host;
+
+ host = extcon_get_state(cdns->extcon, EXTCON_USB_HOST);
+ device = extcon_get_state(cdns->extcon, EXTCON_USB);
+
+ if (host) {
+ if (cdns->roles[CDNS3_ROLE_HOST])
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
+ return;
+ }
+
+ if (device)
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
+ else
+ cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
+}
+
+static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb);
+
+ queue_work(system_freezable_wq, &cdns->role_switch_wq);
+
+ return NOTIFY_DONE;
+}
+
+static int cdns3_register_extcon(struct cdns3 *cdns)
+{
+ struct extcon_dev *extcon;
+ struct device *dev = cdns->dev;
+ int ret;
+
+ if (of_property_read_bool(dev->of_node, "extcon")) {
+ extcon = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(extcon))
+ return PTR_ERR(extcon);
+
+ ret = devm_extcon_register_notifier(dev, extcon,
+ EXTCON_USB_HOST, &cdns->extcon_nb);
+ if (ret < 0) {
+ dev_err(dev, "register Host Connector failed\n");
+ return ret;
+ }
+
+ ret = devm_extcon_register_notifier(dev, extcon,
+ EXTCON_USB, &cdns->extcon_nb);
+ if (ret < 0) {
+ dev_err(dev, "register Device Connector failed\n");
+ return ret;
+ }
+
+ cdns->extcon = extcon;
+ cdns->extcon_nb.notifier_call = cdns3_extcon_notifier;
+ }
+
+ return 0;
+}
+
+/**
+ * cdns3_probe - probe for cdns3 core device
+ * @pdev: Pointer to cdns3 core platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct cdns3 *cdns;
+ void __iomem *regs;
+ int ret;
+
+ cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
+ if (!cdns)
+ return -ENOMEM;
+
+ cdns->dev = dev;
+ platform_set_drvdata(pdev, cdns);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "missing IRQ\n");
+ return -ENODEV;
+ }
+ cdns->irq = res->start;
+
+ /*
+ * Request memory region
+ * region-0: nxp wrap registers
+ * region-1: xHCI
+ * region-2: Peripheral
+ * region-3: PHY registers
+ * region-4: OTG registers
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->none_core_regs = regs;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->xhci_regs = regs;
+ cdns->xhci_res = res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->dev_regs = regs;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->phy_regs = regs;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+ cdns->otg_regs = regs;
+
+ mutex_init(&cdns->mutex);
+ ret = cdns3_get_clks(dev);
+ if (ret)
+ return ret;
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
+
+ cdns->usbphy = devm_usb_get_phy_by_phandle(dev, "cdns3,usbphy", 0);
+ if (IS_ERR(cdns->usbphy)) {
+ ret = PTR_ERR(cdns->usbphy);
+ if (ret == -ENODEV)
+ ret = -EINVAL;
+ goto err1;
+ }
+
+ ret = usb_phy_init(cdns->usbphy);
+ if (ret)
+ goto err1;
+
+ ret = cdns3_core_init_role(cdns);
+ if (ret)
+ goto err2;
+
+ if (cdns->roles[CDNS3_ROLE_GADGET]) {
+ INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
+ ret = cdns3_register_extcon(cdns);
+ if (ret)
+ goto err3;
+ }
+
+ cdns->role = cdns3_get_role(cdns);
+ dev_dbg(dev, "the init role is %d\n", cdns->role);
+ cdns_set_role(cdns, cdns->role);
+ ret = cdns3_role_start(cdns, cdns->role);
+ if (ret) {
+ dev_err(dev, "can't start %s role\n",
+ cdns3_role(cdns)->name);
+ goto err3;
+ }
+
+ ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
+ dev_name(dev), cdns);
+ if (ret)
+ goto err4;
+
+ device_set_wakeup_capable(dev, true);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * The controller needs less time between bus and controller suspend,
+ * and we also needs a small delay to avoid frequently entering low
+ * power mode.
+ */
+ pm_runtime_set_autosuspend_delay(dev, 20);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_use_autosuspend(dev);
+ dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
+
+ return 0;
+
+err4:
+ cdns3_role_stop(cdns);
+err3:
+ cdns3_remove_roles(cdns);
+err2:
+ usb_phy_shutdown(cdns->usbphy);
+err1:
+ cdns3_disable_unprepare_clks(dev);
+ return ret;
+}
+
+/**
+ * cdns3_remove - unbind our drd driver and clean up
+ * @pdev: Pointer to Linux platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_remove(struct platform_device *pdev)
+{
+ struct cdns3 *cdns = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ cdns3_remove_roles(cdns);
+ usb_phy_shutdown(cdns->usbphy);
+ cdns3_disable_unprepare_clks(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_cdns3_match[] = {
+ { .compatible = "Cadence,usb3" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_cdns3_match);
+#endif
+
+#ifdef CONFIG_PM
+static inline bool controller_power_is_lost(struct cdns3 *cdns)
+{
+ u32 value;
+
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ if ((value & SW_RESET_MASK) == ALL_SW_RESET)
+ return true;
+ else
+ return false;
+}
+
+static void cdns3_set_wakeup(void *none_core_regs, bool enable)
+{
+ u32 value;
+
+ if (enable) {
+ /* Enable wakeup and phy_refclk_req */
+ value = readl(none_core_regs + USB3_INT_REG);
+ value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN;
+ writel(value, none_core_regs + USB3_INT_REG);
+ } else {
+ /* disable wakeup and phy_refclk_req */
+ value = readl(none_core_regs + USB3_INT_REG);
+ value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN);
+ writel(value, none_core_regs + USB3_INT_REG);
+ }
+}
+
+static void cdns3_enter_suspend(struct cdns3 *cdns, bool suspend, bool wakeup)
+{
+ void __iomem *otg_regs = cdns->otg_regs;
+ void __iomem *xhci_regs = cdns->xhci_regs;
+ void __iomem *none_core_regs = cdns->none_core_regs;
+ u32 value;
+ int timeout_us = 100000;
+
+ if (cdns->role != CDNS3_ROLE_HOST)
+ return;
+
+ disable_irq(cdns->irq);
+ if (suspend) {
+ value = readl(otg_regs + OTGREFCLK);
+ value |= OTG_STB_CLK_SWITCH_EN;
+ writel(value, otg_regs + OTGREFCLK);
+
+ value = readl(xhci_regs + XECP_PORT_CAP_REG);
+ value |= LPM_2_STB_SWITCH_EN;
+ writel(value, xhci_regs + XECP_PORT_CAP_REG);
+ if (cdns3_role(cdns)->suspend)
+ cdns3_role(cdns)->suspend(cdns, wakeup);
+
+ /*
+ * SW should ensure LPM_2_STB_SWITCH_EN and RXDET_IN_P3_32KHZ
+ * are aligned before setting CFG_RXDET_P3_EN
+ */
+ value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
+ value |= CFG_RXDET_P3_EN;
+ writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
+ /* SW request low power when all usb ports allow to it ??? */
+ value = readl(xhci_regs + XECP_PM_PMCSR);
+ value |= PS_D0;
+ writel(value, xhci_regs + XECP_PM_PMCSR);
+
+ /* mdctrl_clk_sel */
+ value = readl(none_core_regs + USB3_CORE_CTRL1);
+ value |= MDCTRL_CLK_SEL;
+ writel(value, none_core_regs + USB3_CORE_CTRL1);
+
+ /* wait for mdctrl_clk_status */
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ while (!(value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
+
+ dev_dbg(cdns->dev, "mdctrl_clk_status is set\n");
+
+ /* wait lpm_clk_req to be 0 */
+ value = readl(none_core_regs + USB3_INT_REG);
+ timeout_us = 100000;
+ while ((value & LPM_CLK_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_INT_REG);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait lpm_clk_req timeout\n");
+
+ dev_dbg(cdns->dev, "lpm_clk_req cleared\n");
+
+ /* wait phy_refclk_req to be 0 */
+ value = readl(none_core_regs + USB3_SSPHY_STATUS);
+ timeout_us = 100000;
+ while ((value & PHY_REFCLK_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_SSPHY_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait phy_refclk_req timeout\n");
+
+ dev_dbg(cdns->dev, "phy_refclk_req cleared\n");
+
+ cdns3_set_wakeup(none_core_regs, true);
+ } else {
+ value = readl(none_core_regs + USB3_INT_REG);
+ /* wait CLK_125_REQ to be 1 */
+ value = readl(none_core_regs + USB3_INT_REG);
+ while (!(value & CLK_125_REQ) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_INT_REG);
+ udelay(1);
+ }
+
+ cdns3_set_wakeup(none_core_regs, false);
+
+ /* SW request D0 */
+ value = readl(xhci_regs + XECP_PM_PMCSR);
+ value &= ~PS_D0;
+ writel(value, xhci_regs + XECP_PM_PMCSR);
+
+ /* clr CFG_RXDET_P3_EN */
+ value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
+ value &= ~CFG_RXDET_P3_EN;
+ writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
+
+ /* clear mdctrl_clk_sel */
+ value = readl(none_core_regs + USB3_CORE_CTRL1);
+ value &= ~MDCTRL_CLK_SEL;
+ writel(value, none_core_regs + USB3_CORE_CTRL1);
+
+ /* wait for mdctrl_clk_status is cleared */
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while ((value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
+
+ dev_dbg(cdns->dev, "mdctrl_clk_status cleared\n");
+
+ /* Wait until OTG_NRDY is 0 */
+ value = readl(otg_regs + OTGSTS);
+ timeout_us = 100000;
+ while ((value & OTG_NRDY) && timeout_us-- > 0) {
+ value = readl(otg_regs + OTGSTS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait OTG ready timeout\n");
+
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
+ value = readl(none_core_regs + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
+ }
+ enable_irq(cdns->irq);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cdns3_suspend(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ bool wakeup = device_may_wakeup(dev);
+ u32 value;
+
+ dev_dbg(dev, "at %s\n", __func__);
+
+ if (pm_runtime_status_suspended(dev))
+ pm_runtime_resume(dev);
+
+ if (cdns->role == CDNS3_ROLE_HOST)
+ cdns3_enter_suspend(cdns, true, wakeup);
+ else if (cdns->role == CDNS3_ROLE_GADGET) {
+ /* When at device mode, always set controller at reset mode */
+ value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
+ value |= ALL_SW_RESET;
+ writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
+ }
+
+ if (wakeup)
+ enable_irq_wake(cdns->irq);
+
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns3_disable_unprepare_clks(dev);
+ cdns->in_lpm = true;
+
+ return 0;
+}
+
+static int cdns3_resume(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret;
+ bool power_lost;
+
+ dev_dbg(dev, "at %s\n", __func__);
+ if (!cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
+
+ usb_phy_set_suspend(cdns->usbphy, 0);
+ cdns->in_lpm = false;
+ if (device_may_wakeup(dev))
+ disable_irq_wake(cdns->irq);
+ power_lost = controller_power_is_lost(cdns);
+ if (power_lost) {
+ dev_dbg(dev, "power is lost, the role is %d\n", cdns->role);
+ cdns_set_role(cdns, cdns->role);
+ if ((cdns->role != CDNS3_ROLE_END)
+ && cdns3_role(cdns)->resume) {
+ /* Force B Session Valid as 1 */
+ writel(0x0060, cdns->phy_regs + 0x380a4);
+ cdns3_role(cdns)->resume(cdns, true);
+ }
+ } else {
+ cdns3_enter_suspend(cdns, false, false);
+ if (cdns->wakeup_int) {
+ cdns->wakeup_int = false;
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+ enable_irq(cdns->irq);
+ }
+
+ if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
+ cdns3_role(cdns)->resume(cdns, false);
+ }
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ if (cdns->role == CDNS3_ROLE_HOST) {
+ /*
+ * There is no PM APIs for cdns->host_dev, we can only do
+ * it at its parent PM APIs
+ */
+ pm_runtime_disable(cdns->host_dev);
+ pm_runtime_set_active(cdns->host_dev);
+ pm_runtime_enable(cdns->host_dev);
+ }
+
+ dev_dbg(dev, "at end of %s\n", __func__);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+static int cdns3_runtime_suspend(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "at the begin of %s\n", __func__);
+ if (cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ cdns3_enter_suspend(cdns, true, true);
+ usb_phy_set_suspend(cdns->usbphy, 1);
+ cdns3_disable_unprepare_clks(dev);
+ cdns->in_lpm = true;
+
+ dev_dbg(dev, "at the end of %s\n", __func__);
+
+ return 0;
+}
+
+static int cdns3_runtime_resume(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+ int ret;
+
+ if (!cdns->in_lpm) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ret = cdns3_prepare_enable_clks(dev);
+ if (ret)
+ return ret;
+
+ usb_phy_set_suspend(cdns->usbphy, 0);
+ cdns3_enter_suspend(cdns, false, false);
+ cdns->in_lpm = 0;
+
+ if (cdns->role == CDNS3_ROLE_HOST) {
+ if (cdns->wakeup_int) {
+ cdns->wakeup_int = false;
+ pm_runtime_mark_last_busy(cdns->dev);
+ pm_runtime_put_autosuspend(cdns->dev);
+ enable_irq(cdns->irq);
+ }
+
+ if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
+ cdns3_role(cdns)->resume(cdns, false);
+ }
+
+ dev_dbg(dev, "at %s\n", __func__);
+ return 0;
+}
+#endif /* CONFIG_PM */
+static const struct dev_pm_ops cdns3_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
+ SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
+};
+
+static struct platform_driver cdns3_driver = {
+ .probe = cdns3_probe,
+ .remove = cdns3_remove,
+ .driver = {
+ .name = "cdns-usb3",
+ .of_match_table = of_match_ptr(of_cdns3_match),
+ .pm = &cdns3_pm_ops,
+ },
+};
+
+static int __init cdns3_driver_platform_register(void)
+{
+ cdns3_host_driver_init();
+ return platform_driver_register(&cdns3_driver);
+}
+module_init(cdns3_driver_platform_register);
+
+static void __exit cdns3_driver_platform_unregister(void)
+{
+ platform_driver_unregister(&cdns3_driver);
+}
+module_exit(cdns3_driver_platform_unregister);
+
+MODULE_ALIAS("platform:cdns3");
+MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
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..b3d81aabe9c3
--- /dev/null
+++ b/drivers/usb/cdns3/gadget.c
@@ -0,0 +1,2526 @@
+/**
+ * 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 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;
+
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf);
+ if (set)
+ /* set U1EN */
+ reg |= USB_CONF__U1EN__MASK;
+ else
+ /* set U1 disable */
+ reg |= USB_CONF__U1DS__MASK;
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg);
+ 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;
+
+ reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf);
+ if (set)
+ /* set U2EN */
+ reg |= USB_CONF__U2EN__MASK;
+ else
+ /* set U2 disable */
+ reg |= USB_CONF__U2DS__MASK;
+ gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg);
+ 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);
+}
+
+/**
+ * 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);
+
+ 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);
+
+ 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);
+}
+
+/**
+ * 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);
+ 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;
+ 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);
+ device_unregister(cdns->gadget_dev);
+ cdns->gadget_dev = NULL;
+ kfree(usb_ss->zlp_buf);
+}
+
+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);
+
+ gadget_writel(usb_ss, &usb_ss->regs->dbg_link1,
+ DBG_LINK1__LFPS_MIN_GEN_U1_EXIT_SET__MASK |
+ DBG_LINK1__LFPS_MIN_GEN_U1_EXIT__WRITE(0x3C));
+}
+
+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;
+ /* 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..4ed97339a724
--- /dev/null
+++ b/drivers/usb/cdns3/host.c
@@ -0,0 +1,259 @@
+/*
+ * 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"
+
+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;
+}
+
+static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
+ .extra_priv_size = sizeof(struct xhci_hcd),
+ .reset = xhci_cdns3_setup,
+};
+
+struct cdns3_host {
+ struct device dev;
+ struct usb_hcd *hcd;
+};
+
+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;
+ 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;
+ struct xhci_hcd *xhci;
+
+ if (dev) {
+ hcd = dev_get_drvdata(dev);
+ xhci = hcd_to_xhci(hcd);
+ usb_remove_hcd(xhci->shared_hcd);
+ usb_remove_hcd(hcd);
+ synchronize_irq(cdns->irq);
+ usb_put_hcd(xhci->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 05bc4d631cb9..33c99e2fc8fe 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -125,12 +125,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;
};
@@ -174,6 +178,7 @@ struct hw_bank {
* @td_pool: allocation pool for transfer descriptors
* @gadget: device side representation for peripheral controller
* @driver: gadget driver
+ * @resume_state: save the state of gadget suspend from
* @hw_ep_max: total number of endpoints supported by hardware
* @ci_hw_ep: array of endpoints
* @ep0_dir: ep0 direction
@@ -193,12 +198,14 @@ 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
*/
struct ci_hdrc {
struct device *dev;
@@ -222,6 +229,7 @@ struct ci_hdrc {
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
+ enum usb_device_state resume_state;
unsigned hw_ep_max;
struct ci_hw_ep ci_hw_ep[ENDPT_MAX];
u32 ep0_dir;
@@ -243,11 +251,25 @@ 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 mutex mutex;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
@@ -267,9 +289,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)
@@ -282,6 +316,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);
}
/**
@@ -429,6 +466,7 @@ int hw_port_test_set(struct ci_hdrc *ci, u8 mode);
u8 hw_port_test_get(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 099179457f60..53f48cc2eeb5 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
*
@@ -19,6 +20,13 @@
#include <linux/dma-mapping.h>
#include <linux/usb/chipidea.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"
@@ -40,25 +48,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 = {
@@ -70,6 +82,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},
@@ -79,6 +101,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);
@@ -90,12 +114,30 @@ struct ci_hdrc_imx_data {
struct imx_usbmisc_data *usbmisc_data;
bool supports_runtime_pm;
bool in_lpm;
+ bool imx_usb_charger_detection;
+ struct usb_charger charger;
+ 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;
+};
+
+static char *imx_usb_charger_supplied_to[] = {
+ "imx_usb_charger",
+};
+
+static enum power_supply_property imx_usb_charger_power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, /* Charger detected */
+ POWER_SUPPLY_PROP_ONLINE, /* VBUS online */
+ POWER_SUPPLY_PROP_CURRENT_MAX, /* Maximum current in mA */
};
/* Common functions shared by usbmisc drivers */
@@ -143,9 +185,32 @@ 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_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);
+ }
+ }
+
return data;
}
@@ -247,41 +312,212 @@ 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;
+
+ switch (event) {
+ case CI_HDRC_CONTROLLER_VBUS_EVENT:
+ if (data->usbmisc_data && ci->vbus_active) {
+ if (data->imx_usb_charger_detection) {
+ ret = imx_usbmisc_charger_detection(
+ data->usbmisc_data, true);
+ if (!ret && data->charger.psy_desc.type !=
+ POWER_SUPPLY_TYPE_USB)
+ ret = CI_HDRC_NOTIFY_RET_DEFER_EVENT;
+ }
+ } else if (data->usbmisc_data && !ci->vbus_active) {
+ if (data->imx_usb_charger_detection)
+ ret = imx_usbmisc_charger_detection(
+ data->usbmisc_data, false);
+ }
+ break;
+ case CI_HDRC_CONTROLLER_CHARGER_POST_EVENT:
+ if (!data->imx_usb_charger_detection)
+ return ret;
+ imx_usbmisc_charger_secondary_detection(data->usbmisc_data);
+ 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 imx_usb_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct usb_charger *charger =
+ container_of(psy->desc, struct usb_charger, psy_desc);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = charger->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = charger->online;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = charger->max_current;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * imx_usb_register_charger - register a USB charger
+ * @charger: the charger to be initialized
+ * @name: name for the power supply
+
+ * Registers a power supply for the charger. The USB Controller
+ * driver will call this after filling struct usb_charger.
+ */
+static int imx_usb_register_charger(struct usb_charger *charger,
+ const char *name)
+{
+ struct power_supply_desc *desc = &charger->psy_desc;
+
+ if (!charger->dev)
+ return -EINVAL;
+
+ if (name)
+ desc->name = name;
+ else
+ desc->name = "imx_usb_charger";
+
+ charger->bc = BATTERY_CHARGING_SPEC_1_2;
+ mutex_init(&charger->lock);
+
+ desc->type = POWER_SUPPLY_TYPE_MAINS;
+ desc->properties = imx_usb_charger_power_props;
+ desc->num_properties = ARRAY_SIZE(imx_usb_charger_power_props);
+ desc->get_property = imx_usb_charger_get_property;
+
+ charger->psy = devm_power_supply_register(charger->dev,
+ &charger->psy_desc, NULL);
+ if (IS_ERR(charger->psy))
+ return PTR_ERR(charger->psy);
+
+ charger->psy->supplied_to = imx_usb_charger_supplied_to;
+ charger->psy->num_supplicants = sizeof(imx_usb_charger_supplied_to)
+ / sizeof(char *);
+
+ return 0;
+}
+
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 */
@@ -291,46 +527,119 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
}
pdata.usb_phy = data->phy;
- pdata.flags |= imx_platform_flag->flags;
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;
+ }
+
+ if (of_find_property(np, "imx-usb-charger-detection", NULL) &&
+ data->usbmisc_data) {
+ data->imx_usb_charger_detection = true;
+ data->charger.dev = dev;
+ data->usbmisc_data->charger = &data->charger;
+ ret = imx_usb_register_charger(&data->charger,
+ "imx_usb_charger");
+ if (ret && ret != -ENODEV)
+ goto disable_hsic_regulator;
+ if (!ret)
+ dev_dbg(dev, "USB Charger is created\n");
+ }
+
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;
}
@@ -345,6 +654,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;
}
@@ -358,10 +672,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;
@@ -374,27 +701,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;
}
@@ -442,10 +793,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 409aa5ca8dda..8d86a9dbdfe0 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,18 +11,61 @@
#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
+#include <linux/usb/otg.h>
+#include <linux/power_supply.h>
+
+enum battery_charging_spec {
+ BATTERY_CHARGING_SPEC_NONE = 0,
+ BATTERY_CHARGING_SPEC_UNKNOWN,
+ BATTERY_CHARGING_SPEC_1_0,
+ BATTERY_CHARGING_SPEC_1_1,
+ BATTERY_CHARGING_SPEC_1_2,
+};
+
+struct usb_charger {
+ /* USB controller */
+ struct device *dev;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ struct mutex lock;
+
+ /* Compliant with Battery Charging Specification version (if any) */
+ enum battery_charging_spec bc;
+
+ /* properties */
+ unsigned present:1;
+ unsigned online:1;
+ unsigned max_current;
+};
struct imx_usbmisc_data {
struct device *dev;
int index;
+ struct regmap *anatop;
+ struct usb_charger *charger;
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 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 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_charger_secondary_detection(struct imx_usbmisc_data *data);
+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/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c
index 37591a4b1346..6d5cf973dba6 100644
--- a/drivers/usb/chipidea/ci_hdrc_msm.c
+++ b/drivers/usb/chipidea/ci_hdrc_msm.c
@@ -17,7 +17,7 @@
#define MSM_USB_BASE (ci->hw_bank.abs)
-static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
+static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
{
struct device *dev = ci->gadget.dev.parent;
@@ -40,6 +40,8 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
dev_dbg(dev, "unknown ci_hdrc event\n");
break;
}
+
+ return 0;
}
static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 64c6af2c8559..a9b3795a150b 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -461,7 +461,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;
@@ -555,7 +555,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);
@@ -569,35 +569,14 @@ static irqreturn_t ci_irq(int irq, void *data)
return ret;
}
-static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
+static int ci_cable_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
{
- struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = vbus->ci;
+ struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb);
+ struct ci_hdrc *ci = cbl->ci;
- if (event)
- vbus->state = true;
- else
- vbus->state = false;
-
- vbus->changed = true;
-
- ci_irq(ci->irq, ci);
- return NOTIFY_DONE;
-}
-
-static int ci_id_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
-{
- struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = id->ci;
-
- if (event)
- id->state = false;
- else
- id->state = true;
-
- id->changed = true;
+ cbl->connected = event;
+ cbl->changed = true;
ci_irq(ci->irq, ci);
return NOTIFY_DONE;
@@ -706,27 +685,27 @@ static int ci_get_platdata(struct device *dev,
}
cable = &platdata->vbus_extcon;
- cable->nb.notifier_call = ci_vbus_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_vbus;
if (!IS_ERR(ext_vbus)) {
- ret = extcon_get_cable_state_(cable->edev, EXTCON_USB);
+ ret = extcon_get_state(cable->edev, EXTCON_USB);
if (ret)
- cable->state = true;
+ cable->connected = true;
else
- cable->state = false;
+ cable->connected = false;
}
cable = &platdata->id_extcon;
- cable->nb.notifier_call = ci_id_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_id;
if (!IS_ERR(ext_id)) {
- ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST);
+ ret = extcon_get_state(cable->edev, EXTCON_USB_HOST);
if (ret)
- cable->state = false;
+ cable->connected = true;
else
- cable->state = true;
+ cable->connected = false;
}
return 0;
}
@@ -738,9 +717,9 @@ static int ci_extcon_register(struct ci_hdrc *ci)
id = &ci->platdata->id_extcon;
id->ci = ci;
- if (!IS_ERR(id->edev)) {
- ret = extcon_register_notifier(id->edev, EXTCON_USB_HOST,
- &id->nb);
+ if (!IS_ERR_OR_NULL(id->edev)) {
+ ret = devm_extcon_register_notifier(ci->dev, id->edev,
+ EXTCON_USB_HOST, &id->nb);
if (ret < 0) {
dev_err(ci->dev, "register ID failed\n");
return ret;
@@ -749,12 +728,10 @@ static int ci_extcon_register(struct ci_hdrc *ci)
vbus = &ci->platdata->vbus_extcon;
vbus->ci = ci;
- if (!IS_ERR(vbus->edev)) {
- ret = extcon_register_notifier(vbus->edev, EXTCON_USB,
- &vbus->nb);
+ if (!IS_ERR_OR_NULL(vbus->edev)) {
+ ret = devm_extcon_register_notifier(ci->dev, vbus->edev,
+ EXTCON_USB, &vbus->nb);
if (ret < 0) {
- extcon_unregister_notifier(id->edev, EXTCON_USB_HOST,
- &id->nb);
dev_err(ci->dev, "register VBUS failed\n");
return ret;
}
@@ -763,20 +740,6 @@ static int ci_extcon_register(struct ci_hdrc *ci)
return 0;
}
-static void ci_extcon_unregister(struct ci_hdrc *ci)
-{
- struct ci_hdrc_cable *cable;
-
- cable = &ci->platdata->id_extcon;
- if (!IS_ERR(cable->edev))
- extcon_unregister_notifier(cable->edev, EXTCON_USB_HOST,
- &cable->nb);
-
- cable = &ci->platdata->vbus_extcon;
- if (!IS_ERR(cable->edev))
- extcon_unregister_notifier(cable->edev, EXTCON_USB, &cable->nb);
-}
-
static DEFINE_IDA(ci_ida);
struct platform_device *ci_hdrc_add_device(struct device *dev,
@@ -801,9 +764,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
}
pdev->dev.parent = dev;
- pdev->dev.dma_mask = dev->dma_mask;
- pdev->dev.dma_parms = dev->dma_parms;
- dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask);
ret = platform_device_add_resources(pdev, res, nres);
if (ret)
@@ -835,12 +795,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)
@@ -859,6 +846,104 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
}
}
+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);
+
+ 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 ssize_t ci_role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", ci_role(ci)->name);
+}
+
+static ssize_t ci_role_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t n)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+ enum ci_role role;
+ int ret;
+
+ if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) {
+ dev_warn(dev, "Current configuration is not dual-role, quit\n");
+ return -EPERM;
+ }
+
+ for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++)
+ if (!strncmp(buf, ci->roles[role]->name,
+ strlen(ci->roles[role]->name)))
+ break;
+
+ if (role == CI_ROLE_END || role == ci->role)
+ return -EINVAL;
+
+ pm_runtime_get_sync(dev);
+ disable_irq(ci->irq);
+ ci_role_stop(ci);
+ ret = ci_role_start(ci, role);
+ if (!ret && ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
+ enable_irq(ci->irq);
+ pm_runtime_put_sync(dev);
+
+ return (ret == 0) ? n : ret;
+}
+static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store);
+
+static struct attribute *ci_attrs[] = {
+ &dev_attr_role.attr,
+ NULL,
+};
+
+static struct attribute_group ci_attr_group = {
+ .attrs = ci_attrs,
+};
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -971,30 +1056,12 @@ 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)
+ 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",
@@ -1026,11 +1093,22 @@ static int ci_hdrc_probe(struct platform_device *pdev)
device_set_wakeup_capable(&pdev->dev, true);
+ /* Init workqueue for controller power lost handling */
+ INIT_WORK(&ci->power_lost_work, ci_power_lost_work);
+ mutex_init(&ci->mutex);
+
ret = dbg_create_files(ci);
- if (!ret)
- return 0;
+ if (ret)
+ goto stop;
+
+ ret = sysfs_create_group(&dev->kobj, &ci_attr_group);
+ if (ret)
+ goto remove_debug;
- ci_extcon_unregister(ci);
+ return 0;
+
+remove_debug:
+ dbg_remove_files(ci);
stop:
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
ci_hdrc_otg_destroy(ci);
@@ -1055,7 +1133,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
}
dbg_remove_files(ci);
- ci_extcon_unregister(ci);
+ sysfs_remove_group(&ci->dev->kobj, &ci_attr_group);
ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true);
ci_usb_phy_exit(ci);
@@ -1081,13 +1159,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);
}
}
@@ -1103,16 +1178,37 @@ 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);
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);
if (ci->usb_phy) {
@@ -1129,6 +1225,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;
@@ -1155,6 +1252,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);
@@ -1171,8 +1272,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);
@@ -1180,6 +1291,21 @@ 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) {
+ disable_irq_nosync(ci->irq);
+ schedule_work(&ci->power_lost_work);
+ }
+
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -1196,10 +1322,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 111b0e0b8698..4b687bb8db0c 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);
@@ -101,9 +120,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)
@@ -116,7 +290,8 @@ static int host_start(struct ci_hdrc *ci)
if (usb_disabled())
return -ENODEV;
- hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev));
+ hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent,
+ ci->dev, dev_name(ci->dev), NULL);
if (!hcd)
return -ENOMEM;
@@ -156,6 +331,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;
@@ -165,11 +347,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:
@@ -194,16 +382,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)
@@ -211,6 +506,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);
@@ -240,6 +537,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;
}
}
@@ -261,6 +582,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;
@@ -271,5 +594,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 0f12f131bdd3..4dfb59a05a1a 100644
--- a/drivers/usb/chipidea/host.h
+++ b/drivers/usb/chipidea/host.h
@@ -6,6 +6,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
@@ -19,11 +20,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 f36a1ac3bfbd..9a17e04f4bfc 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.
@@ -44,7 +46,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
else
val &= ~OTGSC_BSVIS;
- if (cable->state)
+ if (cable->connected)
val |= OTGSC_BSV;
else
val &= ~OTGSC_BSV;
@@ -62,10 +64,10 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
else
val &= ~OTGSC_IDIS;
- if (cable->state)
- val |= OTGSC_ID;
+ if (cable->connected)
+ val &= ~OTGSC_ID; /* host */
else
- val &= ~OTGSC_ID;
+ val |= OTGSC_ID; /* device */
if (cable->enabled)
val |= OTGSC_IDIE;
@@ -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,12 @@ 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;
+ 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);
@@ -191,7 +235,34 @@ static void ci_handle_id_switch(struct ci_hdrc *ci)
if (role == CI_ROLE_GADGET)
ci_handle_vbus_change(ci);
}
+ 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 +271,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;
@@ -245,13 +325,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 9ecb598e48f0..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,10 +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);
- queue_work(ci->wq, &ci->work);
+ 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 de8e22ec3902..05ff5f155e96 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -1,7 +1,7 @@
/*
* otg_fsm.c - ChipIdea USB IP core OTG FSM driver
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
*
* Author: Jun Li
*
@@ -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 6a15b7250e9c..cf24527d24eb 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
hwreq->req.status = -EALREADY;
- ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir);
+ ret = usb_gadget_map_request_by_dev(ci->dev->parent,
+ &hwreq->req, hwep->dir);
if (ret)
return ret;
@@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
list_del_init(&node->td);
}
- usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir);
+ usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent,
+ &hwreq->req, hwep->dir);
hwreq->req.actual += actual;
@@ -709,12 +711,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
unsigned long flags;
- spin_lock_irqsave(&ci->lock, flags);
- ci->gadget.speed = USB_SPEED_UNKNOWN;
- ci->remote_wakeup = 0;
- ci->suspended = 0;
- spin_unlock_irqrestore(&ci->lock, flags);
-
/* flush all endpoints */
gadget_for_each_ep(ep, gadget) {
usb_ep_fifo_flush(ep);
@@ -732,6 +728,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
ci->status = NULL;
}
+ spin_lock_irqsave(&ci->lock, flags);
+ ci->gadget.speed = USB_SPEED_UNKNOWN;
+ ci->remote_wakeup = 0;
+ ci->suspended = 0;
+ spin_unlock_irqrestore(&ci->lock, flags);
+
return 0;
}
@@ -750,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);
@@ -873,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;
@@ -881,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) ?
@@ -1007,6 +1025,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
@@ -1077,8 +1117,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;
@@ -1129,6 +1170,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;
}
@@ -1306,6 +1353,10 @@ static int ep_disable(struct usb_ep *ep)
return -EBUSY;
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return 0;
+ }
/* only internal SW should disable ctrl endpts */
@@ -1395,6 +1446,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,
return -EINVAL;
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return 0;
+ }
retval = _ep_queue(ep, req, gfp_flags);
spin_unlock_irqrestore(hwep->lock, flags);
return retval;
@@ -1418,8 +1473,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
return -EINVAL;
spin_lock_irqsave(hwep->lock, flags);
-
- hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
+ if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN)
+ hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
dma_pool_free(hwep->td_pool, node->ptr, node->dma);
@@ -1490,6 +1545,10 @@ static void ep_fifo_flush(struct usb_ep *ep)
}
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return;
+ }
hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
@@ -1527,27 +1586,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);
- }
+ /* Charger Detection */
+ ci_usb_charger_connect(ci, is_active);
+
+ if (ci->usb_phy) {
+ 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;
}
@@ -1558,6 +1609,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget)
int ret = 0;
spin_lock_irqsave(&ci->lock, flags);
+ if (ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(&ci->lock, flags);
+ return 0;
+ }
if (!ci->remote_wakeup) {
ret = -EOPNOTSUPP;
goto out;
@@ -1725,7 +1780,6 @@ static int ci_udc_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
- unsigned long flags;
int retval = -ENOMEM;
if (driver->disconnect == NULL)
@@ -1745,25 +1799,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) {
- spin_lock_irqsave(&ci->lock, flags);
- 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);
- spin_unlock_irqrestore(&ci->lock, flags);
- if (retval)
- pm_runtime_put_sync(&ci->gadget.dev);
+ if (ci->vbus_active)
+ ci_hdrc_gadget_connect(&ci->gadget, 1);
return retval;
}
@@ -1848,27 +1891,35 @@ 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->suspended && ci->driver->resume) {
- spin_unlock(&ci->lock);
- ci->driver->resume(&ci->gadget);
- spin_lock(&ci->lock);
+ 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);
+ ci->driver->resume(&ci->gadget);
+ spin_lock(&ci->lock);
+ }
ci->suspended = 0;
+ usb_gadget_set_state(&ci->gadget,
+ ci->resume_state);
}
}
if (USBi_UI & intr)
isr_tr_complete_handler(ci);
- if (USBi_SLI & intr) {
+ if ((USBi_SLI & intr) && !(ci->suspended)) {
+ ci->suspended = 1;
+ ci->resume_state = ci->gadget.state;
if (ci->gadget.speed != USB_SPEED_UNKNOWN &&
ci->driver->suspend) {
- ci->suspended = 1;
spin_unlock(&ci->lock);
ci->driver->suspend(&ci->gadget);
- usb_gadget_set_state(&ci->gadget,
- USB_STATE_SUSPENDED);
spin_lock(&ci->lock);
}
+ usb_gadget_set_state(&ci->gadget,
+ USB_STATE_SUSPENDED);
}
retval = IRQ_HANDLED;
} else {
@@ -1902,13 +1953,13 @@ static int udc_start(struct ci_hdrc *ci)
INIT_LIST_HEAD(&ci->gadget.ep_list);
/* alloc resources */
- ci->qh_pool = dma_pool_create("ci_hw_qh", dev,
+ ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent,
sizeof(struct ci_hw_qh),
64, CI_HDRC_PAGE_SIZE);
if (ci->qh_pool == NULL)
return -ENOMEM;
- ci->td_pool = dma_pool_create("ci_hw_td", dev,
+ ci->td_pool = dma_pool_create("ci_hw_td", dev->parent,
sizeof(struct ci_hw_td),
64, CI_HDRC_PAGE_SIZE);
if (ci->td_pool == NULL) {
@@ -1958,10 +2009,74 @@ 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 (is_active)
+ pm_runtime_get_sync(ci->dev);
+
+ if (ci->platdata->notify_event) {
+ if (is_active)
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+
+ ret = ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_VBUS_EVENT);
+ if (ret == CI_HDRC_NOTIFY_RET_DEFER_EVENT) {
+ hw_device_reset(ci);
+ /* Pull up dp */
+ hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
+ ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_CHARGER_POST_EVENT);
+ /* Pull down dp */
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+ }
+ }
+
+ if (!is_active)
+ 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(&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(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(&gadget->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);
@@ -1970,12 +2085,57 @@ 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);
+ }
}
/**
@@ -1999,6 +2159,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 e66df0020bd4..7c3af66aa8f4 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 20d02a5e418d..b7d21b052c19 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"
@@ -57,11 +60,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
@@ -74,12 +90,50 @@
#define VF610_OVER_CUR_DIS BIT(7)
#define MX7D_USBNC_USB_CTRL2 0x4
+#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_HSIC_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_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 */
@@ -88,6 +142,19 @@ 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 contact and primary detection */
+ int (*charger_primary_detection)(struct imx_usbmisc_data *data);
+ /* usb charger secondary detection */
+ int (*charger_secondary_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 {
@@ -96,6 +163,8 @@ struct imx_usbmisc {
const struct usbmisc_ops *ops;
};
+static struct regulator *vbus_wakeup_reg;
+
static int usbmisc_imx25_init(struct imx_usbmisc_data *data)
{
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
@@ -227,15 +296,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;
@@ -243,15 +383,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;
}
@@ -260,7 +405,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;
@@ -281,6 +426,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);
@@ -297,9 +463,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);
@@ -310,9 +476,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;
}
@@ -342,16 +517,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);
@@ -376,12 +552,33 @@ 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);
+
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 (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);
+
+ reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ writel(reg | MX7D_USBNC_HSIC_AUTO_RESUME,
+ usbmisc->base + MX7D_USBNC_USB_CTRL2);
+ }
spin_unlock_irqrestore(&usbmisc->lock, flags);
usbmisc_imx7d_set_wakeup(data, false);
@@ -389,6 +586,371 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data)
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;
+}
+
+
+/***************************************************************************/
+/* imx usb charger detecton */
+/***************************************************************************/
+static void usb_charger_is_present(struct usb_charger *charger, bool present)
+{
+ if (present)
+ charger->present = 1;
+ else
+ charger->present = 0;
+
+ power_supply_changed(charger->psy);
+ sysfs_notify(&charger->psy->dev.kobj, NULL, "present");
+}
+
+static void imx6_disable_charger_detector(struct imx_usbmisc_data *data)
+{
+ struct regmap *regmap = data->anatop;
+
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+}
+
+static int imx6_charger_data_contact_detect(struct imx_usbmisc_data *data)
+{
+ struct regmap *regmap = data->anatop;
+ struct usb_charger *charger = data->charger;
+ u32 val;
+ int i, data_pin_contact_count = 0;
+
+ /* 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(charger->dev, "vbus is error\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 < 100; i = i + 1) {
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
+ data_pin_contact_count++;
+ 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(charger->dev,
+ "VBUS is coming from a dedicated power supply.\n");
+ imx6_disable_charger_detector(data);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int imx6_charger_primary_detection(struct imx_usbmisc_data *data)
+{
+ struct regmap *regmap = data->anatop;
+ struct usb_charger *charger = data->charger;
+ u32 val;
+ int ret;
+
+ ret = imx6_charger_data_contact_detect(data);
+ if (ret)
+ return ret;
+
+ /*
+ * - 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)) {
+ dev_dbg(charger->dev, "It is a stardard downstream port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ charger->max_current = 500;
+ }
+
+ imx6_disable_charger_detector(data);
+ return 0;
+}
+
+/*
+ * It must be called after dp is pulled up (from USB controller driver),
+ * That is used to differentiate DCP and CDP
+ */
+int imx6_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+ struct regmap *regmap = data->anatop;
+ struct usb_charger *charger = data->charger;
+ int val;
+
+ msleep(80);
+
+ mutex_lock(&charger->lock);
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
+ dev_dbg(charger->dev, "It is a dedicate charging port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
+ charger->max_current = 1500;
+ } else {
+ dev_dbg(charger->dev, "It is a charging downstream port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
+ charger->max_current = 900;
+ }
+
+ usb_charger_is_present(charger, true);
+ mutex_unlock(&charger->lock);
+
+ 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 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);
+ struct usb_charger *charger = data->charger;
+ 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(charger->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(charger->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_charger *charger = data->charger;
+ 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(charger->dev, "It is a stardard downstream port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ charger->max_current = 500;
+ }
+
+ imx7_disable_charger_detector(data);
+
+ return 0;
+}
+
+/*
+ * It must be called after dp is pulled up (from USB controller driver),
+ * That is used to differentiate DCP and CDP
+ */
+int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ struct usb_charger *charger = data->charger;
+ int val;
+
+ msleep(80);
+
+ mutex_lock(&charger->lock);
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS);
+ if (val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE1) {
+ dev_dbg(charger->dev, "It is a dedicate charging port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
+ charger->max_current = 1500;
+ } else {
+ dev_dbg(charger->dev, "It is a charging downstream port\n");
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
+ charger->max_current = 900;
+ }
+
+ usb_charger_is_present(charger, true);
+ mutex_unlock(&charger->lock);
+
+ return 0;
+}
+
+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);
+ 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 const struct usbmisc_ops imx25_usbmisc_ops = {
.init = usbmisc_imx25_init,
.post = usbmisc_imx25_post,
@@ -405,6 +967,10 @@ 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,
+ .charger_primary_detection = imx6_charger_primary_detection,
+ .charger_secondary_detection = imx6_charger_secondary_detection,
+ .hsic_set_connect = usbmisc_imx6_hsic_set_connect,
+ .hsic_set_clk = usbmisc_imx6_hsic_set_clk,
};
static const struct usbmisc_ops vf610_usbmisc_ops = {
@@ -414,11 +980,30 @@ 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,
+ .charger_primary_detection = imx6_charger_primary_detection,
+ .charger_secondary_detection = imx6_charger_secondary_detection,
+ .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_primary_detection = imx7d_charger_primary_detection,
+ .charger_secondary_detection = imx7d_charger_secondary_detection,
+ .term_select_override = usbmisc_term_select_override,
+ .hsic_set_connect = usbmisc_imx6_hsic_set_connect,
+ .hsic_set_clk = usbmisc_imx6_hsic_set_clk,
+};
+
+static const struct usbmisc_ops imx7ulp_usbmisc_ops = {
+ .init = usbmisc_imx7d_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,
};
int imx_usbmisc_init(struct imx_usbmisc_data *data)
@@ -463,6 +1048,115 @@ 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_charger *charger;
+ int ret = 0;
+
+ if (!data)
+ return -EINVAL;
+
+ charger = data->charger;
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->charger_primary_detection)
+ return -ENOTSUPP;
+
+ mutex_lock(&charger->lock);
+ if (connect) {
+ charger->online = 1;
+ ret = usbmisc->ops->charger_primary_detection(data);
+ if (ret) {
+ dev_err(charger->dev,
+ "Error occurs during detection: %d\n",
+ ret);
+ } else {
+ if (charger->psy_desc.type == POWER_SUPPLY_TYPE_USB)
+ usb_charger_is_present(charger, true);
+ }
+ } else {
+ charger->online = 0;
+ charger->max_current = 0;
+ charger->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
+
+ usb_charger_is_present(charger, false);
+ }
+ mutex_unlock(&charger->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection);
+
+int imx_usbmisc_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+ if (!usbmisc->ops->charger_secondary_detection)
+ return 0;
+ return usbmisc->ops->charger_secondary_detection(data);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_charger_secondary_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",
@@ -504,6 +1198,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);
@@ -532,6 +1230,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 2f537bbdda09..23009e3d8ca9 100644
--- a/drivers/usb/common/usb-otg-fsm.c
+++ b/drivers/usb/common/usb-otg-fsm.c
@@ -68,6 +68,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;
@@ -85,6 +86,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;
@@ -131,8 +137,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);
@@ -142,6 +150,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,
@@ -183,6 +216,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;
}
@@ -224,6 +262,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);
@@ -236,6 +278,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);
@@ -250,8 +293,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:
@@ -406,8 +447,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);
@@ -415,9 +455,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/buffer.c b/drivers/usb/core/buffer.c
index 98e39f91723a..a6cd44a711cf 100644
--- a/drivers/usb/core/buffer.c
+++ b/drivers/usb/core/buffer.c
@@ -63,7 +63,7 @@ int hcd_buffer_create(struct usb_hcd *hcd)
int i, size;
if (!IS_ENABLED(CONFIG_HAS_DMA) ||
- (!hcd->self.controller->dma_mask &&
+ (!is_device_dma_capable(hcd->self.sysdev) &&
!(hcd->driver->flags & HCD_LOCAL_MEM)))
return 0;
@@ -72,7 +72,7 @@ int hcd_buffer_create(struct usb_hcd *hcd)
if (!size)
continue;
snprintf(name, sizeof(name), "buffer-%d", size);
- hcd->pool[i] = dma_pool_create(name, hcd->self.controller,
+ hcd->pool[i] = dma_pool_create(name, hcd->self.sysdev,
size, size, 0);
if (!hcd->pool[i]) {
hcd_buffer_destroy(hcd);
@@ -127,7 +127,7 @@ void *hcd_buffer_alloc(
/* some USB hosts just use PIO */
if (!IS_ENABLED(CONFIG_HAS_DMA) ||
- (!bus->controller->dma_mask &&
+ (!is_device_dma_capable(bus->sysdev) &&
!(hcd->driver->flags & HCD_LOCAL_MEM))) {
*dma = ~(dma_addr_t) 0;
return kmalloc(size, mem_flags);
@@ -137,7 +137,7 @@ void *hcd_buffer_alloc(
if (size <= pool_max[i])
return dma_pool_alloc(hcd->pool[i], mem_flags, dma);
}
- return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags);
+ return dma_alloc_coherent(hcd->self.sysdev, size, dma, mem_flags);
}
void hcd_buffer_free(
@@ -154,7 +154,7 @@ void hcd_buffer_free(
return;
if (!IS_ENABLED(CONFIG_HAS_DMA) ||
- (!bus->controller->dma_mask &&
+ (!is_device_dma_capable(bus->sysdev) &&
!(hcd->driver->flags & HCD_LOCAL_MEM))) {
kfree(addr);
return;
@@ -166,5 +166,5 @@ void hcd_buffer_free(
return;
}
}
- dma_free_coherent(hcd->self.controller, size, addr, dma);
+ dma_free_coherent(hcd->self.sysdev, size, addr, dma);
}
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index bdb0d7a08ff9..0f7ef2b2e0a6 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1076,6 +1076,7 @@ static void usb_deregister_bus (struct usb_bus *bus)
static int register_root_hub(struct usb_hcd *hcd)
{
struct device *parent_dev = hcd->self.controller;
+ struct device *sysdev = hcd->self.sysdev;
struct usb_device *usb_dev = hcd->self.root_hub;
const int devnum = 1;
int retval;
@@ -1122,7 +1123,7 @@ static int register_root_hub(struct usb_hcd *hcd)
/* Did the HC die before the root hub was registered? */
if (HCD_DEAD(hcd))
usb_hc_died (hcd); /* This time clean up */
- usb_dev->dev.of_node = parent_dev->of_node;
+ usb_dev->dev.of_node = sysdev->of_node;
}
mutex_unlock(&usb_bus_idr_lock);
@@ -1435,7 +1436,7 @@ void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
if (IS_ENABLED(CONFIG_HAS_DMA) &&
(urb->transfer_flags & URB_SETUP_MAP_SINGLE))
- dma_unmap_single(hcd->self.controller,
+ dma_unmap_single(hcd->self.sysdev,
urb->setup_dma,
sizeof(struct usb_ctrlrequest),
DMA_TO_DEVICE);
@@ -1468,19 +1469,19 @@ void usb_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
if (IS_ENABLED(CONFIG_HAS_DMA) &&
(urb->transfer_flags & URB_DMA_MAP_SG))
- dma_unmap_sg(hcd->self.controller,
+ dma_unmap_sg(hcd->self.sysdev,
urb->sg,
urb->num_sgs,
dir);
else if (IS_ENABLED(CONFIG_HAS_DMA) &&
(urb->transfer_flags & URB_DMA_MAP_PAGE))
- dma_unmap_page(hcd->self.controller,
+ dma_unmap_page(hcd->self.sysdev,
urb->transfer_dma,
urb->transfer_buffer_length,
dir);
else if (IS_ENABLED(CONFIG_HAS_DMA) &&
(urb->transfer_flags & URB_DMA_MAP_SINGLE))
- dma_unmap_single(hcd->self.controller,
+ dma_unmap_single(hcd->self.sysdev,
urb->transfer_dma,
urb->transfer_buffer_length,
dir);
@@ -1523,11 +1524,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
return ret;
if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) {
urb->setup_dma = dma_map_single(
- hcd->self.controller,
+ hcd->self.sysdev,
urb->setup_packet,
sizeof(struct usb_ctrlrequest),
DMA_TO_DEVICE);
- if (dma_mapping_error(hcd->self.controller,
+ if (dma_mapping_error(hcd->self.sysdev,
urb->setup_dma))
return -EAGAIN;
urb->transfer_flags |= URB_SETUP_MAP_SINGLE;
@@ -1558,7 +1559,7 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
}
n = dma_map_sg(
- hcd->self.controller,
+ hcd->self.sysdev,
urb->sg,
urb->num_sgs,
dir);
@@ -1573,12 +1574,12 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
} else if (urb->sg) {
struct scatterlist *sg = urb->sg;
urb->transfer_dma = dma_map_page(
- hcd->self.controller,
+ hcd->self.sysdev,
sg_page(sg),
sg->offset,
urb->transfer_buffer_length,
dir);
- if (dma_mapping_error(hcd->self.controller,
+ if (dma_mapping_error(hcd->self.sysdev,
urb->transfer_dma))
ret = -EAGAIN;
else
@@ -1588,11 +1589,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
ret = -EAGAIN;
} else {
urb->transfer_dma = dma_map_single(
- hcd->self.controller,
+ hcd->self.sysdev,
urb->transfer_buffer,
urb->transfer_buffer_length,
dir);
- if (dma_mapping_error(hcd->self.controller,
+ if (dma_mapping_error(hcd->self.sysdev,
urb->transfer_dma))
ret = -EAGAIN;
else
@@ -2232,6 +2233,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
@@ -2501,24 +2636,8 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh);
}
-/**
- * usb_create_shared_hcd - create and initialize an HCD structure
- * @driver: HC driver that will use this hcd
- * @dev: device for this HC, stored in hcd->self.controller
- * @bus_name: value to store in hcd->self.bus_name
- * @primary_hcd: a pointer to the usb_hcd structure that is sharing the
- * PCI device. Only allocate certain resources for the primary HCD
- * Context: !in_interrupt()
- *
- * Allocate a struct usb_hcd, with extra space at the end for the
- * HC driver's private data. Initialize the generic members of the
- * hcd structure.
- *
- * Return: On success, a pointer to the created and initialized HCD structure.
- * On failure (e.g. if memory is unavailable), %NULL.
- */
-struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
- struct device *dev, const char *bus_name,
+struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,
+ struct device *sysdev, struct device *dev, const char *bus_name,
struct usb_hcd *primary_hcd)
{
struct usb_hcd *hcd;
@@ -2560,8 +2679,9 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
usb_bus_init(&hcd->self);
hcd->self.controller = dev;
+ hcd->self.sysdev = sysdev;
hcd->self.bus_name = bus_name;
- hcd->self.uses_dma = (dev->dma_mask != NULL);
+ hcd->self.uses_dma = (sysdev->dma_mask != NULL);
init_timer(&hcd->rh_timer);
hcd->rh_timer.function = rh_timer_func;
@@ -2576,6 +2696,30 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
"USB Host Controller";
return hcd;
}
+EXPORT_SYMBOL_GPL(__usb_create_hcd);
+
+/**
+ * usb_create_shared_hcd - create and initialize an HCD structure
+ * @driver: HC driver that will use this hcd
+ * @dev: device for this HC, stored in hcd->self.controller
+ * @bus_name: value to store in hcd->self.bus_name
+ * @primary_hcd: a pointer to the usb_hcd structure that is sharing the
+ * PCI device. Only allocate certain resources for the primary HCD
+ * Context: !in_interrupt()
+ *
+ * Allocate a struct usb_hcd, with extra space at the end for the
+ * HC driver's private data. Initialize the generic members of the
+ * hcd structure.
+ *
+ * Return: On success, a pointer to the created and initialized HCD structure.
+ * On failure (e.g. if memory is unavailable), %NULL.
+ */
+struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
+ struct device *dev, const char *bus_name,
+ struct usb_hcd *primary_hcd)
+{
+ return __usb_create_hcd(driver, dev, dev, bus_name, primary_hcd);
+}
EXPORT_SYMBOL_GPL(usb_create_shared_hcd);
/**
@@ -2595,7 +2739,7 @@ EXPORT_SYMBOL_GPL(usb_create_shared_hcd);
struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
struct device *dev, const char *bus_name)
{
- return usb_create_shared_hcd(driver, dev, bus_name, NULL);
+ return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}
EXPORT_SYMBOL_GPL(usb_create_hcd);
@@ -2722,7 +2866,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
struct usb_device *rhdev;
if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) {
- struct usb_phy *phy = usb_get_phy_dev(hcd->self.controller, 0);
+ struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0);
if (IS_ERR(phy)) {
retval = PTR_ERR(phy);
@@ -2740,7 +2884,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
}
if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) {
- struct phy *phy = phy_get(hcd->self.controller, "usb");
+ struct phy *phy = phy_get(hcd->self.sysdev, "usb");
if (IS_ERR(phy)) {
retval = PTR_ERR(phy);
@@ -2788,7 +2932,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
*/
retval = hcd_buffer_create(hcd);
if (retval != 0) {
- dev_dbg(hcd->self.controller, "pool alloc failed\n");
+ dev_dbg(hcd->self.sysdev, "pool alloc failed\n");
goto err_create_buf;
}
@@ -2798,7 +2942,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
if (rhdev == NULL) {
- dev_err(hcd->self.controller, "unable to allocate root hub\n");
+ dev_err(hcd->self.sysdev, "unable to allocate root hub\n");
retval = -ENOMEM;
goto err_allocate_root_hub;
}
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 7aee55244b4a..6436ca2f5d3e 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -21,6 +21,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>
@@ -1113,6 +1114,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;
@@ -2207,9 +2212,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
@@ -2250,6 +2255,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 */
@@ -2266,7 +2277,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
}
}
#endif
- return err;
+ return 0;
}
@@ -2288,6 +2299,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);
@@ -2319,8 +2331,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);
@@ -4364,6 +4378,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
enum usb_device_speed oldspeed = udev->speed;
const char *speed;
int devnum = udev->devnum;
+ const char *driver_name;
/* root hub ports have a slightly longer reset period
* (from USB 2.0 spec, section 7.1.7.5)
@@ -4431,11 +4446,23 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
else
speed = usb_speed_string(udev->speed);
+ /*
+ * The controller driver may be NULL if the controller device
+ * is the middle device between platform device and roothub.
+ * This middle device may not need a device driver due to
+ * all hardware control can be at platform device driver, this
+ * platform device is usually a dual-role USB controller device.
+ */
+ if (udev->bus->controller->driver)
+ driver_name = udev->bus->controller->driver->name;
+ else
+ driver_name = udev->bus->sysdev->driver->name;
+
if (udev->speed < USB_SPEED_SUPER)
dev_info(&udev->dev,
"%s %s USB device number %d using %s\n",
(udev->config) ? "reset" : "new", speed,
- devnum, udev->bus->controller->driver->name);
+ devnum, driver_name);
/* Set up TT records, if needed */
if (hdev->tt) {
@@ -4537,7 +4564,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;
@@ -4569,7 +4597,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
"%s SuperSpeed%s USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
(udev->speed == USB_SPEED_SUPER_PLUS) ? "Plus" : "",
- devnum, udev->bus->controller->driver->name);
+ devnum, driver_name);
}
/* cope with hardware quirkiness:
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/core/usb.c b/drivers/usb/core/usb.c
index eaf1c3b06f02..e1ab778e774a 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -452,9 +452,9 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
* Note: calling dma_set_mask() on a USB device would set the
* mask for the entire HCD, so don't do that.
*/
- dev->dev.dma_mask = bus->controller->dma_mask;
- dev->dev.dma_pfn_offset = bus->controller->dma_pfn_offset;
- set_dev_node(&dev->dev, dev_to_node(bus->controller));
+ dev->dev.dma_mask = bus->sysdev->dma_mask;
+ dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset;
+ set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
atomic_set(&dev->urbnum, 0);
@@ -802,7 +802,7 @@ struct urb *usb_buffer_map(struct urb *urb)
if (!urb
|| !urb->dev
|| !(bus = urb->dev->bus)
- || !(controller = bus->controller))
+ || !(controller = bus->sysdev))
return NULL;
if (controller->dma_mask) {
@@ -840,7 +840,7 @@ void usb_buffer_dmasync(struct urb *urb)
|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
|| !urb->dev
|| !(bus = urb->dev->bus)
- || !(controller = bus->controller))
+ || !(controller = bus->sysdev))
return;
if (controller->dma_mask) {
@@ -874,7 +874,7 @@ void usb_buffer_unmap(struct urb *urb)
|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
|| !urb->dev
|| !(bus = urb->dev->bus)
- || !(controller = bus->controller))
+ || !(controller = bus->sysdev))
return;
if (controller->dma_mask) {
@@ -924,7 +924,7 @@ int usb_buffer_map_sg(const struct usb_device *dev, int is_in,
if (!dev
|| !(bus = dev->bus)
- || !(controller = bus->controller)
+ || !(controller = bus->sysdev)
|| !controller->dma_mask)
return -EINVAL;
@@ -960,7 +960,7 @@ void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in,
if (!dev
|| !(bus = dev->bus)
- || !(controller = bus->controller)
+ || !(controller = bus->sysdev)
|| !controller->dma_mask)
return;
@@ -988,7 +988,7 @@ void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in,
if (!dev
|| !(bus = dev->bus)
- || !(controller = bus->controller)
+ || !(controller = bus->sysdev)
|| !controller->dma_mask)
return;
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index b97cde76914d..3a0481b93118 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -41,6 +41,7 @@ config USB_DWC3_GADGET
config USB_DWC3_DUAL_ROLE
bool "Dual Role mode"
depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3))
+ depends on (EXTCON=y || EXTCON=USB_DWC3)
help
This is the default mode of working of DWC3 controller where
both host and gadget features are enabled.
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 22420e17d68b..e2a3b265b066 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -13,6 +13,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += gadget.o ep0.o
endif
+ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
+ dwc3-y += drd.o
+endif
+
ifneq ($(CONFIG_USB_DWC3_ULPI),)
dwc3-y += ulpi.o
endif
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 53b26e978d90..f4f2a1337dd4 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
return 0;
}
-void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+static int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
+static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
{
u32 reg;
@@ -110,6 +113,93 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}
+static void __dwc3_set_mode(struct work_struct *work)
+{
+ struct dwc3 *dwc = work_to_dwc(work);
+ unsigned long flags;
+ int ret;
+
+ if (!dwc->desired_dr_role)
+ return;
+
+ if (dwc->desired_dr_role == dwc->current_dr_role)
+ return;
+
+ if (dwc->dr_mode != USB_DR_MODE_OTG)
+ return;
+
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ dwc3_host_exit(dwc);
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ dwc3_gadget_exit(dwc);
+ dwc3_event_buffers_cleanup(dwc);
+ break;
+ default:
+ break;
+ }
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ dwc3_set_prtcap(dwc, dwc->desired_dr_role);
+
+ dwc->current_dr_role = dwc->desired_dr_role;
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ switch (dwc->desired_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ ret = dwc3_host_init(dwc);
+ if (ret)
+ dev_err(dwc->dev, "failed to initialize host\n");
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ dwc3_event_buffers_setup(dwc);
+ ret = dwc3_gadget_init(dwc);
+ if (ret)
+ dev_err(dwc->dev, "failed to initialize peripheral\n");
+ break;
+ default:
+ break;
+ }
+}
+
+void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc->desired_dr_role = mode;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ 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;
@@ -663,6 +753,9 @@ static int dwc3_core_init(struct dwc3 *dwc)
if (ret)
goto err0;
+ /* Set suspend_clk */
+ dwc3_set_suspend_clk(dwc);
+
ret = dwc3_phy_setup(dwc);
if (ret)
goto err0;
@@ -760,21 +853,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
goto err4;
}
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
- break;
- case USB_DR_MODE_HOST:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
- break;
- case USB_DR_MODE_OTG:
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
- break;
- default:
- dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode);
- break;
- }
-
/*
* ENDXFER polling is available on version 3.10a and later of
* the DWC_usb3 controller. It is NOT available in the
@@ -882,6 +960,8 @@ 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);
ret = dwc3_gadget_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
@@ -890,6 +970,8 @@ 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);
ret = dwc3_host_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
@@ -898,17 +980,11 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_OTG:
- ret = dwc3_host_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
-
- ret = dwc3_gadget_init(dwc);
+ INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
+ ret = dwc3_drd_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize gadget\n");
+ dev_err(dev, "failed to initialize dual-role\n");
return ret;
}
break;
@@ -930,8 +1006,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
dwc3_host_exit(dwc);
break;
case USB_DR_MODE_OTG:
- dwc3_host_exit(dwc);
- dwc3_gadget_exit(dwc);
+ dwc3_drd_exit(dwc);
break;
default:
/* do nothing */
@@ -1061,6 +1136,19 @@ static int dwc3_probe(struct platform_device *pdev)
dwc->hird_threshold = hird_threshold
| (dwc->is_utmi_l1_suspend << 4);
+ 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);
@@ -1205,21 +1293,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;
}
@@ -1228,18 +1314,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;
@@ -1250,7 +1335,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)
@@ -1293,12 +1378,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;
@@ -1314,13 +1398,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 affb3d757310..c621a0fbc9a7 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -26,6 +26,7 @@
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/debugfs.h>
+#include <linux/wait.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -176,6 +177,7 @@
/* Global Configuration Register */
#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19)
+#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff)
#define DWC3_GCTL_U2RSTECN (1 << 16)
#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6)
#define DWC3_GCTL_CLK_BUS (0)
@@ -451,6 +453,8 @@
#define DWC3_DEPCMD_SETTRANSFRESOURCE (0x02 << 0)
#define DWC3_DEPCMD_SETEPCONFIG (0x01 << 0)
+#define DWC3_DEPCMD_CMD(x) ((x) & 0xf)
+
/* The EP number goes 0..31 so ep0 is always out and ep1 is always in */
#define DWC3_DALEPENA_EP(n) (1 << n)
@@ -500,6 +504,7 @@ struct dwc3_event_buffer {
* @endpoint: usb endpoint
* @pending_list: list of pending requests for this endpoint
* @started_list: list of started requests on this endpoint
+ * @wait_end_transfer: wait_queue_head_t for waiting on End Transfer complete
* @lock: spinlock for endpoint request queue traversal
* @regs: pointer to first endpoint register
* @trb_pool: array of transaction buffers
@@ -525,6 +530,8 @@ struct dwc3_ep {
struct list_head pending_list;
struct list_head started_list;
+ wait_queue_head_t wait_end_transfer;
+
spinlock_t lock;
void __iomem *regs;
@@ -541,6 +548,8 @@ struct dwc3_ep {
#define DWC3_EP_BUSY (1 << 4)
#define DWC3_EP_PENDING_REQUEST (1 << 5)
#define DWC3_EP_MISSED_ISOC (1 << 6)
+#define DWC3_EP_END_TRANSFER_PENDING (1 << 7)
+#define DWC3_EP_TRANSFER_STARTED (1 << 8)
/* This last one is specific to EP0 */
#define DWC3_EP0_DIR_IN (1 << 31)
@@ -740,6 +749,7 @@ struct dwc3_scratchpad_array {
/**
* struct dwc3 - representation of our controller
* @ctrl_req: usb control request which is used for ep0
+ * @drd_work - workqueue used for role swapping
* @ep0_trb: trb which is used for the ctrl_req
* @ep0_bounce: bounce buffer for ep0
* @zlp_buf: used when request->zero is set
@@ -764,6 +774,10 @@ struct dwc3_scratchpad_array {
* @maximum_speed: maximum speed requested (mainly for testing purposes)
* @revision: revision register contents
* @dr_mode: requested mode of operation
+ * @current_dr_role: current role of operation when in dual-role mode
+ * @desired_dr_role: desired role of operation when in dual-role mode
+ * @edev: extcon handle
+ * @edev_nb: extcon notifier
* @hsphy_mode: UTMI phy mode, one of following:
* - USBPHY_INTERFACE_MODE_UTMI
* - USBPHY_INTERFACE_MODE_UTMIW
@@ -834,9 +848,11 @@ struct dwc3_scratchpad_array {
* 1 - -3.5dB de-emphasis
* 2 - No de-emphasis
* 3 - Reserved
+ * @otg_caps: the OTG capabilities from hardware point
*/
struct dwc3 {
struct usb_ctrlrequest *ctrl_req;
+ struct work_struct drd_work;
struct dwc3_trb *ep0_trb;
void *ep0_bounce;
void *zlp_buf;
@@ -874,6 +890,10 @@ struct dwc3 {
size_t regs_size;
enum usb_dr_mode dr_mode;
+ u32 current_dr_role;
+ u32 desired_dr_role;
+ struct extcon_dev *edev;
+ struct notifier_block edev_nb;
enum usb_phy_interface hsphy_mode;
u32 fladj;
@@ -979,9 +999,10 @@ struct dwc3 {
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
+ struct usb_otg_caps otg_caps;
};
-/* -------------------------------------------------------------------------- */
+#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work))
/* -------------------------------------------------------------------------- */
@@ -1046,6 +1067,9 @@ struct dwc3_event_depevt {
#define DEPEVT_TRANSFER_BUS_EXPIRY 2
u32 parameters:16;
+
+/* For Command Complete Events */
+#define DEPEVT_PARAMETER_CMD(n) (((n) & (0xf << 8)) >> 8)
} __packed;
/**
@@ -1180,6 +1204,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
{ return 0; }
#endif
+#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+int dwc3_drd_init(struct dwc3 *dwc);
+void dwc3_drd_exit(struct dwc3 *dwc);
+#else
+static inline int dwc3_drd_init(struct dwc3 *dwc)
+{ return 0; }
+static inline void dwc3_drd_exit(struct dwc3 *dwc)
+{ }
+#endif
+
/* power management interface */
#if !IS_ENABLED(CONFIG_USB_DWC3_HOST)
int dwc3_gadget_suspend(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index 31926dda43c9..ef7dccb55c47 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -319,7 +319,6 @@ static ssize_t dwc3_mode_write(struct file *file,
{
struct seq_file *s = file->private_data;
struct dwc3 *dwc = s->private;
- unsigned long flags;
u32 mode = 0;
char buf[32];
@@ -327,19 +326,16 @@ static ssize_t dwc3_mode_write(struct file *file,
return -EFAULT;
if (!strncmp(buf, "host", 4))
- mode |= DWC3_GCTL_PRTCAP_HOST;
+ mode = DWC3_GCTL_PRTCAP_HOST;
if (!strncmp(buf, "device", 6))
- mode |= DWC3_GCTL_PRTCAP_DEVICE;
+ mode = DWC3_GCTL_PRTCAP_DEVICE;
if (!strncmp(buf, "otg", 3))
- mode |= DWC3_GCTL_PRTCAP_OTG;
+ mode = DWC3_GCTL_PRTCAP_OTG;
+
+ dwc3_set_mode(dwc, mode);
- if (mode) {
- spin_lock_irqsave(&dwc->lock, flags);
- dwc3_set_mode(dwc, mode);
- spin_unlock_irqrestore(&dwc->lock, flags);
- }
return count;
}
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
new file mode 100644
index 000000000000..2765c51c7ef5
--- /dev/null
+++ b/drivers/usb/dwc3/drd.c
@@ -0,0 +1,85 @@
+/**
+ * drd.c - DesignWare USB3 DRD Controller Dual-role support
+ *
+ * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Roger Quadros <rogerq@ti.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/extcon.h>
+
+#include "debug.h"
+#include "core.h"
+#include "gadget.h"
+
+static void dwc3_drd_update(struct dwc3 *dwc)
+{
+ int id;
+
+ id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+ if (id < 0)
+ id = 0;
+
+ dwc3_set_mode(dwc, id ?
+ DWC3_GCTL_PRTCAP_HOST :
+ DWC3_GCTL_PRTCAP_DEVICE);
+}
+
+static int dwc3_drd_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
+
+ dwc3_set_mode(dwc, event ?
+ DWC3_GCTL_PRTCAP_HOST :
+ DWC3_GCTL_PRTCAP_DEVICE);
+
+ return NOTIFY_DONE;
+}
+
+int dwc3_drd_init(struct dwc3 *dwc)
+{
+ int ret;
+
+ if (dwc->dev->of_node) {
+ if (of_property_read_bool(dwc->dev->of_node, "extcon"))
+ dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
+
+ if (IS_ERR(dwc->edev))
+ return PTR_ERR(dwc->edev);
+
+ dwc->edev_nb.notifier_call = dwc3_drd_notifier;
+ ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
+ &dwc->edev_nb);
+ if (ret < 0) {
+ dev_err(dwc->dev, "couldn't register cable notifier\n");
+ return ret;
+ }
+ }
+
+ dwc3_drd_update(dwc);
+
+ return 0;
+}
+
+void dwc3_drd_exit(struct dwc3 *dwc)
+{
+ extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+ &dwc->edev_nb);
+
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+ flush_work(&dwc->drd_work);
+ dwc3_gadget_exit(dwc);
+}
diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c
index 58526932d2b6..29433c80be10 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -180,6 +180,7 @@ static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "xlnx,zynqmp-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
+ { .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 26efe8c7535f..31a07649d99f 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>
@@ -247,7 +248,11 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
struct dwc3_gadget_ep_cmd_params *params)
{
struct dwc3 *dwc = dep->dwc;
- u32 timeout = 1000;
+ /*
+ * FIXME need check why 500 times check
+ * is not enough.
+ */
+ u32 timeout = 20000;
u32 reg;
int cmd_status = 0;
@@ -271,7 +276,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
}
}
- if (cmd == DWC3_DEPCMD_STARTTRANSFER) {
+ if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) {
int needs_wakeup;
needs_wakeup = (dwc->link_state == DWC3_LINK_STATE_U1 ||
@@ -331,6 +336,20 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
trace_dwc3_gadget_ep_cmd(dep, cmd, params, cmd_status);
+ if (ret == 0) {
+ switch (DWC3_DEPCMD_CMD(cmd)) {
+ case DWC3_DEPCMD_STARTTRANSFER:
+ dep->flags |= DWC3_EP_TRANSFER_STARTED;
+ break;
+ case DWC3_DEPCMD_ENDTRANSFER:
+ dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ }
+
if (unlikely(susphy)) {
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
@@ -584,11 +603,14 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep,
dep->comp_desc = comp_desc;
dep->type = usb_endpoint_type(desc);
dep->flags |= DWC3_EP_ENABLED;
+ dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
reg = dwc3_readl(dwc->regs, DWC3_DALEPENA);
reg |= DWC3_DALEPENA_EP(dep->number);
dwc3_writel(dwc->regs, DWC3_DALEPENA, reg);
+ init_waitqueue_head(&dep->wait_end_transfer);
+
if (usb_endpoint_xfer_control(desc))
return 0;
@@ -661,7 +683,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
dep->endpoint.desc = NULL;
dep->comp_desc = NULL;
dep->type = 0;
- dep->flags = 0;
+ dep->flags &= DWC3_EP_END_TRANSFER_PENDING;
return 0;
}
@@ -1060,6 +1082,14 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param)
return 0;
}
+static int __dwc3_gadget_get_frame(struct dwc3 *dwc)
+{
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ return DWC3_DSTS_SOFFN(reg);
+}
+
static void __dwc3_gadget_start_isoc(struct dwc3 *dwc,
struct dwc3_ep *dep, u32 cur_uf)
{
@@ -1137,12 +1167,27 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
* errors which will force us issue EndTransfer command.
*/
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
- if ((dep->flags & DWC3_EP_PENDING_REQUEST) &&
- list_empty(&dep->started_list)) {
- dwc3_stop_active_transfer(dwc, dep->number, true);
- dep->flags = DWC3_EP_ENABLED;
+ if ((dep->flags & DWC3_EP_PENDING_REQUEST)) {
+ if (dep->flags & DWC3_EP_TRANSFER_STARTED) {
+ dwc3_stop_active_transfer(dwc, dep->number, true);
+ dep->flags = DWC3_EP_ENABLED;
+ } else {
+ u32 cur_uf;
+
+ cur_uf = __dwc3_gadget_get_frame(dwc);
+ __dwc3_gadget_start_isoc(dwc, dep, cur_uf);
+ }
+ return 0;
}
- return 0;
+
+ if ((dep->flags & DWC3_EP_BUSY) &&
+ !(dep->flags & DWC3_EP_MISSED_ISOC)) {
+ WARN_ON_ONCE(!dep->resource_index);
+ ret = __dwc3_gadget_kick_transfer(dep,
+ dep->resource_index);
+ }
+
+ goto out;
}
if (!dwc3_calc_trbs_left(dep))
@@ -1153,6 +1198,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
dwc3_trace(trace_dwc3_gadget,
"%s: failed to kick transfers",
dep->name);
+out:
if (ret == -EBUSY)
ret = 0;
@@ -1243,6 +1289,52 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
if (r == req) {
/* wait until it is processed */
dwc3_stop_active_transfer(dwc, dep->number, true);
+
+ /*
+ * If request was already started, this means we had to
+ * stop the transfer. With that we also need to ignore
+ * all TRBs used by the request, however TRBs can only
+ * be modified after completion of END_TRANSFER
+ * command. So what we do here is that we wait for
+ * END_TRANSFER completion and only after that, we jump
+ * over TRBs by clearing HWO and incrementing dequeue
+ * pointer.
+ *
+ * Note that we have 2 possible types of transfers here:
+ *
+ * i) Linear buffer request
+ * ii) SG-list based request
+ *
+ * SG-list based requests will have r->num_pending_sgs
+ * set to a valid number (> 0). Linear requests,
+ * normally use a single TRB.
+ *
+ * All of these cases need to be taken into
+ * consideration so we don't mess up our TRB ring
+ * pointers.
+ */
+ wait_event_lock_irq(dep->wait_end_transfer,
+ !(dep->flags & DWC3_EP_END_TRANSFER_PENDING),
+ dwc->lock);
+
+ if (!r->trb)
+ goto out1;
+
+ if (r->num_pending_sgs) {
+ struct dwc3_trb *trb;
+ int i = 0;
+
+ for (i = 0; i < r->num_pending_sgs; i++) {
+ trb = r->trb + i;
+ trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+ dwc3_ep_inc_deq(dep);
+ }
+ } else {
+ struct dwc3_trb *trb = r->trb;
+
+ trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+ dwc3_ep_inc_deq(dep);
+ }
goto out1;
}
dev_err(dwc->dev, "request %pK was not queued to %s\n",
@@ -1253,6 +1345,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
out1:
/* giveback the request */
+ dep->queued_requests--;
dwc3_gadget_giveback(dep, req, -ECONNRESET);
out0:
@@ -1280,9 +1373,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol)
unsigned transfer_in_flight;
unsigned started;
- if (dep->flags & DWC3_EP_STALL)
- return 0;
-
if (dep->number > 1)
trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue);
else
@@ -1307,8 +1397,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol)
else
dep->flags |= DWC3_EP_STALL;
} else {
- if (!(dep->flags & DWC3_EP_STALL))
- return 0;
ret = dwc3_send_clear_stall_ep_cmd(dep);
if (ret)
@@ -1391,10 +1479,8 @@ static const struct usb_ep_ops dwc3_gadget_ep_ops = {
static int dwc3_gadget_get_frame(struct usb_gadget *g)
{
struct dwc3 *dwc = gadget_to_dwc(g);
- u32 reg;
- reg = dwc3_readl(dwc->regs, DWC3_DSTS);
- return DWC3_DSTS_SOFFN(reg);
+ return __dwc3_gadget_get_frame(dwc);
}
static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
@@ -1768,9 +1854,6 @@ err0:
static void __dwc3_gadget_stop(struct dwc3 *dwc)
{
- if (pm_runtime_suspended(dwc->dev))
- return;
-
dwc3_gadget_disable_irq(dwc);
__dwc3_gadget_ep_disable(dwc->eps[0]);
__dwc3_gadget_ep_disable(dwc->eps[1]);
@@ -1780,9 +1863,30 @@ static int dwc3_gadget_stop(struct usb_gadget *g)
{
struct dwc3 *dwc = gadget_to_dwc(g);
unsigned long flags;
+ int epnum;
spin_lock_irqsave(&dwc->lock, flags);
+
+ if (pm_runtime_suspended(dwc->dev))
+ goto out;
+
__dwc3_gadget_stop(dwc);
+
+ for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+ struct dwc3_ep *dep = dwc->eps[epnum];
+
+ if (!dep)
+ continue;
+
+ if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING))
+ continue;
+
+ wait_event_lock_irq(dep->wait_end_transfer,
+ !(dep->flags & DWC3_EP_END_TRANSFER_PENDING),
+ dwc->lock);
+ }
+
+out:
dwc->gadget_driver = NULL;
spin_unlock_irqrestore(&dwc->lock, flags);
@@ -2156,10 +2260,12 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
{
struct dwc3_ep *dep;
u8 epnum = event->endpoint_number;
+ u8 cmd;
dep = dwc->eps[epnum];
- if (!(dep->flags & DWC3_EP_ENABLED))
+ if (!(dep->flags & DWC3_EP_ENABLED) &&
+ !(dep->flags & DWC3_EP_END_TRANSFER_PENDING))
return;
if (epnum == 0 || epnum == 1) {
@@ -2227,11 +2333,15 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
"unable to find suitable stream");
}
break;
- case DWC3_DEPEVT_RXTXFIFOEVT:
- dwc3_trace(trace_dwc3_gadget, "%s FIFO Overrun", dep->name);
- break;
case DWC3_DEPEVT_EPCMDCMPLT:
- dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete");
+ cmd = DEPEVT_PARAMETER_CMD(event->parameters);
+
+ if (cmd == DWC3_DEPCMD_ENDTRANSFER) {
+ dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
+ wake_up(&dep->wait_end_transfer);
+ }
+ break;
+ case DWC3_DEPEVT_RXTXFIFOEVT:
break;
}
}
@@ -2284,7 +2394,8 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force)
dep = dwc->eps[epnum];
- if (!dep->resource_index)
+ if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) ||
+ !dep->resource_index)
return;
/*
@@ -2328,8 +2439,10 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force)
dep->resource_index = 0;
dep->flags &= ~DWC3_EP_BUSY;
- if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A)
+ if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A) {
+ dep->flags |= DWC3_EP_END_TRANSFER_PENDING;
udelay(100);
+ }
}
static void dwc3_stop_active_transfers(struct dwc3 *dwc)
@@ -2389,14 +2502,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
@@ -3000,7 +3119,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 f3ee80ece682..9f4b89b3d37d 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -337,6 +337,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 d2fc237cd87a..e23150fb50e6 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -329,8 +329,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)
{
@@ -1148,6 +1155,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;
@@ -1169,6 +1185,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;
}
@@ -1662,7 +1682,20 @@ static int 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);
@@ -1853,6 +1886,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]) {
@@ -2522,7 +2563,12 @@ static int fsg_main_thread(void *common_)
* 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) {
@@ -2988,6 +3034,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);
@@ -3039,6 +3089,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)
@@ -3102,6 +3157,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..59ec7479bd71
--- /dev/null
+++ b/drivers/usb/gadget/function/fsl_updater.c
@@ -0,0 +1,647 @@
+/*
+ * 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);
+ 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,
+ &bh->inreq_busy, &bh->state);
+
+ 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,
+ &bh->outreq_busy, &bh->state);
+ 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);
+ 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);
+ 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,
+ &bh->inreq_busy, &bh->state);
+ 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/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index 139f6cce30b1..53abf7e7875b 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1274,6 +1274,7 @@ void usb_del_gadget_udc(struct usb_gadget *gadget)
flush_work(&gadget->work);
device_unregister(&udc->dev);
device_unregister(&gadget->dev);
+ memset(&gadget->dev, 0x00, sizeof(gadget->dev));
}
EXPORT_SYMBOL_GPL(usb_del_gadget_udc);
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 063064801ceb..f75423664003 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 74f62d68f013..9de412272dea 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -722,145 +722,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.controller,
- urb->setup_packet,
- sizeof(struct usb_ctrlrequest),
- DMA_TO_DEVICE);
- urb->transfer_dma = dma_map_single(
- hcd->self.controller,
- 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,
@@ -935,7 +796,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-mem.c b/drivers/usb/host/ehci-mem.c
index 4de43011df23..9b7e63977215 100644
--- a/drivers/usb/host/ehci-mem.c
+++ b/drivers/usb/host/ehci-mem.c
@@ -138,7 +138,7 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci)
ehci->sitd_pool = NULL;
if (ehci->periodic)
- dma_free_coherent (ehci_to_hcd(ehci)->self.controller,
+ dma_free_coherent(ehci_to_hcd(ehci)->self.sysdev,
ehci->periodic_size * sizeof (u32),
ehci->periodic, ehci->periodic_dma);
ehci->periodic = NULL;
@@ -155,7 +155,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
/* QTDs for control/bulk/intr transfers */
ehci->qtd_pool = dma_pool_create ("ehci_qtd",
- ehci_to_hcd(ehci)->self.controller,
+ ehci_to_hcd(ehci)->self.sysdev,
sizeof (struct ehci_qtd),
32 /* byte alignment (for hw parts) */,
4096 /* can't cross 4K */);
@@ -165,7 +165,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
/* QHs for control/bulk/intr transfers */
ehci->qh_pool = dma_pool_create ("ehci_qh",
- ehci_to_hcd(ehci)->self.controller,
+ ehci_to_hcd(ehci)->self.sysdev,
sizeof(struct ehci_qh_hw),
32 /* byte alignment (for hw parts) */,
4096 /* can't cross 4K */);
@@ -179,7 +179,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
/* ITD for high speed ISO transfers */
ehci->itd_pool = dma_pool_create ("ehci_itd",
- ehci_to_hcd(ehci)->self.controller,
+ ehci_to_hcd(ehci)->self.sysdev,
sizeof (struct ehci_itd),
32 /* byte alignment (for hw parts) */,
4096 /* can't cross 4K */);
@@ -189,7 +189,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
/* SITD for full/low speed split ISO transfers */
ehci->sitd_pool = dma_pool_create ("ehci_sitd",
- ehci_to_hcd(ehci)->self.controller,
+ ehci_to_hcd(ehci)->self.sysdev,
sizeof (struct ehci_sitd),
32 /* byte alignment (for hw parts) */,
4096 /* can't cross 4K */);
@@ -199,7 +199,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
/* Hardware periodic table */
ehci->periodic = (__le32 *)
- dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller,
+ dma_alloc_coherent(ehci_to_hcd(ehci)->self.sysdev,
ehci->periodic_size * sizeof(__le32),
&ehci->periodic_dma, flags);
if (ehci->periodic == NULL) {
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index eca3710d8fc4..27b53f4386bd 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -1169,7 +1169,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
@@ -1203,10 +1203,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*/
@@ -1243,12 +1243,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/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c
index 3425154baf8b..faecbfbeb874 100644
--- a/drivers/usb/host/xhci-dbg.c
+++ b/drivers/usb/host/xhci-dbg.c
@@ -240,6 +240,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)
@@ -253,7 +256,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_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb)
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 0722f75f1d6a..638fe817c85f 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -558,6 +558,121 @@ static int xhci_get_ports(struct usb_hcd *hcd, __le32 __iomem ***port_array)
return max_ports;
}
+static __le32 __iomem *xhci_get_port_io_addr(struct usb_hcd *hcd, int index)
+{
+ __le32 __iomem **port_array;
+
+ xhci_get_ports(hcd, &port_array);
+ return port_array[index];
+}
+
+/*
+ * xhci_set_port_power() must be called with xhci->lock held.
+ * It will release and re-aquire the lock while calling ACPI
+ * method.
+ */
+static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd,
+ u16 index, bool on, unsigned long *flags)
+{
+ __le32 __iomem *addr;
+ u32 temp;
+
+ addr = xhci_get_port_io_addr(hcd, index);
+ temp = readl(addr);
+ temp = xhci_port_state_to_neutral(temp);
+ if (on) {
+ /* Power on */
+ writel(temp | PORT_POWER, addr);
+ temp = readl(addr);
+ xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n",
+ index, temp);
+ } else {
+ /* Power off */
+ writel(temp & ~PORT_POWER, addr);
+ }
+
+ spin_unlock_irqrestore(&xhci->lock, *flags);
+ temp = usb_acpi_power_manageable(hcd->self.root_hub,
+ index);
+ if (temp)
+ usb_acpi_set_power_state(hcd->self.root_hub,
+ index, on);
+ spin_lock_irqsave(&xhci->lock, *flags);
+}
+
+static void xhci_port_set_test_mode(struct xhci_hcd *xhci,
+ u16 test_mode, u16 wIndex)
+{
+ u32 temp;
+ __le32 __iomem *addr;
+
+ /* xhci only supports test mode for usb2 ports, i.e. xhci->main_hcd */
+ addr = xhci_get_port_io_addr(xhci->main_hcd, wIndex);
+ temp = readl(addr + PORTPMSC);
+ temp |= test_mode << PORT_TEST_MODE_SHIFT;
+ writel(temp, addr + PORTPMSC);
+ xhci->test_mode = test_mode;
+ if (test_mode == TEST_FORCE_EN)
+ xhci_start(xhci);
+}
+
+static int xhci_enter_test_mode(struct xhci_hcd *xhci,
+ u16 test_mode, u16 wIndex, unsigned long *flags)
+{
+ int i, retval;
+
+ /* Disable all Device Slots */
+ xhci_dbg(xhci, "Disable all slots\n");
+ spin_unlock_irqrestore(&xhci->lock, *flags);
+ for (i = 1; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) {
+ retval = xhci_disable_slot(xhci, NULL, i);
+ if (retval)
+ xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n",
+ i, retval);
+ }
+ spin_lock_irqsave(&xhci->lock, *flags);
+ /* Put all ports to the Disable state by clear PP */
+ xhci_dbg(xhci, "Disable all port (PP = 0)\n");
+ /* Power off USB3 ports*/
+ for (i = 0; i < xhci->num_usb3_ports; i++)
+ xhci_set_port_power(xhci, xhci->shared_hcd, i, false, flags);
+ /* Power off USB2 ports*/
+ for (i = 0; i < xhci->num_usb2_ports; i++)
+ xhci_set_port_power(xhci, xhci->main_hcd, i, false, flags);
+ /* Stop the controller */
+ xhci_dbg(xhci, "Stop controller\n");
+ retval = xhci_halt(xhci);
+ if (retval)
+ return retval;
+ /* Disable runtime PM for test mode */
+ pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller);
+ /* Set PORTPMSC.PTC field to enter selected test mode */
+ /* Port is selected by wIndex. port_id = wIndex + 1 */
+ xhci_dbg(xhci, "Enter Test Mode: %d, Port_id=%d\n",
+ test_mode, wIndex + 1);
+ xhci_port_set_test_mode(xhci, test_mode, wIndex);
+ return retval;
+}
+
+static int xhci_exit_test_mode(struct xhci_hcd *xhci)
+{
+ int retval;
+
+ if (!xhci->test_mode) {
+ xhci_err(xhci, "Not in test mode, do nothing.\n");
+ return 0;
+ }
+ if (xhci->test_mode == TEST_FORCE_EN &&
+ !(xhci->xhc_state & XHCI_STATE_HALTED)) {
+ retval = xhci_halt(xhci);
+ if (retval)
+ return retval;
+ }
+ pm_runtime_allow(xhci_to_hcd(xhci)->self.controller);
+ xhci->test_mode = 0;
+ return xhci_reset(xhci);
+}
+
void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array,
int port_id, u32 link_state)
{
@@ -916,6 +1031,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 link_state = 0;
u16 wake_mask = 0;
u16 timeout = 0;
+ u16 test_mode = 0;
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
@@ -989,6 +1105,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
link_state = (wIndex & 0xff00) >> 3;
if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
wake_mask = wIndex & 0xff00;
+ if (wValue == USB_PORT_FEAT_TEST)
+ test_mode = (wIndex & 0xff00) >> 8;
/* The MSB of wIndex is the U1/U2 timeout */
timeout = (wIndex & 0xff00) >> 8;
wIndex &= 0xff;
@@ -1114,18 +1232,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
* However, hub_wq will ignore the roothub events until
* the roothub is registered.
*/
- writel(temp | PORT_POWER, port_array[wIndex]);
-
- temp = readl(port_array[wIndex]);
- xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp);
-
- spin_unlock_irqrestore(&xhci->lock, flags);
- temp = usb_acpi_power_manageable(hcd->self.root_hub,
- wIndex);
- if (temp)
- usb_acpi_set_power_state(hcd->self.root_hub,
- wIndex, true);
- spin_lock_irqsave(&xhci->lock, flags);
+ xhci_set_port_power(xhci, hcd, wIndex, true, &flags);
break;
case USB_PORT_FEAT_RESET:
temp = (temp | PORT_RESET);
@@ -1164,6 +1271,24 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
temp |= PORT_U2_TIMEOUT(timeout);
writel(temp, port_array[wIndex] + PORTPMSC);
break;
+ case USB_PORT_FEAT_TEST:
+ /* 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,
+ &flags);
+ break;
default:
goto error;
}
@@ -1229,15 +1354,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
port_array[wIndex], temp);
break;
case USB_PORT_FEAT_POWER:
- writel(temp & ~PORT_POWER, port_array[wIndex]);
-
- spin_unlock_irqrestore(&xhci->lock, flags);
- temp = usb_acpi_power_manageable(hcd->self.root_hub,
- wIndex);
- if (temp)
- usb_acpi_set_power_state(hcd->self.root_hub,
- wIndex, false);
- spin_lock_irqsave(&xhci->lock, flags);
+ xhci_set_port_power(xhci, hcd, wIndex, false, &flags);
+ break;
+ case USB_PORT_FEAT_TEST:
+ retval = xhci_exit_test_mode(xhci);
break;
default:
goto error;
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 7199e400fbac..436e787d95b4 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -586,7 +586,7 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci,
unsigned int num_stream_ctxs,
struct xhci_stream_ctx *stream_ctx, dma_addr_t dma)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs;
if (size > MEDIUM_STREAM_ARRAY_SIZE)
@@ -614,7 +614,7 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci,
unsigned int num_stream_ctxs, dma_addr_t *dma,
gfp_t mem_flags)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs;
if (size > MEDIUM_STREAM_ARRAY_SIZE)
@@ -1706,7 +1706,7 @@ void xhci_slot_copy(struct xhci_hcd *xhci,
static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
{
int i;
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
int num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
@@ -1729,36 +1729,27 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
if (!xhci->scratchpad->sp_buffers)
goto fail_sp3;
- xhci->scratchpad->sp_dma_buffers =
- kzalloc(sizeof(dma_addr_t) * num_sp, flags);
-
- if (!xhci->scratchpad->sp_dma_buffers)
- goto fail_sp4;
-
xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma);
for (i = 0; i < num_sp; i++) {
dma_addr_t dma;
void *buf = dma_zalloc_coherent(dev, xhci->page_size, &dma,
flags);
if (!buf)
- goto fail_sp5;
+ goto fail_sp4;
xhci->scratchpad->sp_array[i] = dma;
xhci->scratchpad->sp_buffers[i] = buf;
- xhci->scratchpad->sp_dma_buffers[i] = dma;
}
return 0;
- fail_sp5:
+ fail_sp4:
for (i = i - 1; i >= 0; i--) {
dma_free_coherent(dev, xhci->page_size,
xhci->scratchpad->sp_buffers[i],
- xhci->scratchpad->sp_dma_buffers[i]);
+ xhci->scratchpad->sp_array[i]);
}
- kfree(xhci->scratchpad->sp_dma_buffers);
- fail_sp4:
kfree(xhci->scratchpad->sp_buffers);
fail_sp3:
@@ -1778,7 +1769,7 @@ static void scratchpad_free(struct xhci_hcd *xhci)
{
int num_sp;
int i;
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
if (!xhci->scratchpad)
return;
@@ -1788,9 +1779,8 @@ static void scratchpad_free(struct xhci_hcd *xhci)
for (i = 0; i < num_sp; i++) {
dma_free_coherent(dev, xhci->page_size,
xhci->scratchpad->sp_buffers[i],
- xhci->scratchpad->sp_dma_buffers[i]);
+ xhci->scratchpad->sp_array[i]);
}
- kfree(xhci->scratchpad->sp_dma_buffers);
kfree(xhci->scratchpad->sp_buffers);
dma_free_coherent(dev, num_sp * sizeof(u64),
xhci->scratchpad->sp_array,
@@ -1854,7 +1844,7 @@ void xhci_free_command(struct xhci_hcd *xhci,
void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
int size;
int i, j, num_ports;
@@ -2399,7 +2389,7 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
dma_addr_t dma;
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
unsigned int val, val2;
u64 val_64;
struct xhci_segment *seg;
@@ -2445,7 +2435,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
writel(val, &xhci->op_regs->config_reg);
/*
- * Section 5.4.8 - doorbell array must be
+ * xHCI section 5.4.6 - doorbell array must be
* "physically contiguous and 64-byte (cache line) aligned".
*/
xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma,
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index ca8b0b1ae37d..248cdaa62397 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -14,11 +14,14 @@
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
+#include <linux/pci.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#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"
@@ -139,6 +142,7 @@ static int xhci_plat_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct hc_driver *driver;
+ struct device *sysdev;
struct xhci_hcd *xhci;
struct resource *res;
struct usb_hcd *hcd;
@@ -155,24 +159,47 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (irq < 0)
return irq;
+ /*
+ * sysdev must point to a device that is known to the system firmware
+ * or PCI hardware. We handle these three cases here:
+ * 1. xhci_plat comes from firmware
+ * 2. xhci_plat is child of a device from firmware (dwc3-plat)
+ * 3. xhci_plat is grandchild of a pci device (dwc3-pci)
+ */
+ sysdev = &pdev->dev;
+ if (sysdev->parent && !sysdev->of_node && sysdev->parent->of_node)
+ sysdev = sysdev->parent;
+#ifdef CONFIG_PCI
+ else if (sysdev->parent && sysdev->parent->parent &&
+ sysdev->parent->parent->bus == &pci_bus_type)
+ sysdev = sysdev->parent->parent;
+#endif
+
/* Try to set 64-bit DMA first */
- if (WARN_ON(!pdev->dev.dma_mask))
+ if (WARN_ON(!sysdev->dma_mask))
/* Platform did not initialize dma_mask */
- ret = dma_coerce_mask_and_coherent(&pdev->dev,
+ ret = dma_coerce_mask_and_coherent(sysdev,
DMA_BIT_MASK(64));
else
- ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
/* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
if (ret) {
- ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
if (ret)
return ret;
}
- hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
- if (!hcd)
- return -ENOMEM;
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+
+ hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
+ dev_name(&pdev->dev), NULL);
+ if (!hcd) {
+ ret = -ENOMEM;
+ goto disable_runtime;
+ }
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hcd->regs = devm_ioremap_resource(&pdev->dev, res);
@@ -213,20 +240,23 @@ static int xhci_plat_probe(struct platform_device *pdev)
xhci->clk = clk;
xhci->main_hcd = hcd;
- xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev,
+ xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
dev_name(&pdev->dev), hcd);
if (!xhci->shared_hcd) {
ret = -ENOMEM;
goto disable_clk;
}
- if (device_property_read_bool(&pdev->dev, "usb3-lpm-capable"))
+ 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;
- hcd->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+ hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0);
if (IS_ERR(hcd->usb_phy)) {
ret = PTR_ERR(hcd->usb_phy);
if (ret == -EPROBE_DEFER)
@@ -238,6 +268,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;
@@ -249,6 +283,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (ret)
goto dealloc_usb2_hcd;
+ device_enable_async_suspend(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+
return 0;
@@ -257,6 +294,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);
@@ -268,6 +306,10 @@ disable_clk:
put_hcd:
usb_put_hcd(hcd);
+disable_runtime:
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
return ret;
}
@@ -289,6 +331,12 @@ 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);
+
return 0;
}
@@ -316,14 +364,29 @@ static int xhci_plat_resume(struct device *dev)
return xhci_resume(xhci, 0);
}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM
+static int xhci_plat_runtime_suspend(struct device *dev)
+{
+ release_bus_freq(BUS_FREQ_HIGH);
+ return 0;
+}
+
+static int xhci_plat_runtime_resume(struct device *dev)
+{
+ request_bus_freq(BUS_FREQ_HIGH);
+ return 0;
+}
+#endif /* CONFIG_PM */
static const struct dev_pm_ops xhci_plat_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
+
+ SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
+ xhci_plat_runtime_resume,
+ NULL)
};
-#define DEV_PM_OPS (&xhci_plat_pm_ops)
-#else
-#define DEV_PM_OPS NULL
-#endif /* CONFIG_PM */
static const struct acpi_device_id usb_xhci_acpi_match[] = {
/* XHCI-compliant USB Controller */
@@ -337,7 +400,7 @@ static struct platform_driver usb_xhci_driver = {
.remove = xhci_plat_remove,
.driver = {
.name = "xhci-hcd",
- .pm = DEV_PM_OPS,
+ .pm = &xhci_plat_pm_ops,
.of_match_table = of_match_ptr(usb_xhci_of_match),
.acpi_match_table = ACPI_PTR(usb_xhci_acpi_match),
},
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 89a14d5f6ad8..806816401f59 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1968,9 +1968,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
switch (trb_comp_code) {
case COMP_SUCCESS:
if (event_trb == ep_ring->dequeue) {
- xhci_warn(xhci, "WARN: Success on ctrl setup TRB "
- "without IOC set??\n");
- *status = -ESHUTDOWN;
+ *status = 0;
} else if (event_trb != td->last_trb) {
xhci_warn(xhci, "WARN: Success on ctrl data TRB "
"without IOC set??\n");
@@ -2770,12 +2768,9 @@ hw_died:
*/
status |= STS_EINT;
writel(status, &xhci->op_regs->status);
- /* FIXME when MSI-X is supported and there are multiple vectors */
- /* Clear the MSI-X event interrupt status */
- if (hcd->irq) {
+ if (!hcd->msi_enabled) {
u32 irq_pending;
- /* Acknowledge the PCI interrupt */
irq_pending = readl(&xhci->ir_set->irq_pending);
irq_pending |= IMAN_IP;
writel(irq_pending, &xhci->ir_set->irq_pending);
@@ -3488,6 +3483,135 @@ 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 = kzalloc(sizeof(struct xhci_td), GFP_KERNEL);
+ if (!td) {
+ kfree(urb_priv);
+ return -ENOMEM;
+ }
+
+ urb_priv->td[0] = td;
+ urb_priv->length = 1;
+ urb_priv->td_cnt = 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 e9f5f9c32b49..dbf7bd4582e1 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -125,7 +125,7 @@ int xhci_halt(struct xhci_hcd *xhci)
/*
* Set the run bit and wait for the host to be running.
*/
-static int xhci_start(struct xhci_hcd *xhci)
+int xhci_start(struct xhci_hcd *xhci)
{
u32 temp;
int ret;
@@ -184,7 +184,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,
@@ -234,6 +234,9 @@ static int xhci_free_msi(struct xhci_hcd *xhci)
static int xhci_setup_msi(struct xhci_hcd *xhci)
{
int ret;
+ /*
+ * TODO:Check with MSI Soc for sysdev
+ */
struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
ret = pci_enable_msi(pdev);
@@ -260,7 +263,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci)
*/
static void xhci_free_irq(struct xhci_hcd *xhci)
{
- struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
+ struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.sysdev);
int ret;
/* return if using legacy interrupt */
@@ -395,9 +398,10 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
/* fall back to msi*/
ret = xhci_setup_msi(xhci);
- if (!ret)
- /* hcd->irq is 0, we have MSI */
+ if (!ret) {
+ hcd->msi_enabled = 1;
return 0;
+ }
if (!pdev->irq) {
xhci_err(xhci, "No msi-x/msi found and no IRQ in BIOS\n");
@@ -746,7 +750,7 @@ void xhci_shutdown(struct usb_hcd *hcd)
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
if (xhci->quirks & XHCI_SPURIOUS_REBOOT)
- usb_disable_xhci_ports(to_pci_dev(hcd->self.controller));
+ usb_disable_xhci_ports(to_pci_dev(hcd->self.sysdev));
spin_lock_irq(&xhci->lock);
xhci_halt(xhci);
@@ -763,7 +767,7 @@ void xhci_shutdown(struct usb_hcd *hcd)
/* Yet another workaround for spurious wakeups at shutdown with HSW */
if (xhci->quirks & XHCI_SPURIOUS_WAKEUP)
- pci_set_power_state(to_pci_dev(hcd->self.controller), PCI_D3hot);
+ pci_set_power_state(to_pci_dev(hcd->self.sysdev), PCI_D3hot);
}
#ifdef CONFIG_PM
@@ -3619,8 +3623,6 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_virt_device *virt_dev;
- unsigned long flags;
- u32 state;
int i, ret;
struct xhci_command *command;
@@ -3655,6 +3657,29 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
del_timer_sync(&virt_dev->eps[i].stop_cmd_timer);
}
+ xhci_disable_slot(xhci, command, udev->slot_id);
+ /*
+ * Event command completion handler will free any data structures
+ * associated with the slot. XXX Can free sleep?
+ */
+}
+
+int xhci_disable_slot(struct xhci_hcd *xhci, struct xhci_command *command,
+ u32 slot_id)
+{
+ unsigned long flags;
+ u32 state;
+ int ret = 0;
+ struct xhci_virt_device *virt_dev;
+
+ virt_dev = xhci->devs[slot_id];
+ if (!virt_dev)
+ return -EINVAL;
+ if (!command)
+ command = xhci_alloc_command(xhci, false, false, GFP_KERNEL);
+ if (!command)
+ return -ENOMEM;
+
spin_lock_irqsave(&xhci->lock, flags);
virt_dev->udev = NULL;
@@ -3663,25 +3688,22 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
state = readl(&xhci->op_regs->status);
if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) ||
(xhci->xhc_state & XHCI_STATE_HALTED)) {
- xhci_free_virt_device(xhci, udev->slot_id);
+ xhci_free_virt_device(xhci, slot_id);
spin_unlock_irqrestore(&xhci->lock, flags);
kfree(command);
- return;
+ return ret;
}
- if (xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT,
- udev->slot_id)) {
+ ret = xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT,
+ slot_id);
+ if (ret) {
spin_unlock_irqrestore(&xhci->lock, flags);
xhci_dbg(xhci, "FIXME: allocate a command ring segment\n");
- return;
+ return ret;
}
xhci_ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);
-
- /*
- * Event command completion handler will free any data structures
- * associated with the slot. XXX Can free sleep?
- */
+ return ret;
}
/*
@@ -3789,14 +3811,10 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev)
disable_slot:
/* Disable slot, if we can do it without mem alloc */
- spin_lock_irqsave(&xhci->lock, flags);
+ kfree(command->completion);
command->completion = NULL;
command->status = 0;
- if (!xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT,
- udev->slot_id))
- xhci_ring_cmd_db(xhci);
- spin_unlock_irqrestore(&xhci->lock, flags);
- return 0;
+ return xhci_disable_slot(xhci, command, udev->slot_id);
}
/*
@@ -4869,7 +4887,11 @@ int xhci_get_frame(struct usb_hcd *hcd)
int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
{
struct xhci_hcd *xhci;
- struct device *dev = hcd->self.controller;
+ /*
+ * TODO: Check with DWC3 clients for sysdev according to
+ * quirks
+ */
+ struct device *dev = hcd->self.sysdev;
int retval;
/* Accept arbitrarily long scatter-gather lists */
@@ -5050,6 +5072,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,
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index b9181281aa9e..a6dd4d97a179 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -429,6 +429,7 @@ struct xhci_op_regs {
#define PORT_L1DS_MASK (0xff << 8)
#define PORT_L1DS(p) (((p) & 0xff) << 8)
#define PORT_HLE (1 << 16)
+#define PORT_TEST_MODE_SHIFT 28
/* USB3 Protocol PORTLI Port Link Information */
#define PORT_RX_LANES(p) (((p) >> 16) & 0xf)
@@ -1444,7 +1445,6 @@ struct xhci_scratchpad {
u64 *sp_array;
dma_addr_t sp_dma;
void **sp_buffers;
- dma_addr_t *sp_dma_buffers;
};
struct urb_priv {
@@ -1666,6 +1666,8 @@ struct xhci_hcd {
#define XHCI_LIMIT_ENDPOINT_INTERVAL_7 (1 << 26)
/* Reserved. It was XHCI_U2_DISABLE_WAKE */
#define XHCI_ASMEDIA_MODIFY_FLOWCONTROL (1 << 28)
+#define XHCI_SKIP_ACCESS_RESERVED_REG (1 << 29)
+#define XHCI_CDNS_HOST (1 << 30)
unsigned int num_active_eps;
unsigned int limit_active_eps;
@@ -1691,6 +1693,7 @@ struct xhci_hcd {
/* Compliance Mode Recovery Data */
struct timer_list comp_mode_recovery_timer;
u32 port_status_u0;
+ u16 test_mode;
/* Compliance Mode Timer Triggered every 2 seconds */
#define COMP_MODE_RCVRY_MSECS 2000
@@ -1858,6 +1861,7 @@ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
void xhci_quiesce(struct xhci_hcd *xhci);
int xhci_halt(struct xhci_hcd *xhci);
+int xhci_start(struct xhci_hcd *xhci);
int xhci_reset(struct xhci_hcd *xhci);
int xhci_init(struct usb_hcd *hcd);
int xhci_run(struct usb_hcd *hcd);
@@ -1866,6 +1870,8 @@ void xhci_shutdown(struct usb_hcd *hcd);
int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
void xhci_init_driver(struct hc_driver *drv,
const struct xhci_driver_overrides *over);
+int xhci_disable_slot(struct xhci_hcd *xhci,
+ struct xhci_command *command, u32 slot_id);
#ifdef CONFIG_PM
int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup);
@@ -1971,6 +1977,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength);
int xhci_hub_status_data(struct usb_hcd *hcd, char *buf);
int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1);
+#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 125cea1c3c8d..2c60f0cab568 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -187,7 +187,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-generic.c b/drivers/usb/phy/phy-generic.c
index 8311ba2968cd..cdc6f862dcdb 100644
--- a/drivers/usb/phy/phy-generic.c
+++ b/drivers/usb/phy/phy-generic.c
@@ -59,6 +59,16 @@ EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
static int nop_set_suspend(struct usb_phy *x, int suspend)
{
+ struct usb_phy_generic *nop = dev_get_drvdata(x->dev);
+
+ if (IS_ERR(nop->clk))
+ return 0;
+
+ if (suspend)
+ clk_disable_unprepare(nop->clk);
+ else
+ clk_prepare_enable(nop->clk);
+
return 0;
}
diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c
index 0e2f1a36d315..92b94341b1b4 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,8 +69,20 @@
#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
@@ -94,6 +114,11 @@
#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)
+
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
/* Do disconnection between PHY and controller without vbus */
@@ -126,6 +151,16 @@
#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)
+
struct mxs_phy_data {
unsigned int flags;
};
@@ -137,12 +172,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 +188,21 @@ 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 = {
};
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, },
@@ -177,6 +221,9 @@ struct mxs_phy {
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;
};
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
@@ -189,6 +236,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 +269,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 +356,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 +418,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 +433,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 +472,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 +528,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 +624,61 @@ 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;
+}
+
+/*
+ * 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 int mxs_phy_probe(struct platform_device *pdev)
{
struct resource *res;
@@ -556,6 +756,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 +769,28 @@ 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;
+ 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);
@@ -590,18 +811,30 @@ static int mxs_phy_remove(struct platform_device *pdev)
#ifdef CONFIG_PM_SLEEP
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)
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
new file mode 100644
index 000000000000..17792f9114c6
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB Power Delivery and Type-C drivers"
+
+config TYPEC
+ tristate
+
+endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
new file mode 100644
index 000000000000..1012a8bed6d5
--- /dev/null
+++ b/drivers/usb/typec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TYPEC) += typec.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
new file mode 100644
index 000000000000..25b745173f7c
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1310 @@
+/*
+ * USB Type-C Connector Class
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/usb/typec.h>
+
+struct typec_mode {
+ int index;
+ u32 vdo;
+ char *desc;
+ enum typec_port_type roles;
+
+ struct typec_altmode *alt_mode;
+
+ unsigned int active:1;
+
+ char group_name[6];
+ struct attribute_group group;
+ struct attribute *attrs[5];
+ struct device_attribute vdo_attr;
+ struct device_attribute desc_attr;
+ struct device_attribute active_attr;
+ struct device_attribute roles_attr;
+};
+
+struct typec_altmode {
+ struct device dev;
+ u16 svid;
+ int n_modes;
+ struct typec_mode modes[ALTMODE_MAX_MODES];
+ const struct attribute_group *mode_groups[ALTMODE_MAX_MODES];
+};
+
+struct typec_plug {
+ struct device dev;
+ enum typec_plug_index index;
+};
+
+struct typec_cable {
+ struct device dev;
+ enum typec_plug_type type;
+ struct usb_pd_identity *identity;
+ unsigned int active:1;
+};
+
+struct typec_partner {
+ struct device dev;
+ unsigned int usb_pd:1;
+ struct usb_pd_identity *identity;
+ enum typec_accessory accessory;
+};
+
+struct typec_port {
+ unsigned int id;
+ struct device dev;
+
+ int prefer_role;
+ enum typec_data_role data_role;
+ enum typec_role pwr_role;
+ enum typec_role vconn_role;
+ enum typec_pwr_opmode pwr_opmode;
+
+ const struct typec_capability *cap;
+};
+
+#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
+#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
+#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
+#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
+#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
+
+static const struct device_type typec_partner_dev_type;
+static const struct device_type typec_cable_dev_type;
+static const struct device_type typec_plug_dev_type;
+static const struct device_type typec_port_dev_type;
+
+#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
+#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
+#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
+
+static DEFINE_IDA(typec_index_ida);
+static struct class *typec_class;
+
+/* Common attributes */
+
+static const char * const typec_accessory_modes[] = {
+ [TYPEC_ACCESSORY_NONE] = "none",
+ [TYPEC_ACCESSORY_AUDIO] = "analog_audio",
+ [TYPEC_ACCESSORY_DEBUG] = "debug",
+};
+
+static const char *const typec_port_types[] = {
+ [TYPEC_PORT_DFP] = "dfp",
+ [TYPEC_PORT_UFP] = "ufp",
+ [TYPEC_PORT_DRP] = "drp",
+};
+
+static struct usb_pd_identity *get_pd_identity(struct device *dev)
+{
+ if (is_typec_partner(dev)) {
+ struct typec_partner *partner = to_typec_partner(dev);
+
+ return partner->identity;
+ } else if (is_typec_cable(dev)) {
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ return cable->identity;
+ }
+ return NULL;
+}
+
+static ssize_t id_header_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_pd_identity *id = get_pd_identity(dev);
+
+ return sprintf(buf, "0x%08x\n", id->id_header);
+}
+static DEVICE_ATTR_RO(id_header);
+
+static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_pd_identity *id = get_pd_identity(dev);
+
+ return sprintf(buf, "0x%08x\n", id->cert_stat);
+}
+static DEVICE_ATTR_RO(cert_stat);
+
+static ssize_t product_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_pd_identity *id = get_pd_identity(dev);
+
+ return sprintf(buf, "0x%08x\n", id->product);
+}
+static DEVICE_ATTR_RO(product);
+
+static struct attribute *usb_pd_id_attrs[] = {
+ &dev_attr_id_header.attr,
+ &dev_attr_cert_stat.attr,
+ &dev_attr_product.attr,
+ NULL
+};
+
+static const struct attribute_group usb_pd_id_group = {
+ .name = "identity",
+ .attrs = usb_pd_id_attrs,
+};
+
+static const struct attribute_group *usb_pd_id_groups[] = {
+ &usb_pd_id_group,
+ NULL,
+};
+
+static void typec_report_identity(struct device *dev)
+{
+ sysfs_notify(&dev->kobj, "identity", "id_header");
+ sysfs_notify(&dev->kobj, "identity", "cert_stat");
+ sysfs_notify(&dev->kobj, "identity", "product");
+}
+
+/* ------------------------------------------------------------------------- */
+/* Alternate Modes */
+
+/**
+ * typec_altmode_update_active - Report Enter/Exit mode
+ * @alt: Handle to the alternate mode
+ * @mode: Mode index
+ * @active: True when the mode has been entered
+ *
+ * If a partner or cable plug executes Enter/Exit Mode command successfully, the
+ * drivers use this routine to report the updated state of the mode.
+ */
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+ bool active)
+{
+ struct typec_mode *m = &alt->modes[mode];
+ char dir[6];
+
+ if (m->active == active)
+ return;
+
+ m->active = active;
+ snprintf(dir, sizeof(dir), "mode%d", mode);
+ sysfs_notify(&alt->dev.kobj, dir, "active");
+ kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_update_active);
+
+/**
+ * typec_altmode2port - Alternate Mode to USB Type-C port
+ * @alt: The Alternate Mode
+ *
+ * Returns handle to the port that a cable plug or partner with @alt is
+ * connected to.
+ */
+struct typec_port *typec_altmode2port(struct typec_altmode *alt)
+{
+ if (is_typec_plug(alt->dev.parent))
+ return to_typec_port(alt->dev.parent->parent->parent);
+ if (is_typec_partner(alt->dev.parent))
+ return to_typec_port(alt->dev.parent->parent);
+ if (is_typec_port(alt->dev.parent))
+ return to_typec_port(alt->dev.parent);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode2port);
+
+static ssize_t
+typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ vdo_attr);
+
+ return sprintf(buf, "0x%08x\n", mode->vdo);
+}
+
+static ssize_t
+typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ desc_attr);
+
+ return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
+}
+
+static ssize_t
+typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ active_attr);
+
+ return sprintf(buf, "%s\n", mode->active ? "yes" : "no");
+}
+
+static ssize_t
+typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ active_attr);
+ struct typec_port *port = typec_altmode2port(mode->alt_mode);
+ bool activate;
+ int ret;
+
+ if (!port->cap->activate_mode)
+ return -EOPNOTSUPP;
+
+ ret = kstrtobool(buf, &activate);
+ if (ret)
+ return ret;
+
+ ret = port->cap->activate_mode(port->cap, mode->index, activate);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t
+typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ roles_attr);
+ ssize_t ret;
+
+ switch (mode->roles) {
+ case TYPEC_PORT_DFP:
+ ret = sprintf(buf, "source\n");
+ break;
+ case TYPEC_PORT_UFP:
+ ret = sprintf(buf, "sink\n");
+ break;
+ case TYPEC_PORT_DRP:
+ default:
+ ret = sprintf(buf, "source sink\n");
+ break;
+ }
+ return ret;
+}
+
+static void typec_init_modes(struct typec_altmode *alt,
+ struct typec_mode_desc *desc, bool is_port)
+{
+ int i;
+
+ for (i = 0; i < alt->n_modes; i++, desc++) {
+ struct typec_mode *mode = &alt->modes[i];
+
+ /* Not considering the human readable description critical */
+ mode->desc = kstrdup(desc->desc, GFP_KERNEL);
+ if (desc->desc && !mode->desc)
+ dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
+
+ mode->alt_mode = alt;
+ mode->vdo = desc->vdo;
+ mode->roles = desc->roles;
+ mode->index = desc->index;
+ sprintf(mode->group_name, "mode%d", desc->index);
+
+ sysfs_attr_init(&mode->vdo_attr.attr);
+ mode->vdo_attr.attr.name = "vdo";
+ mode->vdo_attr.attr.mode = 0444;
+ mode->vdo_attr.show = typec_altmode_vdo_show;
+
+ sysfs_attr_init(&mode->desc_attr.attr);
+ mode->desc_attr.attr.name = "description";
+ mode->desc_attr.attr.mode = 0444;
+ mode->desc_attr.show = typec_altmode_desc_show;
+
+ sysfs_attr_init(&mode->active_attr.attr);
+ mode->active_attr.attr.name = "active";
+ mode->active_attr.attr.mode = 0644;
+ mode->active_attr.show = typec_altmode_active_show;
+ mode->active_attr.store = typec_altmode_active_store;
+
+ mode->attrs[0] = &mode->vdo_attr.attr;
+ mode->attrs[1] = &mode->desc_attr.attr;
+ mode->attrs[2] = &mode->active_attr.attr;
+
+ /* With ports, list the roles that the mode is supported with */
+ if (is_port) {
+ sysfs_attr_init(&mode->roles_attr.attr);
+ mode->roles_attr.attr.name = "supported_roles";
+ mode->roles_attr.attr.mode = 0444;
+ mode->roles_attr.show = typec_altmode_roles_show;
+
+ mode->attrs[3] = &mode->roles_attr.attr;
+ }
+
+ mode->group.attrs = mode->attrs;
+ mode->group.name = mode->group_name;
+
+ alt->mode_groups[i] = &mode->group;
+ }
+}
+
+static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_altmode *alt = to_altmode(dev);
+
+ return sprintf(buf, "%04x\n", alt->svid);
+}
+static DEVICE_ATTR_RO(svid);
+
+static struct attribute *typec_altmode_attrs[] = {
+ &dev_attr_svid.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec_altmode);
+
+static void typec_altmode_release(struct device *dev)
+{
+ struct typec_altmode *alt = to_altmode(dev);
+ int i;
+
+ for (i = 0; i < alt->n_modes; i++)
+ kfree(alt->modes[i].desc);
+ kfree(alt);
+}
+
+static const struct device_type typec_altmode_dev_type = {
+ .name = "typec_alternate_mode",
+ .groups = typec_altmode_groups,
+ .release = typec_altmode_release,
+};
+
+static struct typec_altmode *
+typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc)
+{
+ struct typec_altmode *alt;
+ int ret;
+
+ alt = kzalloc(sizeof(*alt), GFP_KERNEL);
+ if (!alt)
+ return NULL;
+
+ alt->svid = desc->svid;
+ alt->n_modes = desc->n_modes;
+ typec_init_modes(alt, desc->modes, is_typec_port(parent));
+
+ alt->dev.parent = parent;
+ alt->dev.groups = alt->mode_groups;
+ alt->dev.type = &typec_altmode_dev_type;
+ dev_set_name(&alt->dev, "svid-%04x", alt->svid);
+
+ ret = device_register(&alt->dev);
+ if (ret) {
+ dev_err(parent, "failed to register alternate mode (%d)\n",
+ ret);
+ put_device(&alt->dev);
+ return NULL;
+ }
+
+ return alt;
+}
+
+/**
+ * typec_unregister_altmode - Unregister Alternate Mode
+ * @alt: The alternate mode to be unregistered
+ *
+ * Unregister device created with typec_partner_register_altmode(),
+ * typec_plug_register_altmode() or typec_port_register_altmode().
+ */
+void typec_unregister_altmode(struct typec_altmode *alt)
+{
+ if (alt)
+ device_unregister(&alt->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_altmode);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static ssize_t accessory_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_partner *p = to_typec_partner(dev);
+
+ return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
+}
+static DEVICE_ATTR_RO(accessory_mode);
+
+static ssize_t supports_usb_power_delivery_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_partner *p = to_typec_partner(dev);
+
+ return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no");
+}
+static DEVICE_ATTR_RO(supports_usb_power_delivery);
+
+static struct attribute *typec_partner_attrs[] = {
+ &dev_attr_accessory_mode.attr,
+ &dev_attr_supports_usb_power_delivery.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec_partner);
+
+static void typec_partner_release(struct device *dev)
+{
+ struct typec_partner *partner = to_typec_partner(dev);
+
+ kfree(partner);
+}
+
+static const struct device_type typec_partner_dev_type = {
+ .name = "typec_partner",
+ .groups = typec_partner_groups,
+ .release = typec_partner_release,
+};
+
+/**
+ * typec_partner_set_identity - Report result from Discover Identity command
+ * @partner: The partner updated identity values
+ *
+ * This routine is used to report that the result of Discover Identity USB power
+ * delivery command has become available.
+ */
+int typec_partner_set_identity(struct typec_partner *partner)
+{
+ if (!partner->identity)
+ return -EINVAL;
+
+ typec_report_identity(&partner->dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_partner_set_identity);
+
+/**
+ * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode
+ * @partner: USB Type-C Partner that supports the alternate mode
+ * @desc: Description of the alternate mode
+ *
+ * This routine is used to register each alternate mode individually that
+ * @partner has listed in response to Discover SVIDs command. The modes for a
+ * SVID listed in response to Discover Modes command need to be listed in an
+ * array in @desc.
+ *
+ * Returns handle to the alternate mode on success or NULL on failure.
+ */
+struct typec_altmode *
+typec_partner_register_altmode(struct typec_partner *partner,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&partner->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
+
+/**
+ * typec_register_partner - Register a USB Type-C Partner
+ * @port: The USB Type-C Port the partner is connected to
+ * @desc: Description of the partner
+ *
+ * Registers a device for USB Type-C Partner described in @desc.
+ *
+ * Returns handle to the partner on success or NULL on failure.
+ */
+struct typec_partner *typec_register_partner(struct typec_port *port,
+ struct typec_partner_desc *desc)
+{
+ struct typec_partner *partner;
+ int ret;
+
+ partner = kzalloc(sizeof(*partner), GFP_KERNEL);
+ if (!partner)
+ return NULL;
+
+ partner->usb_pd = desc->usb_pd;
+ partner->accessory = desc->accessory;
+
+ if (desc->identity) {
+ /*
+ * Creating directory for the identity only if the driver is
+ * able to provide data to it.
+ */
+ partner->dev.groups = usb_pd_id_groups;
+ partner->identity = desc->identity;
+ }
+
+ partner->dev.class = typec_class;
+ partner->dev.parent = &port->dev;
+ partner->dev.type = &typec_partner_dev_type;
+ dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
+
+ ret = device_register(&partner->dev);
+ if (ret) {
+ dev_err(&port->dev, "failed to register partner (%d)\n", ret);
+ put_device(&partner->dev);
+ return NULL;
+ }
+
+ return partner;
+}
+EXPORT_SYMBOL_GPL(typec_register_partner);
+
+/**
+ * typec_unregister_partner - Unregister a USB Type-C Partner
+ * @partner: The partner to be unregistered
+ *
+ * Unregister device created with typec_register_partner().
+ */
+void typec_unregister_partner(struct typec_partner *partner)
+{
+ if (partner)
+ device_unregister(&partner->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_partner);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static void typec_plug_release(struct device *dev)
+{
+ struct typec_plug *plug = to_typec_plug(dev);
+
+ kfree(plug);
+}
+
+static const struct device_type typec_plug_dev_type = {
+ .name = "typec_plug",
+ .release = typec_plug_release,
+};
+
+/**
+ * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode
+ * @plug: USB Type-C Cable Plug that supports the alternate mode
+ * @desc: Description of the alternate mode
+ *
+ * This routine is used to register each alternate mode individually that @plug
+ * has listed in response to Discover SVIDs command. The modes for a SVID that
+ * the plug lists in response to Discover Modes command need to be listed in an
+ * array in @desc.
+ *
+ * Returns handle to the alternate mode on success or NULL on failure.
+ */
+struct typec_altmode *
+typec_plug_register_altmode(struct typec_plug *plug,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&plug->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
+
+/**
+ * typec_register_plug - Register a USB Type-C Cable Plug
+ * @cable: USB Type-C Cable with the plug
+ * @desc: Description of the cable plug
+ *
+ * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C
+ * Cable Plug represents a plug with electronics in it that can response to USB
+ * Power Delivery SOP Prime or SOP Double Prime packages.
+ *
+ * Returns handle to the cable plug on success or NULL on failure.
+ */
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+ struct typec_plug_desc *desc)
+{
+ struct typec_plug *plug;
+ char name[8];
+ int ret;
+
+ plug = kzalloc(sizeof(*plug), GFP_KERNEL);
+ if (!plug)
+ return NULL;
+
+ sprintf(name, "plug%d", desc->index);
+
+ plug->index = desc->index;
+ plug->dev.class = typec_class;
+ plug->dev.parent = &cable->dev;
+ plug->dev.type = &typec_plug_dev_type;
+ dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
+
+ ret = device_register(&plug->dev);
+ if (ret) {
+ dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
+ put_device(&plug->dev);
+ return NULL;
+ }
+
+ return plug;
+}
+EXPORT_SYMBOL_GPL(typec_register_plug);
+
+/**
+ * typec_unregister_plug - Unregister a USB Type-C Cable Plug
+ * @plug: The cable plug to be unregistered
+ *
+ * Unregister device created with typec_register_plug().
+ */
+void typec_unregister_plug(struct typec_plug *plug)
+{
+ if (plug)
+ device_unregister(&plug->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_plug);
+
+/* Type-C Cables */
+
+static ssize_t
+type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ return sprintf(buf, "%s\n", cable->active ? "active" : "passive");
+}
+static DEVICE_ATTR_RO(type);
+
+static const char * const typec_plug_types[] = {
+ [USB_PLUG_NONE] = "unknown",
+ [USB_PLUG_TYPE_A] = "type-a",
+ [USB_PLUG_TYPE_B] = "type-b",
+ [USB_PLUG_TYPE_C] = "type-c",
+ [USB_PLUG_CAPTIVE] = "captive",
+};
+
+static ssize_t plug_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+static DEVICE_ATTR_RO(plug_type);
+
+static struct attribute *typec_cable_attrs[] = {
+ &dev_attr_type.attr,
+ &dev_attr_plug_type.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec_cable);
+
+static void typec_cable_release(struct device *dev)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ kfree(cable);
+}
+
+static const struct device_type typec_cable_dev_type = {
+ .name = "typec_cable",
+ .groups = typec_cable_groups,
+ .release = typec_cable_release,
+};
+
+/**
+ * typec_cable_set_identity - Report result from Discover Identity command
+ * @cable: The cable updated identity values
+ *
+ * This routine is used to report that the result of Discover Identity USB power
+ * delivery command has become available.
+ */
+int typec_cable_set_identity(struct typec_cable *cable)
+{
+ if (!cable->identity)
+ return -EINVAL;
+
+ typec_report_identity(&cable->dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_cable_set_identity);
+
+/**
+ * typec_register_cable - Register a USB Type-C Cable
+ * @port: The USB Type-C Port the cable is connected to
+ * @desc: Description of the cable
+ *
+ * Registers a device for USB Type-C Cable described in @desc. The cable will be
+ * parent for the optional cable plug devises.
+ *
+ * Returns handle to the cable on success or NULL on failure.
+ */
+struct typec_cable *typec_register_cable(struct typec_port *port,
+ struct typec_cable_desc *desc)
+{
+ struct typec_cable *cable;
+ int ret;
+
+ cable = kzalloc(sizeof(*cable), GFP_KERNEL);
+ if (!cable)
+ return NULL;
+
+ cable->type = desc->type;
+ cable->active = desc->active;
+
+ if (desc->identity) {
+ /*
+ * Creating directory for the identity only if the driver is
+ * able to provide data to it.
+ */
+ cable->dev.groups = usb_pd_id_groups;
+ cable->identity = desc->identity;
+ }
+
+ cable->dev.class = typec_class;
+ cable->dev.parent = &port->dev;
+ cable->dev.type = &typec_cable_dev_type;
+ dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
+
+ ret = device_register(&cable->dev);
+ if (ret) {
+ dev_err(&port->dev, "failed to register cable (%d)\n", ret);
+ put_device(&cable->dev);
+ return NULL;
+ }
+
+ return cable;
+}
+EXPORT_SYMBOL_GPL(typec_register_cable);
+
+/**
+ * typec_unregister_cable - Unregister a USB Type-C Cable
+ * @cable: The cable to be unregistered
+ *
+ * Unregister device created with typec_register_cable().
+ */
+void typec_unregister_cable(struct typec_cable *cable)
+{
+ if (cable)
+ device_unregister(&cable->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_cable);
+
+/* ------------------------------------------------------------------------- */
+/* USB Type-C ports */
+
+static const char * const typec_roles[] = {
+ [TYPEC_SINK] = "sink",
+ [TYPEC_SOURCE] = "source",
+};
+
+static const char * const typec_data_roles[] = {
+ [TYPEC_DEVICE] = "device",
+ [TYPEC_HOST] = "host",
+};
+
+static ssize_t
+preferred_role_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int role;
+ int ret;
+
+ if (port->cap->type != TYPEC_PORT_DRP) {
+ dev_dbg(dev, "Preferred role only supported with DRP ports\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->try_role) {
+ dev_dbg(dev, "Setting preferred role not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ role = sysfs_match_string(typec_roles, buf);
+ if (role < 0) {
+ if (sysfs_streq(buf, "none"))
+ role = TYPEC_NO_PREFERRED_ROLE;
+ else
+ return -EINVAL;
+ }
+
+ ret = port->cap->try_role(port->cap, role);
+ if (ret)
+ return ret;
+
+ port->prefer_role = role;
+ return size;
+}
+
+static ssize_t
+preferred_role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->type != TYPEC_PORT_DRP)
+ return 0;
+
+ if (port->prefer_role < 0)
+ return 0;
+
+ return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
+}
+static DEVICE_ATTR_RW(preferred_role);
+
+static ssize_t data_role_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int ret;
+
+ if (port->cap->type != TYPEC_PORT_DRP) {
+ dev_dbg(dev, "data role swap only supported with DRP ports\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->dr_set) {
+ dev_dbg(dev, "data role swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = sysfs_match_string(typec_data_roles, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = port->cap->dr_set(port->cap, ret);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t data_role_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->type == TYPEC_PORT_DRP)
+ return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ?
+ "[host] device" : "host [device]");
+
+ return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RW(data_role);
+
+static ssize_t power_role_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int ret = size;
+
+ if (!port->cap->pd_revision) {
+ dev_dbg(dev, "USB Power Delivery not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->pr_set) {
+ dev_dbg(dev, "power role swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
+ dev_dbg(dev, "partner unable to swap power role\n");
+ return -EIO;
+ }
+
+ ret = sysfs_match_string(typec_roles, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = port->cap->pr_set(port->cap, ret);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t power_role_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->type == TYPEC_PORT_DRP)
+ return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ?
+ "[source] sink" : "source [sink]");
+
+ return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RW(power_role);
+
+static const char * const typec_pwr_opmodes[] = {
+ [TYPEC_PWR_MODE_USB] = "default",
+ [TYPEC_PWR_MODE_1_5A] = "1.5A",
+ [TYPEC_PWR_MODE_3_0A] = "3.0A",
+ [TYPEC_PWR_MODE_PD] = "usb_power_delivery",
+};
+
+static ssize_t power_operation_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
+}
+static DEVICE_ATTR_RO(power_operation_mode);
+
+static ssize_t vconn_source_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ bool source;
+ int ret;
+
+ if (!port->cap->pd_revision) {
+ dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->vconn_set) {
+ dev_dbg(dev, "VCONN swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = kstrtobool(buf, &source);
+ if (ret)
+ return ret;
+
+ ret = port->cap->vconn_set(port->cap, (enum typec_role)source);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t vconn_source_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%s\n",
+ port->vconn_role == TYPEC_SOURCE ? "yes" : "no");
+}
+static DEVICE_ATTR_RW(vconn_source);
+
+static ssize_t supported_accessory_modes_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+ ssize_t ret = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) {
+ if (port->cap->accessory[i])
+ ret += sprintf(buf + ret, "%s ",
+ typec_accessory_modes[port->cap->accessory[i]]);
+ }
+
+ if (!ret)
+ return sprintf(buf, "none\n");
+
+ buf[ret - 1] = '\n';
+
+ return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static ssize_t usb_typec_revision_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+ u16 rev = port->cap->revision;
+
+ return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf);
+}
+static DEVICE_ATTR_RO(usb_typec_revision);
+
+static ssize_t usb_power_delivery_revision_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *p = to_typec_port(dev);
+
+ return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff);
+}
+static DEVICE_ATTR_RO(usb_power_delivery_revision);
+
+static struct attribute *typec_attrs[] = {
+ &dev_attr_data_role.attr,
+ &dev_attr_power_operation_mode.attr,
+ &dev_attr_power_role.attr,
+ &dev_attr_preferred_role.attr,
+ &dev_attr_supported_accessory_modes.attr,
+ &dev_attr_usb_power_delivery_revision.attr,
+ &dev_attr_usb_typec_revision.attr,
+ &dev_attr_vconn_source.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ int ret;
+
+ ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
+ if (ret)
+ dev_err(dev, "failed to add uevent TYPEC_PORT\n");
+
+ return ret;
+}
+
+static void typec_release(struct device *dev)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ ida_simple_remove(&typec_index_ida, port->id);
+ kfree(port);
+}
+
+static const struct device_type typec_port_dev_type = {
+ .name = "typec_port",
+ .groups = typec_groups,
+ .uevent = typec_uevent,
+ .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, ARRAY_SIZE(typec_port_types), 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 */
+
+/**
+ * typec_set_data_role - Report data role change
+ * @port: The USB Type-C Port where the role was changed
+ * @role: The new data role
+ *
+ * This routine is used by the port drivers to report data role changes.
+ */
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
+{
+ if (port->data_role == role)
+ return;
+
+ port->data_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "data_role");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_data_role);
+
+/**
+ * typec_set_pwr_role - Report power role change
+ * @port: The USB Type-C Port where the role was changed
+ * @role: The new data role
+ *
+ * This routine is used by the port drivers to report power role changes.
+ */
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
+{
+ if (port->pwr_role == role)
+ return;
+
+ port->pwr_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "power_role");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_role);
+
+/**
+ * typec_set_pwr_role - Report VCONN source change
+ * @port: The USB Type-C Port which VCONN role changed
+ * @role: Source when @port is sourcing VCONN, or Sink when it's not
+ *
+ * This routine is used by the port drivers to report if the VCONN source is
+ * changes.
+ */
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
+{
+ if (port->vconn_role == role)
+ return;
+
+ port->vconn_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "vconn_source");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_vconn_role);
+
+/**
+ * typec_set_pwr_opmode - Report changed power operation mode
+ * @port: The USB Type-C Port where the mode was changed
+ * @opmode: New power operation mode
+ *
+ * This routine is used by the port drivers to report changed power operation
+ * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB
+ * Type-C specification, and "USB Power Delivery" when the power levels are
+ * negotiated with methods defined in USB Power Delivery specification.
+ */
+void typec_set_pwr_opmode(struct typec_port *port,
+ enum typec_pwr_opmode opmode)
+{
+ if (port->pwr_opmode == opmode)
+ return;
+
+ port->pwr_opmode = opmode;
+ sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
+
+/* --------------------------------------- */
+
+/**
+ * typec_port_register_altmode - Register USB Type-C Port Alternate Mode
+ * @port: USB Type-C Port that supports the alternate mode
+ * @desc: Description of the alternate mode
+ *
+ * This routine is used to register an alternate mode that @port is capable of
+ * supporting.
+ *
+ * Returns handle to the alternate mode on success or NULL on failure.
+ */
+struct typec_altmode *
+typec_port_register_altmode(struct typec_port *port,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&port->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_port_register_altmode);
+
+/**
+ * typec_register_port - Register a USB Type-C Port
+ * @parent: Parent device
+ * @cap: Description of the port
+ *
+ * Registers a device for USB Type-C Port described in @cap.
+ *
+ * Returns handle to the port on success or NULL on failure.
+ */
+struct typec_port *typec_register_port(struct device *parent,
+ const struct typec_capability *cap)
+{
+ struct typec_port *port;
+ int role;
+ int ret;
+ int id;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return NULL;
+
+ id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ kfree(port);
+ return NULL;
+ }
+
+ if (cap->type == TYPEC_PORT_DFP)
+ role = TYPEC_SOURCE;
+ else if (cap->type == TYPEC_PORT_UFP)
+ role = TYPEC_SINK;
+ else
+ role = cap->prefer_role;
+
+ if (role == TYPEC_SOURCE) {
+ port->data_role = TYPEC_HOST;
+ port->pwr_role = TYPEC_SOURCE;
+ port->vconn_role = TYPEC_SOURCE;
+ } else {
+ port->data_role = TYPEC_DEVICE;
+ port->pwr_role = TYPEC_SINK;
+ port->vconn_role = TYPEC_SINK;
+ }
+
+ port->id = id;
+ port->cap = cap;
+ port->prefer_role = cap->prefer_role;
+
+ port->dev.class = typec_class;
+ port->dev.parent = parent;
+ port->dev.fwnode = cap->fwnode;
+ port->dev.type = &typec_port_dev_type;
+ dev_set_name(&port->dev, "port%d", id);
+
+ ret = device_register(&port->dev);
+ if (ret) {
+ dev_err(parent, "failed to register port (%d)\n", ret);
+ put_device(&port->dev);
+ return NULL;
+ }
+
+ return port;
+}
+EXPORT_SYMBOL_GPL(typec_register_port);
+
+/**
+ * typec_unregister_port - Unregister a USB Type-C Port
+ * @port: The port to be unregistered
+ *
+ * Unregister device created with typec_register_port().
+ */
+void typec_unregister_port(struct typec_port *port)
+{
+ if (port)
+ device_unregister(&port->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_port);
+
+static int __init typec_init(void)
+{
+ typec_class = class_create(THIS_MODULE, "typec");
+ return PTR_ERR_OR_ZERO(typec_class);
+}
+subsys_initcall(typec_init);
+
+static void __exit typec_exit(void)
+{
+ class_destroy(typec_class);
+ ida_destroy(&typec_index_ida);
+}
+module_exit(typec_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Type-C Connector Class");