static int dwc3_core_init_mode(struct dwc3 *dwc)
{
struct device *dev = dwc->dev;
- int ret;
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
- ret = dwc3_gadget_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize gadget\n");
- return ret;
- }
break;
case USB_DR_MODE_HOST:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
- ret = dwc3_host_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
break;
case USB_DR_MODE_OTG:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
- ret = dwc3_host_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
-
- ret = dwc3_gadget_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize gadget\n");
- return ret;
- }
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
}
}
+/* XHCI reset, resets other CORE registers as well, re-init those */
+void dwc3_post_host_reset_core_init(struct dwc3 *dwc)
+{
+ dwc3_core_init(dwc);
+ dwc3_gadget_restart(dwc);
+}
+
static void (*notify_event) (struct dwc3 *, unsigned);
void dwc3_set_notifier(void (*notify)(struct dwc3 *, unsigned))
{
}
EXPORT_SYMBOL(dwc3_notify_event);
+int dwc3_core_pre_init(struct dwc3 *dwc)
+{
+ int ret;
+
+ dwc3_cache_hwparams(dwc);
+
+ ret = dwc3_phy_setup(dwc);
+ if (ret)
+ goto err0;
+
+ if (!dwc->ev_buffs) {
+ ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
+ if (ret) {
+ dev_err(dwc->dev, "failed to allocate event buffers\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
+ }
+
+ ret = dwc3_core_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to initialize core\n");
+ goto err2;
+ }
+
+ ret = phy_power_on(dwc->usb2_generic_phy);
+ if (ret < 0)
+ goto err3;
+
+ ret = phy_power_on(dwc->usb3_generic_phy);
+ if (ret < 0)
+ goto err4;
+
+ ret = dwc3_event_buffers_setup(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to setup event buffers\n");
+ goto err5;
+ }
+
+ ret = dwc3_core_init_mode(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to set mode with dwc3 core\n");
+ goto err6;
+ }
+
+ return ret;
+
+err6:
+ dwc3_event_buffers_cleanup(dwc);
+err5:
+ phy_power_off(dwc->usb3_generic_phy);
+err4:
+ phy_power_off(dwc->usb2_generic_phy);
+err3:
+ dwc3_core_exit(dwc);
+err2:
+ dwc3_free_event_buffers(dwc);
+err1:
+ dwc3_ulpi_exit(dwc);
+err0:
+ return ret;
+}
+
#define DWC3_ALIGN_MASK (16 - 1)
static int dwc3_probe(struct platform_device *pdev)
init_waitqueue_head(&dwc->wait_linkstate);
platform_set_drvdata(pdev, dwc);
- dwc3_cache_hwparams(dwc);
-
- ret = dwc3_phy_setup(dwc);
- if (ret)
- goto err0;
-
ret = dwc3_core_get_phy(dwc);
if (ret)
goto err0;
dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
}
+ pm_runtime_no_callbacks(dev);
+ pm_runtime_set_active(dev);
pm_runtime_enable(dev);
- pm_runtime_get_sync(dev);
pm_runtime_forbid(dev);
- ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
- if (ret) {
- dev_err(dwc->dev, "failed to allocate event buffers\n");
- ret = -ENOMEM;
- goto err1;
- }
-
if (IS_ENABLED(CONFIG_USB_DWC3_HOST))
dwc->dr_mode = USB_DR_MODE_HOST;
else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET))
dwc->dr_mode = USB_DR_MODE_PERIPHERAL;
- if (dwc->dr_mode == USB_DR_MODE_UNKNOWN)
+ if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) {
dwc->dr_mode = USB_DR_MODE_OTG;
-
- ret = dwc3_core_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize core\n");
- goto err1;
+ dwc->is_drd = true;
}
/* Adjust Frame Length */
dwc3_frame_length_adjustment(dwc, fladj);
- 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;
-
- ret = phy_power_on(dwc->usb3_generic_phy);
- if (ret < 0)
- goto err3;
+ /* Hardcode number of eps */
+ dwc->num_in_eps = 16;
+ dwc->num_out_eps = 16;
- ret = dwc3_event_buffers_setup(dwc);
- if (ret) {
- dev_err(dwc->dev, "failed to setup event buffers\n");
- goto err4;
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_PERIPHERAL) {
+ ret = dwc3_gadget_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize gadget\n");
+ goto err0;
+ }
}
- ret = dwc3_core_init_mode(dwc);
- if (ret)
- goto err5;
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_HOST) {
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ goto err_gadget;
+ }
+ }
ret = dwc3_debugfs_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize debugfs\n");
- goto err6;
+ goto err_host;
}
pm_runtime_allow(dev);
return 0;
-err6:
- dwc3_core_exit_mode(dwc);
-
-err5:
- dwc3_event_buffers_cleanup(dwc);
-
-err4:
- phy_power_off(dwc->usb3_generic_phy);
-
-err3:
- phy_power_off(dwc->usb2_generic_phy);
-
-err2:
- usb_phy_set_suspend(dwc->usb2_phy, 1);
- usb_phy_set_suspend(dwc->usb3_phy, 1);
- dwc3_core_exit(dwc);
-
-err1:
- dwc3_free_event_buffers(dwc);
- dwc3_ulpi_exit(dwc);
-
+err_host:
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_HOST)
+ dwc3_host_exit(dwc);
+err_gadget:
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_PERIPHERAL)
+ dwc3_gadget_exit(dwc);
err0:
/*
* restore res->start back to its original value so that, in case the
#define DWC3_CORE_PM_RESUME_EVENT 4
#define DWC3_CONTROLLER_CONNDONE_EVENT 5
#define DWC3_CONTROLLER_NOTIFY_OTG_EVENT 6
-#define DWC3_CONTROLLER_RESTART_USB_SESSION 10
+#define DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT 7
+#define DWC3_CONTROLLER_RESTART_USB_SESSION 8
#define MAX_INTR_STATS 10
/**
* 1 - -3.5dB de-emphasis
* 2 - No de-emphasis
* 3 - Reserved
+ * @is_drd: device supports dual-role or not
* @err_evt_seen: previous event in queue was erratic error
* @usb3_u1u2_disable: if true, disable U1U2 low power modes in Superspeed mode.
* @in_lpm: indicates if controller is in low power mode (no clocks)
* @bh_handled_evt_cnt: no. of events handled by tasklet per interrupt
* @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt
* @wait_linkstate: waitqueue for waiting LINK to move into required state
+ * @vbus_draw: current to be drawn from USB
*/
struct dwc3 {
struct usb_ctrlrequest *ctrl_req;
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
+ unsigned is_drd:1;
+ /* Indicate if the gadget was powered by the otg driver */
+ unsigned vbus_active:1;
+ /* Indicate if software connect was issued by the usb_gadget_driver */
+ unsigned softconnect:1;
unsigned nominal_elastic_buffer:1;
unsigned err_evt_seen:1;
unsigned usb3_u1u2_disable:1;
atomic_t in_lpm;
int tx_fifo_size;
bool b_suspend;
+ unsigned vbus_draw;
/* IRQ timing statistics */
int irq;
#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_gadget_init(struct dwc3 *dwc);
void dwc3_gadget_exit(struct dwc3 *dwc);
+void dwc3_gadget_restart(struct dwc3 *dwc);
int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode);
int dwc3_gadget_get_link_state(struct dwc3 *dwc);
int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
{ return 0; }
static inline void dwc3_gadget_exit(struct dwc3 *dwc)
{ }
+static inline void dwc3_gadget_restart(struct dwc3 *dwc)
+{ }
static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode)
{ return 0; }
static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc)
int dwc3_core_init(struct dwc3 *dwc);
+int dwc3_core_pre_init(struct dwc3 *dwc);
+void dwc3_post_host_reset_core_init(struct dwc3 *dwc);
int dwc3_event_buffers_setup(struct dwc3 *dwc);
void dwc3_usb3_phy_suspend(struct dwc3 *dwc, int suspend);
return 0;
}
+static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned mA)
+{
+ struct dwc3 *dwc = gadget_to_dwc(g);
+
+ dwc->vbus_draw = mA;
+ dev_dbg(dwc->dev, "Notify controller from %s. mA = %d\n", __func__, mA);
+ dwc3_notify_event(dwc, DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT);
+ return 0;
+}
+
static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
{
struct dwc3 *dwc = gadget_to_dwc(g);
is_on = !!is_on;
+ dwc->softconnect = is_on;
+
+ if ((dwc->is_drd && !dwc->vbus_active) || !dwc->gadget_driver) {
+ /*
+ * Need to wait for vbus_session(on) from otg driver or to
+ * the udc_start.
+ */
+ return 0;
+ }
+
pm_runtime_get_sync(dwc->dev);
dbg_event(0xFF, "Pullup gsync",
atomic_read(&dwc->dev->power.usage_count));
static irqreturn_t dwc3_interrupt(int irq, void *_dwc);
static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc);
+static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc);
-static int dwc3_gadget_start(struct usb_gadget *g,
- struct usb_gadget_driver *driver)
+static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active)
{
- struct dwc3 *dwc = gadget_to_dwc(g);
- struct dwc3_ep *dep;
- unsigned long flags;
- int ret = 0;
- int irq;
- u32 reg;
+ struct dwc3 *dwc = gadget_to_dwc(_gadget);
+ unsigned long flags;
- irq = platform_get_irq(to_platform_device(dwc->dev), 0);
- dwc->irq = irq;
- ret = request_irq(irq, dwc3_interrupt, IRQF_SHARED, "dwc3", dwc);
- if (ret) {
- dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
- irq, ret);
- goto err0;
- }
+ if (!dwc->is_drd)
+ return -EPERM;
+
+ is_active = !!is_active;
spin_lock_irqsave(&dwc->lock, flags);
- if (dwc->gadget_driver) {
- dev_err(dwc->dev, "%s is already bound to %s\n",
- dwc->gadget.name,
- dwc->gadget_driver->driver.name);
- ret = -EBUSY;
- goto err1;
+ /* Mark that the vbus was powered */
+ dwc->vbus_active = is_active;
+
+ /*
+ * Check if upper level usb_gadget_driver was already registerd with
+ * this udc controller driver (if dwc3_gadget_start was called)
+ */
+ if (dwc->gadget_driver && dwc->softconnect) {
+ if (dwc->vbus_active) {
+ /*
+ * Both vbus was activated by otg and pullup was
+ * signaled by the gadget driver.
+ */
+ dwc3_gadget_run_stop(dwc, 1, false);
+ } else {
+ dwc3_gadget_run_stop(dwc, 0, false);
+ }
}
- dwc->gadget_driver = driver;
+ /*
+ * Clearing run/stop bit might occur before disconnect event is seen.
+ * Make sure to let gadget driver know in that case.
+ */
+ if (!dwc->vbus_active) {
+ dev_dbg(dwc->dev, "calling disconnect from %s\n", __func__);
+ dwc3_gadget_disconnect_interrupt(dwc);
+ }
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+}
+
+static int __dwc3_gadget_start(struct dwc3 *dwc)
+{
+ struct dwc3_ep *dep;
+ int ret = 0;
+ u32 reg;
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
reg &= ~(DWC3_DCFG_SPEED_MASK);
/* Start with SuperSpeed Default */
dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+ dwc->delayed_status = false;
+ /* reinitialize physical ep0-1 */
dep = dwc->eps[0];
+ dep->flags = 0;
+ dep->endpoint.maxburst = 1;
ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
false);
if (ret) {
dev_err(dwc->dev, "failed to enable %s\n", dep->name);
- goto err2;
+ return ret;
}
dep = dwc->eps[1];
+ dep->flags = 0;
+ dep->endpoint.maxburst = 1;
ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
false);
if (ret) {
dev_err(dwc->dev, "failed to enable %s\n", dep->name);
- goto err3;
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
+ return ret;
}
/* begin to receive SETUP packets */
dwc3_gadget_enable_irq(dwc);
- spin_unlock_irqrestore(&dwc->lock, flags);
+ return ret;
+}
- return 0;
+/* Required gadget re-initialization before switching to gadget in OTG mode */
+void dwc3_gadget_restart(struct dwc3 *dwc)
+{
+ __dwc3_gadget_start(dwc);
+}
-err3:
- __dwc3_gadget_ep_disable(dwc->eps[0]);
+static int dwc3_gadget_start(struct usb_gadget *g,
+ struct usb_gadget_driver *driver)
+{
+ struct dwc3 *dwc = gadget_to_dwc(g);
+ unsigned long flags;
+ int ret = 0;
+ int irq;
-err2:
- dwc->gadget_driver = NULL;
+ irq = platform_get_irq(to_platform_device(dwc->dev), 0);
+ dwc->irq = irq;
+ ret = request_irq(irq, dwc3_interrupt, IRQF_SHARED, "dwc3", dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+ irq, ret);
+ goto err0;
+ }
-err1:
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ if (dwc->gadget_driver) {
+ dev_err(dwc->dev, "%s is already bound to %s\n",
+ dwc->gadget.name,
+ dwc->gadget_driver->driver.name);
+ ret = -EBUSY;
+ goto err1;
+ }
+
+ dwc->gadget_driver = driver;
+
+ /*
+ * For DRD, this might get called by gadget driver during bootup
+ * even though host mode might be active. Don't actually perform
+ * device-specific initialization until device mode is activated.
+ * In that case dwc3_gadget_restart() will handle it.
+ */
spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+err1:
+ spin_unlock_irqrestore(&dwc->lock, flags);
free_irq(irq, dwc);
err0:
.wakeup = dwc3_gadget_wakeup,
.func_wakeup = dwc_gadget_func_wakeup,
.set_selfpowered = dwc3_gadget_set_selfpowered,
+ .vbus_session = dwc3_gadget_vbus_session,
+ .vbus_draw = dwc3_gadget_vbus_draw,
.pullup = dwc3_gadget_pullup,
.udc_start = dwc3_gadget_start,
.udc_stop = dwc3_gadget_stop,
dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT);
dwc3_usb3_phy_suspend(dwc, false);
+ usb_gadget_vbus_draw(&dwc->gadget, 0);
dwc3_reset_gadget(dwc);
dbg_event(0xFF, "BUS RST", 0);
{
trace_dwc3_event(event->raw);
+ /* skip event processing in absence of vbus */
+ if (!dwc->vbus_active) {
+ dbg_print_reg("SKIP EVT", event->raw);
+ return;
+ }
+
/* If run/stop is cleared don't process any more events */
if (!dwc->pullups_connected) {
dbg_print_reg("SKIP_EVT_PULLUP", event->raw);
goto err4;
}
+ if (!dwc->is_drd) {
+ pm_runtime_no_callbacks(&dwc->gadget.dev);
+ pm_runtime_set_active(&dwc->gadget.dev);
+ pm_runtime_enable(&dwc->gadget.dev);
+ pm_runtime_get(&dwc->gadget.dev);
+ }
+
return 0;
err4:
void dwc3_gadget_exit(struct dwc3 *dwc)
{
+ if (dwc->is_drd) {
+ pm_runtime_put(&dwc->gadget.dev);
+ pm_runtime_disable(&dwc->gadget.dev);
+ }
+
usb_del_gadget_udc(&dwc->gadget);
dwc3_gadget_free_endpoints(dwc);
phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(&xhci->dev));
- ret = platform_device_add(xhci);
- if (ret) {
- dev_err(dwc->dev, "failed to register xHCI device\n");
- goto err2;
- }
-
+ /* Platform device gets added as part of state machine */
return 0;
-err2:
- phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy",
- dev_name(&xhci->dev));
- phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
- dev_name(&xhci->dev));
+
err1:
platform_device_put(xhci);
return ret;
dev_name(&dwc->xhci->dev));
phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(&dwc->xhci->dev));
- platform_device_unregister(dwc->xhci);
+ if (!dwc->is_drd)
+ platform_device_unregister(dwc->xhci);
}
goto put_hcd;
}
+ if (pdev->dev.parent)
+ pm_runtime_resume(pdev->dev.parent);
+
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+
if (of_device_is_compatible(pdev->dev.of_node,
"marvell,armada-375-xhci") ||
of_device_is_compatible(pdev->dev.of_node,
if (ret)
goto dealloc_usb2_hcd;
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
return 0;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct clk *clk = xhci->clk;
+ pm_runtime_disable(&dev->dev);
+
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
return 0;
}
-#ifdef CONFIG_PM_SLEEP
-static int xhci_plat_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int xhci_plat_runtime_idle(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-
/*
- * xhci_suspend() needs `do_wakeup` to know whether host is allowed
- * to do wakeup during suspend. Since xhci_plat_suspend is currently
- * only designed for system suspend, device_may_wakeup() is enough
- * to dertermine whether host is allowed to do wakeup. Need to
- * reconsider this when xhci_plat_suspend enlarges its scope, e.g.,
- * also applies to runtime suspend.
+ * When pm_runtime_put_autosuspend() is called on this device,
+ * after this idle callback returns the PM core will schedule the
+ * autosuspend if there is any remaining time until expiry. However,
+ * when reaching this point because the child_count becomes 0, the
+ * core does not honor autosuspend in that case and results in
+ * idle/suspend happening immediately. In order to have a delay
+ * before suspend we have to call pm_runtime_autosuspend() manually.
*/
- return xhci_suspend(xhci, device_may_wakeup(dev));
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_autosuspend(dev);
+ return -EBUSY;
}
-static int xhci_plat_resume(struct device *dev)
+static int xhci_plat_runtime_suspend(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ if (!xhci)
+ return 0;
+
+ dev_dbg(dev, "xhci-plat runtime suspend\n");
- return xhci_resume(xhci, 0);
+ return xhci_suspend(xhci, true);
+}
+
+static int xhci_plat_runtime_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
+
+ if (!xhci)
+ return 0;
+
+ dev_dbg(dev, "xhci-plat runtime resume\n");
+
+ ret = xhci_resume(xhci, false);
+ pm_runtime_mark_last_busy(dev);
+
+ return ret;
}
static const struct dev_pm_ops xhci_plat_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
+ SET_SYSTEM_SLEEP_PM_OPS(NULL, NULL)
+ SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, xhci_plat_runtime_resume,
+ xhci_plat_runtime_idle)
};
#define DEV_PM_OPS (&xhci_plat_pm_ops)
#else