lkml.org 
[lkml]   [2017]   [Oct]   [24]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC 2/3] USB: dwc3: Modify dwc3 code for support usb of Hikey960
Date
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@huawei.com>
Signed-off-by: Ning Fan <fanning4@hisilicon.com>
Signed-off-by: Di Yang <yangdi10@hisilicon.com>
Signed-off-by: Rui Li <lirui39@hisilicon.com>

---
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@hisilicon.com>
+ *
+ * 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@hisilicon.com>");
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@hisilicon.com>
+ *
+ * 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@hisilicon.com>");
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@hisilicon.com>
+ *
+ * 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@hisilicon.com>
+ *
+ * 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@hisilicon.com>
+ *
+ * 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@hisilicon.com>");
+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@hisilicon.com>
+ *
+ * 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

\
 
 \ /
  Last update: 2017-10-24 08:25    [W:0.474 / U:0.524 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site