[PATCH 12/21] usb: penwell_otg: add USB charger detection

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Hao Wu <hao.wu@xxxxxxxxx>

This patch enables penwell USB OTG Transceiver driver USB Charger Detection
support. It can detect different types of USB charger based on MSIC.
SDP (Standard Downstream Port - USB Host port charger), DCP (Dedicated
Charging Port - USB Wall charger), CDP (Charging Downstream Port - Special
USB Host port charger).

Signed-off-by: Hao Wu <hao.wu@xxxxxxxxx>
---
 drivers/usb/otg/penwell_otg.c   |  255 ++++++++++++++++++++++++++++++++++++---
 include/linux/usb/penwell_otg.h |   68 ++++++++---
 2 files changed, 291 insertions(+), 32 deletions(-)

diff --git a/drivers/usb/otg/penwell_otg.c b/drivers/usb/otg/penwell_otg.c
index 52636c7..8266ad0 100644
--- a/drivers/usb/otg/penwell_otg.c
+++ b/drivers/usb/otg/penwell_otg.c
@@ -61,6 +61,7 @@ static int penwell_otg_set_host(struct otg_transceiver *otg,
 static int penwell_otg_set_peripheral(struct otg_transceiver *otg,
 				struct usb_gadget *gadget);
 static int penwell_otg_start_srp(struct otg_transceiver *otg);
+static int penwell_otg_msic_write(u16 addr, u8 data);
 
 static DEFINE_PCI_DEVICE_TABLE(pci_ids) = {{
 	.class =        ((PCI_CLASS_SERIAL_USB << 8) | 0x20),
@@ -155,19 +156,22 @@ static int penwell_otg_set_power(struct otg_transceiver *otg,
 static void penwell_otg_phy_enable(int on)
 {
 	struct penwell_otg	*pnw = the_transceiver;
-	u16			addr;
 	u8			data;
 
 	dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
 
-	addr = MSIC_VUSB330CNT;
 	data = on ? 0x37 : 0x24;
 
-	if (intel_scu_ipc_iowrite8(addr, data)) {
-		dev_err(pnw->dev, "Fail to access register for"
-			" OTG PHY power - write reg 0x%x failed.\n", addr);
+	mutex_lock(&pnw->msic_mutex);
+
+	if (penwell_otg_msic_write(MSIC_VUSB330CNT, data)) {
+		mutex_unlock(&pnw->msic_mutex);
+		dev_err(pnw->dev, "Fail to enable PHY power\n");
 		return;
 	}
+
+	mutex_unlock(&pnw->msic_mutex);
+
 	dev_dbg(pnw->dev, "%s <---\n", __func__);
 }
 
@@ -175,25 +179,25 @@ static void penwell_otg_phy_enable(int on)
 static int penwell_otg_set_vbus(struct otg_transceiver *otg, bool enabled)
 {
 	struct penwell_otg	*pnw = the_transceiver;
-	u16			addr;
-	u8			data, mask;
+	u8			data;
+	int			retval;
 
 	dev_dbg(pnw->dev, "%s ---> %s\n", __func__, enabled ? "on" : "off");
 
-	addr = MSIC_VOTGCNT;
 	data = enabled ? VOTGEN : 0;
-	mask = VOTGEN;
 
-	if (intel_scu_ipc_update_register(addr, data, mask)) {
-		dev_err(pnw->dev, "Fail to drive power on OTG Port - "
-				"update register 0x%x failed.\n", addr);
-		return -EBUSY;
-	}
+	mutex_lock(&pnw->msic_mutex);
+
+	retval = intel_scu_ipc_update_register(MSIC_VOTGCNT, data, VOTGEN);
+
+	if (retval)
+		dev_err(pnw->dev, "Fail to set power on OTG Port\n");
+
+	mutex_unlock(&pnw->msic_mutex);
 
-	dev_dbg(pnw->dev, "VOTGCNT val = 0x%x", data);
 	dev_dbg(pnw->dev, "%s <---\n", __func__);
 
-	return 0;
+	return retval;
 }
 
 static int penwell_otg_ulpi_run(void)
@@ -416,6 +420,193 @@ static void penwell_otg_HABA(int on)
 					pnw->iotg.base + CI_OTGSC);
 }
 
+/* write 8bit msic register */
+static int penwell_otg_msic_write(u16 addr, u8 data)
+{
+	struct penwell_otg	*pnw = the_transceiver;
+	int			retval = 0;
+
+	retval = intel_scu_ipc_iowrite8(addr, data);
+	if (retval) {
+		dev_warn(pnw->dev, "Failed to write MSIC register %x\n", addr);
+		return retval;
+	}
+
+	return retval;
+}
+
+/* USB related register in MSIC can be access via SPI address and ulpi address
+ * Access the control register to switch */
+static void penwell_otg_msic_spi_access(bool enabled)
+{
+	struct penwell_otg	*pnw = the_transceiver;
+	u8			data;
+
+	dev_dbg(pnw->dev, "%s --->\n", __func__);
+
+	/* Set ULPI ACCESS MODE */
+	data = enabled ? SPIMODE : 0;
+
+	penwell_otg_msic_write(MSIC_ULPIACCESSMODE, data);
+
+	dev_dbg(pnw->dev, "%s <---\n", __func__);
+}
+
+/* USB Battery Charger detection related functions */
+/* Data contact detection is the first step for charger detection */
+static int penwell_otg_data_contact_detect(void)
+{
+	struct penwell_otg	*pnw = the_transceiver;
+	u8			data;
+	int			count = 10;
+	int			retval = 0;
+
+	dev_dbg(pnw->dev, "%s --->\n", __func__);
+
+	/* Enable SPI access */
+	penwell_otg_msic_spi_access(true);
+
+	/* Set POWER_CTRL_CLR */
+	retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, DPVSRCEN);
+	if (retval)
+		return retval;
+
+	/* Set FUNC_CTRL_SET */
+	retval = penwell_otg_msic_write(MSIC_FUNCTRLSET, OPMODE0);
+	if (retval)
+		return retval;
+
+	/* Set FUNC_CTRL_CLR */
+	retval = penwell_otg_msic_write(MSIC_FUNCTRLCLR, OPMODE1);
+	if (retval)
+		return retval;
+
+	/* Set OTG_CTRL_CLR */
+	retval = penwell_otg_msic_write(MSIC_OTGCTRLCLR,
+					DMPULLDOWN | DPPULLDOWN);
+	if (retval)
+		return retval;
+
+	/* Set POWER_CTRL_CLR */
+	retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, SWCNTRL);
+	if (retval)
+		return retval;
+
+	retval = penwell_otg_msic_write(MSIC_VS3SET, DATACONEN | SWUSBDET);
+	if (retval)
+		return retval;
+
+	dev_dbg(pnw->dev, "Start Polling for Data contact detection!\n");
+
+	while (count) {
+		retval = intel_scu_ipc_ioread8(MSIC_PWRCTRL, &data);
+		if (retval) {
+			dev_warn(pnw->dev, "Failed to read MSIC register\n");
+			return retval;
+		}
+
+		if (data & DPVSRCEN) {
+			dev_dbg(pnw->dev, "Data contact detected!\n");
+			return 0;
+		}
+		count--;
+		/* Interval is 50ms */
+		msleep(50);
+	}
+
+	dev_dbg(pnw->dev, "Data contact Timeout\n");
+
+	retval = penwell_otg_msic_write(MSIC_VS3CLR, DATACONEN | SWUSBDET);
+	if (retval)
+		return retval;
+
+	udelay(100);
+
+	retval = penwell_otg_msic_write(MSIC_VS3SET, SWUSBDET);
+	if (retval)
+		return retval;
+
+	dev_dbg(pnw->dev, "%s <---\n", __func__);
+	return 0;
+}
+
+static int penwell_otg_charger_detect(void)
+{
+	struct penwell_otg		*pnw = the_transceiver;
+
+	dev_dbg(pnw->dev, "%s --->\n", __func__);
+
+	msleep(125);
+
+	dev_dbg(pnw->dev, "%s <---\n", __func__);
+
+	return 0;
+}
+
+static int penwell_otg_charger_type_detect(void)
+{
+	struct penwell_otg		*pnw = the_transceiver;
+	enum usb_charger_type		charger;
+	u8				data;
+	int				retval;
+
+	dev_dbg(pnw->dev, "%s --->\n", __func__);
+
+	retval = penwell_otg_msic_write(MSIC_VS3CLR, DATACONEN);
+	if (retval)
+		return retval;
+
+	retval = penwell_otg_msic_write(MSIC_PWRCTRLSET, DPWKPUEN | SWCNTRL);
+	if (retval)
+		return retval;
+
+	retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, DPVSRCEN);
+	if (retval)
+		return retval;
+
+	retval = penwell_otg_msic_write(MSIC_OTGCTRLCLR,
+					DMPULLDOWN | DPPULLDOWN);
+	if (retval)
+		return retval;
+
+	msleep(55);
+
+	retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR,
+					SWCNTRL | DPWKPUEN | HWDET);
+	if (retval)
+		return retval;
+
+	usleep_range(800, 1000);
+
+	/* Enable ULPI mode */
+	penwell_otg_msic_spi_access(false);
+
+	retval = intel_scu_ipc_ioread8(MSIC_SPWRSRINT1, &data);
+	if (retval) {
+		dev_warn(pnw->dev, "Failed to read MSIC register\n");
+		return retval;
+	}
+
+	switch (data & MSIC_SPWRSRINT1_MASK) {
+	case SPWRSRINT1_SDP:
+		charger = CHRG_SDP;
+		break;
+	case SPWRSRINT1_DCP:
+		charger = CHRG_DCP;
+		break;
+	case SPWRSRINT1_CDP:
+		charger = CHRG_CDP;
+		break;
+	default:
+		charger = CHRG_UNKNOWN;
+		break;
+	}
+
+	dev_dbg(pnw->dev, "%s <---\n", __func__);
+
+	return charger;
+}
+
 void penwell_otg_nsf_msg(unsigned long indicator)
 {
 	switch (indicator) {
@@ -851,6 +1042,8 @@ static void penwell_otg_work(struct work_struct *work)
 					struct penwell_otg, work);
 	struct intel_mid_otg_xceiv	*iotg = &pnw->iotg;
 	struct otg_hsm			*hsm = &iotg->hsm;
+	enum usb_charger_type		charger_type;
+	int				retval;
 
 	dev_dbg(pnw->dev,
 		"old state = %s\n", state_string(iotg->otg.state));
@@ -913,6 +1106,34 @@ static void penwell_otg_work(struct work_struct *work)
 			hsm->b_sess_end = 0;
 			hsm->a_bus_suspend = 0;
 
+			/* Start USB Battery charger detection flow */
+
+			mutex_lock(&pnw->msic_mutex);
+			/* Enable data contact detection */
+			penwell_otg_data_contact_detect();
+			/* Enable charger detection functionality */
+			penwell_otg_charger_detect();
+			retval = penwell_otg_charger_type_detect();
+			mutex_unlock(&pnw->msic_mutex);
+			if (retval < 0) {
+				dev_warn(pnw->dev, "Charger detect failure\n");
+				break;
+			} else
+				charger_type = retval;
+
+			if (charger_type == CHRG_DCP) {
+				dev_info(pnw->dev, "DCP detected\n");
+				penwell_otg_phy_low_power(1);
+				iotg->otg.set_power(&iotg->otg, CHRG_CURR_DCP);
+				break;
+			} else if (charger_type == CHRG_CDP) {
+				dev_info(pnw->dev, "CDP detected\n");
+				iotg->otg.set_power(&iotg->otg, CHRG_CURR_CDP);
+			} else if (charger_type == CHRG_SDP)
+				dev_info(pnw->dev, "SDP detected\n");
+			else if (charger_type == CHRG_UNKNOWN)
+				dev_info(pnw->dev, "Unknown Charger Found\n");
+
 			if (iotg->start_peripheral) {
 				iotg->start_peripheral(iotg);
 				iotg->otg.state = OTG_STATE_B_PERIPHERAL;
@@ -920,7 +1141,6 @@ static void penwell_otg_work(struct work_struct *work)
 				dev_dbg(pnw->dev, "client driver not loaded\n");
 				break;
 			}
-
 		} else if ((hsm->b_bus_req || hsm->power_up ||
 				hsm->adp_change) && !hsm->b_srp_fail_tmr) {
 			if ((hsm->b_ssend_srp && hsm->b_se0_srp) ||
@@ -2056,6 +2276,7 @@ static int penwell_otg_probe(struct pci_dev *pdev,
 		goto err;
 	}
 
+	mutex_init(&pnw->msic_mutex);
 	pnw->msic = penwell_otg_check_msic();
 
 	penwell_otg_phy_enable(1);
diff --git a/include/linux/usb/penwell_otg.h b/include/linux/usb/penwell_otg.h
index 014eac9..d5cefcf 100644
--- a/include/linux/usb/penwell_otg.h
+++ b/include/linux/usb/penwell_otg.h
@@ -112,31 +112,33 @@
 #	define SUSBCHPDET		BIT(6)
 #	define SUSBDCDET		BIT(2)
 #	define MSIC_SPWRSRINT1_MASK	(BIT(6) | BIT(2))
-#	define SPWRSRINT1_CHRG_PORT	BIT(6)
-#	define SPWRSRINT1_HOST_PORT	0
-#	define SPWRSRINT1_DEDT_CHRG	(BIT(6) | BIT(2))
+#	define SPWRSRINT1_CDP		BIT(6)
+#	define SPWRSRINT1_SDP		0
+#	define SPWRSRINT1_DCP		BIT(2)
 #define MSIC_IS4SET		0x2c8	/* Intel Specific */
 #	define IS4_CHGDSERXDPINV	BIT(5)
+#define MSIC_OTGCTRL		0x39c
 #define MSIC_OTGCTRLSET		0x340
 #define MSIC_OTGCTRLCLR		0x341
-#	define DMPULLDOWNCLR		BIT(2)
-#	define DPPULLDOWNCLR		BIT(1)
+#	define DMPULLDOWN		BIT(2)
+#	define DPPULLDOWN		BIT(1)
+#define MSIC_PWRCTRL		0x3b5
 #define MSIC_PWRCTRLSET		0x342
-#	define DPWKPUENSET		BIT(4)
-#	define SWCNTRLSET		BIT(0)
 #define MSIC_PWRCTRLCLR		0x343
-#	define DPVSRCENCLR		BIT(6)
-#	define SWCNTRLCLR		BIT(0)
+#	define HWDET			BIT(7)
+#	define DPVSRCEN			BIT(6)
+#	define DPWKPUEN			BIT(4)
+#	define SWCNTRL			BIT(0)
+#define MSIC_FUNCTRL		0x398
 #define MSIC_FUNCTRLSET		0x344
-#	define OPMODESET0		BIT(3)
 #define MSIC_FUNCTRLCLR		0x345
-#	define OPMODECLR1		BIT(4)
+#	define OPMODE1			BIT(4)
+#	define OPMODE0			BIT(3)
+#define MSIC_VS3		0x3b9
 #define MSIC_VS3SET		0x346	/* Vendor Specific */
-#	define SWUSBDETSET		BIT(4)
-#	define DATACONENSET		BIT(3)
 #define MSIC_VS3CLR		0x347
-#	define SWUSBDETCLR		BIT(4)
-#	define DATACONENCLR		BIT(3)
+#	define SWUSBDET			BIT(4)
+#	define DATACONEN		BIT(3)
 #define MSIC_ULPIACCESSMODE	0x348
 #	define SPIMODE			BIT(0)
 
@@ -210,6 +212,10 @@
 
 #define FS_ADPI_MASK	(ADPIS_ADPRAMPI | ADPIS_SNSMISSI | ADPIS_PRBTRGI)
 
+/* define Data connect checking timeout and polling interval */
+#define DATACON_TIMEOUT		1000
+#define DATACON_INTERVAL	50
+
 enum penwell_otg_timer_type {
 	TA_WAIT_VRISE_TMR,
 	TA_WAIT_BCON_TMR,
@@ -242,11 +248,42 @@ enum msic_vendor {
 	MSIC_VD_UNKNOWN
 };
 
+/* charger defined in BC 1.1 */
+enum usb_charger_type {
+	CHRG_UNKNOWN,
+	CHRG_SDP,	/* Standard Downstream Port */
+	CHRG_CDP,	/* Charging Downstream Port */
+	CHRG_DCP,	/* Dedicated Charging Port */
+	CHRG_ACA	/* Accessory Charger Adapter */
+};
+
 struct adp_status {
 	struct completion	adp_comp;
 	u8			t_adp_rise;
 };
 
+/* OTG Battery Charging capability is used in charger capability detection */
+struct otg_bc_cap {
+	enum usb_charger_type	chrg_type;
+	unsigned int		mA;
+#define CHRG_CURR_UNKNOWN	0
+#define CHRG_CURR_DISCONN	0
+#define CHRG_CURR_SDP_SUSP	2
+#define CHRG_CURR_SDP_LOW	100
+#define CHRG_CURR_SDP_HIGH	500
+#define CHRG_CURR_CDP		500
+#define CHRG_CURR_CDP_HS	950
+#define CHRG_CURR_DCP	1500
+#define CHRG_CURR_ACA	1500
+};
+
+/* define event ids to notify battery driver */
+#define USBCHRG_EVENT_CONNECT	1
+#define USBCHRG_EVENT_DISCONN	2
+#define USBCHRG_EVENT_SUSPEND	3
+#define USBCHRG_EVENT_RESUME	4
+#define USBCHRG_EVENT_UPDATE	5
+
 struct penwell_otg {
 	struct intel_mid_otg_xceiv	iotg;
 	struct device			*dev;
@@ -261,6 +298,7 @@ struct penwell_otg {
 	struct timer_list		hsm_timer;
 	struct timer_list		hnp_poll_timer;
 
+	struct mutex			msic_mutex;
 	enum msic_vendor		msic;
 
 	struct notifier_block		iotg_notifier;
-- 
1.6.0.6

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux