diff options
author | Sanchayan Maity <maitysanchayan@gmail.com> | 2016-09-15 18:45:44 +0530 |
---|---|---|
committer | Max Krummenacher <max.krummenacher@toradex.com> | 2016-09-29 15:22:37 +0200 |
commit | be04dc33f409996c37d8c69702748890b4968c67 (patch) | |
tree | d2b726e676981a73b9307c83970e99072240f8d8 | |
parent | e60b1f9218b5e8de486451a42e44b19b4a20969a (diff) |
usb: chipidea: Use extcon framework for ID and VBUS detection
Rather than relying on special USB/PHY pins/registers use the
generic extcon framework with its extcon-usb-gpio implementation
to detect ID and VBUS changes.
Signed-off-by: Sanchayan Maity <maitysanchayan@gmail.com>
Signed-off-by: Stefan Agner <stefan.agner@toradex.com>
-rw-r--r-- | drivers/usb/chipidea/ci.h | 7 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 104 | ||||
-rw-r--r-- | include/linux/usb/chipidea.h | 2 |
3 files changed, 108 insertions, 5 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 207e4a34be07..7aeb92288643 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -18,6 +18,7 @@ #include <linux/usb.h> #include <linux/usb/gadget.h> #include <linux/usb/otg-fsm.h> +#include <linux/extcon.h> /****************************************************************************** * DEFINE @@ -256,6 +257,12 @@ struct ci_hdrc { bool in_lpm; bool wakeup_int; enum ci_revision rev; + + struct extcon_specific_cable_nb extcon_vbus_dev; + struct extcon_specific_cable_nb extcon_id_dev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + /* register save area for suspend&resume */ u32 pm_command; u32 pm_status; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3b77b04896f8..30dbc9ce3bcf 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -606,6 +606,17 @@ static int ci_get_platdata(struct device *dev, { int ret; + if (of_property_read_bool(dev->of_node, "extcon")) { + platdata->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(platdata->edev)) { + if (PTR_ERR(platdata->edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(dev, "Could not get extcon device: %ld\n", + PTR_ERR(platdata->edev)); + } + platdata->flags |= CI_HDRC_DUAL_ROLE_NOT_OTG; + } + if (!platdata->phy_mode) platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); @@ -797,6 +808,42 @@ static inline void ci_role_destroy(struct ci_hdrc *ci) ci_hdrc_host_destroy(ci); } +static int ci_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, id_nb); + + pm_runtime_get_sync(ci->dev); + + ci_role_stop(ci); + + hw_wait_phy_stable(); + + if (ci_role_start(ci, event ? CI_ROLE_HOST : CI_ROLE_GADGET)) + dev_err(ci->dev, "Can't start %s role\n", ci_role(ci)->name); + + pm_runtime_put_sync(ci->dev); + + return NOTIFY_DONE; +} + +static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, vbus_nb); + + pm_runtime_get_sync(ci->dev); + + if (event) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); + + pm_runtime_put_sync(ci->dev); + + return NOTIFY_DONE; +} + static void ci_get_otg_capable(struct ci_hdrc *ci) { if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) @@ -897,6 +944,22 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } + if (ci->platdata->edev) { + ci->vbus_nb.notifier_call = ci_vbus_notifier; + ret = extcon_register_interest(&ci->extcon_vbus_dev, + ci->platdata->edev->name, "USB", + &ci->vbus_nb); + if (ret < 0) + dev_err(dev, "failed to register notifier for USB aka VBUS\n"); + + ci->id_nb.notifier_call = ci_id_notifier; + ret = extcon_register_interest(&ci->extcon_id_dev, + ci->platdata->edev->name, "USB-HOST", + &ci->id_nb); + if (ret < 0) + dev_err(dev, "failed to register notifier for USB-HOST aka ID\n"); + } + if (ci->platdata->phy) { ci->phy = ci->platdata->phy; } else if (ci->platdata->usb_phy) { @@ -922,7 +985,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) ret = ci_usb_phy_init(ci); if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); - return ret; + goto extcon_cleanup; } ci->hw_bank.phys = res->start; @@ -964,10 +1027,28 @@ static int ci_hdrc_probe(struct platform_device *pdev) } } - 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->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. + */ + if ((ci->platdata->edev) && (extcon_get_cable_state( + ci->platdata->edev, "USB-HOST") == true)) + ci->role = CI_ROLE_HOST; + else + ci->role = CI_ROLE_GADGET; + } + } else { + ci->role = ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } if (!ci_otg_is_fsm_mode(ci)) { ret = ci_role_start(ci, ci->role); @@ -976,6 +1057,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_role(ci)->name); goto stop; } + if ((ci->role == CI_ROLE_GADGET) && (ci->platdata->edev) && + (extcon_get_cable_state(ci->platdata->edev, "USB") == true)) + usb_gadget_vbus_connect(&ci->gadget); } platform_set_drvdata(pdev, ci); @@ -1006,6 +1090,11 @@ static int ci_hdrc_probe(struct platform_device *pdev) stop: ci_role_destroy(ci); +extcon_cleanup: + if (ci->extcon_vbus_dev.edev) + extcon_unregister_interest(&ci->extcon_vbus_dev); + if (ci->extcon_id_dev.edev) + extcon_unregister_interest(&ci->extcon_id_dev); deinit_phy: ci_usb_phy_exit(ci); @@ -1016,6 +1105,11 @@ static int ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); + if (ci->extcon_vbus_dev.edev) + extcon_unregister_interest(&ci->extcon_vbus_dev); + if (ci->extcon_id_dev.edev) + extcon_unregister_interest(&ci->extcon_id_dev); + if (ci->supports_runtime_pm) { pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index fc9163946d75..b02deb0a7d7f 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -52,6 +52,8 @@ struct ci_hdrc_platform_data { struct regulator *reg_vbus; struct usb_otg_caps ci_otg_caps; bool tpl_support; + struct extcon_dev *edev; + /* interrupt threshold setting */ u32 itc_setting; u32 ahb_burst_config; |