[RFC 2/3] USB: dwc3: Modify dwc3 code for support usb of Hikey960

From: Yu Chen
Date: Tue Oct 24 2017 - 02:25:53 EST


The usb controller of Kirin960 is DesignWare Cores SuperSpeed USB 3.0 Controller.
The patch modifies dwc3 for support Kirin960 and adds codes for a USB Hub on board Hikey960.

Signed-off-by: Yu Chen <chenyu56@xxxxxxxxxx>
Signed-off-by: Ning Fan <fanning4@xxxxxxxxxxxxx>
Signed-off-by: Di Yang <yangdi10@xxxxxxxxxxxxx>
Signed-off-by: Rui Li <lirui39@xxxxxxxxxxxxx>

---
arch/arm64/configs/defconfig | 5 +
drivers/usb/dwc3/Kconfig | 26 +
drivers/usb/dwc3/Makefile | 5 +
drivers/usb/dwc3/core.c | 78 +-
drivers/usb/dwc3/core.h | 19 +-
drivers/usb/dwc3/dwc3-hi3660.c | 310 +++++
drivers/usb/dwc3/dwc3-hisi.c | 1972 ++++++++++++++++++++++++++++++
drivers/usb/dwc3/dwc3-hisi.h | 293 +++++
drivers/usb/dwc3/dwc3-otg.c | 360 ++++++
drivers/usb/dwc3/dwc3-otg.h | 133 ++
drivers/usb/dwc3/ep0.c | 55 +-
drivers/usb/dwc3/gadget.c | 20 +-
drivers/usb/dwc3/hisi_hikey_gpio.c | 300 +++++
drivers/usb/dwc3/host.c | 13 +
drivers/usb/dwc3/io.h | 14 +
include/linux/hisi/log/hisi_log.h | 143 +++
include/linux/hisi/usb/hisi_hikey_gpio.h | 24 +
include/linux/hisi/usb/hisi_usb.h | 57 +
18 files changed, 3819 insertions(+), 8 deletions(-)
create mode 100644 drivers/usb/dwc3/dwc3-hi3660.c
create mode 100644 drivers/usb/dwc3/dwc3-hisi.c
create mode 100644 drivers/usb/dwc3/dwc3-hisi.h
create mode 100644 drivers/usb/dwc3/dwc3-otg.c
create mode 100644 drivers/usb/dwc3/dwc3-otg.h
create mode 100644 drivers/usb/dwc3/hisi_hikey_gpio.c
create mode 100644 include/linux/hisi/log/hisi_log.h
create mode 100644 include/linux/hisi/usb/hisi_hikey_gpio.h
create mode 100644 include/linux/hisi/usb/hisi_usb.h

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 34480e9af2e7..8e61b7d96bba 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -405,6 +405,7 @@ CONFIG_SND_SOC_SAMSUNG=y
CONFIG_SND_SOC_RCAR=m
CONFIG_SND_SOC_AK4613=m
CONFIG_SND_SIMPLE_CARD=y
+CONFIG_HISI_HIKEY_GPIO=y
CONFIG_USB=y
CONFIG_USB_OTG=y
CONFIG_USB_XHCI_HCD=y
@@ -419,6 +420,9 @@ CONFIG_USB_OHCI_HCD_PLATFORM=y
CONFIG_USB_RENESAS_USBHS=m
CONFIG_USB_STORAGE=y
CONFIG_USB_DWC3=y
+CONFIG_USB_DWC3_DUAL_ROLE=y
+CONFIG_USB_DWC3_HISI=y
+CONFIG_USB_DWC3_OTG=y
CONFIG_USB_DWC2=y
CONFIG_USB_CHIPIDEA=y
CONFIG_USB_CHIPIDEA_UDC=y
@@ -428,6 +432,7 @@ CONFIG_USB_HSIC_USB3503=y
CONFIG_NOP_USB_XCEIV=y
CONFIG_USB_MSM_OTG=y
CONFIG_USB_QCOM_8X16_PHY=y
+CONFIG_EXTCON=y
CONFIG_USB_ULPI=y
CONFIG_USB_GADGET=y
CONFIG_USB_RENESAS_USBHS_UDC=m
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index ab8c0e0d3b60..5f7d9f19f503 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -106,4 +106,30 @@ config USB_DWC3_ST
inside (i.e. STiH407).
Say 'Y' or 'M' if you have one such device.

+config USB_DWC3_HISI
+ tristate "Hisilicon Platforms"
+ select USB_DWC3_OTG
+ depends on USB_DWC3
+ default n
+ help
+ Support of USB2/3 functionality in hisilicon platforms,
+ Say 'Y' or 'M' here if you have one such device.
+ Use for hisilicon device and it will select USB_DWC3_OTG
+ if Say 'Y' or 'M' here.
+
+config USB_DWC3_OTG
+ bool "Enable DWC3 OTG"
+ default n
+ help
+ Support of USB2/3 functionality in hisilicon platforms,
+ Say 'Y' or 'M' here if you have one such device.
+ Use for hisilicon device
+ if Say 'Y' or 'M' here.
+
+config HISI_HIKEY_GPIO
+ tristate "HISI_HIKEY_GPIO"
+ depends on GPIOLIB
+ default n
+ help
+ If you say yes here you get support for hisi hikey gpio.
endif
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index f15fabbd1e59..c2c32a5effc7 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -1,6 +1,7 @@
# define_trace.h needs to know how to find our header
CFLAGS_trace.o := -I$(src)

+ccflags-$(CONFIG_USB_DWC3_OTG) += -DDWC3_OTG_FORCE_MODE
obj-$(CONFIG_USB_DWC3) += dwc3.o

dwc3-y := core.o
@@ -29,6 +30,8 @@ ifneq ($(CONFIG_DEBUG_FS),)
dwc3-y += debugfs.o
endif

+dwc3-$(CONFIG_USB_DWC3_OTG) += dwc3-otg.o
+
##
# Platform-specific glue layers go here
#
@@ -47,3 +50,5 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
+obj-$(CONFIG_USB_DWC3_HISI) += dwc3-hisi.o dwc3-hi3660.o
+obj-$(CONFIG_HISI_HIKEY_GPIO) += hisi_hikey_gpio.o
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 03474d3575ab..d0105a26867d 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -44,7 +44,7 @@
#include "core.h"
#include "gadget.h"
#include "io.h"
-
+#include "dwc3-otg.h"
#include "debug.h"

#define DWC3_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */
@@ -87,6 +87,8 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
mode = USB_DR_MODE_HOST;
else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET))
mode = USB_DR_MODE_PERIPHERAL;
+ else if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE))
+ mode = USB_DR_MODE_OTG;
}

if (mode != dwc->dr_mode) {
@@ -103,7 +105,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
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)
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
{
u32 reg;

@@ -113,6 +115,7 @@ static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}

+#ifndef CONFIG_USB_DWC3_HISI
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
@@ -177,6 +180,7 @@ static void __dwc3_set_mode(struct work_struct *work)
break;
}
}
+#endif

void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
{
@@ -362,6 +366,12 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc)

evt = dwc->ev_buf;
evt->lpos = 0;
+ #ifdef CONFIG_USB_DWC3_HISI
+ evt->count = 0;
+ evt->flags = 0;
+ memset(evt->buf, 0, evt->length);
+ #endif
+
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0),
lower_32_bits(evt->dma));
dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0),
@@ -730,7 +740,13 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc)
*/
if (dwc->revision < DWC3_REVISION_190A)
reg |= DWC3_GCTL_U2RSTECN;
-
+ #ifdef DWC3_OTG_FORCE_MODE
+ /*
+ * if ID status is detected by third module, default device mode.
+ */
+ reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
+ reg |= DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_DEVICE);
+ #endif
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}

@@ -957,6 +973,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_OTG:
+ #ifndef CONFIG_USB_DWC3_HISI
INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
ret = dwc3_drd_init(dwc);
if (ret) {
@@ -964,6 +981,30 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
dev_err(dev, "failed to initialize dual-role\n");
return ret;
}
+ #else
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+
+ ret = dwc3_otg_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize otg\n");
+ return ret;
+ }
+
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ dwc3_otg_exit(dwc);
+ return ret;
+ }
+
+ ret = dwc3_gadget_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize gadget\n");
+ dwc3_host_exit(dwc);
+ dwc3_otg_exit(dwc);
+ return ret;
+ }
+ #endif
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -984,6 +1025,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
break;
case USB_DR_MODE_OTG:
dwc3_drd_exit(dwc);
+ dwc3_otg_exit(dwc);
break;
default:
/* do nothing */
@@ -1341,8 +1383,10 @@ static int dwc3_runtime_checks(struct dwc3 *dwc)
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
case USB_DR_MODE_OTG:
+#ifndef CONFIG_USB_DWC3_HISI
if (dwc->connected)
return -EBUSY;
+#endif
break;
case USB_DR_MODE_HOST:
default:
@@ -1367,6 +1411,7 @@ static int dwc3_runtime_suspend(struct device *dev)

device_init_wakeup(dev, true);

+ pm_runtime_put(dev);
return 0;
}

@@ -1393,7 +1438,7 @@ static int dwc3_runtime_resume(struct device *dev)
}

pm_runtime_mark_last_busy(dev);
- pm_runtime_put(dev);
+ pm_runtime_get(dev);

return 0;
}
@@ -1461,6 +1506,31 @@ static const struct dev_pm_ops dwc3_dev_pm_ops = {
dwc3_runtime_idle)
};

+int dwc3_resume_device(struct dwc3 *dwc)
+{
+ int status;
+
+ pr_info("[%s] +\n", __func__);
+ status = dwc3_runtime_resume(dwc->dev);
+ if (status < 0)
+ pr_err("dwc3_runtime_resume err, status:%d\n", status);
+
+ pr_info("[%s] -\n", __func__);
+ return status;
+}
+
+void dwc3_suspend_device(struct dwc3 *dwc)
+{
+ int status;
+
+ pr_info("[%s] +\n", __func__);
+ status = dwc3_runtime_suspend(dwc->dev);
+ if (status < 0)
+ pr_err("dwc3_runtime_suspend err, status:%d\n", status);
+
+ pr_info("[%s] -\n", __func__);
+}
+
#ifdef CONFIG_OF
static const struct of_device_id of_dwc3_match[] = {
{
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index ea910acb4bb0..3b6dd99daf9a 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -750,6 +750,7 @@ struct dwc3_request {
unsigned mapped:1;
unsigned started:1;
unsigned zero:1;
+ unsigned send_zlp:1;
};

/*
@@ -980,10 +981,17 @@ struct dwc3 {
u8 lpm_nyet_threshold;
u8 hird_threshold;

+ struct dwc3_otg *dwc_otg;
const char *hsphy_interface;

unsigned connected:1;
unsigned delayed_status:1;
+
+ /* the delayed status may come before notready interrupt,
+ * in this case, don't wait for delayed status
+ */
+ unsigned status_queued:1;
+
unsigned ep0_bounced:1;
unsigned ep0_expect_in:1;
unsigned has_hibernation:1;
@@ -1175,7 +1183,7 @@ struct dwc3_gadget_ep_cmd_params {
/* prototypes */
void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type);
-
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode);
/* check whether we are on the DWC_usb3 core */
static inline bool dwc3_is_usb3(struct dwc3 *dwc)
{
@@ -1209,6 +1217,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
struct dwc3_gadget_ep_cmd_params *params);
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param);
+int dwc3_conndone_notifier_register(struct notifier_block *nb);
+int dwc3_conndone_notifier_unregister(struct notifier_block *nb);
#else
static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; }
@@ -1228,6 +1238,10 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
int cmd, u32 param)
{ return 0; }
+static inline int dwc3_conndone_notifier_register(struct notifier_block *nb)
+{ return 0; }
+static inline int dwc3_conndone_notifier_unregister(struct notifier_block *nb)
+{ return 0; }
#endif

#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
@@ -1261,6 +1275,9 @@ static inline void dwc3_gadget_process_pending_events(struct dwc3 *dwc)
}
#endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */

