[PATCH v2 4/4] usb: dwc3: Workaround for super-speed host on dra7 in dual-role mode
From: Roger Quadros
Date: Thu Feb 16 2017 - 08:07:08 EST
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