[PATCH 03/25] USB OTG Langwell: Enable PHY Low Power Mode

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

 



From: Hao Wu <hao.wu@xxxxxxxxx>

This patch enables PHY low power mode in OTG state machine.
Please notice that there is a limitation, after drive VBus down,
2ms delay is required from SCU FW to sync up OTGSC register with
USBCFG register.

Signed-off-by: Hao Wu <hao.wu@xxxxxxxxx>
Signed-off-by: Alan Cox <alan@xxxxxxxxxxxxxxx>
---

 drivers/usb/otg/langwell_otg.c   |  154 +++++++++++++++++++++++++++++++++++++-
 include/linux/usb/langwell_otg.h |    8 ++
 2 files changed, 157 insertions(+), 5 deletions(-)


diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c
index ada4cda..2cbfec5 100644
--- a/drivers/usb/otg/langwell_otg.c
+++ b/drivers/usb/otg/langwell_otg.c
@@ -295,10 +295,64 @@ static void langwell_otg_loc_sof(int on)
 	dev_dbg(&lnw->pdev->dev, "%s <---\n", __func__);
 }
 
+static int langwell_otg_check_otgsc(void)
+{
+	struct langwell_otg		*lnw = the_transceiver;
+	u32				otgsc, usbcfg;
+
+	dev_dbg(&lnw->pdev->dev, "check sync OTGSC and USBCFG registers\n");
+
+	otgsc = readl(lnw->regs + CI_OTGSC);
+	usbcfg = readl(lnw->usbcfg);
+
+	dev_dbg(&lnw->pdev->dev, "OTGSC = %08x, USBCFG = %08x\n",
+					otgsc, usbcfg);
+	dev_dbg(&lnw->pdev->dev, "OTGSC_AVV = %d\n", !!(otgsc & OTGSC_AVV));
+	dev_dbg(&lnw->pdev->dev, "USBCFG.VBUSVAL = %d\n",
+					!!(usbcfg & USBCFG_VBUSVAL));
+	dev_dbg(&lnw->pdev->dev, "OTGSC_ASV = %d\n", !!(otgsc & OTGSC_ASV));
+	dev_dbg(&lnw->pdev->dev, "USBCFG.AVALID = %d\n",
+					!!(usbcfg & USBCFG_AVALID));
+	dev_dbg(&lnw->pdev->dev, "OTGSC_BSV = %d\n", !!(otgsc & OTGSC_BSV));
+	dev_dbg(&lnw->pdev->dev, "USBCFG.BVALID = %d\n",
+					!!(usbcfg & USBCFG_BVALID));
+	dev_dbg(&lnw->pdev->dev, "OTGSC_BSE = %d\n", !!(otgsc & OTGSC_BSE));
+	dev_dbg(&lnw->pdev->dev, "USBCFG.SESEND = %d\n",
+					!!(usbcfg & USBCFG_SESEND));
+
+	/* Check USBCFG VBusValid/AValid/BValid/SessEnd */
+	if (!!(otgsc & OTGSC_AVV) ^ !!(usbcfg & USBCFG_VBUSVAL)) {
+		dev_dbg(&lnw->pdev->dev, "OTGSC.AVV != USBCFG.VBUSVAL\n");
+		goto err;
+	}
+	if (!!(otgsc & OTGSC_ASV) ^ !!(usbcfg & USBCFG_AVALID)) {
+		dev_dbg(&lnw->pdev->dev, "OTGSC.ASV != USBCFG.AVALID\n");
+		goto err;
+	}
+	if (!!(otgsc & OTGSC_BSV) ^ !!(usbcfg & USBCFG_BVALID)) {
+		dev_dbg(&lnw->pdev->dev, "OTGSC.BSV != USBCFG.BVALID\n");
+		goto err;
+	}
+	if (!!(otgsc & OTGSC_BSE) ^ !!(usbcfg & USBCFG_SESEND)) {
+		dev_dbg(&lnw->pdev->dev, "OTGSC.BSE != USBCFG.SESSEN\n");
+		goto err;
+	}
+
+	dev_dbg(&lnw->pdev->dev, "OTGSC and USBCFG are synced\n");
+
+	return 0;
+
+err:
+	dev_warn(&lnw->pdev->dev, "OTGSC isn't equal to USBCFG\n");
+	return -EPIPE;
+}
+
+
 static void langwell_otg_phy_low_power(int on)
 {
 	struct langwell_otg	*lnw = the_transceiver;
 	u8			val, phcd;
+	int			retval;
 
 	dev_dbg(&lnw->pdev->dev, "%s ---> %s mode\n",
 			__func__, on ? "Low power" : "Normal");
@@ -307,14 +361,34 @@ static void langwell_otg_phy_low_power(int on)
 
 	val = readb(lnw->regs + CI_HOSTPC1 + 2);
 
-	if (on)
+	if (on) {
+		/* Due to hardware issue, after set PHCD, sync will failed
+		 * between USBCFG and OTGSC, so before set PHCD, check if
+		 * sync is in process now. If the answer is "yes", then do
+		 * not touch PHCD bit */
+		retval = langwell_otg_check_otgsc();
+		if (retval) {
+			dev_dbg(&lnw->pdev->dev, "Skip PHCD programming..\n");
+			return ;
+		}
 		writeb(val | phcd, lnw->regs + CI_HOSTPC1 + 2);
-	else
+	} else
 		writeb(val & ~phcd, lnw->regs + CI_HOSTPC1 + 2);
 
 	dev_dbg(&lnw->pdev->dev, "%s <--- done\n", __func__);
 }
 
+/* After drv vbus, add 2 ms delay to set PHCD */
+static void langwell_otg_phy_low_power_wait(int on)
+{
+	struct langwell_otg	*lnw = the_transceiver;
+
+	dev_dbg(&lnw->pdev->dev, "add 2ms delay before programing PHCD\n");
+
+	mdelay(2);
+	langwell_otg_phy_low_power(on);
+}
+
 /* Enable/Disable OTG interrupt */
 static void langwell_otg_intr(int on)
 {
@@ -598,6 +672,8 @@ static void init_hsm(void)
 	lnw->hsm.b_bus_req = 0;
 	/* no system error */
 	lnw->hsm.a_clr_err = 0;
+
+	langwell_otg_phy_low_power_wait(1);
 }
 
 static void update_hsm(void)
@@ -736,6 +812,7 @@ static void langwell_otg_work(struct work_struct *work)
 			langwell_otg_chrg_vbus(0);
 			langwell_otg_drv_vbus(0);
 			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.b_srp_res_tmout) {
@@ -763,10 +840,16 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"LS is not SE0, try again later\n");
 			} else {
+				/* clear the PHCD before start srp */
+				langwell_otg_phy_low_power(0);
+
 				/* Start SRP */
 				langwell_otg_add_timer(b_srp_res_tmr);
 				langwell_otg_start_srp(&lnw->otg);
 				langwell_otg_del_timer(b_srp_res_tmr);
+
+				/* reset PHY low power mode here */
+				langwell_otg_phy_low_power_wait(1);
 			}
 		}
 		break;
