9
0
Fork 0

USB i.MX: Add chipidea driver support

For proper USB function the usbmisc registers have to be initialized.
This patch adds a driver which matches for the usbmisc registers. This
driver is called from a new driver which binds to the USB ports to
configure the misc registers. After that the driver registers the EHCI
driver and an ULPI transceiver if necessary. Currently only host mode
is supported, but device support can be added later.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Sascha Hauer 2012-11-22 15:35:22 +01:00
parent 15fd89d0a4
commit a4076ddf65
7 changed files with 566 additions and 0 deletions

View File

@ -3,6 +3,8 @@ menuconfig USB
if USB
source drivers/usb/imx/Kconfig
source drivers/usb/host/Kconfig
source drivers/usb/otg/Kconfig

View File

@ -1,4 +1,5 @@
obj-$(CONFIG_USB) += core/
obj-$(CONFIG_USB_IMX_CHIPIDEA) += imx/
obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_STORAGE) += storage/
obj-y += host/

15
drivers/usb/imx/Kconfig Normal file
View File

@ -0,0 +1,15 @@
config USB_IMX_CHIPIDEA
bool "i.MX USB support (read help)"
depends on ARCH_IMX
help
The Freescale i.MX SoCs have a variant of the chipidea ci13xxx for
USB support. Traditionally in barebox this is supported through the
EHCI driver for USB host and the ARC USB gadget driver for device.
This option instead enables support for i.MX chipidea support which
acts as a toplevel driver for the i.MX USB support. The chipidea
support also configures the usbmisc registers which traditionally
are configured in the board file.
This driver is recommended for new designs, but it needs board
support to work.
It's safe to say yes here. Also select EHCI support for USB host.

1
drivers/usb/imx/Makefile Normal file
View File

@ -0,0 +1 @@
obj-$(CONFIG_USB_IMX_CHIPIDEA) += imx-usb-misc.o chipidea-imx.o

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>
*
* 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.
*/
#include <common.h>
#include <init.h>
#include <io.h>
#include <driver.h>
#include <usb/ehci.h>
#include <usb/chipidea-imx.h>
#include <usb/ulpi.h>
#define MXC_EHCI_PORTSC_MASK (0xf << 28)
static int imx_chipidea_probe(struct device_d *dev)
{
struct imxusb_platformdata *pdata = dev->platform_data;
int ret;
void __iomem *base;
struct ehci_data data;
uint32_t portsc;
if (!pdata) {
dev_err(dev, "no pdata!\n");
return -EINVAL;
}
base = dev_request_mem_region(dev, 0);
if (!base)
return -ENODEV;
portsc = readl(base + 0x184);
portsc &= ~MXC_EHCI_PORTSC_MASK;
portsc |= pdata->flags & MXC_EHCI_PORTSC_MASK;
writel(portsc, base + 0x184);
ret = imx_usbmisc_port_init(dev->id, pdata->flags);
if (ret) {
dev_err(dev, "failed to init misc regs: %s\n", strerror(-ret));
return ret;
}
if ((pdata->flags & MXC_EHCI_PORTSC_MASK) == MXC_EHCI_MODE_ULPI) {
dev_dbg(dev, "using ULPI phy\n");
if (IS_ENABLED(CONFIG_USB_ULPI)) {
ret = ulpi_setup(base + 0x170, 1);
} else {
dev_err(dev, "no ULPI support available\n");
ret = -ENODEV;
}
if (ret)
return ret;
}
data.hccr = base + 0x100;
data.hcor = base + 0x140;
data.flags = EHCI_HAS_TT;
if (pdata->mode == IMX_USB_MODE_HOST) {
ret = ehci_register(dev, &data);
} else {
/*
* Not yet implemented. Register USB gadget driver here.
*/
ret = -ENOSYS;
}
return ret;
};
static struct driver_d imx_chipidea_driver = {
.name = "imx-usb",
.probe = imx_chipidea_probe,
};
static int imx_chipidea_init(void)
{
return platform_driver_register(&imx_chipidea_driver);
}
device_initcall(imx_chipidea_init);

