]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/commitdiff
usb: tegra: xotg: add XOTG driver
authorMark Kuo <mkuo@nvidia.com>
Fri, 18 Nov 2016 08:23:36 +0000 (16:23 +0800)
committermobile promotions <svcmobile_promotions@nvidia.com>
Wed, 15 Feb 2017 00:06:54 +0000 (16:06 -0800)
Add a new XOTG driver.

The new XOTG driver is based on the new generic phy framework with OTG
core and OTG FSM drivers.

This driver now supports SRP and HNP defined in OTG 2.0 spec. V1.3 HNP
is also supported. VBUS session support is default off and can be turned
on with module parameter.

Bug 200150158

Change-Id: I8be00e781d091e916a6d107dbe2a2324c379cbb8
Signed-off-by: Mark Kuo <mkuo@nvidia.com>
Reviewed-on: http://git-master/r/1282130
GVS: Gerrit_Virtual_Submit
Reviewed-by: ChihMin Cheng <ccheng@nvidia.com>
Reviewed-by: Ashutosh Jha <ajha@nvidia.com>
Documentation/devicetree/bindings/usb/nvidia,tegra186-xotg.txt [new file with mode: 0644]
drivers/usb/phy/Kconfig
drivers/usb/phy/Makefile
drivers/usb/phy/tegra-xotg.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra186-xotg.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra186-xotg.txt
new file mode 100644 (file)
index 0000000..1a12c75
--- /dev/null
@@ -0,0 +1,54 @@
+Device tree binding for NVIDIA Tegra XUSB OTG Controller Driver (XOTG)
+============================
+
+The Tegra XOTG driver works with Tegra host and device controller drivers to
+provide OTG2.0 (OTG3.0 supported, but not implemented yet in SW)
+functionalities. The host and device controller drivers must both contain
+otg-controller phandle pointing to XOTG node. Host or device driver only
+depends on XOTG driver. If a system has OTG port but either XHCI or XUDC
+is not enabled, the port still works fine as long as XOTG is enabled. In
+other words, either one or both XHCI and XUDC should work fine as long as
+XOTG is enabled.
+
+Required properties:
+--------------------
+ - compatible: For Tegra186, must contain "nvidia,tegra186-xotg".
+
+Required properties for Tegra186:
+---------------------------------
+ - interrupts: interrupt line for XOTG
+ - phys: Must contain at least an entry for USB2.0 phy for the OTG port, and
+   at most an additional entry for USB3.0 phy if the OTG port is USB3.0.
+   See ../phy/phy-bindings.txt for details.
+ - phy-names: Should include at least an entry for USB2.0 phy and at most
+   another entry for USB3.0 phy.
+   Names must be "otg-usb2", and "otg-usb3" if the port supports SuperSpeed.
+   - "otg-usb3" phy, SuperSpeed (SSTX+/SSTX-/SSRX+/SSRX-) data lines
+   - "otg-usb2" phy, USB 2.0 (D+/D-) data lines
+ - extcon-cables: Must contains 2 cables for ID and VBUS detection.
+   See ../extcon/extcon-gpio-states.txt for details.
+ - extcon-cable-names: Must be "id" and "vbus".
+
+Optional properties:
+--------------------
+ - otg-rev: See generic.txt for details
+ - hnp-disable: See generic.txt for details
+ - adp-disable: See generic.txt for details
+ - srp-disable: See generic.txt for details
+ - rsp-disable: See generic.txt for details
+
+Example:
+--------
+       xotg {
+               compatible = "nvidia,tegra186-xotg";
+        interrupts = <0 167 0x4>;
+               phys = <&padctl TEGRA_PADCTL_UTMI_P(0)>, /* USB 3.0 micro-AB, J503 */
+                       <&padctl TEGRA_PADCTL_USB3_P(1)>; /* USB 3.0 micro-AB, J503 */
+               phy-names = "otg-usb2", "otg-usb3";
+               extcon-cables = <&vbus_id_extcon 1>, <&vbus_id_extcon 0>;
+               extcon-cable-names = "id", "vbus";
+               #extcon-cells = <1>;
+
+        otg-rev = <0x0300>;
+        adp-disable;
+       };
index e83e3c91494565a0ea7ee321deabbe4c0b66b3e2..2b4c01607ecc44a400f72ea5101a9edea993d56f 100644 (file)
@@ -214,6 +214,14 @@ config USB_TEGRA_OTG
           Enable this driver on boards which use the internal VBUS and ID
           sensing of the Tegra USB PHY.
 
+config USB_TEGRA_XOTG
+       bool "Tegra XOTG Driver"
+       depends on USB && (ARCH_TEGRA || ARCH_TEGRA_18x_SOC)
+       help
+         Enable this driver on boards with XUSB OTG port. This is the Nvidia
+         XOTG driver which implements OTG spec. We will need this enabled for
+         OTG port to work.
+
 config USB_ULPI
        bool "Generic ULPI Transceiver Driver"
        depends on ARM || ARM64
index a4230fe98adf33eba158dcd503b126d3c366026d..380a8effc58be5bd22a2991debb3fc4378452e41 100644 (file)
@@ -28,6 +28,8 @@ obj-$(CONFIG_USB_MV_OTG)              += phy-mv-usb.o
 obj-$(CONFIG_USB_TEGRA_OTG)            += tegra-otg.o
 obj-$(CONFIG_USB_MXS_PHY)              += phy-mxs-usb.o
 obj-$(CONFIG_USB_RCAR_PHY)             += phy-rcar-usb.o
+obj-$(CONFIG_USB_TEGRA_XOTG)   += tegra-xotg.o
+CFLAGS_tegra-xotg.o                            = -Werror
 obj-$(CONFIG_USB_ULPI)                 += phy-ulpi.o
 obj-$(CONFIG_USB_ULPI_VIEWPORT)                += phy-ulpi-viewport.o
 obj-$(CONFIG_KEYSTONE_USB_PHY)         += phy-keystone.o
