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