USB: OTG: msm: Add support for SRP and HNP

This patch changes OTG driver to support different
OTG statemachine states for OTG2.0 complaince. It will
also enable to swap Host role between two OTG devices
via HNP and enable B device to request A device to start
VBUS session via SRP.

Change-Id: Ibe2519ff1570768f51e879af483a6f5c23b38d8f
Signed-off-by: Vijayavardhan Vennapusa <vvreddy@codeaurora.org>
Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org>
diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c
index f45f257..5649fd0 100644
--- a/drivers/usb/host/ehci-msm.c
+++ b/drivers/usb/host/ehci-msm.c
@@ -176,6 +176,7 @@
 		goto put_transceiver;
 	}
 
+	hcd_to_ehci(hcd)->transceiver = otg;
 	device_init_wakeup(&pdev->dev, 1);
 	pm_runtime_enable(&pdev->dev);
 
@@ -199,6 +200,7 @@
 	pm_runtime_disable(&pdev->dev);
 	pm_runtime_set_suspended(&pdev->dev);
 
+	hcd_to_ehci(hcd)->transceiver = NULL;
 	otg_set_host(otg, NULL);
 	otg_put_transceiver(otg);
 
diff --git a/drivers/usb/otg/msm_otg.c b/drivers/usb/otg/msm_otg.c
index dd07d4e..7ee02df 100644
--- a/drivers/usb/otg/msm_otg.c
+++ b/drivers/usb/otg/msm_otg.c
@@ -33,6 +33,7 @@
 #include <linux/usb/ulpi.h>
 #include <linux/usb/gadget.h>
 #include <linux/usb/hcd.h>
+#include <linux/usb/quirks.h>
 #include <linux/usb/msm_hsusb.h>
 #include <linux/usb/msm_hsusb_hw.h>
 #include <linux/regulator/consumer.h>
@@ -49,8 +50,8 @@
 #define DRIVER_NAME	"msm_otg"
 
 #define ID_TIMER_FREQ		(jiffies + msecs_to_jiffies(2000))
+#define ID_TIMER_INITIAL_FREQ	(jiffies + msecs_to_jiffies(1000))
 #define ULPI_IO_TIMEOUT_USEC	(10 * 1000)
-
 #define USB_PHY_3P3_VOL_MIN	3050000 /* uV */
 #define USB_PHY_3P3_VOL_MAX	3300000 /* uV */
 #define USB_PHY_3P3_HPM_LOAD	50000	/* uA */
@@ -581,25 +582,190 @@
 	return 0;
 }
 
