[PATCH v2 4/4] usb: dwc3: Workaround for super-speed host on dra7 in dual-role mode

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

 



dra7 OTG core limits the host controller to USB2.0 (high-speed) mode
when we're operating in dual-role.

We work around that by bypassing the OTG core and reading the
extcon framework directly for ID/VBUS events.

Signed-off-by: Roger Quadros <rogerq@xxxxxx>
---
 Documentation/devicetree/bindings/usb/dwc3.txt |   2 +
 drivers/usb/dwc3/core.c                        | 169 ++++++++++++++++++++++++-
 drivers/usb/dwc3/core.h                        |   5 +
 3 files changed, 170 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt
index e3e6983..9955c0d 100644
--- a/Documentation/devicetree/bindings/usb/dwc3.txt
+++ b/Documentation/devicetree/bindings/usb/dwc3.txt
@@ -53,6 +53,8 @@ Optional properties:
  - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ
 	register for post-silicon frame length adjustment when the
 	fladj_30mhz_sdbnd signal is invalid or incorrect.
+ - extcon: phandle to the USB connector extcon device. If present, extcon
+	device will be used to get USB cable events instead of OTG controller.
 
  - <DEPRECATED> tx-fifo-resize: determines if the FIFO *has* to be reallocated.
 
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 619fa7c..b02d911 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -918,6 +918,19 @@ static void dwc3_otg_fsm_sync(struct dwc3 *dwc)
 	if (dwc->otg_prevent_sync)
 		return;
 
+	if (dwc->edev) {
+		/* get ID */
+		id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+		/* Host means ID == 0 */
+		id = !id;
+
+		/* get VBUS */
+		vbus = extcon_get_state(dwc->edev, EXTCON_USB);
+		dwc3_drd_statemachine(dwc, id, vbus);
+
+		return;
+	}
+
 	reg = dwc3_readl(dwc->regs, DWC3_OSTS);
 	id = !!(reg & DWC3_OSTS_CONIDSTS);
 	vbus = !!(reg & DWC3_OSTS_BSESVLD);
@@ -934,6 +947,17 @@ static void dwc3_drd_work(struct work_struct *work)
 	spin_unlock(&dwc->lock);
 }
 