+int dwc3_resume_device(struct dwc3 *dwc);
+void dwc3_suspend_device(struct dwc3 *dwc);
+
#if IS_ENABLED(CONFIG_USB_DWC3_ULPI)
int dwc3_ulpi_init(struct dwc3 *dwc);
void dwc3_ulpi_exit(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/dwc3-hi3660.c b/drivers/usb/dwc3/dwc3-hi3660.c
new file mode 100644
index 000000000000..d8cdc0f7280b
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-hi3660.c
@@ -0,0 +1,310 @@
+/*
+ * dwc3-hi3660.c
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include "dwc3-hisi.h"
+
+/*lint -e750 -esym(750,*)*/
+/* clk module will round to 228M */
+#define USB3OTG_ACLK_FREQ 229000000
+#ifndef BIT
+#define BIT(x) (1 << (x))
+#endif
+#define SCTRL_SCDEEPSLEEPED 0x08
+#define USB_REFCLK_ISO_EN BIT(25)
+#define PCTRL_PERI_CTRL3 0x10
+#define USB_TCXO_EN BIT(1)
+#define PERI_CTRL3_MSK_START (16)
+#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
+
+#define SC_SEL_ABB_BACKUP BIT(8)
+#define CLKDIV_MASK_START (16)
+
+#define PERI_CRG_CLKDIV21 0xFC
+
+#define GT_CLK_ABB_BACKUP BIT(22)
+#define PERI_CRG_CLK_DIS5 0x54
+
+#define PMC_PPLL3CTRL0 0x048
+#define PPLL3_FBDIV_START (8)
+#define PPLL3_EN BIT(0)
+#define PPLL3_BP BIT(1)
+#define PPLL3_LOCK BIT(26)
+
+#define PMC_PPLL3CTRL1 0x04C
+#define PPLL3_INT_MOD BIT(24)
+#define GT_CLK_PPLL3 BIT(26)
+
+#define PERI_CRG_CLK_EN5 0x50
+
+#define SC_USB3PHY_ABB_GT_EN BIT(15)
+#define REF_SSP_EN BIT(16)
+/*lint -e750 +esym(750,*)*/
+
+static int usb3_regu_init(struct hisi_dwc3_device *hisi_dwc3)
+{
+ if (hisi_dwc3->is_regu_on != 0) {
+ usb_dbg("ldo already opened!\n");
+ return 0;
+ }
+
+ hisi_dwc3->is_regu_on = 1;
+
+ return 0;
+}
+
+static int usb3_regu_shutdown(struct hisi_dwc3_device *hisi_dwc3)
+{
+ if (hisi_dwc3->is_regu_on == 0) {
+ usb_dbg("regu already closed!\n");
+ return 0;
+ }
+
+ hisi_dwc3->is_regu_on = 0;
+
+ return 0;
+}
+
+static int usb3_clk_init(struct hisi_dwc3_device *hisi_dwc3)
+{
+ int ret;
+ u32 temp;
+ void __iomem *pctrl_base = hisi_dwc3->pctrl_reg_base;
+ void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base;
+
+ /* set usb aclk 240MHz to improve performance */
+ ret = clk_set_rate(hisi_dwc3->gt_aclk_usb3otg, USB3OTG_ACLK_FREQ);
+ if (ret)
+ usb_err("usb aclk set rate failed\n");
+
+ ret = clk_prepare_enable(hisi_dwc3->gt_aclk_usb3otg);
+ if (ret) {
+ usb_err("clk_prepare_enable gt_aclk_usb3otg failed\n");
+ return ret;
+ }
+
+ /* usb refclk iso enable */
+ writel(USB_REFCLK_ISO_EN, pericfg_base + PERI_CRG_ISODIS);
+
+ /* enable usb_tcxo_en */
+ writel(USB_TCXO_EN | (USB_TCXO_EN << PERI_CTRL3_MSK_START),
+ pctrl_base + PCTRL_PERI_CTRL3);
+
+ /* select usbphy clk from abb */
+ temp = readl(pctrl_base + PCTRL_PERI_CTRL24);
+ temp &= ~SC_CLK_USB3PHY_3MUX1_SEL;
+ writel(temp, pctrl_base + PCTRL_PERI_CTRL24);
+
+ /* open clk gate */
+ writel(GT_CLK_USB3OTG_REF | GT_ACLK_USB3OTG,
+ pericfg_base + PERI_CRG_CLK_EN4);
+
+ ret = clk_prepare_enable(hisi_dwc3->clk);
+ if (ret) {
+ usb_err("clk_prepare_enable clk failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usb3_clk_shutdown(struct hisi_dwc3_device *hisi_dwc3)
+{
+ u32 temp;
+ void __iomem *pctrl_base = hisi_dwc3->pctrl_reg_base;
+ void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base;
+
+ writel(GT_CLK_USB3OTG_REF | GT_ACLK_USB3OTG,
+ pericfg_base + PERI_CRG_CLK_DIS4);
+
+ temp = readl(pctrl_base + PCTRL_PERI_CTRL24);
+ temp &= ~SC_CLK_USB3PHY_3MUX1_SEL;
+ writel(temp, pctrl_base + PCTRL_PERI_CTRL24);
+
+ /* disable usb_tcxo_en */
+ writel(0 | (USB_TCXO_EN << PERI_CTRL3_MSK_START),
+ pctrl_base + PCTRL_PERI_CTRL3);
+
+ clk_disable_unprepare(hisi_dwc3->clk);
+ clk_disable_unprepare(hisi_dwc3->gt_aclk_usb3otg);
+
+ msleep(20);
+}
+
+static void dwc3_release(struct hisi_dwc3_device *hisi_dwc3)
+{
+ u32 temp;
+ void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base;
+ void __iomem *otg_bc_base = hisi_dwc3->otg_bc_reg_base;
+
+ /* dis-reset the module */
+ writel(IP_RST_USB3OTG_MUX | IP_RST_USB3OTG_AHBIF | IP_RST_USB3OTG_32K,
+ pericfg_base + PERI_CRG_RSTDIS4);
+
+ /* reset phy */
+ writel(IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG,
+ pericfg_base + PERI_CRG_RSTEN4);
+
+ /* enable phy ref clk */
+ temp = readl(otg_bc_base + USBOTG3_CTRL0);
+ temp |= SC_USB3PHY_ABB_GT_EN;
+ writel(temp, otg_bc_base + USBOTG3_CTRL0);
+
+ temp = readl(otg_bc_base + USBOTG3_CTRL7);
+ temp |= REF_SSP_EN;
+ writel(temp, otg_bc_base + USBOTG3_CTRL7);
+
+ /* exit from IDDQ mode */
+ temp = readl(otg_bc_base + USBOTG3_CTRL2);
+ temp &= ~(USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP);
+ writel(temp, otg_bc_base + USBOTG3_CTRL2);
+
+ usleep_range(100, 120);
+
+ /* dis-reset phy */
+ writel(IP_RST_USB3OTGPHY_POR, pericfg_base + PERI_CRG_RSTDIS4);
+
+ /* dis-reset controller */
+ writel(IP_RST_USB3OTG, pericfg_base + PERI_CRG_RSTDIS4);
+
+ msleep(20);
+
+ /* fake vbus valid signal */
+ temp = readl(otg_bc_base + USBOTG3_CTRL3);
+ temp |= (USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL);
+ writel(temp, otg_bc_base + USBOTG3_CTRL3);
+
+ usleep_range(100, 120);
+}
+
+static void dwc3_reset(struct hisi_dwc3_device *hisi_dwc3)
+{
+ void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base;
+
+ writel(IP_RST_USB3OTG, pericfg_base + PERI_CRG_RSTEN4);
+ writel(IP_RST_USB3OTGPHY_POR, pericfg_base + PERI_CRG_RSTEN4);
+ writel(IP_RST_USB3OTG_MUX | IP_RST_USB3OTG_AHBIF | IP_RST_USB3OTG_32K,
+ pericfg_base + PERI_CRG_RSTEN4);
+}
+
+static int hi3660_usb3phy_init(struct hisi_dwc3_device *hisi_dwc3)
+{
+ int ret;
+
+ usb_dbg("+\n");
+
+ ret = usb3_regu_init(hisi_dwc3);
+ if (ret)
+ return ret;
+
+ ret = usb3_clk_init(hisi_dwc3);
+ if (ret)
+ return ret;
+
+ dwc3_release(hisi_dwc3);
+ config_femtophy_param(hisi_dwc3);
+
+ set_hisi_dwc3_power_flag(1);
+
+ usb_dbg("-\n");
+
+ return 0;
+}
+
+static int hi3660_usb3phy_shutdown(struct hisi_dwc3_device *hisi_dwc3)
+{
+ int ret;
+
+ usb_dbg("+\n");
+
+ set_hisi_dwc3_power_flag(0);
+
+ dwc3_reset(hisi_dwc3);
+ usb3_clk_shutdown(hisi_dwc3);
+
+ ret = usb3_regu_shutdown(hisi_dwc3);
+ if (ret)
+ return ret;
+
+ usb_dbg("-\n");
+
+ return 0;
+}
+
+static struct usb3_phy_ops hi3660_phy_ops = {
+ .init = hi3660_usb3phy_init,
+ .shutdown = hi3660_usb3phy_shutdown,
+};
+
+static int dwc3_hi3660_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ ret = hisi_dwc3_probe(pdev, &hi3660_phy_ops);
+ if (ret)
+ usb_err("probe failed, ret=[%d]\n", ret);
+
+ return ret;
+}
+
+static int dwc3_hi3660_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ ret = hisi_dwc3_remove(pdev);
+ if (ret)
+ usb_err("hisi_dwc3_remove failed, ret=[%d]\n", ret);
+
+ return ret;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dwc3_hi3660_match[] = {
+ { .compatible = "hisilicon,hi3660-dwc3" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dwc3_hi3660_match);
+#else
+#define dwc3_hi3660_match NULL
+#endif
+
+static struct platform_driver dwc3_hi3660_driver = {
+ .probe = dwc3_hi3660_probe,
+ .remove = dwc3_hi3660_remove,
+ .driver = {
+ .name = "usb3-hi3660",
+ .of_match_table = of_match_ptr(dwc3_hi3660_match),
+ .pm = HISI_DWC3_PM_OPS,
+ },
+};
+
+module_platform_driver(dwc3_hi3660_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("DesignWare USB3 HI3660 Glue Layer");
+MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>");
diff --git a/drivers/usb/dwc3/dwc3-hisi.c b/drivers/usb/dwc3/dwc3-hisi.c
new file mode 100644
index 000000000000..f62921ca41d3
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-hisi.c
@@ -0,0 +1,1972 @@
+/*
+ * hisi_usb_vbus.c
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/of_gpio.h>
+#include <linux/usb/ch9.h>
+
+#include "dwc3-hisi.h"
+#include "core.h"
+#include "dwc3-otg.h"
+
+#define ENABLE_USB_TEST_PORT
+
+#define BC_AGAIN_DELAY_TIME 8000 /* ms */
+
+struct hisi_dwc3_device *hisi_dwc3_dev;
+atomic_t hisi_dwc3_power_on = ATOMIC_INIT(0);
+
+void set_hisi_dwc3_power_flag(int val)
+{
+ unsigned long flags;
+ struct dwc3 *dwc = NULL;
+
+ if (dwc_otg_handler && dwc_otg_handler->dwc) {
+ dwc = dwc_otg_handler->dwc;
+ spin_lock_irqsave(&dwc->lock, flags);
+ usb_dbg("get dwc3 lock\n");
+ }
+
+ atomic_set(&hisi_dwc3_power_on, val);
+ usb_dbg("set hisi_dwc3_power_flag %d\n", val);
+
+ if (dwc) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ usb_dbg("put dwc3 lock\n");
+ }
+}
+
+#ifdef ENABLE_USB_TEST_PORT
+
+static ssize_t plugusb_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct hisi_dwc3_device *hisi_dwc3 = platform_get_drvdata(pdev);
+ char *s;
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return scnprintf(buf, PAGE_SIZE, "hisi_dwc3 NULL\n");
+ }
+
+ switch (hisi_dwc3->state) {
+ case USB_STATE_UNKNOWN:
+ s = "USB_STATE_UNKNOWN";
+ break;
+ case USB_STATE_OFF:
+ s = "USB_STATE_OFF";
+ break;
+ case USB_STATE_DEVICE:
+ s = "USB_STATE_DEVICE";
+ break;
+ case USB_STATE_HOST:
+ s = "USB_STATE_HOST";
+ break;
+ default:
+ s = "unknown";
+ break;
+ }
+ return scnprintf(buf, PAGE_SIZE, "current state: %s\n usage: %s\n", s,
+ "echo hoston/hostoff/deviceon/deviceoff > plugusb\n");
+}
+
+static ssize_t plugusb_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ if (!strncmp(buf, "hoston", strlen("hoston")))
+ hisi_usb_otg_event(ID_FALL_EVENT);
+ else if (!strncmp(buf, "hostoff", strlen("hostoff")))
+ hisi_usb_otg_event(ID_RISE_EVENT);
+ else if (!strncmp(buf, "deviceon", strlen("deviceon")))
+ hisi_usb_otg_event(CHARGER_CONNECT_EVENT);
+ else if (!strncmp(buf, "deviceoff", strlen("deviceoff")))
+ hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT);
+ else
+ usb_err("input state is ilegal!\n");
+
+ /* added for show message of plugusb status to com port */
+ pr_err("[USB.plugusb] %s\n", buf);
+
+ return size;
+}
+
+/*lint -save -e750 */
+DEVICE_ATTR(plugusb, (0644), plugusb_show, plugusb_store);
+/*lint -restore */
+
+static const char * const charger_type_array[] = {
+ [CHARGER_TYPE_SDP] = "sdp", /* Standard Downstreame Port */
+ [CHARGER_TYPE_CDP] = "cdp", /* Charging Downstreame Port */
+ [CHARGER_TYPE_DCP] = "dcp", /* Dedicate Charging Port */
+ [CHARGER_TYPE_UNKNOWN] = "unknown", /* non-standard */
+ [CHARGER_TYPE_NONE] = "none", /* not connected */
+ [PLEASE_PROVIDE_POWER] = "provide" /* host mode, provide power */
+};
+
+static enum hisi_charger_type get_charger_type_from_str(const char *buf,
+ size_t size)
+{
+ int i = 0;
+ enum hisi_charger_type ret = CHARGER_TYPE_NONE;
+
+ for (i = 0; i < sizeof(charger_type_array) /
+ sizeof(charger_type_array[0]); i++) {
+ if (!strncmp(buf, charger_type_array[i], size - 1)) {
+ ret = (enum hisi_charger_type)i;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+ssize_t hiusb_do_charger_show(void *dev_data, char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+ enum hisi_charger_type charger_type = CHARGER_TYPE_NONE;
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return scnprintf(buf, size,
+ "platform_get_drvdata return null\n");
+ }
+
+ mutex_lock(&hisi_dwc->lock);
+ charger_type = hisi_dwc->charger_type;
+ mutex_unlock(&hisi_dwc->lock);
+
+ return scnprintf(buf, size, "[(%d):Charger type = %s]\n"
+ "----------------------------------------------------------------\n"
+ "usage: echo {str} > chargertest\n"
+ " sdp: Standard Downstreame Port\n"
+ " cdp: Charging Downstreame Port\n"
+ " dcp: Dedicate Charging Port\n"
+ " unknown: non-standard\n"
+ " none: not connected\n"
+ " provide: host mode, provide power\n"
+ , charger_type, charger_type_array[charger_type]);
+}
+
+int hiusb_get_eyepattern_param(void *dev_data, char *buf, size_t len)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+ int ret = 0;
+
+ if (hisi_dwc) {
+ ret = scnprintf(buf, len, "device:0x%x\nhost:0x%x\n",
+ hisi_dwc->eye_diagram_param,
+ hisi_dwc->eye_diagram_host_param);
+ } else {
+ usb_err("hisi_dwc NULL\n");
+ ret = scnprintf(buf, len, "hisi_dwc NULL\n");
+ }
+
+ return ret;
+}
+
+int hiusb_set_eyepattern_param(void *dev_data, const char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+ int eye_diagram_param;
+
+ if (!hisi_dwc) {
+ pr_err("seteye: hisi_dwc is null\n");
+ return size;
+ }
+
+ if (sscanf(buf, "%32x", &eye_diagram_param) != 1)
+ return size;
+
+ hisi_dwc->eye_diagram_param = eye_diagram_param;
+ hisi_dwc->eye_diagram_host_param = eye_diagram_param;
+
+ return size;
+}
+
+static void notify_charger_type(struct hisi_dwc3_device *hisi_dwc3);
+ssize_t hiusb_do_charger_store(void *dev_data, const char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+ enum hisi_charger_type charger_type =
+ get_charger_type_from_str(buf, size);
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return size;
+ }
+
+ mutex_lock(&hisi_dwc->lock);
+ hisi_dwc->charger_type = charger_type;
+ notify_charger_type(hisi_dwc);
+ mutex_unlock(&hisi_dwc->lock);
+
+ return size;
+}
+
+#ifdef CONFIG_HISI_DEBUG_FS
+static ssize_t fakecharger_show(void *dev_data, char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return scnprintf(buf, size,
+ "platform_get_drvdata return null\n");
+ }
+
+ return scnprintf(buf, size, "[fake charger type: %s]\n",
+ hisi_dwc->fake_charger_type == CHARGER_TYPE_NONE ?
+ "not fake" :
+ charger_type_array[hisi_dwc->fake_charger_type]);
+}
+
+static ssize_t fakecharger_store(void *dev_data, const char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+ enum hisi_charger_type charger_type =
+ get_charger_type_from_str(buf, size);
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return size;
+ }
+
+ mutex_lock(&hisi_dwc->lock);
+ hisi_dwc->fake_charger_type = charger_type;
+ mutex_unlock(&hisi_dwc->lock);
+
+ return size;
+}
+#endif
+ssize_t hiusb_do_eventmask_show(void *dev_data, char *buf, size_t size)
+{
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return scnprintf(buf, size,
+ "platform_get_drvdata return null\n");
+ }
+
+ return scnprintf(buf, size, "%d\n", hisi_dwc->eventmask);
+}
+
+ssize_t hiusb_do_eventmask_store(void *dev_data, const char *buf, size_t size)
+{
+ int eventmask;
+ struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data;
+
+ if (!hisi_dwc) {
+ pr_err("platform_get_drvdata return null\n");
+ return size;
+ }
+
+ if (sscanf(buf, "%1d", &eventmask) != 1)
+ return size;
+
+ hisi_dwc->eventmask = eventmask;
+
+ return size;
+}
+
+static struct device_attribute *hisi_dwc3_attributes[] = {
+ &dev_attr_plugusb,
+ NULL
+};
+
+static int create_attr_file(struct device *dev)
+{
+ struct device_attribute **attrs = hisi_dwc3_attributes;
+ struct device_attribute *attr;
+ struct class *hisi_usb_class;
+ struct device *hisi_usb_dev;
+ int i;
+ int ret = 0;
+
+ usb_dbg("+\n");
+ for (i = 0; attrs[i]; i++) {
+ attr = attrs[i];
+ ret = device_create_file(dev, attr);
+ if (ret) {
+ dev_err(dev, "create attr file error!\n");
+ goto err;
+ }
+ }
+
+ hisi_usb_class = class_create(THIS_MODULE, "hisi_usb_class");
+ if (IS_ERR(hisi_usb_class)) {
+ usb_dbg("create hisi_usb_class error!\n");
+ } else {
+ hisi_usb_dev = device_create(hisi_usb_class, NULL, 0,
+ NULL, "hisi_usb_dev");
+ if (IS_ERR(hisi_usb_dev))
+ usb_dbg("create hisi_usb_dev error!\n");
+ else
+ ret |= sysfs_create_link(&hisi_usb_dev->kobj,
+ &dev->kobj, "interface");
+ }
+ if (ret)
+ usb_dbg("create attr file error!\n");
+
+#ifdef CONFIG_HISI_DEBUG_FS
+ hiusb_debug_quick_register(
+ platform_get_drvdata(to_platform_device(dev)),
+ (hiusb_debug_show_ops)fakecharger_show,
+ (hiusb_debug_store_ops)fakecharger_store);
+ hiusb_debug_init(platform_get_drvdata(to_platform_device(dev)));
+#endif
+
+ usb_dbg("-\n");
+ return 0;
+
+err:
+ for (i-- ; i >= 0; i--) {
+ attr = attrs[i];
+ device_remove_file(dev, attr);
+ }
+
+ return ret;
+}
+
+static void remove_attr_file(struct device *dev)
+{
+ struct device_attribute **attrs = hisi_dwc3_attributes;
+ struct device_attribute *attr;
+
+ while ((attr = *attrs++))
+ device_remove_file(dev, attr);
+}
+#else
+static inline int create_attr_file(struct device *dev)
+{
+ return 0;
+}
+
+static inline void remove_attr_file(struct device *dev) {}
+#endif
+
+static void phy_cr_wait_ack(void __iomem *otg_bc_base)
+{
+ int i = 1000;
+
+ while (1) {
+ if ((readl(otg_bc_base + USB3PHY_CR_STS) &
+ USB3OTG_PHY_CR_ACK) == 1)
+ break;
+ usleep_range(50, 60);
+ if (i-- < 0) {
+ usb_err("wait phy_cr_ack timeout!\n");
+ break;
+ }
+ }
+}
+
+static void phy_cr_set_addr(void __iomem *otg_bc_base, u32 addr)
+{
+ u32 reg;
+
+ /* set addr */
+ reg = USB3OTG_PHY_CR_DATA_IN(addr);
+ writel(reg, otg_bc_base + USB3PHY_CR_CTRL);
+
+ usleep_range(100, 120);
+
+ /* cap addr */
+ reg = readl(otg_bc_base + USB3PHY_CR_CTRL);
+ reg |= USB3OTG_PHY_CR_CAP_ADDR;
+ writel(reg, otg_bc_base + USB3PHY_CR_CTRL);
+
+ phy_cr_wait_ack(otg_bc_base);
+
+ /* clear ctrl reg */
+ writel(0, otg_bc_base + USB3PHY_CR_CTRL);
+}
+
+static u16 phy_cr_read(void __iomem *otg_bc_base, u32 addr)
+{
+ u32 reg;
+ int i = 1000;
+
+ phy_cr_set_addr(otg_bc_base, addr);
+
+ /* read cap */
+ writel(USB3OTG_PHY_CR_READ, otg_bc_base + USB3PHY_CR_CTRL);
+
+ usleep_range(100, 120);
+
+ while (1) {
+ reg = readl(otg_bc_base + USB3PHY_CR_STS);
+ if ((reg & USB3OTG_PHY_CR_ACK) == 1)
+ break;
+ usleep_range(50, 60);
+ if (i-- < 0) {
+ usb_err("wait phy_cr_ack timeout!\n");
+ break;
+ }
+ }
+
+ /* clear ctrl reg */
+ writel(0, otg_bc_base + USB3PHY_CR_CTRL);
+
+ return (u16)USB3OTG_PHY_CR_DATA_OUT(reg);
+}
+
+static void phy_cr_write(void __iomem *otg_bc_base, u32 addr, u32 value)
+{
+ u32 reg;
+
+ phy_cr_set_addr(otg_bc_base, addr);
+
+ reg = USB3OTG_PHY_CR_DATA_IN(value);
+ writel(reg, otg_bc_base + USB3PHY_CR_CTRL);
+
+ /* cap data */
+ reg = readl(otg_bc_base + USB3PHY_CR_CTRL);
+ reg |= USB3OTG_PHY_CR_CAP_DATA;
+ writel(reg, otg_bc_base + USB3PHY_CR_CTRL);
+
+ /* wait ack */
+ phy_cr_wait_ack(otg_bc_base);
+
+ /* clear ctrl reg */
+ writel(0, otg_bc_base + USB3PHY_CR_CTRL);
+
+ reg = USB3OTG_PHY_CR_WRITE;
+ writel(reg, otg_bc_base + USB3PHY_CR_CTRL);
+
+ /* wait ack */
+ phy_cr_wait_ack(otg_bc_base);
+}
+
+void set_usb3_phy_cr_param(u32 addr, u32 value)
+{
+ if (!hisi_dwc3_dev) {
+ pr_err("hisi dwc3 device not ready!\n");
+ return;
+ }
+
+ phy_cr_write(hisi_dwc3_dev->otg_bc_reg_base, addr, value);
+}
+EXPORT_SYMBOL_GPL(set_usb3_phy_cr_param);
+
+void read_usb3_phy_cr_param(u32 addr)
+{
+ if (!hisi_dwc3_dev) {
+ pr_err("hisi dwc3 device not ready!\n");
+ return;
+ }
+
+ usb_dbg("read usb3 phy cr param 0x%x\n",
+ phy_cr_read(hisi_dwc3_dev->otg_bc_reg_base, addr));
+}
+EXPORT_SYMBOL_GPL(read_usb3_phy_cr_param);
+
+void config_femtophy_param(struct hisi_dwc3_device *hisi_dwc)
+{
+ u32 reg;
+ void __iomem *otg_bc_base = hisi_dwc->otg_bc_reg_base;
+
+ if (hisi_dwc->fpga_flag != 0)
+ return;
+
+ /* set high speed phy parameter */
+ if (hisi_dwc->host_flag) {
+ writel(hisi_dwc->eye_diagram_host_param,
+ otg_bc_base + USBOTG3_CTRL4);
+ usb_dbg("set hs phy param 0x%x for host\n",
+ readl(otg_bc_base + USBOTG3_CTRL4));
+ } else {
+ writel(hisi_dwc->eye_diagram_param,
+ otg_bc_base + USBOTG3_CTRL4);
+ usb_dbg("set hs phy param 0x%x for device\n",
+ readl(otg_bc_base + USBOTG3_CTRL4));
+ }
+
+ /* set usb3 phy cr config for usb3.0 */
+
+ if (hisi_dwc->host_flag) {
+ phy_cr_write(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI,
+ hisi_dwc->usb3_phy_host_cr_param);
+ } else {
+ phy_cr_write(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI,
+ hisi_dwc->usb3_phy_cr_param);
+ }
+
+ usb_dbg("set ss phy rx equalization 0x%x\n",
+ phy_cr_read(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI));
+
+ /* enable RX_SCOPE_LFPS_EN for usb3.0 */
+ reg = phy_cr_read(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC);
+ reg |= RX_SCOPE_LFPS_EN;
+ phy_cr_write(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC, reg);
+
+ usb_dbg("set ss RX_SCOPE_VDCC 0x%x\n",
+ phy_cr_read(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC));
+
+ reg = readl(otg_bc_base + USBOTG3_CTRL6);
+ reg &= ~TX_VBOOST_LVL_MASK;
+ reg |= TX_VBOOST_LVL(hisi_dwc->usb3_phy_tx_vboost_lvl);
+ writel(reg, otg_bc_base + USBOTG3_CTRL6);
+ usb_dbg("set ss phy tx vboost lvl 0x%x\n",
+ readl(otg_bc_base + USBOTG3_CTRL6));
+}
+
+int hisi_charger_type_notifier_register(struct notifier_block *nb)
+{
+ if (!hisi_dwc3_dev) {
+ pr_err("hisi dwc3 device not ready!\n");
+ return -EBUSY;
+ }
+ if (!nb)
+ return -EINVAL;
+ return atomic_notifier_chain_register(
+ &hisi_dwc3_dev->charger_type_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(hisi_charger_type_notifier_register);
+
+int hisi_charger_type_notifier_unregister(struct notifier_block *nb)
+{
+ if (!hisi_dwc3_dev) {
+ pr_err("hisi dwc3 device not ready!\n");
+ return -EBUSY;
+ }
+ if (!nb)
+ return -EINVAL;
+ return atomic_notifier_chain_unregister(
+ &hisi_dwc3_dev->charger_type_notifier,
+ nb);
+}
+EXPORT_SYMBOL_GPL(hisi_charger_type_notifier_unregister);
+
+/* BC1.2 Spec:
+ * If a PD detects that D+ is greater than VDAT_REF, it knows that it is
+ * attached to a DCP. It is then required to enable VDP_SRC or pull D+
+ * to VDP_UP through RDP_UP
+ */
+static void disable_vdp_src(struct hisi_dwc3_device *hisi_dwc3)
+{
+ void __iomem *base = hisi_dwc3->otg_bc_reg_base;
+ u32 reg;
+
+ usb_dbg("diaable VDP_SRC\n");
+
+ reg = readl(base + BC_CTRL2);
+ reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB);
+ writel(reg, base + BC_CTRL2);
+
+ reg = readl(base + BC_CTRL0);
+ reg |= BC_CTRL0_BC_SUSPEND_N;
+ writel(reg, base + BC_CTRL0);
+
+ writel((readl(base + BC_CTRL1) & ~BC_CTRL1_BC_MODE), base + BC_CTRL1);
+}
+
+static void enable_vdp_src(struct hisi_dwc3_device *hisi_dwc3)
+{
+ void __iomem *base = hisi_dwc3->otg_bc_reg_base;
+ u32 reg;
+
+ reg = readl(base + BC_CTRL2);
+ reg &= ~BC_CTRL2_BC_PHY_CHRGSEL;
+ reg |= (BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB);
+ writel(reg, base + BC_CTRL2);
+}
+
+static enum hisi_charger_type detect_charger_type(struct hisi_dwc3_device
+ *hisi_dwc3)
+{
+ enum hisi_charger_type type = CHARGER_TYPE_NONE;
+ void __iomem *base = hisi_dwc3->otg_bc_reg_base;
+ u32 reg;
+ unsigned long jiffies_expire;
+ int i = 0;
+
+ if (hisi_dwc3->fpga_flag) {
+ usb_dbg("this is fpga platform, charger is SDP\n");
+ return CHARGER_TYPE_SDP;
+ }
+
+ if (hisi_dwc3->fake_charger_type != CHARGER_TYPE_NONE) {
+ usb_dbg("fake type: %d\n", hisi_dwc3->fake_charger_type);
+ return hisi_dwc3->fake_charger_type;
+ }
+
+ writel(BC_CTRL1_BC_MODE, base + BC_CTRL1);
+
+ /* phy suspend */
+ reg = readl(base + BC_CTRL0);
+ reg &= ~BC_CTRL0_BC_SUSPEND_N;
+ writel(reg, base + BC_CTRL0);
+
+ /* enable DCD */
+ reg = readl(base + BC_CTRL2);
+ reg |= BC_CTRL2_BC_PHY_DCDENB;
+ writel(reg, base + BC_CTRL2);
+
+ reg = readl(base + BC_CTRL0);
+ reg |= BC_CTRL0_BC_DMPULLDOWN;
+ writel(reg, base + BC_CTRL0);
+
+ jiffies_expire = jiffies + msecs_to_jiffies(900);
+ msleep(50);
+ while (1) {
+ reg = readl(base + BC_STS0);
+ if ((reg & BC_STS0_BC_PHY_FSVPLUS) == 0) {
+ i++;
+ if (i >= 10)
+ break;
+ } else {
+ i = 0;
+ }
+
+ msleep(20);
+
+ if (time_after(jiffies, jiffies_expire)) {
+ usb_dbg("DCD timeout!\n");
+ type = CHARGER_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ reg = readl(base + BC_CTRL0);
+ reg &= ~BC_CTRL0_BC_DMPULLDOWN;
+ writel(reg, base + BC_CTRL0);
+
+ /* disable DCD */
+ reg = readl(base + BC_CTRL2);
+ reg &= ~BC_CTRL2_BC_PHY_DCDENB;
+ writel(reg, base + BC_CTRL2);
+
+ usb_dbg("DCD done\n");
+
+ if (type == CHARGER_TYPE_NONE) {
+ /* enable vdect */
+ reg = readl(base + BC_CTRL2);
+ reg &= ~BC_CTRL2_BC_PHY_CHRGSEL;
+ reg |= (BC_CTRL2_BC_PHY_VDATARCENB |
+ BC_CTRL2_BC_PHY_VDATDETENB);
+ writel(reg, base + BC_CTRL2);
+
+ msleep(20);
+
+ /* we can detect sdp or cdp dcp */
+ reg = readl(base + BC_STS0);
+ if ((reg & BC_STS0_BC_PHY_CHGDET) == 0)
+ type = CHARGER_TYPE_SDP;
+
+ /* disable vdect */
+ reg = readl(base + BC_CTRL2);
+ reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB |
+ BC_CTRL2_BC_PHY_VDATDETENB);
+ writel(reg, base + BC_CTRL2);
+ }
+
+ usb_dbg("Primary Detection done\n");
+
+ if (type == CHARGER_TYPE_NONE) {
+ /* enable vdect */
+ reg = readl(base + BC_CTRL2);
+ reg |= (BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB
+ | BC_CTRL2_BC_PHY_CHRGSEL);
+ writel(reg, base + BC_CTRL2);
+
+ msleep(20);
+
+ /* we can detect sdp or cdp dcp */
+ reg = readl(base + BC_STS0);
+ if ((reg & BC_STS0_BC_PHY_CHGDET) == 0)
+ type = CHARGER_TYPE_CDP;
+ else
+ type = CHARGER_TYPE_DCP;
+
+ /* disable vdect */
+ reg = readl(base + BC_CTRL2);
+ reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB
+ | BC_CTRL2_BC_PHY_CHRGSEL);
+ writel(reg, base + BC_CTRL2);
+ }
+
+ usb_dbg("Secondary Detection done\n");
+
+ /* If a PD detects that D+ is greater than VDAT_REF, it knows that it is
+ * attached to a DCP. It is then required to enable VDP_SRC or pull D+
+ * to VDP_UP through RDP_UP
+ */
+ if (type == CHARGER_TYPE_DCP) {
+ usb_dbg("charger is DCP, enable VDP_SRC\n");
+ enable_vdp_src(hisi_dwc3);
+ } else {
+ /* bc_suspend = 1, nomal mode */
+ reg = readl(base + BC_CTRL0);
+ reg |= BC_CTRL0_BC_SUSPEND_N;
+ writel(reg, base + BC_CTRL0);
+
+ msleep(20);
+
+ /* disable BC */
+ writel((readl(base + BC_CTRL1) & ~BC_CTRL1_BC_MODE),
+ base + BC_CTRL1);
+ }
+
+ usb_dbg("type: %d\n", type);
+
+ return type;
+}
+
+enum hisi_charger_type hisi_get_charger_type(void)
+{
+ if (!hisi_dwc3_dev) {
+ pr_err("[%s]hisi_dwc3 not yet probed!\n", __func__);
+ return CHARGER_TYPE_NONE;
+ }
+
+ pr_info("[%s]type: %d\n", __func__, hisi_dwc3_dev->charger_type);
+ return hisi_dwc3_dev->charger_type;
+}
+EXPORT_SYMBOL_GPL(hisi_get_charger_type);
+
+static void notify_charger_type(struct hisi_dwc3_device *hisi_dwc3)
+{
+ atomic_notifier_call_chain(&hisi_dwc3->charger_type_notifier,
+ hisi_dwc3->charger_type, hisi_dwc3);
+}
+
+static void set_vbus_power(struct hisi_dwc3_device *hisi_dwc3,
+ unsigned int is_on)
+{
+ enum hisi_charger_type new;
+
+ if (is_on == 0)
+ new = CHARGER_TYPE_NONE;
+ else
+ new = PLEASE_PROVIDE_POWER;
+ if (hisi_dwc3->charger_type != new) {
+ usb_dbg("set port power %d\n", is_on);
+ hisi_dwc3->charger_type = new;
+ notify_charger_type(hisi_dwc3);
+ }
+}
+
+static void hisi_dwc3_wake_lock(struct hisi_dwc3_device *hisi_dwc3)
+{
+ if (!(hisi_dwc3->ws.active)) {
+ usb_dbg("usb otg wake lock\n");
+ __pm_stay_awake(&hisi_dwc3->ws);
+ }
+}
+
+static void hisi_dwc3_wake_unlock(struct hisi_dwc3_device *hisi_dwc3)
+{
+ if (hisi_dwc3->ws.active) {
+ usb_dbg("usb otg wake unlock\n");
+ __pm_relax(&hisi_dwc3->ws);
+ }
+}
+
+static inline bool enumerate_allowed(struct hisi_dwc3_device *hisi_dwc)
+{
+ /* do not start peripheral if real charger connected */
+ return ((hisi_dwc->charger_type == CHARGER_TYPE_SDP) ||
+ (hisi_dwc->charger_type == CHARGER_TYPE_CDP) ||
+ (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN));
+}
+
+static inline bool sleep_allowed(struct hisi_dwc3_device *hisi_dwc)
+{
+ return ((hisi_dwc->charger_type == CHARGER_TYPE_DCP) ||
+ (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN));
+}
+
+/*
+ * create event queue
+ * event_queue: event queue handle
+ * count: set the queue max node
+ */
+int event_queue_creat(struct hiusb_event_queue *event_queue, unsigned int count)
+{
+ if (!event_queue) {
+ pr_err(" %s bad argument (0x%p)\n",
+ __func__, event_queue);
+ return -EINVAL;
+ }
+
+ count = (count >= MAX_EVENT_COUNT ? MAX_EVENT_COUNT : count);
+ event_queue->max_event = count;
+ event_queue->num_event = (count >= EVENT_QUEUE_UNIT ?
+ EVENT_QUEUE_UNIT : count);
+
+ event_queue->event = kzalloc(
+ (event_queue->num_event *
+ sizeof(enum otg_dev_event_type)), GFP_KERNEL);
+ if (!event_queue->event) {
+ pr_err(" %s :Can't alloc space:%d!\n",
+ __func__, event_queue->num_event);
+ return -ENOMEM;
+ }
+
+ event_queue->enpos = 0;
+ event_queue->depos = 0;
+ event_queue->overlay = 0;
+ event_queue->overlay_index = 0;
+
+ return 0;
+}
+
+void event_queue_destroy(struct hiusb_event_queue *event_queue)
+{
+ if (!event_queue)
+ return;
+
+ kfree(event_queue->event);
+ event_queue->event = NULL;
+ event_queue->enpos = 0;
+ event_queue->depos = 0;
+ event_queue->num_event = 0;
+ event_queue->max_event = 0;
+ event_queue->overlay = 0;
+ event_queue->overlay_index = 0;
+}
+
+/*
+ * check if the queue is full
+ * return true means full, false is not.
+ */
+int event_queue_isfull(struct hiusb_event_queue *event_queue)
+{
+ if (!event_queue)
+ return -EINVAL;
+
+ return (((event_queue->enpos + 1) % event_queue->num_event) ==
+ (event_queue->depos));
+}
+
+/*
+ * check if the queue is full
+ * return true means empty, false or not.
+ */
+int event_queue_isempty(struct hiusb_event_queue *event_queue)
+{
+ if (!event_queue)
+ return -EINVAL;
+
+ return (event_queue->enpos == event_queue->depos);
+}
+
+static inline void event_queue_set_overlay(
+ struct hiusb_event_queue *event_queue)
+{
+ if (event_queue->overlay)
+ return;
+ event_queue->overlay = 1;
+ event_queue->overlay_index = event_queue->enpos;
+}
+
+static inline void event_queue_clear_overlay(
+ struct hiusb_event_queue *event_queue)
+{
+ event_queue->overlay = 0;
+ event_queue->overlay_index = 0;
+}
+
+/*
+ * put the new event en queue
+ * if the event_queue is full, return -ENOSPC
+ */
+int event_enqueue(struct hiusb_event_queue *event_queue,
+ enum otg_dev_event_type event)
+{
+ /* no need verify argument, isfull will check it */
+ if (event_queue_isfull(event_queue)) {
+ pr_err("event queue full!\n");
+ return -ENOSPC;
+ }
+
+ if (event_queue->overlay) {
+ if (event_queue->overlay_index == event_queue->enpos) {
+ event_queue->enpos = ((event_queue->enpos + 1) %
+ event_queue->num_event);
+ }
+
+ if (event_queue_isempty(event_queue)) {
+ pr_err("overlay and queue isempty? just enqueue!\n");
+ event_queue->overlay_index = (
+ (event_queue->overlay_index + 1) %
+ event_queue->num_event);
+ event_queue->enpos = ((event_queue->enpos + 1) %
+ event_queue->num_event);
+ event_queue->overlay = 0;
+ }
+
+ event_queue->event[event_queue->overlay_index] = event;
+ } else {
+ event_queue->event[event_queue->enpos] = event;
+ event_queue->enpos = ((event_queue->enpos + 1) %
+ event_queue->num_event);
+ }
+
+ return 0;
+}
+
+/*
+ * get event from event_queue
+ * this function never return fail
+ * if the event_queue is empty, return NONE_EVENT
+ */
+enum otg_dev_event_type event_dequeue(struct hiusb_event_queue *event_queue)
+{
+ enum otg_dev_event_type event;
+
+ /* no need verify argument, isempty will check it */
+ if (event_queue_isempty(event_queue))
+ return NONE_EVENT;
+
+ event = event_queue->event[event_queue->depos];
+ event_queue->depos = ((event_queue->depos + 1) %
+ event_queue->num_event);
+
+ return event;
+}
+
+static void handle_event(struct hisi_dwc3_device *hisi_dwc,
+ enum otg_dev_event_type event)
+{
+ int ret = 0;
+
+ usb_err("[%s] type: %d\n", __func__, event);
+ switch (event) {
+ case CHARGER_CONNECT_EVENT:
+ if (hisi_dwc->state == USB_STATE_DEVICE) {
+ usb_dbg("Already in device mode, do nothing\n");
+ } else if (hisi_dwc->state == USB_STATE_OFF) {
+ hisi_dwc->host_flag = 0;
+
+ /* due to detect charger type, must resume hisi_dwc */
+ ret = pm_runtime_get_sync(&hisi_dwc->pdev->dev);
+ if (ret < 0) {
+ usb_err("resume hisi_dwc failed (ret %d)\n",
+ ret);
+ return;
+ }
+
+ /* detect charger type */
+ hisi_dwc->charger_type = detect_charger_type(hisi_dwc);
+ notify_charger_type(hisi_dwc);
+
+ /* In some cases, DCP is detected as SDP wrongly.
+ * To avoid this, start bc_again delay work to
+ * detect charger type once more.
+ * If later the enum process is executed,
+ * then it's a real SDP, so
+ * the work will be canceled.
+ */
+ if (hisi_dwc->bc_again_flag &&
+ (hisi_dwc->charger_type == CHARGER_TYPE_SDP)) {
+ ret = queue_delayed_work(
+ system_power_efficient_wq,
+ &hisi_dwc->bc_again_work,
+ msecs_to_jiffies(BC_AGAIN_DELAY_TIME));
+ usb_dbg("schedule ret:%d, run bc_again_work %dms later\n",
+ ret, BC_AGAIN_DELAY_TIME);
+ }
+
+ /* do not start peripheral if real charger connected */
+ if (enumerate_allowed(hisi_dwc)) {
+ if (hisi_dwc->fpga_usb_mode_gpio > 0) {
+ gpio_direction_output(
+ hisi_dwc->fpga_usb_mode_gpio,
+ 0);
+ usb_dbg("switch to device mode\n");
+ }
+
+ /* start peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_VBUS_SET);
+ if (ret) {
+ pm_runtime_put(&hisi_dwc->pdev->dev);
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ usb_err("start peripheral error\n");
+ return;
+ }
+ } else {
+ usb_dbg("a real charger connected\n");
+ }
+
+ hisi_dwc->state = USB_STATE_DEVICE;
+
+ if (sleep_allowed(hisi_dwc))
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ else
+ hisi_dwc3_wake_lock(hisi_dwc);
+
+ usb_dbg("hisi usb status: OFF -> DEVICE\n");
+ } else if (hisi_dwc->state == USB_STATE_HOST) {
+ usb_dbg("Charger connect interrupt in HOST mode\n");
+ }
+
+ break;
+
+ case CHARGER_DISCONNECT_EVENT:
+ hisi_dwc->need_disable_vdp = 0;
+
+ if (hisi_dwc->state == USB_STATE_OFF) {
+ usb_dbg("Already in off mode, do nothing\n");
+ } else if (hisi_dwc->state == USB_STATE_DEVICE) {
+ if (hisi_dwc->bc_again_flag) {
+ ret = cancel_delayed_work_sync(
+ &hisi_dwc->bc_again_work);
+ usb_dbg("cancel bc_again_work sync:%d\n", ret);
+ }
+
+ /* peripheral not started, if real charger connected */
+ if (enumerate_allowed(hisi_dwc)) {
+ /* stop peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_VBUS_CLEAR);
+ if (ret) {
+ usb_err("stop peripheral error\n");
+ return;
+ }
+ } else {
+ usb_dbg("connected is a real charger\n");
+ disable_vdp_src(hisi_dwc);
+ }
+
+ /* usb cable disconnect, notify no charger */
+ hisi_dwc->charger_type = CHARGER_TYPE_NONE;
+ notify_charger_type(hisi_dwc);
+
+ hisi_dwc->state = USB_STATE_OFF;
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ pm_runtime_put(&hisi_dwc->pdev->dev);
+
+ usb_dbg("hisi usb status: DEVICE -> OFF\n");
+ } else if (hisi_dwc->state == USB_STATE_HOST) {
+ usb_dbg("Charger disconnect interrupt in HOST mode\n");
+ }
+
+ break;
+
+ case ID_FALL_EVENT:
+ if (hisi_dwc->state == USB_STATE_OFF) {
+ set_vbus_power(hisi_dwc, 1);
+
+ hisi_dwc->host_flag = 1;
+
+ if (hisi_dwc->fpga_usb_mode_gpio > 0) {
+ gpio_direction_output(
+ hisi_dwc->fpga_usb_mode_gpio,
+ 1);
+ usb_dbg("switch to host mode\n");
+ }
+
+ /* start host */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_ID_CLEAR);
+ if (ret) {
+ usb_err("start host error\n");
+ set_vbus_power(hisi_dwc, 0);
+ return;
+ }
+
+ hisi_dwc->state = USB_STATE_HOST;
+ hisi_dwc3_wake_lock(hisi_dwc);
+
+ usb_dbg("hisi usb_status: OFF -> HOST\n");
+ } else if (hisi_dwc->state == USB_STATE_DEVICE) {
+ usb_dbg("id fall interrupt in DEVICE mode\n");
+ } else if (hisi_dwc->state == USB_STATE_HOST) {
+ usb_dbg("Already in host mode, do nothing\n");
+ }
+ break;
+ case ID_RISE_EVENT:
+ if (hisi_dwc->state == USB_STATE_HOST) {
+ set_vbus_power(hisi_dwc, 0);
+
+ /* stop host */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_ID_SET);
+ if (ret) {
+ usb_err("stop host error\n");
+ return;
+ }
+
+ hisi_dwc->state = USB_STATE_OFF;
+ hisi_dwc3_wake_unlock(hisi_dwc);
+
+ usb_dbg("hiusb_status: HOST -> OFF\n");
+ } else if (hisi_dwc->state == USB_STATE_DEVICE) {
+ usb_dbg("id rise interrupt in DEVICE mode\n");
+ } else if (hisi_dwc->state == USB_STATE_OFF) {
+ usb_dbg("Already in host mode, do nothing\n");
+ }
+
+ break;
+ default:
+ usb_dbg("illegal event type!\n");
+ break;
+ }
+}
+
+static void event_work(struct work_struct *work)
+{
+ unsigned long flags;
+ enum otg_dev_event_type event;
+ struct hisi_dwc3_device *hisi_dwc = container_of(work,
+ struct hisi_dwc3_device, event_work);
+
+ mutex_lock(&hisi_dwc->lock);
+
+ usb_dbg("+\n");
+
+ while (!event_queue_isempty(&hisi_dwc->event_queue)) {
+ spin_lock_irqsave(&hisi_dwc->event_lock, flags);
+ event = event_dequeue(&hisi_dwc->event_queue);
+ spin_unlock_irqrestore(&hisi_dwc->event_lock, flags);
+
+ handle_event(hisi_dwc, event);
+ }
+
+ event_queue_clear_overlay(&hisi_dwc->event_queue);
+
+ usb_dbg("-\n");
+ mutex_unlock(&hisi_dwc->lock);
+}
+
+static int event_check(enum otg_dev_event_type last_event,
+ enum otg_dev_event_type new_event)
+{
+ int ret = 0;
+
+ if (last_event == NONE_EVENT)
+ return 1;
+
+ switch (new_event) {
+ case CHARGER_CONNECT_EVENT:
+ if ((last_event == CHARGER_DISCONNECT_EVENT) ||
+ (last_event == ID_RISE_EVENT))
+ ret = 1;
+ break;
+ case CHARGER_DISCONNECT_EVENT:
+ if (last_event == CHARGER_CONNECT_EVENT)
+ ret = 1;
+ break;
+ case ID_FALL_EVENT:
+ if ((last_event == CHARGER_DISCONNECT_EVENT) ||
+ (last_event == ID_RISE_EVENT))
+ ret = 1;
+ break;
+ case ID_RISE_EVENT:
+ if (last_event == ID_FALL_EVENT)
+ ret = 1;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+int hisi_usb_otg_event(enum otg_dev_event_type event)
+{
+ int ret = 0;
+#ifdef CONFIG_USB_DWC3_OTG
+ unsigned long flags;
+ struct hisi_dwc3_device *hisi_dwc3 = hisi_dwc3_dev;
+#endif
+ usb_err("%s in:%d\n", __func__, event);
+#ifdef CONFIG_USB_DWC3_OTG
+ usb_err("%s in otg:%d\n", __func__, event);
+
+ if (!hisi_dwc3) {
+ usb_dbg(" %s error:%d\n", __func__, event);
+ return -EBUSY;
+ }
+
+ if (hisi_dwc3->eventmask) {
+ usb_dbg("eventmask enabled, mask all events.\n");
+ return ret;
+ }
+
+ spin_lock_irqsave(&hisi_dwc3->event_lock, flags);
+
+ if (event_check(hisi_dwc3->event, event)) {
+ usb_dbg("event: %d\n", event);
+ hisi_dwc3->event = event;
+
+ if ((event == CHARGER_CONNECT_EVENT) ||
+ (event == CHARGER_DISCONNECT_EVENT))
+ hisi_dwc3_wake_lock(hisi_dwc3);
+
+ if (!event_enqueue(&hisi_dwc3->event_queue, event)) {
+ ret = queue_work(system_power_efficient_wq,
+ &hisi_dwc3->event_work);
+ if (!ret)
+ usb_err("schedule event_work wait:%d]\n",
+ event);
+ } else {
+ usb_err("%s can't enqueue event:%d\n",
+ __func__, event);
+ }
+
+ if ((event == ID_RISE_EVENT) ||
+ (event == CHARGER_DISCONNECT_EVENT))
+ event_queue_set_overlay(&hisi_dwc3->event_queue);
+ }
+ spin_unlock_irqrestore(&hisi_dwc3->event_lock, flags);
+#endif
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hisi_usb_otg_event);
+
+static void bc_again(struct hisi_dwc3_device *hisi_dwc)
+{
+ int ret;
+
+ /*
+ * STEP 1
+ */
+ /* stop peripheral which is started when detected as SDP before */
+ if (enumerate_allowed(hisi_dwc)) {
+ ret = dwc3_otg_work(dwc_otg_handler, DWC3_OTG_EVT_VBUS_CLEAR);
+ if (ret) {
+ usb_err("stop peripheral error\n");
+ return;
+ }
+ }
+
+ /*
+ * STEP 2
+ */
+ hisi_dwc->charger_type = detect_charger_type(hisi_dwc);
+ notify_charger_type(hisi_dwc);
+
+ /*
+ * STEP 3
+ */
+ /* must recheck enumerate_allowed, because charger_type maybe changed,
+ * and enumerate_allowed according to charger_type
+ */
+ if (enumerate_allowed(hisi_dwc)) {
+ /* start peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_VBUS_SET);
+ if (ret) {
+ pm_runtime_put(&hisi_dwc->pdev->dev);
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ usb_err("start peripheral error\n");
+ return;
+ }
+ } else {
+ usb_dbg("a real charger connected\n");
+ }
+}
+
+void hisi_usb_otg_bc_again(void)
+{
+ struct hisi_dwc3_device *hisi_dwc = hisi_dwc3_dev;
+
+ usb_dbg("+\n");
+
+ if (!hisi_dwc) {
+ usb_err("No usb module, can't call bc again api\n");
+ return;
+ }
+
+ mutex_lock(&hisi_dwc->lock);
+
+ /* we are here because it's detected as SDP before */
+ if (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN) {
+ usb_dbg("charger_type is UNKNOWN, start bc_again_work\n");
+ bc_again(hisi_dwc);
+ }
+
+ mutex_unlock(&hisi_dwc->lock);
+ usb_dbg("-\n");
+}
+EXPORT_SYMBOL_GPL(hisi_usb_otg_bc_again);
+
+static void bc_again_work(struct work_struct *work)
+{
+ struct hisi_dwc3_device *hisi_dwc = container_of(work,
+ struct hisi_dwc3_device, bc_again_work.work);
+
+ usb_dbg("+\n");
+ mutex_lock(&hisi_dwc->lock);
+
+ /* we are here because it's detected as SDP before */
+ if (hisi_dwc->charger_type == CHARGER_TYPE_SDP) {
+ usb_dbg("charger_type is SDP, start %s\n", __func__);
+ bc_again(hisi_dwc);
+ }
+
+ mutex_unlock(&hisi_dwc->lock);
+ usb_dbg("-\n");
+}
+
+static int conndone_notifier_fn(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ int ret;
+ struct hisi_dwc3_device *hisi_dwc = container_of(nb,
+ struct hisi_dwc3_device, conndone_nb);
+
+ ret = cancel_delayed_work(&hisi_dwc->bc_again_work);
+ usb_dbg("cancel bc_again_work:%d\n", ret);
+
+ return 0;
+}
+
+/**
+ * get_usb_state() - get current USB cable state.
+ * @hisi_dwc: the instance pointer of struct hisi_dwc3_device
+ *
+ * return current USB cable state according to VBUS status and ID status.
+ */
+static enum hisi_usb_state get_usb_state(struct hisi_dwc3_device *hisi_dwc)
+{
+ if (hisi_dwc->fpga_flag) {
+ usb_dbg("this is fpga platform, usb is device mode\n");
+ return USB_STATE_DEVICE;
+ }
+
+ if (dwc3_otg_id_value(dwc_otg_handler) == 0)
+ return USB_STATE_HOST;
+ else
+ return USB_STATE_OFF;
+}
+
+static void get_phy_param(struct hisi_dwc3_device *hisi_dwc3)
+{
+ struct device *dev = &hisi_dwc3->pdev->dev;
+
+ /* hs phy param for device mode */
+ if (of_property_read_u32(dev->of_node, "eye_diagram_param",
+ &hisi_dwc3->eye_diagram_param)) {
+ usb_dbg("get eye diagram param form dt failed, use default value\n");
+ hisi_dwc3->eye_diagram_param = 0x1c466e3;
+ }
+ usb_dbg("eye diagram param: 0x%x\n", hisi_dwc3->eye_diagram_param);
+
+ /* hs phy param for host mode */
+ if (of_property_read_u32(dev->of_node, "eye_diagram_host_param",
+ &hisi_dwc3->eye_diagram_host_param)) {
+ usb_dbg("get eye diagram host param form dt failed, use default value\n");
+ hisi_dwc3->eye_diagram_host_param = 0x1c466e3;
+ }
+ usb_dbg("eye diagram host param: 0x%x\n",
+ hisi_dwc3->eye_diagram_host_param);
+
+ /* ss phy Rx Equalization */
+ if (of_property_read_u32(dev->of_node, "usb3_phy_cr_param",
+ &hisi_dwc3->usb3_phy_cr_param)) {
+ usb_dbg("get usb3_phy_cr_param form dt failed, use default value\n");
+ hisi_dwc3->usb3_phy_cr_param = (1 << 11) | (3 << 8) | (1 << 7);
+ }
+
+ /* ss phy Rx Equalization for host mode */
+ if (of_property_read_u32(dev->of_node, "usb3_phy_host_cr_param",
+ &hisi_dwc3->usb3_phy_host_cr_param)) {
+ usb_dbg("get usb3_phy_host_cr_param form dt failed, use default value\n");
+ hisi_dwc3->usb3_phy_host_cr_param =
+ (1 << 11) | (1 << 8) | (1 << 7);
+ }
+
+ usb_dbg("usb3_phy_cr_param: 0x%x\n", hisi_dwc3->usb3_phy_cr_param);
+ usb_dbg("usb3_phy_host_cr_param: 0x%x\n",
+ hisi_dwc3->usb3_phy_host_cr_param);
+
+ /* tx_vboost_lvl */
+ if (of_property_read_u32(dev->of_node, "usb3_phy_tx_vboost_lvl",
+ &hisi_dwc3->usb3_phy_tx_vboost_lvl)) {
+ usb_dbg("get usb3_phy_tx_vboost_lvl form dt failed, use default value\n");
+ hisi_dwc3->usb3_phy_tx_vboost_lvl = 5;
+ }
+ usb_dbg("usb3_phy_tx_vboost_lvl: %d\n",
+ hisi_dwc3->usb3_phy_tx_vboost_lvl);
+}
+
+/**
+ * get_resource() - prepare resources
+ * @hisi_dwc3: the instance pointer of struct hisi_dwc3_device
+ *
+ * 1. get registers base address and map registers region.
+ * 2. get regulator handler.
+ */
+static int get_resource(struct hisi_dwc3_device *hisi_dwc3)
+{
+ struct device *dev = &hisi_dwc3->pdev->dev;
+ struct resource *res;
+ struct device_node *np;
+
+ /*
+ * map PERI CRG region
+ */
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-crgctrl");
+ if (!np) {
+ dev_err(dev, "get peri cfg node failed!\n");
+ return -EINVAL;
+ }
+ hisi_dwc3->pericfg_reg_base = of_iomap(np, 0);
+ if (!hisi_dwc3->pericfg_reg_base) {
+ dev_err(dev, "iomap pericfg_reg_base failed!\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map PCTRL region
+ */
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-pctrl");
+ if (!np) {
+ dev_err(dev, "get pctrl node failed!\n");
+ return -EINVAL;
+ }
+ hisi_dwc3->pctrl_reg_base = of_iomap(np, 0);
+ if (!hisi_dwc3->pctrl_reg_base) {
+ dev_err(dev, "iomap pctrl_reg_base failed!\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map SCTRL region
+ */
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-sctrl");
+ if (!np) {
+ dev_err(dev, "get sysctrl node failed!\n");
+ return -EINVAL;
+ }
+ hisi_dwc3->sctrl_reg_base = of_iomap(np, 0);
+ if (!hisi_dwc3->sctrl_reg_base) {
+ dev_err(dev, "iomap sctrl_reg_base failed!\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map PMCTRL region
+ */
+ np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-pmctrl");
+ if (!np) {
+ dev_err(dev, "get pmctrl node failed!\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map OTG BC region
+ */
+ res = platform_get_resource(hisi_dwc3->pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "missing memory base resource\n");
+ return -EINVAL;
+ }
+
+ hisi_dwc3->otg_bc_reg_base = devm_ioremap_nocache(
+ dev, res->start, resource_size(res));
+ if (IS_ERR_OR_NULL(hisi_dwc3->otg_bc_reg_base)) {
+ dev_err(dev, "ioremap res 0 failed\n");
+ return -ENOMEM;
+ }
+
+ get_phy_param(hisi_dwc3);
+
+ /* get abb clk handler */
+ hisi_dwc3->clk = devm_clk_get(&hisi_dwc3->pdev->dev, "clk_usb3phy_ref");
+ if (IS_ERR_OR_NULL(hisi_dwc3->clk)) {
+ dev_err(dev, "get usb3phy ref clk failed\n");
+ return -EINVAL;
+ }
+
+ /* get h clk handler */
+ hisi_dwc3->gt_aclk_usb3otg = devm_clk_get(
+ &hisi_dwc3->pdev->dev, "aclk_usb3otg");
+ if (IS_ERR_OR_NULL(hisi_dwc3->gt_aclk_usb3otg)) {
+ dev_err(dev, "get aclk_usb3otg failed\n");
+ return -EINVAL;
+ }
+
+ /* judge fpga platform or not, from dts */
+ if (of_property_read_u32(dev->of_node, "fpga_flag",
+ &hisi_dwc3->fpga_flag)) {
+ hisi_dwc3->fpga_flag = 0;
+ }
+ usb_dbg("this is %s platform (fpga flag %d)\n",
+ hisi_dwc3->fpga_flag ? "fpga" : "asic", hisi_dwc3->fpga_flag);
+
+ hisi_dwc3->fpga_usb_mode_gpio = -1;
+
+ if (of_property_read_u32(dev->of_node, "bc_again_flag",
+ &hisi_dwc3->bc_again_flag)) {
+ hisi_dwc3->bc_again_flag = 0;
+ }
+
+ return 0;
+}
+
+static int hisi_dwc3_phy_init(struct hisi_dwc3_device *hisi_dwc)
+{
+ return hisi_dwc->phy_ops->init(hisi_dwc);
+}
+
+static int hisi_dwc3_phy_shutdown(struct hisi_dwc3_device *hisi_dwc)
+{
+ return hisi_dwc->phy_ops->shutdown(hisi_dwc);
+}
+
+int hisi_dwc3_probe(struct platform_device *pdev,
+ struct usb3_phy_ops *phy_ops)
+{
+ int ret;
+ struct hisi_dwc3_device *hisi_dwc;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = pdev->dev.of_node;
+ enum hisi_usb_state init_state;
+
+ usb_dbg("+\n");
+
+ if (!phy_ops) {
+ usb_err("phy_ops is NULL\n");
+ return -EINVAL;
+ }
+
+ hisi_dwc = devm_kzalloc(dev, sizeof(*hisi_dwc), GFP_KERNEL);
+ if (!hisi_dwc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, hisi_dwc);
+ hisi_dwc->pdev = pdev;
+ hisi_dwc->phy_ops = phy_ops;
+
+ hisi_dwc3_dev = hisi_dwc;
+
+ /*
+ * set hisi dwc3 dma mask, it should be 0xffffffff, because the ahb
+ * master of usb can only support 32bit width address.
+ */
+ if (!dev->dma_mask)
+ dev->dma_mask = &dev->coherent_dma_mask;
+ if (!dev->coherent_dma_mask)
+ dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ /*
+ * get resources from dts.
+ */
+ ret = get_resource(hisi_dwc);
+ if (ret) {
+ dev_err(&pdev->dev, "get resource failed!\n");
+ return ret;
+ }
+
+ if (hisi_dwc->fpga_usb_mode_gpio > 0) {
+ ret = gpio_request(hisi_dwc->fpga_usb_mode_gpio, NULL);
+ if (ret) {
+ /* request gpio failure! */
+ usb_err("request gpio %d failed, ret=[%d]\n",
+ hisi_dwc->fpga_usb_mode_gpio, ret);
+ }
+ }
+
+ /* create sysfs files. */
+ ret = create_attr_file(dev);
+ if (ret) {
+ dev_err(&pdev->dev, "create_attr_file failed!\n");
+ return ret;
+ }
+
+ /* initialize */
+ hisi_dwc->charger_type = CHARGER_TYPE_SDP;
+ hisi_dwc->fake_charger_type = CHARGER_TYPE_NONE;
+ hisi_dwc->event = NONE_EVENT;
+ hisi_dwc->host_flag = 0;
+ hisi_dwc->eventmask = 0;
+ spin_lock_init(&hisi_dwc->event_lock);
+ INIT_WORK(&hisi_dwc->event_work, event_work);
+ mutex_init(&hisi_dwc->lock);
+ wakeup_source_init(&hisi_dwc->ws, "usb_wake_lock");
+ ATOMIC_INIT_NOTIFIER_HEAD(&hisi_dwc->charger_type_notifier);
+ event_queue_creat(&hisi_dwc->event_queue, MAX_EVENT_COUNT);
+ hisi_dwc->disable_vdp_src = disable_vdp_src;
+ hisi_dwc->need_disable_vdp = 0;
+
+ /* power on */
+ hisi_dwc->is_regu_on = 0;
+ ret = hisi_dwc3_phy_init(hisi_dwc);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: hisi_dwc3_phy_init failed!\n",
+ __func__);
+ remove_attr_file(dev);
+ return ret;
+ }
+
+ if (hisi_dwc->bc_again_flag) {
+ INIT_DELAYED_WORK(&hisi_dwc->bc_again_work, bc_again_work);
+ hisi_dwc->conndone_nb.notifier_call = conndone_notifier_fn;
+ ret = dwc3_conndone_notifier_register(&hisi_dwc->conndone_nb);
+ if (ret)
+ usb_err("dwc3_conndone_notifier_register failed\n");
+ }
+
+ if (hisi_dwc->charger_type == CHARGER_TYPE_CDP) {
+ usb_dbg("it needs enable VDP_SRC while detect CDP!\n");
+ hisi_dwc->need_disable_vdp = 1;
+ enable_vdp_src(hisi_dwc);
+ }
+
+ /*
+ * enable runtime pm.
+ */
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+ pm_runtime_forbid(dev);
+
+ /*
+ * probe child deivces
+ */
+ ret = of_platform_populate(node, NULL, NULL, dev);
+ if (ret) {
+ pr_err("%s: register dwc3 failed!\n", __func__);
+ goto err1;
+ }
+
+#ifdef CONFIG_USB_DWC3_OTG
+ /* default device state */
+ hisi_dwc->state = USB_STATE_DEVICE;
+
+ if (sleep_allowed(hisi_dwc))
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ else
+ hisi_dwc3_wake_lock(hisi_dwc);
+
+ if (!enumerate_allowed(hisi_dwc)) {
+ /* stop peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler, DWC3_OTG_EVT_VBUS_CLEAR);
+ if (ret)
+ usb_err("stop peripheral error\n");
+ }
+
+ /* balance the put operation when disconnect */
+ pm_runtime_get(dev);
+
+ hisi_dwc->event = CHARGER_CONNECT_EVENT;
+ init_state = get_usb_state(hisi_dwc);
+ if (init_state == USB_STATE_OFF) {
+ usb_dbg("init state: OFF\n");
+ hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT);
+ } else if (init_state == USB_STATE_HOST) {
+ usb_dbg("init state: HOST\n");
+ hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT);
+ msleep(500);
+ hisi_usb_otg_event(ID_FALL_EVENT);
+ }
+#endif
+
+ pm_runtime_put_sync(dev);
+ pm_runtime_allow(dev);
+
+ usb_dbg("-\n");
+
+ return 0;
+
+err1:
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+ remove_attr_file(dev);
+
+ return ret;
+}
+
+static int hisi_dwc3_remove_child(struct device *dev, void *unused)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+ return 0;
+}
+
+int hisi_dwc3_remove(struct platform_device *pdev)
+{
+ struct hisi_dwc3_device *hisi_dwc3 = platform_get_drvdata(pdev);
+ int ret;
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return -EBUSY;
+ }
+
+ device_for_each_child(&pdev->dev, NULL, hisi_dwc3_remove_child);
+ pm_runtime_disable(&pdev->dev);
+
+ if (hisi_dwc3->bc_again_flag) {
+ dwc3_conndone_notifier_unregister(&hisi_dwc3->conndone_nb);
+ hisi_dwc3->conndone_nb.notifier_call = NULL;
+ }
+
+ ret = hisi_dwc3_phy_shutdown(hisi_dwc3);
+ if (ret)
+ usb_err("hisi_dwc3_phy_shutdown error\n");
+ hisi_dwc3->phy_ops = NULL;
+
+ event_queue_destroy(&hisi_dwc3->event_queue);
+
+ remove_attr_file(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
+static int hisi_dwc3_prepare(struct device *dev)
+{
+ struct hisi_dwc3_device *hisi_dwc = platform_get_drvdata(
+ to_platform_device(dev));
+ int ret = 0;
+
+ if (!hisi_dwc)
+ return -ENODEV;
+
+ mutex_lock(&hisi_dwc->lock);
+
+ switch (hisi_dwc->state) {
+ case USB_STATE_OFF:
+ pr_info("%s: off state.\n", __func__);
+ break;
+ case USB_STATE_DEVICE:
+ pr_info("%s: device state.\n", __func__);
+
+ if (enumerate_allowed(hisi_dwc)) {
+ /* stop peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_VBUS_CLEAR);
+ if (ret) {
+ usb_err("stop peripheral error\n");
+ goto error;
+ }
+ } else {
+ usb_dbg("connected is a real charger\n");
+ disable_vdp_src(hisi_dwc);
+ }
+
+ break;
+ case USB_STATE_HOST:
+ usb_err("%s: host mode, should not go to sleep!\n", __func__);
+ ret = -EFAULT;
+ goto error;
+ default:
+ pr_err("%s: ilegal state!\n", __func__);
+ ret = -EFAULT;
+ goto error;
+ }
+
+ return ret;
+error:
+ mutex_unlock(&hisi_dwc->lock);
+ return ret;
+}
+
+static void hisi_dwc3_complete(struct device *dev)
+{
+ struct hisi_dwc3_device *hisi_dwc = platform_get_drvdata(
+ to_platform_device(dev));
+ int ret = 0;
+
+ if (!hisi_dwc) {
+ usb_err("hisi_dwc NULL !\n");
+ return;
+ }
+
+ switch (hisi_dwc->state) {
+ case USB_STATE_OFF:
+ usb_dbg("%s: off state.\n", __func__);
+ break;
+ case USB_STATE_DEVICE:
+ usb_dbg("%s: device state.\n", __func__);
+
+ /* update charger type */
+ hisi_dwc->charger_type = detect_charger_type(hisi_dwc);
+ if (sleep_allowed(hisi_dwc))
+ hisi_dwc3_wake_unlock(hisi_dwc);
+ else
+ hisi_dwc3_wake_lock(hisi_dwc);
+
+ /* do not start peripheral if real charger connected */
+ if (enumerate_allowed(hisi_dwc)) {
+ /* start peripheral */
+ ret = dwc3_otg_work(dwc_otg_handler,
+ DWC3_OTG_EVT_VBUS_SET);
+ if (ret) {
+ usb_err("start peripheral error\n");
+ hisi_dwc->state = USB_STATE_OFF;
+ pm_runtime_put(&hisi_dwc->pdev->dev);
+ goto error;
+ }
+ } else {
+ usb_dbg("a real charger connected\n");
+ }
+
+ break;
+ case USB_STATE_HOST:
+ usb_err("%s: host mode, should not go to sleep!\n", __func__);
+ break;
+ default:
+ usb_err("%s: ilegal state!\n", __func__);
+ break;
+ }
+
+error:
+ mutex_unlock(&hisi_dwc->lock);
+}
+
+static int hisi_dwc3_suspend(struct device *dev)
+{
+ struct hisi_dwc3_device *hisi_dwc3 =
+ platform_get_drvdata(to_platform_device(dev));
+ int ret = 0;
+
+ usb_dbg("+\n");
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return -EBUSY;
+ }
+
+ if (hisi_dwc3->runtime_suspended) {
+ usb_dbg("runtime_suspended\n");
+ } else {
+ ret = hisi_dwc3_phy_shutdown(hisi_dwc3);
+ if (ret)
+ usb_err("hisi_dwc3_phy_shutdown failed\n");
+ }
+
+ usb_dbg("-\n");
+
+ return ret;
+}
+
+static int hisi_dwc3_resume(struct device *dev)
+{
+ struct hisi_dwc3_device *hisi_dwc3 =
+ platform_get_drvdata(to_platform_device(dev));
+ int ret = 0;
+
+ usb_dbg("+\n");
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return -EBUSY;
+ }
+
+ if (hisi_dwc3->runtime_suspended) {
+ usb_dbg("runtime_suspended\n");
+ } else {
+ ret = hisi_dwc3_phy_init(hisi_dwc3);
+ if (ret)
+ usb_err("hisi_dwc3_phy_init failed\n");
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
+ usb_dbg("-\n");
+
+ return ret;
+}
+#endif
+
+static int hisi_dwc3_runtime_suspend(struct device *dev)
+{
+ int ret;
+ struct hisi_dwc3_device *hisi_dwc3 =
+ platform_get_drvdata(to_platform_device(dev));
+
+ usb_dbg("+\n");
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return -EBUSY;
+ }
+
+ ret = hisi_dwc3_phy_shutdown(hisi_dwc3);
+ if (ret)
+ return ret;
+ hisi_dwc3->runtime_suspended = 1;
+ usb_dbg("-\n");
+
+ return 0;
+}
+
+static int hisi_dwc3_runtime_resume(struct device *dev)
+{
+ int ret = 0;
+ struct hisi_dwc3_device *hisi_dwc3 =
+ platform_get_drvdata(to_platform_device(dev));
+
+ usb_dbg("+\n");
+
+ if (!hisi_dwc3) {
+ usb_err("hisi_dwc3 NULL\n");
+ return -EBUSY;
+ }
+
+ ret = hisi_dwc3_phy_init(hisi_dwc3);
+ if (ret)
+ return ret;
+ hisi_dwc3->runtime_suspended = 0;
+ usb_dbg("-\n");
+
+ return ret;
+}
+
+static int hisi_dwc3_runtime_idle(struct device *dev)
+{
+ int ret;
+
+ usb_dbg("+\n");
+ ret = pm_runtime_autosuspend(dev);
+ if (ret)
+ dev_err(dev, "pm_runtime_autosuspend error\n");
+ usb_dbg("-\n");
+
+ return ret;
+}
+
+const struct dev_pm_ops hisi_dwc3_dev_pm_ops = {
+#ifdef CONFIG_PM_SLEEP
+ .prepare = hisi_dwc3_prepare,
+ .complete = hisi_dwc3_complete,
+ SET_SYSTEM_SLEEP_PM_OPS(hisi_dwc3_suspend, hisi_dwc3_resume)
+#endif
+ SET_RUNTIME_PM_OPS(hisi_dwc3_runtime_suspend, hisi_dwc3_runtime_resume,
+ hisi_dwc3_runtime_idle)
+};
+#endif
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>");
diff --git a/drivers/usb/dwc3/dwc3-hisi.h b/drivers/usb/dwc3/dwc3-hisi.h
new file mode 100644
index 000000000000..f497baff563a
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-hisi.h
@@ -0,0 +1,293 @@
+/*
+ * hisi_usb_vbus.h
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#ifndef _DWC3_HISI_H_
+#define _DWC3_HISI_H_
+
+#include <linux/pm_wakeup.h>
+#include <linux/clk.h>
+#include <linux/hisi/usb/hisi_usb.h>
+#include <linux/regulator/consumer.h>
+
+#define REG_BASE_PERI_CRG (0xFFF35000)
+#define PERI_CRG_CLK_EN4 (0x40)
+#define PERI_CRG_CLK_DIS4 (0x44)
+#define PERI_CRG_RSTDIS4 (0x94)
+#define PERI_CRG_RSTEN4 (0x90)
+#define PERI_CRG_ISODIS (0x148)
+#define PERI_CRG_ISOSTAT (0x14C)
+#define STCL_ADDR (0xFFF0A214)
+#ifndef BIT
+#define BIT(x) (1 << (x))
+#endif
+#define PERI_CRG_ISOSTAT_MODEMSUBSYSISOEN BIT(4)
+#define PERI_CRG_ISODIS_MODEMSUBSYSISOEN BIT(4)
+
+#define PCTRL_PERI_CTRL24 (0x64)
+#define PCTRL_PERI_CTRL48 (0xC54)
+
+#define IP_RST_USB3OTG_MUX BIT(8)
+#define IP_RST_USB3OTG_AHBIF BIT(7)
+#define IP_RST_USB3OTG_32K BIT(6)
+#define IP_RST_USB3OTG BIT(5)
+#define IP_RST_USB3OTGPHY_POR BIT(3)
+
+#define GT_CLK_USB3OTG_REF BIT(0)
+#define GT_ACLK_USB3OTG BIT(1)
+#define GT_CLK_USB3PHY_REF BIT(2)
+
+/*
+ * hisi dwc3 phy registers
+ */
+#define DWC3_PHY_RX_OVRD_IN_HI 0x1006
+#define DWC3_PHY_RX_SCOPE_VDCC 0x1026
+
+/* DWC3_PHY_RX_SCOPE_VDCC */
+#define RX_SCOPE_LFPS_EN BIT(0)
+
+/*
+ * hisi dwc3 otg bc registers
+ */
+#define USBOTG3_CTRL0 0x00
+#define USBOTG3_CTRL1 0x04
+#define USBOTG3_CTRL2 0x08
+#define USBOTG3_CTRL3 0x0C
+#define USBOTG3_CTRL4 0x10
+#define USBOTG3_CTRL5 0x14
+#define USBOTG3_CTRL6 0x18
+#define USBOTG3_CTRL7 0x1C
+#define USBOTG3_STS0 0x20
+#define USBOTG3_STS1 0x24
+#define USBOTG3_STS2 0x28
+#define USBOTG3_STS3 0x2C
+#define BC_CTRL0 0x30
+#define BC_CTRL1 0x34
+#define BC_CTRL2 0x38
+#define BC_STS0 0x3C
+#define RAM_CTRL 0x40
+#define USBOTG3_STS4 0x44
+#define USB3PHY_CTRL 0x48
+#define USB3PHY_STS 0x4C
+#define USB3PHY_CR_STS 0x50
+#define USB3PHY_CR_CTRL 0x54
+#define USB3_RES 0x58
+
+/* USTOTG3_CTRL0 */
+# define USBOTG3CTRL0_SESSVLD_SEL BIT(14)
+# define USBOTG3CTRL0_SC_SESSVLD BIT(13)
+# define USBOTG3CTRL0_POWERPRESENT_SEL BIT(12)
+# define USBOTG3CTRL0_SC_POWERPRESENT BIT(11)
+# define USBOTG3CTRL0_BVALID_SEL BIT(10)
+# define USBOTG3CTRL0_SC_BVALID BIT(9)
+# define USBOTG3CTRL0_AVALID_SEL BIT(8)
+# define USBOTG3CTRL0_SC_AVALID BIT(7)
+# define USBOTG3CTRL0_VBUSVALID_SEL BIT(6)
+# define USBOTG3CTRL0_DRVVBUS BIT(5)
+# define USBOTG3CTRL0_DRVVBUS_SEL BIT(4)
+# define USBOTG3CTRL0_IDDIG BIT(3)
+# define USBOTG3CTRL0_IDDIG_SEL BIT(2)
+# define USBOTG3CTRL0_IDPULLUP BIT(1)
+# define USBOTG3CTRL0_IDPULLUP_SEL BIT(0)
+
+/* USTOTG3_CTRL2 */
+# define USBOTG3CTRL2_POWERDOWN_HSP BIT(0)
+# define USBOTG3CTRL2_POWERDOWN_SSP BIT(1)
+
+/* USBOTG3_CTRL3 */
+# define USBOTG3_CTRL3_VBUSVLDEXT BIT(6)
+# define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5)
+# define USBOTG3_CTRL3_TXBITSTUFFEHN BIT(4)
+# define USBOTG3_CTRL3_TXBITSTUFFEN BIT(3)
+# define USBOTG3_CTRL3_RETENABLEN BIT(2)
+# define USBOTG3_CTRL3_OTGDISABLE BIT(1)
+# define USBOTG3_CTRL3_COMMONONN BIT(0)
+
+/* USBOTG3_CTRL4 */
+# define USBOTG3_CTRL4_TXVREFTUNE(x) (((x) << 22) & (0xf << 22))
+# define USBOTG3_CTRL4_TXRISETUNE(x) (((x) << 20) & (3 << 20))
+# define USBOTG3_CTRL4_TXRESTUNE(x) (((x) << 18) & (3 << 18))
+# define USBOTG3_CTRL4_TXPREEMPPULSETUNE BIT(17)
+# define USBOTG3_CTRL4_TXPREEMPAMPTUNE(x) (((x) << 15) & (3 << 15))
+# define USBOTG3_CTRL4_TXHSXVTUNE(x) (((x) << 13) & (3 << 13))
+# define USBOTG3_CTRL4_TXFSLSTUNE(x) (((x) << 9) & (0xf << 9))
+# define USBOTG3_CTRL4_SQRXTUNE(x) (((x) << 6) & (7 << 6))
+# define USBOTG3_CTRL4_OTGTUNE_MASK (7 << 3)
+# define USBOTG3_CTRL4_OTGTUNE(x) \
+(((x) << 3) & USBOTG3_CTRL4_OTGTUNE_MASK)
+# define USBOTG3_CTRL4_COMPDISTUNE_MASK 7
+# define USBOTG3_CTRL4_COMPDISTUNE(x) \
+((x) & USBOTG3_CTRL4_COMPDISTUNE_MASK)
+
+# define USBOTG3_CTRL7_REF_SSP_EN BIT(16)
+
+/* USBOTG3_CTRL6 */
+#define TX_VBOOST_LVL_MASK 7
+#define TX_VBOOST_LVL(x) ((x) & TX_VBOOST_LVL_MASK)
+
+/* BC_CTRL0 */
+# define BC_CTRL0_BC_IDPULLUP BIT(10)
+# define BC_CTRL0_BC_SUSPEND_N BIT(9)
+# define BC_CTRL0_BC_DMPULLDOWN BIT(8)
+# define BC_CTRL0_BC_DPPULLDOWN BIT(7)
+# define BC_CTRL0_BC_TXVALIDH BIT(6)
+# define BC_CTRL0_BC_TXVALID BIT(5)
+# define BC_CTRL0_BC_TERMSELECT BIT(4)
+# define BC_CTRL0_BC_XCVRSELECT(x) (((x) << 2) & (3 << 2))
+# define BC_CTRL0_BC_OPMODE(x) ((x) & 3)
+
+/* BC_CTRL1 */
+# define BC_CTRL1_BC_MODE 1
+
+/* BC_CTRL2 */
+# define BC_CTRL2_BC_PHY_VDATDETENB BIT(4)
+# define BC_CTRL2_BC_PHY_VDATARCENB BIT(3)
+# define BC_CTRL2_BC_PHY_CHRGSEL BIT(2)
+# define BC_CTRL2_BC_PHY_DCDENB BIT(1)
+# define BC_CTRL2_BC_PHY_ACAENB BIT(0)
+
+/* BC_STS0 */
+# define BC_STS0_BC_LINESTATE(x) (((x) << 9) & (3 << 9))
+# define BC_STS0_BC_PHY_CHGDET BIT(8)
+# define BC_STS0_BC_PHY_FSVMINUS BIT(7)
+# define BC_STS0_BC_PHY_FSVPLUS BIT(6)
+# define BC_STS0_BC_RID_GND BIT(5)
+# define BC_STS0_BC_RID_FLOAT BIT(4)
+# define BC_STS0_BC_RID_C BIT(3)
+# define BC_STS0_BC_RID_B BIT(2)
+# define BC_STS0_BC_RID_A BIT(1)
+# define BC_STS0_BC_SESSVLD BIT(0)
+
+/* USB3PHY_CR_STS */
+#define USB3OTG_PHY_CR_DATA_OUT(x) (((x) >> 1) & 0xffff)
+#define USB3OTG_PHY_CR_ACK BIT(0)
+
+/* USB3PHY_CR_CTRL */
+#define USB3OTG_PHY_CR_DATA_IN(x) (((x) << 4) & (0xffff << 4))
+#define USB3OTG_PHY_CR_WRITE BIT(3)
+#define USB3OTG_PHY_CR_READ BIT(2)
+#define USB3OTG_PHY_CR_CAP_DATA BIT(1)
+#define USB3OTG_PHY_CR_CAP_ADDR BIT(0)
+
+#define usb_dbg(format, arg...) \
+ pr_err("[USB3][%s]"format, __func__, ##arg)
+
+#define usb_err(format, arg...) \
+ pr_err("[USB3][%s]"format, __func__, ##arg)
+
+enum hisi_usb_state {
+ USB_STATE_UNKNOWN = 0,
+ USB_STATE_OFF,
+ USB_STATE_DEVICE,
+ USB_STATE_HOST,
+};
+
+struct hiusb_event_queue {
+ enum otg_dev_event_type *event;
+ unsigned int num_event;
+ unsigned int max_event;
+ unsigned int enpos, depos;
+ unsigned int overlay, overlay_index;
+};
+
+#define MAX_EVENT_COUNT 16
+#define EVENT_QUEUE_UNIT MAX_EVENT_COUNT
+
+struct hisi_dwc3_device {
+ struct platform_device *pdev;
+
+ void __iomem *otg_bc_reg_base;
+ void __iomem *pericfg_reg_base;
+ void __iomem *pctrl_reg_base;
+ void __iomem *sctrl_reg_base;
+
+ struct regulator *usb_regu;
+ unsigned int is_regu_on;
+ unsigned int runtime_suspended;
+
+ enum hisi_usb_state state;
+ enum hisi_charger_type charger_type;
+ enum hisi_charger_type fake_charger_type;
+
+ enum otg_dev_event_type event;
+ spinlock_t event_lock;
+
+ struct mutex lock;
+ struct wakeup_source ws;
+ struct atomic_notifier_head charger_type_notifier;
+ struct work_struct event_work;
+
+ u32 eye_diagram_param; /* this param will be set to USBOTG3_CTRL4 */
+ u32 eye_diagram_host_param;
+ u32 usb3_phy_cr_param;
+ u32 usb3_phy_host_cr_param;
+ u32 usb3_phy_tx_vboost_lvl;
+ unsigned int host_flag;
+
+ u32 fpga_flag;
+ int fpga_usb_mode_gpio;
+
+ struct clk *clk;
+ struct clk *gt_aclk_usb3otg;
+
+ int eventmask;
+
+ /* for bc again */
+ u32 bc_again_flag;
+ struct delayed_work bc_again_work;
+ struct notifier_block conndone_nb;
+
+ /* event queue for handle event */
+ struct hiusb_event_queue event_queue;
+
+ struct usb3_phy_ops *phy_ops;
+
+ unsigned int need_disable_vdp;
+ void (*disable_vdp_src)(struct hisi_dwc3_device *hisi_dwc3);
+};
+
+#ifdef CONFIG_PM
+extern const struct dev_pm_ops hisi_dwc3_dev_pm_ops;
+#define HISI_DWC3_PM_OPS (&hisi_dwc3_dev_pm_ops)
+#else
+#define HISI_DWC3_PM_OPS NULL
+#endif
+
+struct usb3_phy_ops {
+ struct regulator *subsys_regu;
+
+ int (*init)(struct hisi_dwc3_device *hisi_dwc3);
+ int (*shutdown)(struct hisi_dwc3_device *hisi_dwc3);
+};
+
+typedef ssize_t (*hiusb_debug_show_ops)(void *, char *, ssize_t);
+typedef ssize_t (*hiusb_debug_store_ops)(void *, const char *, ssize_t);
+void hiusb_debug_init(void *data);
+void hiusb_debug_quick_register(void *dev_data,
+ hiusb_debug_show_ops show,
+ hiusb_debug_store_ops store);
+
+void set_hisi_dwc3_power_flag(int val);
+void config_femtophy_param(struct hisi_dwc3_device *hisi_dwc);
+int hisi_dwc3_probe(struct platform_device *pdev, struct usb3_phy_ops *phy_ops);
+int hisi_dwc3_remove(struct platform_device *pdev);
+#endif /* _DWC3_HISI_H_ */
diff --git a/drivers/usb/dwc3/dwc3-otg.c b/drivers/usb/dwc3/dwc3-otg.c
new file mode 100644
index 000000000000..fd3ef7d154ed
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-otg.c
@@ -0,0 +1,360 @@
+/*
+ * dwc3-otg.c
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include "core.h"
+#include "io.h"
+#include "dwc3-otg.h"
+
+#define DBG(format, arg...) pr_info("[%s]" format, __func__, ##arg)
+
+struct dwc3_otg *dwc_otg_handler;
+
+static void dump_otg_regs(struct dwc3 *dwc)
+{
+#define DUMP_REG(__reg) pr_info("%s:\t0x%x\n", \
+ #__reg, dwc3_readl(dwc->regs, __reg))
+ DUMP_REG(DWC3_OCFG);
+ DUMP_REG(DWC3_OCTL);
+ DUMP_REG(DWC3_OEVT);
+ DUMP_REG(DWC3_OEVTEN);
+ DUMP_REG(DWC3_OSTS);
+
+ DUMP_REG(DWC3_BCFG);
+ DUMP_REG(DWC3_BCEVT);
+ DUMP_REG(DWC3_BCEVTEN);
+}
+
+#ifndef DWC3_OTG_FORCE_MODE
+static void dwc3_disable_otg_event(struct dwc3 *dwc)
+{
+ dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0);
+ dwc3_writel(dwc->regs, DWC3_OEVTEN, 0);
+}
+
+static void dwc3_enable_otg_event(struct dwc3 *dwc)
+{
+ dwc3_writel(dwc, DWC3_OEVTEN, 0);
+ dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0);
+ dwc3_writel(dwc->regs, DWC3_OEVTEN, DWC3_OEVT_OTGBDEVVBUSCHNGEVNT |
+ DWC3_OEVT_OTGCONIDSTSCHNGEVNT);
+}
+#endif
+
+int dwc3_otg_resume(struct dwc3 *dwc)
+{
+ DBG("+\n");
+#ifndef DWC3_OTG_FORCE_MODE
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (reg & DWC3_OSTS_CONIDSTS) {
+ DBG("%s: ID is 1, set peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg |= DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ } else {
+ DBG("%s: ID is 0, clear peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg &= ~DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ }
+#endif
+
+ DBG("-\n");
+
+ return 0;
+}
+
+int dwc3_otg_suspend(struct dwc3 *dwc)
+{
+ DBG("+\n");
+ DBG("-\n");
+ return 0;
+}
+
+static int dwc3_otg_start_host(struct dwc3_otg *dwc_otg)
+{
+ struct dwc3 *dwc = dwc_otg->dwc;
+ unsigned long flags;
+ int ret;
+ u32 reg;
+
+ DBG("+\n");
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+#ifdef DWC3_OTG_FORCE_MODE
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ pr_debug("%s: GCTL value 0x%x\n", __func__, reg);
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
+#else
+ /* check ID ststus */
+ DBG("+before read DWC3_OSTS\n");
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (reg & DWC3_OSTS_CONIDSTS) {
+ pr_warn("%s: CONIDSTS wrong!\n");
+ dump_otg_regs(dwc);
+ }
+ DBG("+before read DWC3_OCFG\n");
+ reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+ reg |= DWC3_OCFG_OTGSFTRSTMSK;
+ reg |= DWC3_OCFG_DISPRTPWRCUTOFF;
+ reg &= ~(DWC3_OCFG_HNPCAP | DWC3_OCFG_SRPCAP);
+ dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+ DBG("set OCFG 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCFG));
+
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg &= ~DWC3_OCTL_PERIMODE;
+ reg |= DWC3_OCTL_PRTPWRCTL;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+
+ DBG("set OCTL 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCTL));
+#endif
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ ret = platform_device_add(dwc->xhci);
+ if (ret) {
+ pr_err("%s: failed to register xHCI device\n", __func__);
+ return ret;
+ }
+
+#ifdef CONFIG_HISI_USB_DWC3_MASK_IRQ_WORKAROUND
+ if (dwc->irq_state == 0) {
+ enable_irq(dwc->irq);
+ dwc->irq_state = 1;
+ pr_info("[%s]enable irq\n", __func__);
+ }
+#endif
+
+ DBG("-\n");
+
+ return ret;
+}
+
+static void dwc3_otg_stop_host(struct dwc3_otg *dwc_otg)
+{
+ DBG("+\n");
+ platform_device_del(dwc_otg->dwc->xhci);
+ DBG("-\n");
+}
+
+static int dwc3_otg_start_peripheral(struct dwc3_otg *dwc_otg)
+{
+ int ret;
+ unsigned long flags;
+ struct dwc3 *dwc = dwc_otg->dwc;
+ u32 reg;
+
+ DBG("+\n");
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+#ifdef DWC3_OTG_FORCE_MODE
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ pr_debug("%s: GCTL value 0x%x\n", __func__, reg);
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+#else
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (!(reg & DWC3_OSTS_CONIDSTS) || !(reg & DWC3_OSTS_BSESVLD)) {
+ pr_warn("%s: CONIDSTS or BSESVLD wrong!\n");
+ dump_otg_regs(dwc);
+ }
+
+ /* set mode as peripheral */
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg |= DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+#endif
+
+ ret = dwc3_gadget_resume(dwc);
+ if (ret)
+ pr_err("[%s] gadget resume error!", __func__);
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ DBG("-\n");
+
+ return ret;
+}
+
+static int dwc3_otg_stop_peripheral(struct dwc3_otg *dwc_otg)
+{
+ int ret;
+ unsigned long flags;
+ struct dwc3 *dwc = dwc_otg->dwc;
+
+ DBG("+\n");
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ ret = dwc3_gadget_suspend(dwc);
+ if (ret)
+ pr_err("[%s] gadget suspend error!", __func__);
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ DBG("-\n");
+
+ return ret;
+}
+
+int dwc3_otg_id_value(struct dwc3_otg *dwc_otg)
+{
+ if (dwc_otg)
+ return !!(dwc3_readl(dwc_otg->dwc->regs, DWC3_OSTS)
+ & DWC3_OSTS_CONIDSTS);
+ else
+ return 1;
+}
+
+int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt)
+{
+ int ret = 0;
+
+ DBG("+\n evt = %d", evt);
+
+ /* if otg is not enabled, do nothing */
+ if (!dwc_otg) {
+ pr_info("%s: dwc3 is not otg mode!\n", __func__);
+ return 0;
+ }
+
+ switch (evt) {
+ case DWC3_OTG_EVT_ID_SET:
+ dwc3_otg_stop_host(dwc_otg);
+ dwc3_suspend_device(dwc_otg->dwc);
+ break;
+ case DWC3_OTG_EVT_ID_CLEAR:
+ ret = dwc3_resume_device(dwc_otg->dwc);
+ if (ret) {
+ pr_err("%s: resume device failed!\n", __func__);
+ return ret;
+ }
+ ret = dwc3_otg_start_host(dwc_otg);
+ if (ret) {
+ pr_err("%s: start host failed!\n", __func__);
+ dwc3_suspend_device(dwc_otg->dwc);
+ return ret;
+ }
+ break;
+ case DWC3_OTG_EVT_VBUS_SET:
+ ret = dwc3_resume_device(dwc_otg->dwc);
+ if (ret) {
+ pr_err("%s: resume device failed!\n", __func__);
+ return ret;
+ }
+ ret = dwc3_otg_start_peripheral(dwc_otg);
+ if (ret) {
+ pr_err("%s: start peripheral failed!\n", __func__);
+ dwc3_suspend_device(dwc_otg->dwc);
+ return ret;
+ }
+ break;
+ case DWC3_OTG_EVT_VBUS_CLEAR:
+ ret = dwc3_otg_stop_peripheral(dwc_otg);
+ dwc3_suspend_device(dwc_otg->dwc);
+ break;
+ default:
+ break;
+ }
+ DBG("-\n");
+
+ return ret;
+}
+
+static void dwc3_otg_work_fun(struct work_struct *w)
+{
+ struct dwc3_otg *dwc_otg = container_of(
+ w, struct dwc3_otg, otg_work.work);
+
+ mutex_lock(&dwc_otg->lock);
+ if (dwc3_otg_work(dwc_otg, atomic_read(&dwc_otg->otg_evt_flag)))
+ pr_err("%s: dwc3_otg_work failed\n", __func__);
+ mutex_unlock(&dwc_otg->lock);
+}
+
+int dwc3_otg_init(struct dwc3 *dwc)
+{
+ struct dwc3_otg *dwc_otg;
+ u32 reg;
+
+ DBG("+\n");
+
+ dwc_otg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL);
+ if (!dwc_otg)
+ return -ENOMEM;
+
+ dwc_otg->dwc = dwc;
+ dwc->dwc_otg = dwc_otg;
+
+ mutex_init(&dwc_otg->lock);
+ INIT_DELAYED_WORK(&dwc_otg->otg_work, dwc3_otg_work_fun);
+
+ dwc_otg_handler = dwc_otg;
+
+#ifdef DWC3_OTG_FORCE_MODE
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ pr_debug("%s: GCTL value 0x%x\n", __func__, reg);
+
+ /* default device mode */
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+#else
+ /* disable hnp and srp */
+ reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+ reg &= ~(DWC3_OCFG_HNPCAP | DWC3_OCFG_SRPCAP);
+ dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (reg & DWC3_OSTS_CONIDSTS) {
+ DBG("%s: ID is 1, set peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg |= DWC3_OCTL_PERIMODE;
+ reg &= ~(DWC3_OCTL_HNPREQ | DWC3_OCTL_DEVSETHNPEN |
+ DWC3_OCTL_HSTSETHNPEN);
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ } else {
+ DBG("%s: ID is 0, clear peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg &= ~DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ }
+#endif
+
+ dump_otg_regs(dwc);
+
+ DBG("-\n");
+
+ return 0;
+}
+
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+ DBG("+\n");
+ dwc_otg_handler = NULL;
+ dwc->dwc_otg->dwc = NULL;
+ dwc->dwc_otg = NULL;
+ DBG("-\n");
+}
diff --git a/drivers/usb/dwc3/dwc3-otg.h b/drivers/usb/dwc3/dwc3-otg.h
new file mode 100644
index 000000000000..b9114b16f050
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-otg.h
@@ -0,0 +1,133 @@
+/*
+ * dwc3-otg.h
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#ifndef __DRIVERS_USB_DWC3_OTG_H
+#define __DRIVERS_USB_DWC3_OTG_H
+
+/* BC Registers */
+#define DWC3_BCFG 0xcc30
+#define DWC3_BCEVT 0xcc38
+#define DWC3_BCEVTEN 0xcc3c
+#ifndef BIT
+#define BIT(x) (1 << (x))
+#endif
+/* OTG Configuration Register */
+#define DWC3_OCFG_DISPRTPWRCUTOFF BIT(5)
+#define DWC3_OCFG_OTGHIBDISMASK BIT(4)
+#define DWC3_OCFG_OTGSFTRSTMSK BIT(3)
+#define DWC3_OCFG_HNPCAP BIT(1)
+#define DWC3_OCFG_SRPCAP 1
+
+/* OTG Control Register */
+#define DWC3_OCTL_OTG3_GOERR BIT(7)
+#define DWC3_OCTL_PERIMODE BIT(6)
+#define DWC3_OCTL_PRTPWRCTL BIT(5)
+#define DWC3_OCTL_HNPREQ BIT(4)
+#define DWC3_OCTL_SESREQ BIT(3)
+#define DWC3_OCTL_TERMSELDLPULSE BIT(2)
+#define DWC3_OCTL_DEVSETHNPEN BIT(1)
+#define DWC3_OCTL_HSTSETHNPEN BIT(0)
+
+/* OTG Events Register */
+#define DWC3_OEVT_DEVICEMOD BIT(31)
+#define DWC3_OEVT_OTGXHCIRUNSTPSETEVNT BIT(27)
+#define DWC3_OEVT_OTGDEVRUNSTPSETEVNT BIT(26)
+#define DWC3_OEVT_OTGHIBENTRYEVNT BIT(25)
+#define DWC3_OEVT_OTGCONIDSTSCHNGEVNT BIT(24)
+#define DWC3_OEVT_HRRCONFNOTIFEVNT BIT(23)
+#define DWC3_OEVT_HRRINITNOTIFEVNT BIT(22)
+#define DWC3_OEVT_OTGADEVIDLEEVNT BIT(21)
+#define DWC3_OEVT_OTGADEVBHOSTENDEVNT BIT(20)
+#define DWC3_OEVT_OTGADEVHOSTEVNT BIT(19)
+#define DWC3_OEVT_OTGADEVHNPCHNGEVNT BIT(18)
+#define DWC3_OEVT_OTGADEVSRPDETEVNT BIT(17)
+#define DWC3_OEVT_OTGADEVSESSENDDETEVNT BIT(16)
+#define DWC3_OEVT_OTGBDEVBHOSTENDEVNT BIT(11)
+#define DWC3_OEVT_OTGBDEVHNPCHNGEVNT BIT(10)
+#define DWC3_OEVT_OTGBDEVSESSVLDDETEVNT BIT(9)
+#define DWC3_OEVT_OTGBDEVVBUSCHNGEVNT BIT(8)
+
+/* OTG Status Register */
+#define DWC3_OSTS_OTGSTATE_MSK (0xf << 8)
+#define DWC3_OSTS_PERIPHERALSTATE BIT(4)
+#define DWC3_OSTS_XHCIPRTPOWER BIT(3)
+#define DWC3_OSTS_BSESVLD BIT(2)
+#define DWC3_OSTS_ASESVLD BIT(1)
+#define DWC3_OSTS_CONIDSTS BIT(0)
+
+struct dwc3_otg {
+ struct usb_otg otg;
+ struct dwc3 *dwc;
+ int otg_irq;
+ struct delayed_work otg_work;
+
+ atomic_t otg_evt_flag;
+#define DWC3_OTG_EVT_ID_SET 1
+#define DWC3_OTG_EVT_ID_CLEAR 2
+#define DWC3_OTG_EVT_VBUS_SET 3
+#define DWC3_OTG_EVT_VBUS_CLEAR 4
+
+ struct mutex lock;
+};
+
+#ifdef CONFIG_USB_DWC3_OTG
+extern struct dwc3_otg *dwc_otg_handler;
+int dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt);
+int dwc3_otg_resume(struct dwc3 *dwc);
+int dwc3_otg_suspend(struct dwc3 *dwc);
+int dwc3_otg_id_value(struct dwc3_otg *dwc_otg);
+#else
+#define dwc_otg_handler ((struct dwc3_otg *)NULL)
+static inline int dwc3_otg_init(struct dwc3 *dwc)
+{
+ return 0;
+}
+
+static inline void dwc3_otg_exit(struct dwc3 *dwc)
+{
+}
+
+static inline int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt)
+{
+ return 0;
+}
+
+static inline int dwc3_otg_resume(struct dwc3 *dwc)
+{
+ return 0;
+}
+
+static inline int dwc3_otg_suspend(struct dwc3 *dwc)
+{
+ return 0;
+}
+
+static inline int dwc3_otg_id_value(struct dwc3_otg *dwc_otg)
+{
+ return 0;
+};
+#endif
+
+#endif /* __DRIVERS_USB_DWC3_OTG_H */
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index 75e6cb044eb2..e2c8d2ebfb64 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -98,11 +98,19 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
struct dwc3_request *req)
{
struct dwc3 *dwc = dep->dwc;
+ int ret;

req->request.actual = 0;
req->request.status = -EINPROGRESS;
req->epnum = dep->number;

+ /* we share one TRB for ep0/1 */
+ if (!list_empty(&dep->pending_list)) {
+ dev_WARN(dwc->dev, "ep0 busy!\n");
+ ret = -EBUSY;
+ return ret;
+ }
+
list_add_tail(&req->list, &dep->pending_list);

/*
@@ -190,8 +198,18 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
__dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req);

dep->flags &= ~DWC3_EP0_DIR_IN;
+
+ return 0;
}

+ /* mark the status phase already queued */
+ if (dwc->ep0_next_event == DWC3_EP0_NRDY_STATUS)
+ dwc->status_queued = true;
+
+ if (req->request.length != 0)
+ dev_WARN(dwc->dev, "status phase len %d\n",
+ req->request.length);
+
return 0;
}

@@ -241,6 +259,7 @@ static void dwc3_ep0_stall_and_restart(struct dwc3 *dwc)
__dwc3_gadget_ep_set_halt(dep, 1, false);
dep->flags = DWC3_EP_ENABLED;
dwc->delayed_status = false;
+ dwc->status_queued = false;

if (!list_empty(&dep->pending_list)) {
struct dwc3_request *req;
@@ -329,6 +348,12 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc,
if (value != 0)
return -EINVAL;

+ if (!(ctrl->bRequestType & USB_DIR_IN))
+ return -EINVAL;
+
+ if (!le16_to_cpu(ctrl->wLength))
+ return -EINVAL;
+
recip = ctrl->bRequestType & USB_RECIP_MASK;
switch (recip) {
case USB_RECIP_DEVICE:
@@ -714,6 +739,12 @@ static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
u16 wLength;
u16 wValue;

+ if (unlikely(ctrl->bRequestType & USB_DIR_IN))
+ return -EINVAL;
+
+ if (unlikely(!le16_to_cpu(ctrl->wLength)))
+ return -EINVAL;
+
if (state == USB_STATE_DEFAULT)
return -EINVAL;

@@ -830,9 +861,25 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
if (ret == USB_GADGET_DELAYED_STATUS)
dwc->delayed_status = true;

+ if (dwc->status_queued) {
+ dwc->status_queued = false;
+ if (dwc->delayed_status) {
+ pr_info("delayed status already come, will not wait for it.\n");
+ dwc->delayed_status = false;
+ usb_gadget_set_state(&dwc->gadget,
+ USB_STATE_CONFIGURED);
+ }
+ }
+
out:
- if (ret < 0)
+ if (ret < 0) {
+ dev_err(dwc->dev, "ep0 setup error, ret %d!\n", ret);
+ dev_err(dwc->dev, "ctrl: %02x %02x %04x %04x %04x\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ ctrl->wValue, ctrl->wIndex, ctrl->wLength);
dwc3_ep0_stall_and_restart(dwc);
+ }
+
}

static void dwc3_ep0_complete_data(struct dwc3 *dwc,
@@ -858,8 +905,10 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
trace_dwc3_complete_trb(ep0, trb);

r = next_request(&ep0->pending_list);
- if (!r)
+ if (!r) {
+ dev_err(dwc->dev, "ep0 request list empty while complete data\n");
return;
+ }

status = DWC3_TRB_SIZE_TRBSTS(trb->size);
if (status == DWC3_TRBSTS_SETUP_PENDING) {
@@ -1135,6 +1184,8 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
return;
}

+ dwc->status_queued = false;
+
dwc3_ep0_do_control_status(dwc, event);
}
}
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index f064f1549333..069c6eb1cc5c 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -34,6 +34,7 @@
#include "core.h"
#include "gadget.h"
#include "io.h"
+#include "dwc3-hisi.h"

/**
* dwc3_gadget_set_test_mode - enables usb2 test modes
@@ -267,7 +268,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
{
const struct usb_endpoint_descriptor *desc = dep->endpoint.desc;
struct dwc3 *dwc = dep->dwc;
- u32 timeout = 500;
+ u32 timeout = 3000;
u32 reg;

int cmd_status = 0;
@@ -1476,6 +1477,9 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,

out1:
/* giveback the request */
+ if (!dep->queued_requests)
+ goto out0;
+
dep->queued_requests--;
dwc3_gadget_giveback(dep, req, -ECONNRESET);

@@ -2710,6 +2714,18 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
}

+ATOMIC_NOTIFIER_HEAD(conndone_nh);
+
+int dwc3_conndone_notifier_register(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&conndone_nh, nb);
+}
+
+int dwc3_conndone_notifier_unregister(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_unregister(&conndone_nh, nb);
+}
+
static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
{
struct dwc3_ep *dep;
@@ -3236,7 +3252,9 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc->gadget.speed = USB_SPEED_UNKNOWN;
dwc->gadget.sg_supported = true;
dwc->gadget.name = "dwc3-gadget";
+#ifndef CONFIG_USB_DWC3_HISI
dwc->gadget.is_otg = dwc->dr_mode == USB_DR_MODE_OTG;
+#endif

/*
* FIXME We might be setting max_speed to <SUPER, however versions
diff --git a/drivers/usb/dwc3/hisi_hikey_gpio.c b/drivers/usb/dwc3/hisi_hikey_gpio.c
new file mode 100644
index 000000000000..ae05bbf9dd4a
--- /dev/null
+++ b/drivers/usb/dwc3/hisi_hikey_gpio.c
@@ -0,0 +1,300 @@
+/*
+ * otgid_gpio_hub.c
+ *
+ * Copyright (c) Hisilicon Tech. Co., Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/timer.h>
+#include <linux/param.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/hisi/log/hisi_log.h>
+#include <linux/hisi/usb/hisi_usb.h>
+#include <linux/tifm.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/hisi/usb/hisi_hikey_gpio.h>
+#define DEVICE_DRIVER_NAME "gpio_hub_for_usb5734"
+
+#define GPIO_HUB_OTG_HOST 1
+#define GPIO_HUB_OTG_DEVICE 0
+#define GPIO_TYPEC_VBUS_POWER 1
+#define GPIO_TYPEC_NO_POWER 0
+#define GPIO_HUB_VBUS_POWER 1
+#define GPIO_HUB_VBUS_NO_POWER 0
+#define GPIO_HUB_HUB_VBUS_POWER 1
+
+/* SOC_CRGPERIPH_PEREN1_UNION */
+#define SOC_CRGPERIPH_PEREN1_ADDR(base) ((base) + (0x010))
+
+#define HISILOG_TAG GPIO_HUB
+HISILOG_REGIST();
+
+struct gpio_hub_info {
+ struct platform_device *pdev;
+ int otg_switch_gpio;
+ int typec_vbus_gpio;
+ int typec_vbus_enable_val;
+ int hub_vbus_gpio;
+};
+
+static struct gpio_hub_info gpio_hub_driver_info = {
+ .otg_switch_gpio = -1,
+ .typec_vbus_gpio = -1,
+ .typec_vbus_enable_val = -1,
+ .hub_vbus_gpio = -1,
+};
+
+void gpio_hub_power_off(void)
+{
+ if (gpio_is_valid(gpio_hub_driver_info.hub_vbus_gpio)) {
+ gpio_set_value(gpio_hub_driver_info.hub_vbus_gpio,
+ GPIO_HUB_VBUS_NO_POWER);
+ hisilog_info("%s: gpio hub hub vbus no power set success",
+ __func__);
+ } else {
+ hisilog_err("%s: gpio hub hub vbus no power set err",
+ __func__);
+ }
+}
+
+void gpio_hub_power_on(void)
+{
+ if (gpio_is_valid(gpio_hub_driver_info.hub_vbus_gpio))
+ gpio_set_value(gpio_hub_driver_info.hub_vbus_gpio,
+ GPIO_HUB_VBUS_POWER);
+ else
+ hisilog_err("%s: gpio hub hub vbus set err", __func__);
+}
+
+void gpio_hub_switch_to_hub(void)
+{
+ int gpio = gpio_hub_driver_info.otg_switch_gpio;
+
+ if (!gpio_is_valid(gpio)) {
+ hisilog_err("%s: otg_switch_gpio is err\n", __func__);
+ return;
+ }
+
+ if (gpio_get_value(gpio)) {
+ hisilog_info("%s: already switch to hub\n", __func__);
+ return;
+ }
+
+ gpio_direction_output(gpio, 1);
+ hisilog_err("%s: switch to hub\n", __func__);
+}
+EXPORT_SYMBOL_GPL(gpio_hub_switch_to_hub);
+
+void gpio_hub_switch_to_typec(void)
+{
+ int gpio = gpio_hub_driver_info.otg_switch_gpio;
+
+ if (!gpio_is_valid(gpio)) {
+ hisilog_err("%s: otg_switch_gpio is err\n", __func__);
+ return;
+ }
+
+ if (!gpio_get_value(gpio)) {
+ hisilog_info("%s: already switch to typec\n", __func__);
+ return;
+ }
+
+ gpio_direction_output(gpio, 0);
+ hisilog_err("%s: switch to typec\n", __func__);
+}
+EXPORT_SYMBOL_GPL(gpio_hub_switch_to_typec);
+
+static void gpio_hub_change_typec_power(int gpio, int on)
+{
+ if (!gpio_is_valid(gpio)) {
+ hisilog_err("%s: typec power gpio is err\n", __func__);
+ return;
+ }
+
+ if (gpio_get_value(gpio) == on) {
+ hisilog_info("%s: typec power no change\n", __func__);
+ return;
+ }
+
+ gpio_direction_output(gpio, on);
+ hisilog_info("%s: set typec vbus gpio to %d\n", __func__, on);
+}
+
+void gpio_hub_typec_power_on(void)
+{
+ struct gpio_hub_info *info = &gpio_hub_driver_info;
+
+ gpio_hub_change_typec_power(info->typec_vbus_gpio,
+ info->typec_vbus_enable_val);
+}
+EXPORT_SYMBOL_GPL(gpio_hub_typec_power_on);
+
+void gpio_hub_typec_power_off(void)
+{
+ struct gpio_hub_info *info = &gpio_hub_driver_info;
+
+ gpio_hub_change_typec_power(info->typec_vbus_gpio,
+ !info->typec_vbus_enable_val);
+}
+EXPORT_SYMBOL_GPL(gpio_hub_typec_power_off);
+
+static int gpio_hub_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device_node *root = pdev->dev.of_node;
+ struct gpio_hub_info *info = &gpio_hub_driver_info;
+
+ hisilog_info("%s: step in\n", __func__);
+
+ info->pdev = pdev;
+ if (!pdev)
+ return -EBUSY;
+
+ info->hub_vbus_gpio = of_get_named_gpio(root, "hub_vdd33_en_gpio", 0);
+ if (!gpio_is_valid(info->hub_vbus_gpio)) {
+ hisilog_err("%s: hub_vbus_gpio is err\n", __func__);
+ return info->hub_vbus_gpio;
+ }
+ ret = gpio_request(info->hub_vbus_gpio, "hub_vbus_int_gpio");
+ if (ret) {
+ hisilog_err("%s: request hub_vbus_gpio err\n", __func__);
+ return ret;
+ }
+
+ info->typec_vbus_gpio = of_get_named_gpio(root,
+ "typc_vbus_int_gpio,typec-gpios", 0);
+ if (!gpio_is_valid(info->hub_vbus_gpio)) {
+ hisilog_err("%s: typec_vbus_gpio is err\n", __func__);
+ ret = info->typec_vbus_gpio;
+ goto free_gpio1;
+ }
+ ret = gpio_request(info->typec_vbus_gpio, "typc_vbus_int_gpio");
+ if (ret) {
+ hisilog_err("%s: request typec_vbus_gpio err\n", __func__);
+ goto free_gpio1;
+ }
+
+ ret = of_property_read_u32(root, "typc_vbus_enable_val",
+ &info->typec_vbus_enable_val);
+ if (ret) {
+ hisilog_err("%s: typc_vbus_enable_val can't get\n", __func__);
+ goto free_gpio2;
+ }
+ info->typec_vbus_enable_val = !!info->typec_vbus_enable_val;
+
+ /* only for v2 */
+ info->otg_switch_gpio = of_get_named_gpio(root, "otg_gpio", 0);
+ if (!gpio_is_valid(info->otg_switch_gpio)) {
+ hisilog_info("%s: otg_switch_gpio is err\n", __func__);
+ info->otg_switch_gpio = -1;
+ }
+
+ ret = gpio_direction_output(info->hub_vbus_gpio, GPIO_HUB_VBUS_POWER);
+ if (ret) {
+ hisilog_err("%s: power on hub vbus err\n", __func__);
+ goto free_gpio2;
+ }
+
+ ret = gpio_direction_output(info->typec_vbus_gpio,
+ info->typec_vbus_enable_val);
+ if (ret) {
+ hisilog_err("%s: power on typec vbus err", __func__);
+ goto free_gpio2;
+ }
+
+ return 0;
+
+free_gpio2:
+ gpio_free(info->typec_vbus_gpio);
+ info->typec_vbus_gpio = -1;
+free_gpio1:
+ gpio_free(info->hub_vbus_gpio);
+ info->hub_vbus_gpio = -1;
+
+ return ret;
+}
+
+static int gpio_hub_remove(struct platform_device *pdev)
+{
+ struct gpio_hub_info *info = &gpio_hub_driver_info;
+
+ if (gpio_is_valid(info->otg_switch_gpio)) {
+ gpio_free(info->otg_switch_gpio);
+ info->otg_switch_gpio = -1;
+ }
+
+ if (gpio_is_valid(info->typec_vbus_gpio)) {
+ gpio_free(info->typec_vbus_gpio);
+ info->typec_vbus_gpio = -1;
+ }
+
+ if (gpio_is_valid(info->hub_vbus_gpio)) {
+ gpio_free(info->hub_vbus_gpio);
+ info->hub_vbus_gpio = -1;
+ }
+ return 0;
+}
+
+static const struct of_device_id id_table_for_gpio_hub[] = {
+ {.compatible = "hisilicon,gpio_hubv1"},
+ {.compatible = "hisilicon,gpio_hubv2"},
+ {}
+};
+
+static struct platform_driver gpio_hub_driver = {
+ .probe = gpio_hub_probe,
+ .remove = gpio_hub_remove,
+ .driver = {
+ .name = DEVICE_DRIVER_NAME,
+ .of_match_table = of_match_ptr(id_table_for_gpio_hub),
+
+ },
+};
+
+static int __init gpio_hub_init(void)
+{
+ int ret = platform_driver_register(&gpio_hub_driver);
+
+ hisilog_info("%s:gpio hub init status:%d\n", __func__, ret);
+ return ret;
+}
+
+static void __exit gpio_hub_exit(void)
+{
+ platform_driver_unregister(&gpio_hub_driver);
+}
+
+module_init(gpio_hub_init);
+module_exit(gpio_hub_exit);
+
+MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("HUB GPIO FOR OTG ID driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 76f0b0df37c1..ccbf0c35a9b1 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -96,6 +96,15 @@ int dwc3_host_init(struct dwc3 *dwc)
goto err1;
}

+#ifdef CONFIG_USB_DWC3_HISI
+ /* if otg, otg will do device_add */
+ if (dwc->dwc_otg) {
+ dev_err(dwc->dev, "%s if otg, otg will do device_add.\n",
+ __func__);
+ return 0;
+ }
+#endif
+
memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props));

if (dwc->usb3_lpm_capable)
@@ -145,6 +154,10 @@ int dwc3_host_init(struct dwc3 *dwc)

void dwc3_host_exit(struct dwc3 *dwc)
{
+#ifdef CONFIG_USB_DWC3_HISI
+ if (dwc->dwc_otg)
+ return;
+#endif
phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy",
dev_name(dwc->dev));
phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h
index c69b06696824..adc8648c92b2 100644
--- a/drivers/usb/dwc3/io.h
+++ b/drivers/usb/dwc3/io.h
@@ -28,6 +28,13 @@ static inline u32 dwc3_readl(void __iomem *base, u32 offset)
{
u32 value;

+#ifdef CONFIG_USB_DWC3_HISI
+ extern atomic_t hisi_dwc3_power_on;
+
+ if (unlikely(atomic_read(&hisi_dwc3_power_on) == 0))
+ return 0;
+#endif
+
/*
* We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c.
@@ -47,6 +54,13 @@ static inline u32 dwc3_readl(void __iomem *base, u32 offset)

static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value)
{
+#ifdef CONFIG_USB_DWC3_HISI
+ extern atomic_t hisi_dwc3_power_on;
+
+ if (unlikely(atomic_read(&hisi_dwc3_power_on) == 0))
+ return;
+#endif
+
/*
* We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c.
diff --git a/include/linux/hisi/log/hisi_log.h b/include/linux/hisi/log/hisi_log.h
new file mode 100644
index 000000000000..cc3eda1c4f0f
--- /dev/null
+++ b/include/linux/hisi/log/hisi_log.h
@@ -0,0 +1,143 @@
+#ifndef _LINUX_HISILOG_H
+#define _LINUX_HISILOG_H
+
+#include <linux/printk.h>
+#include <linux/types.h>
+
+enum {
+ HISILOG_ERR = 1U << 0,
+ HISILOG_WARNING = 1U << 1,
+ HISILOG_INFO = 1U << 2,
+ HISILOG_DEBUG = 1U << 3,
+ HISILOG_DEBUG1 = 1U << 4,
+ HISILOG_DEBUG2 = 1U << 5,
+ HISILOG_DEBUG3 = 1U << 6,
+ HISILOG_DEBUG4 = 1U << 7,
+};
+
+#define HISILOG_TAG_DEFOUTL_LEVEL (HISILOG_ERR \
+ | HISILOG_WARNING \
+ | HISILOG_INFO)
+
+struct hisi_log_tag {
+ const char *name;
+ u32 level;
+};
+
+#define HISILOG_REGIST() \
+ HISILOG_REGIST_TAG_LEVEL(HISILOG_TAG, HISILOG_TAG_DEFOUTL_LEVEL)
+
+#define HISILOG_REGIST_LEVEL(level) \
+ HISILOG_REGIST_TAG_LEVEL(HISILOG_TAG, level)
+
+#define HISILOG_REGIST_TAG_LEVEL(name, level) \
+ _HISILOG_REGIST_TAG_LEVEL(name, level)
+
+#define _HISILOG_REGIST_TAG_LEVEL(name, level) \
+ static struct hisi_log_tag TAG_STRUCT_NAME(name) \
+__used \
+__attribute__ ((unused, __section__("__hisilog_tag"))) \
+= { #name, level}
+
+#define hisilog_err(x...) \
+ _hisilog_err(HISILOG_TAG, ##x)
+
+#define _hisilog_err(TAG, x...) \
+ __hisilog_err(TAG, ##x)
+
+#define __hisilog_err(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_ERR) \
+ pr_err(hw_fmt_tag(TAG, E) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_warn(x...) \
+ _hisilog_warn(HISILOG_TAG, ##x)
+
+#define _hisilog_warn(TAG, x...) \
+ __hisilog_warn(TAG, ##x)
+
+#define __hisilog_warn(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_WARNING) \
+ pr_err(hw_fmt_tag(TAG, W) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_info(x...) \
+ _hisilog_info(HISILOG_TAG, ##x)
+
+#define _hisilog_info(TAG, x...) \
+ __hisilog_info(TAG, ##x)
+
+#define __hisilog_info(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_INFO) \
+ pr_info(hw_fmt_tag(TAG, I) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_debug(x...) \
+ _hisilog_debug(HISILOG_TAG, ##x)
+
+#define _hisilog_debug(TAG, x...) \
+ __hisilog_debug(TAG, ##x)
+
+#define __hisilog_debug(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG) \
+ pr_err(hw_fmt_tag(TAG, D) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_debug1(x...) \
+ _hisilog_debug1(HISILOG_TAG, ##x)
+
+#define _hisilog_debug1(TAG, x...) \
+ __hisilog_debug1(TAG, ##x)
+
+#define __hisilog_debug1(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG1) \
+ pr_err(hw_fmt_tag(TAG, D1) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_debug2(x...) \
+ _hisilog_debug2(HISILOG_TAG, ##x)
+
+#define _hisilog_debug2(TAG, x...) \
+ __hisilog_debug2(TAG, ##x)
+
+#define __hisilog_debug2(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG2) \
+ pr_err(hw_fmt_tag(TAG, D2) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_debug3(x...) \
+ _hisilog_debug3(HISILOG_TAG, ##x)
+
+#define _hisilog_debug3(TAG, x...) \
+ __hisilog_debug3(TAG, ##x)
+
+#define __hisilog_debug3(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG3) \
+ pr_err(hw_fmt_tag(TAG, D3) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define hisilog_debug4(x...) \
+ _hisilog_debug4(HISILOG_TAG, ##x)
+
+#define _hisilog_debug4(TAG, x...) \
+ __hisilog_debug4(TAG, ##x)
+
+#define __hisilog_debug4(TAG, fmt, ...) \
+ do { \
+ if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG4) \
+ pr_err(hw_fmt_tag(TAG, D4) fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define TAG_STRUCT_NAME(name) \
+ _hwtag_##name
+
+#define hw_fmt_tag(TAG, LEVEL) "[" #LEVEL "/" #TAG "] "
+
+#endif
diff --git a/include/linux/hisi/usb/hisi_hikey_gpio.h b/include/linux/hisi/usb/hisi_hikey_gpio.h
new file mode 100644
index 000000000000..99df5772df96
--- /dev/null
+++ b/include/linux/hisi/usb/hisi_hikey_gpio.h
@@ -0,0 +1,24 @@
+/*
+ * hub_usb5734.h
+ *
+ * Copyright (c) Hisilicon Tech. Co., Ltd. All rights reserved.
+ *
+ * Chenjun <chenjun@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ */
+void gpio_hub_power_on(void);
+void gpio_hub_power_off(void);
+void gpio_hub_switch_to_hub(void);
+void gpio_hub_switch_to_typec(void);
+void gpio_hub_typec_power_off(void);
+void gpio_hub_typec_power_on(void);
diff --git a/include/linux/hisi/usb/hisi_usb.h b/include/linux/hisi/usb/hisi_usb.h
new file mode 100644
index 000000000000..9ee216e32cd1
--- /dev/null
+++ b/include/linux/hisi/usb/hisi_usb.h
@@ -0,0 +1,57 @@
+#ifndef _HISI_USB_H_
+#define _HISI_USB_H_
+
+enum hisi_charger_type {
+ CHARGER_TYPE_SDP = 0, /* Standard Downstreame Port */
+ CHARGER_TYPE_CDP, /* Charging Downstreame Port */
+ CHARGER_TYPE_DCP, /* Dedicate Charging Port */
+ CHARGER_TYPE_UNKNOWN, /* non-standard */
+ CHARGER_TYPE_NONE, /* not connected */
+
+ /* other messages */
+ PLEASE_PROVIDE_POWER, /* host mode, provide power */
+};
+
+enum otg_dev_event_type {
+ CHARGER_CONNECT_EVENT = 0,
+ CHARGER_DISCONNECT_EVENT,
+ ID_FALL_EVENT,
+ ID_RISE_EVENT,
+ NONE_EVENT
+};
+
+#if defined(CONFIG_USB_SUSB_HDRC) || defined(CONFIG_USB_DWC3)
+int hisi_charger_type_notifier_register(struct notifier_block *nb);
+int hisi_charger_type_notifier_unregister(struct notifier_block *nb);
+enum hisi_charger_type hisi_get_charger_type(void);
+int hisi_usb_otg_event(enum otg_dev_event_type event_type);
+void hisi_usb_otg_bc_again(void);
+#else
+static inline int hisi_charger_type_notifier_register(
+ struct notifier_block *nb){return 0; }
+static inline int hisi_charger_type_notifier_unregister(
+ struct notifier_block *nb){return 0; }
+static inline enum hisi_charger_type hisi_get_charger_type(void)
+{
+ return CHARGER_TYPE_NONE;
+}
+
+static inline int hisi_usb_otg_event(enum otg_dev_event_type event_type)
+{
+ return 0;
+}
+
+static inline void hisi_usb_otg_bc_again(void)
+{
+}
+#endif /* CONFIG_USB_SUSB_HDRC || CONFIG_USB_DWC3 */
+
+static inline int hisi_usb_id_change(enum otg_dev_event_type event)
+{
+ if ((event == ID_FALL_EVENT) || (event == ID_RISE_EVENT))
+ return hisi_usb_otg_event(event);
+ else
+ return 0;
+}
+
+#endif /* _HISI_USB_H_*/
--
2.11.GIT