+static const char *timer_string(int bit)
+{
+	switch (bit) {
+	case A_WAIT_VRISE:		return "a_wait_vrise";
+	case A_WAIT_VFALL:		return "a_wait_vfall";
+	case B_SRP_FAIL:		return "b_srp_fail";
+	case A_WAIT_BCON:		return "a_wait_bcon";
+	case A_AIDL_BDIS:		return "a_aidl_bdis";
+	case A_BIDL_ADIS:		return "a_bidl_adis";
+	case B_ASE0_BRST:		return "b_ase0_brst";
+	case A_TST_MAINT:		return "a_tst_maint";
+	case B_TST_SRP:			return "b_tst_srp";
+	case B_TST_CONFIG:		return "b_tst_config";
+	default:			return "UNDEFINED";
+	}
+}
+
+static enum hrtimer_restart msm_otg_timer_func(struct hrtimer *hrtimer)
+{
+	struct msm_otg *motg = container_of(hrtimer, struct msm_otg, timer);
+
+	switch (motg->active_tmout) {
+	case A_WAIT_VRISE:
+		/* TODO: use vbus_vld interrupt */
+		set_bit(A_VBUS_VLD, &motg->inputs);
+		break;
+	case A_TST_MAINT:
+		/* OTG PET: End session after TA_TST_MAINT */
+		set_bit(A_BUS_DROP, &motg->inputs);
+		break;
+	case B_TST_SRP:
+		/*
+		 * OTG PET: Initiate SRP after TB_TST_SRP of
+		 * previous session end.
+		 */
+		set_bit(B_BUS_REQ, &motg->inputs);
+		break;
+	case B_TST_CONFIG:
+		clear_bit(A_CONN, &motg->inputs);
+		break;
+	default:
+		set_bit(motg->active_tmout, &motg->tmouts);
+	}
+
+	pr_debug("expired %s timer\n", timer_string(motg->active_tmout));
+	queue_work(system_nrt_wq, &motg->sm_work);
+	return HRTIMER_NORESTART;
+}
+
+static void msm_otg_del_timer(struct msm_otg *motg)
+{
+	int bit = motg->active_tmout;
+
+	pr_debug("deleting %s timer. remaining %lld msec\n", timer_string(bit),
+			div_s64(ktime_to_us(hrtimer_get_remaining(
+					&motg->timer)), 1000));
+	hrtimer_cancel(&motg->timer);
+	clear_bit(bit, &motg->tmouts);
+}
+
+static void msm_otg_start_timer(struct msm_otg *motg, int time, int bit)
+{
+	clear_bit(bit, &motg->tmouts);
+	motg->active_tmout = bit;
+	pr_debug("starting %s timer\n", timer_string(bit));
+	hrtimer_start(&motg->timer,
+			ktime_set(time / 1000, (time % 1000) * 1000000),
+			HRTIMER_MODE_REL);
+}
+
+static void msm_otg_init_timer(struct msm_otg *motg)
+{
+	hrtimer_init(&motg->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	motg->timer.function = msm_otg_timer_func;
+}
+
+static int msm_otg_start_hnp(struct otg_transceiver *otg)
+{
+	struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
+
+	if (otg->state != OTG_STATE_A_HOST) {
+		pr_err("HNP can not be initiated in %s state\n",
+				otg_state_string(otg->state));
+		return -EINVAL;
+	}
+
+	pr_debug("A-Host: HNP initiated\n");
+	clear_bit(A_BUS_REQ, &motg->inputs);
+	queue_work(system_nrt_wq, &motg->sm_work);
+	return 0;
+}
+
+static int msm_otg_start_srp(struct otg_transceiver *otg)
+{
+	struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
+	u32 val;
+	int ret = 0;
+
+	if (otg->state != OTG_STATE_B_IDLE) {
+		pr_err("SRP can not be initiated in %s state\n",
+				otg_state_string(otg->state));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if ((jiffies - motg->b_last_se0_sess) < msecs_to_jiffies(TB_SRP_INIT)) {
+		pr_debug("initial conditions of SRP are not met. Try again"
+				"after some time\n");
+		ret = -EAGAIN;
+		goto out;
+	}
+
+	pr_debug("B-Device SRP started\n");
+
+	/*
+	 * PHY won't pull D+ high unless it detects Vbus valid.
+	 * Since by definition, SRP is only done when Vbus is not valid,
+	 * software work-around needs to be used to spoof the PHY into
+	 * thinking it is valid. This can be done using the VBUSVLDEXTSEL and
+	 * VBUSVLDEXT register bits.
+	 */
+	ulpi_write(otg, 0x03, 0x97);
+	/*
+	 * Harware auto assist data pulsing: Data pulse is given
+	 * for 7msec; wait for vbus
+	 */
+	val = readl_relaxed(USB_OTGSC);
+	writel_relaxed((val & ~OTGSC_INTSTS_MASK) | OTGSC_HADP, USB_OTGSC);
+
+	/* VBUS plusing is obsoleted in OTG 2.0 supplement */
+out:
+	return ret;
+}
+
+static void msm_otg_host_hnp_enable(struct otg_transceiver *otg, bool enable)
+{
+	struct usb_hcd *hcd = bus_to_hcd(otg->host);
+	struct usb_device *rhub = otg->host->root_hub;
+
+	if (enable) {
+		pm_runtime_disable(&rhub->dev);
+		rhub->state = USB_STATE_NOTATTACHED;
+		hcd->driver->bus_suspend(hcd);
+		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+	} else {
+		usb_remove_hcd(hcd);
+		msm_otg_reset(otg);
+		usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
+	}
+}
+
 static int msm_otg_set_suspend(struct otg_transceiver *otg, int suspend)
 {
 	struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
 
-	/*
-	 * Allow bus suspend only for host mode.  Device mode bus suspend
-	 * is not implemented yet.
-	 */
-	if (!test_bit(ID, &motg->inputs) || test_bit(ID_A, &motg->inputs)) {
-		/*
-		 * ID_GND --> ID_A transition can not be detected in LPM.
-		 * Disallow host bus suspend when ACA is enabled.
-		 */
-		if (suspend && !aca_enabled())
-			pm_runtime_put(otg->dev);
-		else
-			pm_runtime_resume(otg->dev);
-	}
+	if (aca_enabled() || (test_bit(ID, &motg->inputs) &&
+				 !test_bit(ID_A, &motg->inputs)))
+		return 0;
 
+	if (suspend) {
+		switch (otg->state) {
+		case OTG_STATE_A_WAIT_BCON:
+			if (TA_WAIT_BCON > 0)
+				break;
+			/* fall through */
+		case OTG_STATE_A_HOST:
+			pr_debug("host bus suspend\n");
+			clear_bit(A_BUS_REQ, &motg->inputs);
+			queue_work(system_nrt_wq, &motg->sm_work);
+			break;
+		default:
+			break;
+		}
+	} else {
+		switch (otg->state) {
+		case OTG_STATE_A_SUSPEND:
+			/* Remote wakeup or resume */
+			set_bit(A_BUS_REQ, &motg->inputs);
+			otg->state = OTG_STATE_A_HOST;
+			break;
+		default:
+			break;
+		}
+	}
 	return 0;
 }
 
@@ -905,6 +1071,11 @@
 
 static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA)
 {
+	struct usb_gadget *g = motg->otg.gadget;
+
+	if (g && g->is_a_peripheral)
+		return;
+
 	if ((motg->chg_type == USB_ACA_DOCK_CHARGER ||
 		motg->chg_type == USB_ACA_A_CHARGER ||
 		motg->chg_type == USB_ACA_B_CHARGER ||
@@ -987,11 +1158,9 @@
 			unsigned long action, void *priv)
 {
 	struct msm_otg *motg = container_of(self, struct msm_otg, usbdev_nb);
+	struct otg_transceiver *otg = &motg->otg;
 	struct usb_device *udev = priv;
 
-	if (!aca_enabled())
-		goto out;
-
 	if (action == USB_BUS_ADD || action == USB_BUS_REMOVE)
 		goto out;
 
@@ -1008,17 +1177,47 @@
 
 	switch (action) {
 	case USB_DEVICE_ADD:
-		usb_disable_autosuspend(udev);
+		if (aca_enabled())
+			usb_disable_autosuspend(udev);
+		if (otg->state == OTG_STATE_A_WAIT_BCON) {
+			pr_debug("B_CONN set\n");
+			set_bit(B_CONN, &motg->inputs);
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_HOST;
+			/*
+			 * OTG PET: A-device must end session within
+			 * 10 sec after PET enumeration.
+			 */
+			if (udev->quirks & USB_QUIRK_OTG_PET)
+				msm_otg_start_timer(motg, TA_TST_MAINT,
+						A_TST_MAINT);
+		}
 		/* fall through */
 	case USB_DEVICE_CONFIG:
 		if (udev->actconfig)
 			motg->mA_port = udev->actconfig->desc.bMaxPower * 2;
 		else
 			motg->mA_port = IUNIT;
+		if (otg->state == OTG_STATE_B_HOST)
+			msm_otg_del_timer(motg);
 		break;
 	case USB_DEVICE_REMOVE:
-		motg->mA_port = IUNIT;
-		break;
+		if ((otg->state == OTG_STATE_A_HOST) ||
+			(otg->state == OTG_STATE_A_SUSPEND)) {
+			pr_debug("B_CONN clear\n");
+			clear_bit(B_CONN, &motg->inputs);
+			/*
+			 * OTG PET: A-device must end session after
+			 * PET disconnection if it is enumerated
+			 * with bcdDevice[0] = 1. USB core sets
+			 * bus->otg_vbus_off for us. clear it here.
+			 */
+			if (udev->bus->otg_vbus_off) {
+				udev->bus->otg_vbus_off = 0;
+				set_bit(A_BUS_DROP, &motg->inputs);
+			}
+			queue_work(system_nrt_wq, &motg->sm_work);
+		}
 	default:
 		break;
 	}
@@ -1101,7 +1300,7 @@
 			msm_hsusb_vbus_power(motg, 0);
 			otg->host = NULL;
 			otg->state = OTG_STATE_UNDEFINED;
-			schedule_work(&motg->sm_work);
+			queue_work(system_nrt_wq, &motg->sm_work);
 		} else {
 			otg->host = NULL;
 		}
@@ -1115,6 +1314,9 @@
 	hcd = bus_to_hcd(host);
 	hcd->power_budget = motg->pdata->power_budget;
 
+#ifdef CONFIG_USB_OTG
+	host->otg_port = 1;
+#endif
 	motg->usbdev_nb.notifier_call = msm_otg_usbdev_notify;
 	usb_register_notify(&motg->usbdev_nb);
 	otg->host = host;
@@ -1126,7 +1328,7 @@
 	 */
 	if (motg->pdata->mode == USB_HOST || otg->gadget) {
 		pm_runtime_get_sync(otg->dev);
-		schedule_work(&motg->sm_work);
+		queue_work(system_nrt_wq, &motg->sm_work);
 	}
 
 	return 0;
@@ -1202,7 +1404,7 @@
 			msm_otg_start_peripheral(otg, 0);
 			otg->gadget = NULL;
 			otg->state = OTG_STATE_UNDEFINED;
-			schedule_work(&motg->sm_work);
+			queue_work(system_nrt_wq, &motg->sm_work);
 		} else {
 			otg->gadget = NULL;
 		}
@@ -1218,7 +1420,7 @@
 	 */
 	if (motg->pdata->mode == USB_PERIPHERAL || otg->host) {
 		pm_runtime_get_sync(otg->dev);
-		schedule_work(&motg->sm_work);
+		queue_work(system_nrt_wq, &motg->sm_work);
 	}
 
 	return 0;
@@ -1380,6 +1582,7 @@
 static void msm_otg_id_timer_func(unsigned long data)
 {
 	struct msm_otg *motg = (struct msm_otg *) data;
+	struct otg_transceiver *otg = &motg->otg;
 
 	if (!aca_enabled())
 		return;
@@ -1389,11 +1592,15 @@
 		return;
 	}
 
+	if (otg->state == OTG_STATE_A_SUSPEND)
+		goto out;
+
 	if (msm_chg_check_aca_intr(motg)) {
 		dev_dbg(motg->otg.dev, "timer: aca work\n");
-		schedule_work(&motg->sm_work);
+		queue_work(system_nrt_wq, &motg->sm_work);
 	}
 
+out:
 	if (!test_bit(ID, &motg->inputs) || test_bit(ID_A, &motg->inputs))
 		mod_timer(&motg->id_timer, ID_TIMER_FREQ);
 }
@@ -1734,16 +1941,22 @@
 	case USB_CHG_STATE_DETECTED:
 		msm_chg_block_off(motg);
 		msm_chg_enable_aca_det(motg);
+		/*
+		 * Spurious interrupt is seen after enabling ACA detection
+		 * due to which charger detection fails in case of PET.
+		 * Add delay of 100 microsec to avoid that.
+		 */
+		udelay(100);
 		msm_chg_enable_aca_intr(motg);
 		dev_dbg(otg->dev, "chg_type = %s\n",
 			chg_to_string(motg->chg_type));
-		schedule_work(&motg->sm_work);
+		queue_work(system_nrt_wq, &motg->sm_work);
 		return;
 	default:
 		return;
 	}
 
-	schedule_delayed_work(&motg->chg_work, delay);
+	queue_delayed_work(system_nrt_wq, &motg->chg_work, delay);
 }
 
 /*
@@ -1771,10 +1984,12 @@
 				clear_bit(B_SESS_VLD, &motg->inputs);
 			}
 		} else if (pdata->otg_control == OTG_PHY_CONTROL) {
-			if (otgsc & OTGSC_ID)
+			if (otgsc & OTGSC_ID) {
 				set_bit(ID, &motg->inputs);
-			else
+			} else {
 				clear_bit(ID, &motg->inputs);
+				set_bit(A_BUS_REQ, &motg->inputs);
+			}
 			if (otgsc & OTGSC_BSV)
 				set_bit(B_SESS_VLD, &motg->inputs);
 			else
@@ -1820,11 +2035,12 @@
 {
 	struct msm_otg *motg = container_of(w, struct msm_otg, sm_work);
 	struct otg_transceiver *otg = &motg->otg;
+	bool work = 0, srp_reqd;
 
 	pm_runtime_resume(otg->dev);
+	pr_debug("%s work\n", otg_state_string(otg->state));
 	switch (otg->state) {
 	case OTG_STATE_UNDEFINED:
-		dev_dbg(otg->dev, "OTG_STATE_UNDEFINED state\n");
 		msm_otg_reset(otg);
 		msm_otg_init_sm(motg);
 		otg->state = OTG_STATE_B_IDLE;
@@ -1836,31 +2052,15 @@
 		}
 		/* FALL THROUGH */
 	case OTG_STATE_B_IDLE:
-		dev_dbg(otg->dev, "OTG_STATE_B_IDLE state\n");
 		if ((!test_bit(ID, &motg->inputs) ||
 				test_bit(ID_A, &motg->inputs)) && otg->host) {
-			if (motg->chg_type == USB_ACA_DOCK_CHARGER)
-				msm_otg_notify_charger(motg,
-						IDEV_ACA_CHG_MAX);
-			else if (test_bit(ID_A, &motg->inputs))
-				msm_otg_notify_charger(motg,
-						IDEV_ACA_CHG_MAX - IUNIT);
-			else
-				msm_hsusb_vbus_power(motg, 1);
-			msm_otg_start_host(otg, 1);
-			/*
-			 * Link can not generate PHY_ALT interrupt
-			 * in host mode when no device is attached
-			 * to the port.  It is also observed PHY_ALT
-			 * interrupt missing upon Micro-A cable disconnect.
-			 * Hence disable PHY_ALT interrupt and perform
-			 * polling to detect RID change.
-			 */
-			msm_chg_enable_aca_det(motg);
-			msm_chg_disable_aca_intr(motg);
-			mod_timer(&motg->id_timer, ID_TIMER_FREQ);
-			otg->state = OTG_STATE_A_HOST;
+			pr_debug("!id || id_A\n");
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			set_bit(A_BUS_REQ, &motg->inputs);
+			otg->state = OTG_STATE_A_IDLE;
+			work = 1;
 		} else if (test_bit(B_SESS_VLD, &motg->inputs)) {
+			pr_debug("b_sess_vld\n");
 			switch (motg->chg_state) {
 			case USB_CHG_STATE_UNDEFINED:
 				msm_chg_detect_work(&motg->chg_work.work);
@@ -1906,7 +2106,18 @@
 			default:
 				break;
 			}
+		} else if (test_bit(B_BUS_REQ, &motg->inputs)) {
+			pr_debug("b_sess_end && b_bus_req\n");
+			if (msm_otg_start_srp(otg) < 0) {
+				clear_bit(B_BUS_REQ, &motg->inputs);
+				work = 1;
+				break;
+			}
+			otg->state = OTG_STATE_B_SRP_INIT;
+			msm_otg_start_timer(motg, TB_SRP_FAIL, B_SRP_FAIL);
+			break;
 		} else {
+			pr_debug("chg_work cancel");
 			cancel_delayed_work_sync(&motg->chg_work);
 			msm_otg_notify_charger(motg, 0);
 			motg->chg_state = USB_CHG_STATE_UNDEFINED;
@@ -1916,64 +2127,451 @@
 			pm_runtime_suspend(otg->dev);
 		}
 		break;
