OTG: usb_multi_charger_detect

Ported from JCsullins 3.0 Kernel

Change-Id: I3908fd6e4fa6c526c1c9bf9d178491d78a3c6022
diff --git a/arch/arm/configs/tenderloin_android_defconfig b/arch/arm/configs/tenderloin_android_defconfig
index 99485a0..0899332 100644
--- a/arch/arm/configs/tenderloin_android_defconfig
+++ b/arch/arm/configs/tenderloin_android_defconfig
@@ -2869,6 +2869,7 @@
 # CONFIG_USB_DUMMY_HCD is not set
 CONFIG_USB_GADGET_DUALSPEED=y
 # CONFIG_USB_GADGET_SUPERSPEED is not set
+CONFIG_USB_MULTIPLE_CHARGER_DETECT=y
 # CONFIG_USB_ZERO is not set
 # CONFIG_USB_AUDIO is not set
 # CONFIG_USB_ETH is not set
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index e0a24e5..0a2e7a5 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -586,6 +586,12 @@
 	 indicate if the device should operate in superspeed(=y)
 	 or not.
 
+config USB_MULTIPLE_CHARGER_DETECT
+	boolean "Multiple USB Wall charger support"
+	depends on USB_GADGET && USB_MSM_72K
+	help
+	  Detect multiple types of USB wall charger
+
 #
 # USB Gadget Drivers
 #
diff --git a/drivers/usb/gadget/msm72k_udc.c b/drivers/usb/gadget/msm72k_udc.c
index e6ba74b..b65a93f 100644
--- a/drivers/usb/gadget/msm72k_udc.c
+++ b/drivers/usb/gadget/msm72k_udc.c
@@ -192,6 +192,10 @@
 	unsigned chg_current;
 	unsigned chg_type_retry_cnt;
 	bool proprietary_chg;
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+	// this flag is to indicate if USB Y-cable(charger + data) is attached.
+	unsigned is_cdp;
+#endif
 	struct delayed_work chg_det;
 	struct delayed_work chg_stop;
 	struct msm_hsusb_gadget_platform_data *pdata;
@@ -238,6 +242,26 @@
 static void usb_reset(struct usb_info *ui);
 static int usb_ept_set_halt(struct usb_ep *_ep, int value);
 
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+static int usb_multi_chg_detect(struct usb_info *ui);
+
+static unsigned ulpi_read_with_reset(struct usb_info *ui, unsigned reg)
+{
+	if (ui->xceiv->io_ops->read_with_reset) {
+		return ui->xceiv->io_ops->read_with_reset(ui->xceiv, reg);
+	}
+	return 0;
+}
+
+static int ulpi_write_with_reset(struct usb_info *ui, unsigned val, unsigned reg)
+{
+	if (ui->xceiv->io_ops->write_with_reset) {
+		return ui->xceiv->io_ops->write_with_reset(ui->xceiv, val, reg);
+	}
+	return 0;
+}
+#endif
+
 static void msm_hsusb_set_speed(struct usb_info *ui)
 {
 	unsigned long flags;
@@ -328,14 +352,30 @@
 	spin_lock_irqsave(&ui->lock, flags);
 	suspended = ui->usb_state == USB_STATE_SUSPENDED ? 1 : 0;
 	configured = atomic_read(&ui->configured);
+#ifndef CONFIG_USB_MULTIPLE_CHARGER_DETECT
 	bmaxpow = ui->b_max_pow;
+#else
+	if (ui->is_cdp != 0) {
+		bmaxpow = ui->chg_current;
+		//printk("UDC: it is CDP\n");
+	}
+	else
+	{
+		bmaxpow = ui->b_max_pow;
+		//printk("UDC: it is SDP\n");
+	}
+#endif
 	spin_unlock_irqrestore(&ui->lock, flags);
 
 	if (temp == USB_CHG_TYPE__INVALID)
 		return -ENODEV;
 
 	if (temp == USB_CHG_TYPE__WALLCHARGER && !ui->proprietary_chg)
+#ifndef CONFIG_USB_MULTIPLE_CHARGER_DETECT
 		return USB_WALLCHARGER_CHG_CURRENT;
+#else
+		return ui->chg_current;
+#endif
 	else
 		return USB_PROPRIETARY_CHG_CURRENT;
 
@@ -441,7 +481,7 @@
 		spin_unlock_irqrestore(&ui->lock, flags);
 		return;
 	}
-
+#ifndef CONFIG_USB_MULTIPLE_CHARGER_DETECT
 	temp = usb_get_chg_type(ui);
 	if (temp != USB_CHG_TYPE__WALLCHARGER && temp != USB_CHG_TYPE__SDP
 					&& !ui->chg_type_retry_cnt) {
@@ -460,7 +500,18 @@
 	maxpower = usb_get_max_power(ui);
 	if (maxpower > 0)
 		usb_phy_set_power(ui->xceiv, maxpower);
+#else
+	spin_unlock_irqrestore(&ui->lock, flags);
 
+	maxpower = usb_multi_chg_detect(ui);
+	msm72k_pullup_internal(&ui->gadget, 1);
+
+	temp = atomic_read(&otg->chg_type);
+	printk("%s: temp = %d\n", __func__, temp);
+
+	if (maxpower > 0 && temp != USB_CHG_TYPE__SDP )
+		usb_phy_set_power(ui->xceiv, maxpower);
+#endif
 	/* USB driver prevents idle and suspend power collapse(pc)
 	 * while USB cable is connected. But when dedicated charger is
 	 * connected, driver can vote for idle and suspend pc.
@@ -474,6 +525,106 @@
 	}
 }
 
+static int usb_multi_chg_detect(struct usb_info *ui)
+{
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+	struct msm_otg *otg = to_msm_otg(ui->xceiv);
+	enum chg_type temp = USB_CHG_TYPE__INVALID;
+	int maxpower = -EINVAL;
+
+	ui->is_cdp = 0; // not CDP
+
+	//disconnect all pull-up and pull-down resistors on D+ and D-
+	writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD);
+	/* S/W workaround, Issue#1 */
+	ulpi_write_with_reset(ui, 0x0f, 0x34);
+	//ulpi_write_with_reset(ui, 0x48, 0x04);
+
+	msleep(10);
+
+	ulpi_write_with_reset(ui,0x4d, 0x04);
+	ulpi_write_with_reset(ui,0x06, 0x0c);
+	msleep(20/*10*/);  // this delay must be here !!
+
+	if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 11)) { //D+ : high
+		if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 10)) { //D+ : high, D- : high
+			ulpi_write_with_reset(ui, 0x45, 0x04);
+			ulpi_write_with_reset(ui, 0x2, 0x0b); //pull-down D+
+			msleep(10/*100*/);
+//			printk("UDC-CHG (2-1): %s (%d) : D+/D- = 0x%x\n", __func__, __LINE__, (readl(USB_PORTSC) & PORTSC_LS));
+			if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 10)) { //D+ : high, D- : high
+				printk("UDC-CHG (2-1-1-1): %s (%d) : HP Phone Adaptor(900mA)!\n", __func__, __LINE__);
+				temp = USB_CHG_TYPE__WALLCHARGER;
+				maxpower = 900;//500;
+			} else { //D+ : high, D- : low
+				printk("UDC-CHG (2-1-1-2): %s (%d) : 10W Adaptor(2000mA)!\n", __func__, __LINE__);
+				temp = USB_CHG_TYPE__WALLCHARGER;
+				maxpower = 2000; //usb_get_max_power(ui);
+
+			}
+		} else { //D+ : high, D- : low
+			printk("UDC-CHG (2-1-2): %s (%d) : Unknown type Adaptor(100mA)!\n", __func__, __LINE__);
+			temp = USB_CHG_TYPE__WALLCHARGER;
+			maxpower = 100;
+
+		}
+	} else { //D+ : low,
+		if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 10)) { //D+ : low, D- : high
+			printk("UDC-CHG (2-2): %s (%d) : Unknown type Adaptor(100mA)!\n", __func__, __LINE__);
+			temp = USB_CHG_TYPE__WALLCHARGER;
+			maxpower = 100;
+
+		} else { //D+ : low, D- : low
+			ulpi_write_with_reset(ui, 0x25, 0x34); //Aplly current source on D+
+			msleep(10/*100*/);
+//			printk("UDC-CHG (2-2): %s (%d) : D+/D- = 0x%x\n", __func__, __LINE__, (readl(USB_PORTSC) & PORTSC_LS));
+
+			if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 11)) { //D+ : high
+				if ((readl(USB_PORTSC) & PORTSC_LS) & (1 << 10)) { //D+ : high, D- : high
+					printk("UDC-CHG (2-2-1-1): %s (%d) : OMTP Phone Adaptor(900mA)!\n", __func__, __LINE__);
+					temp = USB_CHG_TYPE__WALLCHARGER;
+					maxpower = 900;
+				} else { //D+ : high, D- : low
+					printk("UDC-CHG (2-2-1-2): %s (%d) : Unknown type Adaptor(100mA)!\n", __func__, __LINE__);
+					temp = USB_CHG_TYPE__WALLCHARGER;
+					maxpower = 100;
+				}
+			} else { //D+ : low
+				ulpi_write_with_reset(ui, 0x24, 0x34);
+				msleep(10);
+				if (ulpi_read_with_reset(ui, 0x34) & (1 << 4)) {
+					ulpi_write_with_reset(ui, 0x0f, 0x34);
+					writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD);
+					msleep(10);
+					writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD);
+					printk("UDC-CHG (2-2-2-1): %s (%d) : USB host Charging Downstream Port(1400mA)!\n", __func__, __LINE__);
+					//temp = USB_CHG_TYPE__WALLCHARGER;
+					temp = USB_CHG_TYPE__SDP;
+					ui->is_cdp = 1;
+					maxpower = 1400;
+
+				} else {
+					ulpi_write_with_reset(ui, 0x0f, 0x34);
+					writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD);
+					msleep(10);
+					writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD);
+					printk("UDC-CHG (2-2-2): %s (%d) : USB host Adaptor(500mA)!\n", __func__, __LINE__);
+					temp = USB_CHG_TYPE__SDP;
+					maxpower = 500;//usb_get_max_power(ui);
+				}
+			}
+			ulpi_write_with_reset(ui,0x0F, 0x34); // clean up current source on D+
+		}
+	}
+
+	atomic_set(&otg->chg_type, temp);
+
+	ui->chg_current = maxpower;
+
+	return maxpower;
+#endif //CONFIG_USB_MULTIPLE_CHARGER_DETECT
+}
+
 static int usb_ep_get_stall(struct msm_endpoint *ept)
 {
 	unsigned int n;
@@ -1582,9 +1733,9 @@
 
 				if (!atomic_read(&ui->softconnect))
 					break;
-
+#ifndef CONFIG_USB_MULTIPLE_CHARGER_DETECT
 				msm72k_pullup_internal(&ui->gadget, 1);
-
+#endif
 				if (!ui->gadget.is_a_peripheral)
 					schedule_delayed_work(
 							&ui->chg_det,
@@ -1733,8 +1884,9 @@
 
 				if (!atomic_read(&ui->softconnect))
 					break;
+#ifndef CONFIG_USB_MULTIPLE_CHARGER_DETECT
 				msm72k_pullup_internal(&ui->gadget, 1);
-
+#endif
 				if (!ui->gadget.is_a_peripheral)
 					schedule_delayed_work(
 							&ui->chg_det,
diff --git a/drivers/usb/otg/msm72k_otg.c b/drivers/usb/otg/msm72k_otg.c
index c95905e..9a10ff6 100644
--- a/drivers/usb/otg/msm72k_otg.c
+++ b/drivers/usb/otg/msm72k_otg.c
@@ -1424,6 +1424,22 @@
 	return -1;
 }
 
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+static int usb_ulpi_write_with_reset(struct usb_phy *xceiv, u32 val, u32 reg)
+{
+	struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy);
+
+	return ulpi_write_with_reset(dev, val, reg);
+}
+
+static int usb_ulpi_read_with_reset(struct usb_phy *xceiv, u32 reg)
+{
+	struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy);
+
+	return ulpi_read_with_reset(dev, reg);
+}
+#endif
+
 /* some of the older targets does not turn off the PLL
  * if onclock bit is set and clocksuspendM bit is on,
  * hence clear them too and initiate the suspend mode
@@ -2598,6 +2614,10 @@
 struct usb_phy_io_ops msm_otg_io_ops = {
 	.read = usb_ulpi_read,
 	.write = usb_ulpi_write,
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+	.read_with_reset = usb_ulpi_read_with_reset,
+	.write_with_reset = usb_ulpi_write_with_reset,
+#endif
 };
 
 static int __init msm_otg_probe(struct platform_device *pdev)
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index a584d24..c0b9160 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -85,6 +85,10 @@
 struct usb_phy_io_ops {
 	int (*read)(struct usb_phy *x, u32 reg);
 	int (*write)(struct usb_phy *x, u32 val, u32 reg);
+#ifdef CONFIG_USB_MULTIPLE_CHARGER_DETECT
+	int (*read_with_reset)(struct usb_phy *x, u32 reg);
+	int (*write_with_reset)(struct usb_phy *x, u32 val, u32 reg);
+#endif
 };
 
 struct usb_otg {