View File

@ -0,0 +1,410 @@
/*
* Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
* Copyright (C) 2010 Freescale Semiconductor, Inc.
*
* 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.
*/
#include <common.h>
#include <init.h>
#include <io.h>
#include <usb/chipidea-imx.h>
#define MX25_OTG_SIC_SHIFT 29
#define MX25_OTG_SIC_MASK (0x3 << MX25_OTG_SIC_SHIFT)
#define MX25_OTG_PM_BIT (1 << 24)
#define MX25_OTG_PP_BIT (1 << 11)
#define MX25_OTG_OCPOL_BIT (1 << 3)
#define MX25_H1_SIC_SHIFT 21
#define MX25_H1_SIC_MASK (0x3 << MX25_H1_SIC_SHIFT)
#define MX25_H1_PP_BIT (1 << 18)
#define MX25_H1_PM_BIT (1 << 8)
#define MX25_H1_IPPUE_UP_BIT (1 << 7)
#define MX25_H1_IPPUE_DOWN_BIT (1 << 6)
#define MX25_H1_TLL_BIT (1 << 5)
#define MX25_H1_USBTE_BIT (1 << 4)
#define MX25_H1_OCPOL_BIT (1 << 2)
static __maybe_unused int mx25_initialize_usb_hw(void __iomem *base, int port, unsigned int flags)
{
unsigned int v;
v = readl(base);
switch (port) {
case 0: /* OTG port */
v &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PM_BIT | MX25_OTG_PP_BIT |
MX25_OTG_OCPOL_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX25_OTG_PM_BIT;
if (flags & MXC_EHCI_PWR_PIN_ACTIVE_HIGH)
v |= MX25_OTG_PP_BIT;
if (!(flags & MXC_EHCI_OC_PIN_ACTIVE_LOW))
v |= MX25_OTG_OCPOL_BIT;
break;
case 1: /* H1 port */
v &= ~(MX25_H1_SIC_MASK | MX25_H1_PM_BIT | MX25_H1_PP_BIT |
MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT | MX25_H1_USBTE_BIT |
MX25_H1_IPPUE_DOWN_BIT | MX25_H1_IPPUE_UP_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX25_H1_PM_BIT;
if (flags & MXC_EHCI_PWR_PIN_ACTIVE_HIGH)
v |= MX25_H1_PP_BIT;
if (!(flags & MXC_EHCI_OC_PIN_ACTIVE_LOW))
v |= MX25_H1_OCPOL_BIT;
if (!(flags & MXC_EHCI_TLL_ENABLED))
v |= MX25_H1_TLL_BIT;
if (flags & MXC_EHCI_INTERNAL_PHY)
v |= MX25_H1_USBTE_BIT;
if (flags & MXC_EHCI_IPPUE_DOWN)
v |= MX25_H1_IPPUE_DOWN_BIT;
if (flags & MXC_EHCI_IPPUE_UP)
v |= MX25_H1_IPPUE_UP_BIT;
break;
default:
return -EINVAL;
}
writel(v, base);
return 0;
}
#define MX27_OTG_SIC_SHIFT 29
#define MX27_OTG_SIC_MASK (0x3 << MX27_OTG_SIC_SHIFT)
#define MX27_OTG_PM_BIT (1 << 24)
#define MX27_H2_SIC_SHIFT 21
#define MX27_H2_SIC_MASK (0x3 << MX27_H2_SIC_SHIFT)
#define MX27_H2_PM_BIT (1 << 16)
#define MX27_H2_DT_BIT (1 << 5)
#define MX27_H1_SIC_SHIFT 13
#define MX27_H1_SIC_MASK (0x3 << MX27_H1_SIC_SHIFT)
#define MX27_H1_PM_BIT (1 << 8)
#define MX27_H1_DT_BIT (1 << 4)
static __maybe_unused int mx27_mx31_initialize_usb_hw(void __iomem *base, int port, unsigned int flags)
{
unsigned int v;
v = readl(base);
switch (port) {
case 0: /* OTG port */
v &= ~(MX27_OTG_SIC_MASK | MX27_OTG_PM_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX27_OTG_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX27_OTG_PM_BIT;
break;
case 1: /* H1 port */
v &= ~(MX27_H1_SIC_MASK | MX27_H1_PM_BIT | MX27_H1_DT_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX27_H1_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX27_H1_PM_BIT;
if (!(flags & MXC_EHCI_TLL_ENABLED))
v |= MX27_H1_DT_BIT;
break;
case 2: /* H2 port */
v &= ~(MX27_H2_SIC_MASK | MX27_H2_PM_BIT | MX27_H2_DT_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX27_H2_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX27_H2_PM_BIT;
if (!(flags & MXC_EHCI_TLL_ENABLED))
v |= MX27_H2_DT_BIT;
break;
default:
return -EINVAL;
}
writel(v, base);
return 0;
}
#define USBCTRL_OTGBASE_OFFSET 0x600
#define MX35_OTG_SIC_SHIFT 29
#define MX35_OTG_SIC_MASK (0x3 << MX35_OTG_SIC_SHIFT)
#define MX35_OTG_PM_BIT (1 << 24)
#define MX35_OTG_PP_BIT (1 << 11)
#define MX35_OTG_OCPOL_BIT (1 << 3)
#define MX35_H1_SIC_SHIFT 21
#define MX35_H1_SIC_MASK (0x3 << MX35_H1_SIC_SHIFT)
#define MX35_H1_PP_BIT (1 << 18)
#define MX35_H1_PM_BIT (1 << 8)
#define MX35_H1_IPPUE_UP_BIT (1 << 7)
#define MX35_H1_IPPUE_DOWN_BIT (1 << 6)
#define MX35_H1_TLL_BIT (1 << 5)
#define MX35_H1_USBTE_BIT (1 << 4)
#define MX35_H1_OCPOL_BIT (1 << 2)
static __maybe_unused int mx35_initialize_usb_hw(void __iomem *base, int port, unsigned int flags)
{
unsigned int v;
v = readl(base);
switch (port) {
case 0: /* OTG port */
v &= ~(MX35_OTG_SIC_MASK | MX35_OTG_PM_BIT | MX35_OTG_PP_BIT |
MX35_OTG_OCPOL_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX35_OTG_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX35_OTG_PM_BIT;
if (flags & MXC_EHCI_PWR_PIN_ACTIVE_HIGH)
v |= MX35_OTG_PP_BIT;
if (!(flags & MXC_EHCI_OC_PIN_ACTIVE_LOW))
v |= MX35_OTG_OCPOL_BIT;
break;
case 1: /* H1 port */
v &= ~(MX35_H1_SIC_MASK | MX35_H1_PM_BIT | MX35_H1_PP_BIT |
MX35_H1_OCPOL_BIT | MX35_H1_TLL_BIT | MX35_H1_USBTE_BIT |
MX35_H1_IPPUE_DOWN_BIT | MX35_H1_IPPUE_UP_BIT);
v |= (flags & MXC_EHCI_INTERFACE_MASK) << MX35_H1_SIC_SHIFT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX35_H1_PM_BIT;
if (flags & MXC_EHCI_PWR_PIN_ACTIVE_HIGH)
v |= MX35_H1_PP_BIT;
if (!(flags & MXC_EHCI_OC_PIN_ACTIVE_LOW))
v |= MX35_H1_OCPOL_BIT;
if (!(flags & MXC_EHCI_TLL_ENABLED))
v |= MX35_H1_TLL_BIT;
if (flags & MXC_EHCI_INTERNAL_PHY)
v |= MX35_H1_USBTE_BIT;
if (flags & MXC_EHCI_IPPUE_DOWN)
v |= MX35_H1_IPPUE_DOWN_BIT;
if (flags & MXC_EHCI_IPPUE_UP)
v |= MX35_H1_IPPUE_UP_BIT;
break;
default:
return -EINVAL;
}
writel(v, base);
return 0;
}
/* USB_CTRL */
#define MX5_OTG_UCTRL_OWIE_BIT (1 << 27) /* OTG wakeup intr enable */
#define MX5_OTG_UCTRL_OPM_BIT (1 << 24) /* OTG power mask */
#define MX5_H1_UCTRL_H1UIE_BIT (1 << 12) /* Host1 ULPI interrupt enable */
#define MX5_H1_UCTRL_H1WIE_BIT (1 << 11) /* HOST1 wakeup intr enable */
#define MX5_H1_UCTRL_H1PM_BIT (1 << 8) /* HOST1 power mask */
/* USB_PHY_CTRL_FUNC */
#define MX5_OTG_PHYCTRL_OC_POL_BIT (1 << 9) /* OTG Polarity of Overcurrent */
#define MX5_OTG_PHYCTRL_OC_DIS_BIT (1 << 8) /* OTG Disable Overcurrent Event */
#define MX5_H1_OC_POL_BIT (1 << 6) /* UH1 Polarity of Overcurrent */
#define MX5_H1_OC_DIS_BIT (1 << 5) /* UH1 Disable Overcurrent Event */
#define MX5_OTG_PHYCTRL_PWR_POL_BIT (1 << 3) /* OTG Power Pin Polarity */
/* USBH2CTRL */
#define MX5_H2_UCTRL_H2UIE_BIT (1 << 8)
#define MX5_H2_UCTRL_H2WIE_BIT (1 << 7)
#define MX5_H2_UCTRL_H2PM_BIT (1 << 4)
#define MX5_USBCTRL_OFFSET 0x0
#define MX5_UTMI_PHY_CTRL_0 0x8
#define MX5_UTMI_PHY_CTRL_1 0xc
#define MX5_USBH2CTRL_OFFSET 0x14
static __maybe_unused int mx5_initialize_usb_hw(void __iomem *base, int port,
unsigned int flags)
{
unsigned int v;
switch (port) {
case 0: /* OTG port */
if (!(flags & MXC_EHCI_INTERNAL_PHY))
return 0;
/* Adjust UTMI PHY frequency to 24MHz */
v = readl(base + MX5_UTMI_PHY_CTRL_1);
v = (v & ~0x3) | 0x01;
writel(v, base + MX5_UTMI_PHY_CTRL_1);
v = readl(base + MX5_UTMI_PHY_CTRL_0);
v &= ~MX5_OTG_PHYCTRL_OC_POL_BIT;
v &= ~MX5_OTG_PHYCTRL_OC_DIS_BIT;
v &= ~MX5_OTG_PHYCTRL_PWR_POL_BIT;
if (flags & MXC_EHCI_OC_PIN_ACTIVE_LOW)
v |= MX5_OTG_PHYCTRL_OC_POL_BIT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX5_OTG_PHYCTRL_OC_DIS_BIT;
if (flags & MXC_EHCI_PWR_PIN_ACTIVE_HIGH)
v |= MX5_OTG_PHYCTRL_PWR_POL_BIT;
writel(v, base + MX5_UTMI_PHY_CTRL_0);
v = readl(base + MX5_USBCTRL_OFFSET);
v &= ~MX5_OTG_UCTRL_OWIE_BIT;
v &= ~MX5_OTG_UCTRL_OPM_BIT;
if (flags & MXC_EHCI_WAKEUP_ENABLED)
v |= MX5_OTG_UCTRL_OWIE_BIT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX5_OTG_UCTRL_OPM_BIT;
writel(v, base + MX5_USBCTRL_OFFSET);
break;
case 1: /* H1 port */
v = readl(base + MX5_USBCTRL_OFFSET);
v &= ~MX5_H1_UCTRL_H1PM_BIT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX5_H1_UCTRL_H1PM_BIT;
writel(v, base + MX5_USBCTRL_OFFSET);
break;
case 2: /* H2 port */
v = readl(base + MX5_USBH2CTRL_OFFSET);
v &= ~MX5_H2_UCTRL_H2PM_BIT;
if (!(flags & MXC_EHCI_POWER_PINS_ENABLED))
v |= MX5_H2_UCTRL_H2PM_BIT;
writel(v, base + MX5_USBH2CTRL_OFFSET);
break;
default:
return -EINVAL;
}
return 0;
}
static struct platform_device_id imx_usbmisc_ids[] = {
#ifdef CONFIG_ARCH_IMX25
{
.name = "imx25-usb-misc",
.driver_data = (unsigned long)&mx25_initialize_usb_hw,
},
#endif
#ifdef CONFIG_ARCH_IMX27
{
.name = "imx27-usb-misc",
.driver_data = (unsigned long)&mx27_mx31_initialize_usb_hw,
},
#endif
#ifdef CONFIG_ARCH_IMX31
{
.name = "imx31-usb-misc",
.driver_data = (unsigned long)&mx27_mx31_initialize_usb_hw,
},
#endif
#ifdef CONFIG_ARCH_IMX35
{
.name = "imx35-usb-misc",
.driver_data = (unsigned long)&mx35_initialize_usb_hw,
},
#endif
#ifdef CONFIG_ARCH_IMX51
{
.name = "imx51-usb-misc",
.driver_data = (unsigned long)&mx5_initialize_usb_hw,
},
#endif
#ifdef CONFIG_ARCH_IMX53
{
.name = "imx53-usb-misc",
.driver_data = (unsigned long)&mx5_initialize_usb_hw,
},
#endif
{
/* sentinel */
},
};
static int (*__imx_usbmisc_port_init)(void __iomem *base, int port, unsigned flags);
static void __iomem *usbmisc_base;
int imx_usbmisc_port_init(int port, unsigned flags)
{
if (!__imx_usbmisc_port_init)
return -ENODEV;
return __imx_usbmisc_port_init(usbmisc_base, port, flags);
}
static int imx_usbmisc_probe(struct device_d *dev)
{
struct imx_serial_devtype_data *devtype;
int ret;
ret = dev_get_drvdata(dev, (unsigned long *)&devtype);
if (ret)
return ret;
usbmisc_base = dev_request_mem_region(dev, 0);
if (!usbmisc_base)
return -ENOMEM;
__imx_usbmisc_port_init = (void *)devtype;
return 0;
}
static struct driver_d imx_usbmisc_driver = {
.name = "imx-usbmisc",
.probe = imx_usbmisc_probe,
.id_table = imx_usbmisc_ids,
};
static int imx_usbmisc_init(void)
{
platform_driver_register(&imx_usbmisc_driver);
return 0;
}
coredevice_initcall(imx_usbmisc_init);

View File

@ -0,0 +1,46 @@
#ifndef __USB_CHIPIDEA_IMX_H
#define __USB_CHIPIDEA_IMX_H
/*
* POTSC flags
*/
#define MXC_EHCI_SERIAL (1 << 29)
#define MXC_EHCI_MODE_UTMI_8BIT (0 << 30)
#define MXC_EHCI_MODE_UTMI_16_BIT ((0 << 30) | (1 << 28))
#define MXC_EHCI_MODE_PHILIPS (1 << 30)
#define MXC_EHCI_MODE_ULPI (2 << 30)
#define MXC_EHCI_MODE_SERIAL (3 << 30)
/*
* USB misc flags
*/
#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_PWR_PIN_ACTIVE_HIGH (1 << 6)
#define MXC_EHCI_OC_PIN_ACTIVE_LOW (1 << 7)
#define MXC_EHCI_TLL_ENABLED (1 << 8)
#define MXC_EHCI_INTERNAL_PHY (1 << 9)
#define MXC_EHCI_IPPUE_DOWN (1 << 10)
#define MXC_EHCI_IPPUE_UP (1 << 11)
#define MXC_EHCI_WAKEUP_ENABLED (1 << 12)
#define MXC_EHCI_ITC_NO_THRESHOLD (1 << 13)
enum imx_usb_mode {
IMX_USB_MODE_HOST,
IMX_USB_MODE_DEVICE,
};
struct imxusb_platformdata {
unsigned long flags;
enum imx_usb_mode mode;
};
int imx_usbmisc_port_init(int port, unsigned flags);
#endif /* __USB_CHIPIDEA_IMX_H */