-	case OTG_STATE_B_PERIPHERAL:
-		dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n");
-		if (!test_bit(B_SESS_VLD, &motg->inputs) ||
-				!test_bit(ID, &motg->inputs) ||
-				!test_bit(ID_C, &motg->inputs)) {
-			msm_otg_start_peripheral(otg, 0);
+	case OTG_STATE_B_SRP_INIT:
+		if (!test_bit(ID, &motg->inputs) ||
+				test_bit(ID_A, &motg->inputs) ||
+				test_bit(ID_C, &motg->inputs) ||
+				(test_bit(B_SESS_VLD, &motg->inputs) &&
+				!test_bit(ID_B, &motg->inputs))) {
+			pr_debug("!id || id_a/c || b_sess_vld+!id_b\n");
+			msm_otg_del_timer(motg);
 			otg->state = OTG_STATE_B_IDLE;
-			schedule_work(w);
+			/*
+			 * clear VBUSVLDEXTSEL and VBUSVLDEXT register
+			 * bits after SRP initiation.
+			 */
+			ulpi_write(otg, 0x0, 0x98);
+			work = 1;
+		} else if (test_bit(B_SRP_FAIL, &motg->tmouts)) {
+			pr_debug("b_srp_fail\n");
+			pr_info("A-device did not respond to SRP\n");
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			clear_bit(B_SRP_FAIL, &motg->tmouts);
+			otg_send_event(OTG_EVENT_NO_RESP_FOR_SRP);
+			ulpi_write(otg, 0x0, 0x98);
+			otg->state = OTG_STATE_B_IDLE;
+			motg->b_last_se0_sess = jiffies;
+			work = 1;
+		}
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		if (!test_bit(ID, &motg->inputs) ||
+				test_bit(ID_A, &motg->inputs) ||
+				test_bit(ID_B, &motg->inputs) ||
+				!test_bit(B_SESS_VLD, &motg->inputs)) {
+			pr_debug("!id  || id_a/b || !b_sess_vld\n");
+			msm_otg_notify_charger(motg, 0);
+			srp_reqd = otg->gadget->otg_srp_reqd;
+			msm_otg_start_peripheral(otg, 0);
+			motg->chg_state = USB_CHG_STATE_UNDEFINED;
+			motg->chg_type = USB_INVALID_CHARGER;
+			if (test_bit(ID_B, &motg->inputs))
+				clear_bit(ID_B, &motg->inputs);
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			otg->state = OTG_STATE_B_IDLE;
+			motg->b_last_se0_sess = jiffies;
+			if (srp_reqd)
+				msm_otg_start_timer(motg,
+					TB_TST_SRP, B_TST_SRP);
+			else
+				work = 1;
+		} else if (test_bit(B_BUS_REQ, &motg->inputs) &&
+				otg->gadget->b_hnp_enable &&
+				test_bit(A_BUS_SUSPEND, &motg->inputs)) {
+			pr_debug("b_bus_req && b_hnp_en && a_bus_suspend\n");
+			msm_otg_start_timer(motg, TB_ASE0_BRST, B_ASE0_BRST);
+			/* D+ pullup should not be disconnected within 4msec
+			 * after A device suspends the bus. Otherwise PET will
+			 * fail the compliance test.
+			 */
+			udelay(1000);
+			msm_otg_start_peripheral(otg, 0);
+			otg->state = OTG_STATE_B_WAIT_ACON;
+			/*
+			 * start HCD even before A-device enable
+			 * pull-up to meet HNP timings.
+			 */
+			otg->host->is_b_host = 1;
+			msm_otg_start_host(otg, 1);
 		} else if (test_bit(ID_C, &motg->inputs)) {
 			msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX);
 		}
 		break;