+static int dwc3_drd_notifier(struct notifier_block *nb,
+			     unsigned long event, void *ptr)
+{
+	struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
+
+	if (!dwc->otg_prevent_sync)
+		queue_work(system_power_efficient_wq, &dwc->otg_work);
+
+	return NOTIFY_DONE;
+}
+
 static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask)
 {
 	dwc->oevten &= ~(disable_mask);
@@ -1040,6 +1064,27 @@ static int dwc3_drd_start_host(struct dwc3 *dwc, int on, bool skip)
 {
 	u32 reg;
 
+	if (!dwc->edev)
+		goto otg;
+
+	if (on)
+		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+
+	if (!skip) {
+		spin_unlock(&dwc->lock);
+
+		/* start or stop the HCD */
+		if (on)
+			dwc3_host_init(dwc);
+		else
+			dwc3_host_exit(dwc);
+
+		spin_lock(&dwc->lock);
+	}
+
+	return 0;
+
+otg:
 	/* switch OTG core */
 	if (on) {
 		/* As per Figure 11-10 A-Device Flow Diagram */
@@ -1133,6 +1178,33 @@ static int dwc3_drd_start_gadget(struct dwc3 *dwc, int on)
 	if (on)
 		dwc3_event_buffers_setup(dwc);
 
+	if (!dwc->edev)
+		goto otg;
+
+	if (on) {
+		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+		/* start the Peripheral driver  */
+		if (dwc->gadget_driver) {
+			__dwc3_gadget_start(dwc);
+			if (dwc->gadget_pullup)
+				dwc3_gadget_run_stop(dwc, true, false);
+		}
+	} else {
+		/* stop the Peripheral driver */
+		if (dwc->gadget_driver) {
+			if (dwc->gadget_pullup)
+				dwc3_gadget_run_stop(dwc, false, false);
+			spin_unlock(&dwc->lock);
+			if (dwc->gadget_driver->disconnect)
+				dwc->gadget_driver->disconnect(&dwc->gadget);
+			spin_lock(&dwc->lock);
+			__dwc3_gadget_stop(dwc);
+		}
+	}
+
+	return 0;
+
+otg:
 	if (on) {
 		/* As per Figure 11-20 B-Device Flow Diagram */
 
@@ -1251,6 +1323,13 @@ static void dwc3_otg_core_init(struct dwc3 *dwc)
 {
 	u32 reg;
 
+	/* force drd state machine update the first time */
+	dwc->otg_fsm.b_sess_vld = -1;
+	dwc->otg_fsm.id = -1;
+
+	if (dwc->edev)
+		return;
+
 	/*
 	 * As per Figure 11-4 OTG Driver Overall Programming Flow,
 	 * block "Initialize GCTL for OTG operation".
@@ -1264,15 +1343,14 @@ static void dwc3_otg_core_init(struct dwc3 *dwc)
 
 	/* Initialize OTG registers */
 	dwc3_otgregs_init(dwc);
-
-	/* force drd state machine update the first time */
-	dwc->otg_fsm.b_sess_vld = -1;
-	dwc->otg_fsm.id = -1;
 }
 
 /* dwc->lock must be held */
 static void dwc3_otg_core_exit(struct dwc3 *dwc)
 {
+	if (dwc->edev)
+		return;
+
 	/* disable all otg irqs */
 	dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
 	/* clear all events */
@@ -1286,6 +1364,57 @@ static int dwc3_drd_init(struct dwc3 *dwc)
 
 	INIT_WORK(&dwc->otg_work, dwc3_drd_work);
 
+	/* If extcon device is present we don't rely on OTG core for ID event */
+	if (dwc->edev) {
+		int id, vbus;
+
+		dwc->edev_nb.notifier_call = dwc3_drd_notifier;
+		ret = extcon_register_notifier(dwc->edev, EXTCON_USB,
+					       &dwc->edev_nb);
+		if (ret < 0) {
+			dev_err(dwc->dev, "Couldn't register USB cable notifier\n");
+			return -ENODEV;
+		}
+
+		ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
+					       &dwc->edev_nb);
+		if (ret < 0) {
+			dev_err(dwc->dev, "Couldn't register USB-HOST cable notifier\n");
+			ret = -ENODEV;
+			goto extcon_fail;
+		}
+
+		/* sanity check id & vbus states */
+		id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+		vbus = extcon_get_state(dwc->edev, EXTCON_USB);
+		if (id < 0 || vbus < 0) {
+			dev_err(dwc->dev, "Invalid USB cable state. id %d, vbus %d\n",
+				id, vbus);
+			ret = -ENODEV;
+			goto fail;
+		}
+
+		ret = dwc3_gadget_init(dwc);
+		if (ret)
+			goto fail;
+
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otg_core_init(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		queue_work(system_power_efficient_wq, &dwc->otg_work);
+
+		return 0;
+
+fail:
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+					   &dwc->edev_nb);
+extcon_fail:
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB,
+					   &dwc->edev_nb);
+
+		return ret;
+	}
+
 	irq = dwc3_otg_get_irq(dwc);
 	if (irq < 0)
 		return irq;
@@ -1326,13 +1455,24 @@ static void dwc3_drd_exit(struct dwc3 *dwc)
 {
 	unsigned long flags;
 
+	spin_lock(&dwc->lock);
+	dwc->otg_prevent_sync = true;
+	spin_unlock(&dwc->lock);
 	cancel_work_sync(&dwc->otg_work);
+
 	spin_lock_irqsave(&dwc->lock, flags);
 	dwc3_otg_core_exit(dwc);
 	if (dwc->otg_fsm.protocol == PROTO_HOST)
 		dwc3_drd_start_host(dwc, 0, 0);
 	dwc->otg_fsm.protocol = PROTO_UNDEF;
-	free_irq(dwc->otg_irq, dwc);
+	if (dwc->edev) {
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+					   &dwc->edev_nb);
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB,
+					   &dwc->edev_nb);
+	} else {
+		free_irq(dwc->otg_irq, dwc);
+	}
 	spin_unlock_irqrestore(&dwc->lock, flags);
 
 	dwc3_gadget_exit(dwc);
@@ -1587,6 +1727,14 @@ static int dwc3_probe(struct platform_device *pdev)
 
 	dwc3_get_properties(dwc);
 
+	if (dev->of_node) {
+		if (of_property_read_bool(dev->of_node, "extcon"))
+			dwc->edev = extcon_get_edev_by_phandle(dev, 0);
+
+		if (IS_ERR(dwc->edev))
+			return PTR_ERR(dwc->edev);
+	}
+
 	platform_set_drvdata(pdev, dwc);
 	dwc3_cache_hwparams(dwc);
 
@@ -1743,6 +1891,13 @@ static int dwc3_resume_common(struct dwc3 *dwc)
 	if (ret)
 		return ret;
 
+	if (dwc->dr_mode == USB_DR_MODE_OTG &&
+	    dwc->edev) {
+		if (dwc->otg_fsm.protocol == PROTO_HOST)
+			dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+		else if (dwc->otg_fsm.protocol == PROTO_GADGET)
+			dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+	}
 	spin_lock_irqsave(&dwc->lock, flags);
 
 	switch (dwc->dr_mode) {
@@ -1771,8 +1926,10 @@ static int dwc3_resume_common(struct dwc3 *dwc)
 
 	if (dwc->dr_mode == USB_DR_MODE_OTG) {
 		dwc3_otg_core_init(dwc);
-		if (dwc->otg_fsm.protocol == PROTO_HOST)
+		if ((dwc->otg_fsm.protocol == PROTO_HOST) &&
+		    !dwc->edev) {
 			dwc3_drd_start_host(dwc, true, 1);
+		}
 	}
 
 	spin_unlock_irqrestore(&dwc->lock, flags);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index bf8951d..fc060ae 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -38,6 +38,7 @@
 #include <linux/usb/otg.h>
 #include <linux/usb/otg-fsm.h>
 
+#include <linux/extcon.h>
 #include <linux/workqueue.h>
 
 #define DWC3_MSG_MAX	500
@@ -869,6 +870,8 @@ struct dwc3_scratchpad_array {
  * @current_mode: current mode of operation written to PRTCAPDIR
  * @otg_work: work struct for otg/dual-role
  * @otg_needs_host_start: flag that OTG controller needs to start host
+ * @edev: extcon handle
+ * @edev_nb: extcon notifier
  * @fladj: frame length adjustment
  * @irq_gadget: peripheral controller's IRQ number
  * @otg_irq: IRQ number for OTG IRQs
@@ -1007,6 +1010,8 @@ struct dwc3 {
 	u32			current_mode;
 	struct work_struct	otg_work;
 	bool			otg_needs_host_start;
+	struct extcon_dev	*edev;
+	struct notifier_block	edev_nb;
 
 	enum usb_phy_interface	hsphy_mode;
 
-- 
2.7.4

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