mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
166
drivers/usb/musb/Kconfig
Normal file
166
drivers/usb/musb/Kconfig
Normal file
|
@ -0,0 +1,166 @@
|
|||
#
|
||||
# USB Dual Role (OTG-ready) Controller Drivers
|
||||
# for silicon based on Mentor Graphics INVENTRA designs
|
||||
#
|
||||
|
||||
# (M)HDRC = (Multipoint) Highspeed Dual-Role Controller
|
||||
config USB_MUSB_HDRC
|
||||
tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)'
|
||||
depends on (USB || USB_GADGET)
|
||||
help
|
||||
Say Y here if your system has a dual role high speed USB
|
||||
controller based on the Mentor Graphics silicon IP. Then
|
||||
configure options to match your silicon and the board
|
||||
it's being used with, including the USB peripheral role,
|
||||
or the USB host role, or both.
|
||||
|
||||
Texas Instruments families using this IP include DaVinci
|
||||
(35x, 644x ...), OMAP 243x, OMAP 3, and TUSB 6010.
|
||||
|
||||
Analog Devices parts using this IP include Blackfin BF54x,
|
||||
BF525 and BF527.
|
||||
|
||||
If you do not know what this is, please say N.
|
||||
|
||||
To compile this driver as a module, choose M here; the
|
||||
module will be called "musb-hdrc".
|
||||
|
||||
if USB_MUSB_HDRC
|
||||
|
||||
choice
|
||||
bool "MUSB Mode Selection"
|
||||
default USB_MUSB_DUAL_ROLE if (USB && USB_GADGET)
|
||||
default USB_MUSB_HOST if (USB && !USB_GADGET)
|
||||
default USB_MUSB_GADGET if (!USB && USB_GADGET)
|
||||
|
||||
config USB_MUSB_HOST
|
||||
bool "Host only mode"
|
||||
depends on USB=y || USB=USB_MUSB_HDRC
|
||||
help
|
||||
Select this when you want to use MUSB in host mode only,
|
||||
thereby the gadget feature will be regressed.
|
||||
|
||||
config USB_MUSB_GADGET
|
||||
bool "Gadget only mode"
|
||||
depends on USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC
|
||||
depends on HAS_DMA
|
||||
help
|
||||
Select this when you want to use MUSB in gadget mode only,
|
||||
thereby the host feature will be regressed.
|
||||
|
||||
config USB_MUSB_DUAL_ROLE
|
||||
bool "Dual Role mode"
|
||||
depends on ((USB=y || USB=USB_MUSB_HDRC) && (USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC))
|
||||
depends on HAS_DMA
|
||||
help
|
||||
This is the default mode of working of MUSB controller where
|
||||
both host and gadget features are enabled.
|
||||
|
||||
endchoice
|
||||
|
||||
choice
|
||||
prompt "Platform Glue Layer"
|
||||
|
||||
config USB_MUSB_DAVINCI
|
||||
tristate "DaVinci"
|
||||
depends on ARCH_DAVINCI_DMx
|
||||
depends on BROKEN
|
||||
|
||||
config USB_MUSB_DA8XX
|
||||
tristate "DA8xx/OMAP-L1x"
|
||||
depends on ARCH_DAVINCI_DA8XX
|
||||
depends on BROKEN
|
||||
|
||||
config USB_MUSB_TUSB6010
|
||||
tristate "TUSB6010"
|
||||
|
||||
config USB_MUSB_OMAP2PLUS
|
||||
tristate "OMAP2430 and onwards"
|
||||
depends on ARCH_OMAP2PLUS && USB
|
||||
select GENERIC_PHY
|
||||
|
||||
config USB_MUSB_AM35X
|
||||
tristate "AM35x"
|
||||
depends on ARCH_OMAP
|
||||
|
||||
config USB_MUSB_DSPS
|
||||
tristate "TI DSPS platforms"
|
||||
select USB_MUSB_AM335X_CHILD
|
||||
depends on OF_IRQ
|
||||
|
||||
config USB_MUSB_BLACKFIN
|
||||
tristate "Blackfin"
|
||||
depends on (BF54x && !BF544) || (BF52x && ! BF522 && !BF523)
|
||||
|
||||
config USB_MUSB_UX500
|
||||
tristate "Ux500 platforms"
|
||||
|
||||
config USB_MUSB_JZ4740
|
||||
tristate "JZ4740"
|
||||
depends on MACH_JZ4740 || COMPILE_TEST
|
||||
depends on USB_MUSB_GADGET
|
||||
depends on USB_OTG_BLACKLIST_HUB
|
||||
|
||||
endchoice
|
||||
|
||||
config USB_MUSB_AM335X_CHILD
|
||||
tristate
|
||||
|
||||
choice
|
||||
prompt 'MUSB DMA mode'
|
||||
default MUSB_PIO_ONLY if ARCH_MULTIPLATFORM || USB_MUSB_JZ4740
|
||||
default USB_UX500_DMA if USB_MUSB_UX500
|
||||
default USB_INVENTRA_DMA if USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN
|
||||
default USB_TI_CPPI_DMA if USB_MUSB_DAVINCI
|
||||
default USB_TUSB_OMAP_DMA if USB_MUSB_TUSB6010
|
||||
default MUSB_PIO_ONLY if USB_MUSB_TUSB6010 || USB_MUSB_DA8XX || USB_MUSB_AM35X \
|
||||
|| USB_MUSB_DSPS
|
||||
help
|
||||
Unfortunately, only one option can be enabled here. Ideally one
|
||||
should be able to build all these drivers into one kernel to
|
||||
allow using DMA on multiplatform kernels.
|
||||
|
||||
config USB_UX500_DMA
|
||||
bool 'ST Ericsson Ux500'
|
||||
depends on USB_MUSB_UX500
|
||||
help
|
||||
Enable DMA transfers on UX500 platforms.
|
||||
|
||||
config USB_INVENTRA_DMA
|
||||
bool 'Inventra'
|
||||
depends on USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN
|
||||
help
|
||||
Enable DMA transfers using Mentor's engine.
|
||||
|
||||
config USB_TI_CPPI_DMA
|
||||
bool 'TI CPPI (Davinci)'
|
||||
depends on USB_MUSB_DAVINCI
|
||||
help
|
||||
Enable DMA transfers when TI CPPI DMA is available.
|
||||
|
||||
config USB_TI_CPPI41_DMA
|
||||
bool 'TI CPPI 4.1 (AM335x)'
|
||||
depends on ARCH_OMAP
|
||||
select TI_CPPI41
|
||||
|
||||
config USB_TUSB_OMAP_DMA
|
||||
bool 'TUSB 6010'
|
||||
depends on USB_MUSB_TUSB6010 = USB_MUSB_HDRC # both built-in or both modules
|
||||
depends on ARCH_OMAP
|
||||
help
|
||||
Enable DMA transfers on TUSB 6010 when OMAP DMA is available.
|
||||
|
||||
config MUSB_PIO_ONLY
|
||||
bool 'Disable DMA (always use PIO)'
|
||||
help
|
||||
All data is copied between memory and FIFO by the CPU.
|
||||
DMA controllers are ignored.
|
||||
|
||||
Do not choose this unless DMA support for your SOC or board
|
||||
is unavailable (or unstable). When DMA is enabled at compile time,
|
||||
you can still disable it at run time using the "use_dma=n" module
|
||||
parameter.
|
||||
|
||||
endchoice
|
||||
|
||||
endif # USB_MUSB_HDRC
|
36
drivers/usb/musb/Makefile
Normal file
36
drivers/usb/musb/Makefile
Normal file
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# for USB OTG silicon based on Mentor Graphics INVENTRA designs
|
||||
#
|
||||
|
||||
obj-$(CONFIG_USB_MUSB_HDRC) += musb_hdrc.o
|
||||
|
||||
musb_hdrc-y := musb_core.o
|
||||
|
||||
musb_hdrc-$(CONFIG_USB_MUSB_HOST)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_virthub.o musb_host.o
|
||||
musb_hdrc-$(CONFIG_USB_MUSB_GADGET)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_gadget_ep0.o musb_gadget.o
|
||||
musb_hdrc-$(CONFIG_DEBUG_FS) += musb_debugfs.o
|
||||
|
||||
# Hardware Glue Layer
|
||||
obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o
|
||||
obj-$(CONFIG_USB_MUSB_AM35X) += am35x.o
|
||||
obj-$(CONFIG_USB_MUSB_DSPS) += musb_dsps.o
|
||||
obj-$(CONFIG_USB_MUSB_TUSB6010) += tusb6010.o
|
||||
obj-$(CONFIG_USB_MUSB_DAVINCI) += davinci.o
|
||||
obj-$(CONFIG_USB_MUSB_DA8XX) += da8xx.o
|
||||
obj-$(CONFIG_USB_MUSB_BLACKFIN) += blackfin.o
|
||||
obj-$(CONFIG_USB_MUSB_UX500) += ux500.o
|
||||
obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o
|
||||
|
||||
|
||||
obj-$(CONFIG_USB_MUSB_AM335X_CHILD) += musb_am335x.o
|
||||
|
||||
# the kconfig must guarantee that only one of the
|
||||
# possible I/O schemes will be enabled at a time ...
|
||||
# PIO only, or DMA (several potential schemes).
|
||||
# though PIO is always there to back up DMA, and for ep0
|
||||
|
||||
musb_hdrc-$(CONFIG_USB_INVENTRA_DMA) += musbhsdma.o
|
||||
musb_hdrc-$(CONFIG_USB_TI_CPPI_DMA) += cppi_dma.o
|
||||
musb_hdrc-$(CONFIG_USB_TUSB_OMAP_DMA) += tusb6010_omap.o
|
||||
musb_hdrc-$(CONFIG_USB_UX500_DMA) += ux500_dma.o
|
||||
musb_hdrc-$(CONFIG_USB_TI_CPPI41_DMA) += musb_cppi41.o
|
624
drivers/usb/musb/am35x.c
Normal file
624
drivers/usb/musb/am35x.c
Normal file
|
@ -0,0 +1,624 @@
|
|||
|
||||
/*
|
||||
* Texas Instruments AM35x "glue layer"
|
||||
*
|
||||
* Copyright (c) 2010, by Texas Instruments
|
||||
*
|
||||
* Based on the DA8xx "glue layer" code.
|
||||
* Copyright (c) 2008-2009, MontaVista Software, Inc. <source@mvista.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
#include <linux/platform_data/usb-omap.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
/*
|
||||
* AM35x specific definitions
|
||||
*/
|
||||
/* USB 2.0 OTG module registers */
|
||||
#define USB_REVISION_REG 0x00
|
||||
#define USB_CTRL_REG 0x04
|
||||
#define USB_STAT_REG 0x08
|
||||
#define USB_EMULATION_REG 0x0c
|
||||
/* 0x10 Reserved */
|
||||
#define USB_AUTOREQ_REG 0x14
|
||||
#define USB_SRP_FIX_TIME_REG 0x18
|
||||
#define USB_TEARDOWN_REG 0x1c
|
||||
#define EP_INTR_SRC_REG 0x20
|
||||
#define EP_INTR_SRC_SET_REG 0x24
|
||||
#define EP_INTR_SRC_CLEAR_REG 0x28
|
||||
#define EP_INTR_MASK_REG 0x2c
|
||||
#define EP_INTR_MASK_SET_REG 0x30
|
||||
#define EP_INTR_MASK_CLEAR_REG 0x34
|
||||
#define EP_INTR_SRC_MASKED_REG 0x38
|
||||
#define CORE_INTR_SRC_REG 0x40
|
||||
#define CORE_INTR_SRC_SET_REG 0x44
|
||||
#define CORE_INTR_SRC_CLEAR_REG 0x48
|
||||
#define CORE_INTR_MASK_REG 0x4c
|
||||
#define CORE_INTR_MASK_SET_REG 0x50
|
||||
#define CORE_INTR_MASK_CLEAR_REG 0x54
|
||||
#define CORE_INTR_SRC_MASKED_REG 0x58
|
||||
/* 0x5c Reserved */
|
||||
#define USB_END_OF_INTR_REG 0x60
|
||||
|
||||
/* Control register bits */
|
||||
#define AM35X_SOFT_RESET_MASK 1
|
||||
|
||||
/* USB interrupt register bits */
|
||||
#define AM35X_INTR_USB_SHIFT 16
|
||||
#define AM35X_INTR_USB_MASK (0x1ff << AM35X_INTR_USB_SHIFT)
|
||||
#define AM35X_INTR_DRVVBUS 0x100
|
||||
#define AM35X_INTR_RX_SHIFT 16
|
||||
#define AM35X_INTR_TX_SHIFT 0
|
||||
#define AM35X_TX_EP_MASK 0xffff /* EP0 + 15 Tx EPs */
|
||||
#define AM35X_RX_EP_MASK 0xfffe /* 15 Rx EPs */
|
||||
#define AM35X_TX_INTR_MASK (AM35X_TX_EP_MASK << AM35X_INTR_TX_SHIFT)
|
||||
#define AM35X_RX_INTR_MASK (AM35X_RX_EP_MASK << AM35X_INTR_RX_SHIFT)
|
||||
|
||||
#define USB_MENTOR_CORE_OFFSET 0x400
|
||||
|
||||
struct am35x_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct platform_device *phy;
|
||||
struct clk *phy_clk;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
/*
|
||||
* am35x_musb_enable - enable interrupts
|
||||
*/
|
||||
static void am35x_musb_enable(struct musb *musb)
|
||||
{
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
u32 epmask;
|
||||
|
||||
/* Workaround: setup IRQs through both register sets. */
|
||||
epmask = ((musb->epmask & AM35X_TX_EP_MASK) << AM35X_INTR_TX_SHIFT) |
|
||||
((musb->epmask & AM35X_RX_EP_MASK) << AM35X_INTR_RX_SHIFT);
|
||||
|
||||
musb_writel(reg_base, EP_INTR_MASK_SET_REG, epmask);
|
||||
musb_writel(reg_base, CORE_INTR_MASK_SET_REG, AM35X_INTR_USB_MASK);
|
||||
|
||||
/* Force the DRVVBUS IRQ so we can start polling for ID change. */
|
||||
musb_writel(reg_base, CORE_INTR_SRC_SET_REG,
|
||||
AM35X_INTR_DRVVBUS << AM35X_INTR_USB_SHIFT);
|
||||
}
|
||||
|
||||
/*
|
||||
* am35x_musb_disable - disable HDRC and flush interrupts
|
||||
*/
|
||||
static void am35x_musb_disable(struct musb *musb)
|
||||
{
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
|
||||
musb_writel(reg_base, CORE_INTR_MASK_CLEAR_REG, AM35X_INTR_USB_MASK);
|
||||
musb_writel(reg_base, EP_INTR_MASK_CLEAR_REG,
|
||||
AM35X_TX_INTR_MASK | AM35X_RX_INTR_MASK);
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
|
||||
musb_writel(reg_base, USB_END_OF_INTR_REG, 0);
|
||||
}
|
||||
|
||||
#define portstate(stmt) stmt
|
||||
|
||||
static void am35x_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
WARN_ON(is_on && is_peripheral_active(musb));
|
||||
}
|
||||
|
||||
#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 AM35x's won't expose several OTG-critical
|
||||
* status change events (from the transceiver) otherwise.
|
||||
*/
|
||||
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
||||
dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl,
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
|
||||
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:
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
||||
musb_writel(musb->ctrl_base, CORE_INTR_SRC_SET_REG,
|
||||
MUSB_INTR_VBUSERROR << AM35X_INTR_USB_SHIFT);
|
||||
break;
|
||||
case OTG_STATE_B_IDLE:
|
||||
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);
|
||||
}
|
||||
|
||||
static void am35x_musb_try_idle(struct musb *musb, unsigned long timeout)
|
||||
{
|
||||
static unsigned long last_timer;
|
||||
|
||||
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)) {
|
||||
dev_dbg(musb->controller, "%s active, deleting timer\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
del_timer(&otg_workaround);
|
||||
last_timer = jiffies;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) {
|
||||
dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n");
|
||||
return;
|
||||
}
|
||||
last_timer = timeout;
|
||||
|
||||
dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
jiffies_to_msecs(timeout - jiffies));
|
||||
mod_timer(&otg_workaround, timeout);
|
||||
}
|
||||
|
||||
static irqreturn_t am35x_musb_interrupt(int irq, void *hci)
|
||||
{
|
||||
struct musb *musb = hci;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
unsigned long flags;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u32 epintr, usbintr;
|
||||
|
||||
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 & AM35X_RX_INTR_MASK) >> AM35X_INTR_RX_SHIFT;
|
||||
musb->int_tx =
|
||||
(epintr & AM35X_TX_INTR_MASK) >> AM35X_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 & AM35X_INTR_USB_MASK) >> AM35X_INTR_USB_SHIFT;
|
||||
}
|
||||
/*
|
||||
* DRVVBUS IRQs are the only proxy we have (a very poor one!) for
|
||||
* AM35x'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 & (AM35X_INTR_DRVVBUS << AM35X_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 = 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 (drvvbus) {
|
||||
MUSB_HST_MODE(musb);
|
||||
otg->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);
|
||||
otg->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. */
|
||||
dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n",
|
||||
drvvbus ? "on" : "off",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
err ? " ERROR" : "",
|
||||
devctl);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Drop spurious RX and TX if device is disconnected */
|
||||
if (musb->int_usb & MUSB_INTR_DISCONNECT) {
|
||||
musb->int_tx = 0;
|
||||
musb->int_rx = 0;
|
||||
}
|
||||
|
||||
if (musb->int_tx || musb->int_rx || musb->int_usb)
|
||||
ret |= musb_interrupt(musb);
|
||||
|
||||
eoi:
|
||||
/* EOI needs to be written for the IRQ to be re-asserted. */
|
||||
if (ret == IRQ_HANDLED || epintr || usbintr) {
|
||||
/* clear level interrupt */
|
||||
if (data->clear_irq)
|
||||
data->clear_irq();
|
||||
/* write EOI */
|
||||
musb_writel(reg_base, USB_END_OF_INTR_REG, 0);
|
||||
}
|
||||
|
||||
/* Poll for ID change */
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE)
|
||||
mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int am35x_musb_set_mode(struct musb *musb, u8 musb_mode)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
int retval = 0;
|
||||
|
||||
if (data->set_mode)
|
||||
data->set_mode(musb_mode);
|
||||
else
|
||||
retval = -EIO;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int am35x_musb_init(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
u32 rev;
|
||||
|
||||
musb->mregs += USB_MENTOR_CORE_OFFSET;
|
||||
|
||||
/* Returns zero if e.g. not clocked */
|
||||
rev = musb_readl(reg_base, USB_REVISION_REG);
|
||||
if (!rev)
|
||||
return -ENODEV;
|
||||
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (IS_ERR_OR_NULL(musb->xceiv))
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
setup_timer(&otg_workaround, otg_timer, (unsigned long) musb);
|
||||
|
||||
/* Reset the musb */
|
||||
if (data->reset)
|
||||
data->reset();
|
||||
|
||||
/* Reset the controller */
|
||||
musb_writel(reg_base, USB_CTRL_REG, AM35X_SOFT_RESET_MASK);
|
||||
|
||||
/* Start the on-chip PHY and its PLL. */
|
||||
if (data->set_phy_power)
|
||||
data->set_phy_power(1);
|
||||
|
||||
msleep(5);
|
||||
|
||||
musb->isr = am35x_musb_interrupt;
|
||||
|
||||
/* clear level interrupt */
|
||||
if (data->clear_irq)
|
||||
data->clear_irq();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am35x_musb_exit(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
|
||||
del_timer_sync(&otg_workaround);
|
||||
|
||||
/* Shutdown the on-chip PHY and its PLL. */
|
||||
if (data->set_phy_power)
|
||||
data->set_phy_power(0);
|
||||
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* AM35x supports only 32bit read operation */
|
||||
void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst)
|
||||
{
|
||||
void __iomem *fifo = hw_ep->fifo;
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
/* Read for 32bit-aligned destination address */
|
||||
if (likely((0x03 & (unsigned long) dst) == 0) && len >= 4) {
|
||||
readsl(fifo, dst, len >> 2);
|
||||
dst += len & ~0x03;
|
||||
len &= 0x03;
|
||||
}
|
||||
/*
|
||||
* Now read the remaining 1 to 3 byte or complete length if
|
||||
* unaligned address.
|
||||
*/
|
||||
if (len > 4) {
|
||||
for (i = 0; i < (len >> 2); i++) {
|
||||
*(u32 *) dst = musb_readl(fifo, 0);
|
||||
dst += 4;
|
||||
}
|
||||
len &= 0x03;
|
||||
}
|
||||
if (len > 0) {
|
||||
val = musb_readl(fifo, 0);
|
||||
memcpy(dst, &val, len);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops am35x_ops = {
|
||||
.init = am35x_musb_init,
|
||||
.exit = am35x_musb_exit,
|
||||
|
||||
.enable = am35x_musb_enable,
|
||||
.disable = am35x_musb_disable,
|
||||
|
||||
.set_mode = am35x_musb_set_mode,
|
||||
.try_idle = am35x_musb_try_idle,
|
||||
|
||||
.set_vbus = am35x_musb_set_vbus,
|
||||
};
|
||||
|
||||
static const struct platform_device_info am35x_dev_info = {
|
||||
.name = "musb-hdrc",
|
||||
.id = PLATFORM_DEVID_AUTO,
|
||||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
static int am35x_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct platform_device *musb;
|
||||
struct am35x_glue *glue;
|
||||
struct platform_device_info pinfo;
|
||||
struct clk *phy_clk;
|
||||
struct clk *clk;
|
||||
|
||||
int ret = -ENOMEM;
|
||||
|
||||
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
phy_clk = clk_get(&pdev->dev, "fck");
|
||||
if (IS_ERR(phy_clk)) {
|
||||
dev_err(&pdev->dev, "failed to get PHY clock\n");
|
||||
ret = PTR_ERR(phy_clk);
|
||||
goto err3;
|
||||
}
|
||||
|
||||
clk = clk_get(&pdev->dev, "ick");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto err4;
|
||||
}
|
||||
|
||||
ret = clk_enable(phy_clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable PHY clock\n");
|
||||
goto err5;
|
||||
}
|
||||
|
||||
ret = clk_enable(clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable clock\n");
|
||||
goto err6;
|
||||
}
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->phy_clk = phy_clk;
|
||||
glue->clk = clk;
|
||||
|
||||
pdata->platform_ops = &am35x_ops;
|
||||
|
||||
glue->phy = usb_phy_generic_register();
|
||||
if (IS_ERR(glue->phy))
|
||||
goto err7;
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
pinfo = am35x_dev_info;
|
||||
pinfo.parent = &pdev->dev;
|
||||
pinfo.res = pdev->resource;
|
||||
pinfo.num_res = pdev->num_resources;
|
||||
pinfo.data = pdata;
|
||||
pinfo.size_data = sizeof(*pdata);
|
||||
|
||||
glue->musb = musb = platform_device_register_full(&pinfo);
|
||||
if (IS_ERR(musb)) {
|
||||
ret = PTR_ERR(musb);
|
||||
dev_err(&pdev->dev, "failed to register musb device: %d\n", ret);
|
||||
goto err8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err8:
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
|
||||
err7:
|
||||
clk_disable(clk);
|
||||
|
||||
err6:
|
||||
clk_disable(phy_clk);
|
||||
|
||||
err5:
|
||||
clk_put(clk);
|
||||
|
||||
err4:
|
||||
clk_put(phy_clk);
|
||||
|
||||
err3:
|
||||
kfree(glue);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int am35x_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct am35x_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
clk_disable(glue->clk);
|
||||
clk_disable(glue->phy_clk);
|
||||
clk_put(glue->clk);
|
||||
clk_put(glue->phy_clk);
|
||||
kfree(glue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int am35x_suspend(struct device *dev)
|
||||
{
|
||||
struct am35x_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
|
||||
/* Shutdown the on-chip PHY and its PLL. */
|
||||
if (data->set_phy_power)
|
||||
data->set_phy_power(0);
|
||||
|
||||
clk_disable(glue->phy_clk);
|
||||
clk_disable(glue->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am35x_resume(struct device *dev)
|
||||
{
|
||||
struct am35x_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
int ret;
|
||||
|
||||
/* Start the on-chip PHY and its PLL. */
|
||||
if (data->set_phy_power)
|
||||
data->set_phy_power(1);
|
||||
|
||||
ret = clk_enable(glue->phy_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable PHY clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_enable(glue->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(am35x_pm_ops, am35x_suspend, am35x_resume);
|
||||
|
||||
static struct platform_driver am35x_driver = {
|
||||
.probe = am35x_probe,
|
||||
.remove = am35x_remove,
|
||||
.driver = {
|
||||
.name = "musb-am35x",
|
||||
.pm = &am35x_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("AM35x MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Ajay Kumar Gupta <ajay.gupta@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(am35x_driver);
|
581
drivers/usb/musb/blackfin.c
Normal file
581
drivers/usb/musb/blackfin.c
Normal file
|
@ -0,0 +1,581 @@
|
|||
/*
|
||||
* MUSB OTG controller driver for Blackfin Processors
|
||||
*
|
||||
* Copyright 2006-2008 Analog Devices Inc.
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/prefetch.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
#include "musbhsdma.h"
|
||||
#include "blackfin.h"
|
||||
|
||||
struct bfin_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct platform_device *phy;
|
||||
};
|
||||
#define glue_to_musb(g) platform_get_drvdata(g->musb)
|
||||
|
||||
/*
|
||||
* Load an endpoint's FIFO
|
||||
*/
|
||||
void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src)
|
||||
{
|
||||
struct musb *musb = hw_ep->musb;
|
||||
void __iomem *fifo = hw_ep->fifo;
|
||||
void __iomem *epio = hw_ep->regs;
|
||||
u8 epnum = hw_ep->epnum;
|
||||
|
||||
prefetch((u8 *)src);
|
||||
|
||||
musb_writew(epio, MUSB_TXCOUNT, len);
|
||||
|
||||
dev_dbg(musb->controller, "TX ep%d fifo %p count %d buf %p, epio %p\n",
|
||||
hw_ep->epnum, fifo, len, src, epio);
|
||||
|
||||
dump_fifo_data(src, len);
|
||||
|
||||
if (!ANOMALY_05000380 && epnum != 0) {
|
||||
u16 dma_reg;
|
||||
|
||||
flush_dcache_range((unsigned long)src,
|
||||
(unsigned long)(src + len));
|
||||
|
||||
/* Setup DMA address register */
|
||||
dma_reg = (u32)src;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
dma_reg = (u32)src >> 16;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
/* Setup DMA count register */
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_LOW), len);
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_HIGH), 0);
|
||||
SSYNC();
|
||||
|
||||
/* Enable the DMA */
|
||||
dma_reg = (epnum << 4) | DMA_ENA | INT_ENA | DIRECTION;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
/* Wait for complete */
|
||||
while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum)))
|
||||
cpu_relax();
|
||||
|
||||
/* acknowledge dma interrupt */
|
||||
bfin_write_USB_DMA_INTERRUPT(1 << epnum);
|
||||
SSYNC();
|
||||
|
||||
/* Reset DMA */
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), 0);
|
||||
SSYNC();
|
||||
} else {
|
||||
SSYNC();
|
||||
|
||||
if (unlikely((unsigned long)src & 0x01))
|
||||
outsw_8((unsigned long)fifo, src, (len + 1) >> 1);
|
||||
else
|
||||
outsw((unsigned long)fifo, src, (len + 1) >> 1);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Unload an endpoint's FIFO
|
||||
*/
|
||||
void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst)
|
||||
{
|
||||
struct musb *musb = hw_ep->musb;
|
||||
void __iomem *fifo = hw_ep->fifo;
|
||||
u8 epnum = hw_ep->epnum;
|
||||
|
||||
if (ANOMALY_05000467 && epnum != 0) {
|
||||
u16 dma_reg;
|
||||
|
||||
invalidate_dcache_range((unsigned long)dst,
|
||||
(unsigned long)(dst + len));
|
||||
|
||||
/* Setup DMA address register */
|
||||
dma_reg = (u32)dst;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
dma_reg = (u32)dst >> 16;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
/* Setup DMA count register */
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_LOW), len);
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_HIGH), 0);
|
||||
SSYNC();
|
||||
|
||||
/* Enable the DMA */
|
||||
dma_reg = (epnum << 4) | DMA_ENA | INT_ENA;
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg);
|
||||
SSYNC();
|
||||
|
||||
/* Wait for complete */
|
||||
while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum)))
|
||||
cpu_relax();
|
||||
|
||||
/* acknowledge dma interrupt */
|
||||
bfin_write_USB_DMA_INTERRUPT(1 << epnum);
|
||||
SSYNC();
|
||||
|
||||
/* Reset DMA */
|
||||
bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), 0);
|
||||
SSYNC();
|
||||
} else {
|
||||
SSYNC();
|
||||
/* Read the last byte of packet with odd size from address fifo + 4
|
||||
* to trigger 1 byte access to EP0 FIFO.
|
||||
*/
|
||||
if (len == 1)
|
||||
*dst = (u8)inw((unsigned long)fifo + 4);
|
||||
else {
|
||||
if (unlikely((unsigned long)dst & 0x01))
|
||||
insw_8((unsigned long)fifo, dst, len >> 1);
|
||||
else
|
||||
insw((unsigned long)fifo, dst, len >> 1);
|
||||
|
||||
if (len & 0x01)
|
||||
*(dst + len - 1) = (u8)inw((unsigned long)fifo + 4);
|
||||
}
|
||||
}
|
||||
dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n",
|
||||
'R', hw_ep->epnum, fifo, len, dst);
|
||||
|
||||
dump_fifo_data(dst, len);
|
||||
}
|
||||
|
||||
static irqreturn_t blackfin_interrupt(int irq, void *__hci)
|
||||
{
|
||||
unsigned long flags;
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
struct musb *musb = __hci;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
|
||||
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
|
||||
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
|
||||
|
||||
if (musb->int_usb || musb->int_tx || musb->int_rx) {
|
||||
musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb);
|
||||
musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx);
|
||||
musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx);
|
||||
retval = musb_interrupt(musb);
|
||||
}
|
||||
|
||||
/* Start sampling ID pin, when plug is removed from MUSB */
|
||||
if ((musb->xceiv->state == OTG_STATE_B_IDLE
|
||||
|| musb->xceiv->state == OTG_STATE_A_WAIT_BCON) ||
|
||||
(musb->int_usb & MUSB_INTR_DISCONNECT && is_host_active(musb))) {
|
||||
mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
|
||||
musb->a_wait_bcon = TIMER_DELAY;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void musb_conn_timer_handler(unsigned long _musb)
|
||||
{
|
||||
struct musb *musb = (void *)_musb;
|
||||
unsigned long flags;
|
||||
u16 val;
|
||||
static u8 toggle;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_IDLE:
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
/* Start a new session */
|
||||
val = musb_readw(musb->mregs, MUSB_DEVCTL);
|
||||
val &= ~MUSB_DEVCTL_SESSION;
|
||||
musb_writew(musb->mregs, MUSB_DEVCTL, val);
|
||||
val |= MUSB_DEVCTL_SESSION;
|
||||
musb_writew(musb->mregs, MUSB_DEVCTL, val);
|
||||
/* Check if musb is host or peripheral. */
|
||||
val = musb_readw(musb->mregs, MUSB_DEVCTL);
|
||||
|
||||
if (!(val & MUSB_DEVCTL_BDEVICE)) {
|
||||
gpio_set_value(musb->config->gpio_vrsel, 1);
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
||||
} else {
|
||||
gpio_set_value(musb->config->gpio_vrsel, 0);
|
||||
/* Ignore VBUSERROR and SUSPEND IRQ */
|
||||
val = musb_readb(musb->mregs, MUSB_INTRUSBE);
|
||||
val &= ~MUSB_INTR_VBUSERROR;
|
||||
musb_writeb(musb->mregs, MUSB_INTRUSBE, val);
|
||||
|
||||
val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR;
|
||||
musb_writeb(musb->mregs, MUSB_INTRUSB, val);
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
}
|
||||
mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
|
||||
break;
|
||||
case OTG_STATE_B_IDLE:
|
||||
/*
|
||||
* Start a new session. It seems that MUSB needs taking
|
||||
* some time to recognize the type of the plug inserted?
|
||||
*/
|
||||
val = musb_readw(musb->mregs, MUSB_DEVCTL);
|
||||
val |= MUSB_DEVCTL_SESSION;
|
||||
musb_writew(musb->mregs, MUSB_DEVCTL, val);
|
||||
val = musb_readw(musb->mregs, MUSB_DEVCTL);
|
||||
|
||||
if (!(val & MUSB_DEVCTL_BDEVICE)) {
|
||||
gpio_set_value(musb->config->gpio_vrsel, 1);
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
||||
} else {
|
||||
gpio_set_value(musb->config->gpio_vrsel, 0);
|
||||
|
||||
/* Ignore VBUSERROR and SUSPEND IRQ */
|
||||
val = musb_readb(musb->mregs, MUSB_INTRUSBE);
|
||||
val &= ~MUSB_INTR_VBUSERROR;
|
||||
musb_writeb(musb->mregs, MUSB_INTRUSBE, val);
|
||||
|
||||
val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR;
|
||||
musb_writeb(musb->mregs, MUSB_INTRUSB, val);
|
||||
|
||||
/* Toggle the Soft Conn bit, so that we can response to
|
||||
* the inserting of either A-plug or B-plug.
|
||||
*/
|
||||
if (toggle) {
|
||||
val = musb_readb(musb->mregs, MUSB_POWER);
|
||||
val &= ~MUSB_POWER_SOFTCONN;
|
||||
musb_writeb(musb->mregs, MUSB_POWER, val);
|
||||
toggle = 0;
|
||||
} else {
|
||||
val = musb_readb(musb->mregs, MUSB_POWER);
|
||||
val |= MUSB_POWER_SOFTCONN;
|
||||
musb_writeb(musb->mregs, MUSB_POWER, val);
|
||||
toggle = 1;
|
||||
}
|
||||
/* The delay time is set to 1/4 second by default,
|
||||
* shortening it, if accelerating A-plug detection
|
||||
* is needed in OTG mode.
|
||||
*/
|
||||
mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY / 4);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_dbg(musb->controller, "%s state not handled\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
dev_dbg(musb->controller, "state is %s\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
}
|
||||
|
||||
static void bfin_musb_enable(struct musb *musb)
|
||||
{
|
||||
/* REVISIT is this really correct ? */
|
||||
}
|
||||
|
||||
static void bfin_musb_disable(struct musb *musb)
|
||||
{
|
||||
}
|
||||
|
||||
static void bfin_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
int value = musb->config->gpio_vrsel_active;
|
||||
if (!is_on)
|
||||
value = !value;
|
||||
gpio_set_value(musb->config->gpio_vrsel, value);
|
||||
|
||||
dev_dbg(musb->controller, "VBUS %s, devctl %02x "
|
||||
/* otg %3x conf %08x prcm %08x */ "\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
musb_readb(musb->mregs, MUSB_DEVCTL));
|
||||
}
|
||||
|
||||
static int bfin_musb_set_power(struct usb_phy *x, unsigned mA)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bfin_musb_vbus_status(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bfin_musb_set_mode(struct musb *musb, u8 musb_mode)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int bfin_musb_adjust_channel_params(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 *mode,
|
||||
dma_addr_t *dma_addr, u32 *len)
|
||||
{
|
||||
struct musb_dma_channel *musb_channel = channel->private_data;
|
||||
|
||||
/*
|
||||
* Anomaly 05000450 might cause data corruption when using DMA
|
||||
* MODE 1 transmits with short packet. So to work around this,
|
||||
* we truncate all MODE 1 transfers down to a multiple of the
|
||||
* max packet size, and then do the last short packet transfer
|
||||
* (if there is any) using MODE 0.
|
||||
*/
|
||||
if (ANOMALY_05000450) {
|
||||
if (musb_channel->transmit && *mode == 1)
|
||||
*len = *len - (*len % packet_sz);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bfin_musb_reg_init(struct musb *musb)
|
||||
{
|
||||
if (ANOMALY_05000346) {
|
||||
bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value);
|
||||
SSYNC();
|
||||
}
|
||||
|
||||
if (ANOMALY_05000347) {
|
||||
bfin_write_USB_APHY_CNTRL(0x0);
|
||||
SSYNC();
|
||||
}
|
||||
|
||||
/* Configure PLL oscillator register */
|
||||
bfin_write_USB_PLLOSC_CTRL(0x3080 |
|
||||
((480/musb->config->clkin) << 1));
|
||||
SSYNC();
|
||||
|
||||
bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1);
|
||||
SSYNC();
|
||||
|
||||
bfin_write_USB_EP_NI0_RXMAXP(64);
|
||||
SSYNC();
|
||||
|
||||
bfin_write_USB_EP_NI0_TXMAXP(64);
|
||||
SSYNC();
|
||||
|
||||
/* Route INTRUSB/INTR_RX/INTR_TX to USB_INT0*/
|
||||
bfin_write_USB_GLOBINTR(0x7);
|
||||
SSYNC();
|
||||
|
||||
bfin_write_USB_GLOBAL_CTL(GLOBAL_ENA | EP1_TX_ENA | EP2_TX_ENA |
|
||||
EP3_TX_ENA | EP4_TX_ENA | EP5_TX_ENA |
|
||||
EP6_TX_ENA | EP7_TX_ENA | EP1_RX_ENA |
|
||||
EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA |
|
||||
EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA);
|
||||
SSYNC();
|
||||
}
|
||||
|
||||
static int bfin_musb_init(struct musb *musb)
|
||||
{
|
||||
|
||||
/*
|
||||
* Rev 1.0 BF549 EZ-KITs require PE7 to be high for both DEVICE
|
||||
* and OTG HOST modes, while rev 1.1 and greater require PE7 to
|
||||
* be low for DEVICE mode and high for HOST mode. We set it high
|
||||
* here because we are in host mode
|
||||
*/
|
||||
|
||||
if (gpio_request(musb->config->gpio_vrsel, "USB_VRSEL")) {
|
||||
printk(KERN_ERR "Failed ro request USB_VRSEL GPIO_%d\n",
|
||||
musb->config->gpio_vrsel);
|
||||
return -ENODEV;
|
||||
}
|
||||
gpio_direction_output(musb->config->gpio_vrsel, 0);
|
||||
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (IS_ERR_OR_NULL(musb->xceiv)) {
|
||||
gpio_free(musb->config->gpio_vrsel);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
bfin_musb_reg_init(musb);
|
||||
|
||||
setup_timer(&musb_conn_timer, musb_conn_timer_handler,
|
||||
(unsigned long) musb);
|
||||
|
||||
musb->xceiv->set_power = bfin_musb_set_power;
|
||||
|
||||
musb->isr = blackfin_interrupt;
|
||||
musb->double_buffer_not_ok = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bfin_musb_exit(struct musb *musb)
|
||||
{
|
||||
gpio_free(musb->config->gpio_vrsel);
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops bfin_ops = {
|
||||
.init = bfin_musb_init,
|
||||
.exit = bfin_musb_exit,
|
||||
|
||||
.enable = bfin_musb_enable,
|
||||
.disable = bfin_musb_disable,
|
||||
|
||||
.set_mode = bfin_musb_set_mode,
|
||||
|
||||
.vbus_status = bfin_musb_vbus_status,
|
||||
.set_vbus = bfin_musb_set_vbus,
|
||||
|
||||
.adjust_channel_params = bfin_musb_adjust_channel_params,
|
||||
};
|
||||
|
||||
static u64 bfin_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int bfin_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource musb_resources[2];
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct platform_device *musb;
|
||||
struct bfin_glue *glue;
|
||||
|
||||
int ret = -ENOMEM;
|
||||
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
|
||||
if (!musb) {
|
||||
dev_err(&pdev->dev, "failed to allocate musb device\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
musb->dev.parent = &pdev->dev;
|
||||
musb->dev.dma_mask = &bfin_dmamask;
|
||||
musb->dev.coherent_dma_mask = bfin_dmamask;
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->musb = musb;
|
||||
|
||||
pdata->platform_ops = &bfin_ops;
|
||||
|
||||
glue->phy = usb_phy_generic_register();
|
||||
if (IS_ERR(glue->phy))
|
||||
goto err1;
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
memset(musb_resources, 0x00, sizeof(*musb_resources) *
|
||||
ARRAY_SIZE(musb_resources));
|
||||
|
||||
musb_resources[0].name = pdev->resource[0].name;
|
||||
musb_resources[0].start = pdev->resource[0].start;
|
||||
musb_resources[0].end = pdev->resource[0].end;
|
||||
musb_resources[0].flags = pdev->resource[0].flags;
|
||||
|
||||
musb_resources[1].name = pdev->resource[1].name;
|
||||
musb_resources[1].start = pdev->resource[1].start;
|
||||
musb_resources[1].end = pdev->resource[1].end;
|
||||
musb_resources[1].flags = pdev->resource[1].flags;
|
||||
|
||||
ret = platform_device_add_resources(musb, musb_resources,
|
||||
ARRAY_SIZE(musb_resources));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add resources\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add platform_data\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = platform_device_add(musb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register musb device\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
|
||||
err1:
|
||||
platform_device_put(musb);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bfin_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bfin_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int bfin_suspend(struct device *dev)
|
||||
{
|
||||
struct bfin_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
|
||||
if (is_host_active(musb))
|
||||
/*
|
||||
* During hibernate gpio_vrsel will change from high to low
|
||||
* low which will generate wakeup event resume the system
|
||||
* immediately. Set it to 0 before hibernate to avoid this
|
||||
* wakeup event.
|
||||
*/
|
||||
gpio_set_value(musb->config->gpio_vrsel, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bfin_resume(struct device *dev)
|
||||
{
|
||||
struct bfin_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
|
||||
bfin_musb_reg_init(musb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(bfin_pm_ops, bfin_suspend, bfin_resume);
|
||||
|
||||
static struct platform_driver bfin_driver = {
|
||||
.probe = bfin_probe,
|
||||
.remove = __exit_p(bfin_remove),
|
||||
.driver = {
|
||||
.name = "musb-blackfin",
|
||||
.pm = &bfin_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("Blackfin MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Bryan Wy <cooloney@kernel.org>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(bfin_driver);
|
87
drivers/usb/musb/blackfin.h
Normal file
87
drivers/usb/musb/blackfin.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (C) 2007 by Analog Devices, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_BLACKFIN_H__
|
||||
#define __MUSB_BLACKFIN_H__
|
||||
|
||||
/*
|
||||
* Blackfin specific definitions
|
||||
*/
|
||||
|
||||
/* Anomalies notes:
|
||||
*
|
||||
* 05000450 - USB DMA Mode 1 Short Packet Data Corruption:
|
||||
* MUSB driver is designed to transfer buffer of N * maxpacket size
|
||||
* in DMA mode 1 and leave the rest of the data to the next
|
||||
* transfer in DMA mode 0, so we never transmit a short packet in
|
||||
* DMA mode 1.
|
||||
*
|
||||
* 05000463 - This anomaly doesn't affect this driver since it
|
||||
* never uses L1 or L2 memory as data destination.
|
||||
*
|
||||
* 05000464 - This anomaly doesn't affect this driver since it
|
||||
* never uses L1 or L2 memory as data source.
|
||||
*
|
||||
* 05000465 - The anomaly can be seen when SCLK is over 100 MHz, and there is
|
||||
* no way to workaround for bulk endpoints. Since the wMaxPackSize
|
||||
* of bulk is less than or equal to 512, while the fifo size of
|
||||
* endpoint 5, 6, 7 is 1024, the double buffer mode is enabled
|
||||
* automatically when these endpoints are used for bulk OUT.
|
||||
*
|
||||
* 05000466 - This anomaly doesn't affect this driver since it never mixes
|
||||
* concurrent DMA and core accesses to the TX endpoint FIFOs.
|
||||
*
|
||||
* 05000467 - The workaround for this anomaly will introduce another
|
||||
* anomaly - 05000465.
|
||||
*/
|
||||
|
||||
/* The Mentor USB DMA engine on BF52x (silicon v0.0 and v0.1) seems to be
|
||||
* unstable in host mode. This may be caused by Anomaly 05000380. After
|
||||
* digging out the root cause, we will change this number accordingly.
|
||||
* So, need to either use silicon v0.2+ or disable DMA mode in MUSB.
|
||||
*/
|
||||
#if ANOMALY_05000380 && defined(CONFIG_BF52x) && \
|
||||
!defined(CONFIG_MUSB_PIO_ONLY)
|
||||
# error "Please use PIO mode in MUSB driver on bf52x chip v0.0 and v0.1"
|
||||
#endif
|
||||
|
||||
#undef DUMP_FIFO_DATA
|
||||
#ifdef DUMP_FIFO_DATA
|
||||
static void dump_fifo_data(u8 *buf, u16 len)
|
||||
{
|
||||
u8 *tmp = buf;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!(i % 16) && i)
|
||||
pr_debug("\n");
|
||||
pr_debug("%02x ", *tmp++);
|
||||
}
|
||||
pr_debug("\n");
|
||||
}
|
||||
#else
|
||||
#define dump_fifo_data(buf, len) do {} while (0)
|
||||
#endif
|
||||
|
||||
|
||||
#define USB_DMA_BASE USB_DMA_INTERRUPT
|
||||
#define USB_DMAx_CTRL 0x04
|
||||
#define USB_DMAx_ADDR_LOW 0x08
|
||||
#define USB_DMAx_ADDR_HIGH 0x0C
|
||||
#define USB_DMAx_COUNT_LOW 0x10
|
||||
#define USB_DMAx_COUNT_HIGH 0x14
|
||||
|
||||
#define USB_DMA_REG(ep, reg) (USB_DMA_BASE + 0x20 * ep + reg)
|
||||
|
||||
/* Almost 1 second */
|
||||
#define TIMER_DELAY (1 * HZ)
|
||||
|
||||
static struct timer_list musb_conn_timer;
|
||||
|
||||
#endif /* __MUSB_BLACKFIN_H__ */
|
1545
drivers/usb/musb/cppi_dma.c
Normal file
1545
drivers/usb/musb/cppi_dma.c
Normal file
File diff suppressed because it is too large
Load diff
134
drivers/usb/musb/cppi_dma.h
Normal file
134
drivers/usb/musb/cppi_dma.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
/* Copyright (C) 2005-2006 by Texas Instruments */
|
||||
|
||||
#ifndef _CPPI_DMA_H_
|
||||
#define _CPPI_DMA_H_
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/dmapool.h>
|
||||
|
||||
#include "musb_dma.h"
|
||||
#include "musb_core.h"
|
||||
|
||||
|
||||
/* FIXME fully isolate CPPI from DaVinci ... the "CPPI generic" registers
|
||||
* would seem to be shared with the TUSB6020 (over VLYNQ).
|
||||
*/
|
||||
|
||||
#include "davinci.h"
|
||||
|
||||
|
||||
/* CPPI RX/TX state RAM */
|
||||
|
||||
struct cppi_tx_stateram {
|
||||
u32 tx_head; /* "DMA packet" head descriptor */
|
||||
u32 tx_buf;
|
||||
u32 tx_current; /* current descriptor */
|
||||
u32 tx_buf_current;
|
||||
u32 tx_info; /* flags, remaining buflen */
|
||||
u32 tx_rem_len;
|
||||
u32 tx_dummy; /* unused */
|
||||
u32 tx_complete;
|
||||
};
|
||||
|
||||
struct cppi_rx_stateram {
|
||||
u32 rx_skipbytes;
|
||||
u32 rx_head;
|
||||
u32 rx_sop; /* "DMA packet" head descriptor */
|
||||
u32 rx_current; /* current descriptor */
|
||||
u32 rx_buf_current;
|
||||
u32 rx_len_len;
|
||||
u32 rx_cnt_cnt;
|
||||
u32 rx_complete;
|
||||
};
|
||||
|
||||
/* hw_options bits in CPPI buffer descriptors */
|
||||
#define CPPI_SOP_SET ((u32)(1 << 31))
|
||||
#define CPPI_EOP_SET ((u32)(1 << 30))
|
||||
#define CPPI_OWN_SET ((u32)(1 << 29)) /* owned by cppi */
|
||||
#define CPPI_EOQ_MASK ((u32)(1 << 28))
|
||||
#define CPPI_ZERO_SET ((u32)(1 << 23)) /* rx saw zlp; tx issues one */
|
||||
#define CPPI_RXABT_MASK ((u32)(1 << 19)) /* need more rx buffers */
|
||||
|
||||
#define CPPI_RECV_PKTLEN_MASK 0xFFFF
|
||||
#define CPPI_BUFFER_LEN_MASK 0xFFFF
|
||||
|
||||
#define CPPI_TEAR_READY ((u32)(1 << 31))
|
||||
|
||||
/* CPPI data structure definitions */
|
||||
|
||||
#define CPPI_DESCRIPTOR_ALIGN 16 /* bytes; 5-dec docs say 4-byte align */
|
||||
|
||||
struct cppi_descriptor {
|
||||
/* hardware overlay */
|
||||
u32 hw_next; /* next buffer descriptor Pointer */
|
||||
u32 hw_bufp; /* i/o buffer pointer */
|
||||
u32 hw_off_len; /* buffer_offset16, buffer_length16 */
|
||||
u32 hw_options; /* flags: SOP, EOP etc*/
|
||||
|
||||
struct cppi_descriptor *next;
|
||||
dma_addr_t dma; /* address of this descriptor */
|
||||
u32 buflen; /* for RX: original buffer length */
|
||||
} __attribute__ ((aligned(CPPI_DESCRIPTOR_ALIGN)));
|
||||
|
||||
|
||||
struct cppi;
|
||||
|
||||
/* CPPI Channel Control structure */
|
||||
struct cppi_channel {
|
||||
struct dma_channel channel;
|
||||
|
||||
/* back pointer to the DMA controller structure */
|
||||
struct cppi *controller;
|
||||
|
||||
/* which direction of which endpoint? */
|
||||
struct musb_hw_ep *hw_ep;
|
||||
bool transmit;
|
||||
u8 index;
|
||||
|
||||
/* DMA modes: RNDIS or "transparent" */
|
||||
u8 is_rndis;
|
||||
|
||||
/* book keeping for current transfer request */
|
||||
dma_addr_t buf_dma;
|
||||
u32 buf_len;
|
||||
u32 maxpacket;
|
||||
u32 offset; /* dma requested */
|
||||
|
||||
void __iomem *state_ram; /* CPPI state */
|
||||
|
||||
struct cppi_descriptor *freelist;
|
||||
|
||||
/* BD management fields */
|
||||
struct cppi_descriptor *head;
|
||||
struct cppi_descriptor *tail;
|
||||
struct cppi_descriptor *last_processed;
|
||||
|
||||
/* use tx_complete in host role to track endpoints waiting for
|
||||
* FIFONOTEMPTY to clear.
|
||||
*/
|
||||
struct list_head tx_complete;
|
||||
};
|
||||
|
||||
/* CPPI DMA controller object */
|
||||
struct cppi {
|
||||
struct dma_controller controller;
|
||||
struct musb *musb;
|
||||
void __iomem *mregs; /* Mentor regs */
|
||||
void __iomem *tibase; /* TI/CPPI regs */
|
||||
|
||||
int irq;
|
||||
|
||||
struct cppi_channel tx[4];
|
||||
struct cppi_channel rx[4];
|
||||
|
||||
struct dma_pool *pool;
|
||||
|
||||
struct list_head tx_complete;
|
||||
};
|
||||
|
||||
/* CPPI IRQ handler */
|
||||
extern irqreturn_t cppi_interrupt(int, void *);
|
||||
|
||||
#endif /* end of ifndef _CPPI_DMA_H_ */
|
590
drivers/usb/musb/da8xx.c
Normal file
590
drivers/usb/musb/da8xx.c
Normal file
|
@ -0,0 +1,590 @@
|
|||
/*
|
||||
* Texas Instruments DA8xx/OMAP-L1x "glue layer"
|
||||
*
|
||||
* Copyright (c) 2008-2009 MontaVista Software, Inc. <source@mvista.com>
|
||||
*
|
||||
* Based on the DaVinci "glue layer" code.
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
|
||||
#include <mach/da8xx.h>
|
||||
#include <linux/platform_data/usb-davinci.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
/*
|
||||
* DA8XX specific definitions
|
||||
*/
|
||||
|
||||
/* USB 2.0 OTG module registers */
|
||||
#define DA8XX_USB_REVISION_REG 0x00
|
||||
#define DA8XX_USB_CTRL_REG 0x04
|
||||
#define DA8XX_USB_STAT_REG 0x08
|
||||
#define DA8XX_USB_EMULATION_REG 0x0c
|
||||
#define DA8XX_USB_MODE_REG 0x10 /* Transparent, CDC, [Generic] RNDIS */
|
||||
#define DA8XX_USB_AUTOREQ_REG 0x14
|
||||
#define DA8XX_USB_SRP_FIX_TIME_REG 0x18
|
||||
#define DA8XX_USB_TEARDOWN_REG 0x1c
|
||||
#define DA8XX_USB_INTR_SRC_REG 0x20
|
||||
#define DA8XX_USB_INTR_SRC_SET_REG 0x24
|
||||
#define DA8XX_USB_INTR_SRC_CLEAR_REG 0x28
|
||||
#define DA8XX_USB_INTR_MASK_REG 0x2c
|
||||
#define DA8XX_USB_INTR_MASK_SET_REG 0x30
|
||||
#define DA8XX_USB_INTR_MASK_CLEAR_REG 0x34
|
||||
#define DA8XX_USB_INTR_SRC_MASKED_REG 0x38
|
||||
#define DA8XX_USB_END_OF_INTR_REG 0x3c
|
||||
#define DA8XX_USB_GENERIC_RNDIS_EP_SIZE_REG(n) (0x50 + (((n) - 1) << 2))
|
||||
|
||||
/* Control register bits */
|
||||
#define DA8XX_SOFT_RESET_MASK 1
|
||||
|
||||
#define DA8XX_USB_TX_EP_MASK 0x1f /* EP0 + 4 Tx EPs */
|
||||
#define DA8XX_USB_RX_EP_MASK 0x1e /* 4 Rx EPs */
|
||||
|
||||
/* USB interrupt register bits */
|
||||
#define DA8XX_INTR_USB_SHIFT 16
|
||||
#define DA8XX_INTR_USB_MASK (0x1ff << DA8XX_INTR_USB_SHIFT) /* 8 Mentor */
|
||||
/* interrupts and DRVVBUS interrupt */
|
||||
#define DA8XX_INTR_DRVVBUS 0x100
|
||||
#define DA8XX_INTR_RX_SHIFT 8
|
||||
#define DA8XX_INTR_RX_MASK (DA8XX_USB_RX_EP_MASK << DA8XX_INTR_RX_SHIFT)
|
||||
#define DA8XX_INTR_TX_SHIFT 0
|
||||
#define DA8XX_INTR_TX_MASK (DA8XX_USB_TX_EP_MASK << DA8XX_INTR_TX_SHIFT)
|
||||
|
||||
#define DA8XX_MENTOR_CORE_OFFSET 0x400
|
||||
|
||||
#define CFGCHIP2 IO_ADDRESS(DA8XX_SYSCFG0_BASE + DA8XX_CFGCHIP2_REG)
|
||||
|
||||
struct da8xx_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct platform_device *phy;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
/*
|
||||
* REVISIT (PM): we should be able to keep the PHY in low power mode most
|
||||
* of the time (24 MHz oscillator and PLL off, etc.) by setting POWER.D0
|
||||
* and, when in host mode, autosuspending idle root ports... PHY_PLLON
|
||||
* (overriding SUSPENDM?) then likely needs to stay off.
|
||||
*/
|
||||
|
||||
static inline void phy_on(void)
|
||||
{
|
||||
u32 cfgchip2 = __raw_readl(CFGCHIP2);
|
||||
|
||||
/*
|
||||
* Start the on-chip PHY and its PLL.
|
||||
*/
|
||||
cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN);
|
||||
cfgchip2 |= CFGCHIP2_PHY_PLLON;
|
||||
__raw_writel(cfgchip2, CFGCHIP2);
|
||||
|
||||
pr_info("Waiting for USB PHY clock good...\n");
|
||||
while (!(__raw_readl(CFGCHIP2) & CFGCHIP2_PHYCLKGD))
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static inline void phy_off(void)
|
||||
{
|
||||
u32 cfgchip2 = __raw_readl(CFGCHIP2);
|
||||
|
||||
/*
|
||||
* Ensure that USB 1.1 reference clock is not being sourced from
|
||||
* USB 2.0 PHY. Otherwise do not power down the PHY.
|
||||
*/
|
||||
if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX) &&
|
||||
(cfgchip2 & CFGCHIP2_USB1SUSPENDM)) {
|
||||
pr_warning("USB 1.1 clocked from USB 2.0 PHY -- "
|
||||
"can't power it down\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power down the on-chip PHY.
|
||||
*/
|
||||
cfgchip2 |= CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN;
|
||||
__raw_writel(cfgchip2, CFGCHIP2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Because we don't set CTRL.UINT, it's "important" to:
|
||||
* - not read/write INTRUSB/INTRUSBE (except during
|
||||
* initial setup, as a workaround);
|
||||
* - use INTSET/INTCLR instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* da8xx_musb_enable - enable interrupts
|
||||
*/
|
||||
static void da8xx_musb_enable(struct musb *musb)
|
||||
{
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
u32 mask;
|
||||
|
||||
/* Workaround: setup IRQs through both register sets. */
|
||||
mask = ((musb->epmask & DA8XX_USB_TX_EP_MASK) << DA8XX_INTR_TX_SHIFT) |
|
||||
((musb->epmask & DA8XX_USB_RX_EP_MASK) << DA8XX_INTR_RX_SHIFT) |
|
||||
DA8XX_INTR_USB_MASK;
|
||||
musb_writel(reg_base, DA8XX_USB_INTR_MASK_SET_REG, mask);
|
||||
|
||||
/* Force the DRVVBUS IRQ so we can start polling for ID change. */
|
||||
musb_writel(reg_base, DA8XX_USB_INTR_SRC_SET_REG,
|
||||
DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* da8xx_musb_disable - disable HDRC and flush interrupts
|
||||
*/
|
||||
static void da8xx_musb_disable(struct musb *musb)
|
||||
{
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
|
||||
musb_writel(reg_base, DA8XX_USB_INTR_MASK_CLEAR_REG,
|
||||
DA8XX_INTR_USB_MASK |
|
||||
DA8XX_INTR_TX_MASK | DA8XX_INTR_RX_MASK);
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
|
||||
musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0);
|
||||
}
|
||||
|
||||
#define portstate(stmt) stmt
|
||||
|
||||
static void da8xx_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
WARN_ON(is_on && is_peripheral_active(musb));
|
||||
}
|
||||
|
||||
#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 DaVinci's won't expose several OTG-critical
|
||||
* status change events (from the transceiver) otherwise.
|
||||
*/
|
||||
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
||||
dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl,
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
|
||||
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, DA8XX_USB_INTR_SRC_SET_REG,
|
||||
MUSB_INTR_VBUSERROR << DA8XX_INTR_USB_SHIFT);
|
||||
break;
|
||||
case OTG_STATE_B_IDLE:
|
||||
/*
|
||||
* 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 bit).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
musb_writeb(mregs, MUSB_DEVCTL, devctl | MUSB_DEVCTL_SESSION);
|
||||
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);
|
||||
}
|
||||
|
||||
static void da8xx_musb_try_idle(struct musb *musb, unsigned long timeout)
|
||||
{
|
||||
static unsigned long last_timer;
|
||||
|
||||
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)) {
|
||||
dev_dbg(musb->controller, "%s active, deleting timer\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
del_timer(&otg_workaround);
|
||||
last_timer = jiffies;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) {
|
||||
dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n");
|
||||
return;
|
||||
}
|
||||
last_timer = timeout;
|
||||
|
||||
dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
jiffies_to_msecs(timeout - jiffies));
|
||||
mod_timer(&otg_workaround, timeout);
|
||||
}
|
||||
|
||||
static irqreturn_t da8xx_musb_interrupt(int irq, void *hci)
|
||||
{
|
||||
struct musb *musb = hci;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
unsigned long flags;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u32 status;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
/*
|
||||
* NOTE: DA8XX shadows the Mentor IRQs. Don't manage them through
|
||||
* the Mentor registers (except for setup), use the TI ones and EOI.
|
||||
*/
|
||||
|
||||
/* Acknowledge and handle non-CPPI interrupts */
|
||||
status = musb_readl(reg_base, DA8XX_USB_INTR_SRC_MASKED_REG);
|
||||
if (!status)
|
||||
goto eoi;
|
||||
|
||||
musb_writel(reg_base, DA8XX_USB_INTR_SRC_CLEAR_REG, status);
|
||||
dev_dbg(musb->controller, "USB IRQ %08x\n", status);
|
||||
|
||||
musb->int_rx = (status & DA8XX_INTR_RX_MASK) >> DA8XX_INTR_RX_SHIFT;
|
||||
musb->int_tx = (status & DA8XX_INTR_TX_MASK) >> DA8XX_INTR_TX_SHIFT;
|
||||
musb->int_usb = (status & DA8XX_INTR_USB_MASK) >> DA8XX_INTR_USB_SHIFT;
|
||||
|
||||
/*
|
||||
* DRVVBUS IRQs are the only proxy we have (a very poor one!) for
|
||||
* DA8xx'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 5 V)...
|
||||
*/
|
||||
if (status & (DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT)) {
|
||||
int drvvbus = musb_readl(reg_base, DA8XX_USB_STAT_REG);
|
||||
void __iomem *mregs = musb->mregs;
|
||||
u8 devctl = musb_readb(mregs, MUSB_DEVCTL);
|
||||
int err;
|
||||
|
||||
err = 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 (drvvbus) {
|
||||
MUSB_HST_MODE(musb);
|
||||
otg->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);
|
||||
otg->default_a = 0;
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
portstate(musb->port1_status &= ~USB_PORT_STAT_POWER);
|
||||
}
|
||||
|
||||
dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n",
|
||||
drvvbus ? "on" : "off",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
err ? " ERROR" : "",
|
||||
devctl);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (musb->int_tx || musb->int_rx || musb->int_usb)
|
||||
ret |= musb_interrupt(musb);
|
||||
|
||||
eoi:
|
||||
/* EOI needs to be written for the IRQ to be re-asserted. */
|
||||
if (ret == IRQ_HANDLED || status)
|
||||
musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0);
|
||||
|
||||
/* Poll for ID change */
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE)
|
||||
mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da8xx_musb_set_mode(struct musb *musb, u8 musb_mode)
|
||||
{
|
||||
u32 cfgchip2 = __raw_readl(CFGCHIP2);
|
||||
|
||||
cfgchip2 &= ~CFGCHIP2_OTGMODE;
|
||||
switch (musb_mode) {
|
||||
case MUSB_HOST: /* Force VBUS valid, ID = 0 */
|
||||
cfgchip2 |= CFGCHIP2_FORCE_HOST;
|
||||
break;
|
||||
case MUSB_PERIPHERAL: /* Force VBUS valid, ID = 1 */
|
||||
cfgchip2 |= CFGCHIP2_FORCE_DEVICE;
|
||||
break;
|
||||
case MUSB_OTG: /* Don't override the VBUS/ID comparators */
|
||||
cfgchip2 |= CFGCHIP2_NO_OVERRIDE;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(musb->controller, "Trying to set unsupported mode %u\n", musb_mode);
|
||||
}
|
||||
|
||||
__raw_writel(cfgchip2, CFGCHIP2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da8xx_musb_init(struct musb *musb)
|
||||
{
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
u32 rev;
|
||||
int ret = -ENODEV;
|
||||
|
||||
musb->mregs += DA8XX_MENTOR_CORE_OFFSET;
|
||||
|
||||
/* Returns zero if e.g. not clocked */
|
||||
rev = musb_readl(reg_base, DA8XX_USB_REVISION_REG);
|
||||
if (!rev)
|
||||
goto fail;
|
||||
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (IS_ERR_OR_NULL(musb->xceiv)) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
setup_timer(&otg_workaround, otg_timer, (unsigned long)musb);
|
||||
|
||||
/* Reset the controller */
|
||||
musb_writel(reg_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK);
|
||||
|
||||
/* Start the on-chip PHY and its PLL. */
|
||||
phy_on();
|
||||
|
||||
msleep(5);
|
||||
|
||||
/* NOTE: IRQs are in mixed mode, not bypass to pure MUSB */
|
||||
pr_debug("DA8xx OTG revision %08x, PHY %03x, control %02x\n",
|
||||
rev, __raw_readl(CFGCHIP2),
|
||||
musb_readb(reg_base, DA8XX_USB_CTRL_REG));
|
||||
|
||||
musb->isr = da8xx_musb_interrupt;
|
||||
return 0;
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da8xx_musb_exit(struct musb *musb)
|
||||
{
|
||||
del_timer_sync(&otg_workaround);
|
||||
|
||||
phy_off();
|
||||
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops da8xx_ops = {
|
||||
.init = da8xx_musb_init,
|
||||
.exit = da8xx_musb_exit,
|
||||
|
||||
.enable = da8xx_musb_enable,
|
||||
.disable = da8xx_musb_disable,
|
||||
|
||||
.set_mode = da8xx_musb_set_mode,
|
||||
.try_idle = da8xx_musb_try_idle,
|
||||
|
||||
.set_vbus = da8xx_musb_set_vbus,
|
||||
};
|
||||
|
||||
static const struct platform_device_info da8xx_dev_info = {
|
||||
.name = "musb-hdrc",
|
||||
.id = PLATFORM_DEVID_AUTO,
|
||||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
static int da8xx_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource musb_resources[2];
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct platform_device *musb;
|
||||
struct da8xx_glue *glue;
|
||||
struct platform_device_info pinfo;
|
||||
struct clk *clk;
|
||||
|
||||
int ret = -ENOMEM;
|
||||
|
||||
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
clk = clk_get(&pdev->dev, "usb20");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto err3;
|
||||
}
|
||||
|
||||
ret = clk_enable(clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable clock\n");
|
||||
goto err4;
|
||||
}
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->clk = clk;
|
||||
|
||||
pdata->platform_ops = &da8xx_ops;
|
||||
|
||||
glue->phy = usb_phy_generic_register();
|
||||
if (IS_ERR(glue->phy)) {
|
||||
ret = PTR_ERR(glue->phy);
|
||||
goto err5;
|
||||
}
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
memset(musb_resources, 0x00, sizeof(*musb_resources) *
|
||||
ARRAY_SIZE(musb_resources));
|
||||
|
||||
musb_resources[0].name = pdev->resource[0].name;
|
||||
musb_resources[0].start = pdev->resource[0].start;
|
||||
musb_resources[0].end = pdev->resource[0].end;
|
||||
musb_resources[0].flags = pdev->resource[0].flags;
|
||||
|
||||
musb_resources[1].name = pdev->resource[1].name;
|
||||
musb_resources[1].start = pdev->resource[1].start;
|
||||
musb_resources[1].end = pdev->resource[1].end;
|
||||
musb_resources[1].flags = pdev->resource[1].flags;
|
||||
|
||||
pinfo = da8xx_dev_info;
|
||||
pinfo.parent = &pdev->dev;
|
||||
pinfo.res = musb_resources;
|
||||
pinfo.num_res = ARRAY_SIZE(musb_resources);
|
||||
pinfo.data = pdata;
|
||||
pinfo.size_data = sizeof(*pdata);
|
||||
|
||||
glue->musb = musb = platform_device_register_full(&pinfo);
|
||||
if (IS_ERR(musb)) {
|
||||
ret = PTR_ERR(musb);
|
||||
dev_err(&pdev->dev, "failed to register musb device: %d\n", ret);
|
||||
goto err6;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err6:
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
|
||||
err5:
|
||||
clk_disable(clk);
|
||||
|
||||
err4:
|
||||
clk_put(clk);
|
||||
|
||||
err3:
|
||||
kfree(glue);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da8xx_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct da8xx_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
usb_phy_generic_unregister(glue->phy);
|
||||
clk_disable(glue->clk);
|
||||
clk_put(glue->clk);
|
||||
kfree(glue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver da8xx_driver = {
|
||||
.probe = da8xx_probe,
|
||||
.remove = da8xx_remove,
|
||||
.driver = {
|
||||
.name = "musb-da8xx",
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("DA8xx/OMAP-L1x MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Sergei Shtylyov <sshtylyov@ru.mvista.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(da8xx_driver);
|
616
drivers/usb/musb/davinci.c
Normal file
616
drivers/usb/musb/davinci.c
Normal file
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
|
||||
#include <mach/cputype.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
#ifdef CONFIG_MACH_DAVINCI_EVM
|
||||
#define GPIO_nVBUS_DRV 160
|
||||
#endif
|
||||
|
||||
#include "davinci.h"
|
||||
#include "cppi_dma.h"
|
||||
|
||||
|
||||
#define USB_PHY_CTRL IO_ADDRESS(USBPHY_CTL_PADDR)
|
||||
#define DM355_DEEPSLEEP IO_ADDRESS(DM355_DEEPSLEEP_PADDR)
|
||||
|
||||
struct davinci_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
/* REVISIT (PM) we should be able to keep the PHY in low power mode most
|
||||
* of the time (24 MHZ oscillator and PLL off, etc) by setting POWER.D0
|
||||
* and, when in host mode, autosuspending idle root ports... PHYPLLON
|
||||
* (overriding SUSPENDM?) then likely needs to stay off.
|
||||
*/
|
||||
|
||||
static inline void phy_on(void)
|
||||
{
|
||||
u32 phy_ctrl = __raw_readl(USB_PHY_CTRL);
|
||||
|
||||
/* power everything up; start the on-chip PHY and its PLL */
|
||||
phy_ctrl &= ~(USBPHY_OSCPDWN | USBPHY_OTGPDWN | USBPHY_PHYPDWN);
|
||||
phy_ctrl |= USBPHY_SESNDEN | USBPHY_VBDTCTEN | USBPHY_PHYPLLON;
|
||||
__raw_writel(phy_ctrl, USB_PHY_CTRL);
|
||||
|
||||
/* wait for PLL to lock before proceeding */
|
||||
while ((__raw_readl(USB_PHY_CTRL) & USBPHY_PHYCLKGD) == 0)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static inline void phy_off(void)
|
||||
{
|
||||
u32 phy_ctrl = __raw_readl(USB_PHY_CTRL);
|
||||
|
||||
/* powerdown the on-chip PHY, its PLL, and the OTG block */
|
||||
phy_ctrl &= ~(USBPHY_SESNDEN | USBPHY_VBDTCTEN | USBPHY_PHYPLLON);
|
||||
phy_ctrl |= USBPHY_OSCPDWN | USBPHY_OTGPDWN | USBPHY_PHYPDWN;
|
||||
__raw_writel(phy_ctrl, USB_PHY_CTRL);
|
||||
}
|
||||
|
||||
static int dma_off = 1;
|
||||
|
||||
static void davinci_musb_enable(struct musb *musb)
|
||||
{
|
||||
u32 tmp, old, val;
|
||||
|
||||
/* workaround: setup irqs through both register sets */
|
||||
tmp = (musb->epmask & DAVINCI_USB_TX_ENDPTS_MASK)
|
||||
<< DAVINCI_USB_TXINT_SHIFT;
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp);
|
||||
old = tmp;
|
||||
tmp = (musb->epmask & (0xfffe & DAVINCI_USB_RX_ENDPTS_MASK))
|
||||
<< DAVINCI_USB_RXINT_SHIFT;
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp);
|
||||
tmp |= old;
|
||||
|
||||
val = ~MUSB_INTR_SOF;
|
||||
tmp |= ((val & 0x01ff) << DAVINCI_USB_USBINT_SHIFT);
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp);
|
||||
|
||||
if (is_dma_capable() && !dma_off)
|
||||
printk(KERN_WARNING "%s %s: dma not reactivated\n",
|
||||
__FILE__, __func__);
|
||||
else
|
||||
dma_off = 0;
|
||||
|
||||
/* force a DRVVBUS irq so we can start polling for ID change */
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG,
|
||||
DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable the HDRC and flush interrupts
|
||||
*/
|
||||
static void davinci_musb_disable(struct musb *musb)
|
||||
{
|
||||
/* because we don't set CTRLR.UINT, "important" to:
|
||||
* - not read/write INTRUSB/INTRUSBE
|
||||
* - (except during initial setup, as workaround)
|
||||
* - use INTSETR/INTCLRR instead
|
||||
*/
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_CLR_REG,
|
||||
DAVINCI_USB_USBINT_MASK
|
||||
| DAVINCI_USB_TXINT_MASK
|
||||
| DAVINCI_USB_RXINT_MASK);
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
|
||||
musb_writel(musb->ctrl_base, DAVINCI_USB_EOI_REG, 0);
|
||||
|
||||
if (is_dma_capable() && !dma_off)
|
||||
WARNING("dma still active\n");
|
||||
}
|
||||
|
||||
|
||||
#define portstate(stmt) stmt
|
||||
|
||||
/*
|
||||
* VBUS SWITCHING IS BOARD-SPECIFIC ... at least for the DM6446 EVM,
|
||||
* which doesn't wire DRVVBUS to the FET that switches it. Unclear
|
||||
* if that's a problem with the DM6446 chip or just with that board.
|
||||
*
|
||||
* In either case, the DM355 EVM automates DRVVBUS the normal way,
|
||||
* when J10 is out, and TI documents it as handling OTG.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MACH_DAVINCI_EVM
|
||||
|
||||
static int vbus_state = -1;
|
||||
|
||||
/* I2C operations are always synchronous, and require a task context.
|
||||
* With unloaded systems, using the shared workqueue seems to suffice
|
||||
* to satisfy the 100msec A_WAIT_VRISE timeout...
|
||||
*/
|
||||
static void evm_deferred_drvvbus(struct work_struct *ignored)
|
||||
{
|
||||
gpio_set_value_cansleep(GPIO_nVBUS_DRV, vbus_state);
|
||||
vbus_state = !vbus_state;
|
||||
}
|
||||
|
||||
#endif /* EVM */
|
||||
|
||||
static void davinci_musb_source_power(struct musb *musb, int is_on, int immediate)
|
||||
{
|
||||
#ifdef CONFIG_MACH_DAVINCI_EVM
|
||||
if (is_on)
|
||||
is_on = 1;
|
||||
|
||||
if (vbus_state == is_on)
|
||||
return;
|
||||
vbus_state = !is_on; /* 0/1 vs "-1 == unknown/init" */
|
||||
|
||||
if (machine_is_davinci_evm()) {
|
||||
static DECLARE_WORK(evm_vbus_work, evm_deferred_drvvbus);
|
||||
|
||||
if (immediate)
|
||||
gpio_set_value_cansleep(GPIO_nVBUS_DRV, vbus_state);
|
||||
else
|
||||
schedule_work(&evm_vbus_work);
|
||||
}
|
||||
if (immediate)
|
||||
vbus_state = is_on;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void davinci_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
WARN_ON(is_on && is_peripheral_active(musb));
|
||||
davinci_musb_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 DaVinci's won't expose several OTG-critical
|
||||
* status change events (from the transceiver) otherwise.
|
||||
*/
|
||||
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
||||
dev_dbg(musb->controller, "poll devctl %02x (%s)\n", devctl,
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
/* Wait till VBUS falls below SessionEnd (~0.2V); 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, DAVINCI_USB_INT_SET_REG,
|
||||
MUSB_INTR_VBUSERROR << DAVINCI_USB_USBINT_SHIFT);
|
||||
break;
|
||||
case OTG_STATE_B_IDLE:
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
musb_writeb(mregs, MUSB_DEVCTL,
|
||||
devctl | MUSB_DEVCTL_SESSION);
|
||||
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);
|
||||
}
|
||||
|
||||
static irqreturn_t davinci_musb_interrupt(int irq, void *__hci)
|
||||
{
|
||||
unsigned long flags;
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
struct musb *musb = __hci;
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
void __iomem *tibase = musb->ctrl_base;
|
||||
struct cppi *cppi;
|
||||
u32 tmp;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
/* NOTE: DaVinci shadows the Mentor IRQs. Don't manage them through
|
||||
* the Mentor registers (except for setup), use the TI ones and EOI.
|
||||
*
|
||||
* Docs describe irq "vector" registers associated with the CPPI and
|
||||
* USB EOI registers. These hold a bitmask corresponding to the
|
||||
* current IRQ, not an irq handler address. Would using those bits
|
||||
* resolve some of the races observed in this dispatch code??
|
||||
*/
|
||||
|
||||
/* CPPI interrupts share the same IRQ line, but have their own
|
||||
* mask, state, "vector", and EOI registers.
|
||||
*/
|
||||
cppi = container_of(musb->dma_controller, struct cppi, controller);
|
||||
if (is_cppi_enabled() && musb->dma_controller && !cppi->irq)
|
||||
retval = cppi_interrupt(irq, __hci);
|
||||
|
||||
/* ack and handle non-CPPI interrupts */
|
||||
tmp = musb_readl(tibase, DAVINCI_USB_INT_SRC_MASKED_REG);
|
||||
musb_writel(tibase, DAVINCI_USB_INT_SRC_CLR_REG, tmp);
|
||||
dev_dbg(musb->controller, "IRQ %08x\n", tmp);
|
||||
|
||||
musb->int_rx = (tmp & DAVINCI_USB_RXINT_MASK)
|
||||
>> DAVINCI_USB_RXINT_SHIFT;
|
||||
musb->int_tx = (tmp & DAVINCI_USB_TXINT_MASK)
|
||||
>> DAVINCI_USB_TXINT_SHIFT;
|
||||
musb->int_usb = (tmp & DAVINCI_USB_USBINT_MASK)
|
||||
>> DAVINCI_USB_USBINT_SHIFT;
|
||||
|
||||
/* DRVVBUS irqs are the only proxy we have (a very poor one!) for
|
||||
* DaVinci'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 we know its
|
||||
* value, but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set.
|
||||
* Also, DRVVBUS pulses for SRP (but not at 5V) ...
|
||||
*/
|
||||
if (tmp & (DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT)) {
|
||||
int drvvbus = musb_readl(tibase, DAVINCI_USB_STAT_REG);
|
||||
void __iomem *mregs = musb->mregs;
|
||||
u8 devctl = musb_readb(mregs, MUSB_DEVCTL);
|
||||
int err = musb->int_usb & MUSB_INTR_VBUSERROR;
|
||||
|
||||
err = 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 (on EVM, a **long** time) 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 (drvvbus) {
|
||||
MUSB_HST_MODE(musb);
|
||||
otg->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);
|
||||
otg->default_a = 0;
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
portstate(musb->port1_status &= ~USB_PORT_STAT_POWER);
|
||||
}
|
||||
|
||||
/* NOTE: this must complete poweron within 100 msec
|
||||
* (OTG_TIME_A_WAIT_VRISE) but we don't check for that.
|
||||
*/
|
||||
davinci_musb_source_power(musb, drvvbus, 0);
|
||||
dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n",
|
||||
drvvbus ? "on" : "off",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
err ? " ERROR" : "",
|
||||
devctl);
|
||||
retval = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (musb->int_tx || musb->int_rx || musb->int_usb)
|
||||
retval |= musb_interrupt(musb);
|
||||
|
||||
/* irq stays asserted until EOI is written */
|
||||
musb_writel(tibase, DAVINCI_USB_EOI_REG, 0);
|
||||
|
||||
/* poll for ID change */
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE)
|
||||
mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int davinci_musb_set_mode(struct musb *musb, u8 mode)
|
||||
{
|
||||
/* EVM can't do this (right?) */
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int davinci_musb_init(struct musb *musb)
|
||||
{
|
||||
void __iomem *tibase = musb->ctrl_base;
|
||||
u32 revision;
|
||||
int ret = -ENODEV;
|
||||
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (IS_ERR_OR_NULL(musb->xceiv)) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
musb->mregs += DAVINCI_BASE_OFFSET;
|
||||
|
||||
/* returns zero if e.g. not clocked */
|
||||
revision = musb_readl(tibase, DAVINCI_USB_VERSION_REG);
|
||||
if (revision == 0)
|
||||
goto fail;
|
||||
|
||||
setup_timer(&otg_workaround, otg_timer, (unsigned long) musb);
|
||||
|
||||
davinci_musb_source_power(musb, 0, 1);
|
||||
|
||||
/* dm355 EVM swaps D+/D- for signal integrity, and
|
||||
* is clocked from the main 24 MHz crystal.
|
||||
*/
|
||||
if (machine_is_davinci_dm355_evm()) {
|
||||
u32 phy_ctrl = __raw_readl(USB_PHY_CTRL);
|
||||
|
||||
phy_ctrl &= ~(3 << 9);
|
||||
phy_ctrl |= USBPHY_DATAPOL;
|
||||
__raw_writel(phy_ctrl, USB_PHY_CTRL);
|
||||
}
|
||||
|
||||
/* On dm355, the default-A state machine needs DRVVBUS control.
|
||||
* If we won't be a host, there's no need to turn it on.
|
||||
*/
|
||||
if (cpu_is_davinci_dm355()) {
|
||||
u32 deepsleep = __raw_readl(DM355_DEEPSLEEP);
|
||||
|
||||
deepsleep &= ~DRVVBUS_FORCE;
|
||||
__raw_writel(deepsleep, DM355_DEEPSLEEP);
|
||||
}
|
||||
|
||||
/* reset the controller */
|
||||
musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1);
|
||||
|
||||
/* start the on-chip PHY and its PLL */
|
||||
phy_on();
|
||||
|
||||
msleep(5);
|
||||
|
||||
/* NOTE: irqs are in mixed mode, not bypass to pure-musb */
|
||||
pr_debug("DaVinci OTG revision %08x phy %03x control %02x\n",
|
||||
revision, __raw_readl(USB_PHY_CTRL),
|
||||
musb_readb(tibase, DAVINCI_USB_CTRL_REG));
|
||||
|
||||
musb->isr = davinci_musb_interrupt;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
usb_put_phy(musb->xceiv);
|
||||
unregister:
|
||||
usb_phy_generic_unregister();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int davinci_musb_exit(struct musb *musb)
|
||||
{
|
||||
del_timer_sync(&otg_workaround);
|
||||
|
||||
/* force VBUS off */
|
||||
if (cpu_is_davinci_dm355()) {
|
||||
u32 deepsleep = __raw_readl(DM355_DEEPSLEEP);
|
||||
|
||||
deepsleep &= ~DRVVBUS_FORCE;
|
||||
deepsleep |= DRVVBUS_OVERRIDE;
|
||||
__raw_writel(deepsleep, DM355_DEEPSLEEP);
|
||||
}
|
||||
|
||||
davinci_musb_source_power(musb, 0 /*off*/, 1);
|
||||
|
||||
/* delay, to avoid problems with module reload */
|
||||
if (musb->xceiv->otg->default_a) {
|
||||
int maxdelay = 30;
|
||||
u8 devctl, warn = 0;
|
||||
|
||||
/* if there's no peripheral connected, this can take a
|
||||
* long time to fall, especially on EVM with huge C133.
|
||||
*/
|
||||
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;
|
||||
dev_dbg(musb->controller, "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)
|
||||
dev_dbg(musb->controller, "VBUS off timeout (devctl %02x)\n", devctl);
|
||||
}
|
||||
|
||||
phy_off();
|
||||
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops davinci_ops = {
|
||||
.init = davinci_musb_init,
|
||||
.exit = davinci_musb_exit,
|
||||
|
||||
.enable = davinci_musb_enable,
|
||||
.disable = davinci_musb_disable,
|
||||
|
||||
.set_mode = davinci_musb_set_mode,
|
||||
|
||||
.set_vbus = davinci_musb_set_vbus,
|
||||
};
|
||||
|
||||
static const struct platform_device_info davinci_dev_info = {
|
||||
.name = "musb-hdrc",
|
||||
.id = PLATFORM_DEVID_AUTO,
|
||||
.dma_mask = DMA_BIT_MASK(32),
|
||||
};
|
||||
|
||||
static int davinci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource musb_resources[3];
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct platform_device *musb;
|
||||
struct davinci_glue *glue;
|
||||
struct platform_device_info pinfo;
|
||||
struct clk *clk;
|
||||
|
||||
int ret = -ENOMEM;
|
||||
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, "usb");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto err0;
|
||||
}
|
||||
|
||||
ret = clk_enable(clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable clock\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->clk = clk;
|
||||
|
||||
pdata->platform_ops = &davinci_ops;
|
||||
|
||||
usb_phy_generic_register();
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
memset(musb_resources, 0x00, sizeof(*musb_resources) *
|
||||
ARRAY_SIZE(musb_resources));
|
||||
|
||||
musb_resources[0].name = pdev->resource[0].name;
|
||||
musb_resources[0].start = pdev->resource[0].start;
|
||||
musb_resources[0].end = pdev->resource[0].end;
|
||||
musb_resources[0].flags = pdev->resource[0].flags;
|
||||
|
||||
musb_resources[1].name = pdev->resource[1].name;
|
||||
musb_resources[1].start = pdev->resource[1].start;
|
||||
musb_resources[1].end = pdev->resource[1].end;
|
||||
musb_resources[1].flags = pdev->resource[1].flags;
|
||||
|
||||
/*
|
||||
* For DM6467 3 resources are passed. A placeholder for the 3rd
|
||||
* resource is always there, so it's safe to always copy it...
|
||||
*/
|
||||
musb_resources[2].name = pdev->resource[2].name;
|
||||
musb_resources[2].start = pdev->resource[2].start;
|
||||
musb_resources[2].end = pdev->resource[2].end;
|
||||
musb_resources[2].flags = pdev->resource[2].flags;
|
||||
|
||||
pinfo = davinci_dev_info;
|
||||
pinfo.parent = &pdev->dev;
|
||||
pinfo.res = musb_resources;
|
||||
pinfo.num_res = ARRAY_SIZE(musb_resources);
|
||||
pinfo.data = pdata;
|
||||
pinfo.size_data = sizeof(*pdata);
|
||||
|
||||
glue->musb = musb = platform_device_register_full(&pinfo);
|
||||
if (IS_ERR(musb)) {
|
||||
ret = PTR_ERR(musb);
|
||||
dev_err(&pdev->dev, "failed to register musb device: %d\n", ret);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
clk_disable(clk);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int davinci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct davinci_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
usb_phy_generic_unregister();
|
||||
clk_disable(glue->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver davinci_driver = {
|
||||
.probe = davinci_probe,
|
||||
.remove = davinci_remove,
|
||||
.driver = {
|
||||
.name = "musb-davinci",
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("DaVinci MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(davinci_driver);
|
107
drivers/usb/musb/davinci.h
Normal file
107
drivers/usb/musb/davinci.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_HDRDF_H__
|
||||
#define __MUSB_HDRDF_H__
|
||||
|
||||
/*
|
||||
* DaVinci-specific definitions
|
||||
*/
|
||||
|
||||
/* Integrated highspeed/otg PHY */
|
||||
#define USBPHY_CTL_PADDR 0x01c40034
|
||||
#define USBPHY_DATAPOL BIT(11) /* (dm355) switch D+/D- */
|
||||
#define USBPHY_PHYCLKGD BIT(8)
|
||||
#define USBPHY_SESNDEN BIT(7) /* v(sess_end) comparator */
|
||||
#define USBPHY_VBDTCTEN BIT(6) /* v(bus) comparator */
|
||||
#define USBPHY_VBUSSENS BIT(5) /* (dm355,ro) is vbus > 0.5V */
|
||||
#define USBPHY_PHYPLLON BIT(4) /* override pll suspend */
|
||||
#define USBPHY_CLKO1SEL BIT(3)
|
||||
#define USBPHY_OSCPDWN BIT(2)
|
||||
#define USBPHY_OTGPDWN BIT(1)
|
||||
#define USBPHY_PHYPDWN BIT(0)
|
||||
|
||||
#define DM355_DEEPSLEEP_PADDR 0x01c40048
|
||||
#define DRVVBUS_FORCE BIT(2)
|
||||
#define DRVVBUS_OVERRIDE BIT(1)
|
||||
|
||||
/* For now include usb OTG module registers here */
|
||||
#define DAVINCI_USB_VERSION_REG 0x00
|
||||
#define DAVINCI_USB_CTRL_REG 0x04
|
||||
#define DAVINCI_USB_STAT_REG 0x08
|
||||
#define DAVINCI_RNDIS_REG 0x10
|
||||
#define DAVINCI_AUTOREQ_REG 0x14
|
||||
#define DAVINCI_USB_INT_SOURCE_REG 0x20
|
||||
#define DAVINCI_USB_INT_SET_REG 0x24
|
||||
#define DAVINCI_USB_INT_SRC_CLR_REG 0x28
|
||||
#define DAVINCI_USB_INT_MASK_REG 0x2c
|
||||
#define DAVINCI_USB_INT_MASK_SET_REG 0x30
|
||||
#define DAVINCI_USB_INT_MASK_CLR_REG 0x34
|
||||
#define DAVINCI_USB_INT_SRC_MASKED_REG 0x38
|
||||
#define DAVINCI_USB_EOI_REG 0x3c
|
||||
#define DAVINCI_USB_EOI_INTVEC 0x40
|
||||
|
||||
/* BEGIN CPPI-generic (?) */
|
||||
|
||||
/* CPPI related registers */
|
||||
#define DAVINCI_TXCPPI_CTRL_REG 0x80
|
||||
#define DAVINCI_TXCPPI_TEAR_REG 0x84
|
||||
#define DAVINCI_CPPI_EOI_REG 0x88
|
||||
#define DAVINCI_CPPI_INTVEC_REG 0x8c
|
||||
#define DAVINCI_TXCPPI_MASKED_REG 0x90
|
||||
#define DAVINCI_TXCPPI_RAW_REG 0x94
|
||||
#define DAVINCI_TXCPPI_INTENAB_REG 0x98
|
||||
#define DAVINCI_TXCPPI_INTCLR_REG 0x9c
|
||||
|
||||
#define DAVINCI_RXCPPI_CTRL_REG 0xC0
|
||||
#define DAVINCI_RXCPPI_MASKED_REG 0xD0
|
||||
#define DAVINCI_RXCPPI_RAW_REG 0xD4
|
||||
#define DAVINCI_RXCPPI_INTENAB_REG 0xD8
|
||||
#define DAVINCI_RXCPPI_INTCLR_REG 0xDC
|
||||
|
||||
#define DAVINCI_RXCPPI_BUFCNT0_REG 0xE0
|
||||
#define DAVINCI_RXCPPI_BUFCNT1_REG 0xE4
|
||||
#define DAVINCI_RXCPPI_BUFCNT2_REG 0xE8
|
||||
#define DAVINCI_RXCPPI_BUFCNT3_REG 0xEC
|
||||
|
||||
/* CPPI state RAM entries */
|
||||
#define DAVINCI_CPPI_STATERAM_BASE_OFFSET 0x100
|
||||
|
||||
#define DAVINCI_TXCPPI_STATERAM_OFFSET(chnum) \
|
||||
(DAVINCI_CPPI_STATERAM_BASE_OFFSET + ((chnum) * 0x40))
|
||||
#define DAVINCI_RXCPPI_STATERAM_OFFSET(chnum) \
|
||||
(DAVINCI_CPPI_STATERAM_BASE_OFFSET + 0x20 + ((chnum) * 0x40))
|
||||
|
||||
/* CPPI masks */
|
||||
#define DAVINCI_DMA_CTRL_ENABLE 1
|
||||
#define DAVINCI_DMA_CTRL_DISABLE 0
|
||||
|
||||
#define DAVINCI_DMA_ALL_CHANNELS_ENABLE 0xF
|
||||
#define DAVINCI_DMA_ALL_CHANNELS_DISABLE 0xF
|
||||
|
||||
/* END CPPI-generic (?) */
|
||||
|
||||
#define DAVINCI_USB_TX_ENDPTS_MASK 0x1f /* ep0 + 4 tx */
|
||||
#define DAVINCI_USB_RX_ENDPTS_MASK 0x1e /* 4 rx */
|
||||
|
||||
#define DAVINCI_USB_USBINT_SHIFT 16
|
||||
#define DAVINCI_USB_TXINT_SHIFT 0
|
||||
#define DAVINCI_USB_RXINT_SHIFT 8
|
||||
|
||||
#define DAVINCI_INTR_DRVVBUS 0x0100
|
||||
|
||||
#define DAVINCI_USB_USBINT_MASK 0x01ff0000 /* 8 Mentor, DRVVBUS */
|
||||
#define DAVINCI_USB_TXINT_MASK \
|
||||
(DAVINCI_USB_TX_ENDPTS_MASK << DAVINCI_USB_TXINT_SHIFT)
|
||||
#define DAVINCI_USB_RXINT_MASK \
|
||||
(DAVINCI_USB_RX_ENDPTS_MASK << DAVINCI_USB_RXINT_SHIFT)
|
||||
|
||||
#define DAVINCI_BASE_OFFSET 0x400
|
||||
|
||||
#endif /* __MUSB_HDRDF_H__ */
|
204
drivers/usb/musb/jz4740.c
Normal file
204
drivers/usb/musb/jz4740.c
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Ingenic JZ4740 "glue layer"
|
||||
*
|
||||
* Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
struct jz4740_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci)
|
||||
{
|
||||
unsigned long flags;
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
struct musb *musb = __hci;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
|
||||
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
|
||||
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
|
||||
|
||||
/*
|
||||
* The controller is gadget only, the state of the host mode IRQ bits is
|
||||
* undefined. Mask them to make sure that the musb driver core will
|
||||
* never see them set
|
||||
*/
|
||||
musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME |
|
||||
MUSB_INTR_RESET | MUSB_INTR_SOF;
|
||||
|
||||
if (musb->int_usb || musb->int_tx || musb->int_rx)
|
||||
retval = musb_interrupt(musb);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = {
|
||||
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
|
||||
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
|
||||
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 64, },
|
||||
};
|
||||
|
||||
static struct musb_hdrc_config jz4740_musb_config = {
|
||||
/* Silicon does not implement USB OTG. */
|
||||
.multipoint = 0,
|
||||
/* Max EPs scanned, driver will decide which EP can be used. */
|
||||
.num_eps = 4,
|
||||
/* RAMbits needed to configure EPs from table */
|
||||
.ram_bits = 9,
|
||||
.fifo_cfg = jz4740_musb_fifo_cfg,
|
||||
.fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg),
|
||||
};
|
||||
|
||||
static struct musb_hdrc_platform_data jz4740_musb_platform_data = {
|
||||
.mode = MUSB_PERIPHERAL,
|
||||
.config = &jz4740_musb_config,
|
||||
};
|
||||
|
||||
static int jz4740_musb_init(struct musb *musb)
|
||||
{
|
||||
usb_phy_generic_register();
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (!musb->xceiv) {
|
||||
pr_err("HS UDC: no transceiver configured\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Silicon does not implement ConfigData register.
|
||||
* Set dyn_fifo to avoid reading EP config from hardware.
|
||||
*/
|
||||
musb->dyn_fifo = true;
|
||||
|
||||
musb->isr = jz4740_musb_interrupt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jz4740_musb_exit(struct musb *musb)
|
||||
{
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops jz4740_musb_ops = {
|
||||
.init = jz4740_musb_init,
|
||||
.exit = jz4740_musb_exit,
|
||||
};
|
||||
|
||||
static int jz4740_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct musb_hdrc_platform_data *pdata = &jz4740_musb_platform_data;
|
||||
struct platform_device *musb;
|
||||
struct jz4740_glue *glue;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue)
|
||||
return -ENOMEM;
|
||||
|
||||
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
|
||||
if (!musb) {
|
||||
dev_err(&pdev->dev, "failed to allocate musb device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, "udc");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto err_platform_device_put;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable clock\n");
|
||||
goto err_platform_device_put;
|
||||
}
|
||||
|
||||
musb->dev.parent = &pdev->dev;
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->musb = musb;
|
||||
glue->clk = clk;
|
||||
|
||||
pdata->platform_ops = &jz4740_musb_ops;
|
||||
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
ret = platform_device_add_resources(musb, pdev->resource,
|
||||
pdev->num_resources);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add resources\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add platform_data\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
ret = platform_device_add(musb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register musb device\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(clk);
|
||||
err_platform_device_put:
|
||||
platform_device_put(musb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int jz4740_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct jz4740_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
usb_phy_generic_unregister(pdev);
|
||||
clk_disable_unprepare(glue->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver jz4740_driver = {
|
||||
.probe = jz4740_probe,
|
||||
.remove = jz4740_remove,
|
||||
.driver = {
|
||||
.name = "musb-jz4740",
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Apelete Seketeli <apelete@seketeli.net>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(jz4740_driver);
|
43
drivers/usb/musb/musb_am335x.c
Normal file
43
drivers/usb/musb/musb_am335x.c
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
static int am335x_child_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id am335x_child_of_match[] = {
|
||||
{ .compatible = "ti,am33xx-usb" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, am335x_child_of_match);
|
||||
|
||||
static struct platform_driver am335x_child_driver = {
|
||||
.probe = am335x_child_probe,
|
||||
.driver = {
|
||||
.name = "am335x-usb-childs",
|
||||
.of_match_table = am335x_child_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init am335x_child_init(void)
|
||||
{
|
||||
return platform_driver_register(&am335x_child_driver);
|
||||
}
|
||||
module_init(am335x_child_init);
|
||||
|
||||
MODULE_DESCRIPTION("AM33xx child devices");
|
||||
MODULE_LICENSE("GPL v2");
|
2382
drivers/usb/musb/musb_core.c
Normal file
2382
drivers/usb/musb/musb_core.c
Normal file
File diff suppressed because it is too large
Load diff
590
drivers/usb/musb/musb_core.h
Normal file
590
drivers/usb/musb/musb_core.h
Normal file
|
@ -0,0 +1,590 @@
|
|||
/*
|
||||
* MUSB OTG driver defines
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_CORE_H__
|
||||
#define __MUSB_CORE_H__
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/musb.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct musb;
|
||||
struct musb_hw_ep;
|
||||
struct musb_ep;
|
||||
|
||||
/* Helper defines for struct musb->hwvers */
|
||||
#define MUSB_HWVERS_MAJOR(x) ((x >> 10) & 0x1f)
|
||||
#define MUSB_HWVERS_MINOR(x) (x & 0x3ff)
|
||||
#define MUSB_HWVERS_RC 0x8000
|
||||
#define MUSB_HWVERS_1300 0x52C
|
||||
#define MUSB_HWVERS_1400 0x590
|
||||
#define MUSB_HWVERS_1800 0x720
|
||||
#define MUSB_HWVERS_1900 0x784
|
||||
#define MUSB_HWVERS_2000 0x800
|
||||
|
||||
#include "musb_debug.h"
|
||||
#include "musb_dma.h"
|
||||
|
||||
#include "musb_io.h"
|
||||
#include "musb_regs.h"
|
||||
|
||||
#include "musb_gadget.h"
|
||||
#include <linux/usb/hcd.h>
|
||||
#include "musb_host.h"
|
||||
|
||||
/* NOTE: otg and peripheral-only state machines start at B_IDLE.
|
||||
* OTG or host-only go to A_IDLE when ID is sensed.
|
||||
*/
|
||||
#define is_peripheral_active(m) (!(m)->is_host)
|
||||
#define is_host_active(m) ((m)->is_host)
|
||||
|
||||
enum {
|
||||
MUSB_PORT_MODE_HOST = 1,
|
||||
MUSB_PORT_MODE_GADGET,
|
||||
MUSB_PORT_MODE_DUAL_ROLE,
|
||||
};
|
||||
|
||||
/****************************** CONSTANTS ********************************/
|
||||
|
||||
#ifndef MUSB_C_NUM_EPS
|
||||
#define MUSB_C_NUM_EPS ((u8)16)
|
||||
#endif
|
||||
|
||||
#ifndef MUSB_MAX_END0_PACKET
|
||||
#define MUSB_MAX_END0_PACKET ((u16)MUSB_EP0_FIFOSIZE)
|
||||
#endif
|
||||
|
||||
/* host side ep0 states */
|
||||
enum musb_h_ep0_state {
|
||||
MUSB_EP0_IDLE,
|
||||
MUSB_EP0_START, /* expect ack of setup */
|
||||
MUSB_EP0_IN, /* expect IN DATA */
|
||||
MUSB_EP0_OUT, /* expect ack of OUT DATA */
|
||||
MUSB_EP0_STATUS, /* expect ack of STATUS */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* peripheral side ep0 states */
|
||||
enum musb_g_ep0_state {
|
||||
MUSB_EP0_STAGE_IDLE, /* idle, waiting for SETUP */
|
||||
MUSB_EP0_STAGE_SETUP, /* received SETUP */
|
||||
MUSB_EP0_STAGE_TX, /* IN data */
|
||||
MUSB_EP0_STAGE_RX, /* OUT data */
|
||||
MUSB_EP0_STAGE_STATUSIN, /* (after OUT data) */
|
||||
MUSB_EP0_STAGE_STATUSOUT, /* (after IN data) */
|
||||
MUSB_EP0_STAGE_ACKWAIT, /* after zlp, before statusin */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/*
|
||||
* OTG protocol constants. See USB OTG 1.3 spec,
|
||||
* sections 5.5 "Device Timings" and 6.6.5 "Timers".
|
||||
*/
|
||||
#define OTG_TIME_A_WAIT_VRISE 100 /* msec (max) */
|
||||
#define OTG_TIME_A_WAIT_BCON 1100 /* min 1 second */
|
||||
#define OTG_TIME_A_AIDL_BDIS 200 /* min 200 msec */
|
||||
#define OTG_TIME_B_ASE0_BRST 100 /* min 3.125 ms */
|
||||
|
||||
|
||||
/*************************** REGISTER ACCESS ********************************/
|
||||
|
||||
/* Endpoint registers (other than dynfifo setup) can be accessed either
|
||||
* directly with the "flat" model, or after setting up an index register.
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_SOC_OMAP2430) \
|
||||
|| defined(CONFIG_SOC_OMAP3430) || defined(CONFIG_BLACKFIN) \
|
||||
|| defined(CONFIG_ARCH_OMAP4)
|
||||
/* REVISIT indexed access seemed to
|
||||
* misbehave (on DaVinci) for at least peripheral IN ...
|
||||
*/
|
||||
#define MUSB_FLAT_REG
|
||||
#endif
|
||||
|
||||
/* TUSB mapping: "flat" plus ep0 special cases */
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
#define musb_ep_select(_mbase, _epnum) \
|
||||
musb_writeb((_mbase), MUSB_INDEX, (_epnum))
|
||||
#define MUSB_EP_OFFSET MUSB_TUSB_OFFSET
|
||||
|
||||
/* "flat" mapping: each endpoint has its own i/o address */
|
||||
#elif defined(MUSB_FLAT_REG)
|
||||
#define musb_ep_select(_mbase, _epnum) (((void)(_mbase)), ((void)(_epnum)))
|
||||
#define MUSB_EP_OFFSET MUSB_FLAT_OFFSET
|
||||
|
||||
/* "indexed" mapping: INDEX register controls register bank select */
|
||||
#else
|
||||
#define musb_ep_select(_mbase, _epnum) \
|
||||
musb_writeb((_mbase), MUSB_INDEX, (_epnum))
|
||||
#define MUSB_EP_OFFSET MUSB_INDEXED_OFFSET
|
||||
#endif
|
||||
|
||||
/****************************** FUNCTIONS ********************************/
|
||||
|
||||
#define MUSB_HST_MODE(_musb)\
|
||||
{ (_musb)->is_host = true; }
|
||||
#define MUSB_DEV_MODE(_musb) \
|
||||
{ (_musb)->is_host = false; }
|
||||
|
||||
#define test_devctl_hst_mode(_x) \
|
||||
(musb_readb((_x)->mregs, MUSB_DEVCTL)&MUSB_DEVCTL_HM)
|
||||
|
||||
#define MUSB_MODE(musb) ((musb)->is_host ? "Host" : "Peripheral")
|
||||
|
||||
/******************************** TYPES *************************************/
|
||||
|
||||
/**
|
||||
* struct musb_platform_ops - Operations passed to musb_core by HW glue layer
|
||||
* @init: turns on clocks, sets up platform-specific registers, etc
|
||||
* @exit: undoes @init
|
||||
* @set_mode: forcefully changes operating mode
|
||||
* @try_ilde: tries to idle the IP
|
||||
* @vbus_status: returns vbus status if possible
|
||||
* @set_vbus: forces vbus status
|
||||
* @adjust_channel_params: pre check for standard dma channel_program func
|
||||
*/
|
||||
struct musb_platform_ops {
|
||||
int (*init)(struct musb *musb);
|
||||
int (*exit)(struct musb *musb);
|
||||
|
||||
void (*enable)(struct musb *musb);
|
||||
void (*disable)(struct musb *musb);
|
||||
|
||||
int (*set_mode)(struct musb *musb, u8 mode);
|
||||
void (*try_idle)(struct musb *musb, unsigned long timeout);
|
||||
int (*reset)(struct musb *musb);
|
||||
|
||||
int (*vbus_status)(struct musb *musb);
|
||||
void (*set_vbus)(struct musb *musb, int on);
|
||||
|
||||
int (*adjust_channel_params)(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 *mode,
|
||||
dma_addr_t *dma_addr, u32 *len);
|
||||
};
|
||||
|
||||
/*
|
||||
* struct musb_hw_ep - endpoint hardware (bidirectional)
|
||||
*
|
||||
* Ordered slightly for better cacheline locality.
|
||||
*/
|
||||
struct musb_hw_ep {
|
||||
struct musb *musb;
|
||||
void __iomem *fifo;
|
||||
void __iomem *regs;
|
||||
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
void __iomem *conf;
|
||||
#endif
|
||||
|
||||
/* index in musb->endpoints[] */
|
||||
u8 epnum;
|
||||
|
||||
/* hardware configuration, possibly dynamic */
|
||||
bool is_shared_fifo;
|
||||
bool tx_double_buffered;
|
||||
bool rx_double_buffered;
|
||||
u16 max_packet_sz_tx;
|
||||
u16 max_packet_sz_rx;
|
||||
|
||||
struct dma_channel *tx_channel;
|
||||
struct dma_channel *rx_channel;
|
||||
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
/* TUSB has "asynchronous" and "synchronous" dma modes */
|
||||
dma_addr_t fifo_async;
|
||||
dma_addr_t fifo_sync;
|
||||
void __iomem *fifo_sync_va;
|
||||
#endif
|
||||
|
||||
void __iomem *target_regs;
|
||||
|
||||
/* currently scheduled peripheral endpoint */
|
||||
struct musb_qh *in_qh;
|
||||
struct musb_qh *out_qh;
|
||||
|
||||
u8 rx_reinit;
|
||||
u8 tx_reinit;
|
||||
|
||||
/* peripheral side */
|
||||
struct musb_ep ep_in; /* TX */
|
||||
struct musb_ep ep_out; /* RX */
|
||||
};
|
||||
|
||||
static inline struct musb_request *next_in_request(struct musb_hw_ep *hw_ep)
|
||||
{
|
||||
return next_request(&hw_ep->ep_in);
|
||||
}
|
||||
|
||||
static inline struct musb_request *next_out_request(struct musb_hw_ep *hw_ep)
|
||||
{
|
||||
return next_request(&hw_ep->ep_out);
|
||||
}
|
||||
|
||||
struct musb_csr_regs {
|
||||
/* FIFO registers */
|
||||
u16 txmaxp, txcsr, rxmaxp, rxcsr;
|
||||
u16 rxfifoadd, txfifoadd;
|
||||
u8 txtype, txinterval, rxtype, rxinterval;
|
||||
u8 rxfifosz, txfifosz;
|
||||
u8 txfunaddr, txhubaddr, txhubport;
|
||||
u8 rxfunaddr, rxhubaddr, rxhubport;
|
||||
};
|
||||
|
||||
struct musb_context_registers {
|
||||
|
||||
u8 power;
|
||||
u8 intrusbe;
|
||||
u16 frame;
|
||||
u8 index, testmode;
|
||||
|
||||
u8 devctl, busctl, misc;
|
||||
u32 otg_interfsel;
|
||||
|
||||
struct musb_csr_regs index_regs[MUSB_C_NUM_EPS];
|
||||
};
|
||||
|
||||
/*
|
||||
* struct musb - Driver instance data.
|
||||
*/
|
||||
struct musb {
|
||||
/* device lock */
|
||||
spinlock_t lock;
|
||||
|
||||
const struct musb_platform_ops *ops;
|
||||
struct musb_context_registers context;
|
||||
|
||||
irqreturn_t (*isr)(int, void *);
|
||||
struct work_struct irq_work;
|
||||
struct delayed_work recover_work;
|
||||
struct delayed_work deassert_reset_work;
|
||||
struct delayed_work finish_resume_work;
|
||||
u16 hwvers;
|
||||
|
||||
u16 intrrxe;
|
||||
u16 intrtxe;
|
||||
/* this hub status bit is reserved by USB 2.0 and not seen by usbcore */
|
||||
#define MUSB_PORT_STAT_RESUME (1 << 31)
|
||||
|
||||
u32 port1_status;
|
||||
|
||||
unsigned long rh_timer;
|
||||
|
||||
enum musb_h_ep0_state ep0_stage;
|
||||
|
||||
/* bulk traffic normally dedicates endpoint hardware, and each
|
||||
* direction has its own ring of host side endpoints.
|
||||
* we try to progress the transfer at the head of each endpoint's
|
||||
* queue until it completes or NAKs too much; then we try the next
|
||||
* endpoint.
|
||||
*/
|
||||
struct musb_hw_ep *bulk_ep;
|
||||
|
||||
struct list_head control; /* of musb_qh */
|
||||
struct list_head in_bulk; /* of musb_qh */
|
||||
struct list_head out_bulk; /* of musb_qh */
|
||||
|
||||
struct timer_list otg_timer;
|
||||
struct notifier_block nb;
|
||||
|
||||
struct dma_controller *dma_controller;
|
||||
|
||||
struct device *controller;
|
||||
void __iomem *ctrl_base;
|
||||
void __iomem *mregs;
|
||||
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
dma_addr_t async;
|
||||
dma_addr_t sync;
|
||||
void __iomem *sync_va;
|
||||
u8 tusb_revision;
|
||||
#endif
|
||||
|
||||
/* passed down from chip/board specific irq handlers */
|
||||
u8 int_usb;
|
||||
u16 int_rx;
|
||||
u16 int_tx;
|
||||
|
||||
struct usb_phy *xceiv;
|
||||
struct phy *phy;
|
||||
|
||||
int nIrq;
|
||||
unsigned irq_wake:1;
|
||||
|
||||
struct musb_hw_ep endpoints[MUSB_C_NUM_EPS];
|
||||
#define control_ep endpoints
|
||||
|
||||
#define VBUSERR_RETRY_COUNT 3
|
||||
u16 vbuserr_retry;
|
||||
u16 epmask;
|
||||
u8 nr_endpoints;
|
||||
|
||||
int (*board_set_power)(int state);
|
||||
|
||||
u8 min_power; /* vbus for periph, in mA/2 */
|
||||
|
||||
int port_mode; /* MUSB_PORT_MODE_* */
|
||||
bool is_host;
|
||||
|
||||
int a_wait_bcon; /* VBUS timeout in msecs */
|
||||
unsigned long idle_timeout; /* Next timeout in jiffies */
|
||||
|
||||
/* active means connected and not suspended */
|
||||
unsigned is_active:1;
|
||||
|
||||
unsigned is_multipoint:1;
|
||||
|
||||
unsigned hb_iso_rx:1; /* high bandwidth iso rx? */
|
||||
unsigned hb_iso_tx:1; /* high bandwidth iso tx? */
|
||||
unsigned dyn_fifo:1; /* dynamic FIFO supported? */
|
||||
|
||||
unsigned bulk_split:1;
|
||||
#define can_bulk_split(musb,type) \
|
||||
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_split)
|
||||
|
||||
unsigned bulk_combine:1;
|
||||
#define can_bulk_combine(musb,type) \
|
||||
(((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_combine)
|
||||
|
||||
/* is_suspended means USB B_PERIPHERAL suspend */
|
||||
unsigned is_suspended:1;
|
||||
|
||||
/* may_wakeup means remote wakeup is enabled */
|
||||
unsigned may_wakeup:1;
|
||||
|
||||
/* is_self_powered is reported in device status and the
|
||||
* config descriptor. is_bus_powered means B_PERIPHERAL
|
||||
* draws some VBUS current; both can be true.
|
||||
*/
|
||||
unsigned is_self_powered:1;
|
||||
unsigned is_bus_powered:1;
|
||||
|
||||
unsigned set_address:1;
|
||||
unsigned test_mode:1;
|
||||
unsigned softconnect:1;
|
||||
|
||||
u8 address;
|
||||
u8 test_mode_nr;
|
||||
u16 ackpend; /* ep0 */
|
||||
enum musb_g_ep0_state ep0_state;
|
||||
struct usb_gadget g; /* the gadget */
|
||||
struct usb_gadget_driver *gadget_driver; /* its driver */
|
||||
struct usb_hcd *hcd; /* the usb hcd */
|
||||
|
||||
/*
|
||||
* FIXME: Remove this flag.
|
||||
*
|
||||
* This is only added to allow Blackfin to work
|
||||
* with current driver. For some unknown reason
|
||||
* Blackfin doesn't work with double buffering
|
||||
* and that's enabled by default.
|
||||
*
|
||||
* We added this flag to forcefully disable double
|
||||
* buffering until we get it working.
|
||||
*/
|
||||
unsigned double_buffer_not_ok:1;
|
||||
|
||||
struct musb_hdrc_config *config;
|
||||
|
||||
int xceiv_old_state;
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs_root;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct musb *gadget_to_musb(struct usb_gadget *g)
|
||||
{
|
||||
return container_of(g, struct musb, g);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLACKFIN
|
||||
static inline int musb_read_fifosize(struct musb *musb,
|
||||
struct musb_hw_ep *hw_ep, u8 epnum)
|
||||
{
|
||||
musb->nr_endpoints++;
|
||||
musb->epmask |= (1 << epnum);
|
||||
|
||||
if (epnum < 5) {
|
||||
hw_ep->max_packet_sz_tx = 128;
|
||||
hw_ep->max_packet_sz_rx = 128;
|
||||
} else {
|
||||
hw_ep->max_packet_sz_tx = 1024;
|
||||
hw_ep->max_packet_sz_rx = 1024;
|
||||
}
|
||||
hw_ep->is_shared_fifo = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void musb_configure_ep0(struct musb *musb)
|
||||
{
|
||||
musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE;
|
||||
musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE;
|
||||
musb->endpoints[0].is_shared_fifo = true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int musb_read_fifosize(struct musb *musb,
|
||||
struct musb_hw_ep *hw_ep, u8 epnum)
|
||||
{
|
||||
void __iomem *mbase = musb->mregs;
|
||||
u8 reg = 0;
|
||||
|
||||
/* read from core using indexed model */
|
||||
reg = musb_readb(mbase, MUSB_EP_OFFSET(epnum, MUSB_FIFOSIZE));
|
||||
/* 0's returned when no more endpoints */
|
||||
if (!reg)
|
||||
return -ENODEV;
|
||||
|
||||
musb->nr_endpoints++;
|
||||
musb->epmask |= (1 << epnum);
|
||||
|
||||
hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f);
|
||||
|
||||
/* shared TX/RX FIFO? */
|
||||
if ((reg & 0xf0) == 0xf0) {
|
||||
hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx;
|
||||
hw_ep->is_shared_fifo = true;
|
||||
return 0;
|
||||
} else {
|
||||
hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4);
|
||||
hw_ep->is_shared_fifo = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void musb_configure_ep0(struct musb *musb)
|
||||
{
|
||||
musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE;
|
||||
musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE;
|
||||
musb->endpoints[0].is_shared_fifo = true;
|
||||
}
|
||||
#endif /* CONFIG_BLACKFIN */
|
||||
|
||||
|
||||
/***************************** Glue it together *****************************/
|
||||
|
||||
extern const char musb_driver_name[];
|
||||
|
||||
extern void musb_stop(struct musb *musb);
|
||||
extern void musb_start(struct musb *musb);
|
||||
|
||||
extern void musb_write_fifo(struct musb_hw_ep *ep, u16 len, const u8 *src);
|
||||
extern void musb_read_fifo(struct musb_hw_ep *ep, u16 len, u8 *dst);
|
||||
|
||||
extern void musb_load_testpacket(struct musb *);
|
||||
|
||||
extern irqreturn_t musb_interrupt(struct musb *);
|
||||
|
||||
extern void musb_hnp_stop(struct musb *musb);
|
||||
|
||||
static inline void musb_platform_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
if (musb->ops->set_vbus)
|
||||
musb->ops->set_vbus(musb, is_on);
|
||||
}
|
||||
|
||||
static inline void musb_platform_enable(struct musb *musb)
|
||||
{
|
||||
if (musb->ops->enable)
|
||||
musb->ops->enable(musb);
|
||||
}
|
||||
|
||||
static inline void musb_platform_disable(struct musb *musb)
|
||||
{
|
||||
if (musb->ops->disable)
|
||||
musb->ops->disable(musb);
|
||||
}
|
||||
|
||||
static inline int musb_platform_set_mode(struct musb *musb, u8 mode)
|
||||
{
|
||||
if (!musb->ops->set_mode)
|
||||
return 0;
|
||||
|
||||
return musb->ops->set_mode(musb, mode);
|
||||
}
|
||||
|
||||
static inline void musb_platform_try_idle(struct musb *musb,
|
||||
unsigned long timeout)
|
||||
{
|
||||
if (musb->ops->try_idle)
|
||||
musb->ops->try_idle(musb, timeout);
|
||||
}
|
||||
|
||||
static inline int musb_platform_reset(struct musb *musb)
|
||||
{
|
||||
if (!musb->ops->reset)
|
||||
return -EINVAL;
|
||||
|
||||
return musb->ops->reset(musb);
|
||||
}
|
||||
|
||||
static inline int musb_platform_get_vbus_status(struct musb *musb)
|
||||
{
|
||||
if (!musb->ops->vbus_status)
|
||||
return 0;
|
||||
|
||||
return musb->ops->vbus_status(musb);
|
||||
}
|
||||
|
||||
static inline int musb_platform_init(struct musb *musb)
|
||||
{
|
||||
if (!musb->ops->init)
|
||||
return -EINVAL;
|
||||
|
||||
return musb->ops->init(musb);
|
||||
}
|
||||
|
||||
static inline int musb_platform_exit(struct musb *musb)
|
||||
{
|
||||
if (!musb->ops->exit)
|
||||
return -EINVAL;
|
||||
|
||||
return musb->ops->exit(musb);
|
||||
}
|
||||
|
||||
#endif /* __MUSB_CORE_H__ */
|
714
drivers/usb/musb/musb_cppi41.c
Normal file
714
drivers/usb/musb/musb_cppi41.c
Normal file
|
@ -0,0 +1,714 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
#define RNDIS_REG(x) (0x80 + ((x - 1) * 4))
|
||||
|
||||
#define EP_MODE_AUTOREG_NONE 0
|
||||
#define EP_MODE_AUTOREG_ALL_NEOP 1
|
||||
#define EP_MODE_AUTOREG_ALWAYS 3
|
||||
|
||||
#define EP_MODE_DMA_TRANSPARENT 0
|
||||
#define EP_MODE_DMA_RNDIS 1
|
||||
#define EP_MODE_DMA_GEN_RNDIS 3
|
||||
|
||||
#define USB_CTRL_TX_MODE 0x70
|
||||
#define USB_CTRL_RX_MODE 0x74
|
||||
#define USB_CTRL_AUTOREQ 0xd0
|
||||
#define USB_TDOWN 0xd8
|
||||
|
||||
struct cppi41_dma_channel {
|
||||
struct dma_channel channel;
|
||||
struct cppi41_dma_controller *controller;
|
||||
struct musb_hw_ep *hw_ep;
|
||||
struct dma_chan *dc;
|
||||
dma_cookie_t cookie;
|
||||
u8 port_num;
|
||||
u8 is_tx;
|
||||
u8 is_allocated;
|
||||
u8 usb_toggle;
|
||||
|
||||
dma_addr_t buf_addr;
|
||||
u32 total_len;
|
||||
u32 prog_len;
|
||||
u32 transferred;
|
||||
u32 packet_sz;
|
||||
struct list_head tx_check;
|
||||
int tx_zlp;
|
||||
};
|
||||
|
||||
#define MUSB_DMA_NUM_CHANNELS 15
|
||||
|
||||
struct cppi41_dma_controller {
|
||||
struct dma_controller controller;
|
||||
struct cppi41_dma_channel rx_channel[MUSB_DMA_NUM_CHANNELS];
|
||||
struct cppi41_dma_channel tx_channel[MUSB_DMA_NUM_CHANNELS];
|
||||
struct musb *musb;
|
||||
struct hrtimer early_tx;
|
||||
struct list_head early_tx_list;
|
||||
u32 rx_mode;
|
||||
u32 tx_mode;
|
||||
u32 auto_req;
|
||||
};
|
||||
|
||||
static void save_rx_toggle(struct cppi41_dma_channel *cppi41_channel)
|
||||
{
|
||||
u16 csr;
|
||||
u8 toggle;
|
||||
|
||||
if (cppi41_channel->is_tx)
|
||||
return;
|
||||
if (!is_host_active(cppi41_channel->controller->musb))
|
||||
return;
|
||||
|
||||
csr = musb_readw(cppi41_channel->hw_ep->regs, MUSB_RXCSR);
|
||||
toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0;
|
||||
|
||||
cppi41_channel->usb_toggle = toggle;
|
||||
}
|
||||
|
||||
static void update_rx_toggle(struct cppi41_dma_channel *cppi41_channel)
|
||||
{
|
||||
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
|
||||
struct musb *musb = hw_ep->musb;
|
||||
u16 csr;
|
||||
u8 toggle;
|
||||
|
||||
if (cppi41_channel->is_tx)
|
||||
return;
|
||||
if (!is_host_active(musb))
|
||||
return;
|
||||
|
||||
musb_ep_select(musb->mregs, hw_ep->epnum);
|
||||
csr = musb_readw(hw_ep->regs, MUSB_RXCSR);
|
||||
toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0;
|
||||
|
||||
/*
|
||||
* AM335x Advisory 1.0.13: Due to internal synchronisation error the
|
||||
* data toggle may reset from DATA1 to DATA0 during receiving data from
|
||||
* more than one endpoint.
|
||||
*/
|
||||
if (!toggle && toggle == cppi41_channel->usb_toggle) {
|
||||
csr |= MUSB_RXCSR_H_DATATOGGLE | MUSB_RXCSR_H_WR_DATATOGGLE;
|
||||
musb_writew(cppi41_channel->hw_ep->regs, MUSB_RXCSR, csr);
|
||||
dev_dbg(cppi41_channel->controller->musb->controller,
|
||||
"Restoring DATA1 toggle.\n");
|
||||
}
|
||||
|
||||
cppi41_channel->usb_toggle = toggle;
|
||||
}
|
||||
|
||||
static bool musb_is_tx_fifo_empty(struct musb_hw_ep *hw_ep)
|
||||
{
|
||||
u8 epnum = hw_ep->epnum;
|
||||
struct musb *musb = hw_ep->musb;
|
||||
void __iomem *epio = musb->endpoints[epnum].regs;
|
||||
u16 csr;
|
||||
|
||||
musb_ep_select(musb->mregs, hw_ep->epnum);
|
||||
csr = musb_readw(epio, MUSB_TXCSR);
|
||||
if (csr & MUSB_TXCSR_TXPKTRDY)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cppi41_dma_callback(void *private_data);
|
||||
|
||||
static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel)
|
||||
{
|
||||
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
|
||||
struct musb *musb = hw_ep->musb;
|
||||
void __iomem *epio = hw_ep->regs;
|
||||
u16 csr;
|
||||
|
||||
if (!cppi41_channel->prog_len ||
|
||||
(cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)) {
|
||||
|
||||
/* done, complete */
|
||||
cppi41_channel->channel.actual_len =
|
||||
cppi41_channel->transferred;
|
||||
cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE;
|
||||
cppi41_channel->channel.rx_packet_done = true;
|
||||
|
||||
/*
|
||||
* transmit ZLP using PIO mode for transfers which size is
|
||||
* multiple of EP packet size.
|
||||
*/
|
||||
if (cppi41_channel->tx_zlp && (cppi41_channel->transferred %
|
||||
cppi41_channel->packet_sz) == 0) {
|
||||
musb_ep_select(musb->mregs, hw_ep->epnum);
|
||||
csr = MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY;
|
||||
musb_writew(epio, MUSB_TXCSR, csr);
|
||||
}
|
||||
musb_dma_completion(musb, hw_ep->epnum, cppi41_channel->is_tx);
|
||||
} else {
|
||||
/* next iteration, reload */
|
||||
struct dma_chan *dc = cppi41_channel->dc;
|
||||
struct dma_async_tx_descriptor *dma_desc;
|
||||
enum dma_transfer_direction direction;
|
||||
u32 remain_bytes;
|
||||
|
||||
cppi41_channel->buf_addr += cppi41_channel->packet_sz;
|
||||
|
||||
remain_bytes = cppi41_channel->total_len;
|
||||
remain_bytes -= cppi41_channel->transferred;
|
||||
remain_bytes = min(remain_bytes, cppi41_channel->packet_sz);
|
||||
cppi41_channel->prog_len = remain_bytes;
|
||||
|
||||
direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV
|
||||
: DMA_DEV_TO_MEM;
|
||||
dma_desc = dmaengine_prep_slave_single(dc,
|
||||
cppi41_channel->buf_addr,
|
||||
remain_bytes,
|
||||
direction,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (WARN_ON(!dma_desc))
|
||||
return;
|
||||
|
||||
dma_desc->callback = cppi41_dma_callback;
|
||||
dma_desc->callback_param = &cppi41_channel->channel;
|
||||
cppi41_channel->cookie = dma_desc->tx_submit(dma_desc);
|
||||
dma_async_issue_pending(dc);
|
||||
|
||||
if (!cppi41_channel->is_tx) {
|
||||
musb_ep_select(musb->mregs, hw_ep->epnum);
|
||||
csr = musb_readw(epio, MUSB_RXCSR);
|
||||
csr |= MUSB_RXCSR_H_REQPKT;
|
||||
musb_writew(epio, MUSB_RXCSR, csr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer)
|
||||
{
|
||||
struct cppi41_dma_controller *controller;
|
||||
struct cppi41_dma_channel *cppi41_channel, *n;
|
||||
struct musb *musb;
|
||||
unsigned long flags;
|
||||
enum hrtimer_restart ret = HRTIMER_NORESTART;
|
||||
|
||||
controller = container_of(timer, struct cppi41_dma_controller,
|
||||
early_tx);
|
||||
musb = controller->musb;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
list_for_each_entry_safe(cppi41_channel, n, &controller->early_tx_list,
|
||||
tx_check) {
|
||||
bool empty;
|
||||
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
|
||||
|
||||
empty = musb_is_tx_fifo_empty(hw_ep);
|
||||
if (empty) {
|
||||
list_del_init(&cppi41_channel->tx_check);
|
||||
cppi41_trans_done(cppi41_channel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!list_empty(&controller->early_tx_list) &&
|
||||
!hrtimer_is_queued(&controller->early_tx)) {
|
||||
ret = HRTIMER_RESTART;
|
||||
hrtimer_forward_now(&controller->early_tx,
|
||||
ktime_set(0, 20 * NSEC_PER_USEC));
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cppi41_dma_callback(void *private_data)
|
||||
{
|
||||
struct dma_channel *channel = private_data;
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
|
||||
struct musb *musb = hw_ep->musb;
|
||||
unsigned long flags;
|
||||
struct dma_tx_state txstate;
|
||||
u32 transferred;
|
||||
bool empty;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
dmaengine_tx_status(cppi41_channel->dc, cppi41_channel->cookie,
|
||||
&txstate);
|
||||
transferred = cppi41_channel->prog_len - txstate.residue;
|
||||
cppi41_channel->transferred += transferred;
|
||||
|
||||
dev_dbg(musb->controller, "DMA transfer done on hw_ep=%d bytes=%d/%d\n",
|
||||
hw_ep->epnum, cppi41_channel->transferred,
|
||||
cppi41_channel->total_len);
|
||||
|
||||
update_rx_toggle(cppi41_channel);
|
||||
|
||||
if (cppi41_channel->transferred == cppi41_channel->total_len ||
|
||||
transferred < cppi41_channel->packet_sz)
|
||||
cppi41_channel->prog_len = 0;
|
||||
|
||||
empty = musb_is_tx_fifo_empty(hw_ep);
|
||||
if (empty) {
|
||||
cppi41_trans_done(cppi41_channel);
|
||||
} else {
|
||||
struct cppi41_dma_controller *controller;
|
||||
/*
|
||||
* On AM335x it has been observed that the TX interrupt fires
|
||||
* too early that means the TXFIFO is not yet empty but the DMA
|
||||
* engine says that it is done with the transfer. We don't
|
||||
* receive a FIFO empty interrupt so the only thing we can do is
|
||||
* to poll for the bit. On HS it usually takes 2us, on FS around
|
||||
* 110us - 150us depending on the transfer size.
|
||||
* We spin on HS (no longer than than 25us and setup a timer on
|
||||
* FS to check for the bit and complete the transfer.
|
||||
*/
|
||||
controller = cppi41_channel->controller;
|
||||
|
||||
if (musb->g.speed == USB_SPEED_HIGH) {
|
||||
unsigned wait = 25;
|
||||
|
||||
do {
|
||||
empty = musb_is_tx_fifo_empty(hw_ep);
|
||||
if (empty)
|
||||
break;
|
||||
wait--;
|
||||
if (!wait)
|
||||
break;
|
||||
udelay(1);
|
||||
} while (1);
|
||||
|
||||
empty = musb_is_tx_fifo_empty(hw_ep);
|
||||
if (empty) {
|
||||
cppi41_trans_done(cppi41_channel);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
list_add_tail(&cppi41_channel->tx_check,
|
||||
&controller->early_tx_list);
|
||||
if (!hrtimer_is_queued(&controller->early_tx)) {
|
||||
unsigned long usecs = cppi41_channel->total_len / 10;
|
||||
|
||||
hrtimer_start_range_ns(&controller->early_tx,
|
||||
ktime_set(0, usecs * NSEC_PER_USEC),
|
||||
20 * NSEC_PER_USEC,
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
}
|
||||
out:
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
}
|
||||
|
||||
static u32 update_ep_mode(unsigned ep, unsigned mode, u32 old)
|
||||
{
|
||||
unsigned shift;
|
||||
|
||||
shift = (ep - 1) * 2;
|
||||
old &= ~(3 << shift);
|
||||
old |= mode << shift;
|
||||
return old;
|
||||
}
|
||||
|
||||
static void cppi41_set_dma_mode(struct cppi41_dma_channel *cppi41_channel,
|
||||
unsigned mode)
|
||||
{
|
||||
struct cppi41_dma_controller *controller = cppi41_channel->controller;
|
||||
u32 port;
|
||||
u32 new_mode;
|
||||
u32 old_mode;
|
||||
|
||||
if (cppi41_channel->is_tx)
|
||||
old_mode = controller->tx_mode;
|
||||
else
|
||||
old_mode = controller->rx_mode;
|
||||
port = cppi41_channel->port_num;
|
||||
new_mode = update_ep_mode(port, mode, old_mode);
|
||||
|
||||
if (new_mode == old_mode)
|
||||
return;
|
||||
if (cppi41_channel->is_tx) {
|
||||
controller->tx_mode = new_mode;
|
||||
musb_writel(controller->musb->ctrl_base, USB_CTRL_TX_MODE,
|
||||
new_mode);
|
||||
} else {
|
||||
controller->rx_mode = new_mode;
|
||||
musb_writel(controller->musb->ctrl_base, USB_CTRL_RX_MODE,
|
||||
new_mode);
|
||||
}
|
||||
}
|
||||
|
||||
static void cppi41_set_autoreq_mode(struct cppi41_dma_channel *cppi41_channel,
|
||||
unsigned mode)
|
||||
{
|
||||
struct cppi41_dma_controller *controller = cppi41_channel->controller;
|
||||
u32 port;
|
||||
u32 new_mode;
|
||||
u32 old_mode;
|
||||
|
||||
old_mode = controller->auto_req;
|
||||
port = cppi41_channel->port_num;
|
||||
new_mode = update_ep_mode(port, mode, old_mode);
|
||||
|
||||
if (new_mode == old_mode)
|
||||
return;
|
||||
controller->auto_req = new_mode;
|
||||
musb_writel(controller->musb->ctrl_base, USB_CTRL_AUTOREQ, new_mode);
|
||||
}
|
||||
|
||||
static bool cppi41_configure_channel(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
struct dma_chan *dc = cppi41_channel->dc;
|
||||
struct dma_async_tx_descriptor *dma_desc;
|
||||
enum dma_transfer_direction direction;
|
||||
struct musb *musb = cppi41_channel->controller->musb;
|
||||
unsigned use_gen_rndis = 0;
|
||||
|
||||
dev_dbg(musb->controller,
|
||||
"configure ep%d/%x packet_sz=%d, mode=%d, dma_addr=0x%llx, len=%d is_tx=%d\n",
|
||||
cppi41_channel->port_num, RNDIS_REG(cppi41_channel->port_num),
|
||||
packet_sz, mode, (unsigned long long) dma_addr,
|
||||
len, cppi41_channel->is_tx);
|
||||
|
||||
cppi41_channel->buf_addr = dma_addr;
|
||||
cppi41_channel->total_len = len;
|
||||
cppi41_channel->transferred = 0;
|
||||
cppi41_channel->packet_sz = packet_sz;
|
||||
cppi41_channel->tx_zlp = (cppi41_channel->is_tx && mode) ? 1 : 0;
|
||||
|
||||
/*
|
||||
* Due to AM335x' Advisory 1.0.13 we are not allowed to transfer more
|
||||
* than max packet size at a time.
|
||||
*/
|
||||
if (cppi41_channel->is_tx)
|
||||
use_gen_rndis = 1;
|
||||
|
||||
if (use_gen_rndis) {
|
||||
/* RNDIS mode */
|
||||
if (len > packet_sz) {
|
||||
musb_writel(musb->ctrl_base,
|
||||
RNDIS_REG(cppi41_channel->port_num), len);
|
||||
/* gen rndis */
|
||||
cppi41_set_dma_mode(cppi41_channel,
|
||||
EP_MODE_DMA_GEN_RNDIS);
|
||||
|
||||
/* auto req */
|
||||
cppi41_set_autoreq_mode(cppi41_channel,
|
||||
EP_MODE_AUTOREG_ALL_NEOP);
|
||||
} else {
|
||||
musb_writel(musb->ctrl_base,
|
||||
RNDIS_REG(cppi41_channel->port_num), 0);
|
||||
cppi41_set_dma_mode(cppi41_channel,
|
||||
EP_MODE_DMA_TRANSPARENT);
|
||||
cppi41_set_autoreq_mode(cppi41_channel,
|
||||
EP_MODE_AUTOREG_NONE);
|
||||
}
|
||||
} else {
|
||||
/* fallback mode */
|
||||
cppi41_set_dma_mode(cppi41_channel, EP_MODE_DMA_TRANSPARENT);
|
||||
cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREG_NONE);
|
||||
len = min_t(u32, packet_sz, len);
|
||||
}
|
||||
cppi41_channel->prog_len = len;
|
||||
direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
||||
dma_desc = dmaengine_prep_slave_single(dc, dma_addr, len, direction,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!dma_desc)
|
||||
return false;
|
||||
|
||||
dma_desc->callback = cppi41_dma_callback;
|
||||
dma_desc->callback_param = channel;
|
||||
cppi41_channel->cookie = dma_desc->tx_submit(dma_desc);
|
||||
cppi41_channel->channel.rx_packet_done = false;
|
||||
|
||||
save_rx_toggle(cppi41_channel);
|
||||
dma_async_issue_pending(dc);
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct dma_channel *cppi41_dma_channel_allocate(struct dma_controller *c,
|
||||
struct musb_hw_ep *hw_ep, u8 is_tx)
|
||||
{
|
||||
struct cppi41_dma_controller *controller = container_of(c,
|
||||
struct cppi41_dma_controller, controller);
|
||||
struct cppi41_dma_channel *cppi41_channel = NULL;
|
||||
u8 ch_num = hw_ep->epnum - 1;
|
||||
|
||||
if (ch_num >= MUSB_DMA_NUM_CHANNELS)
|
||||
return NULL;
|
||||
|
||||
if (is_tx)
|
||||
cppi41_channel = &controller->tx_channel[ch_num];
|
||||
else
|
||||
cppi41_channel = &controller->rx_channel[ch_num];
|
||||
|
||||
if (!cppi41_channel->dc)
|
||||
return NULL;
|
||||
|
||||
if (cppi41_channel->is_allocated)
|
||||
return NULL;
|
||||
|
||||
cppi41_channel->hw_ep = hw_ep;
|
||||
cppi41_channel->is_allocated = 1;
|
||||
|
||||
return &cppi41_channel->channel;
|
||||
}
|
||||
|
||||
static void cppi41_dma_channel_release(struct dma_channel *channel)
|
||||
{
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
|
||||
if (cppi41_channel->is_allocated) {
|
||||
cppi41_channel->is_allocated = 0;
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
channel->actual_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int cppi41_dma_channel_program(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
int ret;
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
int hb_mult = 0;
|
||||
|
||||
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
|
||||
channel->status == MUSB_DMA_STATUS_BUSY);
|
||||
|
||||
if (is_host_active(cppi41_channel->controller->musb)) {
|
||||
if (cppi41_channel->is_tx)
|
||||
hb_mult = cppi41_channel->hw_ep->out_qh->hb_mult;
|
||||
else
|
||||
hb_mult = cppi41_channel->hw_ep->in_qh->hb_mult;
|
||||
}
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_BUSY;
|
||||
channel->actual_len = 0;
|
||||
|
||||
if (hb_mult)
|
||||
packet_sz = hb_mult * (packet_sz & 0x7FF);
|
||||
|
||||
ret = cppi41_configure_channel(channel, packet_sz, mode, dma_addr, len);
|
||||
if (!ret)
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cppi41_is_compatible(struct dma_channel *channel, u16 maxpacket,
|
||||
void *buf, u32 length)
|
||||
{
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
struct cppi41_dma_controller *controller = cppi41_channel->controller;
|
||||
struct musb *musb = controller->musb;
|
||||
|
||||
if (is_host_active(musb)) {
|
||||
WARN_ON(1);
|
||||
return 1;
|
||||
}
|
||||
if (cppi41_channel->hw_ep->ep_in.type != USB_ENDPOINT_XFER_BULK)
|
||||
return 0;
|
||||
if (cppi41_channel->is_tx)
|
||||
return 1;
|
||||
/* AM335x Advisory 1.0.13. No workaround for device RX mode */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cppi41_dma_channel_abort(struct dma_channel *channel)
|
||||
{
|
||||
struct cppi41_dma_channel *cppi41_channel = channel->private_data;
|
||||
struct cppi41_dma_controller *controller = cppi41_channel->controller;
|
||||
struct musb *musb = controller->musb;
|
||||
void __iomem *epio = cppi41_channel->hw_ep->regs;
|
||||
int tdbit;
|
||||
int ret;
|
||||
unsigned is_tx;
|
||||
u16 csr;
|
||||
|
||||
is_tx = cppi41_channel->is_tx;
|
||||
dev_dbg(musb->controller, "abort channel=%d, is_tx=%d\n",
|
||||
cppi41_channel->port_num, is_tx);
|
||||
|
||||
if (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)
|
||||
return 0;
|
||||
|
||||
list_del_init(&cppi41_channel->tx_check);
|
||||
if (is_tx) {
|
||||
csr = musb_readw(epio, MUSB_TXCSR);
|
||||
csr &= ~MUSB_TXCSR_DMAENAB;
|
||||
musb_writew(epio, MUSB_TXCSR, csr);
|
||||
} else {
|
||||
csr = musb_readw(epio, MUSB_RXCSR);
|
||||
csr &= ~(MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_DMAENAB);
|
||||
musb_writew(epio, MUSB_RXCSR, csr);
|
||||
|
||||
csr = musb_readw(epio, MUSB_RXCSR);
|
||||
if (csr & MUSB_RXCSR_RXPKTRDY) {
|
||||
csr |= MUSB_RXCSR_FLUSHFIFO;
|
||||
musb_writew(epio, MUSB_RXCSR, csr);
|
||||
musb_writew(epio, MUSB_RXCSR, csr);
|
||||
}
|
||||
}
|
||||
|
||||
tdbit = 1 << cppi41_channel->port_num;
|
||||
if (is_tx)
|
||||
tdbit <<= 16;
|
||||
|
||||
do {
|
||||
musb_writel(musb->ctrl_base, USB_TDOWN, tdbit);
|
||||
ret = dmaengine_terminate_all(cppi41_channel->dc);
|
||||
} while (ret == -EAGAIN);
|
||||
|
||||
musb_writel(musb->ctrl_base, USB_TDOWN, tdbit);
|
||||
|
||||
if (is_tx) {
|
||||
csr = musb_readw(epio, MUSB_TXCSR);
|
||||
if (csr & MUSB_TXCSR_TXPKTRDY) {
|
||||
csr |= MUSB_TXCSR_FLUSHFIFO;
|
||||
musb_writew(epio, MUSB_TXCSR, csr);
|
||||
}
|
||||
}
|
||||
|
||||
cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cppi41_release_all_dma_chans(struct cppi41_dma_controller *ctrl)
|
||||
{
|
||||
struct dma_chan *dc;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MUSB_DMA_NUM_CHANNELS; i++) {
|
||||
dc = ctrl->tx_channel[i].dc;
|
||||
if (dc)
|
||||
dma_release_channel(dc);
|
||||
dc = ctrl->rx_channel[i].dc;
|
||||
if (dc)
|
||||
dma_release_channel(dc);
|
||||
}
|
||||
}
|
||||
|
||||
static void cppi41_dma_controller_stop(struct cppi41_dma_controller *controller)
|
||||
{
|
||||
cppi41_release_all_dma_chans(controller);
|
||||
}
|
||||
|
||||
static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller)
|
||||
{
|
||||
struct musb *musb = controller->musb;
|
||||
struct device *dev = musb->controller;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct cppi41_dma_channel *cppi41_channel;
|
||||
int count;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
count = of_property_count_strings(np, "dma-names");
|
||||
if (count < 0)
|
||||
return count;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
struct dma_chan *dc;
|
||||
struct dma_channel *musb_dma;
|
||||
const char *str;
|
||||
unsigned is_tx;
|
||||
unsigned int port;
|
||||
|
||||
ret = of_property_read_string_index(np, "dma-names", i, &str);
|
||||
if (ret)
|
||||
goto err;
|
||||
if (!strncmp(str, "tx", 2))
|
||||
is_tx = 1;
|
||||
else if (!strncmp(str, "rx", 2))
|
||||
is_tx = 0;
|
||||
else {
|
||||
dev_err(dev, "Wrong dmatype %s\n", str);
|
||||
goto err;
|
||||
}
|
||||
ret = kstrtouint(str + 2, 0, &port);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = -EINVAL;
|
||||
if (port > MUSB_DMA_NUM_CHANNELS || !port)
|
||||
goto err;
|
||||
if (is_tx)
|
||||
cppi41_channel = &controller->tx_channel[port - 1];
|
||||
else
|
||||
cppi41_channel = &controller->rx_channel[port - 1];
|
||||
|
||||
cppi41_channel->controller = controller;
|
||||
cppi41_channel->port_num = port;
|
||||
cppi41_channel->is_tx = is_tx;
|
||||
INIT_LIST_HEAD(&cppi41_channel->tx_check);
|
||||
|
||||
musb_dma = &cppi41_channel->channel;
|
||||
musb_dma->private_data = cppi41_channel;
|
||||
musb_dma->status = MUSB_DMA_STATUS_FREE;
|
||||
musb_dma->max_len = SZ_4M;
|
||||
|
||||
dc = dma_request_slave_channel(dev, str);
|
||||
if (!dc) {
|
||||
dev_err(dev, "Failed to request %s.\n", str);
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err;
|
||||
}
|
||||
cppi41_channel->dc = dc;
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
cppi41_release_all_dma_chans(controller);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dma_controller_destroy(struct dma_controller *c)
|
||||
{
|
||||
struct cppi41_dma_controller *controller = container_of(c,
|
||||
struct cppi41_dma_controller, controller);
|
||||
|
||||
hrtimer_cancel(&controller->early_tx);
|
||||
cppi41_dma_controller_stop(controller);
|
||||
kfree(controller);
|
||||
}
|
||||
|
||||
struct dma_controller *dma_controller_create(struct musb *musb,
|
||||
void __iomem *base)
|
||||
{
|
||||
struct cppi41_dma_controller *controller;
|
||||
int ret = 0;
|
||||
|
||||
if (!musb->controller->of_node) {
|
||||
dev_err(musb->controller, "Need DT for the DMA engine.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
controller = kzalloc(sizeof(*controller), GFP_KERNEL);
|
||||
if (!controller)
|
||||
goto kzalloc_fail;
|
||||
|
||||
hrtimer_init(&controller->early_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
controller->early_tx.function = cppi41_recheck_tx_req;
|
||||
INIT_LIST_HEAD(&controller->early_tx_list);
|
||||
controller->musb = musb;
|
||||
|
||||
controller->controller.channel_alloc = cppi41_dma_channel_allocate;
|
||||
controller->controller.channel_release = cppi41_dma_channel_release;
|
||||
controller->controller.channel_program = cppi41_dma_channel_program;
|
||||
controller->controller.channel_abort = cppi41_dma_channel_abort;
|
||||
controller->controller.is_compatible = cppi41_is_compatible;
|
||||
|
||||
ret = cppi41_dma_controller_start(controller);
|
||||
if (ret)
|
||||
goto plat_get_fail;
|
||||
return &controller->controller;
|
||||
|
||||
plat_get_fail:
|
||||
kfree(controller);
|
||||
kzalloc_fail:
|
||||
if (ret == -EPROBE_DEFER)
|
||||
return ERR_PTR(ret);
|
||||
return NULL;
|
||||
}
|
58
drivers/usb/musb/musb_debug.h
Normal file
58
drivers/usb/musb/musb_debug.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* MUSB OTG driver debug defines
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_LINUX_DEBUG_H__
|
||||
#define __MUSB_LINUX_DEBUG_H__
|
||||
|
||||
#define yprintk(facility, format, args...) \
|
||||
do { printk(facility "%s %d: " format , \
|
||||
__func__, __LINE__ , ## args); } while (0)
|
||||
#define WARNING(fmt, args...) yprintk(KERN_WARNING, fmt, ## args)
|
||||
#define INFO(fmt, args...) yprintk(KERN_INFO, fmt, ## args)
|
||||
#define ERR(fmt, args...) yprintk(KERN_ERR, fmt, ## args)
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
int musb_init_debugfs(struct musb *musb);
|
||||
void musb_exit_debugfs(struct musb *musb);
|
||||
#else
|
||||
static inline int musb_init_debugfs(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void musb_exit_debugfs(struct musb *musb)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MUSB_LINUX_DEBUG_H__ */
|
276
drivers/usb/musb/musb_debugfs.c
Normal file
276
drivers/usb/musb/musb_debugfs.c
Normal file
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* MUSB OTG driver debugfs support
|
||||
*
|
||||
* Copyright 2010 Nokia Corporation
|
||||
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
#include "musb_debug.h"
|
||||
|
||||
struct musb_register_map {
|
||||
char *name;
|
||||
unsigned offset;
|
||||
unsigned size;
|
||||
};
|
||||
|
||||
static const struct musb_register_map musb_regmap[] = {
|
||||
{ "FAddr", 0x00, 8 },
|
||||
{ "Power", 0x01, 8 },
|
||||
{ "Frame", 0x0c, 16 },
|
||||
{ "Index", 0x0e, 8 },
|
||||
{ "Testmode", 0x0f, 8 },
|
||||
{ "TxMaxPp", 0x10, 16 },
|
||||
{ "TxCSRp", 0x12, 16 },
|
||||
{ "RxMaxPp", 0x14, 16 },
|
||||
{ "RxCSR", 0x16, 16 },
|
||||
{ "RxCount", 0x18, 16 },
|
||||
{ "ConfigData", 0x1f, 8 },
|
||||
{ "DevCtl", 0x60, 8 },
|
||||
{ "MISC", 0x61, 8 },
|
||||
{ "TxFIFOsz", 0x62, 8 },
|
||||
{ "RxFIFOsz", 0x63, 8 },
|
||||
{ "TxFIFOadd", 0x64, 16 },
|
||||
{ "RxFIFOadd", 0x66, 16 },
|
||||
{ "VControl", 0x68, 32 },
|
||||
{ "HWVers", 0x6C, 16 },
|
||||
{ "EPInfo", 0x78, 8 },
|
||||
{ "RAMInfo", 0x79, 8 },
|
||||
{ "LinkInfo", 0x7A, 8 },
|
||||
{ "VPLen", 0x7B, 8 },
|
||||
{ "HS_EOF1", 0x7C, 8 },
|
||||
{ "FS_EOF1", 0x7D, 8 },
|
||||
{ "LS_EOF1", 0x7E, 8 },
|
||||
{ "SOFT_RST", 0x7F, 8 },
|
||||
{ "DMA_CNTLch0", 0x204, 16 },
|
||||
{ "DMA_ADDRch0", 0x208, 32 },
|
||||
{ "DMA_COUNTch0", 0x20C, 32 },
|
||||
{ "DMA_CNTLch1", 0x214, 16 },
|
||||
{ "DMA_ADDRch1", 0x218, 32 },
|
||||
{ "DMA_COUNTch1", 0x21C, 32 },
|
||||
{ "DMA_CNTLch2", 0x224, 16 },
|
||||
{ "DMA_ADDRch2", 0x228, 32 },
|
||||
{ "DMA_COUNTch2", 0x22C, 32 },
|
||||
{ "DMA_CNTLch3", 0x234, 16 },
|
||||
{ "DMA_ADDRch3", 0x238, 32 },
|
||||
{ "DMA_COUNTch3", 0x23C, 32 },
|
||||
{ "DMA_CNTLch4", 0x244, 16 },
|
||||
{ "DMA_ADDRch4", 0x248, 32 },
|
||||
{ "DMA_COUNTch4", 0x24C, 32 },
|
||||
{ "DMA_CNTLch5", 0x254, 16 },
|
||||
{ "DMA_ADDRch5", 0x258, 32 },
|
||||
{ "DMA_COUNTch5", 0x25C, 32 },
|
||||
{ "DMA_CNTLch6", 0x264, 16 },
|
||||
{ "DMA_ADDRch6", 0x268, 32 },
|
||||
{ "DMA_COUNTch6", 0x26C, 32 },
|
||||
{ "DMA_CNTLch7", 0x274, 16 },
|
||||
{ "DMA_ADDRch7", 0x278, 32 },
|
||||
{ "DMA_COUNTch7", 0x27C, 32 },
|
||||
{ } /* Terminating Entry */
|
||||
};
|
||||
|
||||
static int musb_regdump_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct musb *musb = s->private;
|
||||
unsigned i;
|
||||
|
||||
seq_printf(s, "MUSB (M)HDRC Register Dump\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(musb_regmap); i++) {
|
||||
switch (musb_regmap[i].size) {
|
||||
case 8:
|
||||
seq_printf(s, "%-12s: %02x\n", musb_regmap[i].name,
|
||||
musb_readb(musb->mregs, musb_regmap[i].offset));
|
||||
break;
|
||||
case 16:
|
||||
seq_printf(s, "%-12s: %04x\n", musb_regmap[i].name,
|
||||
musb_readw(musb->mregs, musb_regmap[i].offset));
|
||||
break;
|
||||
case 32:
|
||||
seq_printf(s, "%-12s: %08x\n", musb_regmap[i].name,
|
||||
musb_readl(musb->mregs, musb_regmap[i].offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int musb_regdump_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, musb_regdump_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int musb_test_mode_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct musb *musb = s->private;
|
||||
unsigned test;
|
||||
|
||||
test = musb_readb(musb->mregs, MUSB_TESTMODE);
|
||||
|
||||
if (test & MUSB_TEST_FORCE_HOST)
|
||||
seq_printf(s, "force host\n");
|
||||
|
||||
if (test & MUSB_TEST_FIFO_ACCESS)
|
||||
seq_printf(s, "fifo access\n");
|
||||
|
||||
if (test & MUSB_TEST_FORCE_FS)
|
||||
seq_printf(s, "force full-speed\n");
|
||||
|
||||
if (test & MUSB_TEST_FORCE_HS)
|
||||
seq_printf(s, "force high-speed\n");
|
||||
|
||||
if (test & MUSB_TEST_PACKET)
|
||||
seq_printf(s, "test packet\n");
|
||||
|
||||
if (test & MUSB_TEST_K)
|
||||
seq_printf(s, "test K\n");
|
||||
|
||||
if (test & MUSB_TEST_J)
|
||||
seq_printf(s, "test J\n");
|
||||
|
||||
if (test & MUSB_TEST_SE0_NAK)
|
||||
seq_printf(s, "test SE0 NAK\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations musb_regdump_fops = {
|
||||
.open = musb_regdump_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int musb_test_mode_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, musb_test_mode_show, inode->i_private);
|
||||
}
|
||||
|
||||
static ssize_t musb_test_mode_write(struct file *file,
|
||||
const char __user *ubuf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct musb *musb = s->private;
|
||||
u8 test = 0;
|
||||
char buf[18];
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
|
||||
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!strncmp(buf, "force host", 9))
|
||||
test = MUSB_TEST_FORCE_HOST;
|
||||
|
||||
if (!strncmp(buf, "fifo access", 11))
|
||||
test = MUSB_TEST_FIFO_ACCESS;
|
||||
|
||||
if (!strncmp(buf, "force full-speed", 15))
|
||||
test = MUSB_TEST_FORCE_FS;
|
||||
|
||||
if (!strncmp(buf, "force high-speed", 15))
|
||||
test = MUSB_TEST_FORCE_HS;
|
||||
|
||||
if (!strncmp(buf, "test packet", 10)) {
|
||||
test = MUSB_TEST_PACKET;
|
||||
musb_load_testpacket(musb);
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "test K", 6))
|
||||
test = MUSB_TEST_K;
|
||||
|
||||
if (!strncmp(buf, "test J", 6))
|
||||
test = MUSB_TEST_J;
|
||||
|
||||
if (!strncmp(buf, "test SE0 NAK", 12))
|
||||
test = MUSB_TEST_SE0_NAK;
|
||||
|
||||
musb_writeb(musb->mregs, MUSB_TESTMODE, test);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations musb_test_mode_fops = {
|
||||
.open = musb_test_mode_open,
|
||||
.write = musb_test_mode_write,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
int musb_init_debugfs(struct musb *musb)
|
||||
{
|
||||
struct dentry *root;
|
||||
struct dentry *file;
|
||||
int ret;
|
||||
|
||||
root = debugfs_create_dir(dev_name(musb->controller), NULL);
|
||||
if (!root) {
|
||||
ret = -ENOMEM;
|
||||
goto err0;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("regdump", S_IRUGO, root, musb,
|
||||
&musb_regdump_fops);
|
||||
if (!file) {
|
||||
ret = -ENOMEM;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("testmode", S_IRUGO | S_IWUSR,
|
||||
root, musb, &musb_test_mode_fops);
|
||||
if (!file) {
|
||||
ret = -ENOMEM;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
musb->debugfs_root = root;
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
debugfs_remove_recursive(root);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void /* __init_or_exit */ musb_exit_debugfs(struct musb *musb)
|
||||
{
|
||||
debugfs_remove_recursive(musb->debugfs_root);
|
||||
}
|
195
drivers/usb/musb/musb_dma.h
Normal file
195
drivers/usb/musb/musb_dma.h
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* MUSB OTG driver DMA controller abstraction
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_DMA_H__
|
||||
#define __MUSB_DMA_H__
|
||||
|
||||
struct musb_hw_ep;
|
||||
|
||||
/*
|
||||
* DMA Controller Abstraction
|
||||
*
|
||||
* DMA Controllers are abstracted to allow use of a variety of different
|
||||
* implementations of DMA, as allowed by the Inventra USB cores. On the
|
||||
* host side, usbcore sets up the DMA mappings and flushes caches; on the
|
||||
* peripheral side, the gadget controller driver does. Responsibilities
|
||||
* of a DMA controller driver include:
|
||||
*
|
||||
* - Handling the details of moving multiple USB packets
|
||||
* in cooperation with the Inventra USB core, including especially
|
||||
* the correct RX side treatment of short packets and buffer-full
|
||||
* states (both of which terminate transfers).
|
||||
*
|
||||
* - Knowing the correlation between dma channels and the
|
||||
* Inventra core's local endpoint resources and data direction.
|
||||
*
|
||||
* - Maintaining a list of allocated/available channels.
|
||||
*
|
||||
* - Updating channel status on interrupts,
|
||||
* whether shared with the Inventra core or separate.
|
||||
*/
|
||||
|
||||
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
|
||||
|
||||
#ifdef CONFIG_MUSB_PIO_ONLY
|
||||
#define is_dma_capable() (0)
|
||||
#else
|
||||
#define is_dma_capable() (1)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_USB_TI_CPPI_DMA) || defined(CONFIG_USB_TI_CPPI41_DMA)
|
||||
#define is_cppi_enabled() 1
|
||||
#else
|
||||
#define is_cppi_enabled() 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_TUSB_OMAP_DMA
|
||||
#define tusb_dma_omap() 1
|
||||
#else
|
||||
#define tusb_dma_omap() 0
|
||||
#endif
|
||||
|
||||
/* Anomaly 05000456 - USB Receive Interrupt Is Not Generated in DMA Mode 1
|
||||
* Only allow DMA mode 1 to be used when the USB will actually generate the
|
||||
* interrupts we expect.
|
||||
*/
|
||||
#ifdef CONFIG_BLACKFIN
|
||||
# undef USE_MODE1
|
||||
# if !ANOMALY_05000456
|
||||
# define USE_MODE1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* DMA channel status ... updated by the dma controller driver whenever that
|
||||
* status changes, and protected by the overall controller spinlock.
|
||||
*/
|
||||
enum dma_channel_status {
|
||||
/* unallocated */
|
||||
MUSB_DMA_STATUS_UNKNOWN,
|
||||
/* allocated ... but not busy, no errors */
|
||||
MUSB_DMA_STATUS_FREE,
|
||||
/* busy ... transactions are active */
|
||||
MUSB_DMA_STATUS_BUSY,
|
||||
/* transaction(s) aborted due to ... dma or memory bus error */
|
||||
MUSB_DMA_STATUS_BUS_ABORT,
|
||||
/* transaction(s) aborted due to ... core error or USB fault */
|
||||
MUSB_DMA_STATUS_CORE_ABORT
|
||||
};
|
||||
|
||||
struct dma_controller;
|
||||
|
||||
/**
|
||||
* struct dma_channel - A DMA channel.
|
||||
* @private_data: channel-private data
|
||||
* @max_len: the maximum number of bytes the channel can move in one
|
||||
* transaction (typically representing many USB maximum-sized packets)
|
||||
* @actual_len: how many bytes have been transferred
|
||||
* @status: current channel status (updated e.g. on interrupt)
|
||||
* @desired_mode: true if mode 1 is desired; false if mode 0 is desired
|
||||
*
|
||||
* channels are associated with an endpoint for the duration of at least
|
||||
* one usb transfer.
|
||||
*/
|
||||
struct dma_channel {
|
||||
void *private_data;
|
||||
/* FIXME not void* private_data, but a dma_controller * */
|
||||
size_t max_len;
|
||||
size_t actual_len;
|
||||
enum dma_channel_status status;
|
||||
bool desired_mode;
|
||||
bool rx_packet_done;
|
||||
};
|
||||
|
||||
/*
|
||||
* dma_channel_status - return status of dma channel
|
||||
* @c: the channel
|
||||
*
|
||||
* Returns the software's view of the channel status. If that status is BUSY
|
||||
* then it's possible that the hardware has completed (or aborted) a transfer,
|
||||
* so the driver needs to update that status.
|
||||
*/
|
||||
static inline enum dma_channel_status
|
||||
dma_channel_status(struct dma_channel *c)
|
||||
{
|
||||
return (is_dma_capable() && c) ? c->status : MUSB_DMA_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct dma_controller - A DMA Controller.
|
||||
* @start: call this to start a DMA controller;
|
||||
* return 0 on success, else negative errno
|
||||
* @stop: call this to stop a DMA controller
|
||||
* return 0 on success, else negative errno
|
||||
* @channel_alloc: call this to allocate a DMA channel
|
||||
* @channel_release: call this to release a DMA channel
|
||||
* @channel_abort: call this to abort a pending DMA transaction,
|
||||
* returning it to FREE (but allocated) state
|
||||
*
|
||||
* Controllers manage dma channels.
|
||||
*/
|
||||
struct dma_controller {
|
||||
struct dma_channel *(*channel_alloc)(struct dma_controller *,
|
||||
struct musb_hw_ep *, u8 is_tx);
|
||||
void (*channel_release)(struct dma_channel *);
|
||||
int (*channel_program)(struct dma_channel *channel,
|
||||
u16 maxpacket, u8 mode,
|
||||
dma_addr_t dma_addr,
|
||||
u32 length);
|
||||
int (*channel_abort)(struct dma_channel *);
|
||||
int (*is_compatible)(struct dma_channel *channel,
|
||||
u16 maxpacket,
|
||||
void *buf, u32 length);
|
||||
};
|
||||
|
||||
/* called after channel_program(), may indicate a fault */
|
||||
extern void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit);
|
||||
|
||||
#ifdef CONFIG_MUSB_PIO_ONLY
|
||||
static inline struct dma_controller *dma_controller_create(struct musb *m,
|
||||
void __iomem *io)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void dma_controller_destroy(struct dma_controller *d) { }
|
||||
|
||||
#else
|
||||
|
||||
extern struct dma_controller *dma_controller_create(struct musb *, void __iomem *);
|
||||
|
||||
extern void dma_controller_destroy(struct dma_controller *);
|
||||
#endif
|
||||
|
||||
#endif /* __MUSB_DMA_H__ */
|
934
drivers/usb/musb/musb_dsps.c
Normal file
934
drivers/usb/musb/musb_dsps.c
Normal file
|
@ -0,0 +1,934 @@
|
|||
/*
|
||||
* Texas Instruments DSPS platforms "glue layer"
|
||||
*
|
||||
* Copyright (C) 2012, by Texas Instruments
|
||||
*
|
||||
* Based on the am35x "glue layer" code.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* musb_dsps.c will be a common file for all the TI DSPS platforms
|
||||
* such as dm64x, dm36x, dm35x, da8x, am35x and ti81x.
|
||||
* For now only ti81x is using this and in future davinci.c, am35x.c
|
||||
* da8xx.c would be merged to this file after testing.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
#include <linux/platform_data/usb-omap.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/usb/of.h>
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
static const struct of_device_id musb_dsps_of_match[];
|
||||
|
||||
/**
|
||||
* avoid using musb_readx()/musb_writex() as glue layer should not be
|
||||
* dependent on musb core layer symbols.
|
||||
*/
|
||||
static inline u8 dsps_readb(const void __iomem *addr, unsigned offset)
|
||||
{
|
||||
return __raw_readb(addr + offset);
|
||||
}
|
||||
|
||||
static inline u32 dsps_readl(const void __iomem *addr, unsigned offset)
|
||||
{
|
||||
return __raw_readl(addr + offset);
|
||||
}
|
||||
|
||||
static inline void dsps_writeb(void __iomem *addr, unsigned offset, u8 data)
|
||||
{
|
||||
__raw_writeb(data, addr + offset);
|
||||
}
|
||||
|
||||
static inline void dsps_writel(void __iomem *addr, unsigned offset, u32 data)
|
||||
{
|
||||
__raw_writel(data, addr + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* DSPS musb wrapper register offset.
|
||||
* FIXME: This should be expanded to have all the wrapper registers from TI DSPS
|
||||
* musb ips.
|
||||
*/
|
||||
struct dsps_musb_wrapper {
|
||||
u16 revision;
|
||||
u16 control;
|
||||
u16 status;
|
||||
u16 epintr_set;
|
||||
u16 epintr_clear;
|
||||
u16 epintr_status;
|
||||
u16 coreintr_set;
|
||||
u16 coreintr_clear;
|
||||
u16 coreintr_status;
|
||||
u16 phy_utmi;
|
||||
u16 mode;
|
||||
u16 tx_mode;
|
||||
u16 rx_mode;
|
||||
|
||||
/* bit positions for control */
|
||||
unsigned reset:5;
|
||||
|
||||
/* bit positions for interrupt */
|
||||
unsigned usb_shift:5;
|
||||
u32 usb_mask;
|
||||
u32 usb_bitmap;
|
||||
unsigned drvvbus:5;
|
||||
|
||||
unsigned txep_shift:5;
|
||||
u32 txep_mask;
|
||||
u32 txep_bitmap;
|
||||
|
||||
unsigned rxep_shift:5;
|
||||
u32 rxep_mask;
|
||||
u32 rxep_bitmap;
|
||||
|
||||
/* bit positions for phy_utmi */
|
||||
unsigned otg_disable:5;
|
||||
|
||||
/* bit positions for mode */
|
||||
unsigned iddig:5;
|
||||
unsigned iddig_mux:5;
|
||||
/* miscellaneous stuff */
|
||||
u8 poll_seconds;
|
||||
};
|
||||
|
||||
/*
|
||||
* register shadow for suspend
|
||||
*/
|
||||
struct dsps_context {
|
||||
u32 control;
|
||||
u32 epintr;
|
||||
u32 coreintr;
|
||||
u32 phy_utmi;
|
||||
u32 mode;
|
||||
u32 tx_mode;
|
||||
u32 rx_mode;
|
||||
};
|
||||
|
||||
/**
|
||||
* DSPS glue structure.
|
||||
*/
|
||||
struct dsps_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb; /* child musb pdev */
|
||||
const struct dsps_musb_wrapper *wrp; /* wrapper register offsets */
|
||||
struct timer_list timer; /* otg_workaround timer */
|
||||
unsigned long last_timer; /* last timer data for each instance */
|
||||
bool sw_babble_enabled;
|
||||
|
||||
struct dsps_context context;
|
||||
struct debugfs_regset32 regset;
|
||||
struct dentry *dbgfs_root;
|
||||
};
|
||||
|
||||
static const struct debugfs_reg32 dsps_musb_regs[] = {
|
||||
{ "revision", 0x00 },
|
||||
{ "control", 0x14 },
|
||||
{ "status", 0x18 },
|
||||
{ "eoi", 0x24 },
|
||||
{ "intr0_stat", 0x30 },
|
||||
{ "intr1_stat", 0x34 },
|
||||
{ "intr0_set", 0x38 },
|
||||
{ "intr1_set", 0x3c },
|
||||
{ "txmode", 0x70 },
|
||||
{ "rxmode", 0x74 },
|
||||
{ "autoreq", 0xd0 },
|
||||
{ "srpfixtime", 0xd4 },
|
||||
{ "tdown", 0xd8 },
|
||||
{ "phy_utmi", 0xe0 },
|
||||
{ "mode", 0xe8 },
|
||||
};
|
||||
|
||||
static void dsps_musb_try_idle(struct musb *musb, unsigned long timeout)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
|
||||
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)) {
|
||||
dev_dbg(musb->controller, "%s active, deleting timer\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
del_timer(&glue->timer);
|
||||
glue->last_timer = jiffies;
|
||||
return;
|
||||
}
|
||||
if (musb->port_mode != MUSB_PORT_MODE_DUAL_ROLE)
|
||||
return;
|
||||
|
||||
if (!musb->g.dev.driver)
|
||||
return;
|
||||
|
||||
if (time_after(glue->last_timer, timeout) &&
|
||||
timer_pending(&glue->timer)) {
|
||||
dev_dbg(musb->controller,
|
||||
"Longer idle timer already pending, ignoring...\n");
|
||||
return;
|
||||
}
|
||||
glue->last_timer = timeout;
|
||||
|
||||
dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
jiffies_to_msecs(timeout - jiffies));
|
||||
mod_timer(&glue->timer, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* dsps_musb_enable - enable interrupts
|
||||
*/
|
||||
static void dsps_musb_enable(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct platform_device *pdev = to_platform_device(dev->parent);
|
||||
struct dsps_glue *glue = platform_get_drvdata(pdev);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
u32 epmask, coremask;
|
||||
|
||||
/* Workaround: setup IRQs through both register sets. */
|
||||
epmask = ((musb->epmask & wrp->txep_mask) << wrp->txep_shift) |
|
||||
((musb->epmask & wrp->rxep_mask) << wrp->rxep_shift);
|
||||
coremask = (wrp->usb_bitmap & ~MUSB_INTR_SOF);
|
||||
|
||||
dsps_writel(reg_base, wrp->epintr_set, epmask);
|
||||
dsps_writel(reg_base, wrp->coreintr_set, coremask);
|
||||
/* Force the DRVVBUS IRQ so we can start polling for ID change. */
|
||||
dsps_writel(reg_base, wrp->coreintr_set,
|
||||
(1 << wrp->drvvbus) << wrp->usb_shift);
|
||||
dsps_musb_try_idle(musb, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* dsps_musb_disable - disable HDRC and flush interrupts
|
||||
*/
|
||||
static void dsps_musb_disable(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct platform_device *pdev = to_platform_device(dev->parent);
|
||||
struct dsps_glue *glue = platform_get_drvdata(pdev);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
|
||||
dsps_writel(reg_base, wrp->coreintr_clear, wrp->usb_bitmap);
|
||||
dsps_writel(reg_base, wrp->epintr_clear,
|
||||
wrp->txep_bitmap | wrp->rxep_bitmap);
|
||||
dsps_writeb(musb->mregs, MUSB_DEVCTL, 0);
|
||||
}
|
||||
|
||||
static void otg_timer(unsigned long _musb)
|
||||
{
|
||||
struct musb *musb = (void *)_musb;
|
||||
void __iomem *mregs = musb->mregs;
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
u8 devctl;
|
||||
unsigned long flags;
|
||||
int skip_session = 0;
|
||||
|
||||
/*
|
||||
* We poll because DSPS IP's won't expose several OTG-critical
|
||||
* status change events (from the transceiver) otherwise.
|
||||
*/
|
||||
devctl = dsps_readb(mregs, MUSB_DEVCTL);
|
||||
dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl,
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
dsps_writeb(musb->mregs, MUSB_DEVCTL, 0);
|
||||
skip_session = 1;
|
||||
/* fall */
|
||||
|
||||
case OTG_STATE_A_IDLE:
|
||||
case OTG_STATE_B_IDLE:
|
||||
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);
|
||||
}
|
||||
if (!(devctl & MUSB_DEVCTL_SESSION) && !skip_session)
|
||||
dsps_writeb(mregs, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
|
||||
mod_timer(&glue->timer, jiffies + wrp->poll_seconds * HZ);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
||||
dsps_writel(musb->ctrl_base, wrp->coreintr_set,
|
||||
MUSB_INTR_VBUSERROR << wrp->usb_shift);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
}
|
||||
|
||||
static irqreturn_t dsps_interrupt(int irq, void *hci)
|
||||
{
|
||||
struct musb *musb = hci;
|
||||
void __iomem *reg_base = musb->ctrl_base;
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
unsigned long flags;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u32 epintr, usbintr;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
/* Get endpoint interrupts */
|
||||
epintr = dsps_readl(reg_base, wrp->epintr_status);
|
||||
musb->int_rx = (epintr & wrp->rxep_bitmap) >> wrp->rxep_shift;
|
||||
musb->int_tx = (epintr & wrp->txep_bitmap) >> wrp->txep_shift;
|
||||
|
||||
if (epintr)
|
||||
dsps_writel(reg_base, wrp->epintr_status, epintr);
|
||||
|
||||
/* Get usb core interrupts */
|
||||
usbintr = dsps_readl(reg_base, wrp->coreintr_status);
|
||||
if (!usbintr && !epintr)
|
||||
goto out;
|
||||
|
||||
musb->int_usb = (usbintr & wrp->usb_bitmap) >> wrp->usb_shift;
|
||||
if (usbintr)
|
||||
dsps_writel(reg_base, wrp->coreintr_status, usbintr);
|
||||
|
||||
dev_dbg(musb->controller, "usbintr (%x) epintr(%x)\n",
|
||||
usbintr, epintr);
|
||||
/*
|
||||
* DRVVBUS IRQs are the only proxy we have (a very poor one!) for
|
||||
* DSPS IP'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 (is_host_active(musb) && usbintr & MUSB_INTR_BABBLE) {
|
||||
pr_info("CAUTION: musb: Babble Interrupt Occurred\n");
|
||||
|
||||
/*
|
||||
* When a babble condition occurs, the musb controller removes
|
||||
* the session and is no longer in host mode. Hence, all
|
||||
* devices connected to its root hub get disconnected.
|
||||
*
|
||||
* Hand this error down to the musb core isr, so it can
|
||||
* recover.
|
||||
*/
|
||||
musb->int_usb = MUSB_INTR_BABBLE | MUSB_INTR_DISCONNECT;
|
||||
musb->int_tx = musb->int_rx = 0;
|
||||
}
|
||||
|
||||
if (usbintr & ((1 << wrp->drvvbus) << wrp->usb_shift)) {
|
||||
int drvvbus = dsps_readl(reg_base, wrp->status);
|
||||
void __iomem *mregs = musb->mregs;
|
||||
u8 devctl = dsps_readb(mregs, MUSB_DEVCTL);
|
||||
int err;
|
||||
|
||||
err = 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(&glue->timer,
|
||||
jiffies + wrp->poll_seconds * HZ);
|
||||
WARNING("VBUS error workaround (delay coming)\n");
|
||||
} else if (drvvbus) {
|
||||
MUSB_HST_MODE(musb);
|
||||
musb->xceiv->otg->default_a = 1;
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
||||
del_timer(&glue->timer);
|
||||
} else {
|
||||
musb->is_active = 0;
|
||||
MUSB_DEV_MODE(musb);
|
||||
musb->xceiv->otg->default_a = 0;
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
}
|
||||
|
||||
/* NOTE: this must complete power-on within 100 ms. */
|
||||
dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n",
|
||||
drvvbus ? "on" : "off",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
err ? " ERROR" : "",
|
||||
devctl);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (musb->int_tx || musb->int_rx || musb->int_usb)
|
||||
ret |= musb_interrupt(musb);
|
||||
|
||||
/* Poll for ID change in OTG port mode */
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE &&
|
||||
musb->port_mode == MUSB_PORT_MODE_DUAL_ROLE)
|
||||
mod_timer(&glue->timer, jiffies + wrp->poll_seconds * HZ);
|
||||
out:
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsps_musb_dbg_init(struct musb *musb, struct dsps_glue *glue)
|
||||
{
|
||||
struct dentry *root;
|
||||
struct dentry *file;
|
||||
char buf[128];
|
||||
|
||||
sprintf(buf, "%s.dsps", dev_name(musb->controller));
|
||||
root = debugfs_create_dir(buf, NULL);
|
||||
if (!root)
|
||||
return -ENOMEM;
|
||||
glue->dbgfs_root = root;
|
||||
|
||||
glue->regset.regs = dsps_musb_regs;
|
||||
glue->regset.nregs = ARRAY_SIZE(dsps_musb_regs);
|
||||
glue->regset.base = musb->ctrl_base;
|
||||
|
||||
file = debugfs_create_regset32("regdump", S_IRUGO, root, &glue->regset);
|
||||
if (!file) {
|
||||
debugfs_remove_recursive(root);
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsps_musb_init(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
struct platform_device *parent = to_platform_device(dev->parent);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
void __iomem *reg_base;
|
||||
struct resource *r;
|
||||
u32 rev, val;
|
||||
int ret;
|
||||
|
||||
r = platform_get_resource_byname(parent, IORESOURCE_MEM, "control");
|
||||
if (!r)
|
||||
return -EINVAL;
|
||||
|
||||
reg_base = devm_ioremap_resource(dev, r);
|
||||
if (IS_ERR(reg_base))
|
||||
return PTR_ERR(reg_base);
|
||||
musb->ctrl_base = reg_base;
|
||||
|
||||
/* NOP driver needs change if supporting dual instance */
|
||||
musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0);
|
||||
if (IS_ERR(musb->xceiv))
|
||||
return PTR_ERR(musb->xceiv);
|
||||
|
||||
/* Returns zero if e.g. not clocked */
|
||||
rev = dsps_readl(reg_base, wrp->revision);
|
||||
if (!rev)
|
||||
return -ENODEV;
|
||||
|
||||
usb_phy_init(musb->xceiv);
|
||||
setup_timer(&glue->timer, otg_timer, (unsigned long) musb);
|
||||
|
||||
/* Reset the musb */
|
||||
dsps_writel(reg_base, wrp->control, (1 << wrp->reset));
|
||||
|
||||
musb->isr = dsps_interrupt;
|
||||
|
||||
/* reset the otgdisable bit, needed for host mode to work */
|
||||
val = dsps_readl(reg_base, wrp->phy_utmi);
|
||||
val &= ~(1 << wrp->otg_disable);
|
||||
dsps_writel(musb->ctrl_base, wrp->phy_utmi, val);
|
||||
|
||||
/*
|
||||
* Check whether the dsps version has babble control enabled.
|
||||
* In latest silicon revision the babble control logic is enabled.
|
||||
* If MUSB_BABBLE_CTL returns 0x4 then we have the babble control
|
||||
* logic enabled.
|
||||
*/
|
||||
val = dsps_readb(musb->mregs, MUSB_BABBLE_CTL);
|
||||
if (val == MUSB_BABBLE_RCV_DISABLE) {
|
||||
glue->sw_babble_enabled = true;
|
||||
val |= MUSB_BABBLE_SW_SESSION_CTRL;
|
||||
dsps_writeb(musb->mregs, MUSB_BABBLE_CTL, val);
|
||||
}
|
||||
|
||||
ret = dsps_musb_dbg_init(musb, glue);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsps_musb_exit(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
|
||||
del_timer_sync(&glue->timer);
|
||||
usb_phy_shutdown(musb->xceiv);
|
||||
debugfs_remove_recursive(glue->dbgfs_root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsps_musb_set_mode(struct musb *musb, u8 mode)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
void __iomem *ctrl_base = musb->ctrl_base;
|
||||
u32 reg;
|
||||
|
||||
reg = dsps_readl(ctrl_base, wrp->mode);
|
||||
|
||||
switch (mode) {
|
||||
case MUSB_HOST:
|
||||
reg &= ~(1 << wrp->iddig);
|
||||
|
||||
/*
|
||||
* if we're setting mode to host-only or device-only, we're
|
||||
* going to ignore whatever the PHY sends us and just force
|
||||
* ID pin status by SW
|
||||
*/
|
||||
reg |= (1 << wrp->iddig_mux);
|
||||
|
||||
dsps_writel(ctrl_base, wrp->mode, reg);
|
||||
dsps_writel(ctrl_base, wrp->phy_utmi, 0x02);
|
||||
break;
|
||||
case MUSB_PERIPHERAL:
|
||||
reg |= (1 << wrp->iddig);
|
||||
|
||||
/*
|
||||
* if we're setting mode to host-only or device-only, we're
|
||||
* going to ignore whatever the PHY sends us and just force
|
||||
* ID pin status by SW
|
||||
*/
|
||||
reg |= (1 << wrp->iddig_mux);
|
||||
|
||||
dsps_writel(ctrl_base, wrp->mode, reg);
|
||||
break;
|
||||
case MUSB_OTG:
|
||||
dsps_writel(ctrl_base, wrp->phy_utmi, 0x02);
|
||||
break;
|
||||
default:
|
||||
dev_err(glue->dev, "unsupported mode %d\n", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sw_babble_control(struct musb *musb)
|
||||
{
|
||||
u8 babble_ctl;
|
||||
bool session_restart = false;
|
||||
|
||||
babble_ctl = dsps_readb(musb->mregs, MUSB_BABBLE_CTL);
|
||||
dev_dbg(musb->controller, "babble: MUSB_BABBLE_CTL value %x\n",
|
||||
babble_ctl);
|
||||
/*
|
||||
* check line monitor flag to check whether babble is
|
||||
* due to noise
|
||||
*/
|
||||
dev_dbg(musb->controller, "STUCK_J is %s\n",
|
||||
babble_ctl & MUSB_BABBLE_STUCK_J ? "set" : "reset");
|
||||
|
||||
if (babble_ctl & MUSB_BABBLE_STUCK_J) {
|
||||
int timeout = 10;
|
||||
|
||||
/*
|
||||
* babble is due to noise, then set transmit idle (d7 bit)
|
||||
* to resume normal operation
|
||||
*/
|
||||
babble_ctl = dsps_readb(musb->mregs, MUSB_BABBLE_CTL);
|
||||
babble_ctl |= MUSB_BABBLE_FORCE_TXIDLE;
|
||||
dsps_writeb(musb->mregs, MUSB_BABBLE_CTL, babble_ctl);
|
||||
|
||||
/* wait till line monitor flag cleared */
|
||||
dev_dbg(musb->controller, "Set TXIDLE, wait J to clear\n");
|
||||
do {
|
||||
babble_ctl = dsps_readb(musb->mregs, MUSB_BABBLE_CTL);
|
||||
udelay(1);
|
||||
} while ((babble_ctl & MUSB_BABBLE_STUCK_J) && timeout--);
|
||||
|
||||
/* check whether stuck_at_j bit cleared */
|
||||
if (babble_ctl & MUSB_BABBLE_STUCK_J) {
|
||||
/*
|
||||
* real babble condition has occurred
|
||||
* restart the controller to start the
|
||||
* session again
|
||||
*/
|
||||
dev_dbg(musb->controller, "J not cleared, misc (%x)\n",
|
||||
babble_ctl);
|
||||
session_restart = true;
|
||||
}
|
||||
} else {
|
||||
session_restart = true;
|
||||
}
|
||||
|
||||
return session_restart;
|
||||
}
|
||||
|
||||
static int dsps_musb_reset(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev->parent);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
int session_restart = 0;
|
||||
|
||||
if (glue->sw_babble_enabled)
|
||||
session_restart = sw_babble_control(musb);
|
||||
/*
|
||||
* In case of new silicon version babble condition can be recovered
|
||||
* without resetting the MUSB. But for older silicon versions, MUSB
|
||||
* reset is needed
|
||||
*/
|
||||
if (session_restart || !glue->sw_babble_enabled) {
|
||||
dev_info(musb->controller, "Restarting MUSB to recover from Babble\n");
|
||||
dsps_writel(musb->ctrl_base, wrp->control, (1 << wrp->reset));
|
||||
usleep_range(100, 200);
|
||||
usb_phy_shutdown(musb->xceiv);
|
||||
usleep_range(100, 200);
|
||||
usb_phy_init(musb->xceiv);
|
||||
session_restart = 1;
|
||||
}
|
||||
|
||||
return !session_restart;
|
||||
}
|
||||
|
||||
static struct musb_platform_ops dsps_ops = {
|
||||
.init = dsps_musb_init,
|
||||
.exit = dsps_musb_exit,
|
||||
|
||||
.enable = dsps_musb_enable,
|
||||
.disable = dsps_musb_disable,
|
||||
|
||||
.try_idle = dsps_musb_try_idle,
|
||||
.set_mode = dsps_musb_set_mode,
|
||||
.reset = dsps_musb_reset,
|
||||
};
|
||||
|
||||
static u64 musb_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int get_int_prop(struct device_node *dn, const char *s)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = of_property_read_u32(dn, s, &val);
|
||||
if (ret)
|
||||
return 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
static int get_musb_port_mode(struct device *dev)
|
||||
{
|
||||
enum usb_dr_mode mode;
|
||||
|
||||
mode = of_usb_get_dr_mode(dev->of_node);
|
||||
switch (mode) {
|
||||
case USB_DR_MODE_HOST:
|
||||
return MUSB_PORT_MODE_HOST;
|
||||
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
return MUSB_PORT_MODE_GADGET;
|
||||
|
||||
case USB_DR_MODE_UNKNOWN:
|
||||
case USB_DR_MODE_OTG:
|
||||
default:
|
||||
return MUSB_PORT_MODE_DUAL_ROLE;
|
||||
}
|
||||
}
|
||||
|
||||
static int dsps_create_musb_pdev(struct dsps_glue *glue,
|
||||
struct platform_device *parent)
|
||||
{
|
||||
struct musb_hdrc_platform_data pdata;
|
||||
struct resource resources[2];
|
||||
struct resource *res;
|
||||
struct device *dev = &parent->dev;
|
||||
struct musb_hdrc_config *config;
|
||||
struct platform_device *musb;
|
||||
struct device_node *dn = parent->dev.of_node;
|
||||
int ret;
|
||||
|
||||
memset(resources, 0, sizeof(resources));
|
||||
res = platform_get_resource_byname(parent, IORESOURCE_MEM, "mc");
|
||||
if (!res) {
|
||||
dev_err(dev, "failed to get memory.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
resources[0] = *res;
|
||||
|
||||
res = platform_get_resource_byname(parent, IORESOURCE_IRQ, "mc");
|
||||
if (!res) {
|
||||
dev_err(dev, "failed to get irq.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
resources[1] = *res;
|
||||
|
||||
/* allocate the child platform device */
|
||||
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
|
||||
if (!musb) {
|
||||
dev_err(dev, "failed to allocate musb device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
musb->dev.parent = dev;
|
||||
musb->dev.dma_mask = &musb_dmamask;
|
||||
musb->dev.coherent_dma_mask = musb_dmamask;
|
||||
musb->dev.of_node = of_node_get(dn);
|
||||
|
||||
glue->musb = musb;
|
||||
|
||||
ret = platform_device_add_resources(musb, resources,
|
||||
ARRAY_SIZE(resources));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add resources\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
config = devm_kzalloc(&parent->dev, sizeof(*config), GFP_KERNEL);
|
||||
if (!config) {
|
||||
dev_err(dev, "failed to allocate musb hdrc config\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
pdata.config = config;
|
||||
pdata.platform_ops = &dsps_ops;
|
||||
|
||||
config->num_eps = get_int_prop(dn, "mentor,num-eps");
|
||||
config->ram_bits = get_int_prop(dn, "mentor,ram-bits");
|
||||
config->host_port_deassert_reset_at_resume = 1;
|
||||
pdata.mode = get_musb_port_mode(dev);
|
||||
/* DT keeps this entry in mA, musb expects it as per USB spec */
|
||||
pdata.power = get_int_prop(dn, "mentor,power") / 2;
|
||||
config->multipoint = of_property_read_bool(dn, "mentor,multipoint");
|
||||
|
||||
ret = platform_device_add_data(musb, &pdata, sizeof(pdata));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add platform_data\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = platform_device_add(musb);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register musb device\n");
|
||||
goto err;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
platform_device_put(musb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsps_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
const struct dsps_musb_wrapper *wrp;
|
||||
struct dsps_glue *glue;
|
||||
int ret;
|
||||
|
||||
if (!strcmp(pdev->name, "musb-hdrc"))
|
||||
return -ENODEV;
|
||||
|
||||
match = of_match_node(musb_dsps_of_match, pdev->dev.of_node);
|
||||
if (!match) {
|
||||
dev_err(&pdev->dev, "fail to get matching of_match struct\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
wrp = match->data;
|
||||
|
||||
/* allocate glue */
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "unable to allocate glue memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->wrp = wrp;
|
||||
|
||||
platform_set_drvdata(pdev, glue);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = pm_runtime_get_sync(&pdev->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "pm_runtime_get_sync FAILED");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = dsps_create_musb_pdev(glue, pdev);
|
||||
if (ret)
|
||||
goto err3;
|
||||
|
||||
return 0;
|
||||
|
||||
err3:
|
||||
pm_runtime_put(&pdev->dev);
|
||||
err2:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsps_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dsps_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
|
||||
/* disable usbss clocks */
|
||||
pm_runtime_put(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dsps_musb_wrapper am33xx_driver_data = {
|
||||
.revision = 0x00,
|
||||
.control = 0x14,
|
||||
.status = 0x18,
|
||||
.epintr_set = 0x38,
|
||||
.epintr_clear = 0x40,
|
||||
.epintr_status = 0x30,
|
||||
.coreintr_set = 0x3c,
|
||||
.coreintr_clear = 0x44,
|
||||
.coreintr_status = 0x34,
|
||||
.phy_utmi = 0xe0,
|
||||
.mode = 0xe8,
|
||||
.tx_mode = 0x70,
|
||||
.rx_mode = 0x74,
|
||||
.reset = 0,
|
||||
.otg_disable = 21,
|
||||
.iddig = 8,
|
||||
.iddig_mux = 7,
|
||||
.usb_shift = 0,
|
||||
.usb_mask = 0x1ff,
|
||||
.usb_bitmap = (0x1ff << 0),
|
||||
.drvvbus = 8,
|
||||
.txep_shift = 0,
|
||||
.txep_mask = 0xffff,
|
||||
.txep_bitmap = (0xffff << 0),
|
||||
.rxep_shift = 16,
|
||||
.rxep_mask = 0xfffe,
|
||||
.rxep_bitmap = (0xfffe << 16),
|
||||
.poll_seconds = 2,
|
||||
};
|
||||
|
||||
static const struct of_device_id musb_dsps_of_match[] = {
|
||||
{ .compatible = "ti,musb-am33xx",
|
||||
.data = (void *) &am33xx_driver_data, },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, musb_dsps_of_match);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dsps_suspend(struct device *dev)
|
||||
{
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
struct musb *musb = platform_get_drvdata(glue->musb);
|
||||
void __iomem *mbase;
|
||||
|
||||
del_timer_sync(&glue->timer);
|
||||
|
||||
if (!musb)
|
||||
/* This can happen if the musb device is in -EPROBE_DEFER */
|
||||
return 0;
|
||||
|
||||
mbase = musb->ctrl_base;
|
||||
glue->context.control = dsps_readl(mbase, wrp->control);
|
||||
glue->context.epintr = dsps_readl(mbase, wrp->epintr_set);
|
||||
glue->context.coreintr = dsps_readl(mbase, wrp->coreintr_set);
|
||||
glue->context.phy_utmi = dsps_readl(mbase, wrp->phy_utmi);
|
||||
glue->context.mode = dsps_readl(mbase, wrp->mode);
|
||||
glue->context.tx_mode = dsps_readl(mbase, wrp->tx_mode);
|
||||
glue->context.rx_mode = dsps_readl(mbase, wrp->rx_mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsps_resume(struct device *dev)
|
||||
{
|
||||
struct dsps_glue *glue = dev_get_drvdata(dev);
|
||||
const struct dsps_musb_wrapper *wrp = glue->wrp;
|
||||
struct musb *musb = platform_get_drvdata(glue->musb);
|
||||
void __iomem *mbase;
|
||||
|
||||
if (!musb)
|
||||
return 0;
|
||||
|
||||
mbase = musb->ctrl_base;
|
||||
dsps_writel(mbase, wrp->control, glue->context.control);
|
||||
dsps_writel(mbase, wrp->epintr_set, glue->context.epintr);
|
||||
dsps_writel(mbase, wrp->coreintr_set, glue->context.coreintr);
|
||||
dsps_writel(mbase, wrp->phy_utmi, glue->context.phy_utmi);
|
||||
dsps_writel(mbase, wrp->mode, glue->context.mode);
|
||||
dsps_writel(mbase, wrp->tx_mode, glue->context.tx_mode);
|
||||
dsps_writel(mbase, wrp->rx_mode, glue->context.rx_mode);
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE &&
|
||||
musb->port_mode == MUSB_PORT_MODE_DUAL_ROLE)
|
||||
mod_timer(&glue->timer, jiffies + wrp->poll_seconds * HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(dsps_pm_ops, dsps_suspend, dsps_resume);
|
||||
|
||||
static struct platform_driver dsps_usbss_driver = {
|
||||
.probe = dsps_probe,
|
||||
.remove = dsps_remove,
|
||||
.driver = {
|
||||
.name = "musb-dsps",
|
||||
.pm = &dsps_pm_ops,
|
||||
.of_match_table = musb_dsps_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("TI DSPS MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Ravi B <ravibabu@ti.com>");
|
||||
MODULE_AUTHOR("Ajay Kumar Gupta <ajay.gupta@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
module_platform_driver(dsps_usbss_driver);
|
2140
drivers/usb/musb/musb_gadget.c
Normal file
2140
drivers/usb/musb/musb_gadget.c
Normal file
File diff suppressed because it is too large
Load diff
147
drivers/usb/musb/musb_gadget.h
Normal file
147
drivers/usb/musb/musb_gadget.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* MUSB OTG driver peripheral defines
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_GADGET_H
|
||||
#define __MUSB_GADGET_H
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_MUSB_GADGET) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE)
|
||||
extern irqreturn_t musb_g_ep0_irq(struct musb *);
|
||||
extern void musb_g_tx(struct musb *, u8);
|
||||
extern void musb_g_rx(struct musb *, u8);
|
||||
extern void musb_g_reset(struct musb *);
|
||||
extern void musb_g_suspend(struct musb *);
|
||||
extern void musb_g_resume(struct musb *);
|
||||
extern void musb_g_wakeup(struct musb *);
|
||||
extern void musb_g_disconnect(struct musb *);
|
||||
extern void musb_gadget_cleanup(struct musb *);
|
||||
extern int musb_gadget_setup(struct musb *);
|
||||
|
||||
#else
|
||||
static inline irqreturn_t musb_g_ep0_irq(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void musb_g_tx(struct musb *musb, u8 epnum) {}
|
||||
static inline void musb_g_rx(struct musb *musb, u8 epnum) {}
|
||||
static inline void musb_g_reset(struct musb *musb) {}
|
||||
static inline void musb_g_suspend(struct musb *musb) {}
|
||||
static inline void musb_g_resume(struct musb *musb) {}
|
||||
static inline void musb_g_wakeup(struct musb *musb) {}
|
||||
static inline void musb_g_disconnect(struct musb *musb) {}
|
||||
static inline void musb_gadget_cleanup(struct musb *musb) {}
|
||||
static inline int musb_gadget_setup(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
enum buffer_map_state {
|
||||
UN_MAPPED = 0,
|
||||
PRE_MAPPED,
|
||||
MUSB_MAPPED
|
||||
};
|
||||
|
||||
struct musb_request {
|
||||
struct usb_request request;
|
||||
struct list_head list;
|
||||
struct musb_ep *ep;
|
||||
struct musb *musb;
|
||||
u8 tx; /* endpoint direction */
|
||||
u8 epnum;
|
||||
enum buffer_map_state map_state;
|
||||
};
|
||||
|
||||
static inline struct musb_request *to_musb_request(struct usb_request *req)
|
||||
{
|
||||
return req ? container_of(req, struct musb_request, request) : NULL;
|
||||
}
|
||||
|
||||
extern struct usb_request *
|
||||
musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags);
|
||||
extern void musb_free_request(struct usb_ep *ep, struct usb_request *req);
|
||||
|
||||
|
||||
/*
|
||||
* struct musb_ep - peripheral side view of endpoint rx or tx side
|
||||
*/
|
||||
struct musb_ep {
|
||||
/* stuff towards the head is basically write-once. */
|
||||
struct usb_ep end_point;
|
||||
char name[12];
|
||||
struct musb_hw_ep *hw_ep;
|
||||
struct musb *musb;
|
||||
u8 current_epnum;
|
||||
|
||||
/* ... when enabled/disabled ... */
|
||||
u8 type;
|
||||
u8 is_in;
|
||||
u16 packet_sz;
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
struct dma_channel *dma;
|
||||
|
||||
/* later things are modified based on usage */
|
||||
struct list_head req_list;
|
||||
|
||||
u8 wedged;
|
||||
|
||||
/* true if lock must be dropped but req_list may not be advanced */
|
||||
u8 busy;
|
||||
|
||||
u8 hb_mult;
|
||||
};
|
||||
|
||||
static inline struct musb_ep *to_musb_ep(struct usb_ep *ep)
|
||||
{
|
||||
return ep ? container_of(ep, struct musb_ep, end_point) : NULL;
|
||||
}
|
||||
|
||||
static inline struct musb_request *next_request(struct musb_ep *ep)
|
||||
{
|
||||
struct list_head *queue = &ep->req_list;
|
||||
|
||||
if (list_empty(queue))
|
||||
return NULL;
|
||||
return container_of(queue->next, struct musb_request, list);
|
||||
}
|
||||
|
||||
extern const struct usb_ep_ops musb_g_ep0_ops;
|
||||
|
||||
extern void musb_g_giveback(struct musb_ep *, struct usb_request *, int);
|
||||
|
||||
extern void musb_ep_restart(struct musb *, struct musb_request *);
|
||||
|
||||
#endif /* __MUSB_GADGET_H */
|
1083
drivers/usb/musb/musb_gadget_ep0.c
Normal file
1083
drivers/usb/musb/musb_gadget_ep0.c
Normal file
File diff suppressed because it is too large
Load diff
2707
drivers/usb/musb/musb_host.c
Normal file
2707
drivers/usb/musb/musb_host.c
Normal file
File diff suppressed because it is too large
Load diff
151
drivers/usb/musb/musb_host.h
Normal file
151
drivers/usb/musb/musb_host.h
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* MUSB OTG driver host defines
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MUSB_HOST_H
|
||||
#define _MUSB_HOST_H
|
||||
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
/* stored in "usb_host_endpoint.hcpriv" for scheduled endpoints */
|
||||
struct musb_qh {
|
||||
struct usb_host_endpoint *hep; /* usbcore info */
|
||||
struct usb_device *dev;
|
||||
struct musb_hw_ep *hw_ep; /* current binding */
|
||||
|
||||
struct list_head ring; /* of musb_qh */
|
||||
/* struct musb_qh *next; */ /* for periodic tree */
|
||||
u8 mux; /* qh multiplexed to hw_ep */
|
||||
|
||||
unsigned offset; /* in urb->transfer_buffer */
|
||||
unsigned segsize; /* current xfer fragment */
|
||||
|
||||
u8 type_reg; /* {rx,tx} type register */
|
||||
u8 intv_reg; /* {rx,tx} interval register */
|
||||
u8 addr_reg; /* device address register */
|
||||
u8 h_addr_reg; /* hub address register */
|
||||
u8 h_port_reg; /* hub port register */
|
||||
|
||||
u8 is_ready; /* safe to modify hw_ep */
|
||||
u8 type; /* XFERTYPE_* */
|
||||
u8 epnum;
|
||||
u8 hb_mult; /* high bandwidth pkts per uf */
|
||||
u16 maxpacket;
|
||||
u16 frame; /* for periodic schedule */
|
||||
unsigned iso_idx; /* in urb->iso_frame_desc[] */
|
||||
struct sg_mapping_iter sg_miter; /* for highmem in PIO mode */
|
||||
bool use_sg; /* to track urb using sglist */
|
||||
};
|
||||
|
||||
/* map from control or bulk queue head to the first qh on that ring */
|
||||
static inline struct musb_qh *first_qh(struct list_head *q)
|
||||
{
|
||||
if (list_empty(q))
|
||||
return NULL;
|
||||
return list_entry(q->next, struct musb_qh, ring);
|
||||
}
|
||||
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_MUSB_HOST) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE)
|
||||
extern struct musb *hcd_to_musb(struct usb_hcd *);
|
||||
extern irqreturn_t musb_h_ep0_irq(struct musb *);
|
||||
extern int musb_host_alloc(struct musb *);
|
||||
extern int musb_host_setup(struct musb *, int);
|
||||
extern void musb_host_cleanup(struct musb *);
|
||||
extern void musb_host_tx(struct musb *, u8);
|
||||
extern void musb_host_rx(struct musb *, u8);
|
||||
extern void musb_root_disconnect(struct musb *musb);
|
||||
extern void musb_host_free(struct musb *);
|
||||
extern void musb_host_cleanup(struct musb *);
|
||||
extern void musb_host_tx(struct musb *, u8);
|
||||
extern void musb_host_rx(struct musb *, u8);
|
||||
extern void musb_root_disconnect(struct musb *musb);
|
||||
extern void musb_host_resume_root_hub(struct musb *musb);
|
||||
extern void musb_host_poke_root_hub(struct musb *musb);
|
||||
extern void musb_port_suspend(struct musb *musb, bool do_suspend);
|
||||
extern void musb_port_reset(struct musb *musb, bool do_reset);
|
||||
extern void musb_host_finish_resume(struct work_struct *work);
|
||||
#else
|
||||
static inline struct musb *hcd_to_musb(struct usb_hcd *hcd)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline irqreturn_t musb_h_ep0_irq(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int musb_host_alloc(struct musb *musb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int musb_host_setup(struct musb *musb, int power_budget)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void musb_host_cleanup(struct musb *musb) {}
|
||||
static inline void musb_host_free(struct musb *musb) {}
|
||||
static inline void musb_host_tx(struct musb *musb, u8 epnum) {}
|
||||
static inline void musb_host_rx(struct musb *musb, u8 epnum) {}
|
||||
static inline void musb_root_disconnect(struct musb *musb) {}
|
||||
static inline void musb_host_resume_root_hub(struct musb *musb) {}
|
||||
static inline void musb_host_poll_rh_status(struct musb *musb) {}
|
||||
static inline void musb_host_poke_root_hub(struct musb *musb) {}
|
||||
static inline void musb_port_suspend(struct musb *musb, bool do_suspend) {}
|
||||
static inline void musb_port_reset(struct musb *musb, bool do_reset) {}
|
||||
static inline void musb_host_finish_resume(struct work_struct *work) {}
|
||||
#endif
|
||||
|
||||
struct usb_hcd;
|
||||
|
||||
extern int musb_hub_status_data(struct usb_hcd *hcd, char *buf);
|
||||
extern int musb_hub_control(struct usb_hcd *hcd,
|
||||
u16 typeReq, u16 wValue, u16 wIndex,
|
||||
char *buf, u16 wLength);
|
||||
|
||||
static inline struct urb *next_urb(struct musb_qh *qh)
|
||||
{
|
||||
struct list_head *queue;
|
||||
|
||||
if (!qh)
|
||||
return NULL;
|
||||
queue = &qh->hep->urb_list;
|
||||
if (list_empty(queue))
|
||||
return NULL;
|
||||
return list_entry(queue->next, struct urb, urb_list);
|
||||
}
|
||||
|
||||
#endif /* _MUSB_HOST_H */
|
122
drivers/usb/musb/musb_io.h
Normal file
122
drivers/usb/musb/musb_io.h
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* MUSB OTG driver register I/O
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_LINUX_PLATFORM_ARCH_H__
|
||||
#define __MUSB_LINUX_PLATFORM_ARCH_H__
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
#ifndef CONFIG_BLACKFIN
|
||||
|
||||
/* NOTE: these offsets are all in bytes */
|
||||
|
||||
static inline u16 musb_readw(const void __iomem *addr, unsigned offset)
|
||||
{ return __raw_readw(addr + offset); }
|
||||
|
||||
static inline u32 musb_readl(const void __iomem *addr, unsigned offset)
|
||||
{ return __raw_readl(addr + offset); }
|
||||
|
||||
|
||||
static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data)
|
||||
{ __raw_writew(data, addr + offset); }
|
||||
|
||||
static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data)
|
||||
{ __raw_writel(data, addr + offset); }
|
||||
|
||||
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
|
||||
/*
|
||||
* TUSB6010 doesn't allow 8-bit access; 16-bit access is the minimum.
|
||||
*/
|
||||
static inline u8 musb_readb(const void __iomem *addr, unsigned offset)
|
||||
{
|
||||
u16 tmp;
|
||||
u8 val;
|
||||
|
||||
tmp = __raw_readw(addr + (offset & ~1));
|
||||
if (offset & 1)
|
||||
val = (tmp >> 8);
|
||||
else
|
||||
val = tmp & 0xff;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data)
|
||||
{
|
||||
u16 tmp;
|
||||
|
||||
tmp = __raw_readw(addr + (offset & ~1));
|
||||
if (offset & 1)
|
||||
tmp = (data << 8) | (tmp & 0xff);
|
||||
else
|
||||
tmp = (tmp & 0xff00) | data;
|
||||
|
||||
__raw_writew(tmp, addr + (offset & ~1));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline u8 musb_readb(const void __iomem *addr, unsigned offset)
|
||||
{ return __raw_readb(addr + offset); }
|
||||
|
||||
static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data)
|
||||
{ __raw_writeb(data, addr + offset); }
|
||||
|
||||
#endif /* CONFIG_USB_MUSB_TUSB6010 */
|
||||
|
||||
#else
|
||||
|
||||
static inline u8 musb_readb(const void __iomem *addr, unsigned offset)
|
||||
{ return (u8) (bfin_read16(addr + offset)); }
|
||||
|
||||
static inline u16 musb_readw(const void __iomem *addr, unsigned offset)
|
||||
{ return bfin_read16(addr + offset); }
|
||||
|
||||
static inline u32 musb_readl(const void __iomem *addr, unsigned offset)
|
||||
{ return (u32) (bfin_read16(addr + offset)); }
|
||||
|
||||
static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data)
|
||||
{ bfin_write16(addr + offset, (u16) data); }
|
||||
|
||||
static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data)
|
||||
{ bfin_write16(addr + offset, data); }
|
||||
|
||||
static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data)
|
||||
{ bfin_write16(addr + offset, (u16) data); }
|
||||
|
||||
#endif /* CONFIG_BLACKFIN */
|
||||
|
||||
#endif
|
652
drivers/usb/musb/musb_regs.h
Normal file
652
drivers/usb/musb/musb_regs.h
Normal file
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
* MUSB OTG driver register defines
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_REGS_H__
|
||||
#define __MUSB_REGS_H__
|
||||
|
||||
#define MUSB_EP0_FIFOSIZE 64 /* This is non-configurable */
|
||||
|
||||
/*
|
||||
* MUSB Register bits
|
||||
*/
|
||||
|
||||
/* POWER */
|
||||
#define MUSB_POWER_ISOUPDATE 0x80
|
||||
#define MUSB_POWER_SOFTCONN 0x40
|
||||
#define MUSB_POWER_HSENAB 0x20
|
||||
#define MUSB_POWER_HSMODE 0x10
|
||||
#define MUSB_POWER_RESET 0x08
|
||||
#define MUSB_POWER_RESUME 0x04
|
||||
#define MUSB_POWER_SUSPENDM 0x02
|
||||
#define MUSB_POWER_ENSUSPEND 0x01
|
||||
|
||||
/* INTRUSB */
|
||||
#define MUSB_INTR_SUSPEND 0x01
|
||||
#define MUSB_INTR_RESUME 0x02
|
||||
#define MUSB_INTR_RESET 0x04
|
||||
#define MUSB_INTR_BABBLE 0x04
|
||||
#define MUSB_INTR_SOF 0x08
|
||||
#define MUSB_INTR_CONNECT 0x10
|
||||
#define MUSB_INTR_DISCONNECT 0x20
|
||||
#define MUSB_INTR_SESSREQ 0x40
|
||||
#define MUSB_INTR_VBUSERROR 0x80 /* For SESSION end */
|
||||
|
||||
/* DEVCTL */
|
||||
#define MUSB_DEVCTL_BDEVICE 0x80
|
||||
#define MUSB_DEVCTL_FSDEV 0x40
|
||||
#define MUSB_DEVCTL_LSDEV 0x20
|
||||
#define MUSB_DEVCTL_VBUS 0x18
|
||||
#define MUSB_DEVCTL_VBUS_SHIFT 3
|
||||
#define MUSB_DEVCTL_HM 0x04
|
||||
#define MUSB_DEVCTL_HR 0x02
|
||||
#define MUSB_DEVCTL_SESSION 0x01
|
||||
|
||||
/* BABBLE_CTL */
|
||||
#define MUSB_BABBLE_FORCE_TXIDLE 0x80
|
||||
#define MUSB_BABBLE_SW_SESSION_CTRL 0x40
|
||||
#define MUSB_BABBLE_STUCK_J 0x20
|
||||
#define MUSB_BABBLE_RCV_DISABLE 0x04
|
||||
|
||||
/* MUSB ULPI VBUSCONTROL */
|
||||
#define MUSB_ULPI_USE_EXTVBUS 0x01
|
||||
#define MUSB_ULPI_USE_EXTVBUSIND 0x02
|
||||
/* ULPI_REG_CONTROL */
|
||||
#define MUSB_ULPI_REG_REQ (1 << 0)
|
||||
#define MUSB_ULPI_REG_CMPLT (1 << 1)
|
||||
#define MUSB_ULPI_RDN_WR (1 << 2)
|
||||
|
||||
/* TESTMODE */
|
||||
#define MUSB_TEST_FORCE_HOST 0x80
|
||||
#define MUSB_TEST_FIFO_ACCESS 0x40
|
||||
#define MUSB_TEST_FORCE_FS 0x20
|
||||
#define MUSB_TEST_FORCE_HS 0x10
|
||||
#define MUSB_TEST_PACKET 0x08
|
||||
#define MUSB_TEST_K 0x04
|
||||
#define MUSB_TEST_J 0x02
|
||||
#define MUSB_TEST_SE0_NAK 0x01
|
||||
|
||||
/* Allocate for double-packet buffering (effectively doubles assigned _SIZE) */
|
||||
#define MUSB_FIFOSZ_DPB 0x10
|
||||
/* Allocation size (8, 16, 32, ... 4096) */
|
||||
#define MUSB_FIFOSZ_SIZE 0x0f
|
||||
|
||||
/* CSR0 */
|
||||
#define MUSB_CSR0_FLUSHFIFO 0x0100
|
||||
#define MUSB_CSR0_TXPKTRDY 0x0002
|
||||
#define MUSB_CSR0_RXPKTRDY 0x0001
|
||||
|
||||
/* CSR0 in Peripheral mode */
|
||||
#define MUSB_CSR0_P_SVDSETUPEND 0x0080
|
||||
#define MUSB_CSR0_P_SVDRXPKTRDY 0x0040
|
||||
#define MUSB_CSR0_P_SENDSTALL 0x0020
|
||||
#define MUSB_CSR0_P_SETUPEND 0x0010
|
||||
#define MUSB_CSR0_P_DATAEND 0x0008
|
||||
#define MUSB_CSR0_P_SENTSTALL 0x0004
|
||||
|
||||
/* CSR0 in Host mode */
|
||||
#define MUSB_CSR0_H_DIS_PING 0x0800
|
||||
#define MUSB_CSR0_H_WR_DATATOGGLE 0x0400 /* Set to allow setting: */
|
||||
#define MUSB_CSR0_H_DATATOGGLE 0x0200 /* Data toggle control */
|
||||
#define MUSB_CSR0_H_NAKTIMEOUT 0x0080
|
||||
#define MUSB_CSR0_H_STATUSPKT 0x0040
|
||||
#define MUSB_CSR0_H_REQPKT 0x0020
|
||||
#define MUSB_CSR0_H_ERROR 0x0010
|
||||
#define MUSB_CSR0_H_SETUPPKT 0x0008
|
||||
#define MUSB_CSR0_H_RXSTALL 0x0004
|
||||
|
||||
/* CSR0 bits to avoid zeroing (write zero clears, write 1 ignored) */
|
||||
#define MUSB_CSR0_P_WZC_BITS \
|
||||
(MUSB_CSR0_P_SENTSTALL)
|
||||
#define MUSB_CSR0_H_WZC_BITS \
|
||||
(MUSB_CSR0_H_NAKTIMEOUT | MUSB_CSR0_H_RXSTALL \
|
||||
| MUSB_CSR0_RXPKTRDY)
|
||||
|
||||
/* TxType/RxType */
|
||||
#define MUSB_TYPE_SPEED 0xc0
|
||||
#define MUSB_TYPE_SPEED_SHIFT 6
|
||||
#define MUSB_TYPE_PROTO 0x30 /* Implicitly zero for ep0 */
|
||||
#define MUSB_TYPE_PROTO_SHIFT 4
|
||||
#define MUSB_TYPE_REMOTE_END 0xf /* Implicitly zero for ep0 */
|
||||
|
||||
/* CONFIGDATA */
|
||||
#define MUSB_CONFIGDATA_MPRXE 0x80 /* Auto bulk pkt combining */
|
||||
#define MUSB_CONFIGDATA_MPTXE 0x40 /* Auto bulk pkt splitting */
|
||||
#define MUSB_CONFIGDATA_BIGENDIAN 0x20
|
||||
#define MUSB_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */
|
||||
#define MUSB_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */
|
||||
#define MUSB_CONFIGDATA_DYNFIFO 0x04 /* Dynamic FIFO sizing */
|
||||
#define MUSB_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */
|
||||
#define MUSB_CONFIGDATA_UTMIDW 0x01 /* Data width 0/1 => 8/16bits */
|
||||
|
||||
/* TXCSR in Peripheral and Host mode */
|
||||
#define MUSB_TXCSR_AUTOSET 0x8000
|
||||
#define MUSB_TXCSR_DMAENAB 0x1000
|
||||
#define MUSB_TXCSR_FRCDATATOG 0x0800
|
||||
#define MUSB_TXCSR_DMAMODE 0x0400
|
||||
#define MUSB_TXCSR_CLRDATATOG 0x0040
|
||||
#define MUSB_TXCSR_FLUSHFIFO 0x0008
|
||||
#define MUSB_TXCSR_FIFONOTEMPTY 0x0002
|
||||
#define MUSB_TXCSR_TXPKTRDY 0x0001
|
||||
|
||||
/* TXCSR in Peripheral mode */
|
||||
#define MUSB_TXCSR_P_ISO 0x4000
|
||||
#define MUSB_TXCSR_P_INCOMPTX 0x0080
|
||||
#define MUSB_TXCSR_P_SENTSTALL 0x0020
|
||||
#define MUSB_TXCSR_P_SENDSTALL 0x0010
|
||||
#define MUSB_TXCSR_P_UNDERRUN 0x0004
|
||||
|
||||
/* TXCSR in Host mode */
|
||||
#define MUSB_TXCSR_H_WR_DATATOGGLE 0x0200
|
||||
#define MUSB_TXCSR_H_DATATOGGLE 0x0100
|
||||
#define MUSB_TXCSR_H_NAKTIMEOUT 0x0080
|
||||
#define MUSB_TXCSR_H_RXSTALL 0x0020
|
||||
#define MUSB_TXCSR_H_ERROR 0x0004
|
||||
|
||||
/* TXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */
|
||||
#define MUSB_TXCSR_P_WZC_BITS \
|
||||
(MUSB_TXCSR_P_INCOMPTX | MUSB_TXCSR_P_SENTSTALL \
|
||||
| MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_FIFONOTEMPTY)
|
||||
#define MUSB_TXCSR_H_WZC_BITS \
|
||||
(MUSB_TXCSR_H_NAKTIMEOUT | MUSB_TXCSR_H_RXSTALL \
|
||||
| MUSB_TXCSR_H_ERROR | MUSB_TXCSR_FIFONOTEMPTY)
|
||||
|
||||
/* RXCSR in Peripheral and Host mode */
|
||||
#define MUSB_RXCSR_AUTOCLEAR 0x8000
|
||||
#define MUSB_RXCSR_DMAENAB 0x2000
|
||||
#define MUSB_RXCSR_DISNYET 0x1000
|
||||
#define MUSB_RXCSR_PID_ERR 0x1000
|
||||
#define MUSB_RXCSR_DMAMODE 0x0800
|
||||
#define MUSB_RXCSR_INCOMPRX 0x0100
|
||||
#define MUSB_RXCSR_CLRDATATOG 0x0080
|
||||
#define MUSB_RXCSR_FLUSHFIFO 0x0010
|
||||
#define MUSB_RXCSR_DATAERROR 0x0008
|
||||
#define MUSB_RXCSR_FIFOFULL 0x0002
|
||||
#define MUSB_RXCSR_RXPKTRDY 0x0001
|
||||
|
||||
/* RXCSR in Peripheral mode */
|
||||
#define MUSB_RXCSR_P_ISO 0x4000
|
||||
#define MUSB_RXCSR_P_SENTSTALL 0x0040
|
||||
#define MUSB_RXCSR_P_SENDSTALL 0x0020
|
||||
#define MUSB_RXCSR_P_OVERRUN 0x0004
|
||||
|
||||
/* RXCSR in Host mode */
|
||||
#define MUSB_RXCSR_H_AUTOREQ 0x4000
|
||||
#define MUSB_RXCSR_H_WR_DATATOGGLE 0x0400
|
||||
#define MUSB_RXCSR_H_DATATOGGLE 0x0200
|
||||
#define MUSB_RXCSR_H_RXSTALL 0x0040
|
||||
#define MUSB_RXCSR_H_REQPKT 0x0020
|
||||
#define MUSB_RXCSR_H_ERROR 0x0004
|
||||
|
||||
/* RXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */
|
||||
#define MUSB_RXCSR_P_WZC_BITS \
|
||||
(MUSB_RXCSR_P_SENTSTALL | MUSB_RXCSR_P_OVERRUN \
|
||||
| MUSB_RXCSR_RXPKTRDY)
|
||||
#define MUSB_RXCSR_H_WZC_BITS \
|
||||
(MUSB_RXCSR_H_RXSTALL | MUSB_RXCSR_H_ERROR \
|
||||
| MUSB_RXCSR_DATAERROR | MUSB_RXCSR_RXPKTRDY)
|
||||
|
||||
/* HUBADDR */
|
||||
#define MUSB_HUBADDR_MULTI_TT 0x80
|
||||
|
||||
|
||||
#ifndef CONFIG_BLACKFIN
|
||||
|
||||
/*
|
||||
* Common USB registers
|
||||
*/
|
||||
|
||||
#define MUSB_FADDR 0x00 /* 8-bit */
|
||||
#define MUSB_POWER 0x01 /* 8-bit */
|
||||
|
||||
#define MUSB_INTRTX 0x02 /* 16-bit */
|
||||
#define MUSB_INTRRX 0x04
|
||||
#define MUSB_INTRTXE 0x06
|
||||
#define MUSB_INTRRXE 0x08
|
||||
#define MUSB_INTRUSB 0x0A /* 8 bit */
|
||||
#define MUSB_INTRUSBE 0x0B /* 8 bit */
|
||||
#define MUSB_FRAME 0x0C
|
||||
#define MUSB_INDEX 0x0E /* 8 bit */
|
||||
#define MUSB_TESTMODE 0x0F /* 8 bit */
|
||||
|
||||
/* Get offset for a given FIFO from musb->mregs */
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
#define MUSB_FIFO_OFFSET(epnum) (0x200 + ((epnum) * 0x20))
|
||||
#else
|
||||
#define MUSB_FIFO_OFFSET(epnum) (0x20 + ((epnum) * 4))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Additional Control Registers
|
||||
*/
|
||||
|
||||
#define MUSB_DEVCTL 0x60 /* 8 bit */
|
||||
#define MUSB_BABBLE_CTL 0x61 /* 8 bit */
|
||||
|
||||
/* These are always controlled through the INDEX register */
|
||||
#define MUSB_TXFIFOSZ 0x62 /* 8-bit (see masks) */
|
||||
#define MUSB_RXFIFOSZ 0x63 /* 8-bit (see masks) */
|
||||
#define MUSB_TXFIFOADD 0x64 /* 16-bit offset shifted right 3 */
|
||||
#define MUSB_RXFIFOADD 0x66 /* 16-bit offset shifted right 3 */
|
||||
|
||||
/* REVISIT: vctrl/vstatus: optional vendor utmi+phy register at 0x68 */
|
||||
#define MUSB_HWVERS 0x6C /* 8 bit */
|
||||
#define MUSB_ULPI_BUSCONTROL 0x70 /* 8 bit */
|
||||
#define MUSB_ULPI_INT_MASK 0x72 /* 8 bit */
|
||||
#define MUSB_ULPI_INT_SRC 0x73 /* 8 bit */
|
||||
#define MUSB_ULPI_REG_DATA 0x74 /* 8 bit */
|
||||
#define MUSB_ULPI_REG_ADDR 0x75 /* 8 bit */
|
||||
#define MUSB_ULPI_REG_CONTROL 0x76 /* 8 bit */
|
||||
#define MUSB_ULPI_RAW_DATA 0x77 /* 8 bit */
|
||||
|
||||
#define MUSB_EPINFO 0x78 /* 8 bit */
|
||||
#define MUSB_RAMINFO 0x79 /* 8 bit */
|
||||
#define MUSB_LINKINFO 0x7a /* 8 bit */
|
||||
#define MUSB_VPLEN 0x7b /* 8 bit */
|
||||
#define MUSB_HS_EOF1 0x7c /* 8 bit */
|
||||
#define MUSB_FS_EOF1 0x7d /* 8 bit */
|
||||
#define MUSB_LS_EOF1 0x7e /* 8 bit */
|
||||
|
||||
/* Offsets to endpoint registers */
|
||||
#define MUSB_TXMAXP 0x00
|
||||
#define MUSB_TXCSR 0x02
|
||||
#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */
|
||||
#define MUSB_RXMAXP 0x04
|
||||
#define MUSB_RXCSR 0x06
|
||||
#define MUSB_RXCOUNT 0x08
|
||||
#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */
|
||||
#define MUSB_TXTYPE 0x0A
|
||||
#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */
|
||||
#define MUSB_TXINTERVAL 0x0B
|
||||
#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */
|
||||
#define MUSB_RXTYPE 0x0C
|
||||
#define MUSB_RXINTERVAL 0x0D
|
||||
#define MUSB_FIFOSIZE 0x0F
|
||||
#define MUSB_CONFIGDATA MUSB_FIFOSIZE /* Re-used for EP0 */
|
||||
|
||||
/* Offsets to endpoint registers in indexed model (using INDEX register) */
|
||||
#define MUSB_INDEXED_OFFSET(_epnum, _offset) \
|
||||
(0x10 + (_offset))
|
||||
|
||||
/* Offsets to endpoint registers in flat models */
|
||||
#define MUSB_FLAT_OFFSET(_epnum, _offset) \
|
||||
(0x100 + (0x10*(_epnum)) + (_offset))
|
||||
|
||||
#if defined(CONFIG_USB_MUSB_TUSB6010) || \
|
||||
defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
|
||||
/* TUSB6010 EP0 configuration register is special */
|
||||
#define MUSB_TUSB_OFFSET(_epnum, _offset) \
|
||||
(0x10 + _offset)
|
||||
#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */
|
||||
#endif
|
||||
|
||||
#define MUSB_TXCSR_MODE 0x2000
|
||||
|
||||
/* "bus control"/target registers, for host side multipoint (external hubs) */
|
||||
#define MUSB_TXFUNCADDR 0x00
|
||||
#define MUSB_TXHUBADDR 0x02
|
||||
#define MUSB_TXHUBPORT 0x03
|
||||
|
||||
#define MUSB_RXFUNCADDR 0x04
|
||||
#define MUSB_RXHUBADDR 0x06
|
||||
#define MUSB_RXHUBPORT 0x07
|
||||
|
||||
#define MUSB_BUSCTL_OFFSET(_epnum, _offset) \
|
||||
(0x80 + (8*(_epnum)) + (_offset))
|
||||
|
||||
static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_TXFIFOSZ, c_size);
|
||||
}
|
||||
|
||||
static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off)
|
||||
{
|
||||
musb_writew(mbase, MUSB_TXFIFOADD, c_off);
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_RXFIFOSZ, c_size);
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off)
|
||||
{
|
||||
musb_writew(mbase, MUSB_RXFIFOADD, c_off);
|
||||
}
|
||||
|
||||
static inline void musb_write_ulpi_buscontrol(void __iomem *mbase, u8 val)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_ULPI_BUSCONTROL, val);
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txfifosz(void __iomem *mbase)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_TXFIFOSZ);
|
||||
}
|
||||
|
||||
static inline u16 musb_read_txfifoadd(void __iomem *mbase)
|
||||
{
|
||||
return musb_readw(mbase, MUSB_TXFIFOADD);
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxfifosz(void __iomem *mbase)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_RXFIFOSZ);
|
||||
}
|
||||
|
||||
static inline u16 musb_read_rxfifoadd(void __iomem *mbase)
|
||||
{
|
||||
return musb_readw(mbase, MUSB_RXFIFOADD);
|
||||
}
|
||||
|
||||
static inline u8 musb_read_ulpi_buscontrol(void __iomem *mbase)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_ULPI_BUSCONTROL);
|
||||
}
|
||||
|
||||
static inline u8 musb_read_configdata(void __iomem *mbase)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_INDEX, 0);
|
||||
return musb_readb(mbase, 0x10 + MUSB_CONFIGDATA);
|
||||
}
|
||||
|
||||
static inline u16 musb_read_hwvers(void __iomem *mbase)
|
||||
{
|
||||
return musb_readw(mbase, MUSB_HWVERS);
|
||||
}
|
||||
|
||||
static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase)
|
||||
{
|
||||
return (MUSB_BUSCTL_OFFSET(i, 0) + mbase);
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs,
|
||||
u8 qh_addr_reg)
|
||||
{
|
||||
musb_writeb(ep_target_regs, MUSB_RXFUNCADDR, qh_addr_reg);
|
||||
}
|
||||
|
||||
static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs,
|
||||
u8 qh_h_addr_reg)
|
||||
{
|
||||
musb_writeb(ep_target_regs, MUSB_RXHUBADDR, qh_h_addr_reg);
|
||||
}
|
||||
|
||||
static inline void musb_write_rxhubport(void __iomem *ep_target_regs,
|
||||
u8 qh_h_port_reg)
|
||||
{
|
||||
musb_writeb(ep_target_regs, MUSB_RXHUBPORT, qh_h_port_reg);
|
||||
}
|
||||
|
||||
static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_addr_reg)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR),
|
||||
qh_addr_reg);
|
||||
}
|
||||
|
||||
static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_addr_reg)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR),
|
||||
qh_addr_reg);
|
||||
}
|
||||
|
||||
static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_h_port_reg)
|
||||
{
|
||||
musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT),
|
||||
qh_h_port_reg);
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxfunaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXFUNCADDR));
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxhubaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXHUBADDR));
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxhubport(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXHUBPORT));
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txfunaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR));
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txhubaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR));
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txhubport(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT));
|
||||
}
|
||||
|
||||
#else /* CONFIG_BLACKFIN */
|
||||
|
||||
#define USB_BASE USB_FADDR
|
||||
#define USB_OFFSET(reg) (reg - USB_BASE)
|
||||
|
||||
/*
|
||||
* Common USB registers
|
||||
*/
|
||||
#define MUSB_FADDR USB_OFFSET(USB_FADDR) /* 8-bit */
|
||||
#define MUSB_POWER USB_OFFSET(USB_POWER) /* 8-bit */
|
||||
#define MUSB_INTRTX USB_OFFSET(USB_INTRTX) /* 16-bit */
|
||||
#define MUSB_INTRRX USB_OFFSET(USB_INTRRX)
|
||||
#define MUSB_INTRTXE USB_OFFSET(USB_INTRTXE)
|
||||
#define MUSB_INTRRXE USB_OFFSET(USB_INTRRXE)
|
||||
#define MUSB_INTRUSB USB_OFFSET(USB_INTRUSB) /* 8 bit */
|
||||
#define MUSB_INTRUSBE USB_OFFSET(USB_INTRUSBE)/* 8 bit */
|
||||
#define MUSB_FRAME USB_OFFSET(USB_FRAME)
|
||||
#define MUSB_INDEX USB_OFFSET(USB_INDEX) /* 8 bit */
|
||||
#define MUSB_TESTMODE USB_OFFSET(USB_TESTMODE)/* 8 bit */
|
||||
|
||||
/* Get offset for a given FIFO from musb->mregs */
|
||||
#define MUSB_FIFO_OFFSET(epnum) \
|
||||
(USB_OFFSET(USB_EP0_FIFO) + ((epnum) * 8))
|
||||
|
||||
/*
|
||||
* Additional Control Registers
|
||||
*/
|
||||
|
||||
#define MUSB_DEVCTL USB_OFFSET(USB_OTG_DEV_CTL) /* 8 bit */
|
||||
|
||||
#define MUSB_LINKINFO USB_OFFSET(USB_LINKINFO)/* 8 bit */
|
||||
#define MUSB_VPLEN USB_OFFSET(USB_VPLEN) /* 8 bit */
|
||||
#define MUSB_HS_EOF1 USB_OFFSET(USB_HS_EOF1) /* 8 bit */
|
||||
#define MUSB_FS_EOF1 USB_OFFSET(USB_FS_EOF1) /* 8 bit */
|
||||
#define MUSB_LS_EOF1 USB_OFFSET(USB_LS_EOF1) /* 8 bit */
|
||||
|
||||
/* Offsets to endpoint registers */
|
||||
#define MUSB_TXMAXP 0x00
|
||||
#define MUSB_TXCSR 0x04
|
||||
#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */
|
||||
#define MUSB_RXMAXP 0x08
|
||||
#define MUSB_RXCSR 0x0C
|
||||
#define MUSB_RXCOUNT 0x10
|
||||
#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */
|
||||
#define MUSB_TXTYPE 0x14
|
||||
#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */
|
||||
#define MUSB_TXINTERVAL 0x18
|
||||
#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */
|
||||
#define MUSB_RXTYPE 0x1C
|
||||
#define MUSB_RXINTERVAL 0x20
|
||||
#define MUSB_TXCOUNT 0x28
|
||||
|
||||
/* Offsets to endpoint registers in indexed model (using INDEX register) */
|
||||
#define MUSB_INDEXED_OFFSET(_epnum, _offset) \
|
||||
(0x40 + (_offset))
|
||||
|
||||
/* Offsets to endpoint registers in flat models */
|
||||
#define MUSB_FLAT_OFFSET(_epnum, _offset) \
|
||||
(USB_OFFSET(USB_EP_NI0_TXMAXP) + (0x40 * (_epnum)) + (_offset))
|
||||
|
||||
/* Not implemented - HW has separate Tx/Rx FIFO */
|
||||
#define MUSB_TXCSR_MODE 0x0000
|
||||
|
||||
static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_ulpi_buscontrol(void __iomem *mbase, u8 val)
|
||||
{
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txfifosz(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u16 musb_read_txfifoadd(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxfifosz(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u16 musb_read_rxfifoadd(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_ulpi_buscontrol(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_configdata(void __iomem *mbase)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u16 musb_read_hwvers(void __iomem *mbase)
|
||||
{
|
||||
/*
|
||||
* This register is invisible on Blackfin, actually the MUSB
|
||||
* RTL version of Blackfin is 1.9, so just hardcode its value.
|
||||
*/
|
||||
return MUSB_HWVERS_1900;
|
||||
}
|
||||
|
||||
static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs,
|
||||
u8 qh_addr_req)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs,
|
||||
u8 qh_h_addr_reg)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_rxhubport(void __iomem *ep_target_regs,
|
||||
u8 qh_h_port_reg)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_addr_reg)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_addr_reg)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum,
|
||||
u8 qh_h_port_reg)
|
||||
{
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxfunaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxhubaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_rxhubport(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txfunaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txhubaddr(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 musb_read_txhubport(void __iomem *mbase, u8 epnum)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BLACKFIN */
|
||||
|
||||
#endif /* __MUSB_REGS_H__ */
|
457
drivers/usb/musb/musb_virthub.c
Normal file
457
drivers/usb/musb/musb_virthub.c
Normal file
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* MUSB OTG driver virtual root hub support
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/timer.h>
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
void musb_host_finish_resume(struct work_struct *work)
|
||||
{
|
||||
struct musb *musb;
|
||||
unsigned long flags;
|
||||
u8 power;
|
||||
|
||||
musb = container_of(work, struct musb, finish_resume_work.work);
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
power = musb_readb(musb->mregs, MUSB_POWER);
|
||||
power &= ~MUSB_POWER_RESUME;
|
||||
dev_dbg(musb->controller, "root port resume stopped, power %02x\n",
|
||||
power);
|
||||
musb_writeb(musb->mregs, MUSB_POWER, power);
|
||||
|
||||
/*
|
||||
* ISSUE: DaVinci (RTL 1.300) disconnects after
|
||||
* resume of high speed peripherals (but not full
|
||||
* speed ones).
|
||||
*/
|
||||
musb->is_active = 1;
|
||||
musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME);
|
||||
musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
|
||||
usb_hcd_poll_rh_status(musb->hcd);
|
||||
/* NOTE: it might really be A_WAIT_BCON ... */
|
||||
musb->xceiv->state = OTG_STATE_A_HOST;
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
}
|
||||
|
||||
void musb_port_suspend(struct musb *musb, bool do_suspend)
|
||||
{
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
u8 power;
|
||||
void __iomem *mbase = musb->mregs;
|
||||
|
||||
if (!is_host_active(musb))
|
||||
return;
|
||||
|
||||
/* NOTE: this doesn't necessarily put PHY into low power mode,
|
||||
* turning off its clock; that's a function of PHY integration and
|
||||
* MUSB_POWER_ENSUSPEND. PHY may need a clock (sigh) to detect
|
||||
* SE0 changing to connect (J) or wakeup (K) states.
|
||||
*/
|
||||
power = musb_readb(mbase, MUSB_POWER);
|
||||
if (do_suspend) {
|
||||
int retries = 10000;
|
||||
|
||||
power &= ~MUSB_POWER_RESUME;
|
||||
power |= MUSB_POWER_SUSPENDM;
|
||||
musb_writeb(mbase, MUSB_POWER, power);
|
||||
|
||||
/* Needed for OPT A tests */
|
||||
power = musb_readb(mbase, MUSB_POWER);
|
||||
while (power & MUSB_POWER_SUSPENDM) {
|
||||
power = musb_readb(mbase, MUSB_POWER);
|
||||
if (retries-- < 1)
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(musb->controller, "Root port suspended, power %02x\n", power);
|
||||
|
||||
musb->port1_status |= USB_PORT_STAT_SUSPEND;
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_HOST:
|
||||
musb->xceiv->state = OTG_STATE_A_SUSPEND;
|
||||
musb->is_active = otg->host->b_hnp_enable;
|
||||
if (musb->is_active)
|
||||
mod_timer(&musb->otg_timer, jiffies
|
||||
+ msecs_to_jiffies(
|
||||
OTG_TIME_A_AIDL_BDIS));
|
||||
musb_platform_try_idle(musb, 0);
|
||||
break;
|
||||
case OTG_STATE_B_HOST:
|
||||
musb->xceiv->state = OTG_STATE_B_WAIT_ACON;
|
||||
musb->is_active = otg->host->b_hnp_enable;
|
||||
musb_platform_try_idle(musb, 0);
|
||||
break;
|
||||
default:
|
||||
dev_dbg(musb->controller, "bogus rh suspend? %s\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
}
|
||||
} else if (power & MUSB_POWER_SUSPENDM) {
|
||||
power &= ~MUSB_POWER_SUSPENDM;
|
||||
power |= MUSB_POWER_RESUME;
|
||||
musb_writeb(mbase, MUSB_POWER, power);
|
||||
|
||||
dev_dbg(musb->controller, "Root port resuming, power %02x\n", power);
|
||||
|
||||
/* later, GetPortStatus will stop RESUME signaling */
|
||||
musb->port1_status |= MUSB_PORT_STAT_RESUME;
|
||||
schedule_delayed_work(&musb->finish_resume_work,
|
||||
msecs_to_jiffies(USB_RESUME_TIMEOUT));
|
||||
}
|
||||
}
|
||||
|
||||
void musb_port_reset(struct musb *musb, bool do_reset)
|
||||
{
|
||||
u8 power;
|
||||
void __iomem *mbase = musb->mregs;
|
||||
|
||||
if (musb->xceiv->state == OTG_STATE_B_IDLE) {
|
||||
dev_dbg(musb->controller, "HNP: Returning from HNP; no hub reset from b_idle\n");
|
||||
musb->port1_status &= ~USB_PORT_STAT_RESET;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_host_active(musb))
|
||||
return;
|
||||
|
||||
/* NOTE: caller guarantees it will turn off the reset when
|
||||
* the appropriate amount of time has passed
|
||||
*/
|
||||
power = musb_readb(mbase, MUSB_POWER);
|
||||
if (do_reset) {
|
||||
/*
|
||||
* If RESUME is set, we must make sure it stays minimum 20 ms.
|
||||
* Then we must clear RESUME and wait a bit to let musb start
|
||||
* generating SOFs. If we don't do this, OPT HS A 6.8 tests
|
||||
* fail with "Error! Did not receive an SOF before suspend
|
||||
* detected".
|
||||
*/
|
||||
if (power & MUSB_POWER_RESUME) {
|
||||
long remain = (unsigned long) musb->rh_timer - jiffies;
|
||||
|
||||
if (musb->rh_timer > 0 && remain > 0) {
|
||||
/* take into account the minimum delay after resume */
|
||||
schedule_delayed_work(
|
||||
&musb->deassert_reset_work, remain);
|
||||
return;
|
||||
}
|
||||
|
||||
musb_writeb(mbase, MUSB_POWER,
|
||||
power & ~MUSB_POWER_RESUME);
|
||||
|
||||
/* Give the core 1 ms to clear MUSB_POWER_RESUME */
|
||||
schedule_delayed_work(&musb->deassert_reset_work,
|
||||
msecs_to_jiffies(1));
|
||||
return;
|
||||
}
|
||||
|
||||
power &= 0xf0;
|
||||
musb_writeb(mbase, MUSB_POWER,
|
||||
power | MUSB_POWER_RESET);
|
||||
|
||||
musb->port1_status |= USB_PORT_STAT_RESET;
|
||||
musb->port1_status &= ~USB_PORT_STAT_ENABLE;
|
||||
schedule_delayed_work(&musb->deassert_reset_work,
|
||||
msecs_to_jiffies(50));
|
||||
} else {
|
||||
dev_dbg(musb->controller, "root port reset stopped\n");
|
||||
musb_writeb(mbase, MUSB_POWER,
|
||||
power & ~MUSB_POWER_RESET);
|
||||
|
||||
power = musb_readb(mbase, MUSB_POWER);
|
||||
if (power & MUSB_POWER_HSMODE) {
|
||||
dev_dbg(musb->controller, "high-speed device connected\n");
|
||||
musb->port1_status |= USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
|
||||
musb->port1_status &= ~USB_PORT_STAT_RESET;
|
||||
musb->port1_status |= USB_PORT_STAT_ENABLE
|
||||
| (USB_PORT_STAT_C_RESET << 16)
|
||||
| (USB_PORT_STAT_C_ENABLE << 16);
|
||||
usb_hcd_poll_rh_status(musb->hcd);
|
||||
|
||||
musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
void musb_root_disconnect(struct musb *musb)
|
||||
{
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
|
||||
musb->port1_status = USB_PORT_STAT_POWER
|
||||
| (USB_PORT_STAT_C_CONNECTION << 16);
|
||||
|
||||
usb_hcd_poll_rh_status(musb->hcd);
|
||||
musb->is_active = 0;
|
||||
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_SUSPEND:
|
||||
if (otg->host->b_hnp_enable) {
|
||||
musb->xceiv->state = OTG_STATE_A_PERIPHERAL;
|
||||
musb->g.is_a_peripheral = 1;
|
||||
break;
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
case OTG_STATE_A_HOST:
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
||||
musb->is_active = 0;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(musb->controller, "host disconnect (%s)\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
|
||||
/* Caller may or may not hold musb->lock */
|
||||
int musb_hub_status_data(struct usb_hcd *hcd, char *buf)
|
||||
{
|
||||
struct musb *musb = hcd_to_musb(hcd);
|
||||
int retval = 0;
|
||||
|
||||
/* called in_irq() via usb_hcd_poll_rh_status() */
|
||||
if (musb->port1_status & 0xffff0000) {
|
||||
*buf = 0x02;
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int musb_has_gadget(struct musb *musb)
|
||||
{
|
||||
/*
|
||||
* In host-only mode we start a connection right away. In OTG mode
|
||||
* we have to wait until we loaded a gadget. We don't really need a
|
||||
* gadget if we operate as a host but we should not start a session
|
||||
* as a device without a gadget or else we explode.
|
||||
*/
|
||||
#ifdef CONFIG_USB_MUSB_HOST
|
||||
return 1;
|
||||
#else
|
||||
if (musb->port_mode == MUSB_PORT_MODE_HOST)
|
||||
return 1;
|
||||
return musb->g.dev.driver != NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
int musb_hub_control(
|
||||
struct usb_hcd *hcd,
|
||||
u16 typeReq,
|
||||
u16 wValue,
|
||||
u16 wIndex,
|
||||
char *buf,
|
||||
u16 wLength)
|
||||
{
|
||||
struct musb *musb = hcd_to_musb(hcd);
|
||||
u32 temp;
|
||||
int retval = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) {
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
return -ESHUTDOWN;
|
||||
}
|
||||
|
||||
/* hub features: always zero, setting is a NOP
|
||||
* port features: reported, sometimes updated when host is active
|
||||
* no indicators
|
||||
*/
|
||||
switch (typeReq) {
|
||||
case ClearHubFeature:
|
||||
case SetHubFeature:
|
||||
switch (wValue) {
|
||||
case C_HUB_OVER_CURRENT:
|
||||
case C_HUB_LOCAL_POWER:
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
if ((wIndex & 0xff) != 1)
|
||||
goto error;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
break;
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
musb_port_suspend(musb, false);
|
||||
break;
|
||||
case USB_PORT_FEAT_POWER:
|
||||
if (!hcd->self.is_b_host)
|
||||
musb_platform_set_vbus(musb, 0);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
dev_dbg(musb->controller, "clear feature %d\n", wValue);
|
||||
musb->port1_status &= ~(1 << wValue);
|
||||
break;
|
||||
case GetHubDescriptor:
|
||||
{
|
||||
struct usb_hub_descriptor *desc = (void *)buf;
|
||||
|
||||
desc->bDescLength = 9;
|
||||
desc->bDescriptorType = 0x29;
|
||||
desc->bNbrPorts = 1;
|
||||
desc->wHubCharacteristics = cpu_to_le16(
|
||||
0x0001 /* per-port power switching */
|
||||
| 0x0010 /* no overcurrent reporting */
|
||||
);
|
||||
desc->bPwrOn2PwrGood = 5; /* msec/2 */
|
||||
desc->bHubContrCurrent = 0;
|
||||
|
||||
/* workaround bogus struct definition */
|
||||
desc->u.hs.DeviceRemovable[0] = 0x02; /* port 1 */
|
||||
desc->u.hs.DeviceRemovable[1] = 0xff;
|
||||
}
|
||||
break;
|
||||
case GetHubStatus:
|
||||
temp = 0;
|
||||
*(__le32 *) buf = cpu_to_le32(temp);
|
||||
break;
|
||||
case GetPortStatus:
|
||||
if (wIndex != 1)
|
||||
goto error;
|
||||
|
||||
put_unaligned(cpu_to_le32(musb->port1_status
|
||||
& ~MUSB_PORT_STAT_RESUME),
|
||||
(__le32 *) buf);
|
||||
|
||||
/* port change status is more interesting */
|
||||
dev_dbg(musb->controller, "port status %08x\n",
|
||||
musb->port1_status);
|
||||
break;
|
||||
case SetPortFeature:
|
||||
if ((wIndex & 0xff) != 1)
|
||||
goto error;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_PORT_FEAT_POWER:
|
||||
/* NOTE: this controller has a strange state machine
|
||||
* that involves "requesting sessions" according to
|
||||
* magic side effects from incompletely-described
|
||||
* rules about startup...
|
||||
*
|
||||
* This call is what really starts the host mode; be
|
||||
* very careful about side effects if you reorder any
|
||||
* initialization logic, e.g. for OTG, or change any
|
||||
* logic relating to VBUS power-up.
|
||||
*/
|
||||
if (!hcd->self.is_b_host && musb_has_gadget(musb))
|
||||
musb_start(musb);
|
||||
break;
|
||||
case USB_PORT_FEAT_RESET:
|
||||
musb_port_reset(musb, true);
|
||||
break;
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
musb_port_suspend(musb, true);
|
||||
break;
|
||||
case USB_PORT_FEAT_TEST:
|
||||
if (unlikely(is_host_active(musb)))
|
||||
goto error;
|
||||
|
||||
wIndex >>= 8;
|
||||
switch (wIndex) {
|
||||
case 1:
|
||||
pr_debug("TEST_J\n");
|
||||
temp = MUSB_TEST_J;
|
||||
break;
|
||||
case 2:
|
||||
pr_debug("TEST_K\n");
|
||||
temp = MUSB_TEST_K;
|
||||
break;
|
||||
case 3:
|
||||
pr_debug("TEST_SE0_NAK\n");
|
||||
temp = MUSB_TEST_SE0_NAK;
|
||||
break;
|
||||
case 4:
|
||||
pr_debug("TEST_PACKET\n");
|
||||
temp = MUSB_TEST_PACKET;
|
||||
musb_load_testpacket(musb);
|
||||
break;
|
||||
case 5:
|
||||
pr_debug("TEST_FORCE_ENABLE\n");
|
||||
temp = MUSB_TEST_FORCE_HOST
|
||||
| MUSB_TEST_FORCE_HS;
|
||||
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL,
|
||||
MUSB_DEVCTL_SESSION);
|
||||
break;
|
||||
case 6:
|
||||
pr_debug("TEST_FIFO_ACCESS\n");
|
||||
temp = MUSB_TEST_FIFO_ACCESS;
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
musb_writeb(musb->mregs, MUSB_TESTMODE, temp);
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
dev_dbg(musb->controller, "set feature %d\n", wValue);
|
||||
musb->port1_status |= 1 << wValue;
|
||||
break;
|
||||
|
||||
default:
|
||||
error:
|
||||
/* "protocol stall" on error */
|
||||
retval = -EPIPE;
|
||||
}
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
return retval;
|
||||
}
|
408
drivers/usb/musb/musbhsdma.c
Normal file
408
drivers/usb/musb/musbhsdma.c
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* MUSB OTG driver - support for Mentor's DMA controller
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2007 by Texas Instruments
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include "musb_core.h"
|
||||
#include "musbhsdma.h"
|
||||
|
||||
static void dma_channel_release(struct dma_channel *channel);
|
||||
|
||||
static void dma_controller_stop(struct musb_dma_controller *controller)
|
||||
{
|
||||
struct musb *musb = controller->private_data;
|
||||
struct dma_channel *channel;
|
||||
u8 bit;
|
||||
|
||||
if (controller->used_channels != 0) {
|
||||
dev_err(musb->controller,
|
||||
"Stopping DMA controller while channel active\n");
|
||||
|
||||
for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
|
||||
if (controller->used_channels & (1 << bit)) {
|
||||
channel = &controller->channel[bit].channel;
|
||||
dma_channel_release(channel);
|
||||
|
||||
if (!controller->used_channels)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct dma_channel *dma_channel_allocate(struct dma_controller *c,
|
||||
struct musb_hw_ep *hw_ep, u8 transmit)
|
||||
{
|
||||
struct musb_dma_controller *controller = container_of(c,
|
||||
struct musb_dma_controller, controller);
|
||||
struct musb_dma_channel *musb_channel = NULL;
|
||||
struct dma_channel *channel = NULL;
|
||||
u8 bit;
|
||||
|
||||
for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
|
||||
if (!(controller->used_channels & (1 << bit))) {
|
||||
controller->used_channels |= (1 << bit);
|
||||
musb_channel = &(controller->channel[bit]);
|
||||
musb_channel->controller = controller;
|
||||
musb_channel->idx = bit;
|
||||
musb_channel->epnum = hw_ep->epnum;
|
||||
musb_channel->transmit = transmit;
|
||||
channel = &(musb_channel->channel);
|
||||
channel->private_data = musb_channel;
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
channel->max_len = 0x100000;
|
||||
/* Tx => mode 1; Rx => mode 0 */
|
||||
channel->desired_mode = transmit;
|
||||
channel->actual_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
static void dma_channel_release(struct dma_channel *channel)
|
||||
{
|
||||
struct musb_dma_channel *musb_channel = channel->private_data;
|
||||
|
||||
channel->actual_len = 0;
|
||||
musb_channel->start_addr = 0;
|
||||
musb_channel->len = 0;
|
||||
|
||||
musb_channel->controller->used_channels &=
|
||||
~(1 << musb_channel->idx);
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
static void configure_channel(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
struct musb_dma_channel *musb_channel = channel->private_data;
|
||||
struct musb_dma_controller *controller = musb_channel->controller;
|
||||
struct musb *musb = controller->private_data;
|
||||
void __iomem *mbase = controller->base;
|
||||
u8 bchannel = musb_channel->idx;
|
||||
u16 csr = 0;
|
||||
|
||||
dev_dbg(musb->controller, "%p, pkt_sz %d, addr 0x%x, len %d, mode %d\n",
|
||||
channel, packet_sz, dma_addr, len, mode);
|
||||
|
||||
if (mode) {
|
||||
csr |= 1 << MUSB_HSDMA_MODE1_SHIFT;
|
||||
BUG_ON(len < packet_sz);
|
||||
}
|
||||
csr |= MUSB_HSDMA_BURSTMODE_INCR16
|
||||
<< MUSB_HSDMA_BURSTMODE_SHIFT;
|
||||
|
||||
csr |= (musb_channel->epnum << MUSB_HSDMA_ENDPOINT_SHIFT)
|
||||
| (1 << MUSB_HSDMA_ENABLE_SHIFT)
|
||||
| (1 << MUSB_HSDMA_IRQENABLE_SHIFT)
|
||||
| (musb_channel->transmit
|
||||
? (1 << MUSB_HSDMA_TRANSMIT_SHIFT)
|
||||
: 0);
|
||||
|
||||
/* address/count */
|
||||
musb_write_hsdma_addr(mbase, bchannel, dma_addr);
|
||||
musb_write_hsdma_count(mbase, bchannel, len);
|
||||
|
||||
/* control (this should start things) */
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL),
|
||||
csr);
|
||||
}
|
||||
|
||||
static int dma_channel_program(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
struct musb_dma_channel *musb_channel = channel->private_data;
|
||||
struct musb_dma_controller *controller = musb_channel->controller;
|
||||
struct musb *musb = controller->private_data;
|
||||
|
||||
dev_dbg(musb->controller, "ep%d-%s pkt_sz %d, dma_addr 0x%x length %d, mode %d\n",
|
||||
musb_channel->epnum,
|
||||
musb_channel->transmit ? "Tx" : "Rx",
|
||||
packet_sz, dma_addr, len, mode);
|
||||
|
||||
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
|
||||
channel->status == MUSB_DMA_STATUS_BUSY);
|
||||
|
||||
/* Let targets check/tweak the arguments */
|
||||
if (musb->ops->adjust_channel_params) {
|
||||
int ret = musb->ops->adjust_channel_params(channel,
|
||||
packet_sz, &mode, &dma_addr, &len);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The DMA engine in RTL1.8 and above cannot handle
|
||||
* DMA addresses that are not aligned to a 4 byte boundary.
|
||||
* It ends up masking the last two bits of the address
|
||||
* programmed in DMA_ADDR.
|
||||
*
|
||||
* Fail such DMA transfers, so that the backup PIO mode
|
||||
* can carry out the transfer
|
||||
*/
|
||||
if ((musb->hwvers >= MUSB_HWVERS_1800) && (dma_addr % 4))
|
||||
return false;
|
||||
|
||||
channel->actual_len = 0;
|
||||
musb_channel->start_addr = dma_addr;
|
||||
musb_channel->len = len;
|
||||
musb_channel->max_packet_sz = packet_sz;
|
||||
channel->status = MUSB_DMA_STATUS_BUSY;
|
||||
|
||||
configure_channel(channel, packet_sz, mode, dma_addr, len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int dma_channel_abort(struct dma_channel *channel)
|
||||
{
|
||||
struct musb_dma_channel *musb_channel = channel->private_data;
|
||||
void __iomem *mbase = musb_channel->controller->base;
|
||||
|
||||
u8 bchannel = musb_channel->idx;
|
||||
int offset;
|
||||
u16 csr;
|
||||
|
||||
if (channel->status == MUSB_DMA_STATUS_BUSY) {
|
||||
if (musb_channel->transmit) {
|
||||
offset = MUSB_EP_OFFSET(musb_channel->epnum,
|
||||
MUSB_TXCSR);
|
||||
|
||||
/*
|
||||
* The programming guide says that we must clear
|
||||
* the DMAENAB bit before the DMAMODE bit...
|
||||
*/
|
||||
csr = musb_readw(mbase, offset);
|
||||
csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
|
||||
musb_writew(mbase, offset, csr);
|
||||
csr &= ~MUSB_TXCSR_DMAMODE;
|
||||
musb_writew(mbase, offset, csr);
|
||||
} else {
|
||||
offset = MUSB_EP_OFFSET(musb_channel->epnum,
|
||||
MUSB_RXCSR);
|
||||
|
||||
csr = musb_readw(mbase, offset);
|
||||
csr &= ~(MUSB_RXCSR_AUTOCLEAR |
|
||||
MUSB_RXCSR_DMAENAB |
|
||||
MUSB_RXCSR_DMAMODE);
|
||||
musb_writew(mbase, offset, csr);
|
||||
}
|
||||
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL),
|
||||
0);
|
||||
musb_write_hsdma_addr(mbase, bchannel, 0);
|
||||
musb_write_hsdma_count(mbase, bchannel, 0);
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t dma_controller_irq(int irq, void *private_data)
|
||||
{
|
||||
struct musb_dma_controller *controller = private_data;
|
||||
struct musb *musb = controller->private_data;
|
||||
struct musb_dma_channel *musb_channel;
|
||||
struct dma_channel *channel;
|
||||
|
||||
void __iomem *mbase = controller->base;
|
||||
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
|
||||
unsigned long flags;
|
||||
|
||||
u8 bchannel;
|
||||
u8 int_hsdma;
|
||||
|
||||
u32 addr, count;
|
||||
u16 csr;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
int_hsdma = musb_readb(mbase, MUSB_HSDMA_INTR);
|
||||
|
||||
#ifdef CONFIG_BLACKFIN
|
||||
/* Clear DMA interrupt flags */
|
||||
musb_writeb(mbase, MUSB_HSDMA_INTR, int_hsdma);
|
||||
#endif
|
||||
|
||||
if (!int_hsdma) {
|
||||
dev_dbg(musb->controller, "spurious DMA irq\n");
|
||||
|
||||
for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
|
||||
musb_channel = (struct musb_dma_channel *)
|
||||
&(controller->channel[bchannel]);
|
||||
channel = &musb_channel->channel;
|
||||
if (channel->status == MUSB_DMA_STATUS_BUSY) {
|
||||
count = musb_read_hsdma_count(mbase, bchannel);
|
||||
|
||||
if (count == 0)
|
||||
int_hsdma |= (1 << bchannel);
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(musb->controller, "int_hsdma = 0x%x\n", int_hsdma);
|
||||
|
||||
if (!int_hsdma)
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
|
||||
if (int_hsdma & (1 << bchannel)) {
|
||||
musb_channel = (struct musb_dma_channel *)
|
||||
&(controller->channel[bchannel]);
|
||||
channel = &musb_channel->channel;
|
||||
|
||||
csr = musb_readw(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel,
|
||||
MUSB_HSDMA_CONTROL));
|
||||
|
||||
if (csr & (1 << MUSB_HSDMA_BUSERROR_SHIFT)) {
|
||||
musb_channel->channel.status =
|
||||
MUSB_DMA_STATUS_BUS_ABORT;
|
||||
} else {
|
||||
u8 devctl;
|
||||
|
||||
addr = musb_read_hsdma_addr(mbase,
|
||||
bchannel);
|
||||
channel->actual_len = addr
|
||||
- musb_channel->start_addr;
|
||||
|
||||
dev_dbg(musb->controller, "ch %p, 0x%x -> 0x%x (%zu / %d) %s\n",
|
||||
channel, musb_channel->start_addr,
|
||||
addr, channel->actual_len,
|
||||
musb_channel->len,
|
||||
(channel->actual_len
|
||||
< musb_channel->len) ?
|
||||
"=> reconfig 0" : "=> complete");
|
||||
|
||||
devctl = musb_readb(mbase, MUSB_DEVCTL);
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
|
||||
/* completed */
|
||||
if ((devctl & MUSB_DEVCTL_HM)
|
||||
&& (musb_channel->transmit)
|
||||
&& ((channel->desired_mode == 0)
|
||||
|| (channel->actual_len &
|
||||
(musb_channel->max_packet_sz - 1)))
|
||||
) {
|
||||
u8 epnum = musb_channel->epnum;
|
||||
int offset = MUSB_EP_OFFSET(epnum,
|
||||
MUSB_TXCSR);
|
||||
u16 txcsr;
|
||||
|
||||
/*
|
||||
* The programming guide says that we
|
||||
* must clear DMAENAB before DMAMODE.
|
||||
*/
|
||||
musb_ep_select(mbase, epnum);
|
||||
txcsr = musb_readw(mbase, offset);
|
||||
txcsr &= ~(MUSB_TXCSR_DMAENAB
|
||||
| MUSB_TXCSR_AUTOSET);
|
||||
musb_writew(mbase, offset, txcsr);
|
||||
/* Send out the packet */
|
||||
txcsr &= ~MUSB_TXCSR_DMAMODE;
|
||||
txcsr |= MUSB_TXCSR_TXPKTRDY;
|
||||
musb_writew(mbase, offset, txcsr);
|
||||
}
|
||||
musb_dma_completion(musb, musb_channel->epnum,
|
||||
musb_channel->transmit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retval = IRQ_HANDLED;
|
||||
done:
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void dma_controller_destroy(struct dma_controller *c)
|
||||
{
|
||||
struct musb_dma_controller *controller = container_of(c,
|
||||
struct musb_dma_controller, controller);
|
||||
|
||||
dma_controller_stop(controller);
|
||||
|
||||
if (controller->irq)
|
||||
free_irq(controller->irq, c);
|
||||
|
||||
kfree(controller);
|
||||
}
|
||||
|
||||
struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base)
|
||||
{
|
||||
struct musb_dma_controller *controller;
|
||||
struct device *dev = musb->controller;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int irq = platform_get_irq_byname(pdev, "dma");
|
||||
|
||||
if (irq <= 0) {
|
||||
dev_err(dev, "No DMA interrupt line!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
controller = kzalloc(sizeof(*controller), GFP_KERNEL);
|
||||
if (!controller)
|
||||
return NULL;
|
||||
|
||||
controller->channel_count = MUSB_HSDMA_CHANNELS;
|
||||
controller->private_data = musb;
|
||||
controller->base = base;
|
||||
|
||||
controller->controller.channel_alloc = dma_channel_allocate;
|
||||
controller->controller.channel_release = dma_channel_release;
|
||||
controller->controller.channel_program = dma_channel_program;
|
||||
controller->controller.channel_abort = dma_channel_abort;
|
||||
|
||||
if (request_irq(irq, dma_controller_irq, 0,
|
||||
dev_name(musb->controller), &controller->controller)) {
|
||||
dev_err(dev, "request_irq %d failed!\n", irq);
|
||||
dma_controller_destroy(&controller->controller);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
controller->irq = irq;
|
||||
|
||||
return &controller->controller;
|
||||
}
|
161
drivers/usb/musb/musbhsdma.h
Normal file
161
drivers/usb/musb/musbhsdma.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* MUSB OTG driver - support for Mentor's DMA controller
|
||||
*
|
||||
* Copyright 2005 Mentor Graphics Corporation
|
||||
* Copyright (C) 2005-2007 by Texas Instruments
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* 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 St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_BLACKFIN
|
||||
|
||||
#define MUSB_HSDMA_BASE 0x200
|
||||
#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0)
|
||||
#define MUSB_HSDMA_CONTROL 0x4
|
||||
#define MUSB_HSDMA_ADDRESS 0x8
|
||||
#define MUSB_HSDMA_COUNT 0xc
|
||||
|
||||
#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \
|
||||
(MUSB_HSDMA_BASE + (_bchannel << 4) + _offset)
|
||||
|
||||
#define musb_read_hsdma_addr(mbase, bchannel) \
|
||||
musb_readl(mbase, \
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS))
|
||||
|
||||
#define musb_write_hsdma_addr(mbase, bchannel, addr) \
|
||||
musb_writel(mbase, \
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), \
|
||||
addr)
|
||||
|
||||
#define musb_read_hsdma_count(mbase, bchannel) \
|
||||
musb_readl(mbase, \
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT))
|
||||
|
||||
#define musb_write_hsdma_count(mbase, bchannel, len) \
|
||||
musb_writel(mbase, \
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), \
|
||||
len)
|
||||
#else
|
||||
|
||||
#define MUSB_HSDMA_BASE 0x400
|
||||
#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0)
|
||||
#define MUSB_HSDMA_CONTROL 0x04
|
||||
#define MUSB_HSDMA_ADDR_LOW 0x08
|
||||
#define MUSB_HSDMA_ADDR_HIGH 0x0C
|
||||
#define MUSB_HSDMA_COUNT_LOW 0x10
|
||||
#define MUSB_HSDMA_COUNT_HIGH 0x14
|
||||
|
||||
#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \
|
||||
(MUSB_HSDMA_BASE + (_bchannel * 0x20) + _offset)
|
||||
|
||||
static inline u32 musb_read_hsdma_addr(void __iomem *mbase, u8 bchannel)
|
||||
{
|
||||
u32 addr = musb_readw(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH));
|
||||
|
||||
addr = addr << 16;
|
||||
|
||||
addr |= musb_readw(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW));
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static inline void musb_write_hsdma_addr(void __iomem *mbase,
|
||||
u8 bchannel, dma_addr_t dma_addr)
|
||||
{
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW),
|
||||
dma_addr);
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH),
|
||||
(dma_addr >> 16));
|
||||
}
|
||||
|
||||
static inline u32 musb_read_hsdma_count(void __iomem *mbase, u8 bchannel)
|
||||
{
|
||||
u32 count = musb_readw(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_HIGH));
|
||||
|
||||
count = count << 16;
|
||||
|
||||
count |= musb_readw(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_LOW));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline void musb_write_hsdma_count(void __iomem *mbase,
|
||||
u8 bchannel, u32 len)
|
||||
{
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_LOW),len);
|
||||
musb_writew(mbase,
|
||||
MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_HIGH),
|
||||
(len >> 16));
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BLACKFIN */
|
||||
|
||||
/* control register (16-bit): */
|
||||
#define MUSB_HSDMA_ENABLE_SHIFT 0
|
||||
#define MUSB_HSDMA_TRANSMIT_SHIFT 1
|
||||
#define MUSB_HSDMA_MODE1_SHIFT 2
|
||||
#define MUSB_HSDMA_IRQENABLE_SHIFT 3
|
||||
#define MUSB_HSDMA_ENDPOINT_SHIFT 4
|
||||
#define MUSB_HSDMA_BUSERROR_SHIFT 8
|
||||
#define MUSB_HSDMA_BURSTMODE_SHIFT 9
|
||||
#define MUSB_HSDMA_BURSTMODE (3 << MUSB_HSDMA_BURSTMODE_SHIFT)
|
||||
#define MUSB_HSDMA_BURSTMODE_UNSPEC 0
|
||||
#define MUSB_HSDMA_BURSTMODE_INCR4 1
|
||||
#define MUSB_HSDMA_BURSTMODE_INCR8 2
|
||||
#define MUSB_HSDMA_BURSTMODE_INCR16 3
|
||||
|
||||
#define MUSB_HSDMA_CHANNELS 8
|
||||
|
||||
struct musb_dma_controller;
|
||||
|
||||
struct musb_dma_channel {
|
||||
struct dma_channel channel;
|
||||
struct musb_dma_controller *controller;
|
||||
u32 start_addr;
|
||||
u32 len;
|
||||
u16 max_packet_sz;
|
||||
u8 idx;
|
||||
u8 epnum;
|
||||
u8 transmit;
|
||||
};
|
||||
|
||||
struct musb_dma_controller {
|
||||
struct dma_controller controller;
|
||||
struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS];
|
||||
void *private_data;
|
||||
void __iomem *base;
|
||||
u8 channel_count;
|
||||
u8 used_channels;
|
||||
u8 irq;
|
||||
};
|
736
drivers/usb/musb/omap2430.c
Normal file
736
drivers/usb/musb/omap2430.c
Normal file
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2007 by Texas Instruments
|
||||
* Some code has been taken from tusb6010.c
|
||||
* Copyrights for that are attributable to:
|
||||
* Copyright (C) 2006 Nokia Corporation
|
||||
* Tony Lindgren <tony@atomide.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/usb/musb-omap.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
#include "omap2430.h"
|
||||
|
||||
struct omap2430_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
enum omap_musb_vbus_id_status status;
|
||||
struct work_struct omap_musb_mailbox_work;
|
||||
struct device *control_otghs;
|
||||
};
|
||||
#define glue_to_musb(g) platform_get_drvdata(g->musb)
|
||||
|
||||
static struct omap2430_glue *_glue;
|
||||
|
||||
static struct timer_list musb_idle_timer;
|
||||
|
||||
static void musb_do_idle(unsigned long _musb)
|
||||
{
|
||||
struct musb *musb = (void *)_musb;
|
||||
unsigned long flags;
|
||||
u8 power;
|
||||
u8 devctl;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
switch (musb->xceiv->state) {
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
|
||||
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_SUSPEND:
|
||||
/* finish RESUME signaling? */
|
||||
if (musb->port1_status & MUSB_PORT_STAT_RESUME) {
|
||||
power = musb_readb(musb->mregs, MUSB_POWER);
|
||||
power &= ~MUSB_POWER_RESUME;
|
||||
dev_dbg(musb->controller, "root port resume stopped, power %02x\n", power);
|
||||
musb_writeb(musb->mregs, MUSB_POWER, power);
|
||||
musb->is_active = 1;
|
||||
musb->port1_status &= ~(USB_PORT_STAT_SUSPEND
|
||||
| MUSB_PORT_STAT_RESUME);
|
||||
musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
|
||||
usb_hcd_poll_rh_status(musb->hcd);
|
||||
/* NOTE: it might really be A_WAIT_BCON ... */
|
||||
musb->xceiv->state = OTG_STATE_A_HOST;
|
||||
}
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
||||
if (devctl & MUSB_DEVCTL_BDEVICE)
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
else
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
}
|
||||
|
||||
|
||||
static void omap2430_musb_try_idle(struct musb *musb, unsigned long timeout)
|
||||
{
|
||||
unsigned long default_timeout = jiffies + msecs_to_jiffies(3);
|
||||
static unsigned long last_timer;
|
||||
|
||||
if (timeout == 0)
|
||||
timeout = default_timeout;
|
||||
|
||||
/* 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))) {
|
||||
dev_dbg(musb->controller, "%s active, deleting timer\n",
|
||||
usb_otg_state_string(musb->xceiv->state));
|
||||
del_timer(&musb_idle_timer);
|
||||
last_timer = jiffies;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time_after(last_timer, timeout)) {
|
||||
if (!timer_pending(&musb_idle_timer))
|
||||
last_timer = timeout;
|
||||
else {
|
||||
dev_dbg(musb->controller, "Longer idle timer already pending, ignoring\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_timer = timeout;
|
||||
|
||||
dev_dbg(musb->controller, "%s inactive, for idle timer for %lu ms\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
(unsigned long)jiffies_to_msecs(timeout - jiffies));
|
||||
mod_timer(&musb_idle_timer, timeout);
|
||||
}
|
||||
|
||||
static void omap2430_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
u8 devctl;
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
/* HDRC controls CPEN, but beware current surges during device
|
||||
* connect. They can trigger transient overcurrent conditions
|
||||
* that must be ignored.
|
||||
*/
|
||||
|
||||
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
||||
|
||||
if (is_on) {
|
||||
if (musb->xceiv->state == OTG_STATE_A_IDLE) {
|
||||
int loops = 100;
|
||||
/* start the session */
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
/*
|
||||
* Wait for the musb to set as A device to enable the
|
||||
* VBUS
|
||||
*/
|
||||
while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) {
|
||||
|
||||
mdelay(5);
|
||||
cpu_relax();
|
||||
|
||||
if (time_after(jiffies, timeout)
|
||||
|| loops-- <= 0) {
|
||||
dev_err(musb->controller,
|
||||
"configured as A device timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
otg_set_vbus(otg, 1);
|
||||
} else {
|
||||
musb->is_active = 1;
|
||||
otg->default_a = 1;
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
MUSB_HST_MODE(musb);
|
||||
}
|
||||
} else {
|
||||
musb->is_active = 0;
|
||||
|
||||
/* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and
|
||||
* jumping right to B_IDLE...
|
||||
*/
|
||||
|
||||
otg->default_a = 0;
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
devctl &= ~MUSB_DEVCTL_SESSION;
|
||||
|
||||
MUSB_DEV_MODE(musb);
|
||||
}
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
|
||||
dev_dbg(musb->controller, "VBUS %s, devctl %02x "
|
||||
/* otg %3x conf %08x prcm %08x */ "\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
musb_readb(musb->mregs, MUSB_DEVCTL));
|
||||
}
|
||||
|
||||
static int omap2430_musb_set_mode(struct musb *musb, u8 musb_mode)
|
||||
{
|
||||
u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
||||
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void omap2430_low_level_exit(struct musb *musb)
|
||||
{
|
||||
u32 l;
|
||||
|
||||
/* in any role */
|
||||
l = musb_readl(musb->mregs, OTG_FORCESTDBY);
|
||||
l |= ENABLEFORCE; /* enable MSTANDBY */
|
||||
musb_writel(musb->mregs, OTG_FORCESTDBY, l);
|
||||
}
|
||||
|
||||
static inline void omap2430_low_level_init(struct musb *musb)
|
||||
{
|
||||
u32 l;
|
||||
|
||||
l = musb_readl(musb->mregs, OTG_FORCESTDBY);
|
||||
l &= ~ENABLEFORCE; /* disable MSTANDBY */
|
||||
musb_writel(musb->mregs, OTG_FORCESTDBY, l);
|
||||
}
|
||||
|
||||
void omap_musb_mailbox(enum omap_musb_vbus_id_status status)
|
||||
{
|
||||
struct omap2430_glue *glue = _glue;
|
||||
|
||||
if (!glue) {
|
||||
pr_err("%s: musb core is not yet initialized\n", __func__);
|
||||
return;
|
||||
}
|
||||
glue->status = status;
|
||||
|
||||
if (!glue_to_musb(glue)) {
|
||||
pr_err("%s: musb core is not yet ready\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
schedule_work(&glue->omap_musb_mailbox_work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(omap_musb_mailbox);
|
||||
|
||||
static void omap_musb_set_mailbox(struct omap2430_glue *glue)
|
||||
{
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = pdata->board_data;
|
||||
struct usb_otg *otg = musb->xceiv->otg;
|
||||
|
||||
switch (glue->status) {
|
||||
case OMAP_MUSB_ID_GROUND:
|
||||
dev_dbg(dev, "ID GND\n");
|
||||
|
||||
otg->default_a = true;
|
||||
musb->xceiv->state = OTG_STATE_A_IDLE;
|
||||
musb->xceiv->last_event = USB_EVENT_ID;
|
||||
if (musb->gadget_driver) {
|
||||
pm_runtime_get_sync(dev);
|
||||
omap_control_usb_set_mode(glue->control_otghs,
|
||||
USB_MODE_HOST);
|
||||
omap2430_musb_set_vbus(musb, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case OMAP_MUSB_VBUS_VALID:
|
||||
dev_dbg(dev, "VBUS Connect\n");
|
||||
|
||||
otg->default_a = false;
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
musb->xceiv->last_event = USB_EVENT_VBUS;
|
||||
if (musb->gadget_driver)
|
||||
pm_runtime_get_sync(dev);
|
||||
omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE);
|
||||
break;
|
||||
|
||||
case OMAP_MUSB_ID_FLOAT:
|
||||
case OMAP_MUSB_VBUS_OFF:
|
||||
dev_dbg(dev, "VBUS Disconnect\n");
|
||||
|
||||
musb->xceiv->last_event = USB_EVENT_NONE;
|
||||
if (musb->gadget_driver) {
|
||||
omap2430_musb_set_vbus(musb, 0);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
}
|
||||
|
||||
if (data->interface_type == MUSB_INTERFACE_UTMI)
|
||||
otg_set_vbus(musb->xceiv->otg, 0);
|
||||
|
||||
omap_control_usb_set_mode(glue->control_otghs,
|
||||
USB_MODE_DISCONNECT);
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "ID float\n");
|
||||
}
|
||||
|
||||
atomic_notifier_call_chain(&musb->xceiv->notifier,
|
||||
musb->xceiv->last_event, NULL);
|
||||
}
|
||||
|
||||
|
||||
static void omap_musb_mailbox_work(struct work_struct *mailbox_work)
|
||||
{
|
||||
struct omap2430_glue *glue = container_of(mailbox_work,
|
||||
struct omap2430_glue, omap_musb_mailbox_work);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
struct device *dev = musb->controller;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
omap_musb_set_mailbox(glue);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
}
|
||||
|
||||
static irqreturn_t omap2430_musb_interrupt(int irq, void *__hci)
|
||||
{
|
||||
unsigned long flags;
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
struct musb *musb = __hci;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
|
||||
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
|
||||
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
|
||||
|
||||
if (musb->int_usb || musb->int_tx || musb->int_rx)
|
||||
retval = musb_interrupt(musb);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int omap2430_musb_init(struct musb *musb)
|
||||
{
|
||||
u32 l;
|
||||
int status = 0;
|
||||
struct device *dev = musb->controller;
|
||||
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = plat->board_data;
|
||||
|
||||
/* We require some kind of external transceiver, hooked
|
||||
* up through ULPI. TWL4030-family PMICs include one,
|
||||
* which needs a driver, drivers aren't always needed.
|
||||
*/
|
||||
if (dev->parent->of_node) {
|
||||
musb->phy = devm_phy_get(dev->parent, "usb2-phy");
|
||||
|
||||
/* We can't totally remove musb->xceiv as of now because
|
||||
* musb core uses xceiv.state and xceiv.otg. Once we have
|
||||
* a separate state machine to handle otg, these can be moved
|
||||
* out of xceiv and then we can start using the generic PHY
|
||||
* framework
|
||||
*/
|
||||
musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent,
|
||||
"usb-phy", 0);
|
||||
} else {
|
||||
musb->xceiv = devm_usb_get_phy_dev(dev, 0);
|
||||
musb->phy = devm_phy_get(dev, "usb");
|
||||
}
|
||||
|
||||
if (IS_ERR(musb->xceiv)) {
|
||||
status = PTR_ERR(musb->xceiv);
|
||||
|
||||
if (status == -ENXIO)
|
||||
return status;
|
||||
|
||||
pr_err("HS USB OTG: no transceiver configured\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
if (IS_ERR(musb->phy)) {
|
||||
pr_err("HS USB OTG: no PHY configured\n");
|
||||
return PTR_ERR(musb->phy);
|
||||
}
|
||||
musb->isr = omap2430_musb_interrupt;
|
||||
|
||||
status = pm_runtime_get_sync(dev);
|
||||
if (status < 0) {
|
||||
dev_err(dev, "pm_runtime_get_sync FAILED %d\n", status);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
l = musb_readl(musb->mregs, OTG_INTERFSEL);
|
||||
|
||||
if (data->interface_type == MUSB_INTERFACE_UTMI) {
|
||||
/* OMAP4 uses Internal PHY GS70 which uses UTMI interface */
|
||||
l &= ~ULPI_12PIN; /* Disable ULPI */
|
||||
l |= UTMI_8BIT; /* Enable UTMI */
|
||||
} else {
|
||||
l |= ULPI_12PIN;
|
||||
}
|
||||
|
||||
musb_writel(musb->mregs, OTG_INTERFSEL, l);
|
||||
|
||||
pr_debug("HS USB OTG: revision 0x%x, sysconfig 0x%02x, "
|
||||
"sysstatus 0x%x, intrfsel 0x%x, simenable 0x%x\n",
|
||||
musb_readl(musb->mregs, OTG_REVISION),
|
||||
musb_readl(musb->mregs, OTG_SYSCONFIG),
|
||||
musb_readl(musb->mregs, OTG_SYSSTATUS),
|
||||
musb_readl(musb->mregs, OTG_INTERFSEL),
|
||||
musb_readl(musb->mregs, OTG_SIMENABLE));
|
||||
|
||||
setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb);
|
||||
|
||||
if (glue->status != OMAP_MUSB_UNKNOWN)
|
||||
omap_musb_set_mailbox(glue);
|
||||
|
||||
phy_init(musb->phy);
|
||||
phy_power_on(musb->phy);
|
||||
|
||||
pm_runtime_put_noidle(musb->controller);
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
return status;
|
||||
}
|
||||
|
||||
static void omap2430_musb_enable(struct musb *musb)
|
||||
{
|
||||
u8 devctl;
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
struct device *dev = musb->controller;
|
||||
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(dev);
|
||||
struct omap_musb_board_data *data = pdata->board_data;
|
||||
|
||||
switch (glue->status) {
|
||||
|
||||
case OMAP_MUSB_ID_GROUND:
|
||||
omap_control_usb_set_mode(glue->control_otghs, USB_MODE_HOST);
|
||||
if (data->interface_type != MUSB_INTERFACE_UTMI)
|
||||
break;
|
||||
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
||||
/* start the session */
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
while (musb_readb(musb->mregs, MUSB_DEVCTL) &
|
||||
MUSB_DEVCTL_BDEVICE) {
|
||||
cpu_relax();
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_err(dev, "configured as A device timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OMAP_MUSB_VBUS_VALID:
|
||||
omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void omap2430_musb_disable(struct musb *musb)
|
||||
{
|
||||
struct device *dev = musb->controller;
|
||||
struct omap2430_glue *glue = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (glue->status != OMAP_MUSB_UNKNOWN)
|
||||
omap_control_usb_set_mode(glue->control_otghs,
|
||||
USB_MODE_DISCONNECT);
|
||||
}
|
||||
|
||||
static int omap2430_musb_exit(struct musb *musb)
|
||||
{
|
||||
del_timer_sync(&musb_idle_timer);
|
||||
|
||||
omap2430_low_level_exit(musb);
|
||||
phy_power_off(musb->phy);
|
||||
phy_exit(musb->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops omap2430_ops = {
|
||||
.init = omap2430_musb_init,
|
||||
.exit = omap2430_musb_exit,
|
||||
|
||||
.set_mode = omap2430_musb_set_mode,
|
||||
.try_idle = omap2430_musb_try_idle,
|
||||
|
||||
.set_vbus = omap2430_musb_set_vbus,
|
||||
|
||||
.enable = omap2430_musb_enable,
|
||||
.disable = omap2430_musb_disable,
|
||||
};
|
||||
|
||||
static u64 omap2430_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int omap2430_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource musb_resources[3];
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct omap_musb_board_data *data;
|
||||
struct platform_device *musb;
|
||||
struct omap2430_glue *glue;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct musb_hdrc_config *config;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
|
||||
if (!musb) {
|
||||
dev_err(&pdev->dev, "failed to allocate musb device\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
musb->dev.parent = &pdev->dev;
|
||||
musb->dev.dma_mask = &omap2430_dmamask;
|
||||
musb->dev.coherent_dma_mask = omap2430_dmamask;
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->musb = musb;
|
||||
glue->status = OMAP_MUSB_UNKNOWN;
|
||||
glue->control_otghs = ERR_PTR(-ENODEV);
|
||||
|
||||
if (np) {
|
||||
struct device_node *control_node;
|
||||
struct platform_device *control_pdev;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to allocate musb platform data\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to allocate musb board data\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL);
|
||||
if (!config) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to allocate musb hdrc config\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
of_property_read_u32(np, "mode", (u32 *)&pdata->mode);
|
||||
of_property_read_u32(np, "interface-type",
|
||||
(u32 *)&data->interface_type);
|
||||
of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps);
|
||||
of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits);
|
||||
of_property_read_u32(np, "power", (u32 *)&pdata->power);
|
||||
config->multipoint = of_property_read_bool(np, "multipoint");
|
||||
|
||||
pdata->board_data = data;
|
||||
pdata->config = config;
|
||||
|
||||
control_node = of_parse_phandle(np, "ctrl-module", 0);
|
||||
if (control_node) {
|
||||
control_pdev = of_find_device_by_node(control_node);
|
||||
if (!control_pdev) {
|
||||
dev_err(&pdev->dev, "Failed to get control device\n");
|
||||
ret = -EINVAL;
|
||||
goto err2;
|
||||
}
|
||||
glue->control_otghs = &control_pdev->dev;
|
||||
}
|
||||
}
|
||||
pdata->platform_ops = &omap2430_ops;
|
||||
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
/*
|
||||
* REVISIT if we ever have two instances of the wrapper, we will be
|
||||
* in big trouble
|
||||
*/
|
||||
_glue = glue;
|
||||
|
||||
INIT_WORK(&glue->omap_musb_mailbox_work, omap_musb_mailbox_work);
|
||||
|
||||
memset(musb_resources, 0x00, sizeof(*musb_resources) *
|
||||
ARRAY_SIZE(musb_resources));
|
||||
|
||||
musb_resources[0].name = pdev->resource[0].name;
|
||||
musb_resources[0].start = pdev->resource[0].start;
|
||||
musb_resources[0].end = pdev->resource[0].end;
|
||||
musb_resources[0].flags = pdev->resource[0].flags;
|
||||
|
||||
musb_resources[1].name = pdev->resource[1].name;
|
||||
musb_resources[1].start = pdev->resource[1].start;
|
||||
musb_resources[1].end = pdev->resource[1].end;
|
||||
musb_resources[1].flags = pdev->resource[1].flags;
|
||||
|
||||
musb_resources[2].name = pdev->resource[2].name;
|
||||
musb_resources[2].start = pdev->resource[2].start;
|
||||
musb_resources[2].end = pdev->resource[2].end;
|
||||
musb_resources[2].flags = pdev->resource[2].flags;
|
||||
|
||||
ret = platform_device_add_resources(musb, musb_resources,
|
||||
ARRAY_SIZE(musb_resources));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add resources\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add platform_data\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = platform_device_add(musb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register musb device\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
platform_device_put(musb);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap2430_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap2430_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_work_sync(&glue->omap_musb_mailbox_work);
|
||||
platform_device_unregister(glue->musb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int omap2430_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct omap2430_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
|
||||
if (musb) {
|
||||
musb->context.otg_interfsel = musb_readl(musb->mregs,
|
||||
OTG_INTERFSEL);
|
||||
|
||||
omap2430_low_level_exit(musb);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap2430_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct omap2430_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
|
||||
if (musb) {
|
||||
omap2430_low_level_init(musb);
|
||||
musb_writel(musb->mregs, OTG_INTERFSEL,
|
||||
musb->context.otg_interfsel);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dev_pm_ops omap2430_pm_ops = {
|
||||
.runtime_suspend = omap2430_runtime_suspend,
|
||||
.runtime_resume = omap2430_runtime_resume,
|
||||
};
|
||||
|
||||
#define DEV_PM_OPS (&omap2430_pm_ops)
|
||||
#else
|
||||
#define DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id omap2430_id_table[] = {
|
||||
{
|
||||
.compatible = "ti,omap4-musb"
|
||||
},
|
||||
{
|
||||
.compatible = "ti,omap3-musb"
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap2430_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver omap2430_driver = {
|
||||
.probe = omap2430_probe,
|
||||
.remove = omap2430_remove,
|
||||
.driver = {
|
||||
.name = "musb-omap2430",
|
||||
.pm = DEV_PM_OPS,
|
||||
.of_match_table = of_match_ptr(omap2430_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("OMAP2PLUS MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
static int __init omap2430_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap2430_driver);
|
||||
}
|
||||
subsys_initcall(omap2430_init);
|
||||
|
||||
static void __exit omap2430_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap2430_driver);
|
||||
}
|
||||
module_exit(omap2430_exit);
|
52
drivers/usb/musb/omap2430.h
Normal file
52
drivers/usb/musb/omap2430.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2006 by Texas Instruments
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MUSB_OMAP243X_H__
|
||||
#define __MUSB_OMAP243X_H__
|
||||
|
||||
#include <linux/platform_data/usb-omap.h>
|
||||
|
||||
/*
|
||||
* OMAP2430-specific definitions
|
||||
*/
|
||||
|
||||
#define OTG_REVISION 0x400
|
||||
|
||||
#define OTG_SYSCONFIG 0x404
|
||||
# define MIDLEMODE 12 /* bit position */
|
||||
# define FORCESTDBY (0 << MIDLEMODE)
|
||||
# define NOSTDBY (1 << MIDLEMODE)
|
||||
# define SMARTSTDBY (2 << MIDLEMODE)
|
||||
|
||||
# define SIDLEMODE 3 /* bit position */
|
||||
# define FORCEIDLE (0 << SIDLEMODE)
|
||||
# define NOIDLE (1 << SIDLEMODE)
|
||||
# define SMARTIDLE (2 << SIDLEMODE)
|
||||
|
||||
# define ENABLEWAKEUP (1 << 2)
|
||||
# define SOFTRST (1 << 1)
|
||||
# define AUTOIDLE (1 << 0)
|
||||
|
||||
#define OTG_SYSSTATUS 0x408
|
||||
# define RESETDONE (1 << 0)
|
||||
|
||||
#define OTG_INTERFSEL 0x40c
|
||||
# define EXTCP (1 << 2)
|
||||
# define PHYSEL 0 /* bit position */
|
||||
# define UTMI_8BIT (0 << PHYSEL)
|
||||
# define ULPI_12PIN (1 << PHYSEL)
|
||||
# define ULPI_8PIN (2 << PHYSEL)
|
||||
|
||||
#define OTG_SIMENABLE 0x410
|
||||
# define TM1 (1 << 0)
|
||||
|
||||
#define OTG_FORCESTDBY 0x414
|
||||
# define ENABLEFORCE (1 << 0)
|
||||
|
||||
#endif /* __MUSB_OMAP243X_H__ */
|
1235
drivers/usb/musb/tusb6010.c
Normal file
1235
drivers/usb/musb/tusb6010.c
Normal file
File diff suppressed because it is too large
Load diff
224
drivers/usb/musb/tusb6010.h
Normal file
224
drivers/usb/musb/tusb6010.h
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Definitions for TUSB6010 USB 2.0 OTG Dual Role controller
|
||||
*
|
||||
* Copyright (C) 2006 Nokia Corporation
|
||||
* Tony Lindgren <tony@atomide.com>
|
||||
*
|
||||
* This program 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.
|
||||
*/
|
||||
|
||||
#ifndef __TUSB6010_H__
|
||||
#define __TUSB6010_H__
|
||||
|
||||
#ifdef CONFIG_USB_TUSB_OMAP_DMA
|
||||
#define tusb_dma_omap() 1
|
||||
#else
|
||||
#define tusb_dma_omap() 0
|
||||
#endif
|
||||
|
||||
/* VLYNQ control register. 32-bit at offset 0x000 */
|
||||
#define TUSB_VLYNQ_CTRL 0x004
|
||||
|
||||
/* Mentor Graphics OTG core registers. 8,- 16- and 32-bit at offset 0x400 */
|
||||
#define TUSB_BASE_OFFSET 0x400
|
||||
|
||||
/* FIFO registers 32-bit at offset 0x600 */
|
||||
#define TUSB_FIFO_BASE 0x600
|
||||
|
||||
/* Device System & Control registers. 32-bit at offset 0x800 */
|
||||
#define TUSB_SYS_REG_BASE 0x800
|
||||
|
||||
#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000)
|
||||
#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16)
|
||||
#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15)
|
||||
#define TUSB_DEV_CONF_SOFT_ID (1 << 1)
|
||||
#define TUSB_DEV_CONF_ID_SEL (1 << 0)
|
||||
|
||||
#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004)
|
||||
#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008)
|
||||
#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24)
|
||||
#define TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP (1 << 23)
|
||||
#define TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN (1 << 19)
|
||||
#define TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN (1 << 18)
|
||||
#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17)
|
||||
#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16)
|
||||
#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15)
|
||||
#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14)
|
||||
#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13)
|
||||
#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12)
|
||||
#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11)
|
||||
#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10)
|
||||
#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9)
|
||||
#define TUSB_PHY_OTG_CTRL_PHYREF_CLKSEL(v) (((v) & 3) << 7)
|
||||
#define TUSB_PHY_OTG_CTRL_PD (1 << 6)
|
||||
#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5)
|
||||
#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4)
|
||||
#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3)
|
||||
#define TUSB_PHY_OTG_CTRL_RESET (1 << 2)
|
||||
#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1)
|
||||
#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0)
|
||||
|
||||
/*OTG status register */
|
||||
#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c)
|
||||
#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8)
|
||||
#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7)
|
||||
#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6)
|
||||
#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5)
|
||||
#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4)
|
||||
#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3)
|
||||
#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2)
|
||||
#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0)
|
||||
#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1)
|
||||
#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0)
|
||||
|
||||
#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010)
|
||||
# define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31)
|
||||
# define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff)
|
||||
#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014)
|
||||
|
||||
/* PRCM configuration register */
|
||||
#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018)
|
||||
#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24)
|
||||
#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16)
|
||||
|
||||
/* PRCM management register */
|
||||
#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c)
|
||||
#define TUSB_PRCM_MNGMT_SRP_FIX_TIMER(v) (((v) & 0xf) << 25)
|
||||
#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24)
|
||||
#define TUSB_PRCM_MNGMT_VBUS_VALID_TIMER(v) (((v) & 0xf) << 20)
|
||||
#define TUSB_PRCM_MNGMT_VBUS_VALID_FLT_EN (1 << 19)
|
||||
#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18)
|
||||
#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17)
|
||||
#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10)
|
||||
#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9)
|
||||
#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8)
|
||||
#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4)
|
||||
#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3)
|
||||
#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2)
|
||||
#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1)
|
||||
#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0)
|
||||
|
||||
/* Wake-up source clear and mask registers */
|
||||
#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020)
|
||||
#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028)
|
||||
#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c)
|
||||
#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13)
|
||||
#define TUSB_PRCM_WGPIO_7 (1 << 12)
|
||||
#define TUSB_PRCM_WGPIO_6 (1 << 11)
|
||||
#define TUSB_PRCM_WGPIO_5 (1 << 10)
|
||||
#define TUSB_PRCM_WGPIO_4 (1 << 9)
|
||||
#define TUSB_PRCM_WGPIO_3 (1 << 8)
|
||||
#define TUSB_PRCM_WGPIO_2 (1 << 7)
|
||||
#define TUSB_PRCM_WGPIO_1 (1 << 6)
|
||||
#define TUSB_PRCM_WGPIO_0 (1 << 5)
|
||||
#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */
|
||||
#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */
|
||||
#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */
|
||||
#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */
|
||||
#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */
|
||||
|
||||
#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030)
|
||||
#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034)
|
||||
#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038)
|
||||
#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c)
|
||||
#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040)
|
||||
#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044)
|
||||
#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048)
|
||||
#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c)
|
||||
#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050)
|
||||
#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054)
|
||||
#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058)
|
||||
#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c)
|
||||
#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060)
|
||||
#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064)
|
||||
#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068)
|
||||
#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c)
|
||||
|
||||
/* NOR flash interrupt source registers */
|
||||
#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070)
|
||||
#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074)
|
||||
#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078)
|
||||
#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c)
|
||||
#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24)
|
||||
#define TUSB_INT_SRC_USB_IP_CORE (1 << 17)
|
||||
#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16)
|
||||
#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15)
|
||||
#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14)
|
||||
#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13)
|
||||
#define TUSB_INT_SRC_DEV_READY (1 << 12)
|
||||
#define TUSB_INT_SRC_USB_IP_TX (1 << 9)
|
||||
#define TUSB_INT_SRC_USB_IP_RX (1 << 8)
|
||||
#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7)
|
||||
#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6)
|
||||
#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5)
|
||||
#define TUSB_INT_SRC_USB_IP_CONN (1 << 4)
|
||||
#define TUSB_INT_SRC_USB_IP_SOF (1 << 3)
|
||||
#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2)
|
||||
#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1)
|
||||
#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0)
|
||||
|
||||
/* NOR flash interrupt registers reserved bits. Must be written as 0 */
|
||||
#define TUSB_INT_MASK_RESERVED_17 (0x3fff << 17)
|
||||
#define TUSB_INT_MASK_RESERVED_13 (1 << 13)
|
||||
#define TUSB_INT_MASK_RESERVED_8 (0xf << 8)
|
||||
#define TUSB_INT_SRC_RESERVED_26 (0x1f << 26)
|
||||
#define TUSB_INT_SRC_RESERVED_18 (0x3f << 18)
|
||||
#define TUSB_INT_SRC_RESERVED_10 (0x03 << 10)
|
||||
|
||||
/* Reserved bits for NOR flash interrupt mask and clear register */
|
||||
#define TUSB_INT_MASK_RESERVED_BITS (TUSB_INT_MASK_RESERVED_17 | \
|
||||
TUSB_INT_MASK_RESERVED_13 | \
|
||||
TUSB_INT_MASK_RESERVED_8)
|
||||
|
||||
/* Reserved bits for NOR flash interrupt status register */
|
||||
#define TUSB_INT_SRC_RESERVED_BITS (TUSB_INT_SRC_RESERVED_26 | \
|
||||
TUSB_INT_SRC_RESERVED_18 | \
|
||||
TUSB_INT_SRC_RESERVED_10)
|
||||
|
||||
#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080)
|
||||
#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084)
|
||||
#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100)
|
||||
#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104)
|
||||
#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108)
|
||||
#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148)
|
||||
|
||||
/* Offsets from each ep base register */
|
||||
#define TUSB_EP_TX_OFFSET 0x10c /* EP_IN in docs */
|
||||
#define TUSB_EP_RX_OFFSET 0x14c /* EP_OUT in docs */
|
||||
#define TUSB_EP_MAX_PACKET_SIZE_OFFSET 0x188
|
||||
|
||||
#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8)
|
||||
#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4)
|
||||
#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8)
|
||||
|
||||
/* Device System & Control register bitfields */
|
||||
#define TUSB_INT_CTRL_CONF_INT_RELCYC(v) (((v) & 0x7) << 18)
|
||||
#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17)
|
||||
#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16)
|
||||
#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24)
|
||||
#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26)
|
||||
#define TUSB_DMA_REQ_CONF_DMA_REQ_EN(v) (((v) & 0x3f) << 20)
|
||||
#define TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(v) (((v) & 0xf) << 16)
|
||||
#define TUSB_EP0_CONFIG_SW_EN (1 << 8)
|
||||
#define TUSB_EP0_CONFIG_DIR_TX (1 << 7)
|
||||
#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f)
|
||||
#define TUSB_EP_CONFIG_SW_EN (1 << 31)
|
||||
#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff)
|
||||
#define TUSB_PROD_TEST_RESET_VAL 0xa596
|
||||
#define TUSB_EP_FIFO(ep) (TUSB_FIFO_BASE + (ep) * 0x20)
|
||||
|
||||
#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8)
|
||||
#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc)
|
||||
#define TUSB_DIDR1_HI_CHIP_REV(v) (((v) >> 17) & 0xf)
|
||||
#define TUSB_DIDR1_HI_REV_20 0
|
||||
#define TUSB_DIDR1_HI_REV_30 1
|
||||
#define TUSB_DIDR1_HI_REV_31 2
|
||||
|
||||
#define TUSB_REV_10 0x10
|
||||
#define TUSB_REV_20 0x20
|
||||
#define TUSB_REV_30 0x30
|
||||
#define TUSB_REV_31 0x31
|
||||
|
||||
#endif /* __TUSB6010_H__ */
|
707
drivers/usb/musb/tusb6010_omap.c
Normal file
707
drivers/usb/musb/tusb6010_omap.c
Normal file
|
@ -0,0 +1,707 @@
|
|||
/*
|
||||
* TUSB6010 USB 2.0 OTG Dual Role controller OMAP DMA interface
|
||||
*
|
||||
* Copyright (C) 2006 Nokia Corporation
|
||||
* Tony Lindgren <tony@atomide.com>
|
||||
*
|
||||
* This program 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.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/omap-dma.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
#include "tusb6010.h"
|
||||
|
||||
#define to_chdat(c) ((struct tusb_omap_dma_ch *)(c)->private_data)
|
||||
|
||||
#define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */
|
||||
|
||||
#define OMAP24XX_DMA_EXT_DMAREQ0 2
|
||||
#define OMAP24XX_DMA_EXT_DMAREQ1 3
|
||||
#define OMAP242X_DMA_EXT_DMAREQ2 14
|
||||
#define OMAP242X_DMA_EXT_DMAREQ3 15
|
||||
#define OMAP242X_DMA_EXT_DMAREQ4 16
|
||||
#define OMAP242X_DMA_EXT_DMAREQ5 64
|
||||
|
||||
struct tusb_omap_dma_ch {
|
||||
struct musb *musb;
|
||||
void __iomem *tbase;
|
||||
unsigned long phys_offset;
|
||||
int epnum;
|
||||
u8 tx;
|
||||
struct musb_hw_ep *hw_ep;
|
||||
|
||||
int ch;
|
||||
s8 dmareq;
|
||||
s8 sync_dev;
|
||||
|
||||
struct tusb_omap_dma *tusb_dma;
|
||||
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
u32 len;
|
||||
u16 packet_sz;
|
||||
u16 transfer_packet_sz;
|
||||
u32 transfer_len;
|
||||
u32 completed_len;
|
||||
};
|
||||
|
||||
struct tusb_omap_dma {
|
||||
struct dma_controller controller;
|
||||
struct musb *musb;
|
||||
void __iomem *tbase;
|
||||
|
||||
int ch;
|
||||
s8 dmareq;
|
||||
s8 sync_dev;
|
||||
unsigned multichannel:1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Allocate dmareq0 to the current channel unless it's already taken
|
||||
*/
|
||||
static inline int tusb_omap_use_shared_dmareq(struct tusb_omap_dma_ch *chdat)
|
||||
{
|
||||
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
|
||||
|
||||
if (reg != 0) {
|
||||
dev_dbg(chdat->musb->controller, "ep%i dmareq0 is busy for ep%i\n",
|
||||
chdat->epnum, reg & 0xf);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (chdat->tx)
|
||||
reg = (1 << 4) | chdat->epnum;
|
||||
else
|
||||
reg = chdat->epnum;
|
||||
|
||||
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat)
|
||||
{
|
||||
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
|
||||
|
||||
if ((reg & 0xf) != chdat->epnum) {
|
||||
printk(KERN_ERR "ep%i trying to release dmareq0 for ep%i\n",
|
||||
chdat->epnum, reg & 0xf);
|
||||
return;
|
||||
}
|
||||
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in
|
||||
* musb_gadget.c.
|
||||
*/
|
||||
static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data)
|
||||
{
|
||||
struct dma_channel *channel = (struct dma_channel *)data;
|
||||
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
|
||||
struct tusb_omap_dma *tusb_dma = chdat->tusb_dma;
|
||||
struct musb *musb = chdat->musb;
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hw_ep *hw_ep = chdat->hw_ep;
|
||||
void __iomem *ep_conf = hw_ep->conf;
|
||||
void __iomem *mbase = musb->mregs;
|
||||
unsigned long remaining, flags, pio;
|
||||
int ch;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
if (tusb_dma->multichannel)
|
||||
ch = chdat->ch;
|
||||
else
|
||||
ch = tusb_dma->ch;
|
||||
|
||||
if (ch_status != OMAP_DMA_BLOCK_IRQ)
|
||||
printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status);
|
||||
|
||||
dev_dbg(musb->controller, "ep%i %s dma callback ch: %i status: %x\n",
|
||||
chdat->epnum, chdat->tx ? "tx" : "rx",
|
||||
ch, ch_status);
|
||||
|
||||
if (chdat->tx)
|
||||
remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET);
|
||||
else
|
||||
remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET);
|
||||
|
||||
remaining = TUSB_EP_CONFIG_XFR_SIZE(remaining);
|
||||
|
||||
/* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */
|
||||
if (unlikely(remaining > chdat->transfer_len)) {
|
||||
dev_dbg(musb->controller, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n",
|
||||
chdat->tx ? "tx" : "rx", chdat->ch,
|
||||
remaining);
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
channel->actual_len = chdat->transfer_len - remaining;
|
||||
pio = chdat->len - channel->actual_len;
|
||||
|
||||
dev_dbg(musb->controller, "DMA remaining %lu/%u\n", remaining, chdat->transfer_len);
|
||||
|
||||
/* Transfer remaining 1 - 31 bytes */
|
||||
if (pio > 0 && pio < 32) {
|
||||
u8 *buf;
|
||||
|
||||
dev_dbg(musb->controller, "Using PIO for remaining %lu bytes\n", pio);
|
||||
buf = phys_to_virt((u32)chdat->dma_addr) + chdat->transfer_len;
|
||||
if (chdat->tx) {
|
||||
dma_unmap_single(dev, chdat->dma_addr,
|
||||
chdat->transfer_len,
|
||||
DMA_TO_DEVICE);
|
||||
musb_write_fifo(hw_ep, pio, buf);
|
||||
} else {
|
||||
dma_unmap_single(dev, chdat->dma_addr,
|
||||
chdat->transfer_len,
|
||||
DMA_FROM_DEVICE);
|
||||
musb_read_fifo(hw_ep, pio, buf);
|
||||
}
|
||||
channel->actual_len += pio;
|
||||
}
|
||||
|
||||
if (!tusb_dma->multichannel)
|
||||
tusb_omap_free_shared_dmareq(chdat);
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
|
||||
/* Handle only RX callbacks here. TX callbacks must be handled based
|
||||
* on the TUSB DMA status interrupt.
|
||||
* REVISIT: Use both TUSB DMA status interrupt and OMAP DMA callback
|
||||
* interrupt for RX and TX.
|
||||
*/
|
||||
if (!chdat->tx)
|
||||
musb_dma_completion(musb, chdat->epnum, chdat->tx);
|
||||
|
||||
/* We must terminate short tx transfers manually by setting TXPKTRDY.
|
||||
* REVISIT: This same problem may occur with other MUSB dma as well.
|
||||
* Easy to test with g_ether by pinging the MUSB board with ping -s54.
|
||||
*/
|
||||
if ((chdat->transfer_len < chdat->packet_sz)
|
||||
|| (chdat->transfer_len % chdat->packet_sz != 0)) {
|
||||
u16 csr;
|
||||
|
||||
if (chdat->tx) {
|
||||
dev_dbg(musb->controller, "terminating short tx packet\n");
|
||||
musb_ep_select(mbase, chdat->epnum);
|
||||
csr = musb_readw(hw_ep->regs, MUSB_TXCSR);
|
||||
csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY
|
||||
| MUSB_TXCSR_P_WZC_BITS;
|
||||
musb_writew(hw_ep->regs, MUSB_TXCSR, csr);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
}
|
||||
|
||||
static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz,
|
||||
u8 rndis_mode, dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
|
||||
struct tusb_omap_dma *tusb_dma = chdat->tusb_dma;
|
||||
struct musb *musb = chdat->musb;
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hw_ep *hw_ep = chdat->hw_ep;
|
||||
void __iomem *mbase = musb->mregs;
|
||||
void __iomem *ep_conf = hw_ep->conf;
|
||||
dma_addr_t fifo = hw_ep->fifo_sync;
|
||||
struct omap_dma_channel_params dma_params;
|
||||
u32 dma_remaining;
|
||||
int src_burst, dst_burst;
|
||||
u16 csr;
|
||||
int ch;
|
||||
s8 dmareq;
|
||||
s8 sync_dev;
|
||||
|
||||
if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* HW issue #10: Async dma will eventually corrupt the XFR_SIZE
|
||||
* register which will cause missed DMA interrupt. We could try to
|
||||
* use a timer for the callback, but it is unsafe as the XFR_SIZE
|
||||
* register is corrupt, and we won't know if the DMA worked.
|
||||
*/
|
||||
if (dma_addr & 0x2)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Because of HW issue #10, it seems like mixing sync DMA and async
|
||||
* PIO access can confuse the DMA. Make sure XFR_SIZE is reset before
|
||||
* using the channel for DMA.
|
||||
*/
|
||||
if (chdat->tx)
|
||||
dma_remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET);
|
||||
else
|
||||
dma_remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET);
|
||||
|
||||
dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining);
|
||||
if (dma_remaining) {
|
||||
dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n",
|
||||
chdat->tx ? "tx" : "rx", chdat->ch,
|
||||
dma_remaining);
|
||||
return false;
|
||||
}
|
||||
|
||||
chdat->transfer_len = len & ~0x1f;
|
||||
|
||||
if (len < packet_sz)
|
||||
chdat->transfer_packet_sz = chdat->transfer_len;
|
||||
else
|
||||
chdat->transfer_packet_sz = packet_sz;
|
||||
|
||||
if (tusb_dma->multichannel) {
|
||||
ch = chdat->ch;
|
||||
dmareq = chdat->dmareq;
|
||||
sync_dev = chdat->sync_dev;
|
||||
} else {
|
||||
if (tusb_omap_use_shared_dmareq(chdat) != 0) {
|
||||
dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum);
|
||||
return false;
|
||||
}
|
||||
if (tusb_dma->ch < 0) {
|
||||
/* REVISIT: This should get blocked earlier, happens
|
||||
* with MSC ErrorRecoveryTest
|
||||
*/
|
||||
WARN_ON(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
ch = tusb_dma->ch;
|
||||
dmareq = tusb_dma->dmareq;
|
||||
sync_dev = tusb_dma->sync_dev;
|
||||
omap_set_dma_callback(ch, tusb_omap_dma_cb, channel);
|
||||
}
|
||||
|
||||
chdat->packet_sz = packet_sz;
|
||||
chdat->len = len;
|
||||
channel->actual_len = 0;
|
||||
chdat->dma_addr = dma_addr;
|
||||
channel->status = MUSB_DMA_STATUS_BUSY;
|
||||
|
||||
/* Since we're recycling dma areas, we need to clean or invalidate */
|
||||
if (chdat->tx)
|
||||
dma_map_single(dev, phys_to_virt(dma_addr), len,
|
||||
DMA_TO_DEVICE);
|
||||
else
|
||||
dma_map_single(dev, phys_to_virt(dma_addr), len,
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
/* Use 16-bit transfer if dma_addr is not 32-bit aligned */
|
||||
if ((dma_addr & 0x3) == 0) {
|
||||
dma_params.data_type = OMAP_DMA_DATA_TYPE_S32;
|
||||
dma_params.elem_count = 8; /* Elements in frame */
|
||||
} else {
|
||||
dma_params.data_type = OMAP_DMA_DATA_TYPE_S16;
|
||||
dma_params.elem_count = 16; /* Elements in frame */
|
||||
fifo = hw_ep->fifo_async;
|
||||
}
|
||||
|
||||
dma_params.frame_count = chdat->transfer_len / 32; /* Burst sz frame */
|
||||
|
||||
dev_dbg(musb->controller, "ep%i %s dma ch%i dma: %08x len: %u(%u) packet_sz: %i(%i)\n",
|
||||
chdat->epnum, chdat->tx ? "tx" : "rx",
|
||||
ch, dma_addr, chdat->transfer_len, len,
|
||||
chdat->transfer_packet_sz, packet_sz);
|
||||
|
||||
/*
|
||||
* Prepare omap DMA for transfer
|
||||
*/
|
||||
if (chdat->tx) {
|
||||
dma_params.src_amode = OMAP_DMA_AMODE_POST_INC;
|
||||
dma_params.src_start = (unsigned long)dma_addr;
|
||||
dma_params.src_ei = 0;
|
||||
dma_params.src_fi = 0;
|
||||
|
||||
dma_params.dst_amode = OMAP_DMA_AMODE_DOUBLE_IDX;
|
||||
dma_params.dst_start = (unsigned long)fifo;
|
||||
dma_params.dst_ei = 1;
|
||||
dma_params.dst_fi = -31; /* Loop 32 byte window */
|
||||
|
||||
dma_params.trigger = sync_dev;
|
||||
dma_params.sync_mode = OMAP_DMA_SYNC_FRAME;
|
||||
dma_params.src_or_dst_synch = 0; /* Dest sync */
|
||||
|
||||
src_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 read */
|
||||
dst_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 write */
|
||||
} else {
|
||||
dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX;
|
||||
dma_params.src_start = (unsigned long)fifo;
|
||||
dma_params.src_ei = 1;
|
||||
dma_params.src_fi = -31; /* Loop 32 byte window */
|
||||
|
||||
dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC;
|
||||
dma_params.dst_start = (unsigned long)dma_addr;
|
||||
dma_params.dst_ei = 0;
|
||||
dma_params.dst_fi = 0;
|
||||
|
||||
dma_params.trigger = sync_dev;
|
||||
dma_params.sync_mode = OMAP_DMA_SYNC_FRAME;
|
||||
dma_params.src_or_dst_synch = 1; /* Source sync */
|
||||
|
||||
src_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 read */
|
||||
dst_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 write */
|
||||
}
|
||||
|
||||
dev_dbg(musb->controller, "ep%i %s using %i-bit %s dma from 0x%08lx to 0x%08lx\n",
|
||||
chdat->epnum, chdat->tx ? "tx" : "rx",
|
||||
(dma_params.data_type == OMAP_DMA_DATA_TYPE_S32) ? 32 : 16,
|
||||
((dma_addr & 0x3) == 0) ? "sync" : "async",
|
||||
dma_params.src_start, dma_params.dst_start);
|
||||
|
||||
omap_set_dma_params(ch, &dma_params);
|
||||
omap_set_dma_src_burst_mode(ch, src_burst);
|
||||
omap_set_dma_dest_burst_mode(ch, dst_burst);
|
||||
omap_set_dma_write_mode(ch, OMAP_DMA_WRITE_LAST_NON_POSTED);
|
||||
|
||||
/*
|
||||
* Prepare MUSB for DMA transfer
|
||||
*/
|
||||
if (chdat->tx) {
|
||||
musb_ep_select(mbase, chdat->epnum);
|
||||
csr = musb_readw(hw_ep->regs, MUSB_TXCSR);
|
||||
csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB
|
||||
| MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE);
|
||||
csr &= ~MUSB_TXCSR_P_UNDERRUN;
|
||||
musb_writew(hw_ep->regs, MUSB_TXCSR, csr);
|
||||
} else {
|
||||
musb_ep_select(mbase, chdat->epnum);
|
||||
csr = musb_readw(hw_ep->regs, MUSB_RXCSR);
|
||||
csr |= MUSB_RXCSR_DMAENAB;
|
||||
csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE);
|
||||
musb_writew(hw_ep->regs, MUSB_RXCSR,
|
||||
csr | MUSB_RXCSR_P_WZC_BITS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start DMA transfer
|
||||
*/
|
||||
omap_start_dma(ch);
|
||||
|
||||
if (chdat->tx) {
|
||||
/* Send transfer_packet_sz packets at a time */
|
||||
musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET,
|
||||
chdat->transfer_packet_sz);
|
||||
|
||||
musb_writel(ep_conf, TUSB_EP_TX_OFFSET,
|
||||
TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len));
|
||||
} else {
|
||||
/* Receive transfer_packet_sz packets at a time */
|
||||
musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET,
|
||||
chdat->transfer_packet_sz << 16);
|
||||
|
||||
musb_writel(ep_conf, TUSB_EP_RX_OFFSET,
|
||||
TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int tusb_omap_dma_abort(struct dma_channel *channel)
|
||||
{
|
||||
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
|
||||
struct tusb_omap_dma *tusb_dma = chdat->tusb_dma;
|
||||
|
||||
if (!tusb_dma->multichannel) {
|
||||
if (tusb_dma->ch >= 0) {
|
||||
omap_stop_dma(tusb_dma->ch);
|
||||
omap_free_dma(tusb_dma->ch);
|
||||
tusb_dma->ch = -1;
|
||||
}
|
||||
|
||||
tusb_dma->dmareq = -1;
|
||||
tusb_dma->sync_dev = -1;
|
||||
}
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat)
|
||||
{
|
||||
u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
|
||||
int i, dmareq_nr = -1;
|
||||
|
||||
const int sync_dev[6] = {
|
||||
OMAP24XX_DMA_EXT_DMAREQ0,
|
||||
OMAP24XX_DMA_EXT_DMAREQ1,
|
||||
OMAP242X_DMA_EXT_DMAREQ2,
|
||||
OMAP242X_DMA_EXT_DMAREQ3,
|
||||
OMAP242X_DMA_EXT_DMAREQ4,
|
||||
OMAP242X_DMA_EXT_DMAREQ5,
|
||||
};
|
||||
|
||||
for (i = 0; i < MAX_DMAREQ; i++) {
|
||||
int cur = (reg & (0xf << (i * 5))) >> (i * 5);
|
||||
if (cur == 0) {
|
||||
dmareq_nr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmareq_nr == -1)
|
||||
return -EAGAIN;
|
||||
|
||||
reg |= (chdat->epnum << (dmareq_nr * 5));
|
||||
if (chdat->tx)
|
||||
reg |= ((1 << 4) << (dmareq_nr * 5));
|
||||
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
|
||||
|
||||
chdat->dmareq = dmareq_nr;
|
||||
chdat->sync_dev = sync_dev[chdat->dmareq];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (!chdat || chdat->dmareq < 0)
|
||||
return;
|
||||
|
||||
reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP);
|
||||
reg &= ~(0x1f << (chdat->dmareq * 5));
|
||||
musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg);
|
||||
|
||||
chdat->dmareq = -1;
|
||||
chdat->sync_dev = -1;
|
||||
}
|
||||
|
||||
static struct dma_channel *dma_channel_pool[MAX_DMAREQ];
|
||||
|
||||
static struct dma_channel *
|
||||
tusb_omap_dma_allocate(struct dma_controller *c,
|
||||
struct musb_hw_ep *hw_ep,
|
||||
u8 tx)
|
||||
{
|
||||
int ret, i;
|
||||
const char *dev_name;
|
||||
struct tusb_omap_dma *tusb_dma;
|
||||
struct musb *musb;
|
||||
void __iomem *tbase;
|
||||
struct dma_channel *channel = NULL;
|
||||
struct tusb_omap_dma_ch *chdat = NULL;
|
||||
u32 reg;
|
||||
|
||||
tusb_dma = container_of(c, struct tusb_omap_dma, controller);
|
||||
musb = tusb_dma->musb;
|
||||
tbase = musb->ctrl_base;
|
||||
|
||||
reg = musb_readl(tbase, TUSB_DMA_INT_MASK);
|
||||
if (tx)
|
||||
reg &= ~(1 << hw_ep->epnum);
|
||||
else
|
||||
reg &= ~(1 << (hw_ep->epnum + 15));
|
||||
musb_writel(tbase, TUSB_DMA_INT_MASK, reg);
|
||||
|
||||
/* REVISIT: Why does dmareq5 not work? */
|
||||
if (hw_ep->epnum == 0) {
|
||||
dev_dbg(musb->controller, "Not allowing DMA for ep0 %s\n", tx ? "tx" : "rx");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_DMAREQ; i++) {
|
||||
struct dma_channel *ch = dma_channel_pool[i];
|
||||
if (ch->status == MUSB_DMA_STATUS_UNKNOWN) {
|
||||
ch->status = MUSB_DMA_STATUS_FREE;
|
||||
channel = ch;
|
||||
chdat = ch->private_data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel)
|
||||
return NULL;
|
||||
|
||||
if (tx) {
|
||||
chdat->tx = 1;
|
||||
dev_name = "TUSB transmit";
|
||||
} else {
|
||||
chdat->tx = 0;
|
||||
dev_name = "TUSB receive";
|
||||
}
|
||||
|
||||
chdat->musb = tusb_dma->musb;
|
||||
chdat->tbase = tusb_dma->tbase;
|
||||
chdat->hw_ep = hw_ep;
|
||||
chdat->epnum = hw_ep->epnum;
|
||||
chdat->dmareq = -1;
|
||||
chdat->completed_len = 0;
|
||||
chdat->tusb_dma = tusb_dma;
|
||||
|
||||
channel->max_len = 0x7fffffff;
|
||||
channel->desired_mode = 0;
|
||||
channel->actual_len = 0;
|
||||
|
||||
if (tusb_dma->multichannel) {
|
||||
ret = tusb_omap_dma_allocate_dmareq(chdat);
|
||||
if (ret != 0)
|
||||
goto free_dmareq;
|
||||
|
||||
ret = omap_request_dma(chdat->sync_dev, dev_name,
|
||||
tusb_omap_dma_cb, channel, &chdat->ch);
|
||||
if (ret != 0)
|
||||
goto free_dmareq;
|
||||
} else if (tusb_dma->ch == -1) {
|
||||
tusb_dma->dmareq = 0;
|
||||
tusb_dma->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0;
|
||||
|
||||
/* Callback data gets set later in the shared dmareq case */
|
||||
ret = omap_request_dma(tusb_dma->sync_dev, "TUSB shared",
|
||||
tusb_omap_dma_cb, NULL, &tusb_dma->ch);
|
||||
if (ret != 0)
|
||||
goto free_dmareq;
|
||||
|
||||
chdat->dmareq = -1;
|
||||
chdat->ch = -1;
|
||||
}
|
||||
|
||||
dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n",
|
||||
chdat->epnum,
|
||||
chdat->tx ? "tx" : "rx",
|
||||
chdat->ch >= 0 ? "dedicated" : "shared",
|
||||
chdat->ch >= 0 ? chdat->ch : tusb_dma->ch,
|
||||
chdat->dmareq >= 0 ? chdat->dmareq : tusb_dma->dmareq,
|
||||
chdat->sync_dev >= 0 ? chdat->sync_dev : tusb_dma->sync_dev);
|
||||
|
||||
return channel;
|
||||
|
||||
free_dmareq:
|
||||
tusb_omap_dma_free_dmareq(chdat);
|
||||
|
||||
dev_dbg(musb->controller, "ep%i: Could not get a DMA channel\n", chdat->epnum);
|
||||
channel->status = MUSB_DMA_STATUS_UNKNOWN;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tusb_omap_dma_release(struct dma_channel *channel)
|
||||
{
|
||||
struct tusb_omap_dma_ch *chdat = to_chdat(channel);
|
||||
struct musb *musb = chdat->musb;
|
||||
void __iomem *tbase = musb->ctrl_base;
|
||||
u32 reg;
|
||||
|
||||
dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, chdat->ch);
|
||||
|
||||
reg = musb_readl(tbase, TUSB_DMA_INT_MASK);
|
||||
if (chdat->tx)
|
||||
reg |= (1 << chdat->epnum);
|
||||
else
|
||||
reg |= (1 << (chdat->epnum + 15));
|
||||
musb_writel(tbase, TUSB_DMA_INT_MASK, reg);
|
||||
|
||||
reg = musb_readl(tbase, TUSB_DMA_INT_CLEAR);
|
||||
if (chdat->tx)
|
||||
reg |= (1 << chdat->epnum);
|
||||
else
|
||||
reg |= (1 << (chdat->epnum + 15));
|
||||
musb_writel(tbase, TUSB_DMA_INT_CLEAR, reg);
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_UNKNOWN;
|
||||
|
||||
if (chdat->ch >= 0) {
|
||||
omap_stop_dma(chdat->ch);
|
||||
omap_free_dma(chdat->ch);
|
||||
chdat->ch = -1;
|
||||
}
|
||||
|
||||
if (chdat->dmareq >= 0)
|
||||
tusb_omap_dma_free_dmareq(chdat);
|
||||
|
||||
channel = NULL;
|
||||
}
|
||||
|
||||
void dma_controller_destroy(struct dma_controller *c)
|
||||
{
|
||||
struct tusb_omap_dma *tusb_dma;
|
||||
int i;
|
||||
|
||||
tusb_dma = container_of(c, struct tusb_omap_dma, controller);
|
||||
for (i = 0; i < MAX_DMAREQ; i++) {
|
||||
struct dma_channel *ch = dma_channel_pool[i];
|
||||
if (ch) {
|
||||
kfree(ch->private_data);
|
||||
kfree(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (tusb_dma && !tusb_dma->multichannel && tusb_dma->ch >= 0)
|
||||
omap_free_dma(tusb_dma->ch);
|
||||
|
||||
kfree(tusb_dma);
|
||||
}
|
||||
|
||||
struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base)
|
||||
{
|
||||
void __iomem *tbase = musb->ctrl_base;
|
||||
struct tusb_omap_dma *tusb_dma;
|
||||
int i;
|
||||
|
||||
/* REVISIT: Get dmareq lines used from board-*.c */
|
||||
|
||||
musb_writel(musb->ctrl_base, TUSB_DMA_INT_MASK, 0x7fffffff);
|
||||
musb_writel(musb->ctrl_base, TUSB_DMA_EP_MAP, 0);
|
||||
|
||||
musb_writel(tbase, TUSB_DMA_REQ_CONF,
|
||||
TUSB_DMA_REQ_CONF_BURST_SIZE(2)
|
||||
| TUSB_DMA_REQ_CONF_DMA_REQ_EN(0x3f)
|
||||
| TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(2));
|
||||
|
||||
tusb_dma = kzalloc(sizeof(struct tusb_omap_dma), GFP_KERNEL);
|
||||
if (!tusb_dma)
|
||||
goto out;
|
||||
|
||||
tusb_dma->musb = musb;
|
||||
tusb_dma->tbase = musb->ctrl_base;
|
||||
|
||||
tusb_dma->ch = -1;
|
||||
tusb_dma->dmareq = -1;
|
||||
tusb_dma->sync_dev = -1;
|
||||
|
||||
tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate;
|
||||
tusb_dma->controller.channel_release = tusb_omap_dma_release;
|
||||
tusb_dma->controller.channel_program = tusb_omap_dma_program;
|
||||
tusb_dma->controller.channel_abort = tusb_omap_dma_abort;
|
||||
|
||||
if (musb->tusb_revision >= TUSB_REV_30)
|
||||
tusb_dma->multichannel = 1;
|
||||
|
||||
for (i = 0; i < MAX_DMAREQ; i++) {
|
||||
struct dma_channel *ch;
|
||||
struct tusb_omap_dma_ch *chdat;
|
||||
|
||||
ch = kzalloc(sizeof(struct dma_channel), GFP_KERNEL);
|
||||
if (!ch)
|
||||
goto cleanup;
|
||||
|
||||
dma_channel_pool[i] = ch;
|
||||
|
||||
chdat = kzalloc(sizeof(struct tusb_omap_dma_ch), GFP_KERNEL);
|
||||
if (!chdat)
|
||||
goto cleanup;
|
||||
|
||||
ch->status = MUSB_DMA_STATUS_UNKNOWN;
|
||||
ch->private_data = chdat;
|
||||
}
|
||||
|
||||
return &tusb_dma->controller;
|
||||
|
||||
cleanup:
|
||||
dma_controller_destroy(&tusb_dma->controller);
|
||||
out:
|
||||
return NULL;
|
||||
}
|
391
drivers/usb/musb/ux500.c
Normal file
391
drivers/usb/musb/ux500.c
Normal file
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* Copyright (C) 2010 ST-Ericsson AB
|
||||
* Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
|
||||
*
|
||||
* Based on omap2430.c
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/usb/musb-ux500.h>
|
||||
|
||||
#include "musb_core.h"
|
||||
|
||||
static struct musb_hdrc_config ux500_musb_hdrc_config = {
|
||||
.multipoint = true,
|
||||
.dyn_fifo = true,
|
||||
.num_eps = 16,
|
||||
.ram_bits = 16,
|
||||
};
|
||||
|
||||
struct ux500_glue {
|
||||
struct device *dev;
|
||||
struct platform_device *musb;
|
||||
struct clk *clk;
|
||||
};
|
||||
#define glue_to_musb(g) platform_get_drvdata(g->musb)
|
||||
|
||||
static void ux500_musb_set_vbus(struct musb *musb, int is_on)
|
||||
{
|
||||
u8 devctl;
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
/* HDRC controls CPEN, but beware current surges during device
|
||||
* connect. They can trigger transient overcurrent conditions
|
||||
* that must be ignored.
|
||||
*/
|
||||
|
||||
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
||||
|
||||
if (is_on) {
|
||||
if (musb->xceiv->state == OTG_STATE_A_IDLE) {
|
||||
/* start the session */
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
/*
|
||||
* Wait for the musb to set as A device to enable the
|
||||
* VBUS
|
||||
*/
|
||||
while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) {
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_err(musb->controller,
|
||||
"configured as A device timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
musb->is_active = 1;
|
||||
musb->xceiv->otg->default_a = 1;
|
||||
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
||||
devctl |= MUSB_DEVCTL_SESSION;
|
||||
MUSB_HST_MODE(musb);
|
||||
}
|
||||
} else {
|
||||
musb->is_active = 0;
|
||||
|
||||
/* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and jumping
|
||||
* right to B_IDLE...
|
||||
*/
|
||||
musb->xceiv->otg->default_a = 0;
|
||||
devctl &= ~MUSB_DEVCTL_SESSION;
|
||||
MUSB_DEV_MODE(musb);
|
||||
}
|
||||
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
||||
|
||||
/*
|
||||
* Devctl values will be updated after vbus goes below
|
||||
* session_valid. The time taken depends on the capacitance
|
||||
* on VBUS line. The max discharge time can be upto 1 sec
|
||||
* as per the spec. Typically on our platform, it is 200ms
|
||||
*/
|
||||
if (!is_on)
|
||||
mdelay(200);
|
||||
|
||||
dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
|
||||
usb_otg_state_string(musb->xceiv->state),
|
||||
musb_readb(musb->mregs, MUSB_DEVCTL));
|
||||
}
|
||||
|
||||
static int musb_otg_notifications(struct notifier_block *nb,
|
||||
unsigned long event, void *unused)
|
||||
{
|
||||
struct musb *musb = container_of(nb, struct musb, nb);
|
||||
|
||||
dev_dbg(musb->controller, "musb_otg_notifications %ld %s\n",
|
||||
event, usb_otg_state_string(musb->xceiv->state));
|
||||
|
||||
switch (event) {
|
||||
case UX500_MUSB_ID:
|
||||
dev_dbg(musb->controller, "ID GND\n");
|
||||
ux500_musb_set_vbus(musb, 1);
|
||||
break;
|
||||
case UX500_MUSB_VBUS:
|
||||
dev_dbg(musb->controller, "VBUS Connect\n");
|
||||
break;
|
||||
case UX500_MUSB_NONE:
|
||||
dev_dbg(musb->controller, "VBUS Disconnect\n");
|
||||
if (is_host_active(musb))
|
||||
ux500_musb_set_vbus(musb, 0);
|
||||
else
|
||||
musb->xceiv->state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(musb->controller, "ID float\n");
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static irqreturn_t ux500_musb_interrupt(int irq, void *__hci)
|
||||
{
|
||||
unsigned long flags;
|
||||
irqreturn_t retval = IRQ_NONE;
|
||||
struct musb *musb = __hci;
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
|
||||
musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
|
||||
musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
|
||||
musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
|
||||
|
||||
if (musb->int_usb || musb->int_tx || musb->int_rx)
|
||||
retval = musb_interrupt(musb);
|
||||
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ux500_musb_init(struct musb *musb)
|
||||
{
|
||||
int status;
|
||||
|
||||
musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
if (IS_ERR_OR_NULL(musb->xceiv)) {
|
||||
pr_err("HS USB OTG: no transceiver configured\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
musb->nb.notifier_call = musb_otg_notifications;
|
||||
status = usb_register_notifier(musb->xceiv, &musb->nb);
|
||||
if (status < 0) {
|
||||
dev_dbg(musb->controller, "notification register failed\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
musb->isr = ux500_musb_interrupt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ux500_musb_exit(struct musb *musb)
|
||||
{
|
||||
usb_unregister_notifier(musb->xceiv, &musb->nb);
|
||||
|
||||
usb_put_phy(musb->xceiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct musb_platform_ops ux500_ops = {
|
||||
.init = ux500_musb_init,
|
||||
.exit = ux500_musb_exit,
|
||||
|
||||
.set_vbus = ux500_musb_set_vbus,
|
||||
};
|
||||
|
||||
static struct musb_hdrc_platform_data *
|
||||
ux500_of_probe(struct platform_device *pdev, struct device_node *np)
|
||||
{
|
||||
struct musb_hdrc_platform_data *pdata;
|
||||
const char *mode;
|
||||
int strlen;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
|
||||
mode = of_get_property(np, "dr_mode", &strlen);
|
||||
if (!mode) {
|
||||
dev_err(&pdev->dev, "No 'dr_mode' property found\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strlen > 0) {
|
||||
if (!strcmp(mode, "host"))
|
||||
pdata->mode = MUSB_HOST;
|
||||
if (!strcmp(mode, "otg"))
|
||||
pdata->mode = MUSB_OTG;
|
||||
if (!strcmp(mode, "peripheral"))
|
||||
pdata->mode = MUSB_PERIPHERAL;
|
||||
}
|
||||
|
||||
return pdata;
|
||||
}
|
||||
|
||||
static int ux500_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource musb_resources[2];
|
||||
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct platform_device *musb;
|
||||
struct ux500_glue *glue;
|
||||
struct clk *clk;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
if (!pdata) {
|
||||
if (np) {
|
||||
pdata = ux500_of_probe(pdev, np);
|
||||
if (!pdata)
|
||||
goto err0;
|
||||
|
||||
pdev->dev.platform_data = pdata;
|
||||
} else {
|
||||
dev_err(&pdev->dev, "no pdata or device tree found\n");
|
||||
goto err0;
|
||||
}
|
||||
}
|
||||
|
||||
glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(&pdev->dev, "failed to allocate glue context\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
|
||||
if (!musb) {
|
||||
dev_err(&pdev->dev, "failed to allocate musb device\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable clock\n");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
musb->dev.parent = &pdev->dev;
|
||||
musb->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
||||
musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask;
|
||||
|
||||
glue->dev = &pdev->dev;
|
||||
glue->musb = musb;
|
||||
glue->clk = clk;
|
||||
|
||||
pdata->platform_ops = &ux500_ops;
|
||||
pdata->config = &ux500_musb_hdrc_config;
|
||||
|
||||
platform_set_drvdata(pdev, glue);
|
||||
|
||||
memset(musb_resources, 0x00, sizeof(*musb_resources) *
|
||||
ARRAY_SIZE(musb_resources));
|
||||
|
||||
musb_resources[0].name = pdev->resource[0].name;
|
||||
musb_resources[0].start = pdev->resource[0].start;
|
||||
musb_resources[0].end = pdev->resource[0].end;
|
||||
musb_resources[0].flags = pdev->resource[0].flags;
|
||||
|
||||
musb_resources[1].name = pdev->resource[1].name;
|
||||
musb_resources[1].start = pdev->resource[1].start;
|
||||
musb_resources[1].end = pdev->resource[1].end;
|
||||
musb_resources[1].flags = pdev->resource[1].flags;
|
||||
|
||||
ret = platform_device_add_resources(musb, musb_resources,
|
||||
ARRAY_SIZE(musb_resources));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add resources\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = platform_device_add_data(musb, pdata, sizeof(*pdata));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to add platform_data\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
ret = platform_device_add(musb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register musb device\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
clk_disable_unprepare(clk);
|
||||
|
||||
err1:
|
||||
platform_device_put(musb);
|
||||
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ux500_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ux500_glue *glue = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(glue->musb);
|
||||
clk_disable_unprepare(glue->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ux500_suspend(struct device *dev)
|
||||
{
|
||||
struct ux500_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
|
||||
usb_phy_set_suspend(musb->xceiv, 1);
|
||||
clk_disable_unprepare(glue->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ux500_resume(struct device *dev)
|
||||
{
|
||||
struct ux500_glue *glue = dev_get_drvdata(dev);
|
||||
struct musb *musb = glue_to_musb(glue);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(glue->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
usb_phy_set_suspend(musb->xceiv, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(ux500_pm_ops, ux500_suspend, ux500_resume);
|
||||
|
||||
static const struct of_device_id ux500_match[] = {
|
||||
{ .compatible = "stericsson,db8500-musb", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver ux500_driver = {
|
||||
.probe = ux500_probe,
|
||||
.remove = ux500_remove,
|
||||
.driver = {
|
||||
.name = "musb-ux500",
|
||||
.pm = &ux500_pm_ops,
|
||||
.of_match_table = ux500_match,
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("UX500 MUSB Glue Layer");
|
||||
MODULE_AUTHOR("Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
module_platform_driver(ux500_driver);
|
412
drivers/usb/musb/ux500_dma.c
Normal file
412
drivers/usb/musb/ux500_dma.c
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* drivers/usb/musb/ux500_dma.c
|
||||
*
|
||||
* U8500 DMA support code
|
||||
*
|
||||
* Copyright (C) 2009 STMicroelectronics
|
||||
* Copyright (C) 2011 ST-Ericsson SA
|
||||
* Authors:
|
||||
* Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
|
||||
* Praveena Nadahally <praveen.nadahally@stericsson.com>
|
||||
* Rajaram Regupathy <ragupathy.rajaram@stericsson.com>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/pfn.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/platform_data/usb-musb-ux500.h>
|
||||
#include "musb_core.h"
|
||||
|
||||
static const char *iep_chan_names[] = { "iep_1_9", "iep_2_10", "iep_3_11", "iep_4_12",
|
||||
"iep_5_13", "iep_6_14", "iep_7_15", "iep_8" };
|
||||
static const char *oep_chan_names[] = { "oep_1_9", "oep_2_10", "oep_3_11", "oep_4_12",
|
||||
"oep_5_13", "oep_6_14", "oep_7_15", "oep_8" };
|
||||
|
||||
struct ux500_dma_channel {
|
||||
struct dma_channel channel;
|
||||
struct ux500_dma_controller *controller;
|
||||
struct musb_hw_ep *hw_ep;
|
||||
struct dma_chan *dma_chan;
|
||||
unsigned int cur_len;
|
||||
dma_cookie_t cookie;
|
||||
u8 ch_num;
|
||||
u8 is_tx;
|
||||
u8 is_allocated;
|
||||
};
|
||||
|
||||
struct ux500_dma_controller {
|
||||
struct dma_controller controller;
|
||||
struct ux500_dma_channel rx_channel[UX500_MUSB_DMA_NUM_RX_TX_CHANNELS];
|
||||
struct ux500_dma_channel tx_channel[UX500_MUSB_DMA_NUM_RX_TX_CHANNELS];
|
||||
void *private_data;
|
||||
dma_addr_t phy_base;
|
||||
};
|
||||
|
||||
/* Work function invoked from DMA callback to handle rx transfers. */
|
||||
static void ux500_dma_callback(void *private_data)
|
||||
{
|
||||
struct dma_channel *channel = private_data;
|
||||
struct ux500_dma_channel *ux500_channel = channel->private_data;
|
||||
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
|
||||
struct musb *musb = hw_ep->musb;
|
||||
unsigned long flags;
|
||||
|
||||
dev_dbg(musb->controller, "DMA rx transfer done on hw_ep=%d\n",
|
||||
hw_ep->epnum);
|
||||
|
||||
spin_lock_irqsave(&musb->lock, flags);
|
||||
ux500_channel->channel.actual_len = ux500_channel->cur_len;
|
||||
ux500_channel->channel.status = MUSB_DMA_STATUS_FREE;
|
||||
musb_dma_completion(musb, hw_ep->epnum, ux500_channel->is_tx);
|
||||
spin_unlock_irqrestore(&musb->lock, flags);
|
||||
|
||||
}
|
||||
|
||||
static bool ux500_configure_channel(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
struct ux500_dma_channel *ux500_channel = channel->private_data;
|
||||
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
|
||||
struct dma_chan *dma_chan = ux500_channel->dma_chan;
|
||||
struct dma_async_tx_descriptor *dma_desc;
|
||||
enum dma_transfer_direction direction;
|
||||
struct scatterlist sg;
|
||||
struct dma_slave_config slave_conf;
|
||||
enum dma_slave_buswidth addr_width;
|
||||
dma_addr_t usb_fifo_addr = (MUSB_FIFO_OFFSET(hw_ep->epnum) +
|
||||
ux500_channel->controller->phy_base);
|
||||
struct musb *musb = ux500_channel->controller->private_data;
|
||||
|
||||
dev_dbg(musb->controller,
|
||||
"packet_sz=%d, mode=%d, dma_addr=0x%llx, len=%d is_tx=%d\n",
|
||||
packet_sz, mode, (unsigned long long) dma_addr,
|
||||
len, ux500_channel->is_tx);
|
||||
|
||||
ux500_channel->cur_len = len;
|
||||
|
||||
sg_init_table(&sg, 1);
|
||||
sg_set_page(&sg, pfn_to_page(PFN_DOWN(dma_addr)), len,
|
||||
offset_in_page(dma_addr));
|
||||
sg_dma_address(&sg) = dma_addr;
|
||||
sg_dma_len(&sg) = len;
|
||||
|
||||
direction = ux500_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
||||
addr_width = (len & 0x3) ? DMA_SLAVE_BUSWIDTH_1_BYTE :
|
||||
DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
slave_conf.direction = direction;
|
||||
slave_conf.src_addr = usb_fifo_addr;
|
||||
slave_conf.src_addr_width = addr_width;
|
||||
slave_conf.src_maxburst = 16;
|
||||
slave_conf.dst_addr = usb_fifo_addr;
|
||||
slave_conf.dst_addr_width = addr_width;
|
||||
slave_conf.dst_maxburst = 16;
|
||||
slave_conf.device_fc = false;
|
||||
|
||||
dma_chan->device->device_control(dma_chan, DMA_SLAVE_CONFIG,
|
||||
(unsigned long) &slave_conf);
|
||||
|
||||
dma_desc = dmaengine_prep_slave_sg(dma_chan, &sg, 1, direction,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!dma_desc)
|
||||
return false;
|
||||
|
||||
dma_desc->callback = ux500_dma_callback;
|
||||
dma_desc->callback_param = channel;
|
||||
ux500_channel->cookie = dma_desc->tx_submit(dma_desc);
|
||||
|
||||
dma_async_issue_pending(dma_chan);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c,
|
||||
struct musb_hw_ep *hw_ep, u8 is_tx)
|
||||
{
|
||||
struct ux500_dma_controller *controller = container_of(c,
|
||||
struct ux500_dma_controller, controller);
|
||||
struct ux500_dma_channel *ux500_channel = NULL;
|
||||
struct musb *musb = controller->private_data;
|
||||
u8 ch_num = hw_ep->epnum - 1;
|
||||
|
||||
/* 8 DMA channels (0 - 7). Each DMA channel can only be allocated
|
||||
* to specified hw_ep. For example DMA channel 0 can only be allocated
|
||||
* to hw_ep 1 and 9.
|
||||
*/
|
||||
if (ch_num > 7)
|
||||
ch_num -= 8;
|
||||
|
||||
if (ch_num >= UX500_MUSB_DMA_NUM_RX_TX_CHANNELS)
|
||||
return NULL;
|
||||
|
||||
ux500_channel = is_tx ? &(controller->tx_channel[ch_num]) :
|
||||
&(controller->rx_channel[ch_num]) ;
|
||||
|
||||
/* Check if channel is already used. */
|
||||
if (ux500_channel->is_allocated)
|
||||
return NULL;
|
||||
|
||||
ux500_channel->hw_ep = hw_ep;
|
||||
ux500_channel->is_allocated = 1;
|
||||
|
||||
dev_dbg(musb->controller, "hw_ep=%d, is_tx=0x%x, channel=%d\n",
|
||||
hw_ep->epnum, is_tx, ch_num);
|
||||
|
||||
return &(ux500_channel->channel);
|
||||
}
|
||||
|
||||
static void ux500_dma_channel_release(struct dma_channel *channel)
|
||||
{
|
||||
struct ux500_dma_channel *ux500_channel = channel->private_data;
|
||||
struct musb *musb = ux500_channel->controller->private_data;
|
||||
|
||||
dev_dbg(musb->controller, "channel=%d\n", ux500_channel->ch_num);
|
||||
|
||||
if (ux500_channel->is_allocated) {
|
||||
ux500_channel->is_allocated = 0;
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
channel->actual_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int ux500_dma_is_compatible(struct dma_channel *channel,
|
||||
u16 maxpacket, void *buf, u32 length)
|
||||
{
|
||||
if ((maxpacket & 0x3) ||
|
||||
((unsigned long int) buf & 0x3) ||
|
||||
(length < 512) ||
|
||||
(length & 0x3))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ux500_dma_channel_program(struct dma_channel *channel,
|
||||
u16 packet_sz, u8 mode,
|
||||
dma_addr_t dma_addr, u32 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN ||
|
||||
channel->status == MUSB_DMA_STATUS_BUSY);
|
||||
|
||||
if (!ux500_dma_is_compatible(channel, packet_sz, (void *)dma_addr, len))
|
||||
return false;
|
||||
|
||||
channel->status = MUSB_DMA_STATUS_BUSY;
|
||||
channel->actual_len = 0;
|
||||
ret = ux500_configure_channel(channel, packet_sz, mode, dma_addr, len);
|
||||
if (!ret)
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ux500_dma_channel_abort(struct dma_channel *channel)
|
||||
{
|
||||
struct ux500_dma_channel *ux500_channel = channel->private_data;
|
||||
struct ux500_dma_controller *controller = ux500_channel->controller;
|
||||
struct musb *musb = controller->private_data;
|
||||
void __iomem *epio = musb->endpoints[ux500_channel->hw_ep->epnum].regs;
|
||||
u16 csr;
|
||||
|
||||
dev_dbg(musb->controller, "channel=%d, is_tx=%d\n",
|
||||
ux500_channel->ch_num, ux500_channel->is_tx);
|
||||
|
||||
if (channel->status == MUSB_DMA_STATUS_BUSY) {
|
||||
if (ux500_channel->is_tx) {
|
||||
csr = musb_readw(epio, MUSB_TXCSR);
|
||||
csr &= ~(MUSB_TXCSR_AUTOSET |
|
||||
MUSB_TXCSR_DMAENAB |
|
||||
MUSB_TXCSR_DMAMODE);
|
||||
musb_writew(epio, MUSB_TXCSR, csr);
|
||||
} else {
|
||||
csr = musb_readw(epio, MUSB_RXCSR);
|
||||
csr &= ~(MUSB_RXCSR_AUTOCLEAR |
|
||||
MUSB_RXCSR_DMAENAB |
|
||||
MUSB_RXCSR_DMAMODE);
|
||||
musb_writew(epio, MUSB_RXCSR, csr);
|
||||
}
|
||||
|
||||
ux500_channel->dma_chan->device->
|
||||
device_control(ux500_channel->dma_chan,
|
||||
DMA_TERMINATE_ALL, 0);
|
||||
channel->status = MUSB_DMA_STATUS_FREE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ux500_dma_controller_stop(struct ux500_dma_controller *controller)
|
||||
{
|
||||
struct ux500_dma_channel *ux500_channel;
|
||||
struct dma_channel *channel;
|
||||
u8 ch_num;
|
||||
|
||||
for (ch_num = 0; ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS; ch_num++) {
|
||||
channel = &controller->rx_channel[ch_num].channel;
|
||||
ux500_channel = channel->private_data;
|
||||
|
||||
ux500_dma_channel_release(channel);
|
||||
|
||||
if (ux500_channel->dma_chan)
|
||||
dma_release_channel(ux500_channel->dma_chan);
|
||||
}
|
||||
|
||||
for (ch_num = 0; ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS; ch_num++) {
|
||||
channel = &controller->tx_channel[ch_num].channel;
|
||||
ux500_channel = channel->private_data;
|
||||
|
||||
ux500_dma_channel_release(channel);
|
||||
|
||||
if (ux500_channel->dma_chan)
|
||||
dma_release_channel(ux500_channel->dma_chan);
|
||||
}
|
||||
}
|
||||
|
||||
static int ux500_dma_controller_start(struct ux500_dma_controller *controller)
|
||||
{
|
||||
struct ux500_dma_channel *ux500_channel = NULL;
|
||||
struct musb *musb = controller->private_data;
|
||||
struct device *dev = musb->controller;
|
||||
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
|
||||
struct ux500_musb_board_data *data;
|
||||
struct dma_channel *dma_channel = NULL;
|
||||
char **chan_names;
|
||||
u32 ch_num;
|
||||
u8 dir;
|
||||
u8 is_tx = 0;
|
||||
|
||||
void **param_array;
|
||||
struct ux500_dma_channel *channel_array;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
if (!plat) {
|
||||
dev_err(musb->controller, "No platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data = plat->board_data;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
|
||||
/* Prepare the loop for RX channels */
|
||||
channel_array = controller->rx_channel;
|
||||
param_array = data ? data->dma_rx_param_array : NULL;
|
||||
chan_names = (char **)iep_chan_names;
|
||||
|
||||
for (dir = 0; dir < 2; dir++) {
|
||||
for (ch_num = 0;
|
||||
ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS;
|
||||
ch_num++) {
|
||||
ux500_channel = &channel_array[ch_num];
|
||||
ux500_channel->controller = controller;
|
||||
ux500_channel->ch_num = ch_num;
|
||||
ux500_channel->is_tx = is_tx;
|
||||
|
||||
dma_channel = &(ux500_channel->channel);
|
||||
dma_channel->private_data = ux500_channel;
|
||||
dma_channel->status = MUSB_DMA_STATUS_FREE;
|
||||
dma_channel->max_len = SZ_16M;
|
||||
|
||||
ux500_channel->dma_chan =
|
||||
dma_request_slave_channel(dev, chan_names[ch_num]);
|
||||
|
||||
if (!ux500_channel->dma_chan)
|
||||
ux500_channel->dma_chan =
|
||||
dma_request_channel(mask,
|
||||
data ?
|
||||
data->dma_filter :
|
||||
NULL,
|
||||
param_array ?
|
||||
param_array[ch_num] :
|
||||
NULL);
|
||||
|
||||
if (!ux500_channel->dma_chan) {
|
||||
ERR("Dma pipe allocation error dir=%d ch=%d\n",
|
||||
dir, ch_num);
|
||||
|
||||
/* Release already allocated channels */
|
||||
ux500_dma_controller_stop(controller);
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Prepare the loop for TX channels */
|
||||
channel_array = controller->tx_channel;
|
||||
param_array = data ? data->dma_tx_param_array : NULL;
|
||||
chan_names = (char **)oep_chan_names;
|
||||
is_tx = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dma_controller_destroy(struct dma_controller *c)
|
||||
{
|
||||
struct ux500_dma_controller *controller = container_of(c,
|
||||
struct ux500_dma_controller, controller);
|
||||
|
||||
ux500_dma_controller_stop(controller);
|
||||
kfree(controller);
|
||||
}
|
||||
|
||||
struct dma_controller *dma_controller_create(struct musb *musb,
|
||||
void __iomem *base)
|
||||
{
|
||||
struct ux500_dma_controller *controller;
|
||||
struct platform_device *pdev = to_platform_device(musb->controller);
|
||||
struct resource *iomem;
|
||||
int ret;
|
||||
|
||||
controller = kzalloc(sizeof(*controller), GFP_KERNEL);
|
||||
if (!controller)
|
||||
goto kzalloc_fail;
|
||||
|
||||
controller->private_data = musb;
|
||||
|
||||
/* Save physical address for DMA controller. */
|
||||
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!iomem) {
|
||||
dev_err(musb->controller, "no memory resource defined\n");
|
||||
goto plat_get_fail;
|
||||
}
|
||||
|
||||
controller->phy_base = (dma_addr_t) iomem->start;
|
||||
|
||||
controller->controller.channel_alloc = ux500_dma_channel_allocate;
|
||||
controller->controller.channel_release = ux500_dma_channel_release;
|
||||
controller->controller.channel_program = ux500_dma_channel_program;
|
||||
controller->controller.channel_abort = ux500_dma_channel_abort;
|
||||
controller->controller.is_compatible = ux500_dma_is_compatible;
|
||||
|
||||
ret = ux500_dma_controller_start(controller);
|
||||
if (ret)
|
||||
goto plat_get_fail;
|
||||
return &controller->controller;
|
||||
|
||||
plat_get_fail:
|
||||
kfree(controller);
|
||||
kzalloc_fail:
|
||||
return NULL;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue