diff options
Diffstat (limited to 'drivers/usb/host/ehci-platform.c')
| -rw-r--r-- | drivers/usb/host/ehci-platform.c | 410 | 
1 files changed, 410 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c new file mode 100644 index 00000000000..2f5b9ce3e04 --- /dev/null +++ b/drivers/usb/host/ehci-platform.c @@ -0,0 +1,410 @@ +/* + * Generic platform ehci driver + * + * Copyright 2007 Steven Brown <sbrown@cortland.com> + * Copyright 2010-2012 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright 2014 Hans de Goede <hdegoede@redhat.com> + * + * Derived from the ohci-ssb driver + * Copyright 2007 Michael Buesch <m@bues.ch> + * + * Derived from the EHCI-PCI driver + * Copyright (c) 2000-2004 by David Brownell + * + * Derived from the ohci-pci driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/hrtimer.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/ehci_pdriver.h> + +#include "ehci.h" + +#define DRIVER_DESC "EHCI generic platform driver" +#define EHCI_MAX_CLKS 3 +#define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv) + +struct ehci_platform_priv { +	struct clk *clks[EHCI_MAX_CLKS]; +	struct reset_control *rst; +	struct phy *phy; +}; + +static const char hcd_name[] = "ehci-platform"; + +static int ehci_platform_reset(struct usb_hcd *hcd) +{ +	struct platform_device *pdev = to_platform_device(hcd->self.controller); +	struct usb_ehci_pdata *pdata = dev_get_platdata(&pdev->dev); +	struct ehci_hcd *ehci = hcd_to_ehci(hcd); +	int retval; + +	hcd->has_tt = pdata->has_tt; +	ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug; + +	if (pdata->pre_setup) { +		retval = pdata->pre_setup(hcd); +		if (retval < 0) +			return retval; +	} + +	ehci->caps = hcd->regs + pdata->caps_offset; +	retval = ehci_setup(hcd); +	if (retval) +		return retval; + +	if (pdata->no_io_watchdog) +		ehci->need_io_watchdog = 0; +	return 0; +} + +static int ehci_platform_power_on(struct platform_device *dev) +{ +	struct usb_hcd *hcd = platform_get_drvdata(dev); +	struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); +	int clk, ret; + +	for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) { +		ret = clk_prepare_enable(priv->clks[clk]); +		if (ret) +			goto err_disable_clks; +	} + +	if (priv->phy) { +		ret = phy_init(priv->phy); +		if (ret) +			goto err_disable_clks; + +		ret = phy_power_on(priv->phy); +		if (ret) +			goto err_exit_phy; +	} + +	return 0; + +err_exit_phy: +	phy_exit(priv->phy); +err_disable_clks: +	while (--clk >= 0) +		clk_disable_unprepare(priv->clks[clk]); + +	return ret; +} + +static void ehci_platform_power_off(struct platform_device *dev) +{ +	struct usb_hcd *hcd = platform_get_drvdata(dev); +	struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); +	int clk; + +	if (priv->phy) { +		phy_power_off(priv->phy); +		phy_exit(priv->phy); +	} + +	for (clk = EHCI_MAX_CLKS - 1; clk >= 0; clk--) +		if (priv->clks[clk]) +			clk_disable_unprepare(priv->clks[clk]); +} + +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { +	.reset =		ehci_platform_reset, +	.extra_priv_size =	sizeof(struct ehci_platform_priv), +}; + +static struct usb_ehci_pdata ehci_platform_defaults = { +	.power_on =		ehci_platform_power_on, +	.power_suspend =	ehci_platform_power_off, +	.power_off =		ehci_platform_power_off, +}; + +static int ehci_platform_probe(struct platform_device *dev) +{ +	struct usb_hcd *hcd; +	struct resource *res_mem; +	struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); +	struct ehci_platform_priv *priv; +	struct ehci_hcd *ehci; +	int err, irq, clk = 0; + +	if (usb_disabled()) +		return -ENODEV; + +	/* +	 * Use reasonable defaults so platforms don't have to provide these +	 * with DT probing on ARM. +	 */ +	if (!pdata) +		pdata = &ehci_platform_defaults; + +	err = dma_coerce_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); +	if (err) +		return err; + +	irq = platform_get_irq(dev, 0); +	if (irq < 0) { +		dev_err(&dev->dev, "no irq provided"); +		return irq; +	} +	res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0); +	if (!res_mem) { +		dev_err(&dev->dev, "no memory resource provided"); +		return -ENXIO; +	} + +	hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, +			     dev_name(&dev->dev)); +	if (!hcd) +		return -ENOMEM; + +	platform_set_drvdata(dev, hcd); +	dev->dev.platform_data = pdata; +	priv = hcd_to_ehci_priv(hcd); +	ehci = hcd_to_ehci(hcd); + +	if (pdata == &ehci_platform_defaults && dev->dev.of_node) { +		if (of_property_read_bool(dev->dev.of_node, "big-endian-regs")) +			ehci->big_endian_mmio = 1; + +		if (of_property_read_bool(dev->dev.of_node, "big-endian-desc")) +			ehci->big_endian_desc = 1; + +		if (of_property_read_bool(dev->dev.of_node, "big-endian")) +			ehci->big_endian_mmio = ehci->big_endian_desc = 1; + +		priv->phy = devm_phy_get(&dev->dev, "usb"); +		if (IS_ERR(priv->phy)) { +			err = PTR_ERR(priv->phy); +			if (err == -EPROBE_DEFER) +				goto err_put_hcd; +			priv->phy = NULL; +		} + +		for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { +			priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); +			if (IS_ERR(priv->clks[clk])) { +				err = PTR_ERR(priv->clks[clk]); +				if (err == -EPROBE_DEFER) +					goto err_put_clks; +				priv->clks[clk] = NULL; +				break; +			} +		} +	} + +	priv->rst = devm_reset_control_get_optional(&dev->dev, NULL); +	if (IS_ERR(priv->rst)) { +		err = PTR_ERR(priv->rst); +		if (err == -EPROBE_DEFER) +			goto err_put_clks; +		priv->rst = NULL; +	} else { +		err = reset_control_deassert(priv->rst); +		if (err) +			goto err_put_clks; +	} + +	if (pdata->big_endian_desc) +		ehci->big_endian_desc = 1; +	if (pdata->big_endian_mmio) +		ehci->big_endian_mmio = 1; + +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO +	if (ehci->big_endian_mmio) { +		dev_err(&dev->dev, +			"Error: CONFIG_USB_EHCI_BIG_ENDIAN_MMIO not set\n"); +		err = -EINVAL; +		goto err_reset; +	} +#endif +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_DESC +	if (ehci->big_endian_desc) { +		dev_err(&dev->dev, +			"Error: CONFIG_USB_EHCI_BIG_ENDIAN_DESC not set\n"); +		err = -EINVAL; +		goto err_reset; +	} +#endif + +	if (pdata->power_on) { +		err = pdata->power_on(dev); +		if (err < 0) +			goto err_reset; +	} + +	hcd->rsrc_start = res_mem->start; +	hcd->rsrc_len = resource_size(res_mem); + +	hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); +	if (IS_ERR(hcd->regs)) { +		err = PTR_ERR(hcd->regs); +		goto err_power; +	} +	err = usb_add_hcd(hcd, irq, IRQF_SHARED); +	if (err) +		goto err_power; + +	device_wakeup_enable(hcd->self.controller); +	platform_set_drvdata(dev, hcd); + +	return err; + +err_power: +	if (pdata->power_off) +		pdata->power_off(dev); +err_reset: +	if (priv->rst) +		reset_control_assert(priv->rst); +err_put_clks: +	while (--clk >= 0) +		clk_put(priv->clks[clk]); +err_put_hcd: +	if (pdata == &ehci_platform_defaults) +		dev->dev.platform_data = NULL; + +	usb_put_hcd(hcd); + +	return err; +} + +static int ehci_platform_remove(struct platform_device *dev) +{ +	struct usb_hcd *hcd = platform_get_drvdata(dev); +	struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); +	struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); +	int clk; + +	usb_remove_hcd(hcd); + +	if (pdata->power_off) +		pdata->power_off(dev); + +	if (priv->rst) +		reset_control_assert(priv->rst); + +	for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) +		clk_put(priv->clks[clk]); + +	usb_put_hcd(hcd); + +	if (pdata == &ehci_platform_defaults) +		dev->dev.platform_data = NULL; + +	return 0; +} + +#ifdef CONFIG_PM + +static int ehci_platform_suspend(struct device *dev) +{ +	struct usb_hcd *hcd = dev_get_drvdata(dev); +	struct usb_ehci_pdata *pdata = dev_get_platdata(dev); +	struct platform_device *pdev = +		container_of(dev, struct platform_device, dev); +	bool do_wakeup = device_may_wakeup(dev); +	int ret; + +	ret = ehci_suspend(hcd, do_wakeup); +	if (ret) +		return ret; + +	if (pdata->power_suspend) +		pdata->power_suspend(pdev); + +	return ret; +} + +static int ehci_platform_resume(struct device *dev) +{ +	struct usb_hcd *hcd = dev_get_drvdata(dev); +	struct usb_ehci_pdata *pdata = dev_get_platdata(dev); +	struct platform_device *pdev = +		container_of(dev, struct platform_device, dev); + +	if (pdata->power_on) { +		int err = pdata->power_on(pdev); +		if (err < 0) +			return err; +	} + +	ehci_resume(hcd, false); +	return 0; +} + +#else /* !CONFIG_PM */ +#define ehci_platform_suspend	NULL +#define ehci_platform_resume	NULL +#endif /* CONFIG_PM */ + +static const struct of_device_id vt8500_ehci_ids[] = { +	{ .compatible = "via,vt8500-ehci", }, +	{ .compatible = "wm,prizm-ehci", }, +	{ .compatible = "generic-ehci", }, +	{} +}; +MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); + +static const struct platform_device_id ehci_platform_table[] = { +	{ "ehci-platform", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(platform, ehci_platform_table); + +static const struct dev_pm_ops ehci_platform_pm_ops = { +	.suspend	= ehci_platform_suspend, +	.resume		= ehci_platform_resume, +}; + +static struct platform_driver ehci_platform_driver = { +	.id_table	= ehci_platform_table, +	.probe		= ehci_platform_probe, +	.remove		= ehci_platform_remove, +	.shutdown	= usb_hcd_platform_shutdown, +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "ehci-platform", +		.pm	= &ehci_platform_pm_ops, +		.of_match_table = vt8500_ehci_ids, +	} +}; + +static int __init ehci_platform_init(void) +{ +	if (usb_disabled()) +		return -ENODEV; + +	pr_info("%s: " DRIVER_DESC "\n", hcd_name); + +	ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); +	return platform_driver_register(&ehci_platform_driver); +} +module_init(ehci_platform_init); + +static void __exit ehci_platform_cleanup(void) +{ +	platform_driver_unregister(&ehci_platform_driver); +} +module_exit(ehci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL");  | 
