mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-31 14:28:52 +09:00
![Jean-Jacques Hiblot](/assets/img/avatar_default.png)
Enable DM for USB peripheral in the musb-new driver. Also make sure that the driver can be used in the SPL. This implies that: * the driver must work with and without the OF_CONTROL option. That in turn, implies that the platform data can be passed in a struct ti_musb_platdata or be read from the dtb * usb.o is linked in the SPL if host support is enabled Another change is that the driver does not fail to bind (and stop the boot process) if one of the child driver does not bind. Reporting the error is enough. This kind of error would appear if the port is configured in the DTS but the driver is not activated in the config. Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com> Reviewed-by: Tom Rini <trini@konsulko.com>
2319 lines
59 KiB
C
2319 lines
59 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* MUSB OTG driver peripheral support
|
|
*
|
|
* Copyright 2005 Mentor Graphics Corporation
|
|
* Copyright (C) 2005-2006 by Texas Instruments
|
|
* Copyright (C) 2006-2007 Nokia Corporation
|
|
* Copyright (C) 2009 MontaVista Software, Inc. <source@mvista.com>
|
|
*/
|
|
|
|
#ifndef __UBOOT__
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
#else
|
|
#include <common.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include "linux-compat.h"
|
|
#endif
|
|
|
|
#include "musb_core.h"
|
|
|
|
|
|
/* MUSB PERIPHERAL status 3-mar-2006:
|
|
*
|
|
* - EP0 seems solid. It passes both USBCV and usbtest control cases.
|
|
* Minor glitches:
|
|
*
|
|
* + remote wakeup to Linux hosts work, but saw USBCV failures;
|
|
* in one test run (operator error?)
|
|
* + endpoint halt tests -- in both usbtest and usbcv -- seem
|
|
* to break when dma is enabled ... is something wrongly
|
|
* clearing SENDSTALL?
|
|
*
|
|
* - Mass storage behaved ok when last tested. Network traffic patterns
|
|
* (with lots of short transfers etc) need retesting; they turn up the
|
|
* worst cases of the DMA, since short packets are typical but are not
|
|
* required.
|
|
*
|
|
* - TX/IN
|
|
* + both pio and dma behave in with network and g_zero tests
|
|
* + no cppi throughput issues other than no-hw-queueing
|
|
* + failed with FLAT_REG (DaVinci)
|
|
* + seems to behave with double buffering, PIO -and- CPPI
|
|
* + with gadgetfs + AIO, requests got lost?
|
|
*
|
|
* - RX/OUT
|
|
* + both pio and dma behave in with network and g_zero tests
|
|
* + dma is slow in typical case (short_not_ok is clear)
|
|
* + double buffering ok with PIO
|
|
* + double buffering *FAILS* with CPPI, wrong data bytes sometimes
|
|
* + request lossage observed with gadgetfs
|
|
*
|
|
* - ISO not tested ... might work, but only weakly isochronous
|
|
*
|
|
* - Gadget driver disabling of softconnect during bind() is ignored; so
|
|
* drivers can't hold off host requests until userspace is ready.
|
|
* (Workaround: they can turn it off later.)
|
|
*
|
|
* - PORTABILITY (assumes PIO works):
|
|
* + DaVinci, basically works with cppi dma
|
|
* + OMAP 2430, ditto with mentor dma
|
|
* + TUSB 6010, platform-specific dma in the works
|
|
*/
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
#define is_buffer_mapped(req) (is_dma_capable() && \
|
|
(req->map_state != UN_MAPPED))
|
|
|
|
#ifndef CONFIG_USB_MUSB_PIO_ONLY
|
|
/* Maps the buffer to dma */
|
|
|
|
static inline void map_dma_buffer(struct musb_request *request,
|
|
struct musb *musb, struct musb_ep *musb_ep)
|
|
{
|
|
int compatible = true;
|
|
struct dma_controller *dma = musb->dma_controller;
|
|
|
|
request->map_state = UN_MAPPED;
|
|
|
|
if (!is_dma_capable() || !musb_ep->dma)
|
|
return;
|
|
|
|
/* Check if DMA engine can handle this request.
|
|
* DMA code must reject the USB request explicitly.
|
|
* Default behaviour is to map the request.
|
|
*/
|
|
if (dma->is_compatible)
|
|
compatible = dma->is_compatible(musb_ep->dma,
|
|
musb_ep->packet_sz, request->request.buf,
|
|
request->request.length);
|
|
if (!compatible)
|
|
return;
|
|
|
|
if (request->request.dma == DMA_ADDR_INVALID) {
|
|
request->request.dma = dma_map_single(
|
|
musb->controller,
|
|
request->request.buf,
|
|
request->request.length,
|
|
request->tx
|
|
? DMA_TO_DEVICE
|
|
: DMA_FROM_DEVICE);
|
|
request->map_state = MUSB_MAPPED;
|
|
} else {
|
|
dma_sync_single_for_device(musb->controller,
|
|
request->request.dma,
|
|
request->request.length,
|
|
request->tx
|
|
? DMA_TO_DEVICE
|
|
: DMA_FROM_DEVICE);
|
|
request->map_state = PRE_MAPPED;
|
|
}
|
|
}
|
|
|
|
/* Unmap the buffer from dma and maps it back to cpu */
|
|
static inline void unmap_dma_buffer(struct musb_request *request,
|
|
struct musb *musb)
|
|
{
|
|
if (!is_buffer_mapped(request))
|
|
return;
|
|
|
|
if (request->request.dma == DMA_ADDR_INVALID) {
|
|
dev_vdbg(musb->controller,
|
|
"not unmapping a never mapped buffer\n");
|
|
return;
|
|
}
|
|
if (request->map_state == MUSB_MAPPED) {
|
|
dma_unmap_single(musb->controller,
|
|
request->request.dma,
|
|
request->request.length,
|
|
request->tx
|
|
? DMA_TO_DEVICE
|
|
: DMA_FROM_DEVICE);
|
|
request->request.dma = DMA_ADDR_INVALID;
|
|
} else { /* PRE_MAPPED */
|
|
dma_sync_single_for_cpu(musb->controller,
|
|
request->request.dma,
|
|
request->request.length,
|
|
request->tx
|
|
? DMA_TO_DEVICE
|
|
: DMA_FROM_DEVICE);
|
|
}
|
|
request->map_state = UN_MAPPED;
|
|
}
|
|
#else
|
|
static inline void map_dma_buffer(struct musb_request *request,
|
|
struct musb *musb, struct musb_ep *musb_ep)
|
|
{
|
|
}
|
|
|
|
static inline void unmap_dma_buffer(struct musb_request *request,
|
|
struct musb *musb)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Immediately complete a request.
|
|
*
|
|
* @param request the request to complete
|
|
* @param status the status to complete the request with
|
|
* Context: controller locked, IRQs blocked.
|
|
*/
|
|
void musb_g_giveback(
|
|
struct musb_ep *ep,
|
|
struct usb_request *request,
|
|
int status)
|
|
__releases(ep->musb->lock)
|
|
__acquires(ep->musb->lock)
|
|
{
|
|
struct musb_request *req;
|
|
struct musb *musb;
|
|
int busy = ep->busy;
|
|
|
|
req = to_musb_request(request);
|
|
|
|
list_del(&req->list);
|
|
if (req->request.status == -EINPROGRESS)
|
|
req->request.status = status;
|
|
musb = req->musb;
|
|
|
|
ep->busy = 1;
|
|
spin_unlock(&musb->lock);
|
|
unmap_dma_buffer(req, musb);
|
|
if (request->status == 0)
|
|
dev_dbg(musb->controller, "%s done request %p, %d/%d\n",
|
|
ep->end_point.name, request,
|
|
req->request.actual, req->request.length);
|
|
else
|
|
dev_dbg(musb->controller, "%s request %p, %d/%d fault %d\n",
|
|
ep->end_point.name, request,
|
|
req->request.actual, req->request.length,
|
|
request->status);
|
|
req->request.complete(&req->ep->end_point, &req->request);
|
|
spin_lock(&musb->lock);
|
|
ep->busy = busy;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* Abort requests queued to an endpoint using the status. Synchronous.
|
|
* caller locked controller and blocked irqs, and selected this ep.
|
|
*/
|
|
static void nuke(struct musb_ep *ep, const int status)
|
|
{
|
|
struct musb *musb = ep->musb;
|
|
struct musb_request *req = NULL;
|
|
void __iomem *epio = ep->musb->endpoints[ep->current_epnum].regs;
|
|
|
|
ep->busy = 1;
|
|
|
|
if (is_dma_capable() && ep->dma) {
|
|
struct dma_controller *c = ep->musb->dma_controller;
|
|
int value;
|
|
|
|
if (ep->is_in) {
|
|
/*
|
|
* The programming guide says that we must not clear
|
|
* the DMAMODE bit before DMAENAB, so we only
|
|
* clear it in the second write...
|
|
*/
|
|
musb_writew(epio, MUSB_TXCSR,
|
|
MUSB_TXCSR_DMAMODE | MUSB_TXCSR_FLUSHFIFO);
|
|
musb_writew(epio, MUSB_TXCSR,
|
|
0 | MUSB_TXCSR_FLUSHFIFO);
|
|
} else {
|
|
musb_writew(epio, MUSB_RXCSR,
|
|
0 | MUSB_RXCSR_FLUSHFIFO);
|
|
musb_writew(epio, MUSB_RXCSR,
|
|
0 | MUSB_RXCSR_FLUSHFIFO);
|
|
}
|
|
|
|
value = c->channel_abort(ep->dma);
|
|
dev_dbg(musb->controller, "%s: abort DMA --> %d\n",
|
|
ep->name, value);
|
|
c->channel_release(ep->dma);
|
|
ep->dma = NULL;
|
|
}
|
|
|
|
while (!list_empty(&ep->req_list)) {
|
|
req = list_first_entry(&ep->req_list, struct musb_request, list);
|
|
musb_g_giveback(ep, &req->request, status);
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
/* Data transfers - pure PIO, pure DMA, or mixed mode */
|
|
|
|
/*
|
|
* This assumes the separate CPPI engine is responding to DMA requests
|
|
* from the usb core ... sequenced a bit differently from mentor dma.
|
|
*/
|
|
|
|
static inline int max_ep_writesize(struct musb *musb, struct musb_ep *ep)
|
|
{
|
|
if (can_bulk_split(musb, ep->type))
|
|
return ep->hw_ep->max_packet_sz_tx;
|
|
else
|
|
return ep->packet_sz;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_USB_INVENTRA_DMA
|
|
|
|
/* Peripheral tx (IN) using Mentor DMA works as follows:
|
|
Only mode 0 is used for transfers <= wPktSize,
|
|
mode 1 is used for larger transfers,
|
|
|
|
One of the following happens:
|
|
- Host sends IN token which causes an endpoint interrupt
|
|
-> TxAvail
|
|
-> if DMA is currently busy, exit.
|
|
-> if queue is non-empty, txstate().
|
|
|
|
- Request is queued by the gadget driver.
|
|
-> if queue was previously empty, txstate()
|
|
|
|
txstate()
|
|
-> start
|
|
/\ -> setup DMA
|
|
| (data is transferred to the FIFO, then sent out when
|
|
| IN token(s) are recd from Host.
|
|
| -> DMA interrupt on completion
|
|
| calls TxAvail.
|
|
| -> stop DMA, ~DMAENAB,
|
|
| -> set TxPktRdy for last short pkt or zlp
|
|
| -> Complete Request
|
|
| -> Continue next request (call txstate)
|
|
|___________________________________|
|
|
|
|
* Non-Mentor DMA engines can of course work differently, such as by
|
|
* upleveling from irq-per-packet to irq-per-buffer.
|
|
*/
|
|
|
|
#endif
|
|
|
|
/*
|
|
* An endpoint is transmitting data. This can be called either from
|
|
* the IRQ routine or from ep.queue() to kickstart a request on an
|
|
* endpoint.
|
|
*
|
|
* Context: controller locked, IRQs blocked, endpoint selected
|
|
*/
|
|
static void txstate(struct musb *musb, struct musb_request *req)
|
|
{
|
|
u8 epnum = req->epnum;
|
|
struct musb_ep *musb_ep;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
struct usb_request *request;
|
|
u16 fifo_count = 0, csr;
|
|
int use_dma = 0;
|
|
|
|
musb_ep = req->ep;
|
|
|
|
/* Check if EP is disabled */
|
|
if (!musb_ep->desc) {
|
|
dev_dbg(musb->controller, "ep:%s disabled - ignore request\n",
|
|
musb_ep->end_point.name);
|
|
return;
|
|
}
|
|
|
|
/* we shouldn't get here while DMA is active ... but we do ... */
|
|
if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) {
|
|
dev_dbg(musb->controller, "dma pending...\n");
|
|
return;
|
|
}
|
|
|
|
/* read TXCSR before */
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
|
|
request = &req->request;
|
|
fifo_count = min(max_ep_writesize(musb, musb_ep),
|
|
(int)(request->length - request->actual));
|
|
|
|
if (csr & MUSB_TXCSR_TXPKTRDY) {
|
|
dev_dbg(musb->controller, "%s old packet still ready , txcsr %03x\n",
|
|
musb_ep->end_point.name, csr);
|
|
return;
|
|
}
|
|
|
|
if (csr & MUSB_TXCSR_P_SENDSTALL) {
|
|
dev_dbg(musb->controller, "%s stalling, txcsr %03x\n",
|
|
musb_ep->end_point.name, csr);
|
|
return;
|
|
}
|
|
|
|
dev_dbg(musb->controller, "hw_ep%d, maxpacket %d, fifo count %d, txcsr %03x\n",
|
|
epnum, musb_ep->packet_sz, fifo_count,
|
|
csr);
|
|
|
|
#ifndef CONFIG_USB_MUSB_PIO_ONLY
|
|
if (is_buffer_mapped(req)) {
|
|
struct dma_controller *c = musb->dma_controller;
|
|
size_t request_size;
|
|
|
|
/* setup DMA, then program endpoint CSR */
|
|
request_size = min_t(size_t, request->length - request->actual,
|
|
musb_ep->dma->max_len);
|
|
|
|
use_dma = (request->dma != DMA_ADDR_INVALID);
|
|
|
|
/* MUSB_TXCSR_P_ISO is still set correctly */
|
|
|
|
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA)
|
|
{
|
|
if (request_size < musb_ep->packet_sz)
|
|
musb_ep->dma->desired_mode = 0;
|
|
else
|
|
musb_ep->dma->desired_mode = 1;
|
|
|
|
use_dma = use_dma && c->channel_program(
|
|
musb_ep->dma, musb_ep->packet_sz,
|
|
musb_ep->dma->desired_mode,
|
|
request->dma + request->actual, request_size);
|
|
if (use_dma) {
|
|
if (musb_ep->dma->desired_mode == 0) {
|
|
/*
|
|
* We must not clear the DMAMODE bit
|
|
* before the DMAENAB bit -- and the
|
|
* latter doesn't always get cleared
|
|
* before we get here...
|
|
*/
|
|
csr &= ~(MUSB_TXCSR_AUTOSET
|
|
| MUSB_TXCSR_DMAENAB);
|
|
musb_writew(epio, MUSB_TXCSR, csr
|
|
| MUSB_TXCSR_P_WZC_BITS);
|
|
csr &= ~MUSB_TXCSR_DMAMODE;
|
|
csr |= (MUSB_TXCSR_DMAENAB |
|
|
MUSB_TXCSR_MODE);
|
|
/* against programming guide */
|
|
} else {
|
|
csr |= (MUSB_TXCSR_DMAENAB
|
|
| MUSB_TXCSR_DMAMODE
|
|
| MUSB_TXCSR_MODE);
|
|
if (!musb_ep->hb_mult)
|
|
csr |= MUSB_TXCSR_AUTOSET;
|
|
}
|
|
csr &= ~MUSB_TXCSR_P_UNDERRUN;
|
|
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
}
|
|
}
|
|
|
|
#elif defined(CONFIG_USB_TI_CPPI_DMA)
|
|
/* program endpoint CSR first, then setup DMA */
|
|
csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY);
|
|
csr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE |
|
|
MUSB_TXCSR_MODE;
|
|
musb_writew(epio, MUSB_TXCSR,
|
|
(MUSB_TXCSR_P_WZC_BITS & ~MUSB_TXCSR_P_UNDERRUN)
|
|
| csr);
|
|
|
|
/* ensure writebuffer is empty */
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
|
|
/* NOTE host side sets DMAENAB later than this; both are
|
|
* OK since the transfer dma glue (between CPPI and Mentor
|
|
* fifos) just tells CPPI it could start. Data only moves
|
|
* to the USB TX fifo when both fifos are ready.
|
|
*/
|
|
|
|
/* "mode" is irrelevant here; handle terminating ZLPs like
|
|
* PIO does, since the hardware RNDIS mode seems unreliable
|
|
* except for the last-packet-is-already-short case.
|
|
*/
|
|
use_dma = use_dma && c->channel_program(
|
|
musb_ep->dma, musb_ep->packet_sz,
|
|
0,
|
|
request->dma + request->actual,
|
|
request_size);
|
|
if (!use_dma) {
|
|
c->channel_release(musb_ep->dma);
|
|
musb_ep->dma = NULL;
|
|
csr &= ~MUSB_TXCSR_DMAENAB;
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
/* invariant: prequest->buf is non-null */
|
|
}
|
|
#elif defined(CONFIG_USB_TUSB_OMAP_DMA)
|
|
use_dma = use_dma && c->channel_program(
|
|
musb_ep->dma, musb_ep->packet_sz,
|
|
request->zero,
|
|
request->dma + request->actual,
|
|
request_size);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (!use_dma) {
|
|
/*
|
|
* Unmap the dma buffer back to cpu if dma channel
|
|
* programming fails
|
|
*/
|
|
unmap_dma_buffer(req, musb);
|
|
|
|
musb_write_fifo(musb_ep->hw_ep, fifo_count,
|
|
(u8 *) (request->buf + request->actual));
|
|
request->actual += fifo_count;
|
|
csr |= MUSB_TXCSR_TXPKTRDY;
|
|
csr &= ~MUSB_TXCSR_P_UNDERRUN;
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
}
|
|
|
|
/* host may already have the data when this message shows... */
|
|
dev_dbg(musb->controller, "%s TX/IN %s len %d/%d, txcsr %04x, fifo %d/%d\n",
|
|
musb_ep->end_point.name, use_dma ? "dma" : "pio",
|
|
request->actual, request->length,
|
|
musb_readw(epio, MUSB_TXCSR),
|
|
fifo_count,
|
|
musb_readw(epio, MUSB_TXMAXP));
|
|
}
|
|
|
|
/*
|
|
* FIFO state update (e.g. data ready).
|
|
* Called from IRQ, with controller locked.
|
|
*/
|
|
void musb_g_tx(struct musb *musb, u8 epnum)
|
|
{
|
|
u16 csr;
|
|
struct musb_request *req;
|
|
struct usb_request *request;
|
|
u8 __iomem *mbase = musb->mregs;
|
|
struct musb_ep *musb_ep = &musb->endpoints[epnum].ep_in;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
struct dma_channel *dma;
|
|
|
|
musb_ep_select(mbase, epnum);
|
|
req = next_request(musb_ep);
|
|
request = &req->request;
|
|
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
dev_dbg(musb->controller, "<== %s, txcsr %04x\n", musb_ep->end_point.name, csr);
|
|
|
|
dma = is_dma_capable() ? musb_ep->dma : NULL;
|
|
|
|
/*
|
|
* REVISIT: for high bandwidth, MUSB_TXCSR_P_INCOMPTX
|
|
* probably rates reporting as a host error.
|
|
*/
|
|
if (csr & MUSB_TXCSR_P_SENTSTALL) {
|
|
csr |= MUSB_TXCSR_P_WZC_BITS;
|
|
csr &= ~MUSB_TXCSR_P_SENTSTALL;
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
return;
|
|
}
|
|
|
|
if (csr & MUSB_TXCSR_P_UNDERRUN) {
|
|
/* We NAKed, no big deal... little reason to care. */
|
|
csr |= MUSB_TXCSR_P_WZC_BITS;
|
|
csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY);
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
dev_vdbg(musb->controller, "underrun on ep%d, req %p\n",
|
|
epnum, request);
|
|
}
|
|
|
|
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
|
|
/*
|
|
* SHOULD NOT HAPPEN... has with CPPI though, after
|
|
* changing SENDSTALL (and other cases); harmless?
|
|
*/
|
|
dev_dbg(musb->controller, "%s dma still busy?\n", musb_ep->end_point.name);
|
|
return;
|
|
}
|
|
|
|
if (request) {
|
|
u8 is_dma = 0;
|
|
|
|
if (dma && (csr & MUSB_TXCSR_DMAENAB)) {
|
|
is_dma = 1;
|
|
csr |= MUSB_TXCSR_P_WZC_BITS;
|
|
csr &= ~(MUSB_TXCSR_DMAENAB | MUSB_TXCSR_P_UNDERRUN |
|
|
MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_AUTOSET);
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
/* Ensure writebuffer is empty. */
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
request->actual += musb_ep->dma->actual_len;
|
|
dev_dbg(musb->controller, "TXCSR%d %04x, DMA off, len %zu, req %p\n",
|
|
epnum, csr, musb_ep->dma->actual_len, request);
|
|
}
|
|
|
|
/*
|
|
* First, maybe a terminating short packet. Some DMA
|
|
* engines might handle this by themselves.
|
|
*/
|
|
if ((request->zero && request->length
|
|
&& (request->length % musb_ep->packet_sz == 0)
|
|
&& (request->actual == request->length))
|
|
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA)
|
|
|| (is_dma && (!dma->desired_mode ||
|
|
(request->actual &
|
|
(musb_ep->packet_sz - 1))))
|
|
#endif
|
|
) {
|
|
/*
|
|
* On DMA completion, FIFO may not be
|
|
* available yet...
|
|
*/
|
|
if (csr & MUSB_TXCSR_TXPKTRDY)
|
|
return;
|
|
|
|
dev_dbg(musb->controller, "sending zero pkt\n");
|
|
musb_writew(epio, MUSB_TXCSR, MUSB_TXCSR_MODE
|
|
| MUSB_TXCSR_TXPKTRDY);
|
|
request->zero = 0;
|
|
}
|
|
|
|
if (request->actual == request->length) {
|
|
musb_g_giveback(musb_ep, request, 0);
|
|
/*
|
|
* In the giveback function the MUSB lock is
|
|
* released and acquired after sometime. During
|
|
* this time period the INDEX register could get
|
|
* changed by the gadget_queue function especially
|
|
* on SMP systems. Reselect the INDEX to be sure
|
|
* we are reading/modifying the right registers
|
|
*/
|
|
musb_ep_select(mbase, epnum);
|
|
req = musb_ep->desc ? next_request(musb_ep) : NULL;
|
|
if (!req) {
|
|
dev_dbg(musb->controller, "%s idle now\n",
|
|
musb_ep->end_point.name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
txstate(musb, req);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
|
|
#ifdef CONFIG_USB_INVENTRA_DMA
|
|
|
|
/* Peripheral rx (OUT) using Mentor DMA works as follows:
|
|
- Only mode 0 is used.
|
|
|
|
- Request is queued by the gadget class driver.
|
|
-> if queue was previously empty, rxstate()
|
|
|
|
- Host sends OUT token which causes an endpoint interrupt
|
|
/\ -> RxReady
|
|
| -> if request queued, call rxstate
|
|
| /\ -> setup DMA
|
|
| | -> DMA interrupt on completion
|
|
| | -> RxReady
|
|
| | -> stop DMA
|
|
| | -> ack the read
|
|
| | -> if data recd = max expected
|
|
| | by the request, or host
|
|
| | sent a short packet,
|
|
| | complete the request,
|
|
| | and start the next one.
|
|
| |_____________________________________|
|
|
| else just wait for the host
|
|
| to send the next OUT token.
|
|
|__________________________________________________|
|
|
|
|
* Non-Mentor DMA engines can of course work differently.
|
|
*/
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Context: controller locked, IRQs blocked, endpoint selected
|
|
*/
|
|
static void rxstate(struct musb *musb, struct musb_request *req)
|
|
{
|
|
const u8 epnum = req->epnum;
|
|
struct usb_request *request = &req->request;
|
|
struct musb_ep *musb_ep;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
unsigned fifo_count = 0;
|
|
u16 len;
|
|
u16 csr = musb_readw(epio, MUSB_RXCSR);
|
|
struct musb_hw_ep *hw_ep = &musb->endpoints[epnum];
|
|
u8 use_mode_1;
|
|
|
|
if (hw_ep->is_shared_fifo)
|
|
musb_ep = &hw_ep->ep_in;
|
|
else
|
|
musb_ep = &hw_ep->ep_out;
|
|
|
|
len = musb_ep->packet_sz;
|
|
|
|
/* Check if EP is disabled */
|
|
if (!musb_ep->desc) {
|
|
dev_dbg(musb->controller, "ep:%s disabled - ignore request\n",
|
|
musb_ep->end_point.name);
|
|
return;
|
|
}
|
|
|
|
/* We shouldn't get here while DMA is active, but we do... */
|
|
if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) {
|
|
dev_dbg(musb->controller, "DMA pending...\n");
|
|
return;
|
|
}
|
|
|
|
if (csr & MUSB_RXCSR_P_SENDSTALL) {
|
|
dev_dbg(musb->controller, "%s stalling, RXCSR %04x\n",
|
|
musb_ep->end_point.name, csr);
|
|
return;
|
|
}
|
|
|
|
if (is_cppi_enabled() && is_buffer_mapped(req)) {
|
|
struct dma_controller *c = musb->dma_controller;
|
|
struct dma_channel *channel = musb_ep->dma;
|
|
|
|
/* NOTE: CPPI won't actually stop advancing the DMA
|
|
* queue after short packet transfers, so this is almost
|
|
* always going to run as IRQ-per-packet DMA so that
|
|
* faults will be handled correctly.
|
|
*/
|
|
if (c->channel_program(channel,
|
|
musb_ep->packet_sz,
|
|
!request->short_not_ok,
|
|
request->dma + request->actual,
|
|
request->length - request->actual)) {
|
|
|
|
/* make sure that if an rxpkt arrived after the irq,
|
|
* the cppi engine will be ready to take it as soon
|
|
* as DMA is enabled
|
|
*/
|
|
csr &= ~(MUSB_RXCSR_AUTOCLEAR
|
|
| MUSB_RXCSR_DMAMODE);
|
|
csr |= MUSB_RXCSR_DMAENAB | MUSB_RXCSR_P_WZC_BITS;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (csr & MUSB_RXCSR_RXPKTRDY) {
|
|
len = musb_readw(epio, MUSB_RXCOUNT);
|
|
|
|
/*
|
|
* Enable Mode 1 on RX transfers only when short_not_ok flag
|
|
* is set. Currently short_not_ok flag is set only from
|
|
* file_storage and f_mass_storage drivers
|
|
*/
|
|
|
|
if (request->short_not_ok && len == musb_ep->packet_sz)
|
|
use_mode_1 = 1;
|
|
else
|
|
use_mode_1 = 0;
|
|
|
|
if (request->actual < request->length) {
|
|
#ifdef CONFIG_USB_INVENTRA_DMA
|
|
if (is_buffer_mapped(req)) {
|
|
struct dma_controller *c;
|
|
struct dma_channel *channel;
|
|
int use_dma = 0;
|
|
|
|
c = musb->dma_controller;
|
|
channel = musb_ep->dma;
|
|
|
|
/* We use DMA Req mode 0 in rx_csr, and DMA controller operates in
|
|
* mode 0 only. So we do not get endpoint interrupts due to DMA
|
|
* completion. We only get interrupts from DMA controller.
|
|
*
|
|
* We could operate in DMA mode 1 if we knew the size of the tranfer
|
|
* in advance. For mass storage class, request->length = what the host
|
|
* sends, so that'd work. But for pretty much everything else,
|
|
* request->length is routinely more than what the host sends. For
|
|
* most these gadgets, end of is signified either by a short packet,
|
|
* or filling the last byte of the buffer. (Sending extra data in
|
|
* that last pckate should trigger an overflow fault.) But in mode 1,
|
|
* we don't get DMA completion interrupt for short packets.
|
|
*
|
|
* Theoretically, we could enable DMAReq irq (MUSB_RXCSR_DMAMODE = 1),
|
|
* to get endpoint interrupt on every DMA req, but that didn't seem
|
|
* to work reliably.
|
|
*
|
|
* REVISIT an updated g_file_storage can set req->short_not_ok, which
|
|
* then becomes usable as a runtime "use mode 1" hint...
|
|
*/
|
|
|
|
/* Experimental: Mode1 works with mass storage use cases */
|
|
if (use_mode_1) {
|
|
csr |= MUSB_RXCSR_AUTOCLEAR;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
csr |= MUSB_RXCSR_DMAENAB;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
|
|
/*
|
|
* this special sequence (enabling and then
|
|
* disabling MUSB_RXCSR_DMAMODE) is required
|
|
* to get DMAReq to activate
|
|
*/
|
|
musb_writew(epio, MUSB_RXCSR,
|
|
csr | MUSB_RXCSR_DMAMODE);
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
|
|
} else {
|
|
if (!musb_ep->hb_mult &&
|
|
musb_ep->hw_ep->rx_double_buffered)
|
|
csr |= MUSB_RXCSR_AUTOCLEAR;
|
|
csr |= MUSB_RXCSR_DMAENAB;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
if (request->actual < request->length) {
|
|
int transfer_size = 0;
|
|
if (use_mode_1) {
|
|
transfer_size = min(request->length - request->actual,
|
|
channel->max_len);
|
|
musb_ep->dma->desired_mode = 1;
|
|
} else {
|
|
transfer_size = min(request->length - request->actual,
|
|
(unsigned)len);
|
|
musb_ep->dma->desired_mode = 0;
|
|
}
|
|
|
|
use_dma = c->channel_program(
|
|
channel,
|
|
musb_ep->packet_sz,
|
|
channel->desired_mode,
|
|
request->dma
|
|
+ request->actual,
|
|
transfer_size);
|
|
}
|
|
|
|
if (use_dma)
|
|
return;
|
|
}
|
|
#elif defined(CONFIG_USB_UX500_DMA)
|
|
if ((is_buffer_mapped(req)) &&
|
|
(request->actual < request->length)) {
|
|
|
|
struct dma_controller *c;
|
|
struct dma_channel *channel;
|
|
int transfer_size = 0;
|
|
|
|
c = musb->dma_controller;
|
|
channel = musb_ep->dma;
|
|
|
|
/* In case first packet is short */
|
|
if (len < musb_ep->packet_sz)
|
|
transfer_size = len;
|
|
else if (request->short_not_ok)
|
|
transfer_size = min(request->length -
|
|
request->actual,
|
|
channel->max_len);
|
|
else
|
|
transfer_size = min(request->length -
|
|
request->actual,
|
|
(unsigned)len);
|
|
|
|
csr &= ~MUSB_RXCSR_DMAMODE;
|
|
csr |= (MUSB_RXCSR_DMAENAB |
|
|
MUSB_RXCSR_AUTOCLEAR);
|
|
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
|
|
if (transfer_size <= musb_ep->packet_sz) {
|
|
musb_ep->dma->desired_mode = 0;
|
|
} else {
|
|
musb_ep->dma->desired_mode = 1;
|
|
/* Mode must be set after DMAENAB */
|
|
csr |= MUSB_RXCSR_DMAMODE;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
if (c->channel_program(channel,
|
|
musb_ep->packet_sz,
|
|
channel->desired_mode,
|
|
request->dma
|
|
+ request->actual,
|
|
transfer_size))
|
|
|
|
return;
|
|
}
|
|
#endif /* Mentor's DMA */
|
|
|
|
fifo_count = request->length - request->actual;
|
|
dev_dbg(musb->controller, "%s OUT/RX pio fifo %d/%d, maxpacket %d\n",
|
|
musb_ep->end_point.name,
|
|
len, fifo_count,
|
|
musb_ep->packet_sz);
|
|
|
|
fifo_count = min_t(unsigned, len, fifo_count);
|
|
|
|
#ifdef CONFIG_USB_TUSB_OMAP_DMA
|
|
if (tusb_dma_omap() && is_buffer_mapped(req)) {
|
|
struct dma_controller *c = musb->dma_controller;
|
|
struct dma_channel *channel = musb_ep->dma;
|
|
u32 dma_addr = request->dma + request->actual;
|
|
int ret;
|
|
|
|
ret = c->channel_program(channel,
|
|
musb_ep->packet_sz,
|
|
channel->desired_mode,
|
|
dma_addr,
|
|
fifo_count);
|
|
if (ret)
|
|
return;
|
|
}
|
|
#endif
|
|
/*
|
|
* Unmap the dma buffer back to cpu if dma channel
|
|
* programming fails. This buffer is mapped if the
|
|
* channel allocation is successful
|
|
*/
|
|
if (is_buffer_mapped(req)) {
|
|
unmap_dma_buffer(req, musb);
|
|
|
|
/*
|
|
* Clear DMAENAB and AUTOCLEAR for the
|
|
* PIO mode transfer
|
|
*/
|
|
csr &= ~(MUSB_RXCSR_DMAENAB | MUSB_RXCSR_AUTOCLEAR);
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
musb_read_fifo(musb_ep->hw_ep, fifo_count, (u8 *)
|
|
(request->buf + request->actual));
|
|
request->actual += fifo_count;
|
|
|
|
/* REVISIT if we left anything in the fifo, flush
|
|
* it and report -EOVERFLOW
|
|
*/
|
|
|
|
/* ack the read! */
|
|
csr |= MUSB_RXCSR_P_WZC_BITS;
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
}
|
|
|
|
/* reach the end or short packet detected */
|
|
if (request->actual == request->length || len < musb_ep->packet_sz)
|
|
musb_g_giveback(musb_ep, request, 0);
|
|
}
|
|
|
|
/*
|
|
* Data ready for a request; called from IRQ
|
|
*/
|
|
void musb_g_rx(struct musb *musb, u8 epnum)
|
|
{
|
|
u16 csr;
|
|
struct musb_request *req;
|
|
struct usb_request *request;
|
|
void __iomem *mbase = musb->mregs;
|
|
struct musb_ep *musb_ep;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
struct dma_channel *dma;
|
|
struct musb_hw_ep *hw_ep = &musb->endpoints[epnum];
|
|
|
|
if (hw_ep->is_shared_fifo)
|
|
musb_ep = &hw_ep->ep_in;
|
|
else
|
|
musb_ep = &hw_ep->ep_out;
|
|
|
|
musb_ep_select(mbase, epnum);
|
|
|
|
req = next_request(musb_ep);
|
|
if (!req)
|
|
return;
|
|
|
|
request = &req->request;
|
|
|
|
csr = musb_readw(epio, MUSB_RXCSR);
|
|
dma = is_dma_capable() ? musb_ep->dma : NULL;
|
|
|
|
dev_dbg(musb->controller, "<== %s, rxcsr %04x%s %p\n", musb_ep->end_point.name,
|
|
csr, dma ? " (dma)" : "", request);
|
|
|
|
if (csr & MUSB_RXCSR_P_SENTSTALL) {
|
|
csr |= MUSB_RXCSR_P_WZC_BITS;
|
|
csr &= ~MUSB_RXCSR_P_SENTSTALL;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
return;
|
|
}
|
|
|
|
if (csr & MUSB_RXCSR_P_OVERRUN) {
|
|
/* csr |= MUSB_RXCSR_P_WZC_BITS; */
|
|
csr &= ~MUSB_RXCSR_P_OVERRUN;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
|
|
dev_dbg(musb->controller, "%s iso overrun on %p\n", musb_ep->name, request);
|
|
if (request->status == -EINPROGRESS)
|
|
request->status = -EOVERFLOW;
|
|
}
|
|
if (csr & MUSB_RXCSR_INCOMPRX) {
|
|
/* REVISIT not necessarily an error */
|
|
dev_dbg(musb->controller, "%s, incomprx\n", musb_ep->end_point.name);
|
|
}
|
|
|
|
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
|
|
/* "should not happen"; likely RXPKTRDY pending for DMA */
|
|
dev_dbg(musb->controller, "%s busy, csr %04x\n",
|
|
musb_ep->end_point.name, csr);
|
|
return;
|
|
}
|
|
|
|
if (dma && (csr & MUSB_RXCSR_DMAENAB)) {
|
|
csr &= ~(MUSB_RXCSR_AUTOCLEAR
|
|
| MUSB_RXCSR_DMAENAB
|
|
| MUSB_RXCSR_DMAMODE);
|
|
musb_writew(epio, MUSB_RXCSR,
|
|
MUSB_RXCSR_P_WZC_BITS | csr);
|
|
|
|
request->actual += musb_ep->dma->actual_len;
|
|
|
|
dev_dbg(musb->controller, "RXCSR%d %04x, dma off, %04x, len %zu, req %p\n",
|
|
epnum, csr,
|
|
musb_readw(epio, MUSB_RXCSR),
|
|
musb_ep->dma->actual_len, request);
|
|
|
|
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \
|
|
defined(CONFIG_USB_UX500_DMA)
|
|
/* Autoclear doesn't clear RxPktRdy for short packets */
|
|
if ((dma->desired_mode == 0 && !hw_ep->rx_double_buffered)
|
|
|| (dma->actual_len
|
|
& (musb_ep->packet_sz - 1))) {
|
|
/* ack the read! */
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
/* incomplete, and not short? wait for next IN packet */
|
|
if ((request->actual < request->length)
|
|
&& (musb_ep->dma->actual_len
|
|
== musb_ep->packet_sz)) {
|
|
/* In double buffer case, continue to unload fifo if
|
|
* there is Rx packet in FIFO.
|
|
**/
|
|
csr = musb_readw(epio, MUSB_RXCSR);
|
|
if ((csr & MUSB_RXCSR_RXPKTRDY) &&
|
|
hw_ep->rx_double_buffered)
|
|
goto exit;
|
|
return;
|
|
}
|
|
#endif
|
|
musb_g_giveback(musb_ep, request, 0);
|
|
/*
|
|
* In the giveback function the MUSB lock is
|
|
* released and acquired after sometime. During
|
|
* this time period the INDEX register could get
|
|
* changed by the gadget_queue function especially
|
|
* on SMP systems. Reselect the INDEX to be sure
|
|
* we are reading/modifying the right registers
|
|
*/
|
|
musb_ep_select(mbase, epnum);
|
|
|
|
req = next_request(musb_ep);
|
|
if (!req)
|
|
return;
|
|
}
|
|
#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \
|
|
defined(CONFIG_USB_UX500_DMA)
|
|
exit:
|
|
#endif
|
|
/* Analyze request */
|
|
rxstate(musb, req);
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static int musb_gadget_enable(struct usb_ep *ep,
|
|
const struct usb_endpoint_descriptor *desc)
|
|
{
|
|
unsigned long flags;
|
|
struct musb_ep *musb_ep;
|
|
struct musb_hw_ep *hw_ep;
|
|
void __iomem *regs;
|
|
struct musb *musb;
|
|
void __iomem *mbase;
|
|
u8 epnum;
|
|
u16 csr;
|
|
unsigned tmp;
|
|
int status = -EINVAL;
|
|
|
|
if (!ep || !desc)
|
|
return -EINVAL;
|
|
|
|
musb_ep = to_musb_ep(ep);
|
|
hw_ep = musb_ep->hw_ep;
|
|
regs = hw_ep->regs;
|
|
musb = musb_ep->musb;
|
|
mbase = musb->mregs;
|
|
epnum = musb_ep->current_epnum;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
if (musb_ep->desc) {
|
|
status = -EBUSY;
|
|
goto fail;
|
|
}
|
|
musb_ep->type = usb_endpoint_type(desc);
|
|
|
|
/* check direction and (later) maxpacket size against endpoint */
|
|
if (usb_endpoint_num(desc) != epnum)
|
|
goto fail;
|
|
|
|
/* REVISIT this rules out high bandwidth periodic transfers */
|
|
tmp = usb_endpoint_maxp(desc);
|
|
if (tmp & ~0x07ff) {
|
|
int ok;
|
|
|
|
if (usb_endpoint_dir_in(desc))
|
|
ok = musb->hb_iso_tx;
|
|
else
|
|
ok = musb->hb_iso_rx;
|
|
|
|
if (!ok) {
|
|
dev_dbg(musb->controller, "no support for high bandwidth ISO\n");
|
|
goto fail;
|
|
}
|
|
musb_ep->hb_mult = (tmp >> 11) & 3;
|
|
} else {
|
|
musb_ep->hb_mult = 0;
|
|
}
|
|
|
|
musb_ep->packet_sz = tmp & 0x7ff;
|
|
tmp = musb_ep->packet_sz * (musb_ep->hb_mult + 1);
|
|
|
|
/* enable the interrupts for the endpoint, set the endpoint
|
|
* packet size (or fail), set the mode, clear the fifo
|
|
*/
|
|
musb_ep_select(mbase, epnum);
|
|
if (usb_endpoint_dir_in(desc)) {
|
|
u16 int_txe = musb_readw(mbase, MUSB_INTRTXE);
|
|
|
|
if (hw_ep->is_shared_fifo)
|
|
musb_ep->is_in = 1;
|
|
if (!musb_ep->is_in)
|
|
goto fail;
|
|
|
|
if (tmp > hw_ep->max_packet_sz_tx) {
|
|
dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n");
|
|
goto fail;
|
|
}
|
|
|
|
int_txe |= (1 << epnum);
|
|
musb_writew(mbase, MUSB_INTRTXE, int_txe);
|
|
|
|
/* REVISIT if can_bulk_split(), use by updating "tmp";
|
|
* likewise high bandwidth periodic tx
|
|
*/
|
|
/* Set TXMAXP with the FIFO size of the endpoint
|
|
* to disable double buffering mode.
|
|
*/
|
|
if (musb->double_buffer_not_ok)
|
|
musb_writew(regs, MUSB_TXMAXP, hw_ep->max_packet_sz_tx);
|
|
else
|
|
musb_writew(regs, MUSB_TXMAXP, musb_ep->packet_sz
|
|
| (musb_ep->hb_mult << 11));
|
|
|
|
csr = MUSB_TXCSR_MODE | MUSB_TXCSR_CLRDATATOG;
|
|
if (musb_readw(regs, MUSB_TXCSR)
|
|
& MUSB_TXCSR_FIFONOTEMPTY)
|
|
csr |= MUSB_TXCSR_FLUSHFIFO;
|
|
if (musb_ep->type == USB_ENDPOINT_XFER_ISOC)
|
|
csr |= MUSB_TXCSR_P_ISO;
|
|
|
|
/* set twice in case of double buffering */
|
|
musb_writew(regs, MUSB_TXCSR, csr);
|
|
/* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */
|
|
musb_writew(regs, MUSB_TXCSR, csr);
|
|
|
|
} else {
|
|
u16 int_rxe = musb_readw(mbase, MUSB_INTRRXE);
|
|
|
|
if (hw_ep->is_shared_fifo)
|
|
musb_ep->is_in = 0;
|
|
if (musb_ep->is_in)
|
|
goto fail;
|
|
|
|
if (tmp > hw_ep->max_packet_sz_rx) {
|
|
dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n");
|
|
goto fail;
|
|
}
|
|
|
|
int_rxe |= (1 << epnum);
|
|
musb_writew(mbase, MUSB_INTRRXE, int_rxe);
|
|
|
|
/* REVISIT if can_bulk_combine() use by updating "tmp"
|
|
* likewise high bandwidth periodic rx
|
|
*/
|
|
/* Set RXMAXP with the FIFO size of the endpoint
|
|
* to disable double buffering mode.
|
|
*/
|
|
if (musb->double_buffer_not_ok)
|
|
musb_writew(regs, MUSB_RXMAXP, hw_ep->max_packet_sz_tx);
|
|
else
|
|
musb_writew(regs, MUSB_RXMAXP, musb_ep->packet_sz
|
|
| (musb_ep->hb_mult << 11));
|
|
|
|
/* force shared fifo to OUT-only mode */
|
|
if (hw_ep->is_shared_fifo) {
|
|
csr = musb_readw(regs, MUSB_TXCSR);
|
|
csr &= ~(MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY);
|
|
musb_writew(regs, MUSB_TXCSR, csr);
|
|
}
|
|
|
|
csr = MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_CLRDATATOG;
|
|
if (musb_ep->type == USB_ENDPOINT_XFER_ISOC)
|
|
csr |= MUSB_RXCSR_P_ISO;
|
|
else if (musb_ep->type == USB_ENDPOINT_XFER_INT)
|
|
csr |= MUSB_RXCSR_DISNYET;
|
|
|
|
/* set twice in case of double buffering */
|
|
musb_writew(regs, MUSB_RXCSR, csr);
|
|
musb_writew(regs, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
/* NOTE: all the I/O code _should_ work fine without DMA, in case
|
|
* for some reason you run out of channels here.
|
|
*/
|
|
if (is_dma_capable() && musb->dma_controller) {
|
|
struct dma_controller *c = musb->dma_controller;
|
|
|
|
musb_ep->dma = c->channel_alloc(c, hw_ep,
|
|
(desc->bEndpointAddress & USB_DIR_IN));
|
|
} else
|
|
musb_ep->dma = NULL;
|
|
|
|
musb_ep->desc = desc;
|
|
musb_ep->busy = 0;
|
|
musb_ep->wedged = 0;
|
|
status = 0;
|
|
|
|
pr_debug("%s periph: enabled %s for %s %s, %smaxpacket %d\n",
|
|
musb_driver_name, musb_ep->end_point.name,
|
|
({ char *s; switch (musb_ep->type) {
|
|
case USB_ENDPOINT_XFER_BULK: s = "bulk"; break;
|
|
case USB_ENDPOINT_XFER_INT: s = "int"; break;
|
|
default: s = "iso"; break;
|
|
}; s; }),
|
|
musb_ep->is_in ? "IN" : "OUT",
|
|
musb_ep->dma ? "dma, " : "",
|
|
musb_ep->packet_sz);
|
|
|
|
schedule_work(&musb->irq_work);
|
|
|
|
fail:
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Disable an endpoint flushing all requests queued.
|
|
*/
|
|
static int musb_gadget_disable(struct usb_ep *ep)
|
|
{
|
|
unsigned long flags;
|
|
struct musb *musb;
|
|
u8 epnum;
|
|
struct musb_ep *musb_ep;
|
|
void __iomem *epio;
|
|
int status = 0;
|
|
|
|
musb_ep = to_musb_ep(ep);
|
|
musb = musb_ep->musb;
|
|
epnum = musb_ep->current_epnum;
|
|
epio = musb->endpoints[epnum].regs;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
musb_ep_select(musb->mregs, epnum);
|
|
|
|
/* zero the endpoint sizes */
|
|
if (musb_ep->is_in) {
|
|
u16 int_txe = musb_readw(musb->mregs, MUSB_INTRTXE);
|
|
int_txe &= ~(1 << epnum);
|
|
musb_writew(musb->mregs, MUSB_INTRTXE, int_txe);
|
|
musb_writew(epio, MUSB_TXMAXP, 0);
|
|
} else {
|
|
u16 int_rxe = musb_readw(musb->mregs, MUSB_INTRRXE);
|
|
int_rxe &= ~(1 << epnum);
|
|
musb_writew(musb->mregs, MUSB_INTRRXE, int_rxe);
|
|
musb_writew(epio, MUSB_RXMAXP, 0);
|
|
}
|
|
|
|
musb_ep->desc = NULL;
|
|
#ifndef __UBOOT__
|
|
musb_ep->end_point.desc = NULL;
|
|
#endif
|
|
|
|
/* abort all pending DMA and requests */
|
|
nuke(musb_ep, -ESHUTDOWN);
|
|
|
|
schedule_work(&musb->irq_work);
|
|
|
|
spin_unlock_irqrestore(&(musb->lock), flags);
|
|
|
|
dev_dbg(musb->controller, "%s\n", musb_ep->end_point.name);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Allocate a request for an endpoint.
|
|
* Reused by ep0 code.
|
|
*/
|
|
struct usb_request *musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
struct musb *musb = musb_ep->musb;
|
|
struct musb_request *request = NULL;
|
|
|
|
request = kzalloc(sizeof *request, gfp_flags);
|
|
if (!request) {
|
|
dev_dbg(musb->controller, "not enough memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
request->request.dma = DMA_ADDR_INVALID;
|
|
request->epnum = musb_ep->current_epnum;
|
|
request->ep = musb_ep;
|
|
|
|
return &request->request;
|
|
}
|
|
|
|
/*
|
|
* Free a request
|
|
* Reused by ep0 code.
|
|
*/
|
|
void musb_free_request(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
kfree(to_musb_request(req));
|
|
}
|
|
|
|
static LIST_HEAD(buffers);
|
|
|
|
struct free_record {
|
|
struct list_head list;
|
|
struct device *dev;
|
|
unsigned bytes;
|
|
dma_addr_t dma;
|
|
};
|
|
|
|
/*
|
|
* Context: controller locked, IRQs blocked.
|
|
*/
|
|
void musb_ep_restart(struct musb *musb, struct musb_request *req)
|
|
{
|
|
dev_dbg(musb->controller, "<== %s request %p len %u on hw_ep%d\n",
|
|
req->tx ? "TX/IN" : "RX/OUT",
|
|
&req->request, req->request.length, req->epnum);
|
|
|
|
musb_ep_select(musb->mregs, req->epnum);
|
|
if (req->tx)
|
|
txstate(musb, req);
|
|
else
|
|
rxstate(musb, req);
|
|
}
|
|
|
|
static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req,
|
|
gfp_t gfp_flags)
|
|
{
|
|
struct musb_ep *musb_ep;
|
|
struct musb_request *request;
|
|
struct musb *musb;
|
|
int status = 0;
|
|
unsigned long lockflags;
|
|
|
|
if (!ep || !req)
|
|
return -EINVAL;
|
|
if (!req->buf)
|
|
return -ENODATA;
|
|
|
|
musb_ep = to_musb_ep(ep);
|
|
musb = musb_ep->musb;
|
|
|
|
request = to_musb_request(req);
|
|
request->musb = musb;
|
|
|
|
if (request->ep != musb_ep)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(musb->controller, "<== to %s request=%p\n", ep->name, req);
|
|
|
|
/* request is mine now... */
|
|
request->request.actual = 0;
|
|
request->request.status = -EINPROGRESS;
|
|
request->epnum = musb_ep->current_epnum;
|
|
request->tx = musb_ep->is_in;
|
|
|
|
map_dma_buffer(request, musb, musb_ep);
|
|
|
|
spin_lock_irqsave(&musb->lock, lockflags);
|
|
|
|
/* don't queue if the ep is down */
|
|
if (!musb_ep->desc) {
|
|
dev_dbg(musb->controller, "req %p queued to %s while ep %s\n",
|
|
req, ep->name, "disabled");
|
|
status = -ESHUTDOWN;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* add request to the list */
|
|
list_add_tail(&request->list, &musb_ep->req_list);
|
|
|
|
/* it this is the head of the queue, start i/o ... */
|
|
if (!musb_ep->busy && &request->list == musb_ep->req_list.next)
|
|
musb_ep_restart(musb, request);
|
|
|
|
cleanup:
|
|
spin_unlock_irqrestore(&musb->lock, lockflags);
|
|
return status;
|
|
}
|
|
|
|
static int musb_gadget_dequeue(struct usb_ep *ep, struct usb_request *request)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
struct musb_request *req = to_musb_request(request);
|
|
struct musb_request *r;
|
|
unsigned long flags;
|
|
int status = 0;
|
|
struct musb *musb = musb_ep->musb;
|
|
|
|
if (!ep || !request || to_musb_request(request)->ep != musb_ep)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
list_for_each_entry(r, &musb_ep->req_list, list) {
|
|
if (r == req)
|
|
break;
|
|
}
|
|
if (r != req) {
|
|
dev_dbg(musb->controller, "request %p not queued to %s\n", request, ep->name);
|
|
status = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
/* if the hardware doesn't have the request, easy ... */
|
|
if (musb_ep->req_list.next != &req->list || musb_ep->busy)
|
|
musb_g_giveback(musb_ep, request, -ECONNRESET);
|
|
|
|
/* ... else abort the dma transfer ... */
|
|
else if (is_dma_capable() && musb_ep->dma) {
|
|
struct dma_controller *c = musb->dma_controller;
|
|
|
|
musb_ep_select(musb->mregs, musb_ep->current_epnum);
|
|
if (c->channel_abort)
|
|
status = c->channel_abort(musb_ep->dma);
|
|
else
|
|
status = -EBUSY;
|
|
if (status == 0)
|
|
musb_g_giveback(musb_ep, request, -ECONNRESET);
|
|
} else {
|
|
/* NOTE: by sticking to easily tested hardware/driver states,
|
|
* we leave counting of in-flight packets imprecise.
|
|
*/
|
|
musb_g_giveback(musb_ep, request, -ECONNRESET);
|
|
}
|
|
|
|
done:
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Set or clear the halt bit of an endpoint. A halted enpoint won't tx/rx any
|
|
* data but will queue requests.
|
|
*
|
|
* exported to ep0 code
|
|
*/
|
|
static int musb_gadget_set_halt(struct usb_ep *ep, int value)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
u8 epnum = musb_ep->current_epnum;
|
|
struct musb *musb = musb_ep->musb;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
void __iomem *mbase;
|
|
unsigned long flags;
|
|
u16 csr;
|
|
struct musb_request *request;
|
|
int status = 0;
|
|
|
|
if (!ep)
|
|
return -EINVAL;
|
|
mbase = musb->mregs;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
if ((USB_ENDPOINT_XFER_ISOC == musb_ep->type)) {
|
|
status = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
musb_ep_select(mbase, epnum);
|
|
|
|
request = next_request(musb_ep);
|
|
if (value) {
|
|
if (request) {
|
|
dev_dbg(musb->controller, "request in progress, cannot halt %s\n",
|
|
ep->name);
|
|
status = -EAGAIN;
|
|
goto done;
|
|
}
|
|
/* Cannot portably stall with non-empty FIFO */
|
|
if (musb_ep->is_in) {
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
if (csr & MUSB_TXCSR_FIFONOTEMPTY) {
|
|
dev_dbg(musb->controller, "FIFO busy, cannot halt %s\n", ep->name);
|
|
status = -EAGAIN;
|
|
goto done;
|
|
}
|
|
}
|
|
} else
|
|
musb_ep->wedged = 0;
|
|
|
|
/* set/clear the stall and toggle bits */
|
|
dev_dbg(musb->controller, "%s: %s stall\n", ep->name, value ? "set" : "clear");
|
|
if (musb_ep->is_in) {
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
csr |= MUSB_TXCSR_P_WZC_BITS
|
|
| MUSB_TXCSR_CLRDATATOG;
|
|
if (value)
|
|
csr |= MUSB_TXCSR_P_SENDSTALL;
|
|
else
|
|
csr &= ~(MUSB_TXCSR_P_SENDSTALL
|
|
| MUSB_TXCSR_P_SENTSTALL);
|
|
csr &= ~MUSB_TXCSR_TXPKTRDY;
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
} else {
|
|
csr = musb_readw(epio, MUSB_RXCSR);
|
|
csr |= MUSB_RXCSR_P_WZC_BITS
|
|
| MUSB_RXCSR_FLUSHFIFO
|
|
| MUSB_RXCSR_CLRDATATOG;
|
|
if (value)
|
|
csr |= MUSB_RXCSR_P_SENDSTALL;
|
|
else
|
|
csr &= ~(MUSB_RXCSR_P_SENDSTALL
|
|
| MUSB_RXCSR_P_SENTSTALL);
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
/* maybe start the first request in the queue */
|
|
if (!musb_ep->busy && !value && request) {
|
|
dev_dbg(musb->controller, "restarting the request\n");
|
|
musb_ep_restart(musb, request);
|
|
}
|
|
|
|
done:
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
return status;
|
|
}
|
|
|
|
#ifndef __UBOOT__
|
|
/*
|
|
* Sets the halt feature with the clear requests ignored
|
|
*/
|
|
static int musb_gadget_set_wedge(struct usb_ep *ep)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
|
|
if (!ep)
|
|
return -EINVAL;
|
|
|
|
musb_ep->wedged = 1;
|
|
|
|
return usb_ep_set_halt(ep);
|
|
}
|
|
#endif
|
|
|
|
static int musb_gadget_fifo_status(struct usb_ep *ep)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
void __iomem *epio = musb_ep->hw_ep->regs;
|
|
int retval = -EINVAL;
|
|
|
|
if (musb_ep->desc && !musb_ep->is_in) {
|
|
struct musb *musb = musb_ep->musb;
|
|
int epnum = musb_ep->current_epnum;
|
|
void __iomem *mbase = musb->mregs;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
musb_ep_select(mbase, epnum);
|
|
/* FIXME return zero unless RXPKTRDY is set */
|
|
retval = musb_readw(epio, MUSB_RXCOUNT);
|
|
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static void musb_gadget_fifo_flush(struct usb_ep *ep)
|
|
{
|
|
struct musb_ep *musb_ep = to_musb_ep(ep);
|
|
struct musb *musb = musb_ep->musb;
|
|
u8 epnum = musb_ep->current_epnum;
|
|
void __iomem *epio = musb->endpoints[epnum].regs;
|
|
void __iomem *mbase;
|
|
unsigned long flags;
|
|
u16 csr, int_txe;
|
|
|
|
mbase = musb->mregs;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
musb_ep_select(mbase, (u8) epnum);
|
|
|
|
/* disable interrupts */
|
|
int_txe = musb_readw(mbase, MUSB_INTRTXE);
|
|
musb_writew(mbase, MUSB_INTRTXE, int_txe & ~(1 << epnum));
|
|
|
|
if (musb_ep->is_in) {
|
|
csr = musb_readw(epio, MUSB_TXCSR);
|
|
if (csr & MUSB_TXCSR_FIFONOTEMPTY) {
|
|
csr |= MUSB_TXCSR_FLUSHFIFO | MUSB_TXCSR_P_WZC_BITS;
|
|
/*
|
|
* Setting both TXPKTRDY and FLUSHFIFO makes controller
|
|
* to interrupt current FIFO loading, but not flushing
|
|
* the already loaded ones.
|
|
*/
|
|
csr &= ~MUSB_TXCSR_TXPKTRDY;
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
/* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */
|
|
musb_writew(epio, MUSB_TXCSR, csr);
|
|
}
|
|
} else {
|
|
csr = musb_readw(epio, MUSB_RXCSR);
|
|
csr |= MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_P_WZC_BITS;
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
musb_writew(epio, MUSB_RXCSR, csr);
|
|
}
|
|
|
|
/* re-enable interrupt */
|
|
musb_writew(mbase, MUSB_INTRTXE, int_txe);
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
}
|
|
|
|
static const struct usb_ep_ops musb_ep_ops = {
|
|
.enable = musb_gadget_enable,
|
|
.disable = musb_gadget_disable,
|
|
.alloc_request = musb_alloc_request,
|
|
.free_request = musb_free_request,
|
|
.queue = musb_gadget_queue,
|
|
.dequeue = musb_gadget_dequeue,
|
|
.set_halt = musb_gadget_set_halt,
|
|
#ifndef __UBOOT__
|
|
.set_wedge = musb_gadget_set_wedge,
|
|
#endif
|
|
.fifo_status = musb_gadget_fifo_status,
|
|
.fifo_flush = musb_gadget_fifo_flush
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int musb_gadget_get_frame(struct usb_gadget *gadget)
|
|
{
|
|
struct musb *musb = gadget_to_musb(gadget);
|
|
|
|
return (int)musb_readw(musb->mregs, MUSB_FRAME);
|
|
}
|
|
|
|
static int musb_gadget_wakeup(struct usb_gadget *gadget)
|
|
{
|
|
#ifndef __UBOOT__
|
|
struct musb *musb = gadget_to_musb(gadget);
|
|
void __iomem *mregs = musb->mregs;
|
|
unsigned long flags;
|
|
int status = -EINVAL;
|
|
u8 power, devctl;
|
|
int retries;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
switch (musb->xceiv->state) {
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
/* NOTE: OTG state machine doesn't include B_SUSPENDED;
|
|
* that's part of the standard usb 1.1 state machine, and
|
|
* doesn't affect OTG transitions.
|
|
*/
|
|
if (musb->may_wakeup && musb->is_suspended)
|
|
break;
|
|
goto done;
|
|
case OTG_STATE_B_IDLE:
|
|
/* Start SRP ... OTG not required. */
|
|
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
|
dev_dbg(musb->controller, "Sending SRP: devctl: %02x\n", devctl);
|
|
devctl |= MUSB_DEVCTL_SESSION;
|
|
musb_writeb(mregs, MUSB_DEVCTL, devctl);
|
|
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
|
retries = 100;
|
|
while (!(devctl & MUSB_DEVCTL_SESSION)) {
|
|
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
|
if (retries-- < 1)
|
|
break;
|
|
}
|
|
retries = 10000;
|
|
while (devctl & MUSB_DEVCTL_SESSION) {
|
|
devctl = musb_readb(mregs, MUSB_DEVCTL);
|
|
if (retries-- < 1)
|
|
break;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
otg_start_srp(musb->xceiv->otg);
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
/* Block idling for at least 1s */
|
|
musb_platform_try_idle(musb,
|
|
jiffies + msecs_to_jiffies(1 * HZ));
|
|
|
|
status = 0;
|
|
goto done;
|
|
default:
|
|
dev_dbg(musb->controller, "Unhandled wake: %s\n",
|
|
otg_state_string(musb->xceiv->state));
|
|
goto done;
|
|
}
|
|
|
|
status = 0;
|
|
|
|
power = musb_readb(mregs, MUSB_POWER);
|
|
power |= MUSB_POWER_RESUME;
|
|
musb_writeb(mregs, MUSB_POWER, power);
|
|
dev_dbg(musb->controller, "issue wakeup\n");
|
|
|
|
/* FIXME do this next chunk in a timer callback, no udelay */
|
|
mdelay(2);
|
|
|
|
power = musb_readb(mregs, MUSB_POWER);
|
|
power &= ~MUSB_POWER_RESUME;
|
|
musb_writeb(mregs, MUSB_POWER, power);
|
|
done:
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
return status;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
musb_gadget_set_self_powered(struct usb_gadget *gadget, int is_selfpowered)
|
|
{
|
|
struct musb *musb = gadget_to_musb(gadget);
|
|
|
|
musb->is_self_powered = !!is_selfpowered;
|
|
return 0;
|
|
}
|
|
|
|
static void musb_pullup(struct musb *musb, int is_on)
|
|
{
|
|
u8 power;
|
|
|
|
power = musb_readb(musb->mregs, MUSB_POWER);
|
|
if (is_on)
|
|
power |= MUSB_POWER_SOFTCONN;
|
|
else
|
|
power &= ~MUSB_POWER_SOFTCONN;
|
|
|
|
/* FIXME if on, HdrcStart; if off, HdrcStop */
|
|
|
|
dev_dbg(musb->controller, "gadget D+ pullup %s\n",
|
|
is_on ? "on" : "off");
|
|
musb_writeb(musb->mregs, MUSB_POWER, power);
|
|
}
|
|
|
|
#if 0
|
|
static int musb_gadget_vbus_session(struct usb_gadget *gadget, int is_active)
|
|
{
|
|
dev_dbg(musb->controller, "<= %s =>\n", __func__);
|
|
|
|
/*
|
|
* FIXME iff driver's softconnect flag is set (as it is during probe,
|
|
* though that can clear it), just musb_pullup().
|
|
*/
|
|
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
static int musb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA)
|
|
{
|
|
#ifndef __UBOOT__
|
|
struct musb *musb = gadget_to_musb(gadget);
|
|
|
|
if (!musb->xceiv->set_power)
|
|
return -EOPNOTSUPP;
|
|
return usb_phy_set_power(musb->xceiv, mA);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int musb_gadget_pullup(struct usb_gadget *gadget, int is_on)
|
|
{
|
|
struct musb *musb = gadget_to_musb(gadget);
|
|
unsigned long flags;
|
|
|
|
is_on = !!is_on;
|
|
|
|
pm_runtime_get_sync(musb->controller);
|
|
|
|
/* NOTE: this assumes we are sensing vbus; we'd rather
|
|
* not pullup unless the B-session is active.
|
|
*/
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
if (is_on != musb->softconnect) {
|
|
musb->softconnect = is_on;
|
|
musb_pullup(musb, is_on);
|
|
}
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
|
|
pm_runtime_put(musb->controller);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef __UBOOT__
|
|
static int musb_gadget_start(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver);
|
|
static int musb_gadget_stop(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver);
|
|
#else
|
|
static int musb_gadget_stop(struct usb_gadget *g)
|
|
{
|
|
struct musb *musb = gadget_to_musb(g);
|
|
|
|
musb_stop(musb);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct usb_gadget_ops musb_gadget_operations = {
|
|
.get_frame = musb_gadget_get_frame,
|
|
.wakeup = musb_gadget_wakeup,
|
|
.set_selfpowered = musb_gadget_set_self_powered,
|
|
/* .vbus_session = musb_gadget_vbus_session, */
|
|
.vbus_draw = musb_gadget_vbus_draw,
|
|
.pullup = musb_gadget_pullup,
|
|
#ifndef __UBOOT__
|
|
.udc_start = musb_gadget_start,
|
|
.udc_stop = musb_gadget_stop,
|
|
#else
|
|
.udc_start = musb_gadget_start,
|
|
.udc_stop = musb_gadget_stop,
|
|
#endif
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
/* Registration */
|
|
|
|
/* Only this registration code "knows" the rule (from USB standards)
|
|
* about there being only one external upstream port. It assumes
|
|
* all peripheral ports are external...
|
|
*/
|
|
|
|
#ifndef __UBOOT__
|
|
static void musb_gadget_release(struct device *dev)
|
|
{
|
|
/* kref_put(WHAT) */
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
}
|
|
#endif
|
|
|
|
|
|
static void __devinit
|
|
init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in)
|
|
{
|
|
struct musb_hw_ep *hw_ep = musb->endpoints + epnum;
|
|
|
|
memset(ep, 0, sizeof *ep);
|
|
|
|
ep->current_epnum = epnum;
|
|
ep->musb = musb;
|
|
ep->hw_ep = hw_ep;
|
|
ep->is_in = is_in;
|
|
|
|
INIT_LIST_HEAD(&ep->req_list);
|
|
|
|
sprintf(ep->name, "ep%d%s", epnum,
|
|
(!epnum || hw_ep->is_shared_fifo) ? "" : (
|
|
is_in ? "in" : "out"));
|
|
ep->end_point.name = ep->name;
|
|
INIT_LIST_HEAD(&ep->end_point.ep_list);
|
|
if (!epnum) {
|
|
ep->end_point.maxpacket = 64;
|
|
ep->end_point.ops = &musb_g_ep0_ops;
|
|
musb->g.ep0 = &ep->end_point;
|
|
} else {
|
|
if (is_in)
|
|
ep->end_point.maxpacket = hw_ep->max_packet_sz_tx;
|
|
else
|
|
ep->end_point.maxpacket = hw_ep->max_packet_sz_rx;
|
|
ep->end_point.ops = &musb_ep_ops;
|
|
list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize the endpoints exposed to peripheral drivers, with backlinks
|
|
* to the rest of the driver state.
|
|
*/
|
|
static inline void __devinit musb_g_init_endpoints(struct musb *musb)
|
|
{
|
|
u8 epnum;
|
|
struct musb_hw_ep *hw_ep;
|
|
unsigned count = 0;
|
|
|
|
/* initialize endpoint list just once */
|
|
INIT_LIST_HEAD(&(musb->g.ep_list));
|
|
|
|
for (epnum = 0, hw_ep = musb->endpoints;
|
|
epnum < musb->nr_endpoints;
|
|
epnum++, hw_ep++) {
|
|
if (hw_ep->is_shared_fifo /* || !epnum */) {
|
|
init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0);
|
|
count++;
|
|
} else {
|
|
if (hw_ep->max_packet_sz_tx) {
|
|
init_peripheral_ep(musb, &hw_ep->ep_in,
|
|
epnum, 1);
|
|
count++;
|
|
}
|
|
if (hw_ep->max_packet_sz_rx) {
|
|
init_peripheral_ep(musb, &hw_ep->ep_out,
|
|
epnum, 0);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* called once during driver setup to initialize and link into
|
|
* the driver model; memory is zeroed.
|
|
*/
|
|
int __devinit musb_gadget_setup(struct musb *musb)
|
|
{
|
|
int status;
|
|
|
|
/* REVISIT minor race: if (erroneously) setting up two
|
|
* musb peripherals at the same time, only the bus lock
|
|
* is probably held.
|
|
*/
|
|
|
|
musb->g.ops = &musb_gadget_operations;
|
|
#ifndef __UBOOT__
|
|
musb->g.max_speed = USB_SPEED_HIGH;
|
|
#endif
|
|
musb->g.speed = USB_SPEED_UNKNOWN;
|
|
|
|
#ifndef __UBOOT__
|
|
/* this "gadget" abstracts/virtualizes the controller */
|
|
dev_set_name(&musb->g.dev, "gadget");
|
|
musb->g.dev.parent = musb->controller;
|
|
musb->g.dev.dma_mask = musb->controller->dma_mask;
|
|
musb->g.dev.release = musb_gadget_release;
|
|
#endif
|
|
musb->g.name = musb_driver_name;
|
|
|
|
#ifndef __UBOOT__
|
|
if (is_otg_enabled(musb))
|
|
musb->g.is_otg = 1;
|
|
#endif
|
|
|
|
musb_g_init_endpoints(musb);
|
|
|
|
musb->is_active = 0;
|
|
musb_platform_try_idle(musb, 0);
|
|
|
|
#ifndef __UBOOT__
|
|
status = device_register(&musb->g.dev);
|
|
if (status != 0) {
|
|
put_device(&musb->g.dev);
|
|
return status;
|
|
}
|
|
status = usb_add_gadget_udc(musb->controller, &musb->g);
|
|
if (status)
|
|
goto err;
|
|
#endif
|
|
|
|
return 0;
|
|
#ifndef __UBOOT__
|
|
err:
|
|
musb->g.dev.parent = NULL;
|
|
device_unregister(&musb->g.dev);
|
|
return status;
|
|
#endif
|
|
}
|
|
|
|
void musb_gadget_cleanup(struct musb *musb)
|
|
{
|
|
#ifndef __UBOOT__
|
|
usb_del_gadget_udc(&musb->g);
|
|
if (musb->g.dev.parent)
|
|
device_unregister(&musb->g.dev);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Register the gadget driver. Used by gadget drivers when
|
|
* registering themselves with the controller.
|
|
*
|
|
* -EINVAL something went wrong (not driver)
|
|
* -EBUSY another gadget is already using the controller
|
|
* -ENOMEM no memory to perform the operation
|
|
*
|
|
* @param driver the gadget driver
|
|
* @return <0 if error, 0 if everything is fine
|
|
*/
|
|
#ifndef __UBOOT__
|
|
static int musb_gadget_start(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver)
|
|
#else
|
|
int musb_gadget_start(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver)
|
|
#endif
|
|
{
|
|
struct musb *musb = gadget_to_musb(g);
|
|
#ifndef __UBOOT__
|
|
struct usb_otg *otg = musb->xceiv->otg;
|
|
#endif
|
|
unsigned long flags;
|
|
int retval = -EINVAL;
|
|
|
|
#ifndef __UBOOT__
|
|
if (driver->max_speed < USB_SPEED_HIGH)
|
|
goto err0;
|
|
#endif
|
|
|
|
pm_runtime_get_sync(musb->controller);
|
|
|
|
#ifndef __UBOOT__
|
|
dev_dbg(musb->controller, "registering driver %s\n", driver->function);
|
|
#endif
|
|
|
|
musb->softconnect = 0;
|
|
musb->gadget_driver = driver;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
musb->is_active = 1;
|
|
|
|
#ifndef __UBOOT__
|
|
otg_set_peripheral(otg, &musb->g);
|
|
musb->xceiv->state = OTG_STATE_B_IDLE;
|
|
|
|
/*
|
|
* FIXME this ignores the softconnect flag. Drivers are
|
|
* allowed hold the peripheral inactive until for example
|
|
* userspace hooks up printer hardware or DSP codecs, so
|
|
* hosts only see fully functional devices.
|
|
*/
|
|
|
|
if (!is_otg_enabled(musb))
|
|
#endif
|
|
musb_start(musb);
|
|
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
|
|
#ifndef __UBOOT__
|
|
if (is_otg_enabled(musb)) {
|
|
struct usb_hcd *hcd = musb_to_hcd(musb);
|
|
|
|
dev_dbg(musb->controller, "OTG startup...\n");
|
|
|
|
/* REVISIT: funcall to other code, which also
|
|
* handles power budgeting ... this way also
|
|
* ensures HdrcStart is indirectly called.
|
|
*/
|
|
retval = usb_add_hcd(musb_to_hcd(musb), 0, 0);
|
|
if (retval < 0) {
|
|
dev_dbg(musb->controller, "add_hcd failed, %d\n", retval);
|
|
goto err2;
|
|
}
|
|
|
|
if ((musb->xceiv->last_event == USB_EVENT_ID)
|
|
&& otg->set_vbus)
|
|
otg_set_vbus(otg, 1);
|
|
|
|
hcd->self.uses_pio_for_control = 1;
|
|
}
|
|
if (musb->xceiv->last_event == USB_EVENT_NONE)
|
|
pm_runtime_put(musb->controller);
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
#ifndef __UBOOT__
|
|
err2:
|
|
if (!is_otg_enabled(musb))
|
|
musb_stop(musb);
|
|
err0:
|
|
return retval;
|
|
#endif
|
|
}
|
|
|
|
#ifndef __UBOOT__
|
|
static void stop_activity(struct musb *musb, struct usb_gadget_driver *driver)
|
|
{
|
|
int i;
|
|
struct musb_hw_ep *hw_ep;
|
|
|
|
/* don't disconnect if it's not connected */
|
|
if (musb->g.speed == USB_SPEED_UNKNOWN)
|
|
driver = NULL;
|
|
else
|
|
musb->g.speed = USB_SPEED_UNKNOWN;
|
|
|
|
/* deactivate the hardware */
|
|
if (musb->softconnect) {
|
|
musb->softconnect = 0;
|
|
musb_pullup(musb, 0);
|
|
}
|
|
musb_stop(musb);
|
|
|
|
/* killing any outstanding requests will quiesce the driver;
|
|
* then report disconnect
|
|
*/
|
|
if (driver) {
|
|
for (i = 0, hw_ep = musb->endpoints;
|
|
i < musb->nr_endpoints;
|
|
i++, hw_ep++) {
|
|
musb_ep_select(musb->mregs, i);
|
|
if (hw_ep->is_shared_fifo /* || !epnum */) {
|
|
nuke(&hw_ep->ep_in, -ESHUTDOWN);
|
|
} else {
|
|
if (hw_ep->max_packet_sz_tx)
|
|
nuke(&hw_ep->ep_in, -ESHUTDOWN);
|
|
if (hw_ep->max_packet_sz_rx)
|
|
nuke(&hw_ep->ep_out, -ESHUTDOWN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unregister the gadget driver. Used by gadget drivers when
|
|
* unregistering themselves from the controller.
|
|
*
|
|
* @param driver the gadget driver to unregister
|
|
*/
|
|
static int musb_gadget_stop(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver)
|
|
{
|
|
struct musb *musb = gadget_to_musb(g);
|
|
unsigned long flags;
|
|
|
|
if (musb->xceiv->last_event == USB_EVENT_NONE)
|
|
pm_runtime_get_sync(musb->controller);
|
|
|
|
/*
|
|
* REVISIT always use otg_set_peripheral() here too;
|
|
* this needs to shut down the OTG engine.
|
|
*/
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
musb_hnp_stop(musb);
|
|
|
|
(void) musb_gadget_vbus_draw(&musb->g, 0);
|
|
|
|
musb->xceiv->state = OTG_STATE_UNDEFINED;
|
|
stop_activity(musb, driver);
|
|
otg_set_peripheral(musb->xceiv->otg, NULL);
|
|
|
|
dev_dbg(musb->controller, "unregistering driver %s\n", driver->function);
|
|
|
|
musb->is_active = 0;
|
|
musb_platform_try_idle(musb, 0);
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
|
|
if (is_otg_enabled(musb)) {
|
|
usb_remove_hcd(musb_to_hcd(musb));
|
|
/* FIXME we need to be able to register another
|
|
* gadget driver here and have everything work;
|
|
* that currently misbehaves.
|
|
*/
|
|
}
|
|
|
|
if (!is_otg_enabled(musb))
|
|
musb_stop(musb);
|
|
|
|
pm_runtime_put(musb->controller);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
/* lifecycle operations called through plat_uds.c */
|
|
|
|
void musb_g_resume(struct musb *musb)
|
|
{
|
|
#ifndef __UBOOT__
|
|
musb->is_suspended = 0;
|
|
switch (musb->xceiv->state) {
|
|
case OTG_STATE_B_IDLE:
|
|
break;
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
musb->is_active = 1;
|
|
if (musb->gadget_driver && musb->gadget_driver->resume) {
|
|
spin_unlock(&musb->lock);
|
|
musb->gadget_driver->resume(&musb->g);
|
|
spin_lock(&musb->lock);
|
|
}
|
|
break;
|
|
default:
|
|
WARNING("unhandled RESUME transition (%s)\n",
|
|
otg_state_string(musb->xceiv->state));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* called when SOF packets stop for 3+ msec */
|
|
void musb_g_suspend(struct musb *musb)
|
|
{
|
|
#ifndef __UBOOT__
|
|
u8 devctl;
|
|
|
|
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
dev_dbg(musb->controller, "devctl %02x\n", devctl);
|
|
|
|
switch (musb->xceiv->state) {
|
|
case OTG_STATE_B_IDLE:
|
|
if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS)
|
|
musb->xceiv->state = OTG_STATE_B_PERIPHERAL;
|
|
break;
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
musb->is_suspended = 1;
|
|
if (musb->gadget_driver && musb->gadget_driver->suspend) {
|
|
spin_unlock(&musb->lock);
|
|
musb->gadget_driver->suspend(&musb->g);
|
|
spin_lock(&musb->lock);
|
|
}
|
|
break;
|
|
default:
|
|
/* REVISIT if B_HOST, clear DEVCTL.HOSTREQ;
|
|
* A_PERIPHERAL may need care too
|
|
*/
|
|
WARNING("unhandled SUSPEND transition (%s)\n",
|
|
otg_state_string(musb->xceiv->state));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Called during SRP */
|
|
void musb_g_wakeup(struct musb *musb)
|
|
{
|
|
musb_gadget_wakeup(&musb->g);
|
|
}
|
|
|
|
/* called when VBUS drops below session threshold, and in other cases */
|
|
void musb_g_disconnect(struct musb *musb)
|
|
{
|
|
void __iomem *mregs = musb->mregs;
|
|
u8 devctl = musb_readb(mregs, MUSB_DEVCTL);
|
|
|
|
dev_dbg(musb->controller, "devctl %02x\n", devctl);
|
|
|
|
/* clear HR */
|
|
musb_writeb(mregs, MUSB_DEVCTL, devctl & MUSB_DEVCTL_SESSION);
|
|
|
|
/* don't draw vbus until new b-default session */
|
|
(void) musb_gadget_vbus_draw(&musb->g, 0);
|
|
|
|
musb->g.speed = USB_SPEED_UNKNOWN;
|
|
if (musb->gadget_driver && musb->gadget_driver->disconnect) {
|
|
spin_unlock(&musb->lock);
|
|
musb->gadget_driver->disconnect(&musb->g);
|
|
spin_lock(&musb->lock);
|
|
}
|
|
|
|
#ifndef __UBOOT__
|
|
switch (musb->xceiv->state) {
|
|
default:
|
|
dev_dbg(musb->controller, "Unhandled disconnect %s, setting a_idle\n",
|
|
otg_state_string(musb->xceiv->state));
|
|
musb->xceiv->state = OTG_STATE_A_IDLE;
|
|
MUSB_HST_MODE(musb);
|
|
break;
|
|
case OTG_STATE_A_PERIPHERAL:
|
|
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
|
MUSB_HST_MODE(musb);
|
|
break;
|
|
case OTG_STATE_B_WAIT_ACON:
|
|
case OTG_STATE_B_HOST:
|
|
case OTG_STATE_B_PERIPHERAL:
|
|
case OTG_STATE_B_IDLE:
|
|
musb->xceiv->state = OTG_STATE_B_IDLE;
|
|
break;
|
|
case OTG_STATE_B_SRP_INIT:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
musb->is_active = 0;
|
|
}
|
|
|
|
void musb_g_reset(struct musb *musb)
|
|
__releases(musb->lock)
|
|
__acquires(musb->lock)
|
|
{
|
|
void __iomem *mbase = musb->mregs;
|
|
u8 devctl = musb_readb(mbase, MUSB_DEVCTL);
|
|
u8 power;
|
|
|
|
#ifndef __UBOOT__
|
|
dev_dbg(musb->controller, "<== %s addr=%x driver '%s'\n",
|
|
(devctl & MUSB_DEVCTL_BDEVICE)
|
|
? "B-Device" : "A-Device",
|
|
musb_readb(mbase, MUSB_FADDR),
|
|
musb->gadget_driver
|
|
? musb->gadget_driver->driver.name
|
|
: NULL
|
|
);
|
|
#endif
|
|
|
|
/* report disconnect, if we didn't already (flushing EP state) */
|
|
if (musb->g.speed != USB_SPEED_UNKNOWN)
|
|
musb_g_disconnect(musb);
|
|
|
|
/* clear HR */
|
|
else if (devctl & MUSB_DEVCTL_HR)
|
|
musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
|
|
|
|
|
|
/* what speed did we negotiate? */
|
|
power = musb_readb(mbase, MUSB_POWER);
|
|
musb->g.speed = (power & MUSB_POWER_HSMODE)
|
|
? USB_SPEED_HIGH : USB_SPEED_FULL;
|
|
|
|
/* start in USB_STATE_DEFAULT */
|
|
musb->is_active = 1;
|
|
musb->is_suspended = 0;
|
|
MUSB_DEV_MODE(musb);
|
|
musb->address = 0;
|
|
musb->ep0_state = MUSB_EP0_STAGE_SETUP;
|
|
|
|
musb->may_wakeup = 0;
|
|
musb->g.b_hnp_enable = 0;
|
|
musb->g.a_alt_hnp_support = 0;
|
|
musb->g.a_hnp_support = 0;
|
|
|
|
#ifndef __UBOOT__
|
|
/* Normal reset, as B-Device;
|
|
* or else after HNP, as A-Device
|
|
*/
|
|
if (devctl & MUSB_DEVCTL_BDEVICE) {
|
|
musb->xceiv->state = OTG_STATE_B_PERIPHERAL;
|
|
musb->g.is_a_peripheral = 0;
|
|
} else if (is_otg_enabled(musb)) {
|
|
musb->xceiv->state = OTG_STATE_A_PERIPHERAL;
|
|
musb->g.is_a_peripheral = 1;
|
|
} else
|
|
WARN_ON(1);
|
|
|
|
/* start with default limits on VBUS power draw */
|
|
(void) musb_gadget_vbus_draw(&musb->g,
|
|
is_otg_enabled(musb) ? 8 : 100);
|
|
#endif
|
|
}
|