summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorIvan T. Ivanov <ivan.ivanov@linaro.org>2015-09-07 14:45:25 +0300
committerPeter Chen <peter.chen@freescale.com>2015-10-22 09:24:25 +0800
commit3ecb3e09b042e70799ff3a1ff464a5ecaa7547d9 (patch)
treefea2789cc3e67fac569c9a47d7035b1e33a032d2 /drivers/usb
parentffa2366666f06ce1df3296d106d90e0c2e0cd6b7 (diff)
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/Kconfig1
-rw-r--r--drivers/usb/chipidea/core.c125
-rw-r--r--drivers/usb/chipidea/otg.c39
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;
}
/**