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/Kconfig | 1 + drivers/usb/chipidea/core.c | 125 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/chipidea/otg.c | 39 +++++++++++++- 3 files changed, 164 insertions(+), 1 deletion(-) (limited to 'drivers/usb/chipidea') 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 #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); 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; } /** -- 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') 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 From 5cb377c52fe464c5cdc722944bc0c62a8cb10312 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 9 Sep 2015 16:33:02 +0800 Subject: usb: chipidea: imx: add usb support for imx7d Add imx7d usb support. Signed-off-by: Peter Chen Signed-off-by: Li Jun --- drivers/usb/chipidea/ci_hdrc_imx.c | 5 +++ drivers/usb/chipidea/usbmisc_imx.c | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) (limited to 'drivers/usb/chipidea') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index dcc50c878159..c7f127fb6bea 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -56,12 +56,17 @@ static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { CI_HDRC_DISABLE_HOST_STREAMING, }; +static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, +}; + static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, { .compatible = "fsl,imx27-usb", .data = &imx27_usb_data}, { .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data}, { .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data}, { .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data}, + { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 5ddab30ee240..f3a4753e0e18 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -72,6 +72,14 @@ #define VF610_OVER_CUR_DIS BIT(7) +#define MX7D_USBNC_USB_CTRL2 0x4 +#define MX7D_USB_VBUS_WAKEUP_SOURCE_MASK 0x3 +#define MX7D_USB_VBUS_WAKEUP_SOURCE(v) (v << 0) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_VBUS MX7D_USB_VBUS_WAKEUP_SOURCE(0) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_AVALID MX7D_USB_VBUS_WAKEUP_SOURCE(1) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID MX7D_USB_VBUS_WAKEUP_SOURCE(2) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END MX7D_USB_VBUS_WAKEUP_SOURCE(3) + struct usbmisc_ops { /* It's called once when probe a usb device */ int (*init)(struct imx_usbmisc_data *data); @@ -324,6 +332,55 @@ static int usbmisc_vf610_init(struct imx_usbmisc_data *data) return 0; } +static int usbmisc_imx7d_set_wakeup + (struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE | + MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP); + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base); + if (enabled) { + writel(val | wakeup_setting, usbmisc->base); + } else { + if (val & MX6_BM_WAKEUP_INTR) + dev_dbg(data->dev, "wakeup int\n"); + writel(val & ~wakeup_setting, usbmisc->base); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + if (data->index >= 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->disable_oc) { + reg = readl(usbmisc->base); + writel(reg | MX6_BM_OVER_CUR_DIS, usbmisc->base); + } + + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; + writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usbmisc_imx7d_set_wakeup(data, false); + + return 0; +} + static const struct usbmisc_ops imx25_usbmisc_ops = { .init = usbmisc_imx25_init, .post = usbmisc_imx25_post, @@ -351,6 +408,11 @@ static const struct usbmisc_ops imx6sx_usbmisc_ops = { .init = usbmisc_imx6sx_init, }; +static const struct usbmisc_ops imx7d_usbmisc_ops = { + .init = usbmisc_imx7d_init, + .set_wakeup = usbmisc_imx7d_set_wakeup, +}; + int imx_usbmisc_init(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc; -- cgit v1.2.3 From 52fe568e5d717e4c21a29a2a05a27f3dacc431d5 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 16 Sep 2015 15:52:32 +0800 Subject: usb: chipidea: imx: add imx6ul usb support Add imx6ul usb support. Signed-off-by: Peter chen Signed-off-by: Li Jun Signed-off-by: Peter Chen --- drivers/usb/chipidea/ci_hdrc_imx.c | 6 ++++++ drivers/usb/chipidea/usbmisc_imx.c | 4 ++++ 2 files changed, 10 insertions(+) (limited to 'drivers/usb/chipidea') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index c7f127fb6bea..6ccbf60cdd5c 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -56,6 +56,11 @@ static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { CI_HDRC_DISABLE_HOST_STREAMING, }; +static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON, +}; + static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, }; @@ -66,6 +71,7 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data}, { .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data}, { .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data}, + { .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data}, { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, { /* sentinel */ } }; diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index f3a4753e0e18..fcea4eb36eee 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -488,6 +488,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = { .compatible = "fsl,imx6sx-usbmisc", .data = &imx6sx_usbmisc_ops, }, + { + .compatible = "fsl,imx6ul-usbmisc", + .data = &imx6sx_usbmisc_ops, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); -- cgit v1.2.3 From 46b95a1d66cc58163a6b3aa7c669ab6fec721404 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Sun, 4 Oct 2015 23:55:35 +0300 Subject: chipidea: ci_hdrc_pci: use PCI_VDEVICE() instead of PCI_DEVICE() Fix using the PCI_DEVICE() macro instead of less verbose PCI_VDEVICE(). Signed-off-by: Sergei Shtylyov Signed-off-by: Peter Chen --- drivers/usb/chipidea/ci_hdrc_pci.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb/chipidea') diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c index 773d150512fa..b59195edf636 100644 --- a/drivers/usb/chipidea/ci_hdrc_pci.c +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -142,16 +142,16 @@ static const struct pci_device_id ci_hdrc_pci_id_table[] = { .driver_data = (kernel_ulong_t)&pci_platdata, }, { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0811), + PCI_VDEVICE(INTEL, 0x0811), .driver_data = (kernel_ulong_t)&langwell_pci_platdata, }, { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), + PCI_VDEVICE(INTEL, 0x0829), .driver_data = (kernel_ulong_t)&penwell_pci_platdata, }, { /* Intel Clovertrail */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), + PCI_VDEVICE(INTEL, 0xe006), .driver_data = (kernel_ulong_t)&penwell_pci_platdata, }, { 0 } /* end: all zeroes */ -- cgit v1.2.3 From 851ce932242d5a79bef8fe625fce37cc2f27033e Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 16 Oct 2015 13:53:20 +0800 Subject: usb: chipidea: otg: don't wait vbus drops below BSV when starts host Some HW design may use ID pin state to control vbus for otg port, so before host role start, the vbus is already turned on, in this case, we do not need wait vbus dropping below BSV. Signed-off-by: Li Jun Signed-off-by: Peter Chen --- drivers/usb/chipidea/otg.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/usb/chipidea') diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index ab4bd0c2d4ef..45f86da1d6d3 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -114,9 +114,12 @@ static void ci_handle_id_switch(struct ci_hdrc *ci) ci_role(ci)->name, ci->roles[role]->name); ci_role_stop(ci); - /* wait vbus lower than OTGSC_BSV */ - hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0, - CI_VBUS_STABLE_TIMEOUT_MS); + + if (role == CI_ROLE_GADGET) + /* wait vbus lower than OTGSC_BSV */ + hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0, + CI_VBUS_STABLE_TIMEOUT_MS); + ci_role_start(ci, role); } } -- cgit v1.2.3