usb: dwc3-msm: Add support for charger detection

DWC3 OTG driver can detect charger detection using DWC3 core or
external charger detection circuitry. Add support for external
charger detector in DWC3 OTG driver for MSM platform. This module
detects DCD and SDP/CDP/DCP type of chargers.

Change-Id: I5d1aec183fbcdc9b03aac74d626a4c39c56ed3b7
Signed-off-by: Manu Gautam <mgautam@codeaurora.org>
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index 2e0450b..582dac5 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -30,6 +30,7 @@
 
 #include <mach/rpm-regulator.h>
 
+#include "dwc3_otg.h"
 #include "core.h"
 #include "gadget.h"
 
@@ -87,6 +88,16 @@
 #define DBM_TRB_DATA_SRC	0x40000000
 #define DBM_TRB_DMA		0x20000000
 #define DBM_TRB_EP_NUM(ep)	(ep<<24)
+/**
+ *  USB QSCRATCH Hardware registers
+ *
+ */
+#define QSCRATCH_REG_OFFSET	(0x000F8800)
+#define CHARGING_DET_CTRL_REG	(QSCRATCH_REG_OFFSET + 0x18)
+#define CHARGING_DET_OUTPUT_REG	(QSCRATCH_REG_OFFSET + 0x1C)
+#define ALT_INTERRUPT_EN_REG	(QSCRATCH_REG_OFFSET + 0x20)
+#define HS_PHY_IRQ_STAT_REG	(QSCRATCH_REG_OFFSET + 0x24)
+
 
 struct dwc3_msm_req_complete {
 	struct list_head list_item;
@@ -111,6 +122,11 @@
 	struct regulator	*ssusb_vddcx;
 	enum usb_vdd_type	ss_vdd_type;
 	enum usb_vdd_type	hs_vdd_type;
+	struct dwc3_charger	charger;
+	struct usb_phy		*otg_xceiv;
+	struct delayed_work	chg_work;
+	enum usb_chg_state	chg_state;
+	u8			dcd_retries;
 };
 
 #define USB_HSPHY_3P3_VOL_MIN		3050000 /* uV */
@@ -222,6 +238,34 @@
 }
 
 /**
+ * Write register and read back masked value to confirm it is written
+ *
+ * @base - DWC3 base virtual address.
+ * @offset - register offset.
+ * @mask - register bitmask specifying what should be updated
+ * @val - value to write.
+ *
+ */
+static inline void dwc3_msm_write_readback(void *base, u32 offset,
+					    const u32 mask, u32 val)
+{
+	u32 write_val, tmp = ioread32(base + offset);
+
+	tmp &= ~mask;		/* retain other bits */
+	write_val = tmp | val;
+
+	iowrite32(write_val, base + offset);
+
+	/* Read back to see if val was written */
+	tmp = ioread32(base + offset);
+	tmp &= mask;		/* clear other bits */
+
+	if (tmp != val)
+		dev_err(context->dev, "%s: write: %x to QSCRATCH: %x FAILED\n",
+						__func__, val, offset);
+}
+
+/**
  * Return DBM EP number which is not already configured.
  *
  */
@@ -997,6 +1041,184 @@
 	return rc < 0 ? rc : 0;
 }
 