@@ -776,6 +859,8 @@ static void langwell_otg_work(struct work_struct *work)
 			lnw->hsm.a_srp_det = 0;
 			langwell_otg_drv_vbus(0);
 			langwell_otg_chrg_vbus(0);
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.b_sess_vld) {
@@ -795,7 +880,6 @@ static void langwell_otg_work(struct work_struct *work)
 
 			langwell_otg_drv_vbus(0);
 			langwell_otg_chrg_vbus(0);
-			set_host_mode();
 
 			if (lnw->client_ops) {
 				lnw->client_ops->suspend(lnw->pdev,
@@ -804,6 +888,8 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"client driver has been removed.\n");
 
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.b_sess_vld) {
@@ -852,7 +938,6 @@ static void langwell_otg_work(struct work_struct *work)
 
 			langwell_otg_drv_vbus(0);
 			langwell_otg_chrg_vbus(0);
-			set_host_mode();
 
 			langwell_otg_HAAR(0);
 			if (lnw->host_ops)
@@ -860,6 +945,8 @@ static void langwell_otg_work(struct work_struct *work)
 			else
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.b_sess_vld) {
@@ -874,6 +961,9 @@ static void langwell_otg_work(struct work_struct *work)
 			else
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
+
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 		} else if (lnw->hsm.a_conn) {
 			langwell_otg_del_timer(b_ase0_brst_tmr);
@@ -912,12 +1002,13 @@ static void langwell_otg_work(struct work_struct *work)
 
 			langwell_otg_drv_vbus(0);
 			langwell_otg_chrg_vbus(0);
-			set_host_mode();
 			if (lnw->host_ops)
 				lnw->host_ops->remove(lnw->pdev);
 			else
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.b_sess_vld) {
@@ -929,6 +1020,9 @@ static void langwell_otg_work(struct work_struct *work)
 			else
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
+
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 		} else if ((!lnw->hsm.b_bus_req) ||
 				(!lnw->hsm.a_conn)) {
@@ -960,10 +1054,13 @@ static void langwell_otg_work(struct work_struct *work)
 			lnw->hsm.vbus_srp_up = 0;
 			langwell_otg_drv_vbus(0);
 			langwell_otg_chrg_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.a_bus_drop &&
 			(lnw->hsm.a_srp_det || lnw->hsm.a_bus_req)) {
+			langwell_otg_phy_low_power(0);
 			langwell_otg_drv_vbus(1);
 			lnw->hsm.a_srp_det = 1;
 			lnw->hsm.a_wait_vrise_tmout = 0;
@@ -975,12 +1072,16 @@ static void langwell_otg_work(struct work_struct *work)
 		} else if (!lnw->hsm.a_sess_vld &&
 				lnw->hsm.vbus_srp_up) {
 			msleep(10);
+			langwell_otg_phy_low_power(0);
 			langwell_otg_drv_vbus(1);
 			lnw->hsm.a_srp_det = 1;
 			lnw->hsm.a_wait_vrise_tmout = 0;
 			langwell_otg_add_timer(a_wait_vrise_tmr);
 			lnw->otg.state = OTG_STATE_A_WAIT_VRISE;
 			queue_work(lnw->qwork, &lnw->work);
+		} else if (!lnw->hsm.a_sess_vld &&
+				!lnw->hsm.vbus_srp_up) {
+			langwell_otg_phy_low_power(1);
 		}
 		break;
 	case OTG_STATE_A_WAIT_VRISE:
@@ -989,6 +1090,9 @@ static void langwell_otg_work(struct work_struct *work)
 			lnw->hsm.b_bus_req = 0;
 			lnw->otg.default_a = 0;
 			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
+
 			lnw->otg.state = OTG_STATE_B_IDLE;
 		} else if (lnw->hsm.a_vbus_vld) {
 			langwell_otg_del_timer(a_wait_vrise_tmr);
@@ -1021,6 +1125,7 @@ static void langwell_otg_work(struct work_struct *work)
 				lnw->otg.state = OTG_STATE_A_WAIT_BCON;
 			} else {
 				langwell_otg_drv_vbus(0);
+				langwell_otg_phy_low_power_wait(1);
 				lnw->otg.state = OTG_STATE_A_VBUS_ERR;
 			}
 		}
@@ -1037,6 +1142,8 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.a_vbus_vld) {
@@ -1048,6 +1155,7 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_A_VBUS_ERR;
 		} else if (lnw->hsm.a_bus_drop ||
 				(lnw->hsm.a_wait_bcon_tmout &&
@@ -1117,6 +1225,8 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.a_bus_drop ||
@@ -1136,6 +1246,7 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_A_VBUS_ERR;
 		} else if (lnw->otg.host->b_hnp_enable
 				&& !lnw->hsm.a_bus_req) {
@@ -1179,6 +1290,8 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.a_bus_req ||
@@ -1237,6 +1350,7 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"host driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_A_VBUS_ERR;
 		}
 		break;
@@ -1252,6 +1366,8 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"client driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (!lnw->hsm.a_vbus_vld) {
@@ -1263,6 +1379,7 @@ static void langwell_otg_work(struct work_struct *work)
 				dev_dbg(&lnw->pdev->dev,
 					"client driver has been removed.\n");
 			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
 			lnw->otg.state = OTG_STATE_A_VBUS_ERR;
 		} else if (lnw->hsm.a_bus_drop) {
 			langwell_otg_del_timer(b_bus_suspend_tmr);
@@ -1318,6 +1435,8 @@ static void langwell_otg_work(struct work_struct *work)
 			lnw->otg.default_a = 0;
 			lnw->hsm.a_clr_err = 0;
 			lnw->hsm.a_srp_det = 0;
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.a_clr_err) {
@@ -1327,11 +1446,17 @@ static void langwell_otg_work(struct work_struct *work)
 			init_hsm();
 			if (lnw->otg.state == OTG_STATE_A_IDLE)
 				queue_work(lnw->qwork, &lnw->work);
+		} else {
+			/* FW will clear PHCD bit when any VBus
+			 * event detected. Reset PHCD to 1 again */
+			langwell_otg_phy_low_power(1);
 		}
 		break;
 	case OTG_STATE_A_WAIT_VFALL:
 		if (lnw->hsm.id) {
 			lnw->otg.default_a = 0;
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_B_IDLE;
 			queue_work(lnw->qwork, &lnw->work);
 		} else if (lnw->hsm.a_bus_req) {
@@ -1343,6 +1468,7 @@ static void langwell_otg_work(struct work_struct *work)
 			lnw->hsm.a_srp_det = 0;
 			langwell_otg_drv_vbus(0);
 			set_host_mode();
+			langwell_otg_phy_low_power(1);
 			lnw->otg.state = OTG_STATE_A_IDLE;
 		}
 		break;
@@ -1735,6 +1861,20 @@ static int langwell_otg_probe(struct pci_dev *pdev,
 	}
 	langwell->regs = base;
 
+	if (!request_mem_region(USBCFG_ADDR, USBCFG_LEN, driver_name)) {
+		retval = -EBUSY;
+		goto err;
+	}
+	langwell->cfg_region = 1;
+
+	/* For the SCCB.USBCFG register */
+	base = ioremap_nocache(USBCFG_ADDR, USBCFG_LEN);
+	if (base == NULL) {
+		retval = -EFAULT;
+		goto err;
+	}
+	langwell->usbcfg = base;
+
 	if (!pdev->irq) {
 		dev_dbg(&pdev->dev, "No IRQ.\n");
 		retval = -ENODEV;
@@ -1833,6 +1973,10 @@ static void langwell_otg_remove(struct pci_dev *pdev)
 
 	if (pdev->irq)
 		free_irq(pdev->irq, langwell);
+	if (langwell->usbcfg)
+		iounmap(langwell->usbcfg);
+	if (langwell->cfg_region)
+		release_mem_region(USBCFG_ADDR, USBCFG_LEN);
 	if (langwell->regs)
 		iounmap(langwell->regs);
 	if (langwell->region)
diff --git a/include/linux/usb/langwell_otg.h b/include/linux/usb/langwell_otg.h
index 7619871..64ff588 100644
--- a/include/linux/usb/langwell_otg.h
+++ b/include/linux/usb/langwell_otg.h
@@ -84,6 +84,12 @@ extern void langwell_otg_nsf_msg(unsigned long message);
 #	define USBMODE_IDLE		0
 #	define USBMODE_DEVICE		0x2
 #	define USBMODE_HOST		0x3
+#define USBCFG_ADDR			0xff10801c
+#define USBCFG_LEN			4
+#	define USBCFG_VBUSVAL		BIT(14)
+#	define USBCFG_AVALID		BIT(13)
+#	define USBCFG_BVALID		BIT(12)
+#	define USBCFG_SESEND		BIT(11)
 
 #define INTR_DUMMY_MASK (USBSTS_SLI | USBSTS_URI | USBSTS_PCI)
 
@@ -153,7 +159,9 @@ struct langwell_otg {
 	struct otg_transceiver	otg;
 	struct otg_hsm		hsm;
 	void __iomem		*regs;
+	void __iomem		*usbcfg;	/* SCCB USB config Reg */
 	unsigned		region;
+	unsigned		cfg_region;
 	struct pci_driver	*host_ops;
 	struct pci_driver	*client_ops;
 	struct pci_dev		*pdev;

--
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