-	case OTG_STATE_A_HOST:
-		dev_dbg(otg->dev, "OTG_STATE_A_HOST state\n");
-		if (test_bit(ID, &motg->inputs) &&
-				!test_bit(ID_A, &motg->inputs)) {
-			msm_otg_start_host(otg, 0);
-			msm_hsusb_vbus_power(motg, 0);
-			msleep(100); /* TA_WAIT_VFALL */
+	case OTG_STATE_B_WAIT_ACON:
+		if (!test_bit(ID, &motg->inputs) ||
+				test_bit(ID_A, &motg->inputs) ||
+				test_bit(ID_B, &motg->inputs) ||
+				!test_bit(B_SESS_VLD, &motg->inputs)) {
+			pr_debug("!id || id_a/b || !b_sess_vld\n");
+			msm_otg_del_timer(motg);
 			/*
-			 * Exit point of host mode.
-			 *
-			 * 1. Micro-A cable disconnect: Just schedule
-			 * the work. PHY is reset in B_IDLE and LPM
-			 * is allowed.
-			 * 2. ID_GND --> ID_B: No need to reset the PHY.
-			 * HCD core clears all PORTSC bits and initializes
-			 * the controller to host mode in remove_hcd.
-			 * Restore PORTSC transceiver select bits (ULPI)
-			 * and reset the controller to change MODE bits.
-			 * PHY_ALT interrupt can not occur in host mode.
+			 * A-device is physically disconnected during
+			 * HNP. Remove HCD.
 			 */
-			del_timer_sync(&motg->id_timer);
-			if (motg->chg_state != USB_CHG_STATE_UNDEFINED) {
-				msm_otg_link_reset(motg);
-				msm_chg_enable_aca_intr(motg);
-			}
+			msm_otg_start_host(otg, 0);
+			otg->host->is_b_host = 0;
+
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			clear_bit(A_BUS_SUSPEND, &motg->inputs);
+			motg->b_last_se0_sess = jiffies;
 			otg->state = OTG_STATE_B_IDLE;
-			schedule_work(w);
+			msm_otg_reset(otg);
+			work = 1;
+		} else if (test_bit(A_CONN, &motg->inputs)) {
+			pr_debug("a_conn\n");
+			clear_bit(A_BUS_SUSPEND, &motg->inputs);
+			otg->state = OTG_STATE_B_HOST;
+			/*
+			 * PET disconnects D+ pullup after reset is generated
+			 * by B device in B_HOST role which is not detected by
+			 * B device. As workaorund , start timer of 300msec
+			 * and stop timer if A device is enumerated else clear
+			 * A_CONN.
+			 */
+			msm_otg_start_timer(motg, TB_TST_CONFIG,
+						B_TST_CONFIG);
+		} else if (test_bit(B_ASE0_BRST, &motg->tmouts)) {
+			pr_debug("b_ase0_brst_tmout\n");
+			pr_info("B HNP fail:No response from A device\n");
+			msm_otg_start_host(otg, 0);
+			msm_otg_reset(otg);
+			otg->host->is_b_host = 0;
+			clear_bit(B_ASE0_BRST, &motg->tmouts);
+			clear_bit(A_BUS_SUSPEND, &motg->inputs);
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			otg_send_event(OTG_EVENT_HNP_FAILED);
+			otg->state = OTG_STATE_B_IDLE;
+			work = 1;
+		} else if (test_bit(ID_C, &motg->inputs)) {
+			msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX);
+		}
+		break;
+	case OTG_STATE_B_HOST:
+		if (!test_bit(B_BUS_REQ, &motg->inputs) ||
+				!test_bit(A_CONN, &motg->inputs) ||
+				!test_bit(B_SESS_VLD, &motg->inputs)) {
+			pr_debug("!b_bus_req || !a_conn || !b_sess_vld\n");
+			clear_bit(A_CONN, &motg->inputs);
+			clear_bit(B_BUS_REQ, &motg->inputs);
+			msm_otg_start_host(otg, 0);
+			otg->host->is_b_host = 0;
+			otg->state = OTG_STATE_B_IDLE;
+			msm_otg_reset(otg);
+			work = 1;
+		} else if (test_bit(ID_C, &motg->inputs)) {
+			msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX);
+		}
+		break;
+	case OTG_STATE_A_IDLE:
+		otg->default_a = 1;
+		if (test_bit(ID, &motg->inputs) &&
+			!test_bit(ID_A, &motg->inputs)) {
+			pr_debug("id && !id_a\n");
+			otg->default_a = 0;
+			clear_bit(A_BUS_DROP, &motg->inputs);
+			otg->state = OTG_STATE_B_IDLE;
+			del_timer_sync(&motg->id_timer);
+			msm_otg_link_reset(motg);
+			msm_chg_enable_aca_intr(motg);
+			msm_otg_notify_charger(motg, 0);
+			work = 1;
+		} else if (!test_bit(A_BUS_DROP, &motg->inputs) &&
+				(test_bit(A_SRP_DET, &motg->inputs) ||
+				 test_bit(A_BUS_REQ, &motg->inputs))) {
+			pr_debug("!a_bus_drop && (a_srp_det || a_bus_req)\n");
+
+			clear_bit(A_SRP_DET, &motg->inputs);
+			/* Disable SRP detection */
+			writel_relaxed((readl_relaxed(USB_OTGSC) &
+					~OTGSC_INTSTS_MASK) &
+					~OTGSC_DPIE, USB_OTGSC);
+
+			otg->state = OTG_STATE_A_WAIT_VRISE;
+			/* VBUS should not be supplied before end of SRP pulse
+			 * generated by PET, if not complaince test fail.
+			 */
+			usleep_range(10000, 12000);
+			/* ACA: ID_A: Stop charging untill enumeration */
+			if (test_bit(ID_A, &motg->inputs))
+				msm_otg_notify_charger(motg, 0);
+			else
+				msm_hsusb_vbus_power(motg, 1);
+			msm_otg_start_timer(motg, TA_WAIT_VRISE, A_WAIT_VRISE);
+		} else {
+			pr_debug("No session requested\n");
+			clear_bit(A_BUS_DROP, &motg->inputs);
+			if (test_bit(ID_A, &motg->inputs)) {
+					msm_otg_notify_charger(motg,
+							IDEV_ACA_CHG_MAX);
+			} else if (!test_bit(ID, &motg->inputs)) {
+				msm_otg_notify_charger(motg, 0);
+				/*
+				 * A-device is not providing power on VBUS.
+				 * Enable SRP detection.
+				 */
+				writel_relaxed(0x13, USB_USBMODE);
+				writel_relaxed((readl_relaxed(USB_OTGSC) &
+						~OTGSC_INTSTS_MASK) |
+						OTGSC_DPIE, USB_OTGSC);
+				mb();
+			}
+		}
+		break;
+	case OTG_STATE_A_WAIT_VRISE:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs) ||
+				test_bit(A_WAIT_VRISE, &motg->tmouts)) {
+			pr_debug("id || a_bus_drop || a_wait_vrise_tmout\n");
+			clear_bit(A_BUS_REQ, &motg->inputs);
+			msm_otg_del_timer(motg);
+			msm_hsusb_vbus_power(motg, 0);
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+		} else if (test_bit(A_VBUS_VLD, &motg->inputs)) {
+			pr_debug("a_vbus_vld\n");
+			otg->state = OTG_STATE_A_WAIT_BCON;
+			if (TA_WAIT_BCON > 0)
+				msm_otg_start_timer(motg, TA_WAIT_BCON,
+					A_WAIT_BCON);
+			msm_otg_start_host(otg, 1);
+			msm_chg_enable_aca_det(motg);
+			msm_chg_disable_aca_intr(motg);
+			mod_timer(&motg->id_timer, ID_TIMER_INITIAL_FREQ);
+			if (msm_chg_check_aca_intr(motg))
+				work = 1;
+		}
+		break;
+	case OTG_STATE_A_WAIT_BCON:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs) ||
+				test_bit(A_WAIT_BCON, &motg->tmouts)) {
+			pr_debug("(id && id_a/b/c) || a_bus_drop ||"
+					"a_wait_bcon_tmout\n");
+			if (test_bit(A_WAIT_BCON, &motg->tmouts)) {
+				pr_info("Device No Response\n");
+				otg_send_event(OTG_EVENT_DEV_CONN_TMOUT);
+			}
+			msm_otg_del_timer(motg);
+			clear_bit(A_BUS_REQ, &motg->inputs);
+			clear_bit(B_CONN, &motg->inputs);
+			msm_otg_start_host(otg, 0);
+			/*
+			 * ACA: ID_A with NO accessory, just the A plug is
+			 * attached to ACA: Use IDCHG_MAX for charging
+			 */
+			if (test_bit(ID_A, &motg->inputs))
+				msm_otg_notify_charger(motg, IDEV_CHG_MIN);
+			else
+				msm_hsusb_vbus_power(motg, 0);
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+		} else if (!test_bit(A_VBUS_VLD, &motg->inputs)) {
+			pr_debug("!a_vbus_vld\n");
+			clear_bit(B_CONN, &motg->inputs);
+			msm_otg_del_timer(motg);
+			msm_otg_start_host(otg, 0);
+			otg->state = OTG_STATE_A_VBUS_ERR;
+			msm_otg_reset(otg);
+		} else if (test_bit(ID_A, &motg->inputs)) {
+			msm_hsusb_vbus_power(motg, 0);
+		} else if (!test_bit(A_BUS_REQ, &motg->inputs)) {
+			/*
+			 * If TA_WAIT_BCON is infinite, we don;t
+			 * turn off VBUS. Enter low power mode.
+			 */
+			if (TA_WAIT_BCON < 0)
+				pm_runtime_put_sync(otg->dev);
+		} else if (!test_bit(ID, &motg->inputs)) {
+			msm_hsusb_vbus_power(motg, 1);
+		}
+		break;
+	case OTG_STATE_A_HOST:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs)) {
+			pr_debug("id_a/b/c || a_bus_drop\n");
+			clear_bit(B_CONN, &motg->inputs);
+			clear_bit(A_BUS_REQ, &motg->inputs);
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			msm_otg_start_host(otg, 0);
+			if (!test_bit(ID_A, &motg->inputs))
+				msm_hsusb_vbus_power(motg, 0);
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+		} else if (!test_bit(A_VBUS_VLD, &motg->inputs)) {
+			pr_debug("!a_vbus_vld\n");
+			clear_bit(B_CONN, &motg->inputs);
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_VBUS_ERR;
+			msm_otg_start_host(otg, 0);
+			msm_otg_reset(otg);
+		} else if (!test_bit(A_BUS_REQ, &motg->inputs)) {
+			/*
+			 * a_bus_req is de-asserted when root hub is
+			 * suspended or HNP is in progress.
+			 */
+			pr_debug("!a_bus_req\n");
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_SUSPEND;
+			if (otg->host->b_hnp_enable)
+				msm_otg_start_timer(motg, TA_AIDL_BDIS,
+						A_AIDL_BDIS);
+			else
+				pm_runtime_put_sync(otg->dev);
+		} else if (!test_bit(B_CONN, &motg->inputs)) {
+			pr_debug("!b_conn\n");
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_WAIT_BCON;
+			if (TA_WAIT_BCON > 0)
+				msm_otg_start_timer(motg, TA_WAIT_BCON,
+					A_WAIT_BCON);
+			if (msm_chg_check_aca_intr(motg))
+				work = 1;
+		} else if (test_bit(ID_A, &motg->inputs)) {
+			msm_otg_del_timer(motg);
+			msm_hsusb_vbus_power(motg, 0);
+			if (motg->chg_type == USB_ACA_DOCK_CHARGER)
+				msm_otg_notify_charger(motg,
+						IDEV_ACA_CHG_MAX);
+			else
+				msm_otg_notify_charger(motg,
+						IDEV_CHG_MIN - motg->mA_port);
+		} else if (!test_bit(ID, &motg->inputs)) {
+			motg->chg_state = USB_CHG_STATE_UNDEFINED;
+			motg->chg_type = USB_INVALID_CHARGER;
+			msm_otg_notify_charger(motg, 0);
+			msm_hsusb_vbus_power(motg, 1);
+		}
+		break;
+	case OTG_STATE_A_SUSPEND:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs) ||
+				test_bit(A_AIDL_BDIS, &motg->tmouts)) {
+			pr_debug("id_a/b/c || a_bus_drop ||"
+					"a_aidl_bdis_tmout\n");
+			msm_otg_del_timer(motg);
+			clear_bit(B_CONN, &motg->inputs);
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			msm_otg_start_host(otg, 0);
+			msm_otg_reset(otg);
+			if (!test_bit(ID_A, &motg->inputs))
+				msm_hsusb_vbus_power(motg, 0);
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+		} else if (!test_bit(A_VBUS_VLD, &motg->inputs)) {
+			pr_debug("!a_vbus_vld\n");
+			msm_otg_del_timer(motg);
+			clear_bit(B_CONN, &motg->inputs);
+			otg->state = OTG_STATE_A_VBUS_ERR;
+			msm_otg_start_host(otg, 0);
+			msm_otg_reset(otg);
+		} else if (!test_bit(B_CONN, &motg->inputs) &&
+				otg->host->b_hnp_enable) {
+			pr_debug("!b_conn && b_hnp_enable");
+			otg->state = OTG_STATE_A_PERIPHERAL;
+			msm_otg_host_hnp_enable(otg, 1);
+			otg->gadget->is_a_peripheral = 1;
+			msm_otg_start_peripheral(otg, 1);
+		} else if (!test_bit(B_CONN, &motg->inputs) &&
+				!otg->host->b_hnp_enable) {
+			pr_debug("!b_conn && !b_hnp_enable");
+			/*
+			 * bus request is dropped during suspend.
+			 * acquire again for next device.
+			 */
+			set_bit(A_BUS_REQ, &motg->inputs);
+			otg->state = OTG_STATE_A_WAIT_BCON;
+			if (TA_WAIT_BCON > 0)
+				msm_otg_start_timer(motg, TA_WAIT_BCON,
+					A_WAIT_BCON);
 		} else if (test_bit(ID_A, &motg->inputs)) {
 			msm_hsusb_vbus_power(motg, 0);
 			msm_otg_notify_charger(motg,
-					IDEV_ACA_CHG_MAX - motg->mA_port);
+					IDEV_CHG_MIN - motg->mA_port);
 		} else if (!test_bit(ID, &motg->inputs)) {
 			msm_otg_notify_charger(motg, 0);
 			msm_hsusb_vbus_power(motg, 1);
 		}
 		break;
