diff options
Diffstat (limited to 'drivers/usb/chipidea/core.c')
-rw-r--r-- | drivers/usb/chipidea/core.c | 328 |
1 files changed, 226 insertions, 102 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index e96e3a5808b3..068d05dea4f7 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -461,7 +461,7 @@ void ci_platform_configure(struct ci_hdrc *ci) * * This function returns an error code */ -static int hw_controller_reset(struct ci_hdrc *ci) +int hw_controller_reset(struct ci_hdrc *ci) { int count = 0; @@ -555,7 +555,7 @@ static irqreturn_t ci_irq(int irq, void *data) * and disconnection events. */ if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { - ci->b_sess_valid_event = true; + ci->vbus_glitch_check_event = true; /* Clear BSV irq */ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); ci_otg_queue_work(ci); @@ -569,35 +569,14 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } -static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) +static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) { - struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = vbus->ci; + struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = cbl->ci; - if (event) - vbus->state = true; - else - vbus->state = false; - - vbus->changed = true; - - ci_irq(ci->irq, ci); - return NOTIFY_DONE; -} - -static int ci_id_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) -{ - struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = id->ci; - - if (event) - id->state = false; - else - id->state = true; - - id->changed = true; + cbl->connected = event; + cbl->changed = true; ci_irq(ci->irq, ci); return NOTIFY_DONE; @@ -706,27 +685,27 @@ static int ci_get_platdata(struct device *dev, } cable = &platdata->vbus_extcon; - cable->nb.notifier_call = ci_vbus_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_vbus; if (!IS_ERR(ext_vbus)) { - ret = extcon_get_cable_state_(cable->edev, EXTCON_USB); + ret = extcon_get_state(cable->edev, EXTCON_USB); if (ret) - cable->state = true; + cable->connected = true; else - cable->state = false; + cable->connected = false; } cable = &platdata->id_extcon; - cable->nb.notifier_call = ci_id_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_id; if (!IS_ERR(ext_id)) { - ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST); + ret = extcon_get_state(cable->edev, EXTCON_USB_HOST); if (ret) - cable->state = false; + cable->connected = true; else - cable->state = true; + cable->connected = false; } return 0; } @@ -738,9 +717,9 @@ static int ci_extcon_register(struct ci_hdrc *ci) id = &ci->platdata->id_extcon; id->ci = ci; - if (!IS_ERR(id->edev)) { - ret = extcon_register_notifier(id->edev, EXTCON_USB_HOST, - &id->nb); + if (!IS_ERR_OR_NULL(id->edev)) { + ret = devm_extcon_register_notifier(ci->dev, id->edev, + EXTCON_USB_HOST, &id->nb); if (ret < 0) { dev_err(ci->dev, "register ID failed\n"); return ret; @@ -749,12 +728,10 @@ static int ci_extcon_register(struct ci_hdrc *ci) vbus = &ci->platdata->vbus_extcon; vbus->ci = ci; - if (!IS_ERR(vbus->edev)) { - ret = extcon_register_notifier(vbus->edev, EXTCON_USB, - &vbus->nb); + if (!IS_ERR_OR_NULL(vbus->edev)) { + ret = devm_extcon_register_notifier(ci->dev, vbus->edev, + EXTCON_USB, &vbus->nb); if (ret < 0) { - extcon_unregister_notifier(id->edev, EXTCON_USB_HOST, - &id->nb); dev_err(ci->dev, "register VBUS failed\n"); return ret; } @@ -763,20 +740,6 @@ static int ci_extcon_register(struct ci_hdrc *ci) return 0; } -static void ci_extcon_unregister(struct ci_hdrc *ci) -{ - struct ci_hdrc_cable *cable; - - cable = &ci->platdata->id_extcon; - if (!IS_ERR(cable->edev)) - extcon_unregister_notifier(cable->edev, EXTCON_USB_HOST, - &cable->nb); - - cable = &ci->platdata->vbus_extcon; - if (!IS_ERR(cable->edev)) - extcon_unregister_notifier(cable->edev, EXTCON_USB, &cable->nb); -} - static DEFINE_IDA(ci_ida); struct platform_device *ci_hdrc_add_device(struct device *dev, @@ -801,9 +764,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, } pdev->dev.parent = dev; - pdev->dev.dma_mask = dev->dma_mask; - pdev->dev.dma_parms = dev->dma_parms; - dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask); ret = platform_device_add_resources(pdev, res, nres); if (ret) @@ -835,12 +795,39 @@ void ci_hdrc_remove_device(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(ci_hdrc_remove_device); +/** + * ci_hdrc_query_available_role: get runtime available operation mode + * + * The glue layer can get current operation mode (host/peripheral/otg) + * This function should be called after ci core device has created. + * + * @pdev: the platform device of ci core. + * + * Return USB_DR_MODE_XXX. + */ +enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev) +{ + struct ci_hdrc *ci = platform_get_drvdata(pdev); + + if (!ci) + return USB_DR_MODE_UNKNOWN; + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_OTG; + else if (ci->roles[CI_ROLE_HOST]) + return USB_DR_MODE_HOST; + else if (ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_PERIPHERAL; + else + return USB_DR_MODE_UNKNOWN; +} +EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role); + static inline void ci_role_destroy(struct ci_hdrc *ci) { - ci_hdrc_gadget_destroy(ci); - ci_hdrc_host_destroy(ci); if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); + ci_hdrc_gadget_destroy(ci); + ci_hdrc_host_destroy(ci); } static void ci_get_otg_capable(struct ci_hdrc *ci) @@ -859,6 +846,104 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) } } +static enum ci_role ci_get_role(struct ci_hdrc *ci) +{ + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + if (ci->is_otg) { + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + return ci_otg_role(ci); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + return CI_ROLE_GADGET; + } + } else { + return ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } +} + +static void ci_start_new_role(struct ci_hdrc *ci) +{ + enum ci_role role = ci_get_role(ci); + + if (ci->role != role) { + ci_handle_id_switch(ci); + } else if (role == CI_ROLE_GADGET) { + if (ci->vbus_active) + usb_gadget_vbus_disconnect(&ci->gadget); + ci_handle_vbus_connected(ci); + } +} + +static void ci_power_lost_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, + power_lost_work); + + pm_runtime_get_sync(ci->dev); + if (!ci_otg_is_fsm_mode(ci)) + ci_start_new_role(ci); + else + ci_hdrc_otg_fsm_restart(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); +} + +static ssize_t ci_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", ci_role(ci)->name); +} + +static ssize_t ci_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + enum ci_role role; + int ret; + + if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) { + dev_warn(dev, "Current configuration is not dual-role, quit\n"); + return -EPERM; + } + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (!strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; + + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; + + pm_runtime_get_sync(dev); + disable_irq(ci->irq); + ci_role_stop(ci); + ret = ci_role_start(ci, role); + if (!ret && ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + enable_irq(ci->irq); + pm_runtime_put_sync(dev); + + return (ret == 0) ? n : ret; +} +static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store); + +static struct attribute *ci_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static struct attribute_group ci_attr_group = { + .attrs = ci_attrs, +}; + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -978,30 +1063,12 @@ static int ci_hdrc_probe(struct platform_device *pdev) } } - if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { - if (ci->is_otg) { - ci->role = ci_otg_role(ci); - /* Enable ID change irq */ - hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); - } else { - /* - * If the controller is not OTG capable, but support - * role switch, the defalt role is gadget, and the - * user can switch it through debugfs. - */ - ci->role = CI_ROLE_GADGET; - } - } else { - ci->role = ci->roles[CI_ROLE_HOST] - ? CI_ROLE_HOST - : CI_ROLE_GADGET; - } + ci->role = ci_get_role(ci); + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_connected(ci); if (!ci_otg_is_fsm_mode(ci)) { - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); - ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -1033,11 +1100,22 @@ static int ci_hdrc_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); + /* Init workqueue for controller power lost handling */ + INIT_WORK(&ci->power_lost_work, ci_power_lost_work); + mutex_init(&ci->mutex); + ret = dbg_create_files(ci); - if (!ret) - return 0; + if (ret) + goto stop; + + ret = sysfs_create_group(&dev->kobj, &ci_attr_group); + if (ret) + goto remove_debug; - ci_extcon_unregister(ci); + return 0; + +remove_debug: + dbg_remove_files(ci); stop: if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); @@ -1062,7 +1140,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); - ci_extcon_unregister(ci); + sysfs_remove_group(&ci->dev->kobj, &ci_attr_group); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); @@ -1088,13 +1166,10 @@ static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci) { if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) { - if (!hw_read_otgsc(ci, OTGSC_ID)) { - ci->fsm.a_srp_det = 1; - ci->fsm.a_bus_drop = 0; - } else { + if (!hw_read_otgsc(ci, OTGSC_ID)) + otg_add_timer(&ci->fsm, A_DP_END); + else ci->fsm.id = 1; - } - ci_otg_queue_work(ci); } } @@ -1110,16 +1185,37 @@ static void ci_controller_suspend(struct ci_hdrc *ci) enable_irq(ci->irq); } +/* + * Handle the wakeup interrupt triggered by extcon connector + * We need to call ci_irq again for extcon since the first + * interrupt (wakeup int) only let the controller be out of + * low power mode, but not handle any interrupts. + */ +static void ci_extcon_wakeup_int(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *cable_id, *cable_vbus; + u32 otgsc = hw_read_otgsc(ci, ~0); + + cable_id = &ci->platdata->id_extcon; + cable_vbus = &ci->platdata->vbus_extcon; + + if (!IS_ERR(cable_id->edev) && ci->is_otg && + (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) + ci_irq(ci->irq, ci); + + if (!IS_ERR(cable_vbus->edev) && ci->is_otg && + (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) + ci_irq(ci->irq, ci); +} + static int ci_controller_resume(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); dev_dbg(dev, "at %s\n", __func__); - if (!ci->in_lpm) { - WARN_ON(1); + if (!ci->in_lpm) return 0; - } ci_hdrc_enter_lpm(ci, false); if (ci->usb_phy) { @@ -1136,6 +1232,7 @@ static int ci_controller_resume(struct device *dev) enable_irq(ci->irq); if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_wakeup_by_srp(ci); + ci_extcon_wakeup_int(ci); } return 0; @@ -1162,6 +1259,10 @@ static int ci_suspend(struct device *dev) return 0; } + /* Extra routine per role before system suspend */ + if (ci->role != CI_ROLE_END && ci_role(ci)->suspend) + ci_role(ci)->suspend(ci); + if (device_may_wakeup(dev)) { if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_suspend_for_srp(ci); @@ -1178,8 +1279,18 @@ static int ci_suspend(struct device *dev) static int ci_resume(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); + bool power_lost = false; + u32 sample_reg_val; int ret; + /* Check if controller resume from power lost */ + sample_reg_val = hw_read(ci, OP_ENDPTLISTADDR, ~0); + if (sample_reg_val == 0) + power_lost = true; + else if (sample_reg_val == 0xFFFFFFFF) + /* Restore value 0 if it was set for power lost check */ + hw_write(ci, OP_ENDPTLISTADDR, ~0, 0); + if (device_may_wakeup(dev)) disable_irq_wake(ci->irq); @@ -1187,6 +1298,21 @@ static int ci_resume(struct device *dev) if (ret) return ret; + if (power_lost) { + /* shutdown and re-init for phy */ + ci_usb_phy_exit(ci); + ci_usb_phy_init(ci); + } + + /* Extra routine per role after system resume */ + if (ci->role != CI_ROLE_END && ci_role(ci)->resume) + ci_role(ci)->resume(ci, power_lost); + + if (power_lost) { + disable_irq_nosync(ci->irq); + schedule_work(&ci->power_lost_work); + } + if (ci->supports_runtime_pm) { pm_runtime_disable(dev); pm_runtime_set_active(dev); @@ -1203,10 +1329,8 @@ static int ci_runtime_suspend(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - if (ci->in_lpm) { - WARN_ON(1); + if (ci->in_lpm) return 0; - } if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_suspend_for_srp(ci); |