On Thu, Jun 25, 2009 at 02:11:56PM +0200, Daniel Mack wrote: > The Freescale MX27 and MX31 SoCs have a EHCI controller onboard. > The controller is capable of USB on the go. This patch adds > a driver to support all three of them. > > Users have to pass details about serial interface configuration in the > platform data. > > The USB OTG core used here is the ARC core, so the driver should > be renamed and probably be merged with ehci-fsl.c eventually. > > Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> > Signed-off-by: Daniel Mack <daniel@xxxxxxxx> > --- > arch/arm/plat-mxc/include/mach/mxc_ehci.h | 35 +++ > drivers/usb/Kconfig | 1 + > drivers/usb/host/Kconfig | 7 + > drivers/usb/host/ehci-hcd.c | 5 + > drivers/usb/host/ehci-mxc.c | 335 +++++++++++++++++++++++++++++ > 5 files changed, 383 insertions(+), 0 deletions(-) > create mode 100644 arch/arm/plat-mxc/include/mach/mxc_ehci.h > create mode 100644 drivers/usb/host/ehci-mxc.c > > diff --git a/arch/arm/plat-mxc/include/mach/mxc_ehci.h b/arch/arm/plat-mxc/include/mach/mxc_ehci.h > new file mode 100644 > index 0000000..625e32b > --- /dev/null > +++ b/arch/arm/plat-mxc/include/mach/mxc_ehci.h > @@ -0,0 +1,35 @@ > +#ifndef __INCLUDE_ASM_ARCH_MXC_EHCI_H > +#define __INCLUDE_ASM_ARCH_MXC_EHCI_H > + > +/* values for portsc field */ > +#define MXC_EHCI_PHY_LOW_POWER_SUSPEND (1 << 23) > +#define MXC_EHCI_FORCE_FS (1 << 24) > +#define MXC_EHCI_UTMI_8BIT (0 << 28) > +#define MXC_EHCI_UTMI_16BIT (1 << 28) > +#define MXC_EHCI_SERIAL (1 << 29) > +#define MXC_EHCI_MODE_UTMI (0 << 30) > +#define MXC_EHCI_MODE_PHILIPS (1 << 30) > +#define MXC_EHCI_MODE_ULPI (2 << 30) > +#define MXC_EHCI_MODE_SERIAL (3 << 30) > + > +/* values for flags field */ > +#define MXC_EHCI_INTERFACE_DIFF_UNI (0 << 0) > +#define MXC_EHCI_INTERFACE_DIFF_BI (1 << 0) > +#define MXC_EHCI_INTERFACE_SINGLE_UNI (2 << 0) > +#define MXC_EHCI_INTERFACE_SINGLE_BI (3 << 0) > +#define MXC_EHCI_INTERFACE_MASK (0xf) > + > +#define MXC_EHCI_POWER_PINS_ENABLED (1 << 5) > +#define MXC_EHCI_TTL_ENABLED (1 << 6) > + > +struct mxc_usbh_platform_data { > + int (*init)(struct platform_device *pdev); > + int (*exit)(struct platform_device *pdev); > + > + unsigned int portsc; > + unsigned int flags; > + struct usb_xcvr *xcvr; > +}; > + > +#endif /* __INCLUDE_ASM_ARCH_MXC_EHCI_H */ > + > diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig > index dcd49f1..3ffaed5 100644 > --- a/drivers/usb/Kconfig > +++ b/drivers/usb/Kconfig > @@ -58,6 +58,7 @@ config USB_ARCH_HAS_EHCI > default y if PPC_83xx > default y if SOC_AU1200 > default y if ARCH_IXP4XX > + default y if ARCH_MXC > default PCI > > # ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. > diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig > index 1576a05..59a5ebd 100644 > --- a/drivers/usb/host/Kconfig > +++ b/drivers/usb/host/Kconfig > @@ -105,6 +105,13 @@ config USB_EHCI_FSL > ---help--- > Variation of ARC USB block used in some Freescale chips. > > +config USB_EHCI_MXC > + bool "Support for Freescale on-chip EHCI USB controller" > + depends on USB_EHCI_HCD && ARCH_MXC > + select USB_EHCI_ROOT_HUB_TT > + ---help--- > + Variation of ARC USB block used in some Freescale chips. > + > config USB_EHCI_HCD_PPC_OF > bool "EHCI support for PPC USB controller on OF platform bus" > depends on USB_EHCI_HCD && PPC_OF > diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c > index 2b72473..dc88ac9 100644 > --- a/drivers/usb/host/ehci-hcd.c > +++ b/drivers/usb/host/ehci-hcd.c > @@ -1092,6 +1092,11 @@ MODULE_LICENSE ("GPL"); > #define PLATFORM_DRIVER ehci_fsl_driver > #endif > > +#ifdef CONFIG_USB_EHCI_MXC > +#include "ehci-mxc.c" > +#define PLATFORM_DRIVER ehci_mxc_driver > +#endif > + > #ifdef CONFIG_SOC_AU1200 > #include "ehci-au1xxx.c" > #define PLATFORM_DRIVER ehci_hcd_au1xxx_driver > diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c > new file mode 100644 > index 0000000..36b0924 > --- /dev/null > +++ b/drivers/usb/host/ehci-mxc.c > @@ -0,0 +1,335 @@ > +/* > + * Copyright (c) 2008 Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>, Pengutronix > + * Copyright (c) 2009 Daniel Mack <daniel@xxxxxxxx> > + * > + * 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. > + * > + * 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. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software Foundation, > + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#include <linux/platform_device.h> > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/usb/xcvr.h> > + > +#include <mach/mxc_ehci.h> > + > +#define ULPI_VIEWPORT_OFFSET 0x170 > +#define PORTSC_OFFSET 0x184 > +#define USBMODE_OFFSET 0x1a8 > +#define USBMODE_CM_HOST 3 > +#define USBCTRL_OTGBASE_OFFSET 0x600 > + > +#define OTG_SIC_SHIFT 29 > +#define OTG_SIC_MASK (0xf << OTG_SIC_SHIFT) > +#define OTG_PM_BIT (1 << 24) > + > +#define H2_SIC_SHIFT 21 > +#define H2_SIC_MASK (0xf << H2_SIC_SHIFT) > +#define H2_PM_BIT (1 << 16) > +#define H2_DT_BIT (1 << 5) > + > +#define H1_SIC_SHIFT 13 > +#define H1_SIC_MASK (0xf << H1_SIC_SHIFT) > +#define H1_PM_BIT (1 << 8) > +#define H1_DT_BIT (1 << 4) > + > + > +struct ehci_mxc_priv { > + struct clk *usbclk, *ahbclk; > + struct usb_hcd *hcd; > +}; > + > +/* called during probe() after chip reset completes */ > +static int ehci_mxc_setup(struct usb_hcd *hcd) > +{ > + struct ehci_hcd *ehci = hcd_to_ehci(hcd); > + int retval; > + > + /* EHCI registers start at offset 0x100 */ > + ehci->caps = hcd->regs + 0x100; > + ehci->regs = hcd->regs + 0x100 + > + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase)); > + dbg_hcs_params(ehci, "reset"); > + dbg_hcc_params(ehci, "reset"); > + > + /* cache this readonly data; minimize chip reads */ > + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); > + > + retval = ehci_halt(ehci); > + if (retval) > + return retval; > + > + /* data structure init */ > + retval = ehci_init(hcd); > + if (retval) > + return retval; > + > + hcd->has_tt = 1; > + > + ehci->sbrn = 0x20; > + > + ehci_reset(ehci); > + > + ehci_port_power(ehci, 0); > + return 0; > +} > + > +static const struct hc_driver ehci_mxc_hc_driver = { > + .description = hcd_name, > + .product_desc = "Freescale On-Chip EHCI Host Controller", > + .hcd_priv_size = sizeof(struct ehci_hcd), > + > + /* > + * generic hardware linkage > + */ > + .irq = ehci_irq, > + .flags = HCD_USB2 | HCD_MEMORY, > + > + /* > + * basic lifecycle operations > + */ > + .reset = ehci_mxc_setup, > + .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, > + > + /* > + * scheduling support > + */ > + .get_frame_number = ehci_get_frame, > + > + /* > + * 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, > + .relinquish_port = ehci_relinquish_port, > + .port_handed_over = ehci_port_handed_over, > +}; > + > +static int ehci_mxc_drv_probe(struct platform_device *pdev) > +{ > + struct mxc_usbh_platform_data *pdata = pdev->dev.platform_data; > + struct usb_hcd *hcd; > + struct resource *res; > + int irq, ret, temp; > + struct ehci_mxc_priv *priv; > + struct device *dev = &pdev->dev; > + > + dev_info(&pdev->dev, "initializing i.MX USB Controller\n"); > + > + if (!pdata) { > + dev_err(dev, "No platform data given, bailing out.\n"); > + return -EINVAL; > + } > + > + irq = platform_get_irq(pdev, 0); > + > + hcd = usb_create_hcd(&ehci_mxc_hc_driver, dev, dev_name(dev)); > + if (!hcd) > + return -ENOMEM; > + > + priv = kzalloc(sizeof(*priv), GFP_KERNEL); > + if (!priv) { > + ret = -ENOMEM; > + goto err_alloc; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(dev, "Found HC with no register addr. Check setup!\n"); > + ret = -ENODEV; > + goto err_get_resource; > + } > + > + hcd->rsrc_start = res->start; > + hcd->rsrc_len = resource_size(res); > + > + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { > + dev_dbg(dev, "controller already in use\n"); > + ret = -EBUSY; > + goto err_request_mem; > + } > + > + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); > + if (!hcd->regs) { > + dev_err(dev, "error mapping memory\n"); > + ret = -EFAULT; > + goto err_ioremap; > + } > + > + /* enable clocks */ > + priv->usbclk = clk_get(dev, "usb"); > + if (IS_ERR(priv->usbclk)) { > + ret = PTR_ERR(priv->usbclk); > + goto err_clk; > + } > + clk_enable(priv->usbclk); > + > + if (!cpu_is_mx35()) { > + priv->ahbclk = clk_get(dev, "usb_ahb"); > + if (IS_ERR(priv->ahbclk)) { > + ret = PTR_ERR(priv->ahbclk); > + goto err_clk_ahb; > + } > + clk_enable(priv->ahbclk); > + } > + > + /* set USBMODE to host mode */ > + temp = readl(hcd->regs + USBMODE_OFFSET); > + writel(temp | USBMODE_CM_HOST, hcd->regs + USBMODE_OFFSET); > + > + /* set up the PORTSCx register */ > + writel(pdata->portsc, hcd->regs + PORTSC_OFFSET); > + mdelay(10); > + > + /* setup USBCONTROL. Bits for all 3 ports are MUXed into that > + * single register, hence we have to access memory outside the > + * resource window */ > + temp = readl(IO_ADDRESS(OTG_BASE_ADDR + USBCTRL_OTGBASE_OFFSET)); This is not good. Currently we can build a kernel for i.MX31 and i.MX35. Problem is that the OTG base addresses differ. Maybe we should add a function which encapsulates accesses to this register. We might even want to add locking to this function. As a general note I don't want to depend on defines like OTG_BASE_ADDR in drivers. This all comes back to us when we try to build kernels for multiple i.MXs. > + switch (pdev->id) { > + case 0: /* OTG port */ > + temp &= ~(OTG_SIC_MASK | OTG_PM_BIT); > + temp |= (pdata->flags & MXC_EHCI_INTERFACE_MASK) > + << OTG_SIC_SHIFT; > + if (pdata->flags & MXC_EHCI_POWER_PINS_ENABLED) > + temp |= OTG_PM_BIT; > + > + break; > + case 1: /* H1 port */ > + temp &= ~(H1_SIC_MASK | H1_PM_BIT); > + temp |= (pdata->flags & MXC_EHCI_INTERFACE_MASK) > + << H1_SIC_SHIFT; > + if (pdata->flags & MXC_EHCI_POWER_PINS_ENABLED) > + temp |= H1_PM_BIT; > + > + if (!(pdata->flags & MXC_EHCI_TTL_ENABLED)) > + temp |= H1_DT_BIT; > + > + break; > + case 2: /* H2 port */ > + temp &= ~(H2_SIC_MASK | H2_PM_BIT); > + temp |= (pdata->flags & MXC_EHCI_INTERFACE_MASK) > + << H2_SIC_SHIFT; > + if (!(pdata->flags & MXC_EHCI_POWER_PINS_ENABLED)) > + temp |= H2_PM_BIT; > + > + if (!(pdata->flags & MXC_EHCI_TTL_ENABLED)) > + temp |= H2_DT_BIT; > + > + break; > + } > + writel(temp, IO_ADDRESS(OTG_BASE_ADDR + USBCTRL_OTGBASE_OFFSET)); > + > + /* call platform specific init function */ > + if (pdata->init) { > + ret = pdata->init(pdev); > + if (ret) { > + dev_err(dev, "platform init failed\n"); > + goto err_init; > + } > + } > + My board needs an mdelay here. I can put this into my board code, but does anybody else see this? > + /* Initialize the transceiver */ > + if (pdata->xcvr) { > + pdata->xcvr->access_priv = hcd->regs + ULPI_VIEWPORT_OFFSET; > + if (usb_xcvr_init(pdata->xcvr) != 0) > + dev_err(dev, "unable to init transceiver\n"); > + else if (usb_xcvr_set_vbus(pdata->xcvr, 1) != 0) > + dev_err(dev, "unable to enable vbus on transceiver\n"); We should probably bail out here. > + } > + > + priv->hcd = hcd; > + platform_set_drvdata(pdev, priv); > + > + ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); > + if (ret) > + goto err_add; > + > + return 0; > + > +err_add: > + if (pdata && pdata->exit) > + pdata->exit(pdev); > +err_init: > + if (priv->ahbclk) { > + clk_disable(priv->ahbclk); > + clk_put(priv->ahbclk); > + } > +err_clk_ahb: > + clk_disable(priv->usbclk); > + clk_put(priv->usbclk); > +err_clk: > + iounmap(hcd->regs); > +err_ioremap: > + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); > +err_request_mem: > +err_get_resource: > + kfree(priv); > +err_alloc: > + usb_put_hcd(hcd); > + return ret; > +} > + > +static int __exit ehci_mxc_drv_remove(struct platform_device *pdev) > +{ > + struct ehci_mxc_priv *priv = platform_get_drvdata(pdev); > + struct usb_hcd *hcd = priv->hcd; > + > + usb_remove_hcd(hcd); > + iounmap(hcd->regs); > + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); > + usb_put_hcd(hcd); > + platform_set_drvdata(pdev, NULL); > + > + clk_disable(priv->usbclk); > + clk_put(priv->usbclk); > + if (priv->ahbclk) { > + clk_disable(priv->ahbclk); > + clk_put(priv->ahbclk); > + } > + > + kfree(priv); > + > + return 0; > +} > + > +static void ehci_mxc_drv_shutdown(struct platform_device *pdev) > +{ > + struct ehci_mxc_priv *priv = platform_get_drvdata(pdev); > + struct usb_hcd *hcd = priv->hcd; > + > + if (hcd->driver->shutdown) > + hcd->driver->shutdown(hcd); > +} > + > +MODULE_ALIAS("platform:mxc-ehci"); > + > +static struct platform_driver ehci_mxc_driver = { > + .probe = ehci_mxc_drv_probe, > + .remove = __exit_p(ehci_mxc_drv_remove), > + .shutdown = ehci_mxc_drv_shutdown, > + .driver = { > + .name = "mxc-ehci", > + }, > +}; > -- > 1.6.3.1 > > -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html