From 26ecf99bd66fe915083fb69c7c20b9b9b67d38f0 Mon Sep 17 00:00:00 2001 From: Wu Liang feng Date: Thu, 14 Jul 2016 10:32:19 +0800 Subject: [PATCH] usb: dwc3: core: support DRD mode switch with extcon notifier MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch register extcon notification for DRD mode. If extcon cable state is EXTCON_USB, means that an USB peripheral cable connected, and we need to set DWC3 work in device mode. If extcon cable state is EXTCON_USB_HOST, means that an USB host cable connected, and we need to set DWC3 work in host mode. And we need to register different notifier block for EXTCON_USB notifier and EXTCON_USB_HOST notifier,because if multiple notifiers registered with the same notifier block, it will cause kernel crash on notification events. Change-Id: Ia09e5088f738496b87d06b0f4062c144fa781e4e Signed-off-by: Wu Liang feng --- drivers/usb/dwc3/core.c | 214 ++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/core.h | 9 ++ drivers/usb/dwc3/gadget.c | 14 ++- 3 files changed, 236 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 4c3dda4c572a..db5eda59f9cf 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -879,6 +880,170 @@ bool dwc3_force_mode(struct dwc3 *dwc, u32 mode) return true; } +static void rockchip_otg_extcon_evt_worker(struct work_struct *work) +{ + struct dwc3 *dwc = + container_of(work, struct dwc3, cable.otg_work.work); + struct extcon_dev *edev = dwc->cable.edev; + struct usb_hcd *hcd; + unsigned long flags; + int ret; + u32 reg; + + if (extcon_get_cable_state_(edev, EXTCON_USB) > 0) { + dev_info(dwc->dev, "USB peripheral connected\n"); + + if (dwc->cable.connected) { + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + + if (DWC3_GCTL_PRTCAP(reg) != DWC3_GCTL_PRTCAP_DEVICE) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + return; + } + + /* + * If dr_mode is host only, never to set + * the mode to the peripheral mode. + */ + if (WARN_ON(dwc->dr_mode == USB_DR_MODE_HOST)) + return; + + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) + return; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) + return; + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + dwc3_gadget_restart(dwc, true); + + spin_unlock_irqrestore(&dwc->lock, flags); + + dwc->cable.connected = true; + } else if (extcon_get_cable_state_(edev, EXTCON_USB_HOST) > 0) { + dev_info(dwc->dev, "USB HOST connected\n"); + + if (dwc->cable.connected) { + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + + if (DWC3_GCTL_PRTCAP(reg) != DWC3_GCTL_PRTCAP_HOST) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + return; + } + + /* + * If dr_mode is device only, never to + * set the mode to the host mode. + */ + if (WARN_ON(dwc->dr_mode == USB_DR_MODE_PERIPHERAL)) + return; + + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) + return; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) + return; + + hcd = dev_get_drvdata(&dwc->xhci->dev); + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + + spin_unlock_irqrestore(&dwc->lock, flags); + + if (hcd->state == HC_STATE_HALT) { + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + usb_add_hcd(hcd->shared_hcd, hcd->irq, IRQF_SHARED); + } + + dwc->cable.connected = true; + } else { + dev_info(dwc->dev, "USB unconnected\n"); + + if (!dwc->cable.connected) + return; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + + if (DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_DEVICE || + DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_OTG) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_restart(dwc, false); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + if (DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_HOST || + DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_OTG) { + hcd = dev_get_drvdata(&dwc->xhci->dev); + + if (hcd->state != HC_STATE_HALT) { + usb_remove_hcd(hcd->shared_hcd); + usb_remove_hcd(hcd); + } + } + + spin_lock_irqsave(&dwc->lock, flags); + + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + break; + case USB_DR_MODE_HOST: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + break; + case USB_DR_MODE_OTG: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + break; + default: + return; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + phy_power_off(dwc->usb2_generic_phy); + phy_power_off(dwc->usb3_generic_phy); + + dwc->cable.connected = false; + } +} + +static int dwc3_rockchip_device_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = + container_of(nb, struct dwc3, cable.device_nb); + + schedule_delayed_work(&dwc->cable.otg_work, 0); + + return NOTIFY_DONE; +} + +static int dwc3_rockchip_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = + container_of(nb, struct dwc3, cable.host_nb); + + schedule_delayed_work(&dwc->cable.otg_work, 0); + + return NOTIFY_DONE; +} + #define DWC3_ALIGN_MASK (16 - 1) static int dwc3_probe(struct platform_device *pdev) @@ -891,6 +1056,7 @@ static int dwc3_probe(struct platform_device *pdev) u8 tx_de_emphasis; u8 hird_threshold; u32 fladj = 0; + struct extcon_dev *edev; int ret; @@ -920,6 +1086,50 @@ static int dwc3_probe(struct platform_device *pdev) return ret; } + if (device_property_read_bool(dev, "extcon")) { + edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) != -EPROBE_DEFER) + dev_err(dev, "couldn't get extcon device\n"); + return PTR_ERR(edev); + } + + /* Register for extcon notification */ + INIT_DELAYED_WORK(&dwc->cable.otg_work, + rockchip_otg_extcon_evt_worker); + dwc->cable.device_nb.notifier_call = + dwc3_rockchip_device_notifier; + dwc->cable.host_nb.notifier_call = + dwc3_rockchip_host_notifier; + + ret = extcon_register_notifier(edev, EXTCON_USB, + &dwc->cable.device_nb); + if (ret < 0) { + dev_err(dev, "failed to register notifier for USB\n"); + return ret; + } + + ret = extcon_register_notifier(edev, EXTCON_USB_HOST, + &dwc->cable.host_nb); + if (ret < 0) { + dev_err(dev, "failed to register notifier for USB HOST\n"); + extcon_unregister_notifier(edev, EXTCON_USB, + &dwc->cable.device_nb); + return ret; + } + + /* + * cable.connected flag is used for otg device/host + * connect status, true means connection and false + * means disconnection. However, we initialize the + * cable.connected with true, though nothing connect + * with the usb port. It aims to do phy power off + * and disable controller in cable.work. + */ + dwc->cable.connected = true; + dwc->cable.edev = edev; + } + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(dev, "missing IRQ\n"); @@ -1121,6 +1331,7 @@ static int dwc3_probe(struct platform_device *pdev) usb_phy_set_suspend(dwc->usb2_phy, 0); usb_phy_set_suspend(dwc->usb3_phy, 0); + ret = phy_power_on(dwc->usb2_generic_phy); if (ret < 0) goto err2; @@ -1145,6 +1356,9 @@ static int dwc3_probe(struct platform_device *pdev) goto err6; } + if (dwc->cable.edev) + schedule_delayed_work(&dwc->cable.otg_work, (2 * HZ)); + pm_runtime_allow(dev); return 0; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 4f417c523be0..6ddf8e9462a2 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -706,6 +706,7 @@ struct dwc3_scratchpad_array { * @hwparams: copy of hwparams registers * @root: debugfs root folder pointer * @regset: debugfs pointer to regdump file + * @cable: extcon cable for otg device/host mode notifier * @test_mode: true when we're entering a USB test mode * @test_mode_nr: test feature selector * @lpm_nyet_threshold: LPM NYET response threshold @@ -862,6 +863,14 @@ struct dwc3 { struct dentry *root; struct debugfs_regset32 *regset; + struct { + struct extcon_dev *edev; + bool connected; + struct notifier_block device_nb; + struct notifier_block host_nb; + struct delayed_work otg_work; + } cable; + u8 test_mode; u8 test_mode_nr; u8 lpm_nyet_threshold; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e735b2a88a1a..e3845a5db496 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2918,6 +2918,7 @@ static int dwc3_gadget_reinit(struct dwc3 *dwc) u32 hwparams4 = dwc->hwparams.hwparams4; u32 reg; int ret; + unsigned long flags; struct dwc3_ep *dep = NULL; reg = dwc3_readl(dwc->regs, DWC3_GSNPSID); @@ -2948,11 +2949,15 @@ static int dwc3_gadget_reinit(struct dwc3 *dwc) dwc->maximum_speed = USB_SPEED_HIGH; } + spin_unlock_irqrestore(&dwc->lock, flags); + /* issue device SoftReset too */ ret = dwc3_soft_reset(dwc); if (ret) goto err0; + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg &= ~DWC3_GCTL_SCALEDOWN_MASK; @@ -3029,7 +3034,6 @@ static int dwc3_gadget_reinit(struct dwc3 *dwc) } reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg |= DWC3_DCFG_LPM_CAP; reg &= ~(DWC3_DCFG_SPEED_MASK); /** @@ -3098,6 +3102,14 @@ err0: return ret; } +/** + * dwc3_gadget_restart - reinit gadget related registers + * @dwc: pointer to our controller context structure + * + * The caller must own the device lock. + * + * Returns 0 on success otherwise negative errno. + */ int dwc3_gadget_restart(struct dwc3 *dwc, bool start) { struct dwc3_event_buffer *evt; -- 2.34.1