/*
* MUSB OTG driver host support
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
* Copyright (C) 2008-2009 MontaVista Software, Inc. <source@mvista.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/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/usb/ch9.h>
#include <linux/dma-mapping.h>
#include "musb_core.h"
#include "musb_host.h"
/* MUSB HOST status 22-mar-2006
*
* - There's still lots of partial code duplication for fault paths, so
* they aren't handled as consistently as they need to be.
*
* - PIO mostly behaved when last tested.
* + including ep0, with all usbtest cases 9, 10
* + usbtest 14 (ep0out) doesn't seem to run at all
* + double buffered OUT/TX endpoints saw stalls(!) with certain usbtest
* configurations, but otherwise double buffering passes basic tests.
* + for 2.6.N, for N > ~10, needs API changes for hcd framework.
*
* - DMA (CPPI) ... partially behaves, not currently recommended
* + about 1/15 the speed of typical EHCI implementations (PCI)
* + RX, all too often reqpkt seems to misbehave after tx
* + TX, no known issues (other than evident silicon issue)
*
* - DMA (Mentor/OMAP) ...has at least toggle update problems
*
* - [23-feb-2009] minimal traffic scheduling to avoid bulk RX packet
* starvation ... nothing yet for TX, interrupt, or bulk.
*
* - Not tested with HNP, but some SRP paths seem to behave.
*
* NOTE 24-August-2006:
*
* - Bulk traffic finally uses both sides of hardware ep1, freeing up an
* extra endpoint for periodic use enabling hub + keybd + mouse. That
* mostly works, except that with "usbnet" it's easy to trigger cases
* with "ping" where RX loses. (a) ping to davinci, even "ping -f",
* fine; but (b) ping _from_ davinci, even "ping -c 1", ICMP RX loses
* although ARP RX wins. (That test was done with a full speed link.)
*/
/*
* NOTE on endpoint usage:
*
* CONTROL transfers all go through ep0. BULK ones go through dedicated IN
* and OUT endpoints ... hardware is dedicated for those "async" queue(s).
* (Yes, bulk _could_ use more of the endpoints than that, and would even
* benefit from it.)
*
* INTERUPPT and ISOCHRONOUS transfers are scheduled to the other endpoints.
* So far that scheduling is both dumb and optimistic: the endpoint will be
* "claimed" until its software queue is no longer refilled. No multiplexing
* of transfers between endpoints, or anything clever.
*/
//ENABLE_USB20_ISO_TRANSFER
static bool musb_allocate_ep_fifo(struct musb *musb,
struct musb_qh *qh)
{
struct usb_host_endpoint *hep = qh->hep;
u16 ep_fifo_sz = (le16_to_cpu(hep->desc.wMaxPacketSize)&0x7ff) *(1 + ((le16_to_cpu(hep->desc.wMaxPacketSize) >> 11) & 0x03));
u16 fifo_unit_nr = (ep_fifo_sz+511)/512;
u16 fifosz = 0;
u16 free_uint = 0;
u16 i;
u8 reversed;
u8 free;
u8 found = 0;
u16 fifoaddr;
u8 index;
//INFO("max packet size 0x%x\n",le16_to_cpu(hep->desc.wMaxPacketSize));
reversed = 0;
for(i=0; i<musb->ep_fifo_total_sz; i++)
{
if(reversed)
free = !(musb->ep_fifo & (1<<(musb->ep_fifo_total_sz-1-i)));
else
free = !(musb->ep_fifo & (1<<i));
if(free)
free_uint++;
else
free_uint = 0;
if(free_uint == fifo_unit_nr)
{
found =1;
break;
}
}
if(found == 0)
{
ERR("[usb]fifo not enough!!!!!!!!!!!\n");
return false;
}
if(reversed)
fifoaddr = musb->ep_fifo_total_sz-1-i;
else
fifoaddr = i-(fifo_unit_nr-1);
for(i=0; i< fifo_unit_nr; i++)
{
musb->ep_fifo |= (1<<(fifoaddr+i));
}
if(ep_fifo_sz <= 512){
fifosz = 6;
}
else if(ep_fifo_sz <= 1024){
fifosz = 7;
}
else if(ep_fifo_sz <= 2048){
fifosz = 8;
}
else if(ep_fifo_sz <= 4096){
fifosz = 9;
}
index = musb_readb(musb->mregs, MUSB_INDEX);
musb_writeb(musb->mregs, MUSB_INDEX, qh->hw_ep->epnum);
#if 0
INFO("[usb]assign hwep: %d, is_in:%d, fifoaddr: 0x%08x,fifosz: 0x%02x\n",qh->hw_ep->epnum,
qh->is_in, fifoaddr, fifosz);
INFO("[usb]ep fifo status: 0x%08x\n",musb->ep_fifo);
#endif
if(qh->is_in)
{
musb_write_rxfifosz(musb->mregs, fifosz);
musb_write_rxfifoadd(musb->mregs, 0x08+0x40*fifoaddr);
}
else
{
musb_write_txfifosz(musb->mregs,fifosz);
musb_write_txfifoadd(musb->mregs,0x08+0x40*fifoaddr);
}
musb_writeb(musb->mregs, MUSB_INDEX, index);
return true;
}
static bool musb_release_ep_fifo(struct musb *musb,
struct musb_qh *qh)
{
struct usb_host_endpoint *hep = qh->hep;
u16 ep_fifo_sz = (le16_to_cpu(hep->desc.wMaxPacketSize)&0x7ff) *(1 + ((le16_to_cpu(hep->desc.wMaxPacketSize) >> 11) & 0x03));
u16 fifo_unit_nr = (ep_fifo_sz+511)/512;
u16 i;
u16 fifoaddr;
u8 index;
index = musb_readb(musb->mregs, MUSB_INDEX);
musb_writeb(musb->mregs, MUSB_INDEX, qh->hw_ep->epnum);
if(qh->is_in)
fifoaddr = musb_read_rxfifoadd(musb->mregs);
else
fifoaddr = musb_read_txfifoadd(musb->mregs);
fifoaddr = (fifoaddr-0x08)/0x40;
for(i=0; i< fifo_unit_nr; i++)
{
musb->ep_fifo &= ~(1<<(fifoaddr+i));
}
//INFO("[usb]release ep: %d,in: %d,ep fifo status: 0x%08x\n",qh->hw_ep->epnum,qh->is_in,musb->ep_fifo);
if(qh->is_in)
{
musb_write_rxfifosz(musb->mregs, 0);
musb_write_rxfifoadd(musb->mregs, 0);
}
else
{
musb_write_txfifosz(musb->mregs,0);
musb_write_txfifoadd(musb->mregs,0);
}
musb_writeb(musb->mregs, MUSB_INDEX, index);
return true;
}
//END ENABLE_USB20_ISO_TRANSFER
static void musb_ep_program(struct musb *musb, u8 epnum,
struct urb *urb, int is_out, u8 *buf, u32 offset, u32 len);
/*
* Clear TX fifo. Needed to avoid BABBLE errors.
*/
static void musb_h_tx_flush_fifo(struct musb_hw_ep *ep)
{
void __iomem *epio = ep->regs;
u16 csr;
u16 lastcsr = 0;
int retries = 1000;
csr = musb_readw(epio, MUSB_TXCSR);
while (csr & MUSB_TXCSR_FIFONOTEMPTY) {
if (csr != lastcsr)
DBG(4, "Host TX FIFONOTEMPTY csr: %02x\n", csr);
lastcsr = csr;
csr &= ~MUSB_TXCSR_TXPKTRDY;
csr |= MUSB_TXCSR_FLUSHFIFO;
musb_writew(epio, MUSB_TXCSR, csr);
csr = musb_readw(epio, MUSB_TXCS