[PATCH v2 25/25] usb: penwell_otg: add HNP support

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

 



This patch only enables basic HNP function on penwell back-to-back
case. This driver is still in development and not enough OTG Spec
compliance test was done due to no OPT currently, but more work on
it in next step.

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

diff --git a/drivers/usb/otg/penwell_otg.c b/drivers/usb/otg/penwell_otg.c
index 9999d77..45ac59a 100644
--- a/drivers/usb/otg/penwell_otg.c
+++ b/drivers/usb/otg/penwell_otg.c
@@ -1012,6 +1012,9 @@ static void penwell_otg_add_timer(enum penwell_otg_timer_type timers)
 	unsigned long			j = jiffies;
 	unsigned long			data, time;
 
+	if (timer_pending(&pnw->hsm_timer))
+		return ;
+
 	switch (timers) {
 	case TA_WAIT_VRISE_TMR:
 		iotg->hsm.a_wait_vrise_tmout = 0;
@@ -1070,8 +1073,6 @@ static void penwell_otg_add_timer(enum penwell_otg_timer_type timers)
 		return;
 	}
 
-	init_timer(&pnw->hsm_timer);
-
 	pnw->hsm_timer.data = data;
 	pnw->hsm_timer.function = penwell_otg_timer_fn;
 	pnw->hsm_timer.expires = j + time * HZ / 1000; /* milliseconds */
@@ -1206,6 +1207,36 @@ static void update_hsm(void)
 	iotg->hsm.a_vbus_vld = !!(val32 & OTGSC_AVV);
 }
 
+static irqreturn_t otg_dummy_irq(int irq, void *_dev)
+{
+	struct penwell_otg	*pnw = the_transceiver;
+	void __iomem		*reg_base = _dev;
+	u32			val;
+	u32			int_mask = 0;
+
+	val = readl(reg_base + CI_USBMODE);
+	if ((val & USBMODE_CM) != USBMODE_DEVICE)
+		return IRQ_NONE;
+
+	val = readl(reg_base + CI_USBSTS);
+	int_mask = val & INTR_DUMMY_MASK;
+
+	if (int_mask == 0)
+		return IRQ_NONE;
+
+	/* clear hsm.b_conn here since host driver can't detect it
+	*  otg_dummy_irq called means B-disconnect happened.
+	*/
+	if (pnw->iotg.hsm.b_conn) {
+		pnw->iotg.hsm.b_conn = 0;
+		penwell_update_transceiver();
+	}
+
+	/* Clear interrupts */
+	writel(int_mask, reg_base + CI_USBSTS);
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t otg_irq(int irq, void *_dev)
 {
 	struct penwell_otg		*pnw = _dev;
@@ -1360,11 +1391,28 @@ penwell_otg_notify(struct otg_transceiver *otg, enum usb_otg_events event)
 		break;
 	case USB_OTG_DEV_SUSP:
 		dev_dbg(pnw->dev, "PNW OTG Notify Client Bus suspend Event\n");
-		flag = 0;
+		if (iotg->otg.default_a == 1) {
+			iotg->hsm.b_bus_suspend = 1;
+			flag = 1;
+		} else {
+			if (iotg->hsm.a_bus_suspend == 0) {
+				iotg->hsm.a_bus_suspend = 1;
+				flag = 1;
+			} else
+				flag = 0;
+		}
 		break;
 	case USB_OTG_DEV_RESU:
 		dev_dbg(pnw->dev, "PNW OTG Notify Client Bus resume Event\n");
-		flag = 0;
+		if (iotg->otg.default_a == 1) {
+			/* in A_PERIPHERAL state */
+			iotg->hsm.b_bus_suspend = 0;
+			flag = 1;
+		} else {
+			/* in B_PERIPHERAL state */
+			iotg->hsm.a_bus_suspend = 0;
+			flag = 0;
+		}
 		break;
 	case USB_OTG_HOST_ACTIVE:
 		dev_dbg(pnw->dev, "PNW OTG Nofity Host Driver Add\n");
@@ -1467,12 +1515,15 @@ static void penwell_otg_work(struct work_struct *work)
 	struct otg_hsm			*hsm = &iotg->hsm;
 	enum usb_charger_type		charger_type;
 	int				retval;
+	struct pci_dev			*pdev;
 
 	dev_dbg(pnw->dev,
 		"old state = %s\n", otg_state_string(iotg->otg.state));
 
 	pm_runtime_get_sync(pnw->dev);
 
+	pdev = to_pci_dev(pnw->dev);
+
 	switch (iotg->otg.state) {
 	case OTG_STATE_UNDEFINED:
 	case OTG_STATE_B_IDLE:
@@ -1664,8 +1715,6 @@ static void penwell_otg_work(struct work_struct *work)
 							CHRG_CURR_DISCONN);
 			}
 
-			hsm->b_hnp_enable = 0;
-
 			if (iotg->stop_peripheral)
 				iotg->stop_peripheral(iotg);
 			else
@@ -1676,8 +1725,12 @@ static void penwell_otg_work(struct work_struct *work)
 			penwell_otg_mon_bus(BUS_MON_START);
 
 			iotg->otg.state = OTG_STATE_B_IDLE;
-		} else if (hsm->b_bus_req && hsm->b_hnp_enable
-				&& hsm->a_bus_suspend) {
+		} else if (hsm->b_bus_req && hsm->a_bus_suspend
+				&& iotg->otg.gadget
+				&& iotg->otg.gadget->b_hnp_enable) {
+
+			penwell_otg_phy_low_power(0);
+			usleep_range(7000, 8000);
 
 			if (iotg->stop_peripheral)
 				iotg->stop_peripheral(iotg);
@@ -1685,7 +1738,8 @@ static void penwell_otg_work(struct work_struct *work)
 				dev_dbg(pnw->dev,
 					"client driver has been removed.\n");
 
-			penwell_otg_HAAR(1);
+			penwell_otg_phy_low_power(0);
+
 			hsm->a_conn = 0;
 			hsm->a_bus_resume = 0;
 
@@ -1693,10 +1747,10 @@ static void penwell_otg_work(struct work_struct *work)
 				iotg->start_host(iotg);
 				hsm->test_device = 0;
 				iotg->otg.state = OTG_STATE_B_WAIT_ACON;
+				penwell_otg_add_timer(TB_ASE0_BRST_TMR);
 			} else
 				dev_dbg(pnw->dev, "host driver not loaded.\n");
 
