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@xxxxxxxxxx> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> Cc: linux-usb@xxxxxxxxxxxxxxx --- 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@xxxxxxxxxx> + * + * Based on vendor driver modifications to ehci-mv.c: + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie <chao.xie@xxxxxxxxxxx> + * Neil Zhang <zhangwm@xxxxxxxxxxx> + * + * 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@xxxxxxxxxx>"); +MODULE_LICENSE("GPL v2"); -- 2.1.0 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html