usb: gadget: tegra: add NV charger detection

Adding the support to detect NV charger and
set current limit to withdraw as 2.0amps.

Bug 1193528

Change-Id: I5ef6c553b33dfa45bfd9e07a9ba05ddf3c75e239
Signed-off-by: Rakesh Bodla <rbodla@nvidia.com>
Reviewed-on: http://git-master/r/192055
Reviewed-by: Riham Haidar <rhaidar@nvidia.com>
Tested-by: Riham Haidar <rhaidar@nvidia.com>
diff --git a/arch/arm/mach-tegra/include/mach/usb_phy.h b/arch/arm/mach-tegra/include/mach/usb_phy.h
index 2db26c2..9413c46 100644
--- a/arch/arm/mach-tegra/include/mach/usb_phy.h
+++ b/arch/arm/mach-tegra/include/mach/usb_phy.h
@@ -97,6 +97,12 @@
 bool tegra_usb_phy_charger_detected(struct tegra_usb_phy *phy);
 
 /**
+ * Indicates whether nvidia proprietary charger is connected or not
+ * if nvidia proprietary charger is detected then returns true else false
+ */
+bool tegra_usb_phy_nv_charger_detected(struct tegra_usb_phy *phy);
+
+/**
  * Indicates whether phy resumed due to the pmc remote/hotplug wake event
  *  or not, returns true if remote/hotplug wake is detected.
  */
diff --git a/arch/arm/mach-tegra/tegra11x_usb_phy.c b/arch/arm/mach-tegra/tegra11x_usb_phy.c
index cddfdc1..388f6cc 100644
--- a/arch/arm/mach-tegra/tegra11x_usb_phy.c
+++ b/arch/arm/mach-tegra/tegra11x_usb_phy.c
@@ -75,6 +75,7 @@
 #define   USB_PORTSC_CCS	(1 << 0)
 #define   USB_PORTSC_RWC_BITS (USB_PORTSC_CSC | USB_PORTSC_PEC | USB_PORTSC_OCC)
 #define   USB_PORTSC_PSPD_MASK	3
+#define   USB_PORTSC_LINE_STATE(x) (((x) & (0x3 << 10)) >> 10)
 
 #define HOSTPC1_DEVLC		0x1b4
 #define   HOSTPC1_DEVLC_PHCD		(1 << 22)
@@ -218,10 +219,17 @@
 #define   UTMIP_DPDM_OBSERVE_SEL_FS_SE1 UTMIP_DPDM_OBSERVE_SEL(0xd)
 #define   UTMIP_DPDM_OBSERVE_SEL_FS_SE0 UTMIP_DPDM_OBSERVE_SEL(0xc)
 #define   UTMIP_SUSPEND_EXIT_ON_EDGE	(1 << 22)
-#define   FORCE_PULLDN_DM	(1 << 8)
+#define   DISABLE_PULLUP_DP (1 << 15)
+#define   DISABLE_PULLUP_DM (1 << 14)
+#define   DISABLE_PULLDN_DP (1 << 13)
+#define   DISABLE_PULLDN_DM (1 << 12)
+#define   FORCE_PULLUP_DP	(1 << 11)
+#define   FORCE_PULLUP_DM	(1 << 10)
 #define   FORCE_PULLDN_DP	(1 << 9)
+#define   FORCE_PULLDN_DM	(1 << 8)
 #define   COMB_TERMS		(1 << 0)
 #define   ALWAYS_FREE_RUNNING_TERMS (1 << 1)
+#define   MASK_ALL_PULLUP_PULLDOWN (0xff << 8)
 
 #define UTMIP_SPARE_CFG0	0x834
 #define   FUSE_SETUP_SEL		(1 << 3)
@@ -1800,6 +1808,61 @@
 	return status;
 }
 
+static bool utmi_phy_nv_charger_detect(struct tegra_usb_phy *phy)
+{
+	unsigned long val;
+	void __iomem *base = phy->regs;
+	bool status;
+	DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
+
+	/* Turn off all terminations so we see DPDN states clearly,
+		and only leave on DM pulldown */
+	val = readl(base + UTMIP_MISC_CFG0);
+	val &= ~MASK_ALL_PULLUP_PULLDOWN;
+	val |= (DISABLE_PULLUP_DP | DISABLE_PULLUP_DM
+			| FORCE_PULLDN_DP | DISABLE_PULLDN_DM);
+	writel(val, base + UTMIP_MISC_CFG0);
+
+	udelay(100);
+
+	val = readl(base + USB_PORTSC);
+	status = (USB_PORTSC_LINE_STATE(val) == 0);
+
+	/* Turn off all terminations except for DP pullup */
+	val = readl(base + UTMIP_MISC_CFG0);
+	val &= ~MASK_ALL_PULLUP_PULLDOWN;
+	val |= (FORCE_PULLUP_DP | DISABLE_PULLUP_DM
+			| DISABLE_PULLDN_DP | DISABLE_PULLDN_DM);
+	writel(val, base + UTMIP_MISC_CFG0);
+
+	udelay(100);
+
+	val = readl(base + USB_PORTSC);
+	if (status & (USB_PORTSC_LINE_STATE(val) == 0x3)) {
+		status = false;
+	} else {
+		status = false;
+
+		/* Check for NV charger DISABLE all terminations */
+		val = readl(base + UTMIP_MISC_CFG0);
+		val &= ~MASK_ALL_PULLUP_PULLDOWN;
+		val |= (DISABLE_PULLUP_DP | DISABLE_PULLUP_DM
+				| DISABLE_PULLDN_DP | DISABLE_PULLDN_DM);
+		writel(val, base + UTMIP_MISC_CFG0);
+
+		val = readl(base + USB_PORTSC);
+		if (USB_PORTSC_LINE_STATE(val) == 0x3)
+			status = true;
+	}
+
+	/* Restore standard termination by hardware. */
+	val = readl(base + UTMIP_MISC_CFG0);
+	val &= ~MASK_ALL_PULLUP_PULLDOWN;
+	writel(val, base + UTMIP_MISC_CFG0);
+
+	return status;
+}
+
 static void uhsic_powerup_pmc_wake_detect(struct tegra_usb_phy *phy)
 {
 	unsigned long val;
@@ -3107,6 +3170,7 @@
 	.pre_resume = utmi_phy_pre_resume,
 	.resume	= utmi_phy_resume,
 	.charger_detect = utmi_phy_charger_detect,
+	.nv_charger_detect = utmi_phy_nv_charger_detect,
 };
 
 static struct tegra_usb_phy_ops uhsic_phy_ops = {
diff --git a/arch/arm/mach-tegra/tegra_usb_phy.h b/arch/arm/mach-tegra/tegra_usb_phy.h
index ef7fd79..f4058b5 100644
--- a/arch/arm/mach-tegra/tegra_usb_phy.h
+++ b/arch/arm/mach-tegra/tegra_usb_phy.h
@@ -67,6 +67,7 @@
 	int (*power_off)(struct tegra_usb_phy *phy);
 	int (*power_on)(struct tegra_usb_phy *phy);
 	bool (*charger_detect)(struct tegra_usb_phy *phy);
+	bool (*nv_charger_detect)(struct tegra_usb_phy *phy);
 };
 
 /**
diff --git a/arch/arm/mach-tegra/usb_phy.c b/arch/arm/mach-tegra/usb_phy.c
index 387e29a..40ba21b 100644
--- a/arch/arm/mach-tegra/usb_phy.c
+++ b/arch/arm/mach-tegra/usb_phy.c
@@ -572,6 +572,17 @@
 	return status;
 }
 
+bool tegra_usb_phy_nv_charger_detected(struct tegra_usb_phy *phy)
+{
+	bool status = 0;
+
+	DBG("%s(%d) inst:[%d]\n", __func__, __LINE__, phy->inst);
+	if (phy->ops && phy->ops->nv_charger_detect)
+		status = phy->ops->nv_charger_detect(phy);
+
+	return status;
+}
+
 bool tegra_usb_phy_hw_accessible(struct tegra_usb_phy *phy)
 {
 	if (!phy->hw_accessible)
diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c
index c316091..6826e0b 100644
--- a/drivers/usb/gadget/tegra_udc.c
+++ b/drivers/usb/gadget/tegra_udc.c
@@ -1309,6 +1309,10 @@
 		pr_debug("detected CDP port(1A USB port)");
 		max_ua = USB_CHARGING_CDP_CURRENT_LIMIT_UA;
 		break;
+	case CONNECT_TYPE_NV_CHARGER:
+		pr_debug("detected NV charging port");
+		max_ua = USB_CHARGING_NV_CHARGER_CURRENT_LIMIT_UA;
+		break;
 	case CONNECT_TYPE_NON_STANDARD_CHARGER:
 		pr_debug("detected non-standard charging port");
 		max_ua = USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA;
@@ -1397,15 +1401,18 @@
 		if (tegra_usb_phy_charger_detected(udc->phy)) {
 			tegra_detect_charging_type_is_cdp_or_dcp(udc);
 		} else {
-			udc->connect_type = CONNECT_TYPE_SDP;
-			/*
-			 * Schedule work to wait for 1000 msec and check for
-			 * a non-standard charger if setup packet is not
-			 * received.
-			 */
-			schedule_delayed_work(&udc->non_std_charger_work,
-				msecs_to_jiffies(
-				USB_CHARGER_DETECTION_WAIT_TIME_MS));
+			if (tegra_usb_phy_nv_charger_detected(udc->phy)) {
+				udc->connect_type = CONNECT_TYPE_NV_CHARGER;
+			} else {
+				udc->connect_type = CONNECT_TYPE_SDP;
+				/* Schedule work to wait for 4000 msec and check
+				 * for a non-standard charger if setup packet is
+				 * not received.
+				 */
+				schedule_delayed_work(&udc->non_std_charger_work
+					, msecs_to_jiffies(
+					USB_CHARGER_DETECTION_WAIT_TIME_MS));
+			}
 		}
 		/* start the controller */
 		dr_controller_run(udc);
