diff options
author | Ivan T. Ivanov <ivan.ivanov@linaro.org> | 2015-09-07 14:45:25 +0300 |
---|---|---|
committer | Peter Chen <peter.chen@freescale.com> | 2015-10-22 04:24:25 +0300 |
commit | 3ecb3e09b042e70799ff3a1ff464a5ecaa7547d9 (patch) | |
tree | fea2789cc3e67fac569c9a47d7035b1e33a032d2 /drivers/usb | |
parent | ffa2366666f06ce1df3296d106d90e0c2e0cd6b7 (diff) | |
download | linux-3ecb3e09b042e70799ff3a1ff464a5ecaa7547d9.tar.xz |
usb: chipidea: Use extcon framework for VBUS and ID detect
On recent Qualcomm platforms VBUS and ID lines are not routed to
USB PHY LINK controller. Use extcon framework to receive connect
and disconnect ID and VBUS notification.
Signed-off-by: Ivan T. Ivanov <ivan.ivanov@linaro.org>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/chipidea/Kconfig | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 125 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 39 |
3 files changed, 164 insertions, 1 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 5ce3f1d6a6ed..5619b8ca3bf3 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,6 +1,7 @@ config USB_CHIPIDEA tristate "ChipIdea Highspeed Dual Role Controller" depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA + select EXTCON help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3feebf7f31f0..573c2876b263 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -47,6 +47,7 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/dma-mapping.h> +#include <linux/extcon.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/module.h> @@ -602,9 +603,45 @@ 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) +{ + struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = vbus->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; + + ci_irq(ci->irq, ci); + return NOTIFY_DONE; +} + static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { + struct extcon_dev *ext_vbus, *ext_id; + struct ci_hdrc_cable *cable; int ret; if (!platdata->phy_mode) @@ -695,9 +732,91 @@ static int ci_get_platdata(struct device *dev, platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST; } + ext_id = ERR_PTR(-ENODEV); + ext_vbus = ERR_PTR(-ENODEV); + if (of_property_read_bool(dev->of_node, "extcon")) { + /* Each one of them is not mandatory */ + ext_vbus = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) + return PTR_ERR(ext_vbus); + + ext_id = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) + return PTR_ERR(ext_id); + } + + cable = &platdata->vbus_extcon; + cable->nb.notifier_call = ci_vbus_notifier; + cable->edev = ext_vbus; + + if (!IS_ERR(ext_vbus)) { + ret = extcon_get_cable_state_(cable->edev, EXTCON_USB); + if (ret) + cable->state = true; + else + cable->state = false; + } + + cable = &platdata->id_extcon; + cable->nb.notifier_call = ci_id_notifier; + cable->edev = ext_id; + + if (!IS_ERR(ext_id)) { + ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST); + if (ret) + cable->state = false; + else + cable->state = true; + } return 0; } +static int ci_extcon_register(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *id, *vbus; + int ret; + + 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 (ret < 0) { + dev_err(ci->dev, "register ID failed\n"); + return ret; + } + } + + vbus = &ci->platdata->vbus_extcon; + vbus->ci = ci; + if (!IS_ERR(vbus->edev)) { + ret = extcon_register_notifier(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; + } + } + + 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, @@ -921,6 +1040,10 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + ret = ci_extcon_register(ci); + if (ret) + goto stop; + if (ci->supports_runtime_pm) { pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -938,6 +1061,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (!ret) return 0; + ci_extcon_unregister(ci); stop: ci_role_destroy(ci); deinit_phy: @@ -957,6 +1081,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); + ci_extcon_unregister(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index ad6c87a4653c..ab4bd0c2d4ef 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -30,7 +30,44 @@ */ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) { - return hw_read(ci, OP_OTGSC, mask); + struct ci_hdrc_cable *cable; + u32 val = hw_read(ci, OP_OTGSC, mask); + + /* + * If using extcon framework for VBUS and/or ID signal + * detection overwrite OTGSC register value + */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_BSVIS; + else + val &= ~OTGSC_BSVIS; + + cable->changed = false; + + if (cable->state) + val |= OTGSC_BSV; + else + val &= ~OTGSC_BSV; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_IDIS; + else + val &= ~OTGSC_IDIS; + + cable->changed = false; + + if (cable->state) + val |= OTGSC_ID; + else + val &= ~OTGSC_ID; + } + + return val; } /** |