+	case OTG_STATE_A_PERIPHERAL:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs)) {
+			pr_debug("id _f/b/c || a_bus_drop\n");
+			/* Clear BIDL_ADIS timer */
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			msm_otg_start_peripheral(otg, 0);
+			otg->gadget->is_a_peripheral = 0;
+			msm_otg_start_host(otg, 0);
+			msm_otg_reset(otg);
+			if (!test_bit(ID_A, &motg->inputs))
+				msm_hsusb_vbus_power(motg, 0);
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+		} else if (!test_bit(A_VBUS_VLD, &motg->inputs)) {
+			pr_debug("!a_vbus_vld\n");
+			/* Clear BIDL_ADIS timer */
+			msm_otg_del_timer(motg);
+			otg->state = OTG_STATE_A_VBUS_ERR;
+			msm_otg_start_peripheral(otg, 0);
+			otg->gadget->is_a_peripheral = 0;
+			msm_otg_start_host(otg, 0);
+		} else if (test_bit(A_BIDL_ADIS, &motg->tmouts)) {
+			pr_debug("a_bidl_adis_tmout\n");
+			msm_otg_start_peripheral(otg, 0);
+			otg->gadget->is_a_peripheral = 0;
+			otg->state = OTG_STATE_A_WAIT_BCON;
+			set_bit(A_BUS_REQ, &motg->inputs);
+			msm_otg_host_hnp_enable(otg, 0);
+			if (TA_WAIT_BCON > 0)
+				msm_otg_start_timer(motg, TA_WAIT_BCON,
+					A_WAIT_BCON);
+		} else if (test_bit(ID_A, &motg->inputs)) {
+			msm_hsusb_vbus_power(motg, 0);
+			msm_otg_notify_charger(motg,
+					IDEV_CHG_MIN - motg->mA_port);
+		} else if (!test_bit(ID, &motg->inputs)) {
+			msm_otg_notify_charger(motg, 0);
+			msm_hsusb_vbus_power(motg, 1);
+		}
+		break;
+	case OTG_STATE_A_WAIT_VFALL:
+		if (test_bit(A_WAIT_VFALL, &motg->tmouts)) {
+			clear_bit(A_VBUS_VLD, &motg->inputs);
+			otg->state = OTG_STATE_A_IDLE;
+			work = 1;
+		}
+		break;
+	case OTG_STATE_A_VBUS_ERR:
+		if ((test_bit(ID, &motg->inputs) &&
+				!test_bit(ID_A, &motg->inputs)) ||
+				test_bit(A_BUS_DROP, &motg->inputs) ||
+				test_bit(A_CLR_ERR, &motg->inputs)) {
+			otg->state = OTG_STATE_A_WAIT_VFALL;
+			if (!test_bit(ID_A, &motg->inputs))
+				msm_hsusb_vbus_power(motg, 0);
+			msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL);
+			motg->chg_state = USB_CHG_STATE_UNDEFINED;
+			motg->chg_type = USB_INVALID_CHARGER;
+			msm_otg_notify_charger(motg, 0);
+		}
+		break;
 	default:
 		break;
 	}
+	if (work)
+		queue_work(system_nrt_wq, &motg->sm_work);
 }
 
 static irqreturn_t msm_otg_irq(int irq, void *data)
 {
 	struct msm_otg *motg = data;
 	struct otg_transceiver *otg = &motg->otg;
-	u32 otgsc = 0, usbsts;
+	u32 otgsc = 0, usbsts, pc;
+	bool work = 0;
+	irqreturn_t ret = IRQ_HANDLED;
 
 	if (atomic_read(&motg->in_lpm)) {
 		pr_debug("OTG IRQ: in LPM\n");
@@ -1984,44 +2582,144 @@
 	}
 
 	usbsts = readl(USB_USBSTS);
-	if ((usbsts & PHY_ALT_INT)) {
-		dev_dbg(otg->dev, "PHY_ALT interrupt\n");
-		writel(PHY_ALT_INT, USB_USBSTS);
-		if (msm_chg_check_aca_intr(motg)) {
-			dev_dbg(otg->dev, "ACA work from IRQ\n");
-			schedule_work(&motg->sm_work);
-		}
-		return IRQ_HANDLED;
-	}
-
 	otgsc = readl(USB_OTGSC);
-	if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS)))
+
+	if (!(otgsc & OTG_OTGSTS_MASK) && !(usbsts & OTG_USBSTS_MASK))
 		return IRQ_NONE;
 
 	if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) {
 		if (otgsc & OTGSC_ID) {
-			dev_dbg(otg->dev, "ID set\n");
+			pr_debug("Id set\n");
 			set_bit(ID, &motg->inputs);
 		} else {
-			dev_dbg(otg->dev, "ID clear\n");
+			pr_debug("Id clear\n");
+			/*
+			 * Assert a_bus_req to supply power on
+			 * VBUS when Micro/Mini-A cable is connected
+			 * with out user intervention.
+			 */
+			set_bit(A_BUS_REQ, &motg->inputs);
 			clear_bit(ID, &motg->inputs);
 			msm_chg_enable_aca_det(motg);
 		}
-		schedule_work(&motg->sm_work);
-	} else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) {
+		writel_relaxed(otgsc, USB_OTGSC);
+		work = 1;
+	} else if (otgsc & OTGSC_DPIS) {
+		pr_debug("DPIS detected\n");
+		writel_relaxed(otgsc, USB_OTGSC);
+		set_bit(A_SRP_DET, &motg->inputs);
+		set_bit(A_BUS_REQ, &motg->inputs);
+		work = 1;
+	} else if (otgsc & OTGSC_BSVIS) {
+		writel_relaxed(otgsc, USB_OTGSC);
+		/*
+		 * BSV interrupt comes when operating as an A-device
+		 * (VBUS on/off).
+		 * But, handle BSV when charger is removed from ACA in ID_A
+		 */
+		if ((otg->state >= OTG_STATE_A_IDLE) &&
+			!test_bit(ID_A, &motg->inputs))
+			return IRQ_HANDLED;
 		if (otgsc & OTGSC_BSV) {
-			dev_dbg(otg->dev, "BSV set\n");
+			pr_debug("BSV set\n");
 			set_bit(B_SESS_VLD, &motg->inputs);
 		} else {
-			dev_dbg(otg->dev, "BSV clear\n");
+			pr_debug("BSV clear\n");
 			clear_bit(B_SESS_VLD, &motg->inputs);
 			msm_chg_check_aca_intr(motg);
 		}
-		schedule_work(&motg->sm_work);
+		work = 1;
+	} else if (usbsts & STS_PCI) {
+		pc = readl_relaxed(USB_PORTSC);
+		pr_debug("portsc = %x\n", pc);
+		ret = IRQ_NONE;
+		/*
+		 * HCD Acks PCI interrupt. We use this to switch
+		 * between different OTG states.
+		 */
+		work = 1;
+		switch (otg->state) {
+		case OTG_STATE_A_SUSPEND:
+			if (otg->host->b_hnp_enable && (pc & PORTSC_CSC) &&
+					!(pc & PORTSC_CCS)) {
+				pr_debug("B_CONN clear\n");
+				clear_bit(B_CONN, &motg->inputs);
+				msm_otg_del_timer(motg);
+			}
+			break;
+		case OTG_STATE_A_PERIPHERAL:
+			/*
+			 * A-peripheral observed activity on bus.
+			 * clear A_BIDL_ADIS timer.
+			 */
+			msm_otg_del_timer(motg);
+			work = 0;
+			break;
+		case OTG_STATE_B_WAIT_ACON:
+			if ((pc & PORTSC_CSC) && (pc & PORTSC_CCS)) {
+				pr_debug("A_CONN set\n");
+				set_bit(A_CONN, &motg->inputs);
+				/* Clear ASE0_BRST timer */
+				msm_otg_del_timer(motg);
+			}
+			break;
+		case OTG_STATE_B_HOST:
+			if ((pc & PORTSC_CSC) && !(pc & PORTSC_CCS)) {
+				pr_debug("A_CONN clear\n");
+				clear_bit(A_CONN, &motg->inputs);
+				msm_otg_del_timer(motg);
+			}
+			break;
+		case OTG_STATE_A_WAIT_BCON:
+			if (TA_WAIT_BCON < 0)
+				set_bit(A_BUS_REQ, &motg->inputs);
+		default:
+			work = 0;
+			break;
+		}
+	} else if (usbsts & STS_URI) {
+		ret = IRQ_NONE;
+		switch (otg->state) {
+		case OTG_STATE_A_PERIPHERAL:
+			/*
+			 * A-peripheral observed activity on bus.
+			 * clear A_BIDL_ADIS timer.
+			 */
+			msm_otg_del_timer(motg);
+			work = 0;
+			break;
+		default:
+			work = 0;
+			break;
+		}
+	} else if (usbsts & STS_SLI) {
+		ret = IRQ_NONE;
+		work = 0;
+		switch (otg->state) {
+		case OTG_STATE_B_PERIPHERAL:
+			if (otg->gadget->b_hnp_enable) {
+				set_bit(A_BUS_SUSPEND, &motg->inputs);
+				set_bit(B_BUS_REQ, &motg->inputs);
+				work = 1;
+			}
+			break;
+		case OTG_STATE_A_PERIPHERAL:
+			msm_otg_start_timer(motg, TA_BIDL_ADIS,
+					A_BIDL_ADIS);
+			break;
+		default:
+			break;
+		}
+	} else if ((usbsts & PHY_ALT_INT)) {
+		writel_relaxed(PHY_ALT_INT, USB_USBSTS);
+		if (msm_chg_check_aca_intr(motg))
+			work = 1;
+		ret = IRQ_HANDLED;
 	}
+	if (work)
+		queue_work(system_nrt_wq, &motg->sm_work);
 
-	writel(otgsc, USB_OTGSC);
-	return IRQ_HANDLED;
+	return ret;
 }
 
 static void msm_otg_set_vbus_state(int online)
@@ -2044,7 +2742,7 @@
 		return;
 	}
 