diff --git a/drivers/usb/phy/tegra-xotg.c b/drivers/usb/phy/tegra-xotg.c
new file mode 100644 (file)
index 0000000..0cf0c13
--- /dev/null
@@ -0,0 +1,1528 @@
+/*
+* tegra-xotg.c - Nvidia XUSB OTG Controller Driver
+*
+* Copyright (c) 2017, NVIDIA CORPORATION.  All rights reserved.
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+* more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/extcon.h>
+#include <linux/hrtimer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
+#include <linux/workqueue.h>
+
+#include <soc/tegra/xusb.h>
+
+/* OTG timer values : A-Device */
+
+/*
+ * wait for VBUS rise
+ * Section 4.4, Table 4-1, OTG2.0 [,100]ms
+ * a_wait_vrise: section 7.1.2
+ * a_wait_vrise_tmr: section 7.4.5.1
+ */
+#define XOTG_TA_VBUS_RISE      100
+
+/*
+ * wait for VBUS fall
+ * Table 4-1, OTG2.0
+ * a_wait_vfall: section 7.1.7
+ * a_wait_vfall_tmr: section: 7.4.5.2: TSSEND_LKG
+ */
+#define XOTG_TA_WAIT_VFALL     1000
+
+/*
+ * wait for B-Connect
+ * Section 5.5, Table 5-1, OTG2.0 [1.1,30]s
+ * a_wait_bcon: section 7.1.3
+ */
+#define XOTG_TA_WAIT_BCON      9000
+
+/*
+ * A-Idle to B-Disconnect for A-device
+ * Section 5.5, Table 5-1, OTG2.0 [200,]ms
+ * a_suspend min 200 ms, section 5.2.1
+ */
+#define XOTG_TA_AIDL_BDIS      250
+
+/*
+ * B-Idle to A-Disconnect
+ * Section 5.2.1, Table 5-1, OTG2.0 [155,200]ms
+ */
+#define XOTG_TA_BIDL_ADIS      155
+
+/* OTG timer values : B-Device */
+
+/*
+ * A-SE0 to B-Reset
+ * Section 5.3.1, Table 5-1, OTG2.0 [155,]ms
+ */
+#define XOTG_TB_ASE0_BRST      2000
+
+/*
+ * SE0 Time Before SRP
+ * Section 5.1.2, Table 5-1, OTG2.0 [1,]s
+ */
+#define XOTG_TB_SE0_SRP                1000
+
+/*
+ * SRP Fail Time
+ * Section 5.1.6, Table 5-1, OTG2.0 [5,6]s
+ */
+#define XOTG_TB_SRP_FAIL       5500
+
+/*
+ * SSEND time before SRP
+ * Section 5.1.2, Table 5-1, OTG2.0
+ */
+#define XOTG_TB_SSEND_SRP      1600
+
+/*
+ * A-Idle to B-Disconnect for B-device
+ * Section 5.2.1, Table 5-1, OTG2.0 [4,150]ms
+ */
+#define XOTG_TB_AIDL_BDIS      4
+
+/*
+ * Maintaining configured session on test device
+ * Table 5-1
+ */
+#define XOTG_TA_TST_MAINT      9900
+
+/*
+ * Session support is disabled by default or most majority of devices won't
+ * be able to initiate SRP in case VBUS gets turned off. This should be
+ * enabled before performing USB-PET tests.
+ */
+static bool session_support;
+module_param(session_support, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(session_support, "VBUS session support");
+
+struct tegra_xotg_timer {
+       enum otg_fsm_timer id;
+       struct hrtimer timer;
+       ktime_t timeout;
+       int *timeout_bit; /* pointer to variable that is set on timeout */
+       struct tegra_xotg *xotg;
+};
+
+struct tegra_xotg_soc_config {
+       int (*utmi_vbus_power_on)(struct phy *phy);
+       int (*utmi_vbus_power_off)(struct phy *phy);
+       int (*set_id_override)(struct phy *phy);
+       int (*clear_id_override)(struct phy *phy);
+       int (*set_vbus_override)(struct phy *phy);
+       int (*clear_vbus_override)(struct phy *phy);
+       bool (*has_otg_cap)(struct phy *phy);
+       int (*set_reverse_id)(struct phy *phy);
+       int (*clear_reverse_id)(struct phy *phy);
+       int (*generate_srp)(struct phy *phy);
+       int (*enable_srp)(struct phy *phy);
+       int (*disable_srp)(struct phy *phy);
+       bool (*srp_detected)(struct phy *phy);
+       int (*enable_otg_int)(struct phy *phy);
+       int (*disable_otg_int)(struct phy *phy);
+       int (*ack_otg_int)(struct phy *phy);
+       int (*get_otg_vbus_id)(struct phy *phy,
+                       struct tegra_xusb_otg_vbus_id *);
+};
+
+struct tegra_xotg {
+       struct device *dev;
+
+       const struct tegra_xotg_soc_config *soc_config;
+
+       /* USB OTG controller structure */
+       struct usb_otg *otg;
+       spinlock_t lock; /* note that there is already a mutex for otg_fsm */
+
+       bool vbus_enabled; /* current vbus state (XOTG SW state) */
+
+       /* OTG port phy */
+       struct phy *usb2_phy;
+       struct phy *usb3_phy; /* (optional) */
+
+       /* OTG timers for OTG FSM */
+       struct tegra_xotg_timer timers[NUM_OTG_FSM_TIMERS];
+
+       /* extcon for ID pin and VBUS */
+       struct extcon_dev *id_extcon;
+       struct extcon_dev *vbus_extcon;
+       struct notifier_block id_extcon_nb;
+       struct notifier_block vbus_extcon_nb;
+
+       /* OTG event handler */
+       struct notifier_block otg_nb;
+
+       /* USB-PET support */
+       bool test_device_enumerated;
+};
+
+/* timer callback to set timeout bit and kick FSM */
+static enum hrtimer_restart tegra_xotg_timer_timeout(struct hrtimer *data)
+{
+       struct tegra_xotg_timer *otgtimer;
+       struct tegra_xotg *xotg;
+
+       otgtimer = container_of(data, struct tegra_xotg_timer, timer);
+       xotg = otgtimer->xotg;
+
+       dev_dbg(xotg->dev, "timer [%s] timeout\n",
+               otg_fsm_timer_string(otgtimer->id));
+       if (otgtimer->timeout_bit)
+               *otgtimer->timeout_bit = 1;
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return HRTIMER_NORESTART;
+}
+
+/* Initialize one OTG timer with callback, timeout and timeout bit */
+static void tegra_xotg_timer_init(struct tegra_xotg *xotg,
+       enum otg_fsm_timer id, unsigned long expires_ms, int *timeout_bit)
+{
+       struct tegra_xotg_timer *otgtimer = &xotg->timers[id];
+       struct hrtimer *timer = &otgtimer->timer;
+
+       otgtimer->id = id;
+       otgtimer->timeout = ms_to_ktime(expires_ms);
+       hrtimer_init(timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+       timer->function = tegra_xotg_timer_timeout;
+
+       otgtimer->timeout_bit = timeout_bit;
+       otgtimer->xotg = xotg;
+}
+
+static void tegra_xotg_init_timers(struct tegra_xotg *xotg)
+{
+       struct otg_fsm *fsm = &xotg->otg->fsm;
+
+       tegra_xotg_timer_init(xotg, A_WAIT_VRISE, XOTG_TA_VBUS_RISE,
+                             &fsm->a_wait_vrise_tmout);
+       tegra_xotg_timer_init(xotg, A_WAIT_VFALL, XOTG_TA_WAIT_VFALL,
+                             &fsm->a_wait_vfall_tmout);
+       tegra_xotg_timer_init(xotg, A_WAIT_BCON, XOTG_TA_WAIT_BCON,
+                             &fsm->a_wait_bcon_tmout);
+       tegra_xotg_timer_init(xotg, A_AIDL_BDIS, XOTG_TA_AIDL_BDIS,
+                             &fsm->a_aidl_bdis_tmout);
+       tegra_xotg_timer_init(xotg, A_BIDL_ADIS, XOTG_TA_BIDL_ADIS,
+                             &fsm->a_bidl_adis_tmout);
+       tegra_xotg_timer_init(xotg, B_ASE0_BRST, XOTG_TB_ASE0_BRST,
+                             &fsm->b_ase0_brst_tmout);
+       tegra_xotg_timer_init(xotg, B_SE0_SRP, XOTG_TB_SE0_SRP,
+                             &fsm->b_se0_srp);
+       tegra_xotg_timer_init(xotg, B_SRP_FAIL, XOTG_TB_SRP_FAIL,
+                             &fsm->b_srp_done);
+       tegra_xotg_timer_init(xotg, B_SSEND_SRP, XOTG_TB_SSEND_SRP,
+                             &fsm->b_ssend_srp);
+       tegra_xotg_timer_init(xotg, B_AIDL_BDIS, XOTG_TB_AIDL_BDIS,
+                             &fsm->b_aidl_bdis_tmout);
+       tegra_xotg_timer_init(xotg, TST_MAINT, XOTG_TA_TST_MAINT,
+                             &fsm->tst_maint_tmout);
+}
+
+static int tegra_xotg_set_vbus(struct tegra_xotg *xotg, bool on)
+{
+       int ret = 0;
+
+       if (on && !xotg->vbus_enabled) {
+               if (xotg->soc_config->utmi_vbus_power_on)
+                       ret = xotg->soc_config->utmi_vbus_power_on(
+                                       xotg->usb2_phy);
+               if (ret) {
+                       dev_err(xotg->dev, "failed to enable vbus: %d\n", ret);
+                       return ret;
+               }
+               dev_dbg(xotg->dev, "OTG port vbus enabled\n");
+               xotg->vbus_enabled = true;
+       } else if (!on && xotg->vbus_enabled) {
+               if (xotg->soc_config->utmi_vbus_power_off)
+                       ret = xotg->soc_config->utmi_vbus_power_off(
+                                       xotg->usb2_phy);
+               if (ret) {
+                       dev_err(xotg->dev, "failed to disable vbus: %d\n", ret);
+                       return ret;
+               }
+               xotg->vbus_enabled = false;
+               dev_dbg(xotg->dev, "OTG port vbus disabled\n");
+       } else {
+               dev_warn(xotg->dev, "vbus already %sabled, not turning %s\n",
+                               xotg->vbus_enabled ? "en" : "dis",
+                               on ? "on" : "off");
+       }
+
+       return ret;
+}
+
+static int tegra_xotg_get_id_from_rid(enum tegra_xusb_vbus_rid rid)
+{
+       if (rid == VBUS_ID_RID_A || rid == VBUS_ID_RID_GND) {
+               /* micro-A end attached */
+               return 0;
+       } else if (rid == VBUS_ID_RID_B || rid == VBUS_ID_RID_C ||
+                       rid == VBUS_ID_RID_FLOAT) {
+               return 1;
+       }
+       return 0;
+}
+
+static bool __tegra_xotg_update_inputs(struct tegra_xotg *xotg,
+               struct tegra_xusb_otg_vbus_id *info, bool only_when_changed)
+{
+       bool changed = false;
+       bool ever_changed = false;
+       int id;
+
+       /* IDDIG */
+       changed = false;
+       if (info->iddig_chg) {
+               id = tegra_xotg_get_id_from_rid(info->iddig);
+               dev_dbg(xotg->dev, "IDDIG changed: %u (ID:%d)\n",
+                               info->iddig, id);
+               changed = ever_changed = true;
+       }
+       if (changed || !only_when_changed) {
+               if (id) /* B-device */
+                       dev_dbg(xotg->dev, "IDDIG -> B_IDLE\n");
+               else    /* A-device */
+                       dev_dbg(xotg->dev, "IDDIG -> A_IDLE\n");
+       }
+
+       /* VBUS session valid */
+       changed = false;
+       if (info->vbus_sess_vld_chg) {
+               dev_dbg(xotg->dev, "VBUS_SESS_VLD changed: %u->%u\n",
+                               !info->vbus_sess_vld, info->vbus_sess_vld);
+               changed = ever_changed = true;
+       }
+       if (changed || !only_when_changed)
+               xotg->otg->fsm.b_sess_vld = info->vbus_sess_vld;
+
+       /* VBUS valid */
+       changed = false;
+       if (info->vbus_vld_chg) {
+               dev_dbg(xotg->dev, "VBUS_VLD changed: %u->%u\n",
+                               !info->vbus_vld, info->vbus_vld);
+               changed = ever_changed = true;
+       }
+
+       /*
+        * sometimes VBUS_VLD could do 1->0 even when we already enable vbus,
+        * so not setting a_vbus_vld from info->vbus_vld
+        */
+
+       /* VBUS wakeup: not used */
+       changed = false;
+       if (info->vbus_wakeup_chg) {
+               dev_dbg(xotg->dev, "VBUS_WAKEUP changed: %u->%u\n",
+                               !info->vbus_wakeup, info->vbus_wakeup);
+               changed = ever_changed = true;
+       }
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return ever_changed;
+}
+
+static bool tegra_xotg_update_inputs(struct tegra_xotg *xotg,
+               bool only_when_changed)
+{
+       struct tegra_xusb_otg_vbus_id info;
+
+       if (xotg->soc_config->get_otg_vbus_id)
+               xotg->soc_config->get_otg_vbus_id(xotg->usb2_phy, &info);
+       return __tegra_xotg_update_inputs(xotg, &info, only_when_changed);
+}
+
+static void tegra_xotg_drv_vbus(struct usb_otg *otg, int on)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+
+       dev_dbg(xotg->dev, "%s: %s\n", __func__, on ? "on" : "off");
+
+       tegra_xotg_set_vbus(xotg, on);
+
+       xotg->otg->fsm.a_vbus_vld = xotg->vbus_enabled;
+       usb_otg_sync_inputs(xotg->otg);
+}
+
+static void tegra_xotg_enable_srp(struct usb_otg *otg)
+{
+       struct tegra_xotg *xotg = dev_get_drvdata(otg->dev);
+
+       dev_dbg(xotg->dev, "enable SRP detection\n");
+       if (xotg->soc_config->enable_srp)
+               xotg->soc_config->enable_srp(xotg->usb2_phy);
+}
+
+static void tegra_xotg_disable_srp(struct usb_otg *otg)
+{
+       struct tegra_xotg *xotg = dev_get_drvdata(otg->dev);
+
+       dev_dbg(xotg->dev, "disable SRP detection\n");
+       if (xotg->soc_config->disable_srp)
+               xotg->soc_config->disable_srp(xotg->usb2_phy);
+}
+
+static void tegra_xotg_a_hnp_enter(struct usb_otg *otg)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+       unsigned long flags;
+
+       dev_dbg(xotg->dev, "Enter A-device HNP\n");
+
+       dev_dbg(xotg->dev, "set reverse ID\n");
+       if (xotg->soc_config->set_reverse_id)
+               xotg->soc_config->set_reverse_id(xotg->usb2_phy);
+
+       dev_dbg(xotg->dev, "set vbus override\n");
+       spin_lock_irqsave(&xotg->lock, flags);
+       if (xotg->soc_config->set_vbus_override)
+               xotg->soc_config->set_vbus_override(xotg->usb2_phy);
+       spin_unlock_irqrestore(&xotg->lock, flags);
+
+       if (otg->gadget)
+               otg->gadget->is_a_peripheral = 1;
+}
+
+static void tegra_xotg_a_hnp_exit(struct usb_otg *otg)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+       unsigned long flags;
+
+       dev_dbg(xotg->dev, "Exit A-device HNP\n");
+
+       dev_dbg(xotg->dev, "clear reverse ID\n");
+       if (xotg->soc_config->clear_reverse_id)
+               xotg->soc_config->clear_reverse_id(xotg->usb2_phy);
+
+       dev_dbg(xotg->dev, "clear vbus override\n");
+       spin_lock_irqsave(&xotg->lock, flags);
+       if (xotg->soc_config->clear_vbus_override)
+               xotg->soc_config->clear_vbus_override(xotg->usb2_phy);
+       spin_unlock_irqrestore(&xotg->lock, flags);
+
+       if (otg->gadget)
+               otg->gadget->is_a_peripheral = 0;
+       /* A-device now wants to use the bus so set this */
+       xotg->otg->fsm.a_bus_req = 1;
+}
+
+static void tegra_xotg_b_hnp_enter(struct usb_otg *otg)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+
+       dev_dbg(xotg->dev, "Enter B-device HNP\n");
+
+       dev_dbg(xotg->dev, "set reverse ID\n");
+       if (xotg->soc_config->set_reverse_id)
+               xotg->soc_config->set_reverse_id(xotg->usb2_phy);
+
+       if (otg->hcd)
+               otg->hcd->self.is_b_host = 1;
+}
+
+static void tegra_xotg_b_hnp_exit(struct usb_otg *otg)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+
+       dev_dbg(xotg->dev, "Exit B-device HNP\n");
+
+       if (otg->gadget)
+               otg->gadget->b_hnp_enable = 0;
+
+       dev_dbg(xotg->dev, "clear reverse ID\n");
+       if (xotg->soc_config->clear_reverse_id)
+               xotg->soc_config->clear_reverse_id(xotg->usb2_phy);
+
+       if (otg->hcd)
+               otg->hcd->self.is_b_host = 0;
+}
+
+static void tegra_xotg_vbus_session(struct usb_otg *otg)
+{
+       /* do not turn on VBUS by not moving FSM to a_wait_vrise */
+       if (session_support) {
+               dev_dbg(otg->dev,
+                       "session supported, not moving to a_wait_vrise\n");
+               otg->fsm.a_bus_drop = 1;
+       }
+}
+
+static void tegra_xotg_start_pulse(struct usb_otg *otg)
+{
+       struct device *dev = otg->dev;
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "%s: [%s] generate SRP\n", __func__,
+               usb_otg_state_string(otg->state));
+
+       if (xotg->soc_config->generate_srp)
+               xotg->soc_config->generate_srp(xotg->usb2_phy);
+       otg->fsm.b_srp_done = 1;
+       otg->fsm.b_bus_req = 0;
+
+       usb_otg_sync_inputs(otg);
+}
+
+static void tegra_xotg_add_timer(struct usb_otg *otg, enum otg_fsm_timer id)
+{
+       struct tegra_xotg *xotg = dev_get_drvdata(otg->dev);
+       struct tegra_xotg_timer *otgtimer = &xotg->timers[id];
+       struct hrtimer *timer = &otgtimer->timer;
+
+       /* not defined in spec, ignore it */
+       if (id == A_WAIT_ENUM)
+               return;
+
+       if (!otg->fsm.running)
+               return;
+
+       /* if timer is already active, exit */
+       if (hrtimer_active(timer)) {
+               dev_err(xotg->dev, "timer [%s] is already running\n",
+               otg_fsm_timer_string(id));
+               return;
+       }
+
+       dev_dbg(xotg->dev, "timer [%s] started\n", otg_fsm_timer_string(id));
+       hrtimer_start(timer, otgtimer->timeout, HRTIMER_MODE_REL);
+}
+
+static void tegra_xotg_del_timer(struct usb_otg *otg, enum otg_fsm_timer id)
+{
+       struct tegra_xotg *xotg = dev_get_drvdata(otg->dev);
+       struct tegra_xotg_timer *otgtimer = &xotg->timers[id];
+       struct hrtimer *timer = &otgtimer->timer;
+       int ret;
+
+       /* not defined in spec, ignore it */
+       if (id == A_WAIT_ENUM)
+               return;
+
+       ret = hrtimer_cancel(timer);
+       if (ret != 0)
+               dev_dbg(xotg->dev, "timer [%s] cancelled\n",
+                       otg_fsm_timer_string(id));
+}
+
+static int tegra_xotg_start_host(struct usb_otg *otg, int on)
+{
+       struct device *dev = otg->dev;
+       int ret;
+
+       dev_dbg(dev, "host %s\n", on ? "on" : "off");
+
+       ret = usb_otg_start_host(otg, on);
+       otg->fsm.power_up = on;
+
+       usb_otg_sync_inputs(otg);
+
+       return ret;
+}
+
+static int tegra_xotg_start_gadget(struct usb_otg *otg, int on)
+{
+       struct device *dev = otg->dev;
+       int ret;
+
+       dev_dbg(dev, "gadget %s\n", on ? "on" : "off");
+
+       ret = usb_otg_start_gadget(otg, on);
+
+       if (on) {
+               otg->fsm.power_up = 1;
+       } else {
+               otg->fsm.power_up = 0;
+               otg->fsm.a_bus_suspend = 0;
+               otg->fsm.a_bus_resume = 0;
+       }
+
+       usb_otg_sync_inputs(otg);
+
+       return ret;
+}
+
+static int tegra_xotg_event_handler(struct notifier_block *nb,
+               unsigned long event, void *src_dev)
+{
+       struct tegra_xotg *xotg = container_of(nb, struct tegra_xotg,
+                       otg_nb);
+       struct usb_otg *otg = xotg->otg;
+
+       dev_dbg(xotg->dev, "event [%s] from %s, id=%d\n",
+                       usb_otg_event_string(event),
+                       dev_name((struct device *)src_dev),
+                       otg->fsm.id);
+
+       switch (event) {
+       case OTG_EVENT_HCD_PORT_SUSPEND:
+               otg->fsm.a_bus_req = 0;
+               break;
+       case OTG_EVENT_HCD_PORT_RESUME:
+               otg->fsm.a_bus_req = 1;
+               break;
+       case OTG_EVENT_PCD_PORT_CONNECT:
+               /* notified by PCD */
+               if (otg->fsm.id)
+                       /* B-peripheral has detected A-host's connection */
+                       otg->fsm.a_conn = 1;
+               else
+                       /* A-peripheral has detected B-host's connection */
+                       otg->fsm.b_conn = 1;
+               break;
+       case OTG_EVENT_PCD_PORT_DISCONNECT:
+               /* notified by PCD */
+               if (otg->fsm.id)
+                       /* B-peripheral has detected A-host's disconnection */
+                       otg->fsm.a_conn = 0;
+               else
+                       /* A-peripheral has detected B-host's disconnection */
+                       otg->fsm.b_conn = 0;
+               /* clear suspend state */
+               otg->fsm.a_bus_suspend = 0;
+               otg->fsm.b_bus_suspend = 0;
+               /* reset a_bus_drop */
+               otg->fsm.a_bus_drop = 0;
+               break;
+       case OTG_EVENT_PCD_PORT_RESUME:
+               otg->fsm.a_bus_suspend = 0;
+               otg->fsm.a_bus_resume = 1;
+               break;
+       case OTG_EVENT_PCD_PORT_SUSPEND:
+               otg->fsm.a_bus_suspend = 1;
+               otg->fsm.a_bus_resume = 0;
+               /*
+                * when A-device in A-peripheral state, if PCD port enters
+                * suspend state, set b_bus_suspend so timer A_BIDL_ADIS will
+                * start in OTG FSM. When the timer expires, the A-device
+                * should transition from a_peripheral to a_wait_bcon state
+                */
+               if (otg->state == OTG_STATE_A_PERIPHERAL)
+                       otg->fsm.b_bus_suspend = 1;
+               break;
+       case OTG_EVENT_HCD_PORT_CONNECT:
+               /* notified by HCD (hub.c) */
+               if (otg->fsm.id)
+                       /* B-host has detected A-peripheral's connection */
+                       otg->fsm.a_conn = 1;
+               else
+                       /* A-host has detected B-peripheral's connection */
+                       otg->fsm.b_conn = 1;
+               break;
+       case OTG_EVENT_HCD_PORT_DISCONNECT:
+               /* notified by HCD (hub.c) */
+               if (otg->fsm.id)
+                       /* B-host has detected A-peripheral's disconnection */
+                       otg->fsm.a_conn = 0;
+               else
+                       /* A-host has detected B-peripheral's disconnection */
+                       otg->fsm.b_conn = 0;
+               /* avoid starting TST_MAINT when device is disconnected */
+               otg->fsm.start_tst_maint_timer = 0;
+               /* reset a-variables */
+               otg->fsm.a_bus_req = 1;
+               break;
+       case OTG_EVENT_HCD_TEST_DEVICE:
+               otg->fsm.start_tst_maint_timer = 1;
+               xotg->test_device_enumerated = 1;
+               break;
+       }
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return NOTIFY_OK;
+}
+
+static struct otg_fsm_ops tegra_xotg_fsm_ops = {
+       .drv_vbus = tegra_xotg_drv_vbus,
+       .start_pulse = tegra_xotg_start_pulse,
+       .add_timer = tegra_xotg_add_timer,
+       .del_timer = tegra_xotg_del_timer,
+       .start_host = tegra_xotg_start_host,
+       .start_gadget = tegra_xotg_start_gadget,
+};
+
+static void tegra_xotg_work(struct work_struct *work)
+{
+       struct usb_otg *otg = container_of(work, struct usb_otg, work);
+
+       pm_runtime_get_sync(otg->dev);
+       while (otg_statemachine(otg))
+               ;
+       pm_runtime_put_sync(otg->dev);
+}
+
+static struct usb_otg_config tegra_xotg_otg_config = {
+       .otg_caps = {
+               /*
+                * we support rev 3.0, but set rev 2.0 because RSP hasn't
+                * been implemented in this driver as of now
+                * note that if device tree (DT) has specified different
+                * version, the lesser one will be selected
+                * TODO: implement RSP and change to 0x0300
+                */
+               .otg_rev = 0x0200,
+               /*
+                * note that the following 4 properties can be overwritten by
+                * DT configuration
+                */
+               .hnp_support = true,
+               .srp_support = true,
+               .adp_support = false, /* Nvidia XOTG doesn't support ADP */
+               .rsp_support = false, /* TODO: to implement */
+       },
+       .fsm_ops = &tegra_xotg_fsm_ops,
+       .otg_work = &tegra_xotg_work,
+};
+
+static irqreturn_t tegra_xotg_irq(int irq, void *data)
+{
+       struct tegra_xotg *xotg = (struct tegra_xotg *)data;
+       bool srp_detected = false;
+       unsigned long flags;
+
+
+       spin_lock_irqsave(&xotg->lock, flags);
+       if (xotg->soc_config->srp_detected)
+               srp_detected = xotg->soc_config->srp_detected(xotg->usb2_phy);
+       /* update FSM inputs according to register state */
+       if (!(tegra_xotg_update_inputs(xotg, true) || srp_detected)) {
+               dev_vdbg(xotg->dev, "padctl interrupt isn't for XOTG\n");
+               spin_unlock_irqrestore(&xotg->lock, flags);
+               return IRQ_NONE;
+       }
+
+       dev_vdbg(xotg->dev, "XOTG interrupt\n");
+
+       /* handle SRP detection */
+       if (srp_detected) {
+               dev_info(xotg->dev, "SRP detected\n");
+               if (xotg->soc_config->disable_srp)
+                       xotg->soc_config->disable_srp(xotg->usb2_phy);
+               xotg->otg->fsm.a_srp_det = 1;
+               xotg->otg->fsm.a_bus_drop = 0;
+       } else
+               xotg->otg->fsm.a_srp_det = 0;
+
+       /* ack the OTG interrupt */
+       if (xotg->soc_config->ack_otg_int)
+               xotg->soc_config->ack_otg_int(xotg->usb2_phy);
+       spin_unlock_irqrestore(&xotg->lock, flags);
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return IRQ_HANDLED;
+}
+
+static void update_id_state(struct tegra_xotg *xotg)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&xotg->lock, flags);
+       if (extcon_get_cable_state_(xotg->id_extcon, EXTCON_USB_HOST)) {
+               dev_info(xotg->dev, "%s: ID grounded\n", __func__);
+               if (xotg->soc_config->set_id_override)
+                       xotg->soc_config->set_id_override(xotg->usb2_phy);
+               xotg->otg->fsm.id = 0;
+       } else {
+               dev_info(xotg->dev, "%s: ID floating\n", __func__);
+               if (xotg->soc_config->clear_id_override)
+                       xotg->soc_config->clear_id_override(xotg->usb2_phy);
+               xotg->otg->fsm.id = 1;
+       }
+       spin_unlock_irqrestore(&xotg->lock, flags);
+       usb_otg_sync_inputs(xotg->otg);
+}
+
+static void update_vbus_state(struct tegra_xotg *xotg)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&xotg->lock, flags);
+       if (extcon_get_cable_state_(xotg->vbus_extcon, EXTCON_USB)) {
+               dev_info(xotg->dev, "%s: VBUS detected\n", __func__);
+               if (xotg->soc_config->set_vbus_override)
+                       xotg->soc_config->set_vbus_override(xotg->usb2_phy);
+               xotg->otg->fsm.b_sess_vld = 1;
+       } else {
+               dev_info(xotg->dev, "%s: VBUS not detected\n", __func__);
+               /*
+                * don't do this again because when host mode is active,
+                * if we clear vbus_override, USB device will disconnect
+                */
+               if (xotg->otg->fsm.b_sess_vld &&
+                               xotg->soc_config->clear_vbus_override)
+                       xotg->soc_config->clear_vbus_override(xotg->usb2_phy);
+               xotg->otg->fsm.b_sess_vld = 0;
+       }
+       spin_unlock_irqrestore(&xotg->lock, flags);
+       usb_otg_sync_inputs(xotg->otg);
+}
+
+static void tegra_xotg_update_data_role(struct tegra_xotg *xotg)
+{
+       dev_dbg(xotg->dev, "sync FSM according to extcon state\n");
+       update_id_state(xotg);
+       update_vbus_state(xotg);
+}
+
+static int tegra_xotg_id_extcon_notifier(struct notifier_block *nb,
+               unsigned long action, void *data)
+{
+       struct tegra_xotg *xotg = container_of(nb, struct tegra_xotg,
+                       id_extcon_nb);
+
+       update_id_state(xotg);
+
+       return NOTIFY_DONE;
+}
+
+static int tegra_xotg_vbus_extcon_notifier(struct notifier_block *nb,
+               unsigned long action, void *data)
+{
+       struct tegra_xotg *xotg = container_of(nb, struct tegra_xotg,
+                       vbus_extcon_nb);
+
+       update_vbus_state(xotg);
+
+       return NOTIFY_DONE;
+}
+
+static ssize_t otg_state_show(struct device *dev, struct device_attribute *attr,
+       char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct tegra_xotg *xotg = platform_get_drvdata(pdev);
+       struct otg_fsm *fsm = &xotg->otg->fsm;
+       struct usb_hcd *hcd = xotg->otg->hcd;
+       struct usb_gadget *gadget = xotg->otg->gadget;
+       unsigned size = PAGE_SIZE;
+       char *next = buf;
+       int t, i;
+       struct tegra_xotg_timer *timer;
+
+       mutex_lock(&fsm->lock);
+
+       /* OTG state and driver info */
+       t = scnprintf(next, size,
+                       "OTG state: [%s]\n"
+                       "OTG port vbus: %s\n"
+                       "HCD: %s\n"
+                       "HCD usb_bus otg_port: %d\n"
+                       "HCD usb_bus is_b_host: %d\n"
+                       "HCD usb_bus b_hnp_enable: %d\n"
+                       "HCD usb_bus otg_quick_hnp: %d\n"
+                       "HCD usb_bus otgv13_hnp: %d\n\n"
+                       "PCD: %s\n"
+                       "PCD gadget is_otg: %d\n"
+                       "PCD gadget is_a_peripheral: %d\n"
+                       "PCD gadget b_hnp_enable: %d\n"
+                       "PCD gadget a_hnp_support: %d\n"
+                       "PCD gadget a_alt_hnp_support: %d\n"
+                       "PCD gadget hnp_polling_support: %d\n"
+                       "PCD gadget host_request_flag: %d\n"
+                       "PCD gadget otg_srp_reqd: %d\n"
+                       "PCD gadget otg_hnp_reqd: %d\n\n",
+                       usb_otg_state_string(xotg->otg->state),
+                       xotg->vbus_enabled ? "on" : "off",
+                       hcd ? dev_name(hcd->self.controller) : "not available",
+                       hcd ? hcd->self.otg_port : -1,
+                       hcd ? hcd->self.is_b_host : -1,
+                       hcd ? hcd->self.b_hnp_enable : -1,
+                       hcd ? hcd->self.otg_quick_hnp : -1,
+                       hcd ? hcd->self.otgv13_hnp : -1,
+                       gadget ? dev_name(gadget->dev.parent) : "not available",
+                       gadget ? gadget->is_otg : -1,
+                       gadget ? gadget->is_a_peripheral : -1,
+                       gadget ? gadget->b_hnp_enable : -1,
+                       gadget ? gadget->a_hnp_support : -1,
+                       gadget ? gadget->a_alt_hnp_support : -1,
+                       gadget ? gadget->hnp_polling_support : -1,
+                       gadget ? gadget->host_request_flag : -1,
+                       gadget ? gadget->otg_srp_reqd : -1,
+                       gadget ? gadget->otg_hnp_reqd : -1);
+       size -= t;
+       next += t;
+
+       /* State machine variables */
+       t = scnprintf(next, size,
+                       "Inputs:\n"
+                       "------------------\n"
+                       "id: %d\n"
+                       "adp_change: %d\n"
+                       "power_up: %d\n"
+                       "a_srp_det: %d\n"
+                       "a_vbus_vld: %d\n"
+                       "b_conn: %d\n"
+                       "a_bus_resume: %d\n"
+                       "a_bus_suspend: %d\n"
+                       "a_conn: %d\n"
+                       "b_se0_srp: %d\n"
+                       "b_ssend_srp: %d\n"
+                       "b_sess_vld: %d\n"
+                       "test_device: %d\n"
+                       "a_bus_drop: %d\n"
+                       "a_bus_req: %d\n"
+                       "b_bus_req: %d\n"
+                       "\n"
+                       "Auxilary inputs:\n"
+                       "------------------\n"
+                       "b_bus_suspend: %d\n"
+                       "\n"
+                       "Output:\n"
+                       "------------------\n"
+                       "drv_vbus: %d\n"
+                       "loc_conn: %d\n"
+                       "loc_sof: %d\n"
+                       "adp_prb: %d\n"
+                       "adp_sns: %d\n"
+                       "data_pulse: %d\n"
+                       "\n"
+                       "Internal variables:\n"
+                       "------------------\n"
+                       "a_set_b_hnp_en: %d\n"
+                       "b_srp_done: %d\n"
+                       "b_hnp_enable: %d\n"
+                       "a_clr_err: %d\n"
+                       "a_suspend_req_inf: %d\n"
+                       "otg_vbus_off: %d\n"
+                       "\n",
+                       fsm->id,
+                       fsm->adp_change,
+                       fsm->power_up,
+                       fsm->a_srp_det,
+                       fsm->a_vbus_vld,
+                       fsm->b_conn,
+                       fsm->a_bus_resume,
+                       fsm->a_bus_suspend,
+                       fsm->a_conn,
+                       fsm->b_se0_srp,
+                       fsm->b_ssend_srp,
+                       fsm->b_sess_vld,
+                       fsm->test_device,
+                       fsm->a_bus_drop,
+                       fsm->a_bus_req,
+                       fsm->b_bus_req,
+                       fsm->b_bus_suspend,
+                       fsm->drv_vbus,
+                       fsm->loc_conn,
+                       fsm->loc_sof,
+                       fsm->adp_prb,
+                       fsm->adp_sns,
+                       fsm->data_pulse,
+                       fsm->a_set_b_hnp_en,
+                       fsm->b_srp_done,
+                       fsm->b_hnp_enable,
+                       fsm->a_clr_err,
+                       fsm->a_suspend_req_inf,
+                       fsm->otg_vbus_off);
+       size -= t;
+       next += t;
+
+       /* Timer */
+       t = scnprintf(next, size,
+                       "Timers:\n"
+                       "------------------\n");
+       size -= t;
+       next += t;
+
+       for (i = 0; i < NUM_OTG_FSM_TIMERS; i++) {
+               timer = &xotg->timers[i];
+               t = scnprintf(next, size,
+                               "%s (%lld ms): \ttimeout: %d\n",
+                               otg_fsm_timer_string(i),
+                               ktime_to_ms(timer->timeout),
+                               timer->timeout_bit ? *(timer->timeout_bit) : 0);
+               size -= t;
+               next += t;
+       }
+
+       mutex_unlock(&fsm->lock);
+
+       return PAGE_SIZE - size;
+}
+static DEVICE_ATTR_RO(otg_state);
+
+static int tegra_xotg_start_srp(struct tegra_xotg *xotg)
+{
+       if (xotg->otg->state != OTG_STATE_B_IDLE) {
+               dev_warn(xotg->dev, "%s: skip SRP, state = %s (!=b_idle)\n",
+                       __func__,
+                       usb_otg_state_string(xotg->otg->state));
+               return -ENODEV;
+       }
+
+       dev_dbg(xotg->dev, "%s, state = %s\n", __func__,
+                       usb_otg_state_string(xotg->otg->state));
+
+       /*
+        * The detailed steps of SRP:
+        *
+        * When acting as A-device:
+        * 1. Test device enumerates (VID=0x1a0a, PID=0x0200),
+        *    usb_enumerate_device_otg() from hub.c sends an OTG event:
+        *    OTG_EVENT_HCD_TEST_DEVICE
+        * 2. XOTG receives it, sets FSM: start_tst_maint_timer=1
+        * 3. OTG FSM in a_host state will start TST_MAINT timer (9.9s)
+        * 4. when TST_MAINT times out it will set FSM: a_bus_drop=1, and
+        *    turn off VBUS.
+        * 5. FSM will do [a_host] -> [a_wait_vfall] -> [a_idle] transition
+        * 6. in [a_idle] state, test device will send SRP data line pulsing
+        * 7. XOTG will receives a padctrl interrupt indicating the SRP is
+        *    detected. XOTG then sets FSM: a_bus_drop=0, a_srp_det=1
+        * 8. FSM will do [a_idle] -> [a_wait_vrise] so VBUS is powered again
+        * 9. FSM will do [a_wait_vrise] -> [a_wait_bcon] and then test device
+        *    is enumerated again.
+        * 10.FSM does [a_wait_bcon] -> [a_host]
+        *
+        * When acting as B-device: (using this function)
+        * 1. Test device enumerates Tegra, XOTG stays at b_peripheral state
+        * 2. A-device (test device) powers off VBUS, we sees PCD_PORT_SUSPEND
+        *    event and then receives extcon cable 0x0 event, saying that
+        *    VBUS is not detected. FSM moves to b_idle state.
+        * 3. Test device asks for triggering SRP. We do so through sysfs which
+        *    will call this function. XOTG sets FSM: b_bus_req=1
+        * 4. FSM will do [b_idle] -> [b_srp_init], and tegra_xotg_start_pulse()
+        *    will be invoked. It also sets FSM: b_srp_done=1
+        * 5. FSM will do [b_srp_init] -> [b_idle], waiting for A-device to
+        *    power on VBUS.
+        * 6. When A-device (test device) powers up, it will enumerates us and
+        *    moves [b_idle] -> [b_peripheral]
+        *
+        *
+        */
+       xotg->otg->fsm.b_bus_req = 1;
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return 0;
+}
+
+static ssize_t trigger_srp_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct tegra_xotg *xotg = platform_get_drvdata(pdev);
+       bool trigger = false;
+
+       if (strtobool(buf, &trigger) < 0)
+               return -EINVAL;
+
+       if (trigger)
+               tegra_xotg_start_srp(xotg);
+
+       return count;
+}
+static DEVICE_ATTR_WO(trigger_srp);
+
+static int tegra_xotg_start_hnp(struct tegra_xotg *xotg)
+{
+       /*
+        * The detailed steps of HNP:
+        *
+        *      When a OTG-capable device is connected, host learns about it
+        *      by usb_enumerate_device_otg() by USB_DT_OTG descriptor. When an
+        *      OTG-capable device is connected, host sets host->
+        *      b_hnp_enable=1, and then sends a control transfer:
+        *      USB_REQ_SET_FEATURE with value USB_DEVICE_B_HNP_ENABLE.
+        *      When XUDC receives this set feature, it also sets
+        *      gadget->b_hnp_enable=1.
+        *
+        *      Host now starts doing HNP polling (otg_hnp_polling_work in
+        *      usb-otg-fsm.c) by OTG statemachine in usb-otg-fsm.c too. The
+        *      polling makes use of control transfer to poll the device for
+        *      request type: USB_REQ_GET_STATUS, index: OTG_STS_SELECTOR every
+        *      1.5 seconds.
+        *
+        * Case: B-peripheral (device side) requests host role:
+        *      This case means that B-device with default peripheral role
+        *      wants to become host.
+        *
+        *      1. [B]: user/app on B-device asks for host role
+        *      2. [B]: XOTG sets gadget->host_request_flag=1, FSM: b_bus_req=1
+        *      3. [A]: next HNP polling happens
+        *      4. [B]: when XUDC receives the HNP poll from host
+        *         (OTG_STS_SELECTOR), since host_request_flag=1, XUDC will
+        *         respond with HOST_REQUEST_FLAG (defined to be 1)
+        *      5. [A]: host learns about the flag has been set, clear FSM:
+        *         a_bus_req, which means app on A-device doesn't need to use
+        *         the bus anymore. Host then suspends the OTG port
+        *      6. [B]: device side receives the PCD_PORT_SUSPEND event, which
+        *         causes FSM to do [b_peripheral] -> [b_wait_acon] transition
+        *      7. [B]: the state transition will cause XOTG to do
+        *         xotg_b_hnp_enter() which sets reverse ID
+        *      8. [B]: OTG statemachine then turns off device mode and turns on
+        *         host mode.
+        *      9. [A]: device has disconnected, so HCD_PORT_DISCONNECT event
+        *         is received. FSM does [a_host] -> [a_suspend] transition.
+        *      10.[A]: since host->b_hnp_enable=1 and device is disconnected,
+        *         OTG statemachine does [a_suspend] -> [a_peripheral]
+        *      11.[A]: the state transition will cause XOTG to do
+        *         xotg_a_hnp_enter() which sets reverse ID and vbus override.
+        *      12.[A]: OTG statemachine then turns off host mode and turns on
+        *         device mode.
+        *      13.[B]: now it's b_host role. Host mode driver is active, and
+        *         enumerates the now a_peripheral device.
+        *      14.[B]: when device is enumerated, HCD_PORT_CONNECT event is
+        *         received, OTG state does [b_wait_acon] -> [b_host].
+        *
+        * Case: A-peripheral (device side) requests host role:
+        *      This case means that A-device which is now in peripheral mode
+        *      (through the above case so it is in a_peripheral state) wants
+        *      to become host again.
+        *
+        *      1. [A]: user/app on A-device asks for host role
+        *      2. [A]: XOTG sets FSM:a_bus_drop=1 to indicate that A-device
+        *         needs to power down the bus.
+        *      3. [A]: FSM does [a_peripheral] -> [a_wait_vfall]
+        *      4. [A]: the state transition will cause XOTG to do
+        *         tegra_xotg_a_hnp_exit() which clears reverse ID and clear
+        *         vbus override. The "clear reverse ID" then results in the
+        *         USB disconnection.
+        *      5. [A]: OTG statemachine then turns off device mode and turns on
+        *         host mode. In a_wait_vfall, A-device also turns off VBUS.
+        *      6. [B]: HCD_PORT_DISCONNECT is received, which causes FSM to do
+        *         [b_host] -> [b_peripheral] transition.
+        *      7. [B]: the state transition will cause XOTG to do
+        *         tegra_xotg_b_hnp_exit() which clears reverse ID.
+        *      8. [B]: OTG statemachine then turns off host mode and turns on
+        *         device mode.
+        *      9. [B]: since A-device turns off VBUS, B-device now gets extcon
+        *         notification of 0x0 (no cable connected), FSM does:
+        *         [b_peripheral] -> [b_idle]
+        *      10.[A]: FSM does [a_wait_vfall] -> [a_idle] -> [a_wait_vrise]
+        *         -> [a_wait_bcon] transitions, which then turns on VBUS on
+        *         OTG port
+        *      11.[B]: extcon notification of 0x1 (vbus detected), FSM does
+        *         [b_idle] -> [b_peripheral] transition
+        *      12.[A]: A-device now enumerates B-device. HCD_PORT_CONNECT event
+        *         is received, OTG state does [a_wait_bcon] -> [a_host].
+        *
+        * Case: B-host (host side) requests peripheral role:
+        *      This case means that after the initial HNP, A becomes peripheral
+        *      and B becomes host. When B (now as host) doesn't want to be host
+        *      anymore, it requests peripheral role.
+        *
+        *      1. [B]: user/app on B-device now is done with host mode. It
+        *         requests to become peripheral role again.
+        *      2. [B]: XOTG sets FSM:b_bus_req=0, indicating that it doesn't
+        *         want to use the bus anymore.
+        *      3. [B]: FSM does [b_host] -> [b_peripheral]
+        *      4. [B]: the state transition will cause XOTG to do
+        *         tegra_xotg_b_hnp_exit() which clears reverse ID. The "clear
+        *         reverse ID" then results in the USB disconnection.
+        *      5. [B]: OTG statemachine then turns off host mode and turns on
+        *         device mode.
+        *      6. [A]: OTG_EVENT_PCD_PORT_SUSPEND is received due to B-device
+        *         clears reverse ID. XOTG sets FSM:b_bus_suspend=1, which
+        *         causes FSM to start A_BIDL_ADIS timer. It is used by A-device
+        *         to determine when the B-device has finished being host.
+        *      7. [A]: A_BIDL_ADIS times out (XOTG_TA_BIDL_ADIS: 155ms). FSM
+        *         does [a_peripheral] -> [a_wait_bcon].
+        *      8. [A]: the state transition will cause XOTG to do
+        *         tegra_xotg_a_hnp_exit() which clears reverse ID and clear
+        *         vbus override. The "clear reverse ID" then cause peripheral
+        *         side to disconnect. (PCD_PORT_DISCONNECT is received).
+        *      9. [A]: OTG statemachine then turns off device mode and turns on
+        *         host mode.
+        *      10.[A]: A-device now enumerates B-device. HCD_PORT_CONNECT event
+        *         is received, OTG state does [b_wait_acon] -> [b_host].
+        *
+        */
+       switch (xotg->otg->state) {
+       case OTG_STATE_B_PERIPHERAL:
+               dev_info(xotg->dev, "[%s] B-peripheral requests host role\n",
+                               __func__);
+               if (xotg->otg->gadget)
+                       xotg->otg->gadget->host_request_flag = 1;
+               xotg->otg->fsm.b_bus_req = 1;
+               break;
+       case OTG_STATE_A_PERIPHERAL:
+               dev_info(xotg->dev, "[%s] A-peripheral requests host role\n",
+                               __func__);
+               /* reset to 0 in PCD_PORT_DISCONNECT */
+               xotg->otg->fsm.a_bus_drop = 1;
+               break;
+       case OTG_STATE_B_HOST:
+               dev_info(xotg->dev, "[%s] B-host requests peripheral role\n",
+                               __func__);
+               /* set back to 1 in HCD_PORT_DISCONNECT */
+               xotg->otg->fsm.b_bus_req = 0;
+               break;
+       default:
+               dev_info(xotg->dev, "[%s] ignore HNP request\n",
+                               usb_otg_state_string(xotg->otg->state));
+       }
+
+       usb_otg_sync_inputs(xotg->otg);
+
+       return 0;
+}
+
+static ssize_t trigger_hnp_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct tegra_xotg *xotg = platform_get_drvdata(pdev);
+       bool trigger = false;
+
+       if (strtobool(buf, &trigger) < 0)
+               return -EINVAL;
+
+       if (trigger) {
+               dev_info(xotg->dev, "trigger HNP...\n");
+               tegra_xotg_start_hnp(xotg);
+       }
+
+       return count;
+}
+static DEVICE_ATTR_WO(trigger_hnp);
+
+static struct attribute *tegra_xotg_attrs[] = {
+       &dev_attr_otg_state.attr,
+       &dev_attr_trigger_srp.attr,
+       &dev_attr_trigger_hnp.attr,
+       NULL,
+};
+
+static struct attribute_group tegra_xotg_attr_group = {
+       .attrs = tegra_xotg_attrs,
+};
+
+static const struct tegra_xotg_soc_config tegra186_soc_config = {
+       .utmi_vbus_power_on = tegra18x_phy_xusb_utmi_vbus_power_on,
+       .utmi_vbus_power_off = tegra18x_phy_xusb_utmi_vbus_power_off,
+       .set_id_override = tegra18x_phy_xusb_set_id_override,
+       .clear_id_override = tegra18x_phy_xusb_clear_id_override,
+       .set_vbus_override = tegra18x_phy_xusb_set_vbus_override,
+       .clear_vbus_override = tegra18x_phy_xusb_clear_vbus_override,
+       .has_otg_cap = tegra18x_phy_xusb_has_otg_cap,
+       .set_reverse_id = tegra18x_phy_xusb_set_reverse_id,
+       .clear_reverse_id = tegra18x_phy_xusb_clear_reverse_id,
+       .generate_srp = tegra18x_phy_xusb_generate_srp,
+       .enable_srp = tegra18x_phy_xusb_enable_srp_detect,
+       .disable_srp = tegra18x_phy_xusb_disable_srp_detect,
+       .srp_detected = tegra18x_phy_xusb_srp_detected,
+       .enable_otg_int = tegra18x_phy_xusb_enable_otg_int,
+       .disable_otg_int = tegra18x_phy_xusb_disable_otg_int,
+       .ack_otg_int = tegra18x_phy_xusb_ack_otg_int,
+       .get_otg_vbus_id = tegra18x_phy_xusb_get_otg_vbus_id,
+};
+
+static const struct of_device_id tegra_xotg_of_match[] = {
+       { .compatible = "nvidia,tegra186-xotg", .data = &tegra186_soc_config },
+       {}
+};
+MODULE_DEVICE_TABLE(of, tegra_xotg_of_match);
+
+static int tegra_xotg_probe(struct platform_device *pdev)
+{
+       const struct of_device_id *match;
+       struct tegra_xotg *xotg;
+       int err;
+       int irq;
+
+       xotg = devm_kzalloc(&pdev->dev, sizeof(*xotg), GFP_KERNEL);
+       if (!xotg)
+               return -ENOMEM;
+       xotg->dev = &pdev->dev;
+       platform_set_drvdata(pdev, xotg);
+
+       match = of_match_device(tegra_xotg_of_match, &pdev->dev);
+       if (!match) {
+               dev_warn(xotg->dev, "of doesn't match\n");
+               return -ENODEV;
+       }
+       xotg->soc_config = match->data;
+
+       spin_lock_init(&xotg->lock);
+
+       /* shared padctl IRQ */
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(xotg->dev, "failed to get irq resource 0: %d\n", irq);
+               return irq;
+       }
+       err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+                       tegra_xotg_irq,
+                       IRQF_ONESHOT | IRQF_TRIGGER_HIGH | IRQF_SHARED,
+                       dev_name(&pdev->dev), xotg);
+       if (err < 0) {
+               dev_err(xotg->dev, "failed to claim irq %d\n", err);
+               return err;
+       }
+
+       /* OTG port phy */
+       xotg->usb2_phy = devm_phy_get(&pdev->dev, "otg-usb2");
+       if (IS_ERR(xotg->usb2_phy)) {
+               err = PTR_ERR(xotg->usb2_phy);
+               if (err == -EPROBE_DEFER)
+                       dev_info(xotg->dev, "usb2 phy is not available yet\n");
+               else
+                       dev_err(xotg->dev, "failed to get usb2 phy %d\n", err);
+               return err;
+       }
+       if (xotg->soc_config->has_otg_cap &&
+                       !xotg->soc_config->has_otg_cap(xotg->usb2_phy)) {
+               err = -EINVAL;
+               dev_err(xotg->dev, "usb2 phy doesn't have OTG capability\n");
+               return err;
+       }
+       xotg->usb3_phy = devm_phy_optional_get(&pdev->dev, "otg-usb3");
+       if (IS_ERR(xotg->usb3_phy)) {
+               err = PTR_ERR(xotg->usb3_phy);
+               if (err == -EPROBE_DEFER)
+                       dev_info(xotg->dev, "usb3 phy is not available yet\n");
+               else
+                       dev_err(xotg->dev, "failed to get usb3 phy %d\n", err);
+               return err;
+       } else if (xotg->usb3_phy && xotg->soc_config->has_otg_cap &&
+                       !xotg->soc_config->has_otg_cap(xotg->usb3_phy)) {
+               err = -EINVAL;
+               dev_err(xotg->dev, "usb3 phy doesn't have OTG capability\n");
+               return err;
+       }
+
+       /* ID extcon initialization */
+       xotg->id_extcon = extcon_get_extcon_dev_by_cable(&pdev->dev, "id");
+       if (!IS_ERR(xotg->id_extcon)) {
+               xotg->id_extcon_nb.notifier_call =
+                       tegra_xotg_id_extcon_notifier;
+               extcon_register_notifier(xotg->id_extcon, EXTCON_USB_HOST,
+                                        &xotg->id_extcon_nb);
+       } else if (PTR_ERR(xotg->id_extcon) == -EPROBE_DEFER) {
+               err = -EPROBE_DEFER;
+               goto exit;
+       } else
+               dev_warn(&pdev->dev, "no USB ID extcon found\n");
+
+       /* vbus extcon initialization */
+       xotg->vbus_extcon = extcon_get_extcon_dev_by_cable(&pdev->dev, "vbus");
+       if (!IS_ERR(xotg->vbus_extcon)) {
+               xotg->vbus_extcon_nb.notifier_call =
+                       tegra_xotg_vbus_extcon_notifier;
+               extcon_register_notifier(xotg->vbus_extcon, EXTCON_USB,
+                                        &xotg->vbus_extcon_nb);
+       } else if (PTR_ERR(xotg->vbus_extcon) == -EPROBE_DEFER) {
+               err = -EPROBE_DEFER;
+               goto unregister_id_extcon;
+       } else
+               dev_warn(&pdev->dev, "no USB VBUS extcon found\n");
+
+       dev_info(&pdev->dev, "OTG rev:%04x, ADP:%d, SRP:%d, HNP:%d, RSP:%d\n",
+                       tegra_xotg_otg_config.otg_caps.otg_rev,
+                       tegra_xotg_otg_config.otg_caps.adp_support,
+                       tegra_xotg_otg_config.otg_caps.srp_support,
+                       tegra_xotg_otg_config.otg_caps.hnp_support,
+                       tegra_xotg_otg_config.otg_caps.rsp_support);
+
+       /* register to OTG Core */
+       xotg->otg = usb_otg_register(xotg->dev, &tegra_xotg_otg_config);
+       if (IS_ERR(xotg->otg)) {
+               err = PTR_ERR(xotg->otg);
+               if (err == -ENOTSUPP)
+                       dev_err(xotg->dev, "CONFIG_USB_OTG needed for XOTG\n");
+               else
+                       dev_err(xotg->dev,
+                               "failed to register to OTG core: %d\n", err);
+               goto unregister_vbus_extcon;
+       }
+
+       /* init otg timers */
+       tegra_xotg_init_timers(xotg);
+
+       /* register OTG event handler */
+       xotg->otg_nb.notifier_call = tegra_xotg_event_handler;
+       usb_otg_register_notifier(xotg->otg, &xotg->otg_nb);
+
+       /* sync FSM initial state from extcon cable state */
+       tegra_xotg_update_data_role(xotg);
+       dev_dbg(xotg->dev, "initial state: id=%d, b_sess_vld=%d\n",
+                       xotg->otg->fsm.id, xotg->otg->fsm.b_sess_vld);
+
+       /* allocate fsm.host_req_flag so HNP polling will start */
+       xotg->otg->fsm.host_req_flag = devm_kzalloc(&pdev->dev, sizeof(u8),
+                                                   GFP_KERNEL);
+       /* set initial FSM variables */
+       xotg->otg->fsm.a_bus_drop = 0;
+       xotg->otg->fsm.a_bus_req = 1;
+       usb_otg_sync_inputs(xotg->otg);
+
+       /* initialize OTG FSM state transition operation */
+       otg_init_state_op(xotg->otg);
+
+       /* for SRP: only enable SRP detection in A_IDLE state */
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_UNDEFINED, OTG_STATE_A_IDLE,
+                       tegra_xotg_enable_srp);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_A_IDLE, OTG_STATE_UNDEFINED,
+                       tegra_xotg_disable_srp);
+       /* for A-device HNP */
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_A_SUSPEND, OTG_STATE_A_PERIPHERAL,
+                       tegra_xotg_a_hnp_enter);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_A_PERIPHERAL, OTG_STATE_UNDEFINED,
+                       tegra_xotg_a_hnp_exit);
+       /* for B-device HNP */
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_B_PERIPHERAL, OTG_STATE_B_WAIT_ACON,
+                       tegra_xotg_b_hnp_enter);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_B_WAIT_ACON, OTG_STATE_B_PERIPHERAL,
+                       tegra_xotg_b_hnp_exit);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_B_WAIT_ACON, OTG_STATE_B_IDLE,
+                       tegra_xotg_b_hnp_exit);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_B_HOST, OTG_STATE_B_PERIPHERAL,
+                       tegra_xotg_b_hnp_exit);
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_B_HOST, OTG_STATE_B_IDLE,
+                       tegra_xotg_b_hnp_exit);
+
+       /* for session support */
+       otg_set_state_op(xotg->otg,
+                       OTG_STATE_A_WAIT_VFALL, OTG_STATE_A_IDLE,
+                       tegra_xotg_vbus_session);
+
+
+       /* enable OTG interrupts */
+       if (xotg->soc_config->enable_otg_int) {
+               err = xotg->soc_config->enable_otg_int(xotg->usb2_phy);
+               if (err) {
+                       dev_err(xotg->dev, "failed to enable OTG interrupt: %d\n",
+                               err);
+                       goto unregister_otg;
+               }
+       }
+
+       /* sysfs nodes */
+       err = sysfs_create_group(&pdev->dev.kobj, &tegra_xotg_attr_group);
+       if (err) {
+               dev_err(xotg->dev, "cannot create sysfs group: %d\n", err);
+               goto disable_otg_int;
+       }
+
+       dev_info(xotg->dev, "Nvidia XUSB OTG Controller\n");
+
+       return 0;
+
+disable_otg_int:
+       if (xotg->soc_config->disable_otg_int)
+               xotg->soc_config->disable_otg_int(xotg->usb2_phy);
+unregister_otg:
+       usb_otg_unregister_notifier(xotg->otg, &xotg->otg_nb);
+       usb_otg_unregister(xotg->dev);
+unregister_vbus_extcon:
+       extcon_unregister_notifier(xotg->vbus_extcon, EXTCON_USB,
+                                  &xotg->vbus_extcon_nb);
+unregister_id_extcon:
+       extcon_unregister_notifier(xotg->id_extcon, EXTCON_USB_HOST,
+                                  &xotg->id_extcon_nb);
+exit:
+       return err;
+}
+
+static int tegra_xotg_remove(struct platform_device *pdev)
+{
+       struct tegra_xotg *xotg = platform_get_drvdata(pdev);
+
+       sysfs_remove_group(&pdev->dev.kobj, &tegra_xotg_attr_group);
+       if (xotg->soc_config->disable_srp)
+               xotg->soc_config->disable_srp(xotg->usb2_phy);
+       if (xotg->soc_config->disable_otg_int)
+               xotg->soc_config->disable_otg_int(xotg->usb2_phy);
+       usb_otg_unregister_notifier(xotg->otg, &xotg->otg_nb);
+       usb_otg_unregister(xotg->dev);
+       extcon_unregister_notifier(xotg->vbus_extcon, EXTCON_USB,
+                                  &xotg->vbus_extcon_nb);
+       extcon_unregister_notifier(xotg->id_extcon, EXTCON_USB_HOST,
+                                  &xotg->id_extcon_nb);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra_xotg_resume(struct device *dev)
+{
+       struct tegra_xotg *xotg = dev_get_drvdata(dev);
+       struct otg_fsm *fsm = &xotg->otg->fsm;
+
+       /* update FSM according to cable state (extcon) */
+       tegra_xotg_update_data_role(xotg);
+
+       /* re-start host or gadget based on current role */
+       if (fsm->protocol == PROTO_HOST)
+               otg_start_host(xotg->otg, 1);
+       else if (fsm->protocol == PROTO_GADGET)
+               otg_start_gadget(xotg->otg, 1);
+
+       /*
+        * if we resume and find ourselves still in a_peripheral state,
+        * force a FSM reset to a_wait_bcon state
+        */
+       if (xotg->otg->state == OTG_STATE_A_PERIPHERAL) {
+               xotg->otg->fsm.a_bidl_adis_tmout = 1;
+               usb_otg_sync_inputs(xotg->otg);
+       }
+
+       return 0;
+}
+
+static int tegra_xotg_suspend(struct device *dev)
+{
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops tegra_xotg_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(tegra_xotg_suspend, tegra_xotg_resume)
+};
+
+static struct platform_driver tegra_xotg_driver = {
+       .probe = tegra_xotg_probe,
+       .remove = tegra_xotg_remove,
+       .driver = {
+               .name = "tegra-xotg",
+               .pm = &tegra_xotg_pm_ops,
+               .of_match_table = tegra_xotg_of_match,
+       },
+};
+module_platform_driver(tegra_xotg_driver);
+
+MODULE_DESCRIPTION("Nvidia XUSB OTG Controller Driver");
+MODULE_AUTHOR("Mark Kuo (mkuo@nvidia.com)");
+MODULE_LICENSE("GPL");