[PATCH 5/5] usb: add pxa1928 ehci support

Rob Herring
2015-05-13T22:49:48Z

Add platform driver for Marvell PXA1928 SOC. This is different from the
mv-ehci driver in that it uses the generic phy framework, uses DT, does
not use platform_data, is host only, and has a specific HSIC PHY and
controller initialization handshake.

Signed-off-by: Rob Herring <robh@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: linux-usb@vger.kernel.org
---
drivers/usb/host/Kconfig | 15 ++-
drivers/usb/host/Makefile | 1 +
drivers/usb/host/ehci-mv-of.c | 243 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 258 insertions(+), 1 deletion(-)
create mode 100644 drivers/usb/host/ehci-mv-of.c

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 197a6a3..4d8cfbc 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -251,6 +251,19 @@ config USB_EHCI_MV
Dova, Armada 370 and Armada XP. See "Support for Marvell EBU
on-chip EHCI USB controller" for those.

+config USB_EHCI_MV_OF
+ bool "EHCI OF support for Marvell PXA/MMP USB controller"
+ depends on (ARCH_PXA || ARCH_MMP)
+ select USB_EHCI_ROOT_HUB_TT
+ ---help---
+ Enables support for Marvell (including PXA and MMP series) on-chip
+ USB SPH and OTG controller. SPH is a single port host, and it can
+ only be EHCI host. OTG is controller that can switch to host mode.
+ Note that this driver will not work on Marvell's other EHCI
+ controller used by the EBU-type SoCs including Orion, Kirkwood,
+ Dova, Armada 370 and Armada XP. See "Support for Marvell EBU
+ on-chip EHCI USB controller" for those.
+
config USB_W90X900_EHCI
tristate "W90X900(W90P910) EHCI support"
depends on ARCH_W90X900
@@ -663,7 +676,7 @@ config USB_SL811_HCD
help
The SL811HS is a single-port USB controller that supports either
host side or peripheral side roles. Enable this option if your
- board has this chip, and you want to use it as a host controller.
+ board has this chip, and you want to use it as a host controller.
If unsure, say N.

To compile this driver as a module, choose M here: the
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 65b0b6a..5ed93ff 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
obj-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o
obj-$(CONFIG_USB_EHCI_HCD_PLATFORM) += ehci-platform.o
+obj-$(CONFIG_USB_EHCI_MV_OF) += ehci-mv-of.o
obj-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o
obj-$(CONFIG_USB_EHCI_HCD_OMAP) += ehci-omap.o
obj-$(CONFIG_USB_EHCI_HCD_ORION) += ehci-orion.o
diff --git a/drivers/usb/host/ehci-mv-of.c b/drivers/usb/host/ehci-mv-of.c
new file mode 100644
index 0000000..3783299
--- /dev/null
+++ b/drivers/usb/host/ehci-mv-of.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2015 Linaro, Ltd.
+ * Rob Herring <robh@kernel.org>
+ *
+ * Based on vendor driver modifications to ehci-mv.c:
+ * Copyright (C) 2011 Marvell International Ltd. All rights reserved.
+ * Author: Chao Xie <chao.xie@marvell.com>
+ * Neil Zhang <zhangwm@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/err.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "ehci.h"
+
+struct ehci_hcd_mv {
+ struct usb_hcd *hcd;
+ struct phy *phy;
+ struct clk *clk;
+};
+
+#define hcd_to_mv_priv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv)
+
+static struct hc_driver ehci_mv_hc_driver;
+
+static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
+{
+ timeout += jiffies;
+ while (time_is_after_eq_jiffies(timeout)) {
+ if ((readl(reg) & mask) == mask)
+ return true;
+ msleep(1);
+ }
+ return false;
+}
+
+static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(ehci_mv->hcd);
+ struct ehci_regs *ehci_regs = ehci->regs;
+ int retval;
+ u32 status;
+
+ /* enable port power and reserved bit 25 */
+ status = __raw_readl(&ehci_regs->port_status[0]);
+ status |= (PORT_POWER) | (1 << 25);
+ /* Clear bits 30:31 for HSIC to be enabled */
+ status &= ~(0x3 << 30);
+ __raw_writel(status, &ehci_regs->port_status[0]);
+
+ /* test mode: force enable hs */
+ status = __raw_readl(&ehci_regs->port_status[0]);
+ status &= ~(0xf << 16);
+ status |= (0x5 << 16);
+ __raw_writel(status, &ehci_regs->port_status[0]);
+
+ /* disable test mode */
+ status = __raw_readl(&ehci_regs->port_status[0]);
+ status &= ~(0xf << 16);
+ __raw_writel(status, &ehci_regs->port_status[0]);
+
+ retval = phy_power_on(ehci_mv->hcd->phy);
+ if (retval)
+ return retval;
+
+ /* issue port reset */
+ status = __raw_readl(&ehci_regs->port_status[0]);
+ status |= PORT_RESET;
+ status &= ~PORT_PE;
+ __raw_writel(status, &ehci_regs->port_status[0]);
+
+ /* check reset done */
+ if (!wait_for_reg(&ehci_regs->port_status[0], PORT_RESET, HZ / 10)) {
+ pr_err("HSIC port reset not done: port_status 0x%x\n", status);
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static int mv_ehci_probe(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd;
+ struct ehci_hcd *ehci;
+ struct ehci_hcd_mv *ehci_mv;
+ struct resource *r;
+ int retval = -ENODEV;
+
+ hcd = usb_create_hcd(&ehci_mv_hc_driver, &pdev->dev, "mv ehci");
+ if (!hcd)
+ return -ENOMEM;
+ ehci = hcd_to_ehci(hcd);
+ ehci_mv = hcd_to_mv_priv(hcd);
+ ehci_mv->hcd = hcd;
+
+ platform_set_drvdata(pdev, ehci_mv);
+
+ ehci_mv->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ehci_mv->clk)) {
+ dev_err(&pdev->dev, "error getting clock\n");
+ retval = PTR_ERR(ehci_mv->clk);
+ goto err_put_hcd;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ehci->caps = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(ehci->caps)) {
+ retval = PTR_ERR(ehci->caps);
+ goto err_put_hcd;
+ }
+
+ hcd->phy = devm_of_phy_get(&pdev->dev, pdev->dev.of_node, NULL);
+ if (IS_ERR_OR_NULL(hcd->phy)) {
+ retval = PTR_ERR(hcd->phy);
+ if (retval != -EPROBE_DEFER && retval != -ENODEV)
+ dev_err(&pdev->dev, "failed to get the phy\n");
+ else
+ return -EPROBE_DEFER;
+ goto err_put_hcd;
+ }
+
+ hcd->rsrc_start = r->start;
+ hcd->rsrc_len = resource_size(r);
+
+ hcd->irq = platform_get_irq(pdev, 0);
+ if (!hcd->irq) {
+ dev_err(&pdev->dev, "Cannot get irq.");
+ retval = -ENODEV;
+ goto err_put_hcd;
+ }
+
+ retval = phy_init(hcd->phy);
+ if (retval)
+ goto err_put_hcd;
+
+ clk_prepare(ehci_mv->clk);
+ clk_enable(ehci_mv->clk);
+
+ retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
+ if (retval) {
+ dev_err(&pdev->dev,
+ "failed to add hcd with err %d\n", retval);
+ goto err_disable_clk;
+ }
+
+ retval = mv_ehci_enable(ehci_mv);
+ if (retval) {
+ dev_err(&pdev->dev,
+ "EHCI: private init failed with err %d\n", retval);
+ goto err_disable_clk;
+ }
+
+ device_wakeup_enable(hcd->self.controller);
+
+ return 0;
+
+err_disable_clk:
+ clk_disable(ehci_mv->clk);
+ clk_unprepare(ehci_mv->clk);
+ phy_exit(hcd->phy);
+err_put_hcd:
+ usb_put_hcd(hcd);
+ return retval;
+}
+
+static int mv_ehci_remove(struct platform_device *pdev)
+{
+ struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = ehci_mv->hcd;
+
+ phy_power_off(hcd->phy);
+ phy_exit(hcd->phy);
+ clk_disable(ehci_mv->clk);
+ clk_unprepare(ehci_mv->clk);
+
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+
+ return 0;
+}
+
+static const struct of_device_id mv_ehci_dt_match[] = {
+ {.compatible = "marvell,pxa1928-ehci"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, mv_ehci_dt_match);
+
+static void mv_ehci_shutdown(struct platform_device *pdev)
+{
+ struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = ehci_mv->hcd;
+
+ if (!hcd->rh_registered)
+ return;
+
+ if (hcd->driver->shutdown)
+ hcd->driver->shutdown(hcd);
+}
+
+static const struct ehci_driver_overrides ehci_mv_overrides __initconst = {
+ .extra_priv_size = sizeof(struct ehci_hcd_mv),
+};
+
+static struct platform_driver ehci_mv_driver = {
+ .probe = mv_ehci_probe,
+ .remove = mv_ehci_remove,
+ .shutdown = mv_ehci_shutdown,
+ .driver = {
+ .name = "mv-ehci-of",
+ .of_match_table = of_match_ptr(mv_ehci_dt_match),
+ },
+};
+
+static int __init ehci_mv_init(void)
+{
+ if (usb_disabled())
+ return -ENODEV;
+
+ ehci_init_driver(&ehci_mv_hc_driver, &ehci_mv_overrides);
+ return platform_driver_register(&ehci_mv_driver);
+}
+module_init(ehci_mv_init);
+
+static void __exit ehci_mv_cleanup(void)
+{
+ platform_driver_unregister(&ehci_mv_driver);
+}
+module_exit(ehci_mv_cleanup);
+
+MODULE_AUTHOR("Rob Herring <robh@kernel.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.0