AM35x has musb interface and uses CPPI4.1 DMA engine. Current patch supports only PIO mode and there are on-going discussions on location of CPPI4.1 DMA. Signed-off-by: Ajay Kumar Gupta <ajay.gupta@xxxxxx> --- drivers/usb/musb/Kconfig | 4 +- drivers/usb/musb/Makefile | 4 + drivers/usb/musb/am3517.c | 536 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/musb/musb_core.c | 3 +- 4 files changed, 544 insertions(+), 3 deletions(-) create mode 100644 drivers/usb/musb/am3517.c diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index b4c783c..a29cf84 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -10,7 +10,7 @@ comment "Enable Host or Gadget support to see Inventra options" config USB_MUSB_HDRC depends on (USB || USB_GADGET) depends on (ARM || (BF54x && !BF544) || (BF52x && !BF522 && !BF523)) - select NOP_USB_XCEIV if (ARCH_DAVINCI || MACH_OMAP3EVM || BLACKFIN) + select NOP_USB_XCEIV if (ARCH_DAVINCI || MACH_OMAP3EVM || BLACKFIN || MACH_OMAP3517EVM) select TWL4030_USB if MACH_OMAP_3430SDP select USB_OTG_UTILS tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' @@ -140,7 +140,7 @@ config USB_MUSB_HDRC_HCD config MUSB_PIO_ONLY bool 'Disable DMA (always use PIO)' depends on USB_MUSB_HDRC - default y if USB_TUSB6010 + default USB_TUSB6010 || MACH_OMAP3517EVM help All data is copied between memory and FIFO by the CPU. DMA controllers are ignored. diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index 85710cc..9263033 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -19,7 +19,11 @@ ifeq ($(CONFIG_ARCH_OMAP2430),y) endif ifeq ($(CONFIG_ARCH_OMAP3430),y) + ifeq ($(CONFIG_MACH_OMAP3517EVM),y) + musb_hdrc-objs += am3517.o + else musb_hdrc-objs += omap2430.o + endif endif ifeq ($(CONFIG_BF54x),y) diff --git a/drivers/usb/musb/am3517.c b/drivers/usb/musb/am3517.c new file mode 100644 index 0000000..913a294 --- /dev/null +++ b/drivers/usb/musb/am3517.c @@ -0,0 +1,536 @@ +/* + * Texas Instruments AM3517 "glue layer" + * + * Copyright (c) 2010, by Texas Instruments + * + * Based on the DA8xx "glue layer" code. + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (c) 2008, MontaVista Software, Inc. <source@xxxxxxxxxx> + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <plat/control.h> +#include <plat/usb.h> + +#include "musb_core.h" + +/* + * AM3517 specific definitions + */ + +/* CPPI 4.1 queue manager registers */ +#define QMGR_PEND0_REG 0x4090 +#define QMGR_PEND1_REG 0x4094 +#define QMGR_PEND2_REG 0x4098 + +/* USB 2.0 PHY Control */ +#define CONF2_PHY_GPIOMODE (1 << 23) +#define CONF2_OTGMODE (3 << 14) +#define CONF2_SESENDEN (1 << 13) +#define CONF2_VBDTCTEN (1 << 12) +#define CONF2_REFFREQ_24MHZ (2 << 8) +#define CONF2_REFFREQ_26MHZ (7 << 8) +#define CONF2_REFFREQ_13MHZ (6 << 8) +#define CONF2_REFFREQ (0xf << 8) +#define CONF2_PHYCLKGD (1 << 7) +#define CONF2_VBUSSENSE (1 << 6) +#define CONF2_PHY_PLLON (1 << 5) +#define CONF2_RESET (1 << 4) +#define CONF2_PHYPWRDN (1 << 3) +#define CONF2_OTGPWRDN (1 << 2) +#define CONF2_DATPOL (1 << 1) + + +#define AM3517_TX_EP_MASK 0xffff /* EP0 + 15 Tx EPs */ +#define AM3517_RX_EP_MASK 0xfffe /* 15 Rx EPs */ + +#define AM3517_TX_INTR_MASK (AM3517_TX_EP_MASK << USB_INTR_TX_SHIFT) +#define AM3517_RX_INTR_MASK (AM3517_RX_EP_MASK << USB_INTR_RX_SHIFT) + +#define A_WAIT_BCON_TIMEOUT 1100 /* in ms */ + +static inline void phy_on(void) +{ + u32 devconf2; + + /* + * Start the on-chip PHY and its PLL. + */ + devconf2 = omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2); + + devconf2 &= ~(CONF2_RESET | CONF2_PHYPWRDN | CONF2_OTGPWRDN | + CONF2_OTGMODE | CONF2_REFFREQ | CONF2_PHY_GPIOMODE); + devconf2 |= CONF2_SESENDEN | CONF2_VBDTCTEN | CONF2_PHY_PLLON | + CONF2_REFFREQ_13MHZ | CONF2_DATPOL; + + omap_ctrl_writel(devconf2, AM35XX_CONTROL_DEVCONF2); + + DBG(1, "Waiting for PHY clock good...\n"); + while (!(omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2) + & CONF2_PHYCLKGD)) + cpu_relax(); +} + +static inline void phy_off(void) +{ + u32 devconf2; + + /* + * Power down the on-chip PHY. + */ + devconf2 = omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2); + + devconf2 &= ~CONF2_PHY_PLLON; + devconf2 |= CONF2_PHYPWRDN | CONF2_OTGPWRDN; + omap_ctrl_writel(devconf2, AM35XX_CONTROL_DEVCONF2); +} + +/** + * musb_platform_enable - enable interrupts + */ +void musb_platform_enable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + u32 epmask, coremask; + + /* Workaround: setup IRQs through both register sets. */ + epmask = ((musb->epmask & AM3517_TX_EP_MASK) << USB_INTR_TX_SHIFT) | + ((musb->epmask & AM3517_RX_EP_MASK) << USB_INTR_RX_SHIFT); + coremask = (0x01ff << USB_INTR_USB_SHIFT); + + musb_writel(reg_base, EP_INTR_MASK_SET_REG, epmask); + musb_writel(reg_base, CORE_INTR_MASK_SET_REG, coremask); + + /* Force the DRVVBUS IRQ so we can start polling for ID change. */ + if (is_otg_enabled(musb)) + musb_writel(reg_base, CORE_INTR_SRC_SET_REG, + USB_INTR_DRVVBUS << USB_INTR_USB_SHIFT); +} + +/** + * musb_platform_disable - disable HDRC and flush interrupts + */ +void musb_platform_disable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, CORE_INTR_MASK_CLEAR_REG, USB_INTR_USB_MASK); + musb_writel(reg_base, EP_INTR_MASK_CLEAR_REG, + AM3517_TX_INTR_MASK | AM3517_RX_INTR_MASK); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_writel(reg_base, USB_END_OF_INTR_REG, 0); +} + +static int vbus_state = -1; + +#ifdef CONFIG_USB_MUSB_HDRC_HCD +#define portstate(stmt) stmt +#else +#define portstate(stmt) +#endif + +static void am3517_source_power(struct musb *musb, int is_on, int immediate) +{ + if (is_on) + is_on = 1; + + if (vbus_state == is_on) + return; + vbus_state = is_on; +} + +static void am3517_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); + am3517_source_power(musb, is_on, 0); +} + +#define POLL_SECONDS 2 + +static struct timer_list otg_workaround; + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* We poll because AM3517's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + DBG(7, "Poll devctl %02x (%s)\n", devctl, otg_state_string(musb)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_WAIT_VFALL: + /* + * Wait till VBUS falls below SessionEnd (~0.2 V); the 1.3 + * RTL seems to mis-handle session "start" otherwise (or in + * our case "recover"), in routine "VBUS was valid by the time + * VBUSERR got reported during enumeration" cases. + */ + if (devctl & MUSB_DEVCTL_VBUS) { + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, CORE_INTR_SRC_SET_REG, + MUSB_INTR_VBUSERROR << USB_INTR_USB_SHIFT); + break; + case OTG_STATE_B_IDLE: + if (!is_peripheral_enabled(musb)) + break; + + /* + * There's no ID-changed IRQ, so we have no good way to tell + * when to switch to the A-Default state machine (by setting + * the DEVCTL.SESSION flag). + * + * Workaround: whenever we're in B_IDLE, try setting the + * session flag every few seconds. If it works, ID was + * grounded and we're now in the A-Default state machine. + * + * NOTE: setting the session flag is _supposed_ to trigger + * SRP but clearly it doesn't. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +void musb_platform_try_idle(struct musb *musb, unsigned long timeout) +{ + static unsigned long last_timer; + + if (!is_otg_enabled(musb)) + return; + + if (timeout == 0) + timeout = jiffies + msecs_to_jiffies(3); + + /* Never idle if active, or when VBUS timeout is not set as host */ + if (musb->is_active || (musb->a_wait_bcon == 0 && + musb->xceiv->state == OTG_STATE_A_WAIT_BCON)) { + DBG(4, "%s active, deleting timer\n", otg_state_string(musb)); + del_timer(&otg_workaround); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) { + DBG(4, "Longer idle timer already pending, ignoring...\n"); + return; + } + last_timer = timeout; + + DBG(4, "%s inactive, starting idle timer for %u ms\n", + otg_state_string(musb), jiffies_to_msecs(timeout - jiffies)); + mod_timer(&otg_workaround, timeout); +} + +static irqreturn_t am3517_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + void __iomem *reg_base = musb->ctrl_base; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + u32 epintr, usbintr, lvl_intr; + + spin_lock_irqsave(&musb->lock, flags); + + /* Get endpoint interrupts */ + epintr = musb_readl(reg_base, EP_INTR_SRC_MASKED_REG); + + if (epintr) { + musb_writel(reg_base, EP_INTR_SRC_CLEAR_REG, epintr); + + musb->int_rx = + (epintr & AM3517_RX_INTR_MASK) >> USB_INTR_RX_SHIFT; + musb->int_tx = + (epintr & AM3517_TX_INTR_MASK) >> USB_INTR_TX_SHIFT; + } + + /* Get usb core interrupts */ + usbintr = musb_readl(reg_base, CORE_INTR_SRC_MASKED_REG); + if (!usbintr && !epintr) + goto eoi; + + if (usbintr) { + musb_writel(reg_base, CORE_INTR_SRC_CLEAR_REG, usbintr); + + musb->int_usb = + (usbintr & USB_INTR_USB_MASK) >> USB_INTR_USB_SHIFT; + } + /* + * DRVVBUS IRQs are the only proxy we have (a very poor one!) for + * AM3517's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.SESSION per Mentor docs requires that we know its + * value but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set. + * Also, DRVVBUS pulses for SRP (but not at 5V) ... + */ + if (usbintr & (USB_INTR_DRVVBUS << USB_INTR_USB_SHIFT)) { + int drvvbus = musb_readl(reg_base, USB_STAT_REG); + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + int err; + + err = is_host_enabled(musb) && (musb->int_usb & + MUSB_INTR_VBUSERROR); + if (err) { + /* + * The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"-starting sessions + * without waiting for VBUS to stop registering in + * devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (is_host_enabled(musb) && drvvbus) { + musb->is_active = 1; + MUSB_HST_MODE(musb); + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + del_timer(&otg_workaround); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + /* NOTE: this must complete power-on within 100 ms. */ + am3517_source_power(musb, drvvbus, 0); + DBG(2, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + otg_state_string(musb), + err ? " ERROR" : "", + devctl); + ret = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) { + irqreturn_t mret; + + mret = musb_interrupt(musb); + if (mret == IRQ_HANDLED) + ret = IRQ_HANDLED; + } + + eoi: + /* EOI needs to be written for the IRQ to be re-asserted. */ + if (ret == IRQ_HANDLED || epintr || usbintr) { + /* clear level interrupt */ + lvl_intr = omap_ctrl_readl(AM35XX_CONTROL_LVL_INTR_CLEAR); + lvl_intr |= AM35XX_USBOTGSS_INT_CLR; + omap_ctrl_writel(lvl_intr, AM35XX_CONTROL_LVL_INTR_CLEAR); + /* write EOI */ + musb_writel(reg_base, USB_END_OF_INTR_REG, 0); + } + + /* Poll for ID change */ + if (is_otg_enabled(musb) && musb->xceiv->state == OTG_STATE_B_IDLE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + if (ret != IRQ_HANDLED) { + if (epintr || usbintr) + /* + * We sometimes get unhandled IRQs in the peripheral + * mode from EP0 and SOF... + */ + DBG(2, "Unhandled USB IRQ %08x-%08x\n", + epintr, usbintr); + else if (printk_ratelimit()) + /* + * We've seen series of spurious interrupts in the + * peripheral mode after USB reset and then after some + * time a real interrupt storm starting... + */ + DBG(2, "Spurious IRQ\n"); + } + return ret; +} + +int musb_platform_set_mode(struct musb *musb, u8 musb_mode) +{ + WARNING("FIXME: %s not implemented\n", __func__); + return -EIO; +} + +int __init musb_platform_init(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + struct clk *otg_fck; + u32 rev, lvl_intr, sw_reset; + + usb_nop_xceiv_register(); + + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) + return -ENODEV; + + /* Mentor is at offset of 0x400 in AM3517 */ + musb->mregs += USB_MENTOR_CORE_OFFSET; + + /* musb->clock is already set from board/arch files */ + if (IS_ERR(musb->clock)) + return PTR_ERR(musb->clock); + + if (musb->set_clock) + musb->set_clock(musb->clock, 1); + else + clk_enable(musb->clock); + + DBG(2, "usbotg_ck=%lud\n", clk_get_rate(musb->clock)); + otg_fck = clk_get(musb->controller, "fck"); + clk_enable(otg_fck); + + DBG(2, "usbotg_phy_ck=%lud\n", clk_get_rate(otg_fck)); + /* Returns zero if e.g. not clocked */ + rev = musb_readl(reg_base, USB_REVISION_REG); + if (!rev) + return -ENODEV; + + if (is_host_enabled(musb)) + setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); + + musb->board_set_vbus = am3517_set_vbus; + am3517_source_power(musb, 0, 1); + + /* global reset */ + sw_reset = omap_ctrl_readl(AM35XX_CONTROL_IP_SW_RESET); + + sw_reset |= AM35XX_USBOTGSS_SW_RST; + omap_ctrl_writel(sw_reset, AM35XX_CONTROL_IP_SW_RESET); + + sw_reset &= ~AM35XX_USBOTGSS_SW_RST; + omap_ctrl_writel(sw_reset, AM35XX_CONTROL_IP_SW_RESET); + + /* Reset the controller */ + musb_writel(reg_base, USB_CTRL_REG, USB_SOFT_RESET_MASK); + + /* Start the on-chip PHY and its PLL. */ + phy_on(); + + msleep(15); + + musb->a_wait_bcon = A_WAIT_BCON_TIMEOUT; + musb->isr = am3517_interrupt; + + /* clear level interrupt */ + lvl_intr = omap_ctrl_readl(AM35XX_CONTROL_LVL_INTR_CLEAR); + lvl_intr |= AM35XX_USBOTGSS_INT_CLR; + omap_ctrl_writel(lvl_intr, AM35XX_CONTROL_LVL_INTR_CLEAR); + + return 0; +} + +int musb_platform_exit(struct musb *musb) +{ + if (is_host_enabled(musb)) + del_timer_sync(&otg_workaround); + + am3517_source_power(musb, 0 /* off */, 1); + + /* Delay to avoid problems with module reload... */ + if (is_host_enabled(musb) && musb->xceiv->default_a) { + int maxdelay = 30; + u8 devctl, warn = 0; + + /* + * If there's no peripheral connected, this can take a + * long time to fall... + */ + do { + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (!(devctl & MUSB_DEVCTL_VBUS)) + break; + if ((devctl & MUSB_DEVCTL_VBUS) != warn) { + warn = devctl & MUSB_DEVCTL_VBUS; + DBG(1, "VBUS %d\n", + warn >> MUSB_DEVCTL_VBUS_SHIFT); + } + msleep(1000); + maxdelay--; + } while (maxdelay > 0); + + /* In OTG mode, another host might be connected... */ + if (devctl & MUSB_DEVCTL_VBUS) + DBG(1, "VBUS off timeout (devctl %02x)\n", devctl); + } + + phy_off(); + + usb_nop_xceiv_unregister(); + + return 0; +} + +#ifdef CONFIG_PM +void musb_platform_save_context(struct musb_context_registers + *musb_context) +{ + phy_off(); +} + +void musb_platform_restore_context(struct musb_context_registers + *musb_context) +{ + phy_on(); +} +#endif diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 98fd5b6..f8efe00 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -982,7 +982,8 @@ static void musb_shutdown(struct platform_device *pdev) * more than selecting one of a bunch of predefined configurations. */ #if defined(CONFIG_USB_TUSB6010) || \ - defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) + defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) || \ + defined(CONFIG_MACH_OMAP3517EVM) static ushort __initdata fifo_mode = 4; #else static ushort __initdata fifo_mode = 2; -- 1.6.2.4 -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html