summaryrefslogtreecommitdiff
path: root/arch/arm/plat-mxs/usb_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/plat-mxs/usb_common.c')
-rw-r--r--arch/arm/plat-mxs/usb_common.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/arch/arm/plat-mxs/usb_common.c b/arch/arm/plat-mxs/usb_common.c
new file mode 100644
index 000000000000..16ef4736b4d0
--- /dev/null
+++ b/arch/arm/plat-mxs/usb_common.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*!
+ *@defgroup USB ARC OTG USB Driver
+ */
+
+/*!
+ * @file usb_common.c
+ *
+ * @brief platform related part of usb driver.
+ * @ingroup USB
+ */
+
+/*!
+ *Include files
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/fsl_xcvr.h>
+#include <mach/arc_otg.h>
+#include <mach/hardware.h>
+#include <linux/io.h>
+#define MXC_NUMBER_USB_TRANSCEIVER 6
+struct fsl_xcvr_ops *g_xc_ops[MXC_NUMBER_USB_TRANSCEIVER] = { NULL };
+
+void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops)
+{
+ int i;
+
+ pr_debug("%s\n", __func__);
+ for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) {
+ if (g_xc_ops[i] == NULL) {
+ g_xc_ops[i] = xcvr_ops;
+ return;
+ }
+ }
+
+ pr_debug("Failed %s\n", __func__);
+}
+EXPORT_SYMBOL(fsl_usb_xcvr_register);
+
+void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops)
+{
+ int i;
+
+ pr_debug("%s\n", __func__);
+ for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) {
+ if (g_xc_ops[i] == xcvr_ops) {
+ g_xc_ops[i] = NULL;
+ return;
+ }
+ }
+
+ pr_debug("Failed %s\n", __func__);
+}
+EXPORT_SYMBOL(fsl_usb_xcvr_unregister);
+
+void fsl_platform_set_test_mode(
+ struct fsl_usb2_platform_data *pdata,
+ enum usb_test_mode mode)
+{
+}
+EXPORT_SYMBOL(fsl_platform_set_test_mode);
+
+/* enable/disable high-speed disconnect detector of phy ctrl */
+void fsl_platform_set_usb_phy_dis(struct fsl_usb2_platform_data *pdata,
+ bool enable)
+{
+ if (enable)
+ __raw_writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ IO_ADDRESS(pdata->phy_regs) + HW_USBPHY_CTRL_SET);
+ else
+ __raw_writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ IO_ADDRESS(pdata->phy_regs) + HW_USBPHY_CTRL_CLR);
+}
+EXPORT_SYMBOL(fsl_platform_set_usb_phy_dis);
+
+
+#if defined(CONFIG_USB_OTG)
+static struct resource *otg_resources;
+
+struct resource *otg_get_resources(void)
+{
+ pr_debug("otg_get_resources\n");
+ return otg_resources;
+}
+EXPORT_SYMBOL(otg_get_resources);
+
+int otg_set_resources(struct resource *resources)
+{
+ otg_resources = resources;
+ return 0;
+}
+EXPORT_SYMBOL(otg_set_resources);
+#endif
+
+/*!
+ * Register remote wakeup by this usb controller
+ *
+ * @param pdev: platform_device for this usb controller
+ *
+ * @return 0 or negative error code in case not supportted.
+ */
+static int usb_register_remote_wakeup(struct platform_device *pdev)
+{
+ struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data;
+ struct resource *res;
+ int irq;
+
+ pr_debug("%s: pdev=0x%p \n", __func__, pdev);
+ if (!(pdata->wake_up_enable))
+ return -ECANCELED;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev,
+ "Found HC with no IRQ. Check %s setup!\n",
+ dev_name(&pdev->dev));
+ return -ENODEV;
+ }
+ irq = res->start;
+ pdev->dev.power.can_wakeup = 1;
+ enable_irq_wake(irq);
+
+ return 0;
+}
+
+static struct fsl_xcvr_ops *fsl_usb_get_xcvr(char *name)
+{
+ int i;
+
+ pr_debug("%s\n", __func__);
+ if (name == NULL) {
+ printk(KERN_ERR "get_xcvr(): No tranceiver name\n");
+ return NULL;
+ }
+
+ for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) {
+ if (strcmp(g_xc_ops[i]->name, name) == 0)
+ return g_xc_ops[i];
+ }
+ pr_debug("Failed %s\n", __func__);
+ return NULL;
+}
+
+/* The dmamask must be set for EHCI to work */
+static u64 ehci_dmamask = ~(u32) 0;
+
+static int instance_id = ~(u32) 0;
+struct platform_device *host_pdev_register(struct resource *res, int n_res,
+ struct fsl_usb2_platform_data
+ *config)
+{
+ struct platform_device *pdev;
+ int rc;
+
+ pr_debug("register host res=0x%p, size=%d\n", res, n_res);
+
+ pdev = platform_device_register_simple("fsl-ehci",
+ instance_id, res, n_res);
+ if (IS_ERR(pdev)) {
+ pr_debug("can't register %s Host, %ld\n",
+ config->name, PTR_ERR(pdev));
+ return NULL;
+ }
+
+ pdev->dev.coherent_dma_mask = 0xffffffff;
+ pdev->dev.dma_mask = &ehci_dmamask;
+
+ /*
+ * platform_device_add_data() makes a copy of
+ * the platform_data passed in. That makes it
+ * impossible to share the same config struct for
+ * all OTG devices (host,gadget,otg). So, just
+ * set the platorm_data pointer ourselves.
+ */
+ rc = platform_device_add_data(pdev, config,
+ sizeof(struct fsl_usb2_platform_data));
+ if (rc) {
+ platform_device_unregister(pdev);
+ return NULL;
+ }
+
+ pr_debug(KERN_INFO "usb: %s host (%s) registered\n", config->name,
+ config->transceiver);
+ pr_debug("pdev=0x%p dev=0x%p resources=0x%p pdata=0x%p\n",
+ pdev, &pdev->dev, pdev->resource, pdev->dev.platform_data);
+
+ instance_id++;
+
+ return pdev;
+}
+
+int usb_phy_enable(struct fsl_usb2_platform_data *pdata)
+{
+ u32 tmp;
+ void __iomem *phy_reg = IO_ADDRESS(pdata->phy_regs);
+ void __iomem *usb_reg = pdata->regs;
+ void __iomem *usbcmd, *phy_ctrl, *portsc;
+
+ /* Reset USB IP */
+ usbcmd = usb_reg + UOG_USBCMD;
+ tmp = __raw_readl(usbcmd); /* usb command */
+ tmp &= ~UCMD_RUN_STOP;
+ __raw_writel(tmp, usbcmd);
+ while (__raw_readl(usbcmd) & UCMD_RUN_STOP)
+ ;
+
+ tmp |= UCMD_RESET;
+ __raw_writel(tmp, usbcmd);
+ while (__raw_readl(usbcmd) & UCMD_RESET)
+ ;
+ mdelay(10);
+
+ /* Reset USBPHY module */
+ phy_ctrl = phy_reg + HW_USBPHY_CTRL;
+ tmp = __raw_readl(phy_ctrl);
+ tmp |= BM_USBPHY_CTRL_SFTRST;
+ __raw_writel(tmp, phy_ctrl);
+ udelay(10);
+
+ /* Remove CLKGATE and SFTRST */
+ tmp = __raw_readl(phy_ctrl);
+ tmp &= ~(BM_USBPHY_CTRL_CLKGATE | BM_USBPHY_CTRL_SFTRST);
+ __raw_writel(tmp, phy_ctrl);
+ udelay(10);
+
+ /* set UTMI xcvr */
+ /* Workaround an IC issue for ehci driver:
+ * when turn off root hub port power, EHCI set
+ * PORTSC reserved bits to be 0, but PTW with 0
+ * means 8 bits tranceiver width, here change
+ * it back to be 16 bits and do PHY diable and
+ * then enable.
+ */
+ portsc = usb_reg + UOG_PORTSC1;
+ tmp = __raw_readl(portsc);
+ tmp &= ~PORTSC_PTS_MASK;
+ tmp |= (PORTSC_PTS_UTMI | PORTSC_PTW);
+ __raw_writel(tmp, portsc);
+
+ /* Power up the PHY */
+ __raw_writel(0, phy_reg + HW_USBPHY_PWD);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_phy_enable);
+
+static int otg_used;
+
+int usbotg_init(struct platform_device *pdev)
+{
+ struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data;
+ struct fsl_xcvr_ops *xops;
+
+ pr_debug("%s: pdev=0x%p pdata=0x%p\n", __func__, pdev, pdata);
+
+ xops = fsl_usb_get_xcvr(pdata->transceiver);
+ if (!xops) {
+ printk(KERN_ERR "DR transceiver ops missing\n");
+ return -EINVAL;
+ }
+ pdata->xcvr_ops = xops;
+ pdata->xcvr_type = xops->xcvr_type;
+ pdata->pdev = pdev;
+
+ if (!otg_used) {
+ pr_debug("%s: grab pins\n", __func__);
+ if (xops->init)
+ xops->init(xops);
+ usb_phy_enable(pdata);
+ }
+
+ if ((pdata->operating_mode == FSL_USB2_DR_HOST) ||
+ (pdata->operating_mode == FSL_USB2_DR_OTG)) {
+ /* enable FS/LS device */
+ __raw_writel(BM_USBPHY_CTRL_ENUTMILEVEL2 | BM_USBPHY_CTRL_ENUTMILEVEL3
+ , IO_ADDRESS(pdata->phy_regs) + HW_USBPHY_CTRL_SET);
+ }
+
+ if (usb_register_remote_wakeup(pdev))
+ pr_debug("%s port is not a wakeup source.\n", pdata->name);
+
+ otg_used++;
+ pr_debug("%s: success\n", __func__);
+ return 0;
+}
+EXPORT_SYMBOL(usbotg_init);
+
+void usbotg_uninit(struct fsl_usb2_platform_data *pdata)
+{
+ pr_debug("%s\n", __func__);
+
+ if (pdata->xcvr_ops && pdata->xcvr_ops->uninit)
+ pdata->xcvr_ops->uninit(pdata->xcvr_ops);
+
+ pdata->regs = NULL;
+ otg_used--;
+}
+EXPORT_SYMBOL(usbotg_uninit);
+
+int fsl_usb_host_init(struct platform_device *pdev)
+{
+ struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data;
+ struct fsl_xcvr_ops *xops;
+ u32 tmp;
+ void __iomem *phy_reg = IO_ADDRESS(pdata->phy_regs);
+
+ pr_debug("%s: pdev=0x%p pdata=0x%p\n", __func__, pdev, pdata);
+
+ xops = fsl_usb_get_xcvr(pdata->transceiver);
+ if (!xops) {
+ printk(KERN_ERR "%s transceiver ops missing\n", pdata->name);
+ return -EINVAL;
+ }
+ pdata->xcvr_ops = xops;
+ pdata->xcvr_type = xops->xcvr_type;
+ pdata->pdev = pdev;
+
+ if (xops->init)
+ xops->init(xops);
+ usb_phy_enable(pdata);
+ /* enable FS/LS device */
+ tmp = __raw_readl(phy_reg + HW_USBPHY_CTRL);
+ tmp |= (BM_USBPHY_CTRL_ENUTMILEVEL2 | BM_USBPHY_CTRL_ENUTMILEVEL3);
+ __raw_writel(tmp, phy_reg + HW_USBPHY_CTRL);
+
+ if (usb_register_remote_wakeup(pdev))
+ pr_debug("%s port is not a wakeup source.\n", pdata->name);
+
+ pr_debug("%s: %s success\n", __func__, pdata->name);
+ return 0;
+}
+EXPORT_SYMBOL(fsl_usb_host_init);
+
+void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata)
+{
+ pr_debug("%s\n", __func__);
+
+ if (pdata->xcvr_ops && pdata->xcvr_ops->uninit)
+ pdata->xcvr_ops->uninit(pdata->xcvr_ops);
+
+ pdata->regs = NULL;
+}
+EXPORT_SYMBOL(fsl_usb_host_uninit);
+
+/*
+ * This function is used to debounce the reading value for id/vbus at
+ * the register of otgsc
+ */
+void usb_debounce_id_vbus(void)
+{
+ mdelay(3);
+}
+EXPORT_SYMBOL(usb_debounce_id_vbus);
+
+int usb_host_wakeup_irq(struct device *wkup_dev)
+{
+ return 0;
+}
+EXPORT_SYMBOL(usb_host_wakeup_irq);
+
+void usb_host_set_wakeup(struct device *wkup_dev, bool para)
+{
+ struct fsl_usb2_platform_data *pdata = wkup_dev->platform_data;
+ if (pdata->wake_up_enable)
+ pdata->wake_up_enable(pdata, para);
+}
+EXPORT_SYMBOL(usb_host_set_wakeup);
+
+#ifdef CONFIG_ARCH_MX28
+#define USBPHY_PHYS_ADDR USBPHY0_PHYS_ADDR
+#endif
+
+int usb_event_is_otg_wakeup(void)
+{
+ u32 wakeup_irq_bits;
+ wakeup_irq_bits = BM_USBPHY_CTRL_RESUME_IRQ | BM_USBPHY_CTRL_WAKEUP_IRQ;
+
+ if (__raw_readl(IO_ADDRESS(USBPHY_PHYS_ADDR) + HW_USBPHY_STATUS) && wakeup_irq_bits) {
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL(usb_event_is_otg_wakeup);
+
+int fsl_is_usb_plugged(void)
+{
+ return __raw_readl(IO_ADDRESS(USBPHY_PHYS_ADDR) + HW_USBPHY_STATUS) & \
+ BM_USBPHY_STATUS_DEVPLUGIN_STATUS;
+}
+EXPORT_SYMBOL(fsl_is_usb_plugged);
+
+void fsl_enable_usb_plugindetect(void)
+{
+ __raw_writel(BM_USBPHY_CTRL_ENDEVPLUGINDETECT,
+ IO_ADDRESS(USBPHY_PHYS_ADDR) + HW_USBPHY_CTRL_SET);
+}
+EXPORT_SYMBOL(fsl_enable_usb_plugindetect);
+