summaryrefslogtreecommitdiff
path: root/drivers/usb/dwc3/dwc3-otg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc3/dwc3-otg.c')
-rw-r--r--drivers/usb/dwc3/dwc3-otg.c362
1 files changed, 362 insertions, 0 deletions
diff --git a/drivers/usb/dwc3/dwc3-otg.c b/drivers/usb/dwc3/dwc3-otg.c
new file mode 100644
index 000000000000..34a082ea96e3
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-otg.c
@@ -0,0 +1,362 @@
+/*
+ * dwc3-otg.c
+ *
+ * Copyright: (C) 2008-2018 hisilicon.
+ * Contact: wangbinghui<wangbinghui@hisilicon.com>
+ *
+ * USB vbus for Hisilicon device
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include "core.h"
+#include "io.h"
+#include "dwc3-otg.h"
+
+#define DBG(format, arg...) pr_info("[%s]" format, __func__, ##arg)
+
+struct dwc3_otg *dwc_otg_handler;
+
+static void dump_otg_regs(struct dwc3 *dwc)
+{
+#define DUMP_REG(__reg) pr_info("%s:\t0x%x\n", \
+ #__reg, dwc3_readl(dwc->regs, __reg))
+ DUMP_REG(DWC3_OCFG);
+ DUMP_REG(DWC3_OCTL);
+ DUMP_REG(DWC3_OEVT);
+ DUMP_REG(DWC3_OEVTEN);
+ DUMP_REG(DWC3_OSTS);
+
+ DUMP_REG(DWC3_BCFG);
+ DUMP_REG(DWC3_BCEVT);
+ DUMP_REG(DWC3_BCEVTEN);
+}
+
+#ifndef DWC3_OTG_FORCE_MODE
+static void dwc3_disable_otg_event(struct dwc3 *dwc)
+{
+ dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0);
+ dwc3_writel(dwc->regs, DWC3_OEVTEN, 0);
+}
+
+static void dwc3_enable_otg_event(struct dwc3 *dwc)
+{
+ dwc3_writel(dwc, DWC3_OEVTEN, 0);
+ dwc3_writel(dwc->regs, DWC3_OEVT, 0x0ffffff0);
+ dwc3_writel(dwc->regs, DWC3_OEVTEN, DWC3_OEVT_OTGBDEVVBUSCHNGEVNT |
+ DWC3_OEVT_OTGCONIDSTSCHNGEVNT);
+}
+#endif
+
+int dwc3_otg_resume(struct dwc3 *dwc)
+{
+ DBG("+\n");
+#ifndef DWC3_OTG_FORCE_MODE
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (reg & DWC3_OSTS_CONIDSTS) {
+ DBG("%s: ID is 1, set peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg |= DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ } else {
+ DBG("%s: ID is 0, clear peripheral mode\n", __func__);
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg &= ~DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+ }
+#endif
+
+ DBG("-\n");
+
+ return 0;
+}
+
+int dwc3_otg_suspend(struct dwc3 *dwc)
+{
+ DBG("+\n");
+ DBG("-\n");
+ return 0;
+}
+
+static int dwc3_otg_start_host(struct dwc3_otg *dwc_otg)
+{
+ struct dwc3 *dwc = dwc_otg->dwc;
+ unsigned long flags;
+ int ret;
+ u32 reg;
+
+ DBG("+\n");
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+#ifdef DWC3_OTG_FORCE_MODE
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ pr_debug("%s: GCTL value 0x%x\n", __func__, reg);
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
+#else
+ /* check ID ststus */
+ DBG("+before read DWC3_OSTS\n");
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (reg & DWC3_OSTS_CONIDSTS) {
+ pr_warn("%s: CONIDSTS wrong!\n");
+ dump_otg_regs(dwc);
+ }
+ DBG("+before read DWC3_OCFG\n");
+ reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+ reg |= DWC3_OCFG_OTGSFTRSTMSK;
+ reg |= DWC3_OCFG_DISPRTPWRCUTOFF;
+ reg &= ~(DWC3_OCFG_HNPCAP | DWC3_OCFG_SRPCAP);
+ dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+ DBG("set OCFG 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCFG));
+
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg &= ~DWC3_OCTL_PERIMODE;
+ reg |= DWC3_OCTL_PRTPWRCTL;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+
+ DBG("set OCTL 0x%x\n", dwc3_readl(dwc->regs, DWC3_OCTL));
+#endif
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ ret = platform_device_add(dwc->xhci);
+ if (ret) {
+ pr_err("%s: failed to register xHCI device\n", __func__);
+ return ret;
+ }
+
+#ifdef CONFIG_HISI_USB_DWC3_MASK_IRQ_WORKAROUND
+ if (dwc->irq_state == 0) {
+ enable_irq(dwc->irq);
+ dwc->irq_state = 1;
+ pr_info("[%s]enable irq\n", __func__);
+ }
+#endif
+
+ DBG("-\n");
+
+ return ret;
+}
+
+static void dwc3_otg_stop_host(struct dwc3_otg *dwc_otg)
+{
+ DBG("+\n");
+ platform_device_del(dwc_otg->dwc->xhci);
+ DBG("-\n");
+}
+
+static int dwc3_otg_start_peripheral(struct dwc3_otg *dwc_otg)
+{
+ int ret;
+ unsigned long flags;
+ struct dwc3 *dwc = dwc_otg->dwc;
+ u32 reg;
+
+ DBG("+\n");
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+#ifdef DWC3_OTG_FORCE_MODE
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ pr_debug("%s: GCTL value 0x%x\n", __func__, reg);
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+#else
+ reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+ if (!(reg & DWC3_OSTS_CONIDSTS) || !(reg & DWC3_OSTS_BSESVLD)) {
+ pr_warn("%s: CONIDSTS or BSESVLD wrong!\n");
+ dump_otg_regs(dwc);
+ }
+
+ /* set mode as peripheral */
+ reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+ reg |= DWC3_OCTL_PERIMODE;
+ dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+#endif
+
+ ret = dwc3_gadget_resume(dwc);
+ if (ret)
+ pr_err("[%s] gadget resume error!", __func__);
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ DBG("-\n");
+
+ return ret;
+}
+
+static int dwc3_otg_stop_peripheral(struct dwc3_otg *dwc_otg)
+{
+ int ret;
+ unsigned long flags;
+ struct dwc3 *dwc = dwc_otg->dwc;
+
+ DBG("+\n");
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ ret = dwc3_gadget_suspend(dwc);
+ if (ret)
+ pr_err("[%s] gadget suspend error!", __func__);
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ DBG("-\n");
+
+ return ret;
+}
+
+int dwc3_otg_id_value(struct dwc3_otg *dwc_otg)
+{
+ if (dwc_otg)
+ return !!(dwc3_readl(dwc_otg->dwc->regs, DWC3_OSTS)
+ & DWC3_OSTS_CONIDSTS);
+ else
+ return 1;
+}
+
+int dwc3_otg_work(struct dwc3_otg *dwc_otg, int evt)
+{
+ int ret = 0;
+
+ DBG("+\n evt = %d", evt);
+
+ /* if otg is not enabled, do nothing */
+ if (!dwc_otg) {
+ pr_info("%s: dwc3 is not otg mode!\n", __func__);
+ return 0;
+ }
+
+ switch (evt) {
+ case DWC3_OTG_EVT_ID_SET:
+ dwc3_otg_stop_host(dwc_otg);
+ dwc3_suspend_device(dwc_otg->dwc);
+ break;
+ case DWC3_OTG_EVT_ID_CLEAR:
+ ret = dwc3_resume_device(dwc_otg->dwc);
+ if (ret) {
+ pr_err("%s: resume device failed!\n", __func__);
+ return ret;
+ }
+ ret = dwc3_otg_start_host(dwc_otg);
+ if (ret) {
+ pr_err("%s: start host failed!\n", __func__);
+ dwc3_suspend_device(dwc_otg->dwc);
+ return ret;
+ }
+ break;
+ case DWC3_OTG_EVT_VBUS_SET:
+ ret = dwc3_resume_device(dwc_otg->dwc);
+ if (ret) {
+ pr_err("%s: resume device failed!\n", __func__);
+ return ret;
+ }
+ ret = dwc3_otg_start_peripheral(dwc_otg);
+ if (ret) {
+ pr_err("%s: start peripheral failed!\n", __func__);
+ dwc3_suspend_device(dwc_otg->dwc);
+ return ret;
+ }
+ break;
+ case DWC3_OTG_EVT_VBUS_CLEAR:
+ ret = dwc3_otg_stop_peripheral(dwc_otg);
+ dwc3_suspend_device(dwc_otg->dwc);
+ break;
+ default:
+ break;
+ }
+ DBG("-\n");
+
+ return ret;
+}
+
+static void dwc3_otg_work_fun(struct work_struct *w)
+{
+ struct dwc3_otg *dwc_otg = container_of(
+ w, struct dwc3_otg, otg_work.work);
+
+ mutex_lock(&dwc_otg->lock);
+ if (dwc3_otg_work(dwc_otg, atomic_read(&dwc_otg->otg_evt_flag)))
+ pr_err("%s: dwc3_otg_work failed\n", __func__);
+ mutex_unlock(&dwc_otg->lock);
+}
+
+int dwc3_otg_init(struct dwc3 *dwc)
+{
+ struct dwc3_otg *dwc_otg;
+ u32 reg;
+
+ DBG("+\n");
+
+ dwc_otg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL);
+ if (!dwc_otg) {
+ dev_err(dwc->dev, "unable to allocate dwc3_otg\n");
+ 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");
+}