+static void dwc3_chg_enable_secondary_det(struct dwc3_msm *mdwc)
+{
+	u32 chg_ctrl;
+
+	/* Turn off VDP_SRC */
+	dwc3_msm_write_reg(mdwc->base, CHARGING_DET_CTRL_REG, 0x0);
+	msleep(20);
+
+	/* Before proceeding make sure VDP_SRC is OFF */
+	chg_ctrl = dwc3_msm_read_reg(mdwc->base, CHARGING_DET_CTRL_REG);
+	if (chg_ctrl & 0x3F)
+		dev_err(mdwc->dev, "%s Unable to reset chg_det block: %x\n",
+						 __func__, chg_ctrl);
+	/*
+	 * Configure DM as current source, DP as current sink
+	 * and enable battery charging comparators.
+	 */
+	dwc3_msm_write_readback(mdwc->base, CHARGING_DET_CTRL_REG, 0x3F, 0x34);
+}
+
+static bool dwc3_chg_det_check_output(struct dwc3_msm *mdwc)
+{
+	u32 chg_det;
+	bool ret = false;
+
+	chg_det = dwc3_msm_read_reg(mdwc->base, CHARGING_DET_OUTPUT_REG);
+	ret = chg_det & 1;
+
+	return ret;
+}
+
+static void dwc3_chg_enable_primary_det(struct dwc3_msm *mdwc)
+{
+	/*
+	 * Configure DP as current source, DM as current sink
+	 * and enable battery charging comparators.
+	 */
+	dwc3_msm_write_readback(mdwc->base, CHARGING_DET_CTRL_REG, 0x3F, 0x30);
+}
+
+static inline bool dwc3_chg_check_dcd(struct dwc3_msm *mdwc)
+{
+	u32 chg_state;
+	bool ret = false;
+
+	chg_state = dwc3_msm_read_reg(mdwc->base, CHARGING_DET_OUTPUT_REG);
+	ret = chg_state & 2;
+
+	return ret;
+}
+
+static inline void dwc3_chg_disable_dcd(struct dwc3_msm *mdwc)
+{
+	dwc3_msm_write_readback(mdwc->base, CHARGING_DET_CTRL_REG, 0x3F, 0x0);
+}
+
+static inline void dwc3_chg_enable_dcd(struct dwc3_msm *mdwc)
+{
+	/* Data contact detection enable, DCDENB */
+	dwc3_msm_write_readback(mdwc->base, CHARGING_DET_CTRL_REG, 0x3F, 0x2);
+}
+
+static void dwc3_chg_block_reset(struct dwc3_msm *mdwc)
+{
+	u32 chg_ctrl;
+
+	/* Clear charger detecting control bits */
+	dwc3_msm_write_reg(mdwc->base, CHARGING_DET_CTRL_REG, 0x0);
+
+	/* Clear alt interrupt latch and enable bits */
+	dwc3_msm_write_reg(mdwc->base, HS_PHY_IRQ_STAT_REG, 0xFFF);
+	dwc3_msm_write_reg(mdwc->base, ALT_INTERRUPT_EN_REG, 0x0);
+
+	udelay(100);
+
+	/* Before proceeding make sure charger block is RESET */
+	chg_ctrl = dwc3_msm_read_reg(mdwc->base, CHARGING_DET_CTRL_REG);
+	if (chg_ctrl & 0x3F)
+		dev_err(mdwc->dev, "%s Unable to reset chg_det block: %x\n",
+						 __func__, chg_ctrl);
+}
+
+static const char *chg_to_string(enum dwc3_chg_type chg_type)
+{
+	switch (chg_type) {
+	case USB_SDP_CHARGER:		return "USB_SDP_CHARGER";
+	case USB_DCP_CHARGER:		return "USB_DCP_CHARGER";
+	case USB_CDP_CHARGER:		return "USB_CDP_CHARGER";
+	default:			return "INVALID_CHARGER";
+	}
+}
+
+#define DWC3_CHG_DCD_POLL_TIME		(100 * HZ/1000) /* 100 msec */
+#define DWC3_CHG_DCD_MAX_RETRIES	6 /* Tdcd_tmout = 6 * 100 msec */
+#define DWC3_CHG_PRIMARY_DET_TIME	(50 * HZ/1000) /* TVDPSRC_ON */
+#define DWC3_CHG_SECONDARY_DET_TIME	(50 * HZ/1000) /* TVDMSRC_ON */
+
+static void dwc3_chg_detect_work(struct work_struct *w)
+{
+	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, chg_work.work);
+	bool is_dcd = false, tmout, vout;
+	unsigned long delay;
+
+	dev_dbg(mdwc->dev, "chg detection work\n");
+	switch (mdwc->chg_state) {
+	case USB_CHG_STATE_UNDEFINED:
+		dwc3_chg_block_reset(mdwc);
+		dwc3_chg_enable_dcd(mdwc);
+		mdwc->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
+		mdwc->dcd_retries = 0;
+		delay = DWC3_CHG_DCD_POLL_TIME;
+		break;
+	case USB_CHG_STATE_WAIT_FOR_DCD:
+		is_dcd = dwc3_chg_check_dcd(mdwc);
+		tmout = ++mdwc->dcd_retries == DWC3_CHG_DCD_MAX_RETRIES;
+		if (is_dcd || tmout) {
+			dwc3_chg_disable_dcd(mdwc);
+			dwc3_chg_enable_primary_det(mdwc);
+			delay = DWC3_CHG_PRIMARY_DET_TIME;
+			mdwc->chg_state = USB_CHG_STATE_DCD_DONE;
+		} else {
+			delay = DWC3_CHG_DCD_POLL_TIME;
+		}
+		break;
+	case USB_CHG_STATE_DCD_DONE:
+		vout = dwc3_chg_det_check_output(mdwc);
+		if (vout) {
+			dwc3_chg_enable_secondary_det(mdwc);
+			delay = DWC3_CHG_SECONDARY_DET_TIME;
+			mdwc->chg_state = USB_CHG_STATE_PRIMARY_DONE;
+		} else {
+			mdwc->charger.chg_type = USB_SDP_CHARGER;
+			mdwc->chg_state = USB_CHG_STATE_DETECTED;
+			delay = 0;
+		}
+		break;
+	case USB_CHG_STATE_PRIMARY_DONE:
+		vout = dwc3_chg_det_check_output(mdwc);
+		if (vout)
+			mdwc->charger.chg_type = USB_DCP_CHARGER;
+		else
+			mdwc->charger.chg_type = USB_CDP_CHARGER;
+		mdwc->chg_state = USB_CHG_STATE_SECONDARY_DONE;
+		/* fall through */
+	case USB_CHG_STATE_SECONDARY_DONE:
+		mdwc->chg_state = USB_CHG_STATE_DETECTED;
+		/* fall through */
+	case USB_CHG_STATE_DETECTED:
+		dwc3_chg_block_reset(mdwc);
+		dev_dbg(mdwc->dev, "chg_type = %s\n",
+			chg_to_string(mdwc->charger.chg_type));
+		mdwc->charger.notify_detection_complete(mdwc->otg_xceiv->otg,
+								&mdwc->charger);
+		return;
+	default:
+		return;
+	}
+
+	queue_delayed_work(system_nrt_wq, &mdwc->chg_work, delay);
+}
+
+static void dwc3_start_chg_det(struct dwc3_charger *charger, bool start)
+{
+	struct dwc3_msm *mdwc = context;
+
+	if (start == false) {
+		cancel_delayed_work_sync(&mdwc->chg_work);
+		mdwc->chg_state = USB_CHG_STATE_UNDEFINED;
+		charger->chg_type = DWC3_INVALID_CHARGER;
+		return;
+	}
+
+	mdwc->chg_state = USB_CHG_STATE_UNDEFINED;
+	charger->chg_type = DWC3_INVALID_CHARGER;
+	queue_delayed_work(system_nrt_wq, &mdwc->chg_work, 0);
+}
+
+
 static int __devinit dwc3_msm_probe(struct platform_device *pdev)
 {
 	struct device_node *node = pdev->dev.of_node;
@@ -1016,6 +1238,7 @@
 	msm->dev = &pdev->dev;
 
 	INIT_LIST_HEAD(&msm->req_complete_list);
+	INIT_DELAYED_WORK(&msm->chg_work, dwc3_chg_detect_work);
 
 	/* SS PHY */
 	msm->ss_vdd_type = VDDCX_CORNER;
@@ -1155,8 +1378,24 @@
 	/* Reset the DBM */
 	dwc3_msm_dbm_soft_reset();
 
+	msm->otg_xceiv = usb_get_transceiver();
+	if (msm->otg_xceiv) {
+		msm->charger.start_detection = dwc3_start_chg_det;
+		ret = dwc3_set_charger(msm->otg_xceiv->otg, &msm->charger);
+		if (ret || !msm->charger.notify_detection_complete) {
+			dev_err(&pdev->dev, "failed to register charger: %d\n",
+									ret);
+			goto put_xcvr;
+		}
+	} else {
+		dev_err(&pdev->dev, "%s: No OTG transceiver found\n", __func__);
+	}
+
 	return 0;
 
+put_xcvr:
+	usb_put_transceiver(msm->otg_xceiv);
+	platform_device_del(dwc3);
 put_pdev:
 	platform_device_put(dwc3);
 disable_hs_ldo:
@@ -1183,6 +1422,10 @@
 {
 	struct dwc3_msm	*msm = platform_get_drvdata(pdev);
 
+	if (msm->otg_xceiv) {
+		dwc3_start_chg_det(&msm->charger, false);
+		usb_put_transceiver(msm->otg_xceiv);
+	}
 	platform_device_unregister(msm->dwc3);
 
 	dwc3_hsusb_ldo_enable(0);