From 0cbdc8c1daf2bd9a42e653f4d85ad70c549b7265 Mon Sep 17 00:00:00 2001 From: William Wu Date: Wed, 15 Feb 2017 21:24:23 +0800 Subject: [PATCH] usb: dwc3: add a new glue layer for rockchip SoCs with INNO PHY This patch add a rockchip specific glue layer to support USB 3.0 HOST only mode for rockchip USB 3.0 core wrapper consisting of USB 3.0 controller IP from Synopsys and USB 3.0 PHY IP from Innosilicon. With this patch, we can support for XHCI integrated in DWC3 IP on rockchip platforms. Because some INNO USB 3.0 PHY can't detect disconnection by PHY IP, and cause USB3 device unrecognized when replugged again. So we depend on the HUB core driver to detect the disconnection, and send notifier to DWC3 driver from USB PHY driver, then we can do phy reset and remove/add hcd to reinit HCD. Change-Id: I6972c6f9f8f7160dbd74ad531b843a65ccec5dc0 Signed-off-by: William Wu --- drivers/usb/dwc3/Kconfig | 9 + drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-rockchip-inno.c | 286 ++++++++++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-rockchip-inno.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 3d5ec30b4843..3fbf82d494f9 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -105,6 +105,15 @@ config USB_DWC3_ROCKCHIP Support of USB2/3 functionality in Rockchip platforms. say 'Y' or 'M' if you have one such device. +config USB_DWC3_ROCKCHIP_INNO + tristate "Rockchip Platforms with INNO PHY" + depends on OF && COMMON_CLK && ARCH_ROCKCHIP + default USB_DWC3 + help + Support of USB2/3 functionality in Rockchip platforms + with INNO USB 3.0 PHY IP inside. + say 'Y' or 'M' if you have one such device. + config USB_DWC3_ST tristate "STMicroelectronics Platforms" depends on ARCH_STI && OF diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 86fc4fd041e4..f6953419dd8a 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -39,4 +39,5 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ROCKCHIP) += dwc3-rockchip.o +obj-$(CONFIG_USB_DWC3_ROCKCHIP_INNO) += dwc3-rockchip-inno.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o diff --git a/drivers/usb/dwc3/dwc3-rockchip-inno.c b/drivers/usb/dwc3/dwc3-rockchip-inno.c new file mode 100644 index 000000000000..5c907b97d6a6 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-rockchip-inno.c @@ -0,0 +1,286 @@ +/** + * dwc3-rockchip-inno.c - Rockchip DWC3 Specific Glue layer with INNO PHY + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * + * Authors: William Wu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "../host/xhci.h" + +struct dwc3_rockchip { + struct device *dev; + struct clk **clks; + int num_clocks; + struct dwc3 *dwc; + struct usb_phy *phy; + struct notifier_block u3phy_nb; + struct work_struct u3_work; + struct mutex lock; +}; + +static int u3phy_disconnect_det_notifier(struct notifier_block *nb, + unsigned long event, void *p) +{ + struct dwc3_rockchip *rockchip = + container_of(nb, struct dwc3_rockchip, u3phy_nb); + + schedule_work(&rockchip->u3_work); + + return NOTIFY_DONE; +} + +static void u3phy_disconnect_det_work(struct work_struct *work) +{ + struct dwc3_rockchip *rockchip = + container_of(work, struct dwc3_rockchip, u3_work); + struct usb_hcd *hcd = dev_get_drvdata(&rockchip->dwc->xhci->dev); + struct usb_hcd *shared_hcd = hcd->shared_hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 count = 0; + + mutex_lock(&rockchip->lock); + + if (hcd->state != HC_STATE_HALT) { + usb_remove_hcd(shared_hcd); + usb_remove_hcd(hcd); + } + + if (rockchip->phy) + usb_phy_shutdown(rockchip->phy); + + while (hcd->state != HC_STATE_HALT) { + if (++count > 1000) { + dev_err(rockchip->dev, + "wait for HCD remove 1s timeout!\n"); + break; + } + usleep_range(1000, 1100); + } + + if (hcd->state == HC_STATE_HALT) { + xhci->shared_hcd = shared_hcd; + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + usb_add_hcd(shared_hcd, hcd->irq, IRQF_SHARED); + } + + if (rockchip->phy) + usb_phy_init(rockchip->phy); + + mutex_unlock(&rockchip->lock); +} + +static int dwc3_rockchip_probe(struct platform_device *pdev) +{ + struct dwc3_rockchip *rockchip; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node, *child; + struct platform_device *child_pdev; + + unsigned int count; + int ret; + int i; + + rockchip = devm_kzalloc(dev, sizeof(*rockchip), GFP_KERNEL); + if (!rockchip) + return -ENOMEM; + + child = of_get_child_by_name(np, "dwc3"); + if (!child) { + dev_err(dev, "failed to find dwc3 core node\n"); + return -ENODEV; + } + + count = of_clk_get_parent_count(np); + if (!count) + return -ENOENT; + + rockchip->num_clocks = count; + + rockchip->clks = devm_kcalloc(dev, rockchip->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!rockchip->clks) + return -ENOMEM; + + platform_set_drvdata(pdev, rockchip); + rockchip->dev = dev; + + for (i = 0; i < rockchip->num_clocks; i++) { + struct clk *clk; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto err0; + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + clk_put(clk); + goto err0; + } + + rockchip->clks[i] = clk; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "get_sync failed with err %d\n", ret); + goto err1; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + goto err1; + + child_pdev = of_find_device_by_node(child); + if (!child_pdev) { + dev_err(dev, "failed to find dwc3 core device\n"); + ret = -ENODEV; + goto err2; + } + + rockchip->dwc = platform_get_drvdata(child_pdev); + if (!rockchip->dwc || !rockchip->dwc->xhci) { + dev_dbg(dev, "failed to get drvdata dwc3\n"); + ret = -EPROBE_DEFER; + goto err2; + } + + mutex_init(&rockchip->lock); + + rockchip->phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3); + if (rockchip->phy) { + INIT_WORK(&rockchip->u3_work, u3phy_disconnect_det_work); + rockchip->u3phy_nb.notifier_call = + u3phy_disconnect_det_notifier; + usb_register_notifier(rockchip->phy, &rockchip->u3phy_nb); + } + + return 0; + +err2: + of_platform_depopulate(dev); +err1: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +err0: + for (i = 0; i < rockchip->num_clocks && rockchip->clks[i]; i++) { + if (!pm_runtime_status_suspended(dev)) + clk_disable(rockchip->clks[i]); + clk_unprepare(rockchip->clks[i]); + clk_put(rockchip->clks[i]); + } + + return ret; +} + +static int dwc3_rockchip_remove(struct platform_device *pdev) +{ + struct dwc3_rockchip *rockchip = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + of_platform_depopulate(dev); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + for (i = 0; i < rockchip->num_clocks; i++) { + if (!pm_runtime_status_suspended(dev)) + clk_disable(rockchip->clks[i]); + clk_unprepare(rockchip->clks[i]); + clk_put(rockchip->clks[i]); + } + + return 0; +} + +#ifdef CONFIG_PM +static int dwc3_rockchip_runtime_suspend(struct device *dev) +{ + struct dwc3_rockchip *rockchip = dev_get_drvdata(dev); + int i; + + for (i = 0; i < rockchip->num_clocks; i++) + clk_disable(rockchip->clks[i]); + + return 0; +} + +static int dwc3_rockchip_runtime_resume(struct device *dev) +{ + struct dwc3_rockchip *rockchip = dev_get_drvdata(dev); + int ret; + int i; + + for (i = 0; i < rockchip->num_clocks; i++) { + ret = clk_enable(rockchip->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable(rockchip->clks[i]); + return ret; + } + } + + return 0; +} + +static const struct dev_pm_ops dwc3_rockchip_dev_pm_ops = { + SET_RUNTIME_PM_OPS(dwc3_rockchip_runtime_suspend, + dwc3_rockchip_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (&dwc3_rockchip_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct of_device_id rockchip_dwc3_match[] = { + { .compatible = "rockchip,rk3328-dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_dwc3_match); + +static struct platform_driver dwc3_rockchip_driver = { + .probe = dwc3_rockchip_probe, + .remove = dwc3_rockchip_remove, + .driver = { + .name = "rockchip-inno-dwc3", + .pm = DEV_PM_OPS, + .of_match_table = rockchip_dwc3_match, + }, +}; + +module_platform_driver(dwc3_rockchip_driver); + +MODULE_ALIAS("platform:rockchip-inno-dwc3"); +MODULE_AUTHOR("William Wu "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 rockchip-inno Glue Layer"); -- 2.34.1