diff options
Diffstat (limited to 'drivers/usb/host/ehci-omap.c')
| -rw-r--r-- | drivers/usb/host/ehci-omap.c | 329 | 
1 files changed, 143 insertions, 186 deletions
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 0555ee42d7c..3d1491b5f36 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -4,10 +4,11 @@   * Bus Glue for the EHCI controllers in OMAP3/4   * Tested on several OMAP3 boards, and OMAP4 Pandaboard   * - * Copyright (C) 2007-2011 Texas Instruments, Inc. + * Copyright (C) 2007-2013 Texas Instruments, Inc.   *	Author: Vikram Pandita <vikram.pandita@ti.com>   *	Author: Anand Gadiyar <gadiyar@ti.com>   *	Author: Keshava Munegowda <keshava_mgowda@ti.com> + *	Author: Roger Quadros <rogerq@ti.com>   *   * Copyright (C) 2009 Nokia Corporation   *	Contact: Felipe Balbi <felipe.balbi@nokia.com> @@ -28,21 +29,23 @@   * along with this program; if not, write to the Free Software   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA   * - * TODO (last updated Feb 27, 2010): - *	- add kernel-doc - *	- enable AUTOIDLE - *	- add suspend/resume - *	- add HSIC and TLL support - *	- convert to use hwmod and runtime PM   */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h>  #include <linux/platform_device.h>  #include <linux/slab.h>  #include <linux/usb/ulpi.h> -#include <linux/regulator/consumer.h>  #include <linux/pm_runtime.h>  #include <linux/gpio.h>  #include <linux/clk.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/of.h> +#include <linux/dma-mapping.h> + +#include "ehci.h"  #include <linux/platform_data/usb-omap.h> @@ -57,10 +60,16 @@  #define	EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT		8  #define	EHCI_INSNREG05_ULPI_WRDATA_SHIFT		0 -/*-------------------------------------------------------------------------*/ +#define DRIVER_DESC "OMAP-EHCI Host Controller driver" + +static const char hcd_name[] = "ehci-omap"; -static const struct hc_driver ehci_omap_hc_driver; +/*-------------------------------------------------------------------------*/ +struct omap_hcd { +	struct usb_phy *phy[OMAP3_HS_USB_PORTS]; /* one PHY for each port */ +	int nports; +};  static inline void ehci_write(void __iomem *base, u32 reg, u32 val)  { @@ -72,99 +81,16 @@ static inline u32 ehci_read(void __iomem *base, u32 reg)  	return __raw_readl(base + reg);  } +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ -static void omap_ehci_soft_phy_reset(struct usb_hcd *hcd, u8 port) -{ -	unsigned long timeout = jiffies + msecs_to_jiffies(1000); -	unsigned reg = 0; - -	reg = ULPI_FUNC_CTRL_RESET -		/* FUNCTION_CTRL_SET register */ -		| (ULPI_SET(ULPI_FUNC_CTRL) << EHCI_INSNREG05_ULPI_REGADD_SHIFT) -		/* Write */ -		| (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) -		/* PORTn */ -		| ((port + 1) << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) -		/* start ULPI access*/ -		| (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT); - -	ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, reg); - -	/* Wait for ULPI access completion */ -	while ((ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) -			& (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT))) { -		cpu_relax(); - -		if (time_after(jiffies, timeout)) { -			dev_dbg(hcd->self.controller, -					"phy reset operation timed out\n"); -			break; -		} -	} -} - -static int omap_ehci_init(struct usb_hcd *hcd) -{ -	struct ehci_hcd		*ehci = hcd_to_ehci(hcd); -	int			rc; -	struct usbhs_omap_platform_data	*pdata; - -	pdata = hcd->self.controller->platform_data; - -	/* Hold PHYs in reset while initializing EHCI controller */ -	if (pdata->phy_reset) { -		if (gpio_is_valid(pdata->reset_gpio_port[0])) -			gpio_set_value_cansleep(pdata->reset_gpio_port[0], 0); - -		if (gpio_is_valid(pdata->reset_gpio_port[1])) -			gpio_set_value_cansleep(pdata->reset_gpio_port[1], 0); - -		/* Hold the PHY in RESET for enough time till DIR is high */ -		udelay(10); -	} - -	/* Soft reset the PHY using PHY reset command over ULPI */ -	if (pdata->port_mode[0] == OMAP_EHCI_PORT_MODE_PHY) -		omap_ehci_soft_phy_reset(hcd, 0); -	if (pdata->port_mode[1] == OMAP_EHCI_PORT_MODE_PHY) -		omap_ehci_soft_phy_reset(hcd, 1); - -	/* we know this is the memory we want, no need to ioremap again */ -	ehci->caps = hcd->regs; - -	rc = ehci_setup(hcd); - -	if (pdata->phy_reset) { -		/* Hold the PHY in RESET for enough time till -		 * PHY is settled and ready -		 */ -		udelay(10); - -		if (gpio_is_valid(pdata->reset_gpio_port[0])) -			gpio_set_value_cansleep(pdata->reset_gpio_port[0], 1); - -		if (gpio_is_valid(pdata->reset_gpio_port[1])) -			gpio_set_value_cansleep(pdata->reset_gpio_port[1], 1); -	} - -	return rc; -} - -static void disable_put_regulator( -		struct usbhs_omap_platform_data *pdata) -{ -	int i; +static struct hc_driver __read_mostly ehci_omap_hc_driver; -	for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) { -		if (pdata->regulator[i]) { -			regulator_disable(pdata->regulator[i]); -			regulator_put(pdata->regulator[i]); -		} -	} -} +static const struct ehci_driver_overrides ehci_omap_overrides __initdata = { +	.extra_priv_size = sizeof(struct omap_hcd), +}; -/* configure so an HC device and id are always provided */ -/* always called with process context; sleeping is OK */ +static u64 omap_ehci_dma_mask = DMA_BIT_MASK(32);  /**   * ehci_hcd_omap_probe - initialize TI-based HCDs @@ -175,15 +101,15 @@ static void disable_put_regulator(   */  static int ehci_hcd_omap_probe(struct platform_device *pdev)  { -	struct device				*dev = &pdev->dev; -	struct usbhs_omap_platform_data		*pdata = dev->platform_data; -	struct resource				*res; -	struct usb_hcd				*hcd; -	void __iomem				*regs; -	int					ret = -ENODEV; -	int					irq; -	int					i; -	char					supply[7]; +	struct device *dev = &pdev->dev; +	struct usbhs_omap_platform_data *pdata = dev->platform_data; +	struct resource	*res; +	struct usb_hcd	*hcd; +	void __iomem *regs; +	int ret = -ENODEV; +	int irq; +	int i; +	struct omap_hcd	*omap;  	if (usb_disabled())  		return -ENODEV; @@ -193,52 +119,74 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)  		return -ENODEV;  	} -	irq = platform_get_irq_byname(pdev, "ehci-irq"); -	if (irq < 0) { -		dev_err(dev, "EHCI irq failed\n"); -		return -ENODEV; +	/* For DT boot, get platform data from parent. i.e. usbhshost */ +	if (dev->of_node) { +		pdata = dev->parent->platform_data; +		dev->platform_data = pdata;  	} -	res =  platform_get_resource_byname(pdev, -				IORESOURCE_MEM, "ehci"); -	if (!res) { -		dev_err(dev, "UHH EHCI get resource failed\n"); +	if (!pdata) { +		dev_err(dev, "Missing platform data\n");  		return -ENODEV;  	} -	regs = ioremap(res->start, resource_size(res)); -	if (!regs) { -		dev_err(dev, "UHH EHCI ioremap failed\n"); -		return -ENOMEM; +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		dev_err(dev, "EHCI irq failed\n"); +		return -ENODEV;  	} +	res =  platform_get_resource(pdev, IORESOURCE_MEM, 0); +	regs = devm_ioremap_resource(dev, res); +	if (IS_ERR(regs)) +		return PTR_ERR(regs); + +	/* +	 * Right now device-tree probed devices don't get dma_mask set. +	 * Since shared usb code relies on it, set it here for now. +	 * Once we have dma capability bindings this can go away. +	 */ +	if (!pdev->dev.dma_mask) +		pdev->dev.dma_mask = &omap_ehci_dma_mask; +  	hcd = usb_create_hcd(&ehci_omap_hc_driver, dev,  			dev_name(dev));  	if (!hcd) { -		dev_err(dev, "failed to create hcd with err %d\n", ret); -		ret = -ENOMEM; -		goto err_io; +		dev_err(dev, "Failed to create HCD\n"); +		return -ENOMEM;  	}  	hcd->rsrc_start = res->start;  	hcd->rsrc_len = resource_size(res);  	hcd->regs = regs; +	hcd_to_ehci(hcd)->caps = regs; -	/* get ehci regulator and enable */ -	for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) { -		if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) { -			pdata->regulator[i] = NULL; -			continue; -		} -		snprintf(supply, sizeof(supply), "hsusb%d", i); -		pdata->regulator[i] = regulator_get(dev, supply); -		if (IS_ERR(pdata->regulator[i])) { -			pdata->regulator[i] = NULL; -			dev_dbg(dev, -			"failed to get ehci port%d regulator\n", i); -		} else { -			regulator_enable(pdata->regulator[i]); +	omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv; +	omap->nports = pdata->nports; + +	platform_set_drvdata(pdev, hcd); + +	/* get the PHY devices if needed */ +	for (i = 0 ; i < omap->nports ; i++) { +		struct usb_phy *phy; + +		/* get the PHY device */ +		if (dev->of_node) +			phy = devm_usb_get_phy_by_phandle(dev, "phys", i); +		else +			phy = devm_usb_get_phy_dev(dev, i); +		if (IS_ERR(phy)) { +			/* Don't bail out if PHY is not absolutely necessary */ +			if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) +				continue; + +			ret = PTR_ERR(phy); +			dev_err(dev, "Can't get PHY device for port %d: %d\n", +					i, ret); +			goto err_phy;  		} + +		omap->phy[i] = phy;  	}  	pm_runtime_enable(dev); @@ -262,16 +210,34 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)  		goto err_pm_runtime;  	} +	/* +	 * Bring PHYs out of reset. +	 * Even though HSIC mode is a PHY-less mode, the reset +	 * line exists between the chips and can be modelled +	 * as a PHY device for reset control. +	 */ +	for (i = 0; i < omap->nports; i++) { +		if (!omap->phy[i]) +			continue; + +		usb_phy_init(omap->phy[i]); +		/* bring PHY out of suspend */ +		usb_phy_set_suspend(omap->phy[i], 0); +	}  	return 0;  err_pm_runtime: -	disable_put_regulator(pdata);  	pm_runtime_put_sync(dev); + +err_phy: +	for (i = 0; i < omap->nports; i++) { +		if (omap->phy[i]) +			usb_phy_shutdown(omap->phy[i]); +	} +  	usb_put_hcd(hcd); -err_io: -	iounmap(regs);  	return ret;  } @@ -286,14 +252,19 @@ err_io:   */  static int ehci_hcd_omap_remove(struct platform_device *pdev)  { -	struct device *dev				= &pdev->dev; -	struct usb_hcd *hcd				= dev_get_drvdata(dev); +	struct device *dev = &pdev->dev; +	struct usb_hcd *hcd = dev_get_drvdata(dev); +	struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv; +	int i;  	usb_remove_hcd(hcd); -	disable_put_regulator(dev->platform_data); -	iounmap(hcd->regs); -	usb_put_hcd(hcd); +	for (i = 0; i < omap->nports; i++) { +		if (omap->phy[i]) +			usb_phy_shutdown(omap->phy[i]); +	} + +	usb_put_hcd(hcd);  	pm_runtime_put_sync(dev);  	pm_runtime_disable(dev); @@ -308,6 +279,13 @@ static void ehci_hcd_omap_shutdown(struct platform_device *pdev)  		hcd->driver->shutdown(hcd);  } +static const struct of_device_id omap_ehci_dt_ids[] = { +	{ .compatible = "ti,ehci-omap" }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids); +  static struct platform_driver ehci_hcd_omap_driver = {  	.probe			= ehci_hcd_omap_probe,  	.remove			= ehci_hcd_omap_remove, @@ -315,56 +293,35 @@ static struct platform_driver ehci_hcd_omap_driver = {  	/*.suspend		= ehci_hcd_omap_suspend, */  	/*.resume		= ehci_hcd_omap_resume, */  	.driver = { -		.name		= "ehci-omap", +		.name		= hcd_name, +		.of_match_table = of_match_ptr(omap_ehci_dt_ids),  	}  };  /*-------------------------------------------------------------------------*/ -static const struct hc_driver ehci_omap_hc_driver = { -	.description		= hcd_name, -	.product_desc		= "OMAP-EHCI Host Controller", -	.hcd_priv_size		= sizeof(struct ehci_hcd), - -	/* -	 * generic hardware linkage -	 */ -	.irq			= ehci_irq, -	.flags			= HCD_MEMORY | HCD_USB2, - -	/* -	 * basic lifecycle operations -	 */ -	.reset			= omap_ehci_init, -	.start			= ehci_run, -	.stop			= ehci_stop, -	.shutdown		= ehci_shutdown, - -	/* -	 * managing i/o requests and associated device resources -	 */ -	.urb_enqueue		= ehci_urb_enqueue, -	.urb_dequeue		= ehci_urb_dequeue, -	.endpoint_disable	= ehci_endpoint_disable, -	.endpoint_reset		= ehci_endpoint_reset, +static int __init ehci_omap_init(void) +{ +	if (usb_disabled()) +		return -ENODEV; -	/* -	 * scheduling support -	 */ -	.get_frame_number	= ehci_get_frame, +	pr_info("%s: " DRIVER_DESC "\n", hcd_name); -	/* -	 * root hub support -	 */ -	.hub_status_data	= ehci_hub_status_data, -	.hub_control		= ehci_hub_control, -	.bus_suspend		= ehci_bus_suspend, -	.bus_resume		= ehci_bus_resume, +	ehci_init_driver(&ehci_omap_hc_driver, &ehci_omap_overrides); +	return platform_driver_register(&ehci_hcd_omap_driver); +} +module_init(ehci_omap_init); -	.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, -}; +static void __exit ehci_omap_cleanup(void) +{ +	platform_driver_unregister(&ehci_hcd_omap_driver); +} +module_exit(ehci_omap_cleanup);  MODULE_ALIAS("platform:ehci-omap");  MODULE_AUTHOR("Texas Instruments, Inc.");  MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>"); +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL");  |