diff options
author | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2015-09-20 00:24:45 +0200 |
---|---|---|
committer | Max Krummenacher <max.krummenacher@toradex.com> | 2015-12-26 14:48:13 +0100 |
commit | ef8aa6790398c976204c56b1536652ac8f510331 (patch) | |
tree | 40a9e67cb3a103ce7db765ab458cd54c3e27b6d4 /drivers/usb | |
parent | a9c74fc52003271bb6d575eb2cd30d8b6d2bbe29 (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.
(cherry picked from commit a257098741b441e6e84ffe97b7793c580642d502)
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/chipidea/ci.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 83 |
2 files changed, 87 insertions, 2 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 880283d31c5c..7fb3563df934 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 @@ -272,6 +273,11 @@ struct ci_hdrc { struct work_struct power_lost_work; bool adp_probe_event; bool adp_sense_event; + + 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; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index e5f315636569..6921ca5172ab 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -595,6 +595,17 @@ static irqreturn_t ci_irq(int irq, void *data) static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { + 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, "couldn't 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); @@ -762,7 +773,11 @@ static enum ci_role ci_get_role(struct ci_hdrc *ci) * role switch, the defalt role is gadget, and the * user can switch it through debugfs. */ - return CI_ROLE_GADGET; + if ((ci->platdata->edev) && (extcon_get_cable_state( + ci->platdata->edev, "USB-HOST") == true)) + return CI_ROLE_HOST; + else + return CI_ROLE_GADGET; } } else { return ci->roles[CI_ROLE_HOST] @@ -796,6 +811,42 @@ static void ci_power_lost_work(struct work_struct *work) enable_irq(ci->irq); } +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 int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -832,6 +883,21 @@ 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) { @@ -857,7 +923,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; @@ -911,6 +977,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); @@ -941,6 +1010,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); @@ -951,6 +1025,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); |