The usb controller of Kirin960 is DesignWare Cores SuperSpeed USB 3.0 Controller. The patch modifies dwc3 for support Kirin960 and adds codes for a USB Hub on board Hikey960. Signed-off-by: Yu Chen <chenyu56@xxxxxxxxxx> Signed-off-by: Ning Fan <fanning4@xxxxxxxxxxxxx> Signed-off-by: Di Yang <yangdi10@xxxxxxxxxxxxx> Signed-off-by: Rui Li <lirui39@xxxxxxxxxxxxx> --- arch/arm64/configs/defconfig | 5 + drivers/usb/dwc3/Kconfig | 26 + drivers/usb/dwc3/Makefile | 5 + drivers/usb/dwc3/core.c | 78 +- drivers/usb/dwc3/core.h | 19 +- drivers/usb/dwc3/dwc3-hi3660.c | 310 +++++ drivers/usb/dwc3/dwc3-hisi.c | 1972 ++++++++++++++++++++++++++++++ drivers/usb/dwc3/dwc3-hisi.h | 293 +++++ drivers/usb/dwc3/dwc3-otg.c | 360 ++++++ drivers/usb/dwc3/dwc3-otg.h | 133 ++ drivers/usb/dwc3/ep0.c | 55 +- drivers/usb/dwc3/gadget.c | 20 +- drivers/usb/dwc3/hisi_hikey_gpio.c | 300 +++++ drivers/usb/dwc3/host.c | 13 + drivers/usb/dwc3/io.h | 14 + include/linux/hisi/log/hisi_log.h | 143 +++ include/linux/hisi/usb/hisi_hikey_gpio.h | 24 + include/linux/hisi/usb/hisi_usb.h | 57 + 18 files changed, 3819 insertions(+), 8 deletions(-) create mode 100644 drivers/usb/dwc3/dwc3-hi3660.c create mode 100644 drivers/usb/dwc3/dwc3-hisi.c create mode 100644 drivers/usb/dwc3/dwc3-hisi.h create mode 100644 drivers/usb/dwc3/dwc3-otg.c create mode 100644 drivers/usb/dwc3/dwc3-otg.h create mode 100644 drivers/usb/dwc3/hisi_hikey_gpio.c create mode 100644 include/linux/hisi/log/hisi_log.h create mode 100644 include/linux/hisi/usb/hisi_hikey_gpio.h create mode 100644 include/linux/hisi/usb/hisi_usb.h diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 34480e9af2e7..8e61b7d96bba 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -405,6 +405,7 @@ CONFIG_SND_SOC_SAMSUNG=y CONFIG_SND_SOC_RCAR=m CONFIG_SND_SOC_AK4613=m CONFIG_SND_SIMPLE_CARD=y +CONFIG_HISI_HIKEY_GPIO=y CONFIG_USB=y CONFIG_USB_OTG=y CONFIG_USB_XHCI_HCD=y @@ -419,6 +420,9 @@ CONFIG_USB_OHCI_HCD_PLATFORM=y CONFIG_USB_RENESAS_USBHS=m CONFIG_USB_STORAGE=y CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_DUAL_ROLE=y +CONFIG_USB_DWC3_HISI=y +CONFIG_USB_DWC3_OTG=y CONFIG_USB_DWC2=y CONFIG_USB_CHIPIDEA=y CONFIG_USB_CHIPIDEA_UDC=y @@ -428,6 +432,7 @@ CONFIG_USB_HSIC_USB3503=y CONFIG_NOP_USB_XCEIV=y CONFIG_USB_MSM_OTG=y CONFIG_USB_QCOM_8X16_PHY=y +CONFIG_EXTCON=y CONFIG_USB_ULPI=y CONFIG_USB_GADGET=y CONFIG_USB_RENESAS_USBHS_UDC=m diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index ab8c0e0d3b60..5f7d9f19f503 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -106,4 +106,30 @@ config USB_DWC3_ST inside (i.e. STiH407). Say 'Y' or 'M' if you have one such device. +config USB_DWC3_HISI + tristate "Hisilicon Platforms" + select USB_DWC3_OTG + depends on USB_DWC3 + default n + help + Support of USB2/3 functionality in hisilicon platforms, + Say 'Y' or 'M' here if you have one such device. + Use for hisilicon device and it will select USB_DWC3_OTG + if Say 'Y' or 'M' here. + +config USB_DWC3_OTG + bool "Enable DWC3 OTG" + default n + help + Support of USB2/3 functionality in hisilicon platforms, + Say 'Y' or 'M' here if you have one such device. + Use for hisilicon device + if Say 'Y' or 'M' here. + +config HISI_HIKEY_GPIO + tristate "HISI_HIKEY_GPIO" + depends on GPIOLIB + default n + help + If you say yes here you get support for hisi hikey gpio. endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index f15fabbd1e59..c2c32a5effc7 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -1,6 +1,7 @@ # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) +ccflags-$(CONFIG_USB_DWC3_OTG) += -DDWC3_OTG_FORCE_MODE obj-$(CONFIG_USB_DWC3) += dwc3.o dwc3-y := core.o @@ -29,6 +30,8 @@ ifneq ($(CONFIG_DEBUG_FS),) dwc3-y += debugfs.o endif +dwc3-$(CONFIG_USB_DWC3_OTG) += dwc3-otg.o + ## # Platform-specific glue layers go here # @@ -47,3 +50,5 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o +obj-$(CONFIG_USB_DWC3_HISI) += dwc3-hisi.o dwc3-hi3660.o +obj-$(CONFIG_HISI_HIKEY_GPIO) += hisi_hikey_gpio.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 03474d3575ab..d0105a26867d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -44,7 +44,7 @@ #include "core.h" #include "gadget.h" #include "io.h" - +#include "dwc3-otg.h" #include "debug.h" #define DWC3_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */ @@ -87,6 +87,8 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) mode = USB_DR_MODE_HOST; else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) mode = USB_DR_MODE_PERIPHERAL; + else if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) + mode = USB_DR_MODE_OTG; } if (mode != dwc->dr_mode) { @@ -103,7 +105,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) static void dwc3_event_buffers_cleanup(struct dwc3 *dwc); static int dwc3_event_buffers_setup(struct dwc3 *dwc); -static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) { u32 reg; @@ -113,6 +115,7 @@ static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } +#ifndef CONFIG_USB_DWC3_HISI static void __dwc3_set_mode(struct work_struct *work) { struct dwc3 *dwc = work_to_dwc(work); @@ -177,6 +180,7 @@ static void __dwc3_set_mode(struct work_struct *work) break; } } +#endif void dwc3_set_mode(struct dwc3 *dwc, u32 mode) { @@ -362,6 +366,12 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) evt = dwc->ev_buf; evt->lpos = 0; + #ifdef CONFIG_USB_DWC3_HISI + evt->count = 0; + evt->flags = 0; + memset(evt->buf, 0, evt->length); + #endif + dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0), lower_32_bits(evt->dma)); dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0), @@ -730,7 +740,13 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc) */ if (dwc->revision < DWC3_REVISION_190A) reg |= DWC3_GCTL_U2RSTECN; - + #ifdef DWC3_OTG_FORCE_MODE + /* + * if ID status is detected by third module, default device mode. + */ + reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); + reg |= DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_DEVICE); + #endif dwc3_writel(dwc->regs, DWC3_GCTL, reg); } @@ -957,6 +973,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: + #ifndef CONFIG_USB_DWC3_HISI INIT_WORK(&dwc->drd_work, __dwc3_set_mode); ret = dwc3_drd_init(dwc); if (ret) { @@ -964,6 +981,30 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) dev_err(dev, "failed to initialize dual-role\n"); return ret; } + #else + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); + + ret = dwc3_otg_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize otg\n"); + return ret; + } + + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + dwc3_otg_exit(dwc); + return ret; + } + + ret = dwc3_gadget_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + dwc3_host_exit(dwc); + dwc3_otg_exit(dwc); + return ret; + } + #endif break; default: dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); @@ -984,6 +1025,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) break; case USB_DR_MODE_OTG: dwc3_drd_exit(dwc); + dwc3_otg_exit(dwc); break; default: /* do nothing */ @@ -1341,8 +1383,10 @@ static int dwc3_runtime_checks(struct dwc3 *dwc) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: +#ifndef CONFIG_USB_DWC3_HISI if (dwc->connected) return -EBUSY; +#endif break; case USB_DR_MODE_HOST: default: @@ -1367,6 +1411,7 @@ static int dwc3_runtime_suspend(struct device *dev) device_init_wakeup(dev, true); + pm_runtime_put(dev); return 0; } @@ -1393,7 +1438,7 @@ static int dwc3_runtime_resume(struct device *dev) } pm_runtime_mark_last_busy(dev); - pm_runtime_put(dev); + pm_runtime_get(dev); return 0; } @@ -1461,6 +1506,31 @@ static const struct dev_pm_ops dwc3_dev_pm_ops = { dwc3_runtime_idle) }; +int dwc3_resume_device(struct dwc3 *dwc) +{ + int status; + + pr_info("[%s] +\n", __func__); + status = dwc3_runtime_resume(dwc->dev); + if (status < 0) + pr_err("dwc3_runtime_resume err, status:%d\n", status); + + pr_info("[%s] -\n", __func__); + return status; +} + +void dwc3_suspend_device(struct dwc3 *dwc) +{ + int status; + + pr_info("[%s] +\n", __func__); + status = dwc3_runtime_suspend(dwc->dev); + if (status < 0) + pr_err("dwc3_runtime_suspend err, status:%d\n", status); + + pr_info("[%s] -\n", __func__); +} + #ifdef CONFIG_OF static const struct of_device_id of_dwc3_match[] = { { diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index ea910acb4bb0..3b6dd99daf9a 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -750,6 +750,7 @@ struct dwc3_request { unsigned mapped:1; unsigned started:1; unsigned zero:1; + unsigned send_zlp:1; }; /* @@ -980,10 +981,17 @@ struct dwc3 { u8 lpm_nyet_threshold; u8 hird_threshold; + struct dwc3_otg *dwc_otg; const char *hsphy_interface; unsigned connected:1; unsigned delayed_status:1; + + /* the delayed status may come before notready interrupt, + * in this case, don't wait for delayed status + */ + unsigned status_queued:1; + unsigned ep0_bounced:1; unsigned ep0_expect_in:1; unsigned has_hibernation:1; @@ -1175,7 +1183,7 @@ struct dwc3_gadget_ep_cmd_params { /* prototypes */ void dwc3_set_mode(struct dwc3 *dwc, u32 mode); u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type); - +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode); /* check whether we are on the DWC_usb3 core */ static inline bool dwc3_is_usb3(struct dwc3 *dwc) { @@ -1209,6 +1217,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +int dwc3_conndone_notifier_register(struct notifier_block *nb); +int dwc3_conndone_notifier_unregister(struct notifier_block *nb); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } @@ -1228,6 +1238,10 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) { return 0; } +static inline int dwc3_conndone_notifier_register(struct notifier_block *nb) +{ return 0; } +static inline int dwc3_conndone_notifier_unregister(struct notifier_block *nb) +{ return 0; } #endif #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) @@ -1261,6 +1275,9 @@ static inline void dwc3_gadget_process_pending_events(struct dwc3 *dwc) } #endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */ +int dwc3_resume_device(struct dwc3 *dwc); +void dwc3_suspend_device(struct dwc3 *dwc); + #if IS_ENABLED(CONFIG_USB_DWC3_ULPI) int dwc3_ulpi_init(struct dwc3 *dwc); void dwc3_ulpi_exit(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/dwc3-hi3660.c b/drivers/usb/dwc3/dwc3-hi3660.c new file mode 100644 index 000000000000..d8cdc0f7280b --- /dev/null +++ b/drivers/usb/dwc3/dwc3-hi3660.c @@ -0,0 +1,310 @@ +/* + * dwc3-hi3660.c + * + * Copyright: (C) 2008-2018 hisilicon. + * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx> + * + * USB vbus for Hisilicon device + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include "dwc3-hisi.h" + +/*lint -e750 -esym(750,*)*/ +/* clk module will round to 228M */ +#define USB3OTG_ACLK_FREQ 229000000 +#ifndef BIT +#define BIT(x) (1 << (x)) +#endif +#define SCTRL_SCDEEPSLEEPED 0x08 +#define USB_REFCLK_ISO_EN BIT(25) +#define PCTRL_PERI_CTRL3 0x10 +#define USB_TCXO_EN BIT(1) +#define PERI_CTRL3_MSK_START (16) +#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25) + +#define SC_SEL_ABB_BACKUP BIT(8) +#define CLKDIV_MASK_START (16) + +#define PERI_CRG_CLKDIV21 0xFC + +#define GT_CLK_ABB_BACKUP BIT(22) +#define PERI_CRG_CLK_DIS5 0x54 + +#define PMC_PPLL3CTRL0 0x048 +#define PPLL3_FBDIV_START (8) +#define PPLL3_EN BIT(0) +#define PPLL3_BP BIT(1) +#define PPLL3_LOCK BIT(26) + +#define PMC_PPLL3CTRL1 0x04C +#define PPLL3_INT_MOD BIT(24) +#define GT_CLK_PPLL3 BIT(26) + +#define PERI_CRG_CLK_EN5 0x50 + +#define SC_USB3PHY_ABB_GT_EN BIT(15) +#define REF_SSP_EN BIT(16) +/*lint -e750 +esym(750,*)*/ + +static int usb3_regu_init(struct hisi_dwc3_device *hisi_dwc3) +{ + if (hisi_dwc3->is_regu_on != 0) { + usb_dbg("ldo already opened!\n"); + return 0; + } + + hisi_dwc3->is_regu_on = 1; + + return 0; +} + +static int usb3_regu_shutdown(struct hisi_dwc3_device *hisi_dwc3) +{ + if (hisi_dwc3->is_regu_on == 0) { + usb_dbg("regu already closed!\n"); + return 0; + } + + hisi_dwc3->is_regu_on = 0; + + return 0; +} + +static int usb3_clk_init(struct hisi_dwc3_device *hisi_dwc3) +{ + int ret; + u32 temp; + void __iomem *pctrl_base = hisi_dwc3->pctrl_reg_base; + void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base; + + /* set usb aclk 240MHz to improve performance */ + ret = clk_set_rate(hisi_dwc3->gt_aclk_usb3otg, USB3OTG_ACLK_FREQ); + if (ret) + usb_err("usb aclk set rate failed\n"); + + ret = clk_prepare_enable(hisi_dwc3->gt_aclk_usb3otg); + if (ret) { + usb_err("clk_prepare_enable gt_aclk_usb3otg failed\n"); + return ret; + } + + /* usb refclk iso enable */ + writel(USB_REFCLK_ISO_EN, pericfg_base + PERI_CRG_ISODIS); + + /* enable usb_tcxo_en */ + writel(USB_TCXO_EN | (USB_TCXO_EN << PERI_CTRL3_MSK_START), + pctrl_base + PCTRL_PERI_CTRL3); + + /* select usbphy clk from abb */ + temp = readl(pctrl_base + PCTRL_PERI_CTRL24); + temp &= ~SC_CLK_USB3PHY_3MUX1_SEL; + writel(temp, pctrl_base + PCTRL_PERI_CTRL24); + + /* open clk gate */ + writel(GT_CLK_USB3OTG_REF | GT_ACLK_USB3OTG, + pericfg_base + PERI_CRG_CLK_EN4); + + ret = clk_prepare_enable(hisi_dwc3->clk); + if (ret) { + usb_err("clk_prepare_enable clk failed\n"); + return ret; + } + + return 0; +} + +static void usb3_clk_shutdown(struct hisi_dwc3_device *hisi_dwc3) +{ + u32 temp; + void __iomem *pctrl_base = hisi_dwc3->pctrl_reg_base; + void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base; + + writel(GT_CLK_USB3OTG_REF | GT_ACLK_USB3OTG, + pericfg_base + PERI_CRG_CLK_DIS4); + + temp = readl(pctrl_base + PCTRL_PERI_CTRL24); + temp &= ~SC_CLK_USB3PHY_3MUX1_SEL; + writel(temp, pctrl_base + PCTRL_PERI_CTRL24); + + /* disable usb_tcxo_en */ + writel(0 | (USB_TCXO_EN << PERI_CTRL3_MSK_START), + pctrl_base + PCTRL_PERI_CTRL3); + + clk_disable_unprepare(hisi_dwc3->clk); + clk_disable_unprepare(hisi_dwc3->gt_aclk_usb3otg); + + msleep(20); +} + +static void dwc3_release(struct hisi_dwc3_device *hisi_dwc3) +{ + u32 temp; + void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base; + void __iomem *otg_bc_base = hisi_dwc3->otg_bc_reg_base; + + /* dis-reset the module */ + writel(IP_RST_USB3OTG_MUX | IP_RST_USB3OTG_AHBIF | IP_RST_USB3OTG_32K, + pericfg_base + PERI_CRG_RSTDIS4); + + /* reset phy */ + writel(IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG, + pericfg_base + PERI_CRG_RSTEN4); + + /* enable phy ref clk */ + temp = readl(otg_bc_base + USBOTG3_CTRL0); + temp |= SC_USB3PHY_ABB_GT_EN; + writel(temp, otg_bc_base + USBOTG3_CTRL0); + + temp = readl(otg_bc_base + USBOTG3_CTRL7); + temp |= REF_SSP_EN; + writel(temp, otg_bc_base + USBOTG3_CTRL7); + + /* exit from IDDQ mode */ + temp = readl(otg_bc_base + USBOTG3_CTRL2); + temp &= ~(USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP); + writel(temp, otg_bc_base + USBOTG3_CTRL2); + + usleep_range(100, 120); + + /* dis-reset phy */ + writel(IP_RST_USB3OTGPHY_POR, pericfg_base + PERI_CRG_RSTDIS4); + + /* dis-reset controller */ + writel(IP_RST_USB3OTG, pericfg_base + PERI_CRG_RSTDIS4); + + msleep(20); + + /* fake vbus valid signal */ + temp = readl(otg_bc_base + USBOTG3_CTRL3); + temp |= (USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL); + writel(temp, otg_bc_base + USBOTG3_CTRL3); + + usleep_range(100, 120); +} + +static void dwc3_reset(struct hisi_dwc3_device *hisi_dwc3) +{ + void __iomem *pericfg_base = hisi_dwc3->pericfg_reg_base; + + writel(IP_RST_USB3OTG, pericfg_base + PERI_CRG_RSTEN4); + writel(IP_RST_USB3OTGPHY_POR, pericfg_base + PERI_CRG_RSTEN4); + writel(IP_RST_USB3OTG_MUX | IP_RST_USB3OTG_AHBIF | IP_RST_USB3OTG_32K, + pericfg_base + PERI_CRG_RSTEN4); +} + +static int hi3660_usb3phy_init(struct hisi_dwc3_device *hisi_dwc3) +{ + int ret; + + usb_dbg("+\n"); + + ret = usb3_regu_init(hisi_dwc3); + if (ret) + return ret; + + ret = usb3_clk_init(hisi_dwc3); + if (ret) + return ret; + + dwc3_release(hisi_dwc3); + config_femtophy_param(hisi_dwc3); + + set_hisi_dwc3_power_flag(1); + + usb_dbg("-\n"); + + return 0; +} + +static int hi3660_usb3phy_shutdown(struct hisi_dwc3_device *hisi_dwc3) +{ + int ret; + + usb_dbg("+\n"); + + set_hisi_dwc3_power_flag(0); + + dwc3_reset(hisi_dwc3); + usb3_clk_shutdown(hisi_dwc3); + + ret = usb3_regu_shutdown(hisi_dwc3); + if (ret) + return ret; + + usb_dbg("-\n"); + + return 0; +} + +static struct usb3_phy_ops hi3660_phy_ops = { + .init = hi3660_usb3phy_init, + .shutdown = hi3660_usb3phy_shutdown, +}; + +static int dwc3_hi3660_probe(struct platform_device *pdev) +{ + int ret = 0; + + ret = hisi_dwc3_probe(pdev, &hi3660_phy_ops); + if (ret) + usb_err("probe failed, ret=[%d]\n", ret); + + return ret; +} + +static int dwc3_hi3660_remove(struct platform_device *pdev) +{ + int ret = 0; + + ret = hisi_dwc3_remove(pdev); + if (ret) + usb_err("hisi_dwc3_remove failed, ret=[%d]\n", ret); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id dwc3_hi3660_match[] = { + { .compatible = "hisilicon,hi3660-dwc3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_hi3660_match); +#else +#define dwc3_hi3660_match NULL +#endif + +static struct platform_driver dwc3_hi3660_driver = { + .probe = dwc3_hi3660_probe, + .remove = dwc3_hi3660_remove, + .driver = { + .name = "usb3-hi3660", + .of_match_table = of_match_ptr(dwc3_hi3660_match), + .pm = HISI_DWC3_PM_OPS, + }, +}; + +module_platform_driver(dwc3_hi3660_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 HI3660 Glue Layer"); +MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>"); diff --git a/drivers/usb/dwc3/dwc3-hisi.c b/drivers/usb/dwc3/dwc3-hisi.c new file mode 100644 index 000000000000..f62921ca41d3 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-hisi.c @@ -0,0 +1,1972 @@ +/* + * hisi_usb_vbus.c + * + * Copyright: (C) 2008-2018 hisilicon. + * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx> + * + * USB vbus for Hisilicon device + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/io.h> +#include <linux/of_gpio.h> +#include <linux/usb/ch9.h> + +#include "dwc3-hisi.h" +#include "core.h" +#include "dwc3-otg.h" + +#define ENABLE_USB_TEST_PORT + +#define BC_AGAIN_DELAY_TIME 8000 /* ms */ + +struct hisi_dwc3_device *hisi_dwc3_dev; +atomic_t hisi_dwc3_power_on = ATOMIC_INIT(0); + +void set_hisi_dwc3_power_flag(int val) +{ + unsigned long flags; + struct dwc3 *dwc = NULL; + + if (dwc_otg_handler && dwc_otg_handler->dwc) { + dwc = dwc_otg_handler->dwc; + spin_lock_irqsave(&dwc->lock, flags); + usb_dbg("get dwc3 lock\n"); + } + + atomic_set(&hisi_dwc3_power_on, val); + usb_dbg("set hisi_dwc3_power_flag %d\n", val); + + if (dwc) { + spin_unlock_irqrestore(&dwc->lock, flags); + usb_dbg("put dwc3 lock\n"); + } +} + +#ifdef ENABLE_USB_TEST_PORT + +static ssize_t plugusb_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct hisi_dwc3_device *hisi_dwc3 = platform_get_drvdata(pdev); + char *s; + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return scnprintf(buf, PAGE_SIZE, "hisi_dwc3 NULL\n"); + } + + switch (hisi_dwc3->state) { + case USB_STATE_UNKNOWN: + s = "USB_STATE_UNKNOWN"; + break; + case USB_STATE_OFF: + s = "USB_STATE_OFF"; + break; + case USB_STATE_DEVICE: + s = "USB_STATE_DEVICE"; + break; + case USB_STATE_HOST: + s = "USB_STATE_HOST"; + break; + default: + s = "unknown"; + break; + } + return scnprintf(buf, PAGE_SIZE, "current state: %s\n usage: %s\n", s, + "echo hoston/hostoff/deviceon/deviceoff > plugusb\n"); +} + +static ssize_t plugusb_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (!strncmp(buf, "hoston", strlen("hoston"))) + hisi_usb_otg_event(ID_FALL_EVENT); + else if (!strncmp(buf, "hostoff", strlen("hostoff"))) + hisi_usb_otg_event(ID_RISE_EVENT); + else if (!strncmp(buf, "deviceon", strlen("deviceon"))) + hisi_usb_otg_event(CHARGER_CONNECT_EVENT); + else if (!strncmp(buf, "deviceoff", strlen("deviceoff"))) + hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT); + else + usb_err("input state is ilegal!\n"); + + /* added for show message of plugusb status to com port */ + pr_err("[USB.plugusb] %s\n", buf); + + return size; +} + +/*lint -save -e750 */ +DEVICE_ATTR(plugusb, (0644), plugusb_show, plugusb_store); +/*lint -restore */ + +static const char * const charger_type_array[] = { + [CHARGER_TYPE_SDP] = "sdp", /* Standard Downstreame Port */ + [CHARGER_TYPE_CDP] = "cdp", /* Charging Downstreame Port */ + [CHARGER_TYPE_DCP] = "dcp", /* Dedicate Charging Port */ + [CHARGER_TYPE_UNKNOWN] = "unknown", /* non-standard */ + [CHARGER_TYPE_NONE] = "none", /* not connected */ + [PLEASE_PROVIDE_POWER] = "provide" /* host mode, provide power */ +}; + +static enum hisi_charger_type get_charger_type_from_str(const char *buf, + size_t size) +{ + int i = 0; + enum hisi_charger_type ret = CHARGER_TYPE_NONE; + + for (i = 0; i < sizeof(charger_type_array) / + sizeof(charger_type_array[0]); i++) { + if (!strncmp(buf, charger_type_array[i], size - 1)) { + ret = (enum hisi_charger_type)i; + break; + } + } + + return ret; +} + +ssize_t hiusb_do_charger_show(void *dev_data, char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + enum hisi_charger_type charger_type = CHARGER_TYPE_NONE; + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return scnprintf(buf, size, + "platform_get_drvdata return null\n"); + } + + mutex_lock(&hisi_dwc->lock); + charger_type = hisi_dwc->charger_type; + mutex_unlock(&hisi_dwc->lock); + + return scnprintf(buf, size, "[(%d):Charger type = %s]\n" + "----------------------------------------------------------------\n" + "usage: echo {str} > chargertest\n" + " sdp: Standard Downstreame Port\n" + " cdp: Charging Downstreame Port\n" + " dcp: Dedicate Charging Port\n" + " unknown: non-standard\n" + " none: not connected\n" + " provide: host mode, provide power\n" + , charger_type, charger_type_array[charger_type]); +} + +int hiusb_get_eyepattern_param(void *dev_data, char *buf, size_t len) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + int ret = 0; + + if (hisi_dwc) { + ret = scnprintf(buf, len, "device:0x%x\nhost:0x%x\n", + hisi_dwc->eye_diagram_param, + hisi_dwc->eye_diagram_host_param); + } else { + usb_err("hisi_dwc NULL\n"); + ret = scnprintf(buf, len, "hisi_dwc NULL\n"); + } + + return ret; +} + +int hiusb_set_eyepattern_param(void *dev_data, const char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + int eye_diagram_param; + + if (!hisi_dwc) { + pr_err("seteye: hisi_dwc is null\n"); + return size; + } + + if (sscanf(buf, "%32x", &eye_diagram_param) != 1) + return size; + + hisi_dwc->eye_diagram_param = eye_diagram_param; + hisi_dwc->eye_diagram_host_param = eye_diagram_param; + + return size; +} + +static void notify_charger_type(struct hisi_dwc3_device *hisi_dwc3); +ssize_t hiusb_do_charger_store(void *dev_data, const char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + enum hisi_charger_type charger_type = + get_charger_type_from_str(buf, size); + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return size; + } + + mutex_lock(&hisi_dwc->lock); + hisi_dwc->charger_type = charger_type; + notify_charger_type(hisi_dwc); + mutex_unlock(&hisi_dwc->lock); + + return size; +} + +#ifdef CONFIG_HISI_DEBUG_FS +static ssize_t fakecharger_show(void *dev_data, char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return scnprintf(buf, size, + "platform_get_drvdata return null\n"); + } + + return scnprintf(buf, size, "[fake charger type: %s]\n", + hisi_dwc->fake_charger_type == CHARGER_TYPE_NONE ? + "not fake" : + charger_type_array[hisi_dwc->fake_charger_type]); +} + +static ssize_t fakecharger_store(void *dev_data, const char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + enum hisi_charger_type charger_type = + get_charger_type_from_str(buf, size); + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return size; + } + + mutex_lock(&hisi_dwc->lock); + hisi_dwc->fake_charger_type = charger_type; + mutex_unlock(&hisi_dwc->lock); + + return size; +} +#endif +ssize_t hiusb_do_eventmask_show(void *dev_data, char *buf, size_t size) +{ + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return scnprintf(buf, size, + "platform_get_drvdata return null\n"); + } + + return scnprintf(buf, size, "%d\n", hisi_dwc->eventmask); +} + +ssize_t hiusb_do_eventmask_store(void *dev_data, const char *buf, size_t size) +{ + int eventmask; + struct hisi_dwc3_device *hisi_dwc = (struct hisi_dwc3_device *)dev_data; + + if (!hisi_dwc) { + pr_err("platform_get_drvdata return null\n"); + return size; + } + + if (sscanf(buf, "%1d", &eventmask) != 1) + return size; + + hisi_dwc->eventmask = eventmask; + + return size; +} + +static struct device_attribute *hisi_dwc3_attributes[] = { + &dev_attr_plugusb, + NULL +}; + +static int create_attr_file(struct device *dev) +{ + struct device_attribute **attrs = hisi_dwc3_attributes; + struct device_attribute *attr; + struct class *hisi_usb_class; + struct device *hisi_usb_dev; + int i; + int ret = 0; + + usb_dbg("+\n"); + for (i = 0; attrs[i]; i++) { + attr = attrs[i]; + ret = device_create_file(dev, attr); + if (ret) { + dev_err(dev, "create attr file error!\n"); + goto err; + } + } + + hisi_usb_class = class_create(THIS_MODULE, "hisi_usb_class"); + if (IS_ERR(hisi_usb_class)) { + usb_dbg("create hisi_usb_class error!\n"); + } else { + hisi_usb_dev = device_create(hisi_usb_class, NULL, 0, + NULL, "hisi_usb_dev"); + if (IS_ERR(hisi_usb_dev)) + usb_dbg("create hisi_usb_dev error!\n"); + else + ret |= sysfs_create_link(&hisi_usb_dev->kobj, + &dev->kobj, "interface"); + } + if (ret) + usb_dbg("create attr file error!\n"); + +#ifdef CONFIG_HISI_DEBUG_FS + hiusb_debug_quick_register( + platform_get_drvdata(to_platform_device(dev)), + (hiusb_debug_show_ops)fakecharger_show, + (hiusb_debug_store_ops)fakecharger_store); + hiusb_debug_init(platform_get_drvdata(to_platform_device(dev))); +#endif + + usb_dbg("-\n"); + return 0; + +err: + for (i-- ; i >= 0; i--) { + attr = attrs[i]; + device_remove_file(dev, attr); + } + + return ret; +} + +static void remove_attr_file(struct device *dev) +{ + struct device_attribute **attrs = hisi_dwc3_attributes; + struct device_attribute *attr; + + while ((attr = *attrs++)) + device_remove_file(dev, attr); +} +#else +static inline int create_attr_file(struct device *dev) +{ + return 0; +} + +static inline void remove_attr_file(struct device *dev) {} +#endif + +static void phy_cr_wait_ack(void __iomem *otg_bc_base) +{ + int i = 1000; + + while (1) { + if ((readl(otg_bc_base + USB3PHY_CR_STS) & + USB3OTG_PHY_CR_ACK) == 1) + break; + usleep_range(50, 60); + if (i-- < 0) { + usb_err("wait phy_cr_ack timeout!\n"); + break; + } + } +} + +static void phy_cr_set_addr(void __iomem *otg_bc_base, u32 addr) +{ + u32 reg; + + /* set addr */ + reg = USB3OTG_PHY_CR_DATA_IN(addr); + writel(reg, otg_bc_base + USB3PHY_CR_CTRL); + + usleep_range(100, 120); + + /* cap addr */ + reg = readl(otg_bc_base + USB3PHY_CR_CTRL); + reg |= USB3OTG_PHY_CR_CAP_ADDR; + writel(reg, otg_bc_base + USB3PHY_CR_CTRL); + + phy_cr_wait_ack(otg_bc_base); + + /* clear ctrl reg */ + writel(0, otg_bc_base + USB3PHY_CR_CTRL); +} + +static u16 phy_cr_read(void __iomem *otg_bc_base, u32 addr) +{ + u32 reg; + int i = 1000; + + phy_cr_set_addr(otg_bc_base, addr); + + /* read cap */ + writel(USB3OTG_PHY_CR_READ, otg_bc_base + USB3PHY_CR_CTRL); + + usleep_range(100, 120); + + while (1) { + reg = readl(otg_bc_base + USB3PHY_CR_STS); + if ((reg & USB3OTG_PHY_CR_ACK) == 1) + break; + usleep_range(50, 60); + if (i-- < 0) { + usb_err("wait phy_cr_ack timeout!\n"); + break; + } + } + + /* clear ctrl reg */ + writel(0, otg_bc_base + USB3PHY_CR_CTRL); + + return (u16)USB3OTG_PHY_CR_DATA_OUT(reg); +} + +static void phy_cr_write(void __iomem *otg_bc_base, u32 addr, u32 value) +{ + u32 reg; + + phy_cr_set_addr(otg_bc_base, addr); + + reg = USB3OTG_PHY_CR_DATA_IN(value); + writel(reg, otg_bc_base + USB3PHY_CR_CTRL); + + /* cap data */ + reg = readl(otg_bc_base + USB3PHY_CR_CTRL); + reg |= USB3OTG_PHY_CR_CAP_DATA; + writel(reg, otg_bc_base + USB3PHY_CR_CTRL); + + /* wait ack */ + phy_cr_wait_ack(otg_bc_base); + + /* clear ctrl reg */ + writel(0, otg_bc_base + USB3PHY_CR_CTRL); + + reg = USB3OTG_PHY_CR_WRITE; + writel(reg, otg_bc_base + USB3PHY_CR_CTRL); + + /* wait ack */ + phy_cr_wait_ack(otg_bc_base); +} + +void set_usb3_phy_cr_param(u32 addr, u32 value) +{ + if (!hisi_dwc3_dev) { + pr_err("hisi dwc3 device not ready!\n"); + return; + } + + phy_cr_write(hisi_dwc3_dev->otg_bc_reg_base, addr, value); +} +EXPORT_SYMBOL_GPL(set_usb3_phy_cr_param); + +void read_usb3_phy_cr_param(u32 addr) +{ + if (!hisi_dwc3_dev) { + pr_err("hisi dwc3 device not ready!\n"); + return; + } + + usb_dbg("read usb3 phy cr param 0x%x\n", + phy_cr_read(hisi_dwc3_dev->otg_bc_reg_base, addr)); +} +EXPORT_SYMBOL_GPL(read_usb3_phy_cr_param); + +void config_femtophy_param(struct hisi_dwc3_device *hisi_dwc) +{ + u32 reg; + void __iomem *otg_bc_base = hisi_dwc->otg_bc_reg_base; + + if (hisi_dwc->fpga_flag != 0) + return; + + /* set high speed phy parameter */ + if (hisi_dwc->host_flag) { + writel(hisi_dwc->eye_diagram_host_param, + otg_bc_base + USBOTG3_CTRL4); + usb_dbg("set hs phy param 0x%x for host\n", + readl(otg_bc_base + USBOTG3_CTRL4)); + } else { + writel(hisi_dwc->eye_diagram_param, + otg_bc_base + USBOTG3_CTRL4); + usb_dbg("set hs phy param 0x%x for device\n", + readl(otg_bc_base + USBOTG3_CTRL4)); + } + + /* set usb3 phy cr config for usb3.0 */ + + if (hisi_dwc->host_flag) { + phy_cr_write(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI, + hisi_dwc->usb3_phy_host_cr_param); + } else { + phy_cr_write(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI, + hisi_dwc->usb3_phy_cr_param); + } + + usb_dbg("set ss phy rx equalization 0x%x\n", + phy_cr_read(otg_bc_base, DWC3_PHY_RX_OVRD_IN_HI)); + + /* enable RX_SCOPE_LFPS_EN for usb3.0 */ + reg = phy_cr_read(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC); + reg |= RX_SCOPE_LFPS_EN; + phy_cr_write(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC, reg); + + usb_dbg("set ss RX_SCOPE_VDCC 0x%x\n", + phy_cr_read(otg_bc_base, DWC3_PHY_RX_SCOPE_VDCC)); + + reg = readl(otg_bc_base + USBOTG3_CTRL6); + reg &= ~TX_VBOOST_LVL_MASK; + reg |= TX_VBOOST_LVL(hisi_dwc->usb3_phy_tx_vboost_lvl); + writel(reg, otg_bc_base + USBOTG3_CTRL6); + usb_dbg("set ss phy tx vboost lvl 0x%x\n", + readl(otg_bc_base + USBOTG3_CTRL6)); +} + +int hisi_charger_type_notifier_register(struct notifier_block *nb) +{ + if (!hisi_dwc3_dev) { + pr_err("hisi dwc3 device not ready!\n"); + return -EBUSY; + } + if (!nb) + return -EINVAL; + return atomic_notifier_chain_register( + &hisi_dwc3_dev->charger_type_notifier, nb); +} +EXPORT_SYMBOL_GPL(hisi_charger_type_notifier_register); + +int hisi_charger_type_notifier_unregister(struct notifier_block *nb) +{ + if (!hisi_dwc3_dev) { + pr_err("hisi dwc3 device not ready!\n"); + return -EBUSY; + } + if (!nb) + return -EINVAL; + return atomic_notifier_chain_unregister( + &hisi_dwc3_dev->charger_type_notifier, + nb); +} +EXPORT_SYMBOL_GPL(hisi_charger_type_notifier_unregister); + +/* BC1.2 Spec: + * If a PD detects that D+ is greater than VDAT_REF, it knows that it is + * attached to a DCP. It is then required to enable VDP_SRC or pull D+ + * to VDP_UP through RDP_UP + */ +static void disable_vdp_src(struct hisi_dwc3_device *hisi_dwc3) +{ + void __iomem *base = hisi_dwc3->otg_bc_reg_base; + u32 reg; + + usb_dbg("diaable VDP_SRC\n"); + + reg = readl(base + BC_CTRL2); + reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB); + writel(reg, base + BC_CTRL2); + + reg = readl(base + BC_CTRL0); + reg |= BC_CTRL0_BC_SUSPEND_N; + writel(reg, base + BC_CTRL0); + + writel((readl(base + BC_CTRL1) & ~BC_CTRL1_BC_MODE), base + BC_CTRL1); +} + +static void enable_vdp_src(struct hisi_dwc3_device *hisi_dwc3) +{ + void __iomem *base = hisi_dwc3->otg_bc_reg_base; + u32 reg; + + reg = readl(base + BC_CTRL2); + reg &= ~BC_CTRL2_BC_PHY_CHRGSEL; + reg |= (BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB); + writel(reg, base + BC_CTRL2); +} + +static enum hisi_charger_type detect_charger_type(struct hisi_dwc3_device + *hisi_dwc3) +{ + enum hisi_charger_type type = CHARGER_TYPE_NONE; + void __iomem *base = hisi_dwc3->otg_bc_reg_base; + u32 reg; + unsigned long jiffies_expire; + int i = 0; + + if (hisi_dwc3->fpga_flag) { + usb_dbg("this is fpga platform, charger is SDP\n"); + return CHARGER_TYPE_SDP; + } + + if (hisi_dwc3->fake_charger_type != CHARGER_TYPE_NONE) { + usb_dbg("fake type: %d\n", hisi_dwc3->fake_charger_type); + return hisi_dwc3->fake_charger_type; + } + + writel(BC_CTRL1_BC_MODE, base + BC_CTRL1); + + /* phy suspend */ + reg = readl(base + BC_CTRL0); + reg &= ~BC_CTRL0_BC_SUSPEND_N; + writel(reg, base + BC_CTRL0); + + /* enable DCD */ + reg = readl(base + BC_CTRL2); + reg |= BC_CTRL2_BC_PHY_DCDENB; + writel(reg, base + BC_CTRL2); + + reg = readl(base + BC_CTRL0); + reg |= BC_CTRL0_BC_DMPULLDOWN; + writel(reg, base + BC_CTRL0); + + jiffies_expire = jiffies + msecs_to_jiffies(900); + msleep(50); + while (1) { + reg = readl(base + BC_STS0); + if ((reg & BC_STS0_BC_PHY_FSVPLUS) == 0) { + i++; + if (i >= 10) + break; + } else { + i = 0; + } + + msleep(20); + + if (time_after(jiffies, jiffies_expire)) { + usb_dbg("DCD timeout!\n"); + type = CHARGER_TYPE_UNKNOWN; + break; + } + } + + reg = readl(base + BC_CTRL0); + reg &= ~BC_CTRL0_BC_DMPULLDOWN; + writel(reg, base + BC_CTRL0); + + /* disable DCD */ + reg = readl(base + BC_CTRL2); + reg &= ~BC_CTRL2_BC_PHY_DCDENB; + writel(reg, base + BC_CTRL2); + + usb_dbg("DCD done\n"); + + if (type == CHARGER_TYPE_NONE) { + /* enable vdect */ + reg = readl(base + BC_CTRL2); + reg &= ~BC_CTRL2_BC_PHY_CHRGSEL; + reg |= (BC_CTRL2_BC_PHY_VDATARCENB | + BC_CTRL2_BC_PHY_VDATDETENB); + writel(reg, base + BC_CTRL2); + + msleep(20); + + /* we can detect sdp or cdp dcp */ + reg = readl(base + BC_STS0); + if ((reg & BC_STS0_BC_PHY_CHGDET) == 0) + type = CHARGER_TYPE_SDP; + + /* disable vdect */ + reg = readl(base + BC_CTRL2); + reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB | + BC_CTRL2_BC_PHY_VDATDETENB); + writel(reg, base + BC_CTRL2); + } + + usb_dbg("Primary Detection done\n"); + + if (type == CHARGER_TYPE_NONE) { + /* enable vdect */ + reg = readl(base + BC_CTRL2); + reg |= (BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB + | BC_CTRL2_BC_PHY_CHRGSEL); + writel(reg, base + BC_CTRL2); + + msleep(20); + + /* we can detect sdp or cdp dcp */ + reg = readl(base + BC_STS0); + if ((reg & BC_STS0_BC_PHY_CHGDET) == 0) + type = CHARGER_TYPE_CDP; + else + type = CHARGER_TYPE_DCP; + + /* disable vdect */ + reg = readl(base + BC_CTRL2); + reg &= ~(BC_CTRL2_BC_PHY_VDATARCENB | BC_CTRL2_BC_PHY_VDATDETENB + | BC_CTRL2_BC_PHY_CHRGSEL); + writel(reg, base + BC_CTRL2); + } + + usb_dbg("Secondary Detection done\n"); + + /* If a PD detects that D+ is greater than VDAT_REF, it knows that it is + * attached to a DCP. It is then required to enable VDP_SRC or pull D+ + * to VDP_UP through RDP_UP + */ + if (type == CHARGER_TYPE_DCP) { + usb_dbg("charger is DCP, enable VDP_SRC\n"); + enable_vdp_src(hisi_dwc3); + } else { + /* bc_suspend = 1, nomal mode */ + reg = readl(base + BC_CTRL0); + reg |= BC_CTRL0_BC_SUSPEND_N; + writel(reg, base + BC_CTRL0); + + msleep(20); + + /* disable BC */ + writel((readl(base + BC_CTRL1) & ~BC_CTRL1_BC_MODE), + base + BC_CTRL1); + } + + usb_dbg("type: %d\n", type); + + return type; +} + +enum hisi_charger_type hisi_get_charger_type(void) +{ + if (!hisi_dwc3_dev) { + pr_err("[%s]hisi_dwc3 not yet probed!\n", __func__); + return CHARGER_TYPE_NONE; + } + + pr_info("[%s]type: %d\n", __func__, hisi_dwc3_dev->charger_type); + return hisi_dwc3_dev->charger_type; +} +EXPORT_SYMBOL_GPL(hisi_get_charger_type); + +static void notify_charger_type(struct hisi_dwc3_device *hisi_dwc3) +{ + atomic_notifier_call_chain(&hisi_dwc3->charger_type_notifier, + hisi_dwc3->charger_type, hisi_dwc3); +} + +static void set_vbus_power(struct hisi_dwc3_device *hisi_dwc3, + unsigned int is_on) +{ + enum hisi_charger_type new; + + if (is_on == 0) + new = CHARGER_TYPE_NONE; + else + new = PLEASE_PROVIDE_POWER; + if (hisi_dwc3->charger_type != new) { + usb_dbg("set port power %d\n", is_on); + hisi_dwc3->charger_type = new; + notify_charger_type(hisi_dwc3); + } +} + +static void hisi_dwc3_wake_lock(struct hisi_dwc3_device *hisi_dwc3) +{ + if (!(hisi_dwc3->ws.active)) { + usb_dbg("usb otg wake lock\n"); + __pm_stay_awake(&hisi_dwc3->ws); + } +} + +static void hisi_dwc3_wake_unlock(struct hisi_dwc3_device *hisi_dwc3) +{ + if (hisi_dwc3->ws.active) { + usb_dbg("usb otg wake unlock\n"); + __pm_relax(&hisi_dwc3->ws); + } +} + +static inline bool enumerate_allowed(struct hisi_dwc3_device *hisi_dwc) +{ + /* do not start peripheral if real charger connected */ + return ((hisi_dwc->charger_type == CHARGER_TYPE_SDP) || + (hisi_dwc->charger_type == CHARGER_TYPE_CDP) || + (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN)); +} + +static inline bool sleep_allowed(struct hisi_dwc3_device *hisi_dwc) +{ + return ((hisi_dwc->charger_type == CHARGER_TYPE_DCP) || + (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN)); +} + +/* + * create event queue + * event_queue: event queue handle + * count: set the queue max node + */ +int event_queue_creat(struct hiusb_event_queue *event_queue, unsigned int count) +{ + if (!event_queue) { + pr_err(" %s bad argument (0x%p)\n", + __func__, event_queue); + return -EINVAL; + } + + count = (count >= MAX_EVENT_COUNT ? MAX_EVENT_COUNT : count); + event_queue->max_event = count; + event_queue->num_event = (count >= EVENT_QUEUE_UNIT ? + EVENT_QUEUE_UNIT : count); + + event_queue->event = kzalloc( + (event_queue->num_event * + sizeof(enum otg_dev_event_type)), GFP_KERNEL); + if (!event_queue->event) { + pr_err(" %s :Can't alloc space:%d!\n", + __func__, event_queue->num_event); + return -ENOMEM; + } + + event_queue->enpos = 0; + event_queue->depos = 0; + event_queue->overlay = 0; + event_queue->overlay_index = 0; + + return 0; +} + +void event_queue_destroy(struct hiusb_event_queue *event_queue) +{ + if (!event_queue) + return; + + kfree(event_queue->event); + event_queue->event = NULL; + event_queue->enpos = 0; + event_queue->depos = 0; + event_queue->num_event = 0; + event_queue->max_event = 0; + event_queue->overlay = 0; + event_queue->overlay_index = 0; +} + +/* + * check if the queue is full + * return true means full, false is not. + */ +int event_queue_isfull(struct hiusb_event_queue *event_queue) +{ + if (!event_queue) + return -EINVAL; + + return (((event_queue->enpos + 1) % event_queue->num_event) == + (event_queue->depos)); +} + +/* + * check if the queue is full + * return true means empty, false or not. + */ +int event_queue_isempty(struct hiusb_event_queue *event_queue) +{ + if (!event_queue) + return -EINVAL; + + return (event_queue->enpos == event_queue->depos); +} + +static inline void event_queue_set_overlay( + struct hiusb_event_queue *event_queue) +{ + if (event_queue->overlay) + return; + event_queue->overlay = 1; + event_queue->overlay_index = event_queue->enpos; +} + +static inline void event_queue_clear_overlay( + struct hiusb_event_queue *event_queue) +{ + event_queue->overlay = 0; + event_queue->overlay_index = 0; +} + +/* + * put the new event en queue + * if the event_queue is full, return -ENOSPC + */ +int event_enqueue(struct hiusb_event_queue *event_queue, + enum otg_dev_event_type event) +{ + /* no need verify argument, isfull will check it */ + if (event_queue_isfull(event_queue)) { + pr_err("event queue full!\n"); + return -ENOSPC; + } + + if (event_queue->overlay) { + if (event_queue->overlay_index == event_queue->enpos) { + event_queue->enpos = ((event_queue->enpos + 1) % + event_queue->num_event); + } + + if (event_queue_isempty(event_queue)) { + pr_err("overlay and queue isempty? just enqueue!\n"); + event_queue->overlay_index = ( + (event_queue->overlay_index + 1) % + event_queue->num_event); + event_queue->enpos = ((event_queue->enpos + 1) % + event_queue->num_event); + event_queue->overlay = 0; + } + + event_queue->event[event_queue->overlay_index] = event; + } else { + event_queue->event[event_queue->enpos] = event; + event_queue->enpos = ((event_queue->enpos + 1) % + event_queue->num_event); + } + + return 0; +} + +/* + * get event from event_queue + * this function never return fail + * if the event_queue is empty, return NONE_EVENT + */ +enum otg_dev_event_type event_dequeue(struct hiusb_event_queue *event_queue) +{ + enum otg_dev_event_type event; + + /* no need verify argument, isempty will check it */ + if (event_queue_isempty(event_queue)) + return NONE_EVENT; + + event = event_queue->event[event_queue->depos]; + event_queue->depos = ((event_queue->depos + 1) % + event_queue->num_event); + + return event; +} + +static void handle_event(struct hisi_dwc3_device *hisi_dwc, + enum otg_dev_event_type event) +{ + int ret = 0; + + usb_err("[%s] type: %d\n", __func__, event); + switch (event) { + case CHARGER_CONNECT_EVENT: + if (hisi_dwc->state == USB_STATE_DEVICE) { + usb_dbg("Already in device mode, do nothing\n"); + } else if (hisi_dwc->state == USB_STATE_OFF) { + hisi_dwc->host_flag = 0; + + /* due to detect charger type, must resume hisi_dwc */ + ret = pm_runtime_get_sync(&hisi_dwc->pdev->dev); + if (ret < 0) { + usb_err("resume hisi_dwc failed (ret %d)\n", + ret); + return; + } + + /* detect charger type */ + hisi_dwc->charger_type = detect_charger_type(hisi_dwc); + notify_charger_type(hisi_dwc); + + /* In some cases, DCP is detected as SDP wrongly. + * To avoid this, start bc_again delay work to + * detect charger type once more. + * If later the enum process is executed, + * then it's a real SDP, so + * the work will be canceled. + */ + if (hisi_dwc->bc_again_flag && + (hisi_dwc->charger_type == CHARGER_TYPE_SDP)) { + ret = queue_delayed_work( + system_power_efficient_wq, + &hisi_dwc->bc_again_work, + msecs_to_jiffies(BC_AGAIN_DELAY_TIME)); + usb_dbg("schedule ret:%d, run bc_again_work %dms later\n", + ret, BC_AGAIN_DELAY_TIME); + } + + /* do not start peripheral if real charger connected */ + if (enumerate_allowed(hisi_dwc)) { + if (hisi_dwc->fpga_usb_mode_gpio > 0) { + gpio_direction_output( + hisi_dwc->fpga_usb_mode_gpio, + 0); + usb_dbg("switch to device mode\n"); + } + + /* start peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_VBUS_SET); + if (ret) { + pm_runtime_put(&hisi_dwc->pdev->dev); + hisi_dwc3_wake_unlock(hisi_dwc); + usb_err("start peripheral error\n"); + return; + } + } else { + usb_dbg("a real charger connected\n"); + } + + hisi_dwc->state = USB_STATE_DEVICE; + + if (sleep_allowed(hisi_dwc)) + hisi_dwc3_wake_unlock(hisi_dwc); + else + hisi_dwc3_wake_lock(hisi_dwc); + + usb_dbg("hisi usb status: OFF -> DEVICE\n"); + } else if (hisi_dwc->state == USB_STATE_HOST) { + usb_dbg("Charger connect interrupt in HOST mode\n"); + } + + break; + + case CHARGER_DISCONNECT_EVENT: + hisi_dwc->need_disable_vdp = 0; + + if (hisi_dwc->state == USB_STATE_OFF) { + usb_dbg("Already in off mode, do nothing\n"); + } else if (hisi_dwc->state == USB_STATE_DEVICE) { + if (hisi_dwc->bc_again_flag) { + ret = cancel_delayed_work_sync( + &hisi_dwc->bc_again_work); + usb_dbg("cancel bc_again_work sync:%d\n", ret); + } + + /* peripheral not started, if real charger connected */ + if (enumerate_allowed(hisi_dwc)) { + /* stop peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_VBUS_CLEAR); + if (ret) { + usb_err("stop peripheral error\n"); + return; + } + } else { + usb_dbg("connected is a real charger\n"); + disable_vdp_src(hisi_dwc); + } + + /* usb cable disconnect, notify no charger */ + hisi_dwc->charger_type = CHARGER_TYPE_NONE; + notify_charger_type(hisi_dwc); + + hisi_dwc->state = USB_STATE_OFF; + hisi_dwc3_wake_unlock(hisi_dwc); + pm_runtime_put(&hisi_dwc->pdev->dev); + + usb_dbg("hisi usb status: DEVICE -> OFF\n"); + } else if (hisi_dwc->state == USB_STATE_HOST) { + usb_dbg("Charger disconnect interrupt in HOST mode\n"); + } + + break; + + case ID_FALL_EVENT: + if (hisi_dwc->state == USB_STATE_OFF) { + set_vbus_power(hisi_dwc, 1); + + hisi_dwc->host_flag = 1; + + if (hisi_dwc->fpga_usb_mode_gpio > 0) { + gpio_direction_output( + hisi_dwc->fpga_usb_mode_gpio, + 1); + usb_dbg("switch to host mode\n"); + } + + /* start host */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_ID_CLEAR); + if (ret) { + usb_err("start host error\n"); + set_vbus_power(hisi_dwc, 0); + return; + } + + hisi_dwc->state = USB_STATE_HOST; + hisi_dwc3_wake_lock(hisi_dwc); + + usb_dbg("hisi usb_status: OFF -> HOST\n"); + } else if (hisi_dwc->state == USB_STATE_DEVICE) { + usb_dbg("id fall interrupt in DEVICE mode\n"); + } else if (hisi_dwc->state == USB_STATE_HOST) { + usb_dbg("Already in host mode, do nothing\n"); + } + break; + case ID_RISE_EVENT: + if (hisi_dwc->state == USB_STATE_HOST) { + set_vbus_power(hisi_dwc, 0); + + /* stop host */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_ID_SET); + if (ret) { + usb_err("stop host error\n"); + return; + } + + hisi_dwc->state = USB_STATE_OFF; + hisi_dwc3_wake_unlock(hisi_dwc); + + usb_dbg("hiusb_status: HOST -> OFF\n"); + } else if (hisi_dwc->state == USB_STATE_DEVICE) { + usb_dbg("id rise interrupt in DEVICE mode\n"); + } else if (hisi_dwc->state == USB_STATE_OFF) { + usb_dbg("Already in host mode, do nothing\n"); + } + + break; + default: + usb_dbg("illegal event type!\n"); + break; + } +} + +static void event_work(struct work_struct *work) +{ + unsigned long flags; + enum otg_dev_event_type event; + struct hisi_dwc3_device *hisi_dwc = container_of(work, + struct hisi_dwc3_device, event_work); + + mutex_lock(&hisi_dwc->lock); + + usb_dbg("+\n"); + + while (!event_queue_isempty(&hisi_dwc->event_queue)) { + spin_lock_irqsave(&hisi_dwc->event_lock, flags); + event = event_dequeue(&hisi_dwc->event_queue); + spin_unlock_irqrestore(&hisi_dwc->event_lock, flags); + + handle_event(hisi_dwc, event); + } + + event_queue_clear_overlay(&hisi_dwc->event_queue); + + usb_dbg("-\n"); + mutex_unlock(&hisi_dwc->lock); +} + +static int event_check(enum otg_dev_event_type last_event, + enum otg_dev_event_type new_event) +{ + int ret = 0; + + if (last_event == NONE_EVENT) + return 1; + + switch (new_event) { + case CHARGER_CONNECT_EVENT: + if ((last_event == CHARGER_DISCONNECT_EVENT) || + (last_event == ID_RISE_EVENT)) + ret = 1; + break; + case CHARGER_DISCONNECT_EVENT: + if (last_event == CHARGER_CONNECT_EVENT) + ret = 1; + break; + case ID_FALL_EVENT: + if ((last_event == CHARGER_DISCONNECT_EVENT) || + (last_event == ID_RISE_EVENT)) + ret = 1; + break; + case ID_RISE_EVENT: + if (last_event == ID_FALL_EVENT) + ret = 1; + break; + default: + break; + } + return ret; +} + +int hisi_usb_otg_event(enum otg_dev_event_type event) +{ + int ret = 0; +#ifdef CONFIG_USB_DWC3_OTG + unsigned long flags; + struct hisi_dwc3_device *hisi_dwc3 = hisi_dwc3_dev; +#endif + usb_err("%s in:%d\n", __func__, event); +#ifdef CONFIG_USB_DWC3_OTG + usb_err("%s in otg:%d\n", __func__, event); + + if (!hisi_dwc3) { + usb_dbg(" %s error:%d\n", __func__, event); + return -EBUSY; + } + + if (hisi_dwc3->eventmask) { + usb_dbg("eventmask enabled, mask all events.\n"); + return ret; + } + + spin_lock_irqsave(&hisi_dwc3->event_lock, flags); + + if (event_check(hisi_dwc3->event, event)) { + usb_dbg("event: %d\n", event); + hisi_dwc3->event = event; + + if ((event == CHARGER_CONNECT_EVENT) || + (event == CHARGER_DISCONNECT_EVENT)) + hisi_dwc3_wake_lock(hisi_dwc3); + + if (!event_enqueue(&hisi_dwc3->event_queue, event)) { + ret = queue_work(system_power_efficient_wq, + &hisi_dwc3->event_work); + if (!ret) + usb_err("schedule event_work wait:%d]\n", + event); + } else { + usb_err("%s can't enqueue event:%d\n", + __func__, event); + } + + if ((event == ID_RISE_EVENT) || + (event == CHARGER_DISCONNECT_EVENT)) + event_queue_set_overlay(&hisi_dwc3->event_queue); + } + spin_unlock_irqrestore(&hisi_dwc3->event_lock, flags); +#endif + return ret; +} +EXPORT_SYMBOL_GPL(hisi_usb_otg_event); + +static void bc_again(struct hisi_dwc3_device *hisi_dwc) +{ + int ret; + + /* + * STEP 1 + */ + /* stop peripheral which is started when detected as SDP before */ + if (enumerate_allowed(hisi_dwc)) { + ret = dwc3_otg_work(dwc_otg_handler, DWC3_OTG_EVT_VBUS_CLEAR); + if (ret) { + usb_err("stop peripheral error\n"); + return; + } + } + + /* + * STEP 2 + */ + hisi_dwc->charger_type = detect_charger_type(hisi_dwc); + notify_charger_type(hisi_dwc); + + /* + * STEP 3 + */ + /* must recheck enumerate_allowed, because charger_type maybe changed, + * and enumerate_allowed according to charger_type + */ + if (enumerate_allowed(hisi_dwc)) { + /* start peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_VBUS_SET); + if (ret) { + pm_runtime_put(&hisi_dwc->pdev->dev); + hisi_dwc3_wake_unlock(hisi_dwc); + usb_err("start peripheral error\n"); + return; + } + } else { + usb_dbg("a real charger connected\n"); + } +} + +void hisi_usb_otg_bc_again(void) +{ + struct hisi_dwc3_device *hisi_dwc = hisi_dwc3_dev; + + usb_dbg("+\n"); + + if (!hisi_dwc) { + usb_err("No usb module, can't call bc again api\n"); + return; + } + + mutex_lock(&hisi_dwc->lock); + + /* we are here because it's detected as SDP before */ + if (hisi_dwc->charger_type == CHARGER_TYPE_UNKNOWN) { + usb_dbg("charger_type is UNKNOWN, start bc_again_work\n"); + bc_again(hisi_dwc); + } + + mutex_unlock(&hisi_dwc->lock); + usb_dbg("-\n"); +} +EXPORT_SYMBOL_GPL(hisi_usb_otg_bc_again); + +static void bc_again_work(struct work_struct *work) +{ + struct hisi_dwc3_device *hisi_dwc = container_of(work, + struct hisi_dwc3_device, bc_again_work.work); + + usb_dbg("+\n"); + mutex_lock(&hisi_dwc->lock); + + /* we are here because it's detected as SDP before */ + if (hisi_dwc->charger_type == CHARGER_TYPE_SDP) { + usb_dbg("charger_type is SDP, start %s\n", __func__); + bc_again(hisi_dwc); + } + + mutex_unlock(&hisi_dwc->lock); + usb_dbg("-\n"); +} + +static int conndone_notifier_fn(struct notifier_block *nb, + unsigned long action, void *data) +{ + int ret; + struct hisi_dwc3_device *hisi_dwc = container_of(nb, + struct hisi_dwc3_device, conndone_nb); + + ret = cancel_delayed_work(&hisi_dwc->bc_again_work); + usb_dbg("cancel bc_again_work:%d\n", ret); + + return 0; +} + +/** + * get_usb_state() - get current USB cable state. + * @hisi_dwc: the instance pointer of struct hisi_dwc3_device + * + * return current USB cable state according to VBUS status and ID status. + */ +static enum hisi_usb_state get_usb_state(struct hisi_dwc3_device *hisi_dwc) +{ + if (hisi_dwc->fpga_flag) { + usb_dbg("this is fpga platform, usb is device mode\n"); + return USB_STATE_DEVICE; + } + + if (dwc3_otg_id_value(dwc_otg_handler) == 0) + return USB_STATE_HOST; + else + return USB_STATE_OFF; +} + +static void get_phy_param(struct hisi_dwc3_device *hisi_dwc3) +{ + struct device *dev = &hisi_dwc3->pdev->dev; + + /* hs phy param for device mode */ + if (of_property_read_u32(dev->of_node, "eye_diagram_param", + &hisi_dwc3->eye_diagram_param)) { + usb_dbg("get eye diagram param form dt failed, use default value\n"); + hisi_dwc3->eye_diagram_param = 0x1c466e3; + } + usb_dbg("eye diagram param: 0x%x\n", hisi_dwc3->eye_diagram_param); + + /* hs phy param for host mode */ + if (of_property_read_u32(dev->of_node, "eye_diagram_host_param", + &hisi_dwc3->eye_diagram_host_param)) { + usb_dbg("get eye diagram host param form dt failed, use default value\n"); + hisi_dwc3->eye_diagram_host_param = 0x1c466e3; + } + usb_dbg("eye diagram host param: 0x%x\n", + hisi_dwc3->eye_diagram_host_param); + + /* ss phy Rx Equalization */ + if (of_property_read_u32(dev->of_node, "usb3_phy_cr_param", + &hisi_dwc3->usb3_phy_cr_param)) { + usb_dbg("get usb3_phy_cr_param form dt failed, use default value\n"); + hisi_dwc3->usb3_phy_cr_param = (1 << 11) | (3 << 8) | (1 << 7); + } + + /* ss phy Rx Equalization for host mode */ + if (of_property_read_u32(dev->of_node, "usb3_phy_host_cr_param", + &hisi_dwc3->usb3_phy_host_cr_param)) { + usb_dbg("get usb3_phy_host_cr_param form dt failed, use default value\n"); + hisi_dwc3->usb3_phy_host_cr_param = + (1 << 11) | (1 << 8) | (1 << 7); + } + + usb_dbg("usb3_phy_cr_param: 0x%x\n", hisi_dwc3->usb3_phy_cr_param); + usb_dbg("usb3_phy_host_cr_param: 0x%x\n", + hisi_dwc3->usb3_phy_host_cr_param); + + /* tx_vboost_lvl */ + if (of_property_read_u32(dev->of_node, "usb3_phy_tx_vboost_lvl", + &hisi_dwc3->usb3_phy_tx_vboost_lvl)) { + usb_dbg("get usb3_phy_tx_vboost_lvl form dt failed, use default value\n"); + hisi_dwc3->usb3_phy_tx_vboost_lvl = 5; + } + usb_dbg("usb3_phy_tx_vboost_lvl: %d\n", + hisi_dwc3->usb3_phy_tx_vboost_lvl); +} + +/** + * get_resource() - prepare resources + * @hisi_dwc3: the instance pointer of struct hisi_dwc3_device + * + * 1. get registers base address and map registers region. + * 2. get regulator handler. + */ +static int get_resource(struct hisi_dwc3_device *hisi_dwc3) +{ + struct device *dev = &hisi_dwc3->pdev->dev; + struct resource *res; + struct device_node *np; + + /* + * map PERI CRG region + */ + np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-crgctrl"); + if (!np) { + dev_err(dev, "get peri cfg node failed!\n"); + return -EINVAL; + } + hisi_dwc3->pericfg_reg_base = of_iomap(np, 0); + if (!hisi_dwc3->pericfg_reg_base) { + dev_err(dev, "iomap pericfg_reg_base failed!\n"); + return -EINVAL; + } + + /* + * map PCTRL region + */ + np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-pctrl"); + if (!np) { + dev_err(dev, "get pctrl node failed!\n"); + return -EINVAL; + } + hisi_dwc3->pctrl_reg_base = of_iomap(np, 0); + if (!hisi_dwc3->pctrl_reg_base) { + dev_err(dev, "iomap pctrl_reg_base failed!\n"); + return -EINVAL; + } + + /* + * map SCTRL region + */ + np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-sctrl"); + if (!np) { + dev_err(dev, "get sysctrl node failed!\n"); + return -EINVAL; + } + hisi_dwc3->sctrl_reg_base = of_iomap(np, 0); + if (!hisi_dwc3->sctrl_reg_base) { + dev_err(dev, "iomap sctrl_reg_base failed!\n"); + return -EINVAL; + } + + /* + * map PMCTRL region + */ + np = of_find_compatible_node(NULL, NULL, "hisilicon,hi3660-pmctrl"); + if (!np) { + dev_err(dev, "get pmctrl node failed!\n"); + return -EINVAL; + } + + /* + * map OTG BC region + */ + res = platform_get_resource(hisi_dwc3->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + return -EINVAL; + } + + hisi_dwc3->otg_bc_reg_base = devm_ioremap_nocache( + dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(hisi_dwc3->otg_bc_reg_base)) { + dev_err(dev, "ioremap res 0 failed\n"); + return -ENOMEM; + } + + get_phy_param(hisi_dwc3); + + /* get abb clk handler */ + hisi_dwc3->clk = devm_clk_get(&hisi_dwc3->pdev->dev, "clk_usb3phy_ref"); + if (IS_ERR_OR_NULL(hisi_dwc3->clk)) { + dev_err(dev, "get usb3phy ref clk failed\n"); + return -EINVAL; + } + + /* get h clk handler */ + hisi_dwc3->gt_aclk_usb3otg = devm_clk_get( + &hisi_dwc3->pdev->dev, "aclk_usb3otg"); + if (IS_ERR_OR_NULL(hisi_dwc3->gt_aclk_usb3otg)) { + dev_err(dev, "get aclk_usb3otg failed\n"); + return -EINVAL; + } + + /* judge fpga platform or not, from dts */ + if (of_property_read_u32(dev->of_node, "fpga_flag", + &hisi_dwc3->fpga_flag)) { + hisi_dwc3->fpga_flag = 0; + } + usb_dbg("this is %s platform (fpga flag %d)\n", + hisi_dwc3->fpga_flag ? "fpga" : "asic", hisi_dwc3->fpga_flag); + + hisi_dwc3->fpga_usb_mode_gpio = -1; + + if (of_property_read_u32(dev->of_node, "bc_again_flag", + &hisi_dwc3->bc_again_flag)) { + hisi_dwc3->bc_again_flag = 0; + } + + return 0; +} + +static int hisi_dwc3_phy_init(struct hisi_dwc3_device *hisi_dwc) +{ + return hisi_dwc->phy_ops->init(hisi_dwc); +} + +static int hisi_dwc3_phy_shutdown(struct hisi_dwc3_device *hisi_dwc) +{ + return hisi_dwc->phy_ops->shutdown(hisi_dwc); +} + +int hisi_dwc3_probe(struct platform_device *pdev, + struct usb3_phy_ops *phy_ops) +{ + int ret; + struct hisi_dwc3_device *hisi_dwc; + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + enum hisi_usb_state init_state; + + usb_dbg("+\n"); + + if (!phy_ops) { + usb_err("phy_ops is NULL\n"); + return -EINVAL; + } + + hisi_dwc = devm_kzalloc(dev, sizeof(*hisi_dwc), GFP_KERNEL); + if (!hisi_dwc) + return -ENOMEM; + + platform_set_drvdata(pdev, hisi_dwc); + hisi_dwc->pdev = pdev; + hisi_dwc->phy_ops = phy_ops; + + hisi_dwc3_dev = hisi_dwc; + + /* + * set hisi dwc3 dma mask, it should be 0xffffffff, because the ahb + * master of usb can only support 32bit width address. + */ + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_BIT_MASK(32); + + /* + * get resources from dts. + */ + ret = get_resource(hisi_dwc); + if (ret) { + dev_err(&pdev->dev, "get resource failed!\n"); + return ret; + } + + if (hisi_dwc->fpga_usb_mode_gpio > 0) { + ret = gpio_request(hisi_dwc->fpga_usb_mode_gpio, NULL); + if (ret) { + /* request gpio failure! */ + usb_err("request gpio %d failed, ret=[%d]\n", + hisi_dwc->fpga_usb_mode_gpio, ret); + } + } + + /* create sysfs files. */ + ret = create_attr_file(dev); + if (ret) { + dev_err(&pdev->dev, "create_attr_file failed!\n"); + return ret; + } + + /* initialize */ + hisi_dwc->charger_type = CHARGER_TYPE_SDP; + hisi_dwc->fake_charger_type = CHARGER_TYPE_NONE; + hisi_dwc->event = NONE_EVENT; + hisi_dwc->host_flag = 0; + hisi_dwc->eventmask = 0; + spin_lock_init(&hisi_dwc->event_lock); + INIT_WORK(&hisi_dwc->event_work, event_work); + mutex_init(&hisi_dwc->lock); + wakeup_source_init(&hisi_dwc->ws, "usb_wake_lock"); + ATOMIC_INIT_NOTIFIER_HEAD(&hisi_dwc->charger_type_notifier); + event_queue_creat(&hisi_dwc->event_queue, MAX_EVENT_COUNT); + hisi_dwc->disable_vdp_src = disable_vdp_src; + hisi_dwc->need_disable_vdp = 0; + + /* power on */ + hisi_dwc->is_regu_on = 0; + ret = hisi_dwc3_phy_init(hisi_dwc); + if (ret) { + dev_err(&pdev->dev, "%s: hisi_dwc3_phy_init failed!\n", + __func__); + remove_attr_file(dev); + return ret; + } + + if (hisi_dwc->bc_again_flag) { + INIT_DELAYED_WORK(&hisi_dwc->bc_again_work, bc_again_work); + hisi_dwc->conndone_nb.notifier_call = conndone_notifier_fn; + ret = dwc3_conndone_notifier_register(&hisi_dwc->conndone_nb); + if (ret) + usb_err("dwc3_conndone_notifier_register failed\n"); + } + + if (hisi_dwc->charger_type == CHARGER_TYPE_CDP) { + usb_dbg("it needs enable VDP_SRC while detect CDP!\n"); + hisi_dwc->need_disable_vdp = 1; + enable_vdp_src(hisi_dwc); + } + + /* + * enable runtime pm. + */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + pm_runtime_forbid(dev); + + /* + * probe child deivces + */ + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + pr_err("%s: register dwc3 failed!\n", __func__); + goto err1; + } + +#ifdef CONFIG_USB_DWC3_OTG + /* default device state */ + hisi_dwc->state = USB_STATE_DEVICE; + + if (sleep_allowed(hisi_dwc)) + hisi_dwc3_wake_unlock(hisi_dwc); + else + hisi_dwc3_wake_lock(hisi_dwc); + + if (!enumerate_allowed(hisi_dwc)) { + /* stop peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, DWC3_OTG_EVT_VBUS_CLEAR); + if (ret) + usb_err("stop peripheral error\n"); + } + + /* balance the put operation when disconnect */ + pm_runtime_get(dev); + + hisi_dwc->event = CHARGER_CONNECT_EVENT; + init_state = get_usb_state(hisi_dwc); + if (init_state == USB_STATE_OFF) { + usb_dbg("init state: OFF\n"); + hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT); + } else if (init_state == USB_STATE_HOST) { + usb_dbg("init state: HOST\n"); + hisi_usb_otg_event(CHARGER_DISCONNECT_EVENT); + msleep(500); + hisi_usb_otg_event(ID_FALL_EVENT); + } +#endif + + pm_runtime_put_sync(dev); + pm_runtime_allow(dev); + + usb_dbg("-\n"); + + return 0; + +err1: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + remove_attr_file(dev); + + return ret; +} + +static int hisi_dwc3_remove_child(struct device *dev, void *unused) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + return 0; +} + +int hisi_dwc3_remove(struct platform_device *pdev) +{ + struct hisi_dwc3_device *hisi_dwc3 = platform_get_drvdata(pdev); + int ret; + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return -EBUSY; + } + + device_for_each_child(&pdev->dev, NULL, hisi_dwc3_remove_child); + pm_runtime_disable(&pdev->dev); + + if (hisi_dwc3->bc_again_flag) { + dwc3_conndone_notifier_unregister(&hisi_dwc3->conndone_nb); + hisi_dwc3->conndone_nb.notifier_call = NULL; + } + + ret = hisi_dwc3_phy_shutdown(hisi_dwc3); + if (ret) + usb_err("hisi_dwc3_phy_shutdown error\n"); + hisi_dwc3->phy_ops = NULL; + + event_queue_destroy(&hisi_dwc3->event_queue); + + remove_attr_file(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP +static int hisi_dwc3_prepare(struct device *dev) +{ + struct hisi_dwc3_device *hisi_dwc = platform_get_drvdata( + to_platform_device(dev)); + int ret = 0; + + if (!hisi_dwc) + return -ENODEV; + + mutex_lock(&hisi_dwc->lock); + + switch (hisi_dwc->state) { + case USB_STATE_OFF: + pr_info("%s: off state.\n", __func__); + break; + case USB_STATE_DEVICE: + pr_info("%s: device state.\n", __func__); + + if (enumerate_allowed(hisi_dwc)) { + /* stop peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_VBUS_CLEAR); + if (ret) { + usb_err("stop peripheral error\n"); + goto error; + } + } else { + usb_dbg("connected is a real charger\n"); + disable_vdp_src(hisi_dwc); + } + + break; + case USB_STATE_HOST: + usb_err("%s: host mode, should not go to sleep!\n", __func__); + ret = -EFAULT; + goto error; + default: + pr_err("%s: ilegal state!\n", __func__); + ret = -EFAULT; + goto error; + } + + return ret; +error: + mutex_unlock(&hisi_dwc->lock); + return ret; +} + +static void hisi_dwc3_complete(struct device *dev) +{ + struct hisi_dwc3_device *hisi_dwc = platform_get_drvdata( + to_platform_device(dev)); + int ret = 0; + + if (!hisi_dwc) { + usb_err("hisi_dwc NULL !\n"); + return; + } + + switch (hisi_dwc->state) { + case USB_STATE_OFF: + usb_dbg("%s: off state.\n", __func__); + break; + case USB_STATE_DEVICE: + usb_dbg("%s: device state.\n", __func__); + + /* update charger type */ + hisi_dwc->charger_type = detect_charger_type(hisi_dwc); + if (sleep_allowed(hisi_dwc)) + hisi_dwc3_wake_unlock(hisi_dwc); + else + hisi_dwc3_wake_lock(hisi_dwc); + + /* do not start peripheral if real charger connected */ + if (enumerate_allowed(hisi_dwc)) { + /* start peripheral */ + ret = dwc3_otg_work(dwc_otg_handler, + DWC3_OTG_EVT_VBUS_SET); + if (ret) { + usb_err("start peripheral error\n"); + hisi_dwc->state = USB_STATE_OFF; + pm_runtime_put(&hisi_dwc->pdev->dev); + goto error; + } + } else { + usb_dbg("a real charger connected\n"); + } + + break; + case USB_STATE_HOST: + usb_err("%s: host mode, should not go to sleep!\n", __func__); + break; + default: + usb_err("%s: ilegal state!\n", __func__); + break; + } + +error: + mutex_unlock(&hisi_dwc->lock); +} + +static int hisi_dwc3_suspend(struct device *dev) +{ + struct hisi_dwc3_device *hisi_dwc3 = + platform_get_drvdata(to_platform_device(dev)); + int ret = 0; + + usb_dbg("+\n"); + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return -EBUSY; + } + + if (hisi_dwc3->runtime_suspended) { + usb_dbg("runtime_suspended\n"); + } else { + ret = hisi_dwc3_phy_shutdown(hisi_dwc3); + if (ret) + usb_err("hisi_dwc3_phy_shutdown failed\n"); + } + + usb_dbg("-\n"); + + return ret; +} + +static int hisi_dwc3_resume(struct device *dev) +{ + struct hisi_dwc3_device *hisi_dwc3 = + platform_get_drvdata(to_platform_device(dev)); + int ret = 0; + + usb_dbg("+\n"); + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return -EBUSY; + } + + if (hisi_dwc3->runtime_suspended) { + usb_dbg("runtime_suspended\n"); + } else { + ret = hisi_dwc3_phy_init(hisi_dwc3); + if (ret) + usb_err("hisi_dwc3_phy_init failed\n"); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + usb_dbg("-\n"); + + return ret; +} +#endif + +static int hisi_dwc3_runtime_suspend(struct device *dev) +{ + int ret; + struct hisi_dwc3_device *hisi_dwc3 = + platform_get_drvdata(to_platform_device(dev)); + + usb_dbg("+\n"); + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return -EBUSY; + } + + ret = hisi_dwc3_phy_shutdown(hisi_dwc3); + if (ret) + return ret; + hisi_dwc3->runtime_suspended = 1; + usb_dbg("-\n"); + + return 0; +} + +static int hisi_dwc3_runtime_resume(struct device *dev) +{ + int ret = 0; + struct hisi_dwc3_device *hisi_dwc3 = + platform_get_drvdata(to_platform_device(dev)); + + usb_dbg("+\n"); + + if (!hisi_dwc3) { + usb_err("hisi_dwc3 NULL\n"); + return -EBUSY; + } + + ret = hisi_dwc3_phy_init(hisi_dwc3); + if (ret) + return ret; + hisi_dwc3->runtime_suspended = 0; + usb_dbg("-\n"); + + return ret; +} + +static int hisi_dwc3_runtime_idle(struct device *dev) +{ + int ret; + + usb_dbg("+\n"); + ret = pm_runtime_autosuspend(dev); + if (ret) + dev_err(dev, "pm_runtime_autosuspend error\n"); + usb_dbg("-\n"); + + return ret; +} + +const struct dev_pm_ops hisi_dwc3_dev_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .prepare = hisi_dwc3_prepare, + .complete = hisi_dwc3_complete, + SET_SYSTEM_SLEEP_PM_OPS(hisi_dwc3_suspend, hisi_dwc3_resume) +#endif + SET_RUNTIME_PM_OPS(hisi_dwc3_runtime_suspend, hisi_dwc3_runtime_resume, + hisi_dwc3_runtime_idle) +}; +#endif + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>"); diff --git a/drivers/usb/dwc3/dwc3-hisi.h b/drivers/usb/dwc3/dwc3-hisi.h new file mode 100644 index 000000000000..f497baff563a --- /dev/null +++ b/drivers/usb/dwc3/dwc3-hisi.h @@ -0,0 +1,293 @@ +/* + * hisi_usb_vbus.h + * + * Copyright: (C) 2008-2018 hisilicon. + * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx> + * + * USB vbus for Hisilicon device + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + */ +#ifndef _DWC3_HISI_H_ +#define _DWC3_HISI_H_ + +#include <linux/pm_wakeup.h> +#include <linux/clk.h> +#include <linux/hisi/usb/hisi_usb.h> +#include <linux/regulator/consumer.h> + +#define REG_BASE_PERI_CRG (0xFFF35000) +#define PERI_CRG_CLK_EN4 (0x40) +#define PERI_CRG_CLK_DIS4 (0x44) +#define PERI_CRG_RSTDIS4 (0x94) +#define PERI_CRG_RSTEN4 (0x90) +#define PERI_CRG_ISODIS (0x148) +#define PERI_CRG_ISOSTAT (0x14C) +#define STCL_ADDR (0xFFF0A214) +#ifndef BIT +#define BIT(x) (1 << (x)) +#endif +#define PERI_CRG_ISOSTAT_MODEMSUBSYSISOEN BIT(4) +#define PERI_CRG_ISODIS_MODEMSUBSYSISOEN BIT(4) + +#define PCTRL_PERI_CTRL24 (0x64) +#define PCTRL_PERI_CTRL48 (0xC54) + +#define IP_RST_USB3OTG_MUX BIT(8) +#define IP_RST_USB3OTG_AHBIF BIT(7) +#define IP_RST_USB3OTG_32K BIT(6) +#define IP_RST_USB3OTG BIT(5) +#define IP_RST_USB3OTGPHY_POR BIT(3) + +#define GT_CLK_USB3OTG_REF BIT(0) +#define GT_ACLK_USB3OTG BIT(1) +#define GT_CLK_USB3PHY_REF BIT(2) + +/* + * hisi dwc3 phy registers + */ +#define DWC3_PHY_RX_OVRD_IN_HI 0x1006 +#define DWC3_PHY_RX_SCOPE_VDCC 0x1026 + +/* DWC3_PHY_RX_SCOPE_VDCC */ +#define RX_SCOPE_LFPS_EN BIT(0) + +/* + * hisi dwc3 otg bc registers + */ +#define USBOTG3_CTRL0 0x00 +#define USBOTG3_CTRL1 0x04 +#define USBOTG3_CTRL2 0x08 +#define USBOTG3_CTRL3 0x0C +#define USBOTG3_CTRL4 0x10 +#define USBOTG3_CTRL5 0x14 +#define USBOTG3_CTRL6 0x18 +#define USBOTG3_CTRL7 0x1C +#define USBOTG3_STS0 0x20 +#define USBOTG3_STS1 0x24 +#define USBOTG3_STS2 0x28 +#define USBOTG3_STS3 0x2C +#define BC_CTRL0 0x30 +#define BC_CTRL1 0x34 +#define BC_CTRL2 0x38 +#define BC_STS0 0x3C +#define RAM_CTRL 0x40 +#define USBOTG3_STS4 0x44 +#define USB3PHY_CTRL 0x48 +#define USB3PHY_STS 0x4C +#define USB3PHY_CR_STS 0x50 +#define USB3PHY_CR_CTRL 0x54 +#define USB3_RES 0x58 + +/* USTOTG3_CTRL0 */ +# define USBOTG3CTRL0_SESSVLD_SEL BIT(14) +# define USBOTG3CTRL0_SC_SESSVLD BIT(13) +# define USBOTG3CTRL0_POWERPRESENT_SEL BIT(12) +# define USBOTG3CTRL0_SC_POWERPRESENT BIT(11) +# define USBOTG3CTRL0_BVALID_SEL BIT(10) +# define USBOTG3CTRL0_SC_BVALID BIT(9) +# define USBOTG3CTRL0_AVALID_SEL BIT(8) +# define USBOTG3CTRL0_SC_AVALID BIT(7) +# define USBOTG3CTRL0_VBUSVALID_SEL BIT(6) +# define USBOTG3CTRL0_DRVVBUS BIT(5) +# define USBOTG3CTRL0_DRVVBUS_SEL BIT(4) +# define USBOTG3CTRL0_IDDIG BIT(3) +# define USBOTG3CTRL0_IDDIG_SEL BIT(2) +# define USBOTG3CTRL0_IDPULLUP BIT(1) +# define USBOTG3CTRL0_IDPULLUP_SEL BIT(0) + +/* USTOTG3_CTRL2 */ +# define USBOTG3CTRL2_POWERDOWN_HSP BIT(0) +# define USBOTG3CTRL2_POWERDOWN_SSP BIT(1) + +/* USBOTG3_CTRL3 */ +# define USBOTG3_CTRL3_VBUSVLDEXT BIT(6) +# define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5) +# define USBOTG3_CTRL3_TXBITSTUFFEHN BIT(4) +# define USBOTG3_CTRL3_TXBITSTUFFEN BIT(3) +# define USBOTG3_CTRL3_RETENABLEN BIT(2) +# define USBOTG3_CTRL3_OTGDISABLE BIT(1) +# define USBOTG3_CTRL3_COMMONONN BIT(0) + +/* USBOTG3_CTRL4 */ +# define USBOTG3_CTRL4_TXVREFTUNE(x) (((x) << 22) & (0xf << 22)) +# define USBOTG3_CTRL4_TXRISETUNE(x) (((x) << 20) & (3 << 20)) +# define USBOTG3_CTRL4_TXRESTUNE(x) (((x) << 18) & (3 << 18)) +# define USBOTG3_CTRL4_TXPREEMPPULSETUNE BIT(17) +# define USBOTG3_CTRL4_TXPREEMPAMPTUNE(x) (((x) << 15) & (3 << 15)) +# define USBOTG3_CTRL4_TXHSXVTUNE(x) (((x) << 13) & (3 << 13)) +# define USBOTG3_CTRL4_TXFSLSTUNE(x) (((x) << 9) & (0xf << 9)) +# define USBOTG3_CTRL4_SQRXTUNE(x) (((x) << 6) & (7 << 6)) +# define USBOTG3_CTRL4_OTGTUNE_MASK (7 << 3) +# define USBOTG3_CTRL4_OTGTUNE(x) \ +(((x) << 3) & USBOTG3_CTRL4_OTGTUNE_MASK) +# define USBOTG3_CTRL4_COMPDISTUNE_MASK 7 +# define USBOTG3_CTRL4_COMPDISTUNE(x) \ +((x) & USBOTG3_CTRL4_COMPDISTUNE_MASK) + +# define USBOTG3_CTRL7_REF_SSP_EN BIT(16) + +/* USBOTG3_CTRL6 */ +#define TX_VBOOST_LVL_MASK 7 +#define TX_VBOOST_LVL(x) ((x) & TX_VBOOST_LVL_MASK) + +/* BC_CTRL0 */ +# define BC_CTRL0_BC_IDPULLUP BIT(10) +# define BC_CTRL0_BC_SUSPEND_N BIT(9) +# define BC_CTRL0_BC_DMPULLDOWN BIT(8) +# define BC_CTRL0_BC_DPPULLDOWN BIT(7) +# define BC_CTRL0_BC_TXVALIDH BIT(6) +# define BC_CTRL0_BC_TXVALID BIT(5) +# define BC_CTRL0_BC_TERMSELECT BIT(4) +# define BC_CTRL0_BC_XCVRSELECT(x) (((x) << 2) & (3 << 2)) +# define BC_CTRL0_BC_OPMODE(x) ((x) & 3) + +/* BC_CTRL1 */ +# define BC_CTRL1_BC_MODE 1 + +/* BC_CTRL2 */ +# define BC_CTRL2_BC_PHY_VDATDETENB BIT(4) +# define BC_CTRL2_BC_PHY_VDATARCENB BIT(3) +# define BC_CTRL2_BC_PHY_CHRGSEL BIT(2) +# define BC_CTRL2_BC_PHY_DCDENB BIT(1) +# define BC_CTRL2_BC_PHY_ACAENB BIT(0) + +/* BC_STS0 */ +# define BC_STS0_BC_LINESTATE(x) (((x) << 9) & (3 << 9)) +# define BC_STS0_BC_PHY_CHGDET BIT(8) +# define BC_STS0_BC_PHY_FSVMINUS BIT(7) +# define BC_STS0_BC_PHY_FSVPLUS BIT(6) +# define BC_STS0_BC_RID_GND BIT(5) +# define BC_STS0_BC_RID_FLOAT BIT(4) +# define BC_STS0_BC_RID_C BIT(3) +# define BC_STS0_BC_RID_B BIT(2) +# define BC_STS0_BC_RID_A BIT(1) +# define BC_STS0_BC_SESSVLD BIT(0) + +/* USB3PHY_CR_STS */ +#define USB3OTG_PHY_CR_DATA_OUT(x) (((x) >> 1) & 0xffff) +#define USB3OTG_PHY_CR_ACK BIT(0) + +/* USB3PHY_CR_CTRL */ +#define USB3OTG_PHY_CR_DATA_IN(x) (((x) << 4) & (0xffff << 4)) +#define USB3OTG_PHY_CR_WRITE BIT(3) +#define USB3OTG_PHY_CR_READ BIT(2) +#define USB3OTG_PHY_CR_CAP_DATA BIT(1) +#define USB3OTG_PHY_CR_CAP_ADDR BIT(0) + +#define usb_dbg(format, arg...) \ + pr_err("[USB3][%s]"format, __func__, ##arg) + +#define usb_err(format, arg...) \ + pr_err("[USB3][%s]"format, __func__, ##arg) + +enum hisi_usb_state { + USB_STATE_UNKNOWN = 0, + USB_STATE_OFF, + USB_STATE_DEVICE, + USB_STATE_HOST, +}; + +struct hiusb_event_queue { + enum otg_dev_event_type *event; + unsigned int num_event; + unsigned int max_event; + unsigned int enpos, depos; + unsigned int overlay, overlay_index; +}; + +#define MAX_EVENT_COUNT 16 +#define EVENT_QUEUE_UNIT MAX_EVENT_COUNT + +struct hisi_dwc3_device { + struct platform_device *pdev; + + void __iomem *otg_bc_reg_base; + void __iomem *pericfg_reg_base; + void __iomem *pctrl_reg_base; + void __iomem *sctrl_reg_base; + + struct regulator *usb_regu; + unsigned int is_regu_on; + unsigned int runtime_suspended; + + enum hisi_usb_state state; + enum hisi_charger_type charger_type; + enum hisi_charger_type fake_charger_type; + + enum otg_dev_event_type event; + spinlock_t event_lock; + + struct mutex lock; + struct wakeup_source ws; + struct atomic_notifier_head charger_type_notifier; + struct work_struct event_work; + + u32 eye_diagram_param; /* this param will be set to USBOTG3_CTRL4 */ + u32 eye_diagram_host_param; + u32 usb3_phy_cr_param; + u32 usb3_phy_host_cr_param; + u32 usb3_phy_tx_vboost_lvl; + unsigned int host_flag; + + u32 fpga_flag; + int fpga_usb_mode_gpio; + + struct clk *clk; + struct clk *gt_aclk_usb3otg; + + int eventmask; + + /* for bc again */ + u32 bc_again_flag; + struct delayed_work bc_again_work; + struct notifier_block conndone_nb; + + /* event queue for handle event */ + struct hiusb_event_queue event_queue; + + struct usb3_phy_ops *phy_ops; + + unsigned int need_disable_vdp; + void (*disable_vdp_src)(struct hisi_dwc3_device *hisi_dwc3); +}; + +#ifdef CONFIG_PM +extern const struct dev_pm_ops hisi_dwc3_dev_pm_ops; +#define HISI_DWC3_PM_OPS (&hisi_dwc3_dev_pm_ops) +#else +#define HISI_DWC3_PM_OPS NULL +#endif + +struct usb3_phy_ops { + struct regulator *subsys_regu; + + int (*init)(struct hisi_dwc3_device *hisi_dwc3); + int (*shutdown)(struct hisi_dwc3_device *hisi_dwc3); +}; + +typedef ssize_t (*hiusb_debug_show_ops)(void *, char *, ssize_t); +typedef ssize_t (*hiusb_debug_store_ops)(void *, const char *, ssize_t); +void hiusb_debug_init(void *data); +void hiusb_debug_quick_register(void *dev_data, + hiusb_debug_show_ops show, + hiusb_debug_store_ops store); + +void set_hisi_dwc3_power_flag(int val); +void config_femtophy_param(struct hisi_dwc3_device *hisi_dwc); +int hisi_dwc3_probe(struct platform_device *pdev, struct usb3_phy_ops *phy_ops); +int hisi_dwc3_remove(struct platform_device *pdev); +#endif /* _DWC3_HISI_H_ */ diff --git a/drivers/usb/dwc3/dwc3-otg.c b/drivers/usb/dwc3/dwc3-otg.c new file mode 100644 index 000000000000..fd3ef7d154ed --- /dev/null +++ b/drivers/usb/dwc3/dwc3-otg.c @@ -0,0 +1,360 @@ +/* + * dwc3-otg.c + * + * Copyright: (C) 2008-2018 hisilicon. + * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx> + * + * USB vbus for Hisilicon device + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + */ +#include <linux/kernel.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> + +#include "core.h" +#include "io.h" +#include "dwc3-otg.h" + +#define DBG(format, arg...) pr_info("[%s]" format, __func__, ##arg) + +struct dwc3_otg *dwc_otg_handler; + +static void dump_otg_regs(struct dwc3 *dwc) +{ +#define DUMP_REG(__reg) pr_info("%s:\t0x%x\n", \ + #__reg, dwc3_readl(dwc->regs, __reg)) + DUMP_REG(DWC3_OCFG); + DUMP_REG(DWC3_OCTL); + DUMP_REG(DWC3_OEVT); + DUMP_REG(DWC3_OEVTEN); + DUMP_REG(DWC3_OSTS); + + DUMP_REG(DWC3_BCFG); + DUMP_REG(DWC3_BCEVT); + DUMP_REG(DWC3_BCEVTEN); +} + +#ifndef DWC3_OTG_FORCE_MODE +static void dwc3_disable_otg_event(struct dwc3 *dwc) +{ + dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0); + dwc3_writel(dwc->regs, DWC3_OEVTEN, 0); +} + +static void dwc3_enable_otg_event(struct dwc3 *dwc) +{ + dwc3_writel(dwc, DWC3_OEVTEN, 0); + dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0); + dwc3_writel(dwc->regs, DWC3_OEVTEN, DWC3_OEVT_OTGBDEVVBUSCHNGEVNT | + DWC3_OEVT_OTGCONIDSTSCHNGEVNT); +} +#endif + +int dwc3_otg_resume(struct dwc3 *dwc) +{ + DBG("+\n"); +#ifndef DWC3_OTG_FORCE_MODE + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + if (reg & DWC3_OSTS_CONIDSTS) { + DBG("%s: ID is 1, set peripheral mode\n", __func__); + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + } else { + DBG("%s: ID is 0, clear peripheral mode\n", __func__); + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + } +#endif + + DBG("-\n"); + + return 0; +} + +int dwc3_otg_suspend(struct dwc3 *dwc) +{ + DBG("+\n"); + DBG("-\n"); + return 0; +} + +static int dwc3_otg_start_host(struct dwc3_otg *dwc_otg) +{ + struct dwc3 *dwc = dwc_otg->dwc; + unsigned long flags; + int ret; + u32 reg; + + DBG("+\n"); + + spin_lock_irqsave(&dwc->lock, flags); + +#ifdef DWC3_OTG_FORCE_MODE + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + pr_debug("%s: GCTL value 0x%x\n", __func__, reg); + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); +#else + /* check ID ststus */ + DBG("+before read DWC3_OSTS\n"); + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + if (reg & DWC3_OSTS_CONIDSTS) { + pr_warn("%s: CONIDSTS wrong!\n"); + dump_otg_regs(dwc); + } + DBG("+before read DWC3_OCFG\n"); + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg |= DWC3_OCFG_OTGSFTRSTMSK; + reg |= DWC3_OCFG_DISPRTPWRCUTOFF; + reg &= ~(DWC3_OCFG_HNPCAP | DWC3_OCFG_SRPCAP); + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + DBG("set OCFG 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCFG)); + + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~DWC3_OCTL_PERIMODE; + reg |= DWC3_OCTL_PRTPWRCTL; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + + DBG("set OCTL 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCTL)); +#endif + + spin_unlock_irqrestore(&dwc->lock, flags); + + ret = platform_device_add(dwc->xhci); + if (ret) { + pr_err("%s: failed to register xHCI device\n", __func__); + return ret; + } + +#ifdef CONFIG_HISI_USB_DWC3_MASK_IRQ_WORKAROUND + if (dwc->irq_state == 0) { + enable_irq(dwc->irq); + dwc->irq_state = 1; + pr_info("[%s]enable irq\n", __func__); + } +#endif + + DBG("-\n"); + + return ret; +} + +static void dwc3_otg_stop_host(struct dwc3_otg *dwc_otg) +{ + DBG("+\n"); + platform_device_del(dwc_otg->dwc->xhci); + DBG("-\n"); +} + +static int dwc3_otg_start_peripheral(struct dwc3_otg *dwc_otg) +{ + int ret; + unsigned long flags; + struct dwc3 *dwc = dwc_otg->dwc; + u32 reg; + + DBG("+\n"); + + spin_lock_irqsave(&dwc->lock, flags); + +#ifdef DWC3_OTG_FORCE_MODE + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + pr_debug("%s: GCTL value 0x%x\n", __func__, reg); + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); +#else + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + if (!(reg & DWC3_OSTS_CONIDSTS) || !(reg & DWC3_OSTS_BSESVLD)) { + pr_warn("%s: CONIDSTS or BSESVLD wrong!\n"); + dump_otg_regs(dwc); + } + + /* set mode as peripheral */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +#endif + + ret = dwc3_gadget_resume(dwc); + if (ret) + pr_err("[%s] gadget resume error!", __func__); + + spin_unlock_irqrestore(&dwc->lock, flags); + DBG("-\n"); + + return ret; +} + +static int dwc3_otg_stop_peripheral(struct dwc3_otg *dwc_otg) +{ + int ret; + unsigned long flags; + struct dwc3 *dwc = dwc_otg->dwc; + + DBG("+\n"); + spin_lock_irqsave(&dwc->lock, flags); + + ret = dwc3_gadget_suspend(dwc); + if (ret) + pr_err("[%s] gadget suspend error!", __func__); + + spin_unlock_irqrestore(&dwc->lock, flags); + DBG("-\n"); + + return ret; +} + +int dwc3_otg_id_value(struct dwc3_otg *dwc_otg) +{ + if (dwc_otg) + return !!(dwc3_readl(dwc_otg->dwc->regs, DWC3_OSTS) + & DWC3_OSTS_CONIDSTS); + else + return 1; +} + +int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt) +{ + int ret = 0; + + DBG("+\n evt = %d", evt); + + /* if otg is not enabled, do nothing */ + if (!dwc_otg) { + pr_info("%s: dwc3 is not otg mode!\n", __func__); + return 0; + } + + switch (evt) { + case DWC3_OTG_EVT_ID_SET: + dwc3_otg_stop_host(dwc_otg); + dwc3_suspend_device(dwc_otg->dwc); + break; + case DWC3_OTG_EVT_ID_CLEAR: + ret = dwc3_resume_device(dwc_otg->dwc); + if (ret) { + pr_err("%s: resume device failed!\n", __func__); + return ret; + } + ret = dwc3_otg_start_host(dwc_otg); + if (ret) { + pr_err("%s: start host failed!\n", __func__); + dwc3_suspend_device(dwc_otg->dwc); + return ret; + } + break; + case DWC3_OTG_EVT_VBUS_SET: + ret = dwc3_resume_device(dwc_otg->dwc); + if (ret) { + pr_err("%s: resume device failed!\n", __func__); + return ret; + } + ret = dwc3_otg_start_peripheral(dwc_otg); + if (ret) { + pr_err("%s: start peripheral failed!\n", __func__); + dwc3_suspend_device(dwc_otg->dwc); + return ret; + } + break; + case DWC3_OTG_EVT_VBUS_CLEAR: + ret = dwc3_otg_stop_peripheral(dwc_otg); + dwc3_suspend_device(dwc_otg->dwc); + break; + default: + break; + } + DBG("-\n"); + + return ret; +} + +static void dwc3_otg_work_fun(struct work_struct *w) +{ + struct dwc3_otg *dwc_otg = container_of( + w, struct dwc3_otg, otg_work.work); + + mutex_lock(&dwc_otg->lock); + if (dwc3_otg_work(dwc_otg, atomic_read(&dwc_otg->otg_evt_flag))) + pr_err("%s: dwc3_otg_work failed\n", __func__); + mutex_unlock(&dwc_otg->lock); +} + +int dwc3_otg_init(struct dwc3 *dwc) +{ + struct dwc3_otg *dwc_otg; + u32 reg; + + DBG("+\n"); + + dwc_otg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL); + if (!dwc_otg) + return -ENOMEM; + + dwc_otg->dwc = dwc; + dwc->dwc_otg = dwc_otg; + + mutex_init(&dwc_otg->lock); + INIT_DELAYED_WORK(&dwc_otg->otg_work, dwc3_otg_work_fun); + + dwc_otg_handler = dwc_otg; + +#ifdef DWC3_OTG_FORCE_MODE + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + pr_debug("%s: GCTL value 0x%x\n", __func__, reg); + + /* default device mode */ + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); +#else + /* disable hnp and srp */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~(DWC3_OCFG_HNPCAP | DWC3_OCFG_SRPCAP); + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + if (reg & DWC3_OSTS_CONIDSTS) { + DBG("%s: ID is 1, set peripheral mode\n", __func__); + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_HNPREQ | DWC3_OCTL_DEVSETHNPEN | + DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + } else { + DBG("%s: ID is 0, clear peripheral mode\n", __func__); + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + } +#endif + + dump_otg_regs(dwc); + + DBG("-\n"); + + return 0; +} + +void dwc3_otg_exit(struct dwc3 *dwc) +{ + DBG("+\n"); + dwc_otg_handler = NULL; + dwc->dwc_otg->dwc = NULL; + dwc->dwc_otg = NULL; + DBG("-\n"); +} diff --git a/drivers/usb/dwc3/dwc3-otg.h b/drivers/usb/dwc3/dwc3-otg.h new file mode 100644 index 000000000000..b9114b16f050 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-otg.h @@ -0,0 +1,133 @@ +/* + * dwc3-otg.h + * + * Copyright: (C) 2008-2018 hisilicon. + * Contact: wangbinghui<wangbinghui@xxxxxxxxxxxxx> + * + * USB vbus for Hisilicon device + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + */ +#ifndef __DRIVERS_USB_DWC3_OTG_H +#define __DRIVERS_USB_DWC3_OTG_H + +/* BC Registers */ +#define DWC3_BCFG 0xcc30 +#define DWC3_BCEVT 0xcc38 +#define DWC3_BCEVTEN 0xcc3c +#ifndef BIT +#define BIT(x) (1 << (x)) +#endif +/* OTG Configuration Register */ +#define DWC3_OCFG_DISPRTPWRCUTOFF BIT(5) +#define DWC3_OCFG_OTGHIBDISMASK BIT(4) +#define DWC3_OCFG_OTGSFTRSTMSK BIT(3) +#define DWC3_OCFG_HNPCAP BIT(1) +#define DWC3_OCFG_SRPCAP 1 + +/* OTG Control Register */ +#define DWC3_OCTL_OTG3_GOERR BIT(7) +#define DWC3_OCTL_PERIMODE BIT(6) +#define DWC3_OCTL_PRTPWRCTL BIT(5) +#define DWC3_OCTL_HNPREQ BIT(4) +#define DWC3_OCTL_SESREQ BIT(3) +#define DWC3_OCTL_TERMSELDLPULSE BIT(2) +#define DWC3_OCTL_DEVSETHNPEN BIT(1) +#define DWC3_OCTL_HSTSETHNPEN BIT(0) + +/* OTG Events Register */ +#define DWC3_OEVT_DEVICEMOD BIT(31) +#define DWC3_OEVT_OTGXHCIRUNSTPSETEVNT BIT(27) +#define DWC3_OEVT_OTGDEVRUNSTPSETEVNT BIT(26) +#define DWC3_OEVT_OTGHIBENTRYEVNT BIT(25) +#define DWC3_OEVT_OTGCONIDSTSCHNGEVNT BIT(24) +#define DWC3_OEVT_HRRCONFNOTIFEVNT BIT(23) +#define DWC3_OEVT_HRRINITNOTIFEVNT BIT(22) +#define DWC3_OEVT_OTGADEVIDLEEVNT BIT(21) +#define DWC3_OEVT_OTGADEVBHOSTENDEVNT BIT(20) +#define DWC3_OEVT_OTGADEVHOSTEVNT BIT(19) +#define DWC3_OEVT_OTGADEVHNPCHNGEVNT BIT(18) +#define DWC3_OEVT_OTGADEVSRPDETEVNT BIT(17) +#define DWC3_OEVT_OTGADEVSESSENDDETEVNT BIT(16) +#define DWC3_OEVT_OTGBDEVBHOSTENDEVNT BIT(11) +#define DWC3_OEVT_OTGBDEVHNPCHNGEVNT BIT(10) +#define DWC3_OEVT_OTGBDEVSESSVLDDETEVNT BIT(9) +#define DWC3_OEVT_OTGBDEVVBUSCHNGEVNT BIT(8) + +/* OTG Status Register */ +#define DWC3_OSTS_OTGSTATE_MSK (0xf << 8) +#define DWC3_OSTS_PERIPHERALSTATE BIT(4) +#define DWC3_OSTS_XHCIPRTPOWER BIT(3) +#define DWC3_OSTS_BSESVLD BIT(2) +#define DWC3_OSTS_ASESVLD BIT(1) +#define DWC3_OSTS_CONIDSTS BIT(0) + +struct dwc3_otg { + struct usb_otg otg; + struct dwc3 *dwc; + int otg_irq; + struct delayed_work otg_work; + + atomic_t otg_evt_flag; +#define DWC3_OTG_EVT_ID_SET 1 +#define DWC3_OTG_EVT_ID_CLEAR 2 +#define DWC3_OTG_EVT_VBUS_SET 3 +#define DWC3_OTG_EVT_VBUS_CLEAR 4 + + struct mutex lock; +}; + +#ifdef CONFIG_USB_DWC3_OTG +extern struct dwc3_otg *dwc_otg_handler; +int dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); +int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt); +int dwc3_otg_resume(struct dwc3 *dwc); +int dwc3_otg_suspend(struct dwc3 *dwc); +int dwc3_otg_id_value(struct dwc3_otg *dwc_otg); +#else +#define dwc_otg_handler ((struct dwc3_otg *)NULL) +static inline int dwc3_otg_init(struct dwc3 *dwc) +{ + return 0; +} + +static inline void dwc3_otg_exit(struct dwc3 *dwc) +{ +} + +static inline int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt) +{ + return 0; +} + +static inline int dwc3_otg_resume(struct dwc3 *dwc) +{ + return 0; +} + +static inline int dwc3_otg_suspend(struct dwc3 *dwc) +{ + return 0; +} + +static inline int dwc3_otg_id_value(struct dwc3_otg *dwc_otg) +{ + return 0; +}; +#endif + +#endif /* __DRIVERS_USB_DWC3_OTG_H */ diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 75e6cb044eb2..e2c8d2ebfb64 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -98,11 +98,19 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, struct dwc3_request *req) { struct dwc3 *dwc = dep->dwc; + int ret; req->request.actual = 0; req->request.status = -EINPROGRESS; req->epnum = dep->number; + /* we share one TRB for ep0/1 */ + if (!list_empty(&dep->pending_list)) { + dev_WARN(dwc->dev, "ep0 busy!\n"); + ret = -EBUSY; + return ret; + } + list_add_tail(&req->list, &dep->pending_list); /* @@ -190,8 +198,18 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, __dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req); dep->flags &= ~DWC3_EP0_DIR_IN; + + return 0; } + /* mark the status phase already queued */ + if (dwc->ep0_next_event == DWC3_EP0_NRDY_STATUS) + dwc->status_queued = true; + + if (req->request.length != 0) + dev_WARN(dwc->dev, "status phase len %d\n", + req->request.length); + return 0; } @@ -241,6 +259,7 @@ static void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) __dwc3_gadget_ep_set_halt(dep, 1, false); dep->flags = DWC3_EP_ENABLED; dwc->delayed_status = false; + dwc->status_queued = false; if (!list_empty(&dep->pending_list)) { struct dwc3_request *req; @@ -329,6 +348,12 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, if (value != 0) return -EINVAL; + if (!(ctrl->bRequestType & USB_DIR_IN)) + return -EINVAL; + + if (!le16_to_cpu(ctrl->wLength)) + return -EINVAL; + recip = ctrl->bRequestType & USB_RECIP_MASK; switch (recip) { case USB_RECIP_DEVICE: @@ -714,6 +739,12 @@ static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) u16 wLength; u16 wValue; + if (unlikely(ctrl->bRequestType & USB_DIR_IN)) + return -EINVAL; + + if (unlikely(!le16_to_cpu(ctrl->wLength))) + return -EINVAL; + if (state == USB_STATE_DEFAULT) return -EINVAL; @@ -830,9 +861,25 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, if (ret == USB_GADGET_DELAYED_STATUS) dwc->delayed_status = true; + if (dwc->status_queued) { + dwc->status_queued = false; + if (dwc->delayed_status) { + pr_info("delayed status already come, will not wait for it.\n"); + dwc->delayed_status = false; + usb_gadget_set_state(&dwc->gadget, + USB_STATE_CONFIGURED); + } + } + out: - if (ret < 0) + if (ret < 0) { + dev_err(dwc->dev, "ep0 setup error, ret %d!\n", ret); + dev_err(dwc->dev, "ctrl: %02x %02x %04x %04x %04x\n", + ctrl->bRequestType, ctrl->bRequest, + ctrl->wValue, ctrl->wIndex, ctrl->wLength); dwc3_ep0_stall_and_restart(dwc); + } + } static void dwc3_ep0_complete_data(struct dwc3 *dwc, @@ -858,8 +905,10 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, trace_dwc3_complete_trb(ep0, trb); r = next_request(&ep0->pending_list); - if (!r) + if (!r) { + dev_err(dwc->dev, "ep0 request list empty while complete data\n"); return; + } status = DWC3_TRB_SIZE_TRBSTS(trb->size); if (status == DWC3_TRBSTS_SETUP_PENDING) { @@ -1135,6 +1184,8 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, return; } + dwc->status_queued = false; + dwc3_ep0_do_control_status(dwc, event); } } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f064f1549333..069c6eb1cc5c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -34,6 +34,7 @@ #include "core.h" #include "gadget.h" #include "io.h" +#include "dwc3-hisi.h" /** * dwc3_gadget_set_test_mode - enables usb2 test modes @@ -267,7 +268,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, { const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; struct dwc3 *dwc = dep->dwc; - u32 timeout = 500; + u32 timeout = 3000; u32 reg; int cmd_status = 0; @@ -1476,6 +1477,9 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, out1: /* giveback the request */ + if (!dep->queued_requests) + goto out0; + dep->queued_requests--; dwc3_gadget_giveback(dep, req, -ECONNRESET); @@ -2710,6 +2714,18 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DCFG, reg); } +ATOMIC_NOTIFIER_HEAD(conndone_nh); + +int dwc3_conndone_notifier_register(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&conndone_nh, nb); +} + +int dwc3_conndone_notifier_unregister(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&conndone_nh, nb); +} + static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) { struct dwc3_ep *dep; @@ -3236,7 +3252,9 @@ int dwc3_gadget_init(struct dwc3 *dwc) dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->gadget.sg_supported = true; dwc->gadget.name = "dwc3-gadget"; +#ifndef CONFIG_USB_DWC3_HISI dwc->gadget.is_otg = dwc->dr_mode == USB_DR_MODE_OTG; +#endif /* * FIXME We might be setting max_speed to <SUPER, however versions diff --git a/drivers/usb/dwc3/hisi_hikey_gpio.c b/drivers/usb/dwc3/hisi_hikey_gpio.c new file mode 100644 index 000000000000..ae05bbf9dd4a --- /dev/null +++ b/drivers/usb/dwc3/hisi_hikey_gpio.c @@ -0,0 +1,300 @@ +/* + * otgid_gpio_hub.c + * + * Copyright (c) Hisilicon Tech. Co., Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/timer.h> +#include <linux/param.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/hisi/log/hisi_log.h> +#include <linux/hisi/usb/hisi_usb.h> +#include <linux/tifm.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/hisi/usb/hisi_hikey_gpio.h> +#define DEVICE_DRIVER_NAME "gpio_hub_for_usb5734" + +#define GPIO_HUB_OTG_HOST 1 +#define GPIO_HUB_OTG_DEVICE 0 +#define GPIO_TYPEC_VBUS_POWER 1 +#define GPIO_TYPEC_NO_POWER 0 +#define GPIO_HUB_VBUS_POWER 1 +#define GPIO_HUB_VBUS_NO_POWER 0 +#define GPIO_HUB_HUB_VBUS_POWER 1 + +/* SOC_CRGPERIPH_PEREN1_UNION */ +#define SOC_CRGPERIPH_PEREN1_ADDR(base) ((base) + (0x010)) + +#define HISILOG_TAG GPIO_HUB +HISILOG_REGIST(); + +struct gpio_hub_info { + struct platform_device *pdev; + int otg_switch_gpio; + int typec_vbus_gpio; + int typec_vbus_enable_val; + int hub_vbus_gpio; +}; + +static struct gpio_hub_info gpio_hub_driver_info = { + .otg_switch_gpio = -1, + .typec_vbus_gpio = -1, + .typec_vbus_enable_val = -1, + .hub_vbus_gpio = -1, +}; + +void gpio_hub_power_off(void) +{ + if (gpio_is_valid(gpio_hub_driver_info.hub_vbus_gpio)) { + gpio_set_value(gpio_hub_driver_info.hub_vbus_gpio, + GPIO_HUB_VBUS_NO_POWER); + hisilog_info("%s: gpio hub hub vbus no power set success", + __func__); + } else { + hisilog_err("%s: gpio hub hub vbus no power set err", + __func__); + } +} + +void gpio_hub_power_on(void) +{ + if (gpio_is_valid(gpio_hub_driver_info.hub_vbus_gpio)) + gpio_set_value(gpio_hub_driver_info.hub_vbus_gpio, + GPIO_HUB_VBUS_POWER); + else + hisilog_err("%s: gpio hub hub vbus set err", __func__); +} + +void gpio_hub_switch_to_hub(void) +{ + int gpio = gpio_hub_driver_info.otg_switch_gpio; + + if (!gpio_is_valid(gpio)) { + hisilog_err("%s: otg_switch_gpio is err\n", __func__); + return; + } + + if (gpio_get_value(gpio)) { + hisilog_info("%s: already switch to hub\n", __func__); + return; + } + + gpio_direction_output(gpio, 1); + hisilog_err("%s: switch to hub\n", __func__); +} +EXPORT_SYMBOL_GPL(gpio_hub_switch_to_hub); + +void gpio_hub_switch_to_typec(void) +{ + int gpio = gpio_hub_driver_info.otg_switch_gpio; + + if (!gpio_is_valid(gpio)) { + hisilog_err("%s: otg_switch_gpio is err\n", __func__); + return; + } + + if (!gpio_get_value(gpio)) { + hisilog_info("%s: already switch to typec\n", __func__); + return; + } + + gpio_direction_output(gpio, 0); + hisilog_err("%s: switch to typec\n", __func__); +} +EXPORT_SYMBOL_GPL(gpio_hub_switch_to_typec); + +static void gpio_hub_change_typec_power(int gpio, int on) +{ + if (!gpio_is_valid(gpio)) { + hisilog_err("%s: typec power gpio is err\n", __func__); + return; + } + + if (gpio_get_value(gpio) == on) { + hisilog_info("%s: typec power no change\n", __func__); + return; + } + + gpio_direction_output(gpio, on); + hisilog_info("%s: set typec vbus gpio to %d\n", __func__, on); +} + +void gpio_hub_typec_power_on(void) +{ + struct gpio_hub_info *info = &gpio_hub_driver_info; + + gpio_hub_change_typec_power(info->typec_vbus_gpio, + info->typec_vbus_enable_val); +} +EXPORT_SYMBOL_GPL(gpio_hub_typec_power_on); + +void gpio_hub_typec_power_off(void) +{ + struct gpio_hub_info *info = &gpio_hub_driver_info; + + gpio_hub_change_typec_power(info->typec_vbus_gpio, + !info->typec_vbus_enable_val); +} +EXPORT_SYMBOL_GPL(gpio_hub_typec_power_off); + +static int gpio_hub_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *root = pdev->dev.of_node; + struct gpio_hub_info *info = &gpio_hub_driver_info; + + hisilog_info("%s: step in\n", __func__); + + info->pdev = pdev; + if (!pdev) + return -EBUSY; + + info->hub_vbus_gpio = of_get_named_gpio(root, "hub_vdd33_en_gpio", 0); + if (!gpio_is_valid(info->hub_vbus_gpio)) { + hisilog_err("%s: hub_vbus_gpio is err\n", __func__); + return info->hub_vbus_gpio; + } + ret = gpio_request(info->hub_vbus_gpio, "hub_vbus_int_gpio"); + if (ret) { + hisilog_err("%s: request hub_vbus_gpio err\n", __func__); + return ret; + } + + info->typec_vbus_gpio = of_get_named_gpio(root, + "typc_vbus_int_gpio,typec-gpios", 0); + if (!gpio_is_valid(info->hub_vbus_gpio)) { + hisilog_err("%s: typec_vbus_gpio is err\n", __func__); + ret = info->typec_vbus_gpio; + goto free_gpio1; + } + ret = gpio_request(info->typec_vbus_gpio, "typc_vbus_int_gpio"); + if (ret) { + hisilog_err("%s: request typec_vbus_gpio err\n", __func__); + goto free_gpio1; + } + + ret = of_property_read_u32(root, "typc_vbus_enable_val", + &info->typec_vbus_enable_val); + if (ret) { + hisilog_err("%s: typc_vbus_enable_val can't get\n", __func__); + goto free_gpio2; + } + info->typec_vbus_enable_val = !!info->typec_vbus_enable_val; + + /* only for v2 */ + info->otg_switch_gpio = of_get_named_gpio(root, "otg_gpio", 0); + if (!gpio_is_valid(info->otg_switch_gpio)) { + hisilog_info("%s: otg_switch_gpio is err\n", __func__); + info->otg_switch_gpio = -1; + } + + ret = gpio_direction_output(info->hub_vbus_gpio, GPIO_HUB_VBUS_POWER); + if (ret) { + hisilog_err("%s: power on hub vbus err\n", __func__); + goto free_gpio2; + } + + ret = gpio_direction_output(info->typec_vbus_gpio, + info->typec_vbus_enable_val); + if (ret) { + hisilog_err("%s: power on typec vbus err", __func__); + goto free_gpio2; + } + + return 0; + +free_gpio2: + gpio_free(info->typec_vbus_gpio); + info->typec_vbus_gpio = -1; +free_gpio1: + gpio_free(info->hub_vbus_gpio); + info->hub_vbus_gpio = -1; + + return ret; +} + +static int gpio_hub_remove(struct platform_device *pdev) +{ + struct gpio_hub_info *info = &gpio_hub_driver_info; + + if (gpio_is_valid(info->otg_switch_gpio)) { + gpio_free(info->otg_switch_gpio); + info->otg_switch_gpio = -1; + } + + if (gpio_is_valid(info->typec_vbus_gpio)) { + gpio_free(info->typec_vbus_gpio); + info->typec_vbus_gpio = -1; + } + + if (gpio_is_valid(info->hub_vbus_gpio)) { + gpio_free(info->hub_vbus_gpio); + info->hub_vbus_gpio = -1; + } + return 0; +} + +static const struct of_device_id id_table_for_gpio_hub[] = { + {.compatible = "hisilicon,gpio_hubv1"}, + {.compatible = "hisilicon,gpio_hubv2"}, + {} +}; + +static struct platform_driver gpio_hub_driver = { + .probe = gpio_hub_probe, + .remove = gpio_hub_remove, + .driver = { + .name = DEVICE_DRIVER_NAME, + .of_match_table = of_match_ptr(id_table_for_gpio_hub), + + }, +}; + +static int __init gpio_hub_init(void) +{ + int ret = platform_driver_register(&gpio_hub_driver); + + hisilog_info("%s:gpio hub init status:%d\n", __func__, ret); + return ret; +} + +static void __exit gpio_hub_exit(void) +{ + platform_driver_unregister(&gpio_hub_driver); +} + +module_init(gpio_hub_init); +module_exit(gpio_hub_exit); + +MODULE_AUTHOR("wangbinghui<wangbinghui@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("HUB GPIO FOR OTG ID driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 76f0b0df37c1..ccbf0c35a9b1 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -96,6 +96,15 @@ int dwc3_host_init(struct dwc3 *dwc) goto err1; } +#ifdef CONFIG_USB_DWC3_HISI + /* if otg, otg will do device_add */ + if (dwc->dwc_otg) { + dev_err(dwc->dev, "%s if otg, otg will do device_add.\n", + __func__); + return 0; + } +#endif + memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); if (dwc->usb3_lpm_capable) @@ -145,6 +154,10 @@ int dwc3_host_init(struct dwc3 *dwc) void dwc3_host_exit(struct dwc3 *dwc) { +#ifdef CONFIG_USB_DWC3_HISI + if (dwc->dwc_otg) + return; +#endif phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy", dev_name(dwc->dev)); phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy", diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h index c69b06696824..adc8648c92b2 100644 --- a/drivers/usb/dwc3/io.h +++ b/drivers/usb/dwc3/io.h @@ -28,6 +28,13 @@ static inline u32 dwc3_readl(void __iomem *base, u32 offset) { u32 value; +#ifdef CONFIG_USB_DWC3_HISI + extern atomic_t hisi_dwc3_power_on; + + if (unlikely(atomic_read(&hisi_dwc3_power_on) == 0)) + return 0; +#endif + /* * We requested the mem region starting from the Globals address * space, see dwc3_probe in core.c. @@ -47,6 +54,13 @@ static inline u32 dwc3_readl(void __iomem *base, u32 offset) static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value) { +#ifdef CONFIG_USB_DWC3_HISI + extern atomic_t hisi_dwc3_power_on; + + if (unlikely(atomic_read(&hisi_dwc3_power_on) == 0)) + return; +#endif + /* * We requested the mem region starting from the Globals address * space, see dwc3_probe in core.c. diff --git a/include/linux/hisi/log/hisi_log.h b/include/linux/hisi/log/hisi_log.h new file mode 100644 index 000000000000..cc3eda1c4f0f --- /dev/null +++ b/include/linux/hisi/log/hisi_log.h @@ -0,0 +1,143 @@ +#ifndef _LINUX_HISILOG_H +#define _LINUX_HISILOG_H + +#include <linux/printk.h> +#include <linux/types.h> + +enum { + HISILOG_ERR = 1U << 0, + HISILOG_WARNING = 1U << 1, + HISILOG_INFO = 1U << 2, + HISILOG_DEBUG = 1U << 3, + HISILOG_DEBUG1 = 1U << 4, + HISILOG_DEBUG2 = 1U << 5, + HISILOG_DEBUG3 = 1U << 6, + HISILOG_DEBUG4 = 1U << 7, +}; + +#define HISILOG_TAG_DEFOUTL_LEVEL (HISILOG_ERR \ + | HISILOG_WARNING \ + | HISILOG_INFO) + +struct hisi_log_tag { + const char *name; + u32 level; +}; + +#define HISILOG_REGIST() \ + HISILOG_REGIST_TAG_LEVEL(HISILOG_TAG, HISILOG_TAG_DEFOUTL_LEVEL) + +#define HISILOG_REGIST_LEVEL(level) \ + HISILOG_REGIST_TAG_LEVEL(HISILOG_TAG, level) + +#define HISILOG_REGIST_TAG_LEVEL(name, level) \ + _HISILOG_REGIST_TAG_LEVEL(name, level) + +#define _HISILOG_REGIST_TAG_LEVEL(name, level) \ + static struct hisi_log_tag TAG_STRUCT_NAME(name) \ +__used \ +__attribute__ ((unused, __section__("__hisilog_tag"))) \ += { #name, level} + +#define hisilog_err(x...) \ + _hisilog_err(HISILOG_TAG, ##x) + +#define _hisilog_err(TAG, x...) \ + __hisilog_err(TAG, ##x) + +#define __hisilog_err(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_ERR) \ + pr_err(hw_fmt_tag(TAG, E) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_warn(x...) \ + _hisilog_warn(HISILOG_TAG, ##x) + +#define _hisilog_warn(TAG, x...) \ + __hisilog_warn(TAG, ##x) + +#define __hisilog_warn(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_WARNING) \ + pr_err(hw_fmt_tag(TAG, W) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_info(x...) \ + _hisilog_info(HISILOG_TAG, ##x) + +#define _hisilog_info(TAG, x...) \ + __hisilog_info(TAG, ##x) + +#define __hisilog_info(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_INFO) \ + pr_info(hw_fmt_tag(TAG, I) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_debug(x...) \ + _hisilog_debug(HISILOG_TAG, ##x) + +#define _hisilog_debug(TAG, x...) \ + __hisilog_debug(TAG, ##x) + +#define __hisilog_debug(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG) \ + pr_err(hw_fmt_tag(TAG, D) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_debug1(x...) \ + _hisilog_debug1(HISILOG_TAG, ##x) + +#define _hisilog_debug1(TAG, x...) \ + __hisilog_debug1(TAG, ##x) + +#define __hisilog_debug1(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG1) \ + pr_err(hw_fmt_tag(TAG, D1) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_debug2(x...) \ + _hisilog_debug2(HISILOG_TAG, ##x) + +#define _hisilog_debug2(TAG, x...) \ + __hisilog_debug2(TAG, ##x) + +#define __hisilog_debug2(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG2) \ + pr_err(hw_fmt_tag(TAG, D2) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_debug3(x...) \ + _hisilog_debug3(HISILOG_TAG, ##x) + +#define _hisilog_debug3(TAG, x...) \ + __hisilog_debug3(TAG, ##x) + +#define __hisilog_debug3(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG3) \ + pr_err(hw_fmt_tag(TAG, D3) fmt, ##__VA_ARGS__); \ + } while (0) + +#define hisilog_debug4(x...) \ + _hisilog_debug4(HISILOG_TAG, ##x) + +#define _hisilog_debug4(TAG, x...) \ + __hisilog_debug4(TAG, ##x) + +#define __hisilog_debug4(TAG, fmt, ...) \ + do { \ + if (TAG_STRUCT_NAME(TAG).level & HISILOG_DEBUG4) \ + pr_err(hw_fmt_tag(TAG, D4) fmt, ##__VA_ARGS__); \ + } while (0) + +#define TAG_STRUCT_NAME(name) \ + _hwtag_##name + +#define hw_fmt_tag(TAG, LEVEL) "[" #LEVEL "/" #TAG "] " + +#endif diff --git a/include/linux/hisi/usb/hisi_hikey_gpio.h b/include/linux/hisi/usb/hisi_hikey_gpio.h new file mode 100644 index 000000000000..99df5772df96 --- /dev/null +++ b/include/linux/hisi/usb/hisi_hikey_gpio.h @@ -0,0 +1,24 @@ +/* + * hub_usb5734.h + * + * Copyright (c) Hisilicon Tech. Co., Ltd. All rights reserved. + * + * Chenjun <chenjun@xxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +void gpio_hub_power_on(void); +void gpio_hub_power_off(void); +void gpio_hub_switch_to_hub(void); +void gpio_hub_switch_to_typec(void); +void gpio_hub_typec_power_off(void); +void gpio_hub_typec_power_on(void); diff --git a/include/linux/hisi/usb/hisi_usb.h b/include/linux/hisi/usb/hisi_usb.h new file mode 100644 index 000000000000..9ee216e32cd1 --- /dev/null +++ b/include/linux/hisi/usb/hisi_usb.h @@ -0,0 +1,57 @@ +#ifndef _HISI_USB_H_ +#define _HISI_USB_H_ + +enum hisi_charger_type { + CHARGER_TYPE_SDP = 0, /* Standard Downstreame Port */ + CHARGER_TYPE_CDP, /* Charging Downstreame Port */ + CHARGER_TYPE_DCP, /* Dedicate Charging Port */ + CHARGER_TYPE_UNKNOWN, /* non-standard */ + CHARGER_TYPE_NONE, /* not connected */ + + /* other messages */ + PLEASE_PROVIDE_POWER, /* host mode, provide power */ +}; + +enum otg_dev_event_type { + CHARGER_CONNECT_EVENT = 0, + CHARGER_DISCONNECT_EVENT, + ID_FALL_EVENT, + ID_RISE_EVENT, + NONE_EVENT +}; + +#if defined(CONFIG_USB_SUSB_HDRC) || defined(CONFIG_USB_DWC3) +int hisi_charger_type_notifier_register(struct notifier_block *nb); +int hisi_charger_type_notifier_unregister(struct notifier_block *nb); +enum hisi_charger_type hisi_get_charger_type(void); +int hisi_usb_otg_event(enum otg_dev_event_type event_type); +void hisi_usb_otg_bc_again(void); +#else +static inline int hisi_charger_type_notifier_register( + struct notifier_block *nb){return 0; } +static inline int hisi_charger_type_notifier_unregister( + struct notifier_block *nb){return 0; } +static inline enum hisi_charger_type hisi_get_charger_type(void) +{ + return CHARGER_TYPE_NONE; +} + +static inline int hisi_usb_otg_event(enum otg_dev_event_type event_type) +{ + return 0; +} + +static inline void hisi_usb_otg_bc_again(void) +{ +} +#endif /* CONFIG_USB_SUSB_HDRC || CONFIG_USB_DWC3 */ + +static inline int hisi_usb_id_change(enum otg_dev_event_type event) +{ + if ((event == ID_FALL_EVENT) || (event == ID_RISE_EVENT)) + return hisi_usb_otg_event(event); + else + return 0; +} + +#endif /* _HISI_USB_H_*/ -- 2.11.GIT -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html