-	schedule_work(&motg->sm_work);
+	queue_work(system_nrt_wq, &motg->sm_work);
 }
 
 static irqreturn_t msm_pmic_id_irq(int irq, void *data)
@@ -2060,10 +2758,11 @@
 	} else {
 		pr_debug("PMIC: ID clear\n");
 		clear_bit(ID, &motg->inputs);
+		set_bit(A_BUS_REQ, &motg->inputs);
 	}
 
 	if (motg->otg.state != OTG_STATE_UNDEFINED)
-		schedule_work(&motg->sm_work);
+		queue_work(system_nrt_wq, &motg->sm_work);
 
 	return IRQ_HANDLED;
 }
@@ -2159,7 +2858,7 @@
 	}
 
 	pm_runtime_resume(otg->dev);
-	schedule_work(&motg->sm_work);
+	queue_work(system_nrt_wq, &motg->sm_work);
 out:
 	return status;
 }
@@ -2622,6 +3321,7 @@
 	mb();
 
 	wake_lock_init(&motg->wlock, WAKE_LOCK_SUSPEND, "msm_otg");
+	msm_otg_init_timer(motg);
 	INIT_WORK(&motg->sm_work, msm_otg_sm_work);
 	INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work);
 	setup_timer(&motg->id_timer, msm_otg_id_timer_func,
@@ -2637,6 +3337,8 @@
 	otg->set_host = msm_otg_set_host;
 	otg->set_peripheral = msm_otg_set_peripheral;
 	otg->set_power = msm_otg_set_power;
+	otg->start_hnp = msm_otg_start_hnp;
+	otg->start_srp = msm_otg_start_srp;
 	otg->set_suspend = msm_otg_set_suspend;
 
 	otg->io_ops = &msm_otg_io_ops;
diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h
index f2ad7e1..5845cb6 100644
--- a/include/linux/usb/msm_hsusb.h
+++ b/include/linux/usb/msm_hsusb.h
@@ -183,6 +183,46 @@
 	struct msm_bus_scale_pdata *bus_scale_table;
 };
 
+/* Timeout (in msec) values (min - max) associated with OTG timers */
+
+#define TA_WAIT_VRISE	100	/* ( - 100)  */
+#define TA_WAIT_VFALL	500	/* ( - 1000) */
+
+/*
+ * This option is set for embedded hosts or OTG devices in which leakage
+ * currents are very minimal.
+ */
+#ifdef CONFIG_USB_OTG
+#define TA_WAIT_BCON	30000	/* (1100 - 30000) */
+#else
+#define TA_WAIT_BCON	-1
+#endif
+
+#define TA_AIDL_BDIS	500	/* (200 - ) */
+#define TA_BIDL_ADIS	155	/* (155 - 200) */
+#define TB_SRP_FAIL	6000	/* (5000 - 6000) */
+#define TB_ASE0_BRST	200	/* (155 - ) */
+
+/* TB_SSEND_SRP and TB_SE0_SRP are combined */
+#define TB_SRP_INIT	2000	/* (1500 - ) */
+
+#define TA_TST_MAINT	10100	/* (9900 - 10100) */
+#define TB_TST_SRP	3000	/* ( - 5000) */
+#define TB_TST_CONFIG	300
+
+/* Timeout variables */
+
+#define A_WAIT_VRISE	0
+#define A_WAIT_VFALL	1
+#define A_WAIT_BCON	2
+#define A_AIDL_BDIS	3
+#define A_BIDL_ADIS	4
+#define B_SRP_FAIL	5
+#define B_ASE0_BRST	6
+#define A_TST_MAINT	7
+#define B_TST_SRP	8
+#define B_TST_CONFIG	9
+
 /**
  * struct msm_otg: OTG driver data. Shared by HCD and DCD.
  * @otg: USB OTG Transceiver structure.
@@ -229,6 +269,18 @@
 #define ID_A		2
 #define ID_B		3
 #define ID_C		4
+#define A_BUS_DROP	5
+#define A_BUS_REQ	6
+#define A_SRP_DET	7
+#define A_VBUS_VLD	8
+#define B_CONN		9
+#define ADP_CHANGE	10
+#define POWER_UP	11
+#define A_CLR_ERR	12
+#define A_BUS_RESUME	13
+#define A_BUS_SUSPEND	14
+#define A_CONN		15
+#define B_BUS_REQ	16
 	unsigned long inputs;
 	struct work_struct sm_work;
 	atomic_t in_lpm;
@@ -269,6 +321,10 @@
 #define PHY_OTG_COMP_DISABLED		BIT(2)
 	struct pm_qos_request_list pm_qos_req_dma;
 	int reset_counter;
+	unsigned long b_last_se0_sess;
+	unsigned long tmouts;
+	u8 active_tmout;
+	struct hrtimer timer;
 };
 
 struct msm_hsic_host_platform_data {
diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h
index fcd91da..8a05136 100644
--- a/include/linux/usb/msm_hsusb_hw.h
+++ b/include/linux/usb/msm_hsusb_hw.h
@@ -33,6 +33,8 @@
 #define PORTSC_PHCD            (1 << 23) /* phy suspend mode */
 #define PORTSC_PTS_MASK         (3 << 30)
 #define PORTSC_PTS_ULPI         (3 << 30)
+#define PORTSC_CSC              (1 << 1)
+#define PORTSC_CCS              (1 << 0)
 
 #define USB_ULPI_VIEWPORT    (MSM_USB_BASE + 0x0170)
 #define ULPI_RUN              (1 << 30)
@@ -54,14 +56,26 @@
 #define PHY_IDHV_INTEN          (1 << 8) /* PHY ID HV interrupt */
 #define PHY_OTGSESSVLDHV_INTEN  (1 << 9) /* PHY Session Valid HV int. */
 
+#define STS_PCI                 (1 << 2) /* R/WC - Port Change Detect */
+#define STS_URI                 (1 << 6) /* R/WC - RESET recv'd */
+#define STS_SLI                 (1 << 8) /* R/WC - suspend state entered */
+
 /* OTG definitions */
 #define OTGSC_INTSTS_MASK	(0x7f << 16)
 #define OTGSC_IDPU		(1 << 5)
+#define OTGSC_INTR_MASK		(0x7f << 24)
+#define OTGSC_HADP		(1 << 6)
 #define OTGSC_ID		(1 << 8)
 #define OTGSC_BSV		(1 << 11)
 #define OTGSC_IDIS		(1 << 16)
 #define OTGSC_BSVIS		(1 << 19)
 #define OTGSC_IDIE		(1 << 24)
 #define OTGSC_BSVIE		(1 << 27)
+#define OTGSC_DPIE		(1 << 30)
+#define OTGSC_DPIS		(1 << 22)
+
+/* OTG interrupt status mask */
+#define OTG_USBSTS_MASK		(STS_PCI | STS_URI | STS_SLI | PHY_ALT_INT)
+#define OTG_OTGSTS_MASK		(OTGSC_IDIS | OTGSC_BSVIS | OTGSC_DPIS)
 
 #endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */