diff options
Diffstat (limited to 'drivers/usb/cdns3/core.c')
| -rw-r--r-- | drivers/usb/cdns3/core.c | 288 |
1 files changed, 204 insertions, 84 deletions
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 19bbb5b7e6b6..1991cb5cf6bf 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -27,13 +27,6 @@ static int cdns3_idle_init(struct cdns3 *cdns); -static inline -struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns) -{ - WARN_ON(!cdns->roles[cdns->role]); - return cdns->roles[cdns->role]; -} - static int cdns3_role_start(struct cdns3 *cdns, enum usb_role role) { int ret; @@ -93,7 +86,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns) struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; enum usb_dr_mode dr_mode; - int ret = 0; + int ret; dr_mode = usb_get_dr_mode(dev); cdns->role = USB_ROLE_NONE; @@ -184,7 +177,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns) goto err; } - return ret; + return 0; err: cdns3_exit_roles(cdns); return ret; @@ -198,11 +191,17 @@ err: */ static enum usb_role cdns3_hw_role_state_machine(struct cdns3 *cdns) { - enum usb_role role; + enum usb_role role = USB_ROLE_NONE; int id, vbus; - if (cdns->dr_mode != USB_DR_MODE_OTG) - goto not_otg; + if (cdns->dr_mode != USB_DR_MODE_OTG) { + if (cdns3_is_host(cdns)) + role = USB_ROLE_HOST; + if (cdns3_is_device(cdns)) + role = USB_ROLE_DEVICE; + + return role; + } id = cdns3_get_id(cdns); vbus = cdns3_get_vbus(cdns); @@ -239,14 +238,6 @@ static enum usb_role cdns3_hw_role_state_machine(struct cdns3 *cdns) dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role); return role; - -not_otg: - if (cdns3_is_host(cdns)) - role = USB_ROLE_HOST; - if (cdns3_is_device(cdns)) - role = USB_ROLE_DEVICE; - - return role; } static int cdns3_idle_role_start(struct cdns3 *cdns) @@ -282,13 +273,17 @@ static int cdns3_idle_init(struct cdns3 *cdns) /** * cdns3_hw_role_switch - switch roles based on HW state - * @cdns3: controller + * @cdns: controller */ int cdns3_hw_role_switch(struct cdns3 *cdns) { enum usb_role real_role, current_role; int ret = 0; + /* Depends on role switch class */ + if (cdns->role_sw) + return 0; + pm_runtime_get_sync(cdns->dev); current_role = cdns->role; @@ -320,7 +315,7 @@ exit: /** * cdsn3_role_get - get current role of controller. * - * @dev: Pointer to device structure + * @sw: pointer to USB role switch structure * * Returns role */ @@ -334,8 +329,8 @@ static enum usb_role cdns3_role_get(struct usb_role_switch *sw) /** * cdns3_role_set - set current role of controller. * - * @dev: pointer to device object - * @role - the previous role + * @sw: pointer to USB role switch structure + * @role: the previous role * Handles below events: * - Role switch for dual-role devices * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices @@ -356,7 +351,6 @@ static int cdns3_role_set(struct usb_role_switch *sw, enum usb_role role) case USB_ROLE_HOST: break; default: - ret = -EPERM; goto pm_put; } } @@ -367,23 +361,64 @@ static int cdns3_role_set(struct usb_role_switch *sw, enum usb_role role) case USB_ROLE_DEVICE: break; default: - ret = -EPERM; goto pm_put; } } cdns3_role_stop(cdns); ret = cdns3_role_start(cdns, role); - if (ret) { + if (ret) dev_err(cdns->dev, "set role %d has failed\n", role); - ret = -EPERM; - } pm_put: pm_runtime_put_sync(cdns->dev); return ret; } +static int set_phy_power_on(struct cdns3 *cdns) +{ + int ret; + + ret = phy_power_on(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb3_phy); + if (ret) + phy_power_off(cdns->usb2_phy); + + return ret; +} + +static void set_phy_power_off(struct cdns3 *cdns) +{ + phy_power_off(cdns->usb3_phy); + phy_power_off(cdns->usb2_phy); +} + +/** + * cdns3_wakeup_irq - interrupt handler for wakeup events + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_wakeup_irq(int irq, void *data) +{ + struct cdns3 *cdns = data; + + if (cdns->in_lpm) { + disable_irq_nosync(irq); + cdns->wakeup_pending = true; + if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev) + pm_request_resume(&cdns->host_dev->dev); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + /** * cdns3_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -392,7 +427,6 @@ pm_put: */ static int cdns3_probe(struct platform_device *pdev) { - struct usb_role_switch_desc sw_desc = { }; struct device *dev = &pdev->dev; struct resource *res; struct cdns3 *cdns; @@ -402,7 +436,7 @@ static int cdns3_probe(struct platform_device *pdev) ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (ret) { dev_err(dev, "error setting dma mask: %d\n", ret); - return -ENODEV; + return ret; } cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); @@ -410,6 +444,7 @@ static int cdns3_probe(struct platform_device *pdev) return -ENOMEM; cdns->dev = dev; + cdns->pdata = dev_get_platdata(dev); platform_set_drvdata(pdev, cdns); @@ -430,35 +465,39 @@ static int cdns3_probe(struct platform_device *pdev) cdns->xhci_res[1] = *res; cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); - if (cdns->dev_irq == -EPROBE_DEFER) - return cdns->dev_irq; - if (cdns->dev_irq < 0) - dev_err(dev, "couldn't get peripheral irq\n"); + return cdns->dev_irq; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dev"); - regs = devm_ioremap_resource(dev, res); + regs = devm_platform_ioremap_resource_byname(pdev, "dev"); if (IS_ERR(regs)) return PTR_ERR(regs); cdns->dev_regs = regs; cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq == -EPROBE_DEFER) + if (cdns->otg_irq < 0) return cdns->otg_irq; - if (cdns->otg_irq < 0) { - dev_err(dev, "couldn't get otg irq\n"); - return cdns->otg_irq; - } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); if (!res) { dev_err(dev, "couldn't get otg resource\n"); return -ENXIO; } + cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); + cdns->otg_res = *res; + cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (cdns->wakeup_irq == -EPROBE_DEFER) + return cdns->wakeup_irq; + else if (cdns->wakeup_irq == 0) + return -EINVAL; + + if (cdns->wakeup_irq < 0) { + dev_dbg(dev, "couldn't get wakeup irq\n"); + cdns->wakeup_irq = 0x0; + } + mutex_init(&cdns->mutex); cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); @@ -477,39 +516,53 @@ static int cdns3_probe(struct platform_device *pdev) if (ret) goto err1; - ret = phy_power_on(cdns->usb2_phy); + ret = set_phy_power_on(cdns); if (ret) goto err2; - ret = phy_power_on(cdns->usb3_phy); - if (ret) - goto err3; + if (device_property_read_bool(dev, "usb-role-switch")) { + struct usb_role_switch_desc sw_desc = { }; - sw_desc.set = cdns3_role_set; - sw_desc.get = cdns3_role_get; - sw_desc.allow_userspace_control = true; - sw_desc.driver_data = cdns; - if (device_property_read_bool(dev, "usb-role-switch")) + sw_desc.set = cdns3_role_set; + sw_desc.get = cdns3_role_get; + sw_desc.allow_userspace_control = true; + sw_desc.driver_data = cdns; sw_desc.fwnode = dev->fwnode; - cdns->role_sw = usb_role_switch_register(dev, &sw_desc); - if (IS_ERR(cdns->role_sw)) { - ret = PTR_ERR(cdns->role_sw); - dev_warn(dev, "Unable to register Role Switch\n"); - goto err4; + cdns->role_sw = usb_role_switch_register(dev, &sw_desc); + if (IS_ERR(cdns->role_sw)) { + ret = PTR_ERR(cdns->role_sw); + dev_warn(dev, "Unable to register Role Switch\n"); + goto err3; + } + } + + if (cdns->wakeup_irq) { + ret = devm_request_irq(cdns->dev, cdns->wakeup_irq, + cdns3_wakeup_irq, + IRQF_SHARED, + dev_name(cdns->dev), cdns); + + if (ret) { + dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); + goto err4; + } } ret = cdns3_drd_init(cdns); if (ret) - goto err5; + goto err4; ret = cdns3_core_init_role(cdns); if (ret) - goto err5; + goto err4; + spin_lock_init(&cdns->lock); device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); + if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) + pm_runtime_forbid(dev); /* * The controller needs less time between bus and controller suspend, @@ -522,14 +575,12 @@ static int cdns3_probe(struct platform_device *pdev) dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); return 0; -err5: - cdns3_drd_exit(cdns); - usb_role_switch_unregister(cdns->role_sw); err4: - phy_power_off(cdns->usb3_phy); - + cdns3_drd_exit(cdns); + if (cdns->role_sw) + usb_role_switch_unregister(cdns->role_sw); err3: - phy_power_off(cdns->usb2_phy); + set_phy_power_off(cdns); err2: phy_exit(cdns->usb3_phy); err1: @@ -553,59 +604,128 @@ static int cdns3_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); cdns3_exit_roles(cdns); usb_role_switch_unregister(cdns->role_sw); - phy_power_off(cdns->usb2_phy); - phy_power_off(cdns->usb3_phy); + set_phy_power_off(cdns); phy_exit(cdns->usb2_phy); phy_exit(cdns->usb3_phy); return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM -static int cdns3_suspend(struct device *dev) +static int cdns3_set_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret = 0; + + if (cdns->pdata && cdns->pdata->platform_suspend) + ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); + + return ret; +} + +static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) { struct cdns3 *cdns = dev_get_drvdata(dev); + bool wakeup; unsigned long flags; - if (cdns->role == USB_ROLE_HOST) + if (cdns->in_lpm) return 0; - if (pm_runtime_status_suspended(dev)) - pm_runtime_resume(dev); + if (PMSG_IS_AUTO(msg)) + wakeup = true; + else + wakeup = device_may_wakeup(dev); - if (cdns->roles[cdns->role]->suspend) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); - cdns->roles[cdns->role]->suspend(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); - } + cdns3_set_platform_suspend(cdns->dev, true, wakeup); + set_phy_power_off(cdns); + spin_lock_irqsave(&cdns->lock, flags); + cdns->in_lpm = true; + spin_unlock_irqrestore(&cdns->lock, flags); + dev_dbg(cdns->dev, "%s ends\n", __func__); return 0; } -static int cdns3_resume(struct device *dev) +static int cdns3_controller_resume(struct device *dev, pm_message_t msg) { struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; unsigned long flags; - if (cdns->role == USB_ROLE_HOST) + if (!cdns->in_lpm) return 0; - if (cdns->roles[cdns->role]->resume) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); + ret = set_phy_power_on(cdns); + if (ret) + return ret; + + cdns3_set_platform_suspend(cdns->dev, false, false); + + spin_lock_irqsave(&cdns->lock, flags); + if (cdns->roles[cdns->role]->resume && !PMSG_IS_AUTO(msg)) cdns->roles[cdns->role]->resume(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); + + cdns->in_lpm = false; + spin_unlock_irqrestore(&cdns->lock, flags); + if (cdns->wakeup_pending) { + cdns->wakeup_pending = false; + enable_irq(cdns->wakeup_irq); } + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return ret; +} + +static int cdns3_runtime_suspend(struct device *dev) +{ + return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); +} + +static int cdns3_runtime_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); +} +#ifdef CONFIG_PM_SLEEP + +static int cdns3_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + unsigned long flags; + + if (pm_runtime_status_suspended(dev)) + pm_runtime_resume(dev); + + if (cdns->roles[cdns->role]->suspend) { + spin_lock_irqsave(&cdns->lock, flags); + cdns->roles[cdns->role]->suspend(cdns, false); + spin_unlock_irqrestore(&cdns->lock, flags); + } + + return cdns3_controller_suspend(dev, PMSG_SUSPEND); +} + +static int cdns3_resume(struct device *dev) +{ + int ret; + + ret = cdns3_controller_resume(dev, PMSG_RESUME); + if (ret) + return ret; pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); - return 0; + return ret; } -#endif +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ static const struct dev_pm_ops cdns3_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume) + SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL) }; #ifdef CONFIG_OF |