From 3ecb3e09b042e70799ff3a1ff464a5ecaa7547d9 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Mon, 7 Sep 2015 14:45:25 +0300 Subject: 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 Signed-off-by: Peter Chen --- drivers/usb/chipidea/core.c | 125 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) (limited to 'drivers/usb/chipidea/core.c') 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 #include #include +#include #include #include #include @@ -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); -- cgit v1.2.3 From 1fbf46280eb6866c762de5ec8ba35f09097b0d53 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 8 Sep 2015 22:18:14 -0300 Subject: usb: chipidea: Add support for 'phy-clkgate-delay-us' property Add support for the optional 'phy-clkgate-delay-us' property that is used to describe the delay time between putting PHY into low power mode and turning off the PHY clock. Signed-off-by: Li Jun Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen --- drivers/usb/chipidea/core.c | 7 +++++++ include/linux/usb/chipidea.h | 1 + 2 files changed, 8 insertions(+) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 573c2876b263..f4fd76ab3aef 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -688,6 +688,10 @@ static int ci_get_platdata(struct device *dev, if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + if (of_find_property(dev->of_node, "phy-clkgate-delay-us", NULL)) + of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", + &platdata->phy_clkgate_delay_us); + platdata->itc_setting = 1; if (of_find_property(dev->of_node, "itc-setting", NULL)) { ret = of_property_read_u32(dev->of_node, "itc-setting", @@ -1121,6 +1125,9 @@ static void ci_controller_suspend(struct ci_hdrc *ci) { disable_irq(ci->irq); ci_hdrc_enter_lpm(ci, true); + if (ci->platdata->phy_clkgate_delay_us) + usleep_range(ci->platdata->phy_clkgate_delay_us, + ci->platdata->phy_clkgate_delay_us + 50); usb_phy_set_suspend(ci->usb_phy, 1); ci->in_lpm = true; enable_irq(ci->irq); diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index c5cddc6901d0..5dd75fa47dd8 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -71,6 +71,7 @@ struct ci_hdrc_platform_data { /* VBUS and ID signal state tracking, using extcon framework */ struct ci_hdrc_cable vbus_extcon; struct ci_hdrc_cable id_extcon; + u32 phy_clkgate_delay_us; }; /* Default offset of capability registers */ -- cgit v1.2.3