diff --git a/drivers/usb/gadget/tegra_udc.h b/drivers/usb/gadget/tegra_udc.h
index ac609ed..d90d462 100644
--- a/drivers/usb/gadget/tegra_udc.h
+++ b/drivers/usb/gadget/tegra_udc.h
@@ -41,10 +41,11 @@
 #define USB_CHARGING_DCP_CURRENT_LIMIT_UA 1800000
 #define USB_CHARGING_CDP_CURRENT_LIMIT_UA 1500000
 #define USB_CHARGING_SDP_CURRENT_LIMIT_UA 100000
+#define USB_CHARGING_NV_CHARGER_CURRENT_LIMIT_UA 2000000
 #define USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA 1800000
 
- /* 1 sec wait time for charger detection after vbus is detected */
-#define USB_CHARGER_DETECTION_WAIT_TIME_MS 1000
+ /* 4 sec wait time for charger detection after vbus is detected */
+#define USB_CHARGER_DETECTION_WAIT_TIME_MS 4000
 #define BOOST_TRIGGER_SIZE 4096
 
 #define UDC_RESET_TIMEOUT_MS 1000
@@ -409,6 +410,7 @@
 	CONNECT_TYPE_SDP,
 	CONNECT_TYPE_DCP,
 	CONNECT_TYPE_CDP,
+	CONNECT_TYPE_NV_CHARGER,
 	CONNECT_TYPE_NON_STANDARD_CHARGER
 };