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

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

 



I had a segmentation fault when powering the kernel off with this module loaded, here is how I corrected it (see below).

Daniel Mack wrote:
From: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>

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               |  326 +++++++++++++++++++++++++++++
 5 files changed, 374 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 c6c816b..86a85c3 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -57,6 +57,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 845479f..5d7ecf1 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -85,6 +85,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 c637207..808b5d4 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1047,6 +1047,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..3ebd0b3
--- /dev/null
+++ b/drivers/usb/host/ehci-mxc.c
@@ -0,0 +1,326 @@
+/*
+ * 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 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;
+       }
+
+       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 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));
+       switch (pdev->id) {
+       case 0:
+               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:
+               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:
+               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;
+               }
+       }
+
+       /* Initialize the transceiver */
+       if (pdata->xcvr) {
+               pdata->xcvr->access_priv = hcd->regs + ULPI_VIEWPORT_OFFSET;
+               ret = usb_xcvr_init(pdata->xcvr);
+               if (ret)
+                       dev_err(dev, "unable to init transceiver\n");
+
+               ret = usb_xcvr_set_vbus(pdata->xcvr, 1);
+               if (ret)
+                       dev_err(dev, "unable to enable vbus on transceiver\n");
+       }
+
+       /* Set to Host mode */
+       temp = readl(hcd->regs + 0x1a8);
+       writel(temp | 0x3, hcd->regs + 0x1a8);
+
+       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;
+}
+
+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 = usb_hcd_platform_shutdown,
+       .driver = {
+                  .name = "mxc-ehci",
+       },
+};

The call to usb_hcd_platform_shutdown does not work, because this function awaits that the dev->priv_data directly is a usb_hcd struct. However, in our case we have a ehci_mxc_priv struct. Here is a patch suggesting a way to correct this (inspired by the usb_hcd_platform_shutdown implementation):

+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 = usb_hcd_platform_shutdown,
+       .shutdown = ehci_mxc_drv_shutdown,
        .driver = {
                   .name = "mxc-ehci",
        },

Best regards

Val

--
Valentin Longchamp, PhD Student, EPFL-STI-LSRO1
valentin.longchamp@xxxxxxx, Phone: +41216937827
http://people.epfl.ch/valentin.longchamp
MEA3485, Station 9, CH-1015 Lausanne
--
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