-			penwell_otg_add_timer(TB_ASE0_BRST_TMR);
 		} else if (hsm->id == ID_ACA_C) {
 			/* Make sure current limit updated */
 			penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
@@ -1770,6 +1824,7 @@ static void penwell_otg_work(struct work_struct *work)
 			penwell_otg_del_timer(TB_ASE0_BRST_TMR);
 
 			penwell_otg_HAAR(0);
+
 			iotg->otg.state = OTG_STATE_B_HOST;
 			penwell_update_transceiver();
 		} else if (hsm->a_bus_resume || hsm->b_ase0_brst_tmout) {
@@ -2078,6 +2133,7 @@ static void penwell_otg_work(struct work_struct *work)
 			if (hsm->id == ID_ACA_B)
 				penwell_otg_update_chrg_cap(CHRG_ACA,
 							CHRG_CURR_ACA);
+			penwell_otg_phy_low_power(0);
 
 			if (iotg->stop_host)
 				iotg->stop_host(iotg);
@@ -2115,13 +2171,30 @@ static void penwell_otg_work(struct work_struct *work)
 			/* Stop HNP polling */
 			iotg->stop_hnp_poll(iotg);
 
-			penwell_otg_loc_sof(0);
-
 			if (iotg->otg.host->b_hnp_enable) {
 				/* According to Spec 7.1.5 */
 				penwell_otg_add_timer(TA_AIDL_BDIS_TMR);
+
+				/* Set HABA to enable hardware assistance to
+				 * signal A-connect after receiver B-disconnect
+				 * Hardware will then set client mode and
+				 * enable URE, SLE and PCE after the assistance
+				 * otg_dummy_irq is used to clean these ints
+				 * when client driver is not resumed.
+				 */
+				if (request_irq(pdev->irq, otg_dummy_irq,
+						IRQF_SHARED, driver_name,
+							iotg->base) != 0) {
+					dev_dbg(pnw->dev,
+						"request interrupt %d failed\n",
+								pdev->irq);
+				}
+				penwell_otg_HABA(1);
 			}
 
+			penwell_otg_loc_sof(0);
+			penwell_otg_phy_low_power(0);
+
 			iotg->otg.state = OTG_STATE_A_SUSPEND;
 		} else if (!hsm->b_conn) {
 			/* Stop HNP polling */
@@ -2142,6 +2215,8 @@ static void penwell_otg_work(struct work_struct *work)
 		if (hsm->id == ID_B || hsm->id == ID_ACA_B ||
 				hsm->a_bus_drop || hsm->a_aidl_bdis_tmout) {
 			/* Move to A_WAIT_VFALL state, timeout/user request */
+			penwell_otg_HABA(0);
+			free_irq(pdev->irq, iotg->base);
 
 			/* Delete current timer and clear HW assist */
 			if (hsm->a_aidl_bdis_tmout)
@@ -2169,6 +2244,8 @@ static void penwell_otg_work(struct work_struct *work)
 			iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
 		} else if (!hsm->a_vbus_vld) {
 			/* Move to A_VBUS_ERR state, Over-current */
+			penwell_otg_HABA(0);
+			free_irq(pdev->irq, iotg->base);
 
 			/* Delete current timer and clear flags */
 			penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
@@ -2186,6 +2263,8 @@ static void penwell_otg_work(struct work_struct *work)
 			iotg->otg.state = OTG_STATE_A_VBUS_ERR;
 		} else if (!hsm->b_conn && !pnw->iotg.otg.host->b_hnp_enable) {
 			/* Move to A_WAIT_BCON */
+			penwell_otg_HABA(0);
+			free_irq(pdev->irq, iotg->base);
 
 			/* delete current timer */
 			penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
@@ -2195,12 +2274,12 @@ static void penwell_otg_work(struct work_struct *work)
 			iotg->otg.state = OTG_STATE_A_WAIT_BCON;
 		} else if (!hsm->b_conn && pnw->iotg.otg.host->b_hnp_enable) {
 			/* Move to A_PERIPHERAL state, HNP */
+			penwell_otg_HABA(0);
+			free_irq(pdev->irq, iotg->base);
 
 			/* Delete current timer and clear flags */
 			penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
-
-			/* Stop HNP polling */
-			iotg->stop_hnp_poll(iotg);
+			penwell_otg_phy_low_power(0);
 
 			if (iotg->stop_host)
 				iotg->stop_host(iotg);
@@ -2208,12 +2287,15 @@ static void penwell_otg_work(struct work_struct *work)
 				dev_dbg(pnw->dev,
 					"host driver has been removed.\n");
 
+			penwell_otg_phy_low_power(0);
 			hsm->b_bus_suspend = 0;
 
 			/* Clear HNP polling flag */
 			if (iotg->otg.gadget)
 				iotg->otg.gadget->host_request_flag = 0;
 
+			penwell_otg_phy_low_power(0);
+
 			if (iotg->start_peripheral)
 				iotg->start_peripheral(iotg);
 			else
@@ -2224,14 +2306,17 @@ static void penwell_otg_work(struct work_struct *work)
 			iotg->otg.state = OTG_STATE_A_PERIPHERAL;
 		} else if (hsm->a_bus_req) {
 			/* Move to A_HOST state, user request */
+			penwell_otg_HABA(0);
+			free_irq(pdev->irq, iotg->base);
 
 			/* Delete current timer and clear flags */
 			penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
 
+			penwell_otg_loc_sof(1);
+
 			/* Start HNP polling */
 			iotg->start_hnp_poll(iotg);
 
-			penwell_otg_loc_sof(1);
 			iotg->otg.state = OTG_STATE_A_HOST;
 		} else if (hsm->id == ID_ACA_A) {
 			penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
@@ -2283,6 +2368,9 @@ static void penwell_otg_work(struct work_struct *work)
 			/* Move to A_WAIT_BCON state */
 			hsm->a_bidl_adis_tmr = 0;
 
+			penwell_otg_phy_low_power(0);
+			usleep_range(7000, 8000);
+
 			/* Disable client function and switch to host mode */
 			if (iotg->stop_peripheral)
 				iotg->stop_peripheral(iotg);
@@ -2293,6 +2381,8 @@ static void penwell_otg_work(struct work_struct *work)
 			hsm->hnp_poll_enable = 0;
 			hsm->b_conn = 0;
 
+			penwell_otg_phy_low_power(0);
+
 			if (iotg->start_host)
 				iotg->start_host(iotg);
 			else
@@ -2301,13 +2391,11 @@ static void penwell_otg_work(struct work_struct *work)
 
 			penwell_otg_add_timer(TA_WAIT_BCON_TMR);
 			iotg->otg.state = OTG_STATE_A_WAIT_BCON;
-		} else if (!hsm->b_bus_suspend && hsm->a_bidl_adis_tmr) {
-			/* Client report suspend state end, delete timer */
-			penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
-		} else if (hsm->b_bus_suspend && !hsm->a_bidl_adis_tmr) {
-			/* Client report suspend state start, start timer */
+		} else if (hsm->id == ID_A && hsm->b_bus_suspend) {
 			if (!timer_pending(&pnw->hsm_timer))
 				penwell_otg_add_timer(TA_BIDL_ADIS_TMR);
+		} else if (hsm->id == ID_A && !hsm->b_bus_suspend) {
+			penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
 		} else if (hsm->id == ID_ACA_A) {
 			penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
 
diff --git a/include/linux/usb/penwell_otg.h b/include/linux/usb/penwell_otg.h
index 3361d01..579209c 100644
--- a/include/linux/usb/penwell_otg.h
+++ b/include/linux/usb/penwell_otg.h
@@ -29,6 +29,8 @@
 #	define USBSTS_SLI		BIT(8)
 #	define USBSTS_URI		BIT(6)
 #	define USBSTS_PCI		BIT(2)
+#define CI_USBINTR		0x38
+#	define USBINTR_PCE		BIT(2)
 #define CI_ULPIVP		0x60
 #	define ULPI_WU			BIT(31)
 #	define ULPI_RUN			BIT(30)
@@ -234,7 +236,7 @@ enum penwell_otg_timer_type {
 };
 
 #define TA_WAIT_VRISE		100
-#define TA_WAIT_BCON		30000
+#define TA_WAIT_BCON		15000
 #define TA_AIDL_BDIS		1500
 #define TA_BIDL_ADIS		300
 #define TA_WAIT_VFALL		950
-- 
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