Re: [PATCH 7/8] Add EHCI support for MX27 and MX31 based boards

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Sascha,

On Fri, Jun 26, 2009 at 10:45:50AM +0200, Sascha Hauer wrote:
> On Thu, Jun 25, 2009 at 02:11:56PM +0200, Daniel Mack wrote:
> > +	/* 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.

Yes, fully agreed. See the new patch below. Better now?

There is need to add the special cases CPUs other than MX31, but at
least the skeleton is there now, and it is serparated from the generic
host controller driver.

> > +	/* 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?

I still got trouble with that on my board and I can't really figure out
what the problem is. Adding an mdelay() here gives some improvement, but
for the OTG port, I still get 0x24e4/0x2524 as ID in the xvcr's init
function, but I'm not totally sure I can rule out the hardware. That's
why I was asking for more testers.


> > +	/* 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.

In my case, this isn't really a fatal situation since the VBUS power is
not even switched by th 1504. So if this fails, USB will still work
normally. Hence I removed the error handling for this condition.

Daniel


commit 04bcb5f8471aa181bcd5a4edd2357fd366281a3f
Author: Daniel Mack <daniel@xxxxxxxx>
Date:   Thu Jun 25 13:07:54 2009 +0200

    Add EHCI support for MX27 and MX31 based boards
    
    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>

diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile
index 545412f..20a9dba 100644
--- a/arch/arm/plat-mxc/Makefile
+++ b/arch/arm/plat-mxc/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_ARCH_MX2) += iomux-mx1-mx2.o dma-mx1-mx2.o
 obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o
 obj-$(CONFIG_MXC_PWM)  += pwm.o
 obj-$(CONFIG_MXC_ULPI) += ulpi.o
+obj-$(CONFIG_USB_EHCI_MXC)	+= ehci.o
+
diff --git a/arch/arm/plat-mxc/ehci.c b/arch/arm/plat-mxc/ehci.c
new file mode 100644
index 0000000..2539454
--- /dev/null
+++ b/arch/arm/plat-mxc/ehci.c
@@ -0,0 +1,89 @@
+/*
+ * 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 <mach/hardware.h>
+#include <mach/mxc_ehci.h>
+#include <asm/io.h>
+
+#define USBCTRL_OTGBASE_OFFSET	0x600
+
+#define MX31_OTG_SIC_SHIFT	29
+#define MX31_OTG_SIC_MASK	(0xf << MX31_OTG_SIC_SHIFT)
+#define MX31_OTG_PM_BIT		(1 << 24)
+
+#define MX31_H2_SIC_SHIFT	21
+#define MX31_H2_SIC_MASK	(0xf << MX31_H2_SIC_SHIFT)
+#define MX31_H2_PM_BIT		(1 << 16)
+#define MX31_H2_DT_BIT		(1 << 5)
+
+#define MX31_H1_SIC_SHIFT	13
+#define MX31_H1_SIC_MASK	(0xf << MX31_H1_SIC_SHIFT)
+#define MX31_H1_PM_BIT		(1 << 8)
+#define MX31_H1_DT_BIT		(1 << 4)
+
+int mxc_set_usbcontrol(int port, unsigned int flags)
+{
+	unsigned int v;
+
+	if (cpu_is_mx31()) {
+		v = readl(IO_ADDRESS(OTG_BASE_ADDR + USBCTRL_OTGBASE_OFFSET));
+
+		switch (port) {
+		case 0:	/* OTG port */
+			v &= ~(MX31_OTG_SIC_MASK | MX31_OTG_PM_BIT);
+			v |= (flags & MXC_EHCI_INTERFACE_MASK)
+					<< MX31_OTG_SIC_SHIFT;
+			if (flags & MXC_EHCI_POWER_PINS_ENABLED)
+				v |= MX31_OTG_PM_BIT;
+
+			break;
+		case 1: /* H1 port */
+			v &= ~(MX31_H1_SIC_MASK | MX31_H1_PM_BIT);
+			v |= (flags & MXC_EHCI_INTERFACE_MASK)
+						<< MX31_H1_SIC_SHIFT;
+			if (flags & MXC_EHCI_POWER_PINS_ENABLED)
+				v |= MX31_H1_PM_BIT;
+
+			if (!(flags & MXC_EHCI_TTL_ENABLED))
+				v |= MX31_H1_DT_BIT;
+
+			break;
+		case 2:	/* H2 port */
+			v &= ~(MX31_H2_SIC_MASK | MX31_H2_PM_BIT);
+			v |= (flags & MXC_EHCI_INTERFACE_MASK)
+						<< MX31_H2_SIC_SHIFT;
+			if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
+				v |= MX31_H2_PM_BIT;
+
+			if (!(flags & MXC_EHCI_TTL_ENABLED))
+				v |= MX31_H2_DT_BIT;
+
+			break;
+		}
+
+		writel(v, IO_ADDRESS(OTG_BASE_ADDR + USBCTRL_OTGBASE_OFFSET));
+		return 0;
+	}
+
+	printk(KERN_WARNING
+		"%s() unable to setup USBCONTROL for this CPU\n", __func__);
+	return -EINVAL;
+}
+
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..7259edc
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mxc_ehci.h
@@ -0,0 +1,37 @@
+#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;
+};
+
+extern int mxc_set_usbcontrol(int port, unsigned int flags);
+
+#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..303d6f7
--- /dev/null
+++ b/drivers/usb/host/ehci-mxc.c
@@ -0,0 +1,286 @@
+/*
+ * 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
+
+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. */
+	ret = mxc_set_usbcontrol(pdev->id, pdata->flags);
+	if (ret < 0)
+		goto err_init;
+
+	/* call platform specific init function */
+	if (pdata->init) {
+		ret = pdata->init(pdev);
+		if (ret) {
+			dev_err(dev, "platform init failed\n");
+			goto err_init;
+		}
+	}
+
+	/* 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");
+	}
+
+	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",
+	},
+};
--
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

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux