⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pxa2xx_udc.c

📁 ARM S3C2410 USB SLAVE LINUX驱动
💻 C
📖 第 1 页 / 共 5 页
字号:
			}
			return 1;
		}

		// TODO experiment: how robust can fifo mode tweaking be?
		// double buffering is off in the default fifo mode, which
		// prevents TFS from being set here.

	} while (*ep->reg_udccs & UDCCS_BI_TFS);
	return 0;
}

/* caller asserts req->pending (ep0 irq status nyet cleared); starts
 * ep0 data stage.  these chips want very simple state transitions.
 */
static inline
void ep0start(struct pxa2xx_udc *dev, u32 flags, const char *tag)
{
	UDCCS0 = flags|UDCCS0_SA|UDCCS0_OPR;
	USIR0 = USIR0_IR0;
	dev->req_pending = 0;
	DBG(DBG_VERY_NOISY, "%s %s, %02x/%02x\n",
		__FUNCTION__, tag, UDCCS0, flags);
}

static int
write_ep0_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req)
{
	unsigned	count;
	int		is_short;

	count = write_packet(&UDDR0, req, EP0_FIFO_SIZE);
	ep->dev->stats.write.bytes += count;

	/* last packet "must be" short (or a zlp) */
	is_short = (count != EP0_FIFO_SIZE);

	DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count,
		req->req.length - req->req.actual, req);

	if (unlikely (is_short)) {
		if (ep->dev->req_pending)
			ep0start(ep->dev, UDCCS0_IPR, "short IN");
		else
			UDCCS0 = UDCCS0_IPR;

		count = req->req.length;
		done (ep, req, 0);
		ep0_idle(ep->dev);
#if 1
		/* This seems to get rid of lost status irqs in some cases:
		 * host responds quickly, or next request involves config
		 * change automagic, or should have been hidden, or ...
		 *
		 * FIXME get rid of all udelays possible...
		 */
		if (count >= EP0_FIFO_SIZE) {
			count = 100;
			do {
				if ((UDCCS0 & UDCCS0_OPR) != 0) {
					/* clear OPR, generate ack */
					UDCCS0 = UDCCS0_OPR;
					break;
				}
				count--;
				udelay(1);
			} while (count);
		}
#endif
	} else if (ep->dev->req_pending)
		ep0start(ep->dev, 0, "IN");
	return is_short;
}


/*
 * read_fifo -  unload packet(s) from the fifo we use for usb OUT
 * transfers and put them into the request.  caller should have made
 * sure there's at least one packet ready.
 *
 * returns true if the request completed because of short packet or the
 * request buffer having filled (and maybe overran till end-of-packet).
 */
static int
read_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req)
{
	for (;;) {
		u32		udccs;
		u8		*buf;
		unsigned	bufferspace, count, is_short;

		/* make sure there's a packet in the FIFO.
		 * UDCCS_{BO,IO}_RPC are all the same bit value.
		 * UDCCS_{BO,IO}_RNE are all the same bit value.
		 */
		udccs = *ep->reg_udccs;
		if (unlikely ((udccs & UDCCS_BO_RPC) == 0))
			break;
		buf = req->req.buf + req->req.actual;
		prefetchw(buf);
		bufferspace = req->req.length - req->req.actual;

		/* read all bytes from this packet */
		if (likely (udccs & UDCCS_BO_RNE)) {
			count = 1 + (0x0ff & *ep->reg_ubcr);
			req->req.actual += min (count, bufferspace);
		} else /* zlp */
			count = 0;
		is_short = (count < ep->ep.maxpacket);
		DBG(DBG_VERY_NOISY, "read %s %02x, %d bytes%s req %p %d/%d\n",
			ep->ep.name, udccs, count,
			is_short ? "/S" : "",
			req, req->req.actual, req->req.length);
		while (likely (count-- != 0)) {
			u8	byte = (u8) *ep->reg_uddr;

			if (unlikely (bufferspace == 0)) {
				/* this happens when the driver's buffer
				 * is smaller than what the host sent.
				 * discard the extra data.
				 */
				if (req->req.status != -EOVERFLOW)
					DMSG("%s overflow %d\n",
						ep->ep.name, count);
				req->req.status = -EOVERFLOW;
			} else {
				*buf++ = byte;
				bufferspace--;
			}
		}
		*ep->reg_udccs =  UDCCS_BO_RPC;
		/* RPC/RSP/RNE could now reflect the other packet buffer */

		/* iso is one request per packet */
		if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
			if (udccs & UDCCS_IO_ROF)
				req->req.status = -EHOSTUNREACH;
			/* more like "is_done" */
			is_short = 1;
		}

		/* completion */
		if (is_short || req->req.actual == req->req.length) {
			done (ep, req, 0);
			if (list_empty(&ep->queue))
				pio_irq_disable (ep->bEndpointAddress);
			return 1;
		}

		/* finished that packet.  the next one may be waiting... */
	}
	return 0;
}

/*
 * special ep0 version of the above.  no UBCR0 or double buffering; status
 * handshaking is magic.  most device protocols don't need control-OUT.
 * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other
 * protocols do use them.
 */
static int
read_ep0_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req)
{
	u8		*buf, byte;
	unsigned	bufferspace;

	buf = req->req.buf + req->req.actual;
	bufferspace = req->req.length - req->req.actual;

	while (UDCCS0 & UDCCS0_RNE) {
		byte = (u8) UDDR0;

		if (unlikely (bufferspace == 0)) {
			/* this happens when the driver's buffer
			 * is smaller than what the host sent.
			 * discard the extra data.
			 */
			if (req->req.status != -EOVERFLOW)
				DMSG("%s overflow\n", ep->ep.name);
			req->req.status = -EOVERFLOW;
		} else {
			*buf++ = byte;
			req->req.actual++;
			bufferspace--;
		}
	}

	UDCCS0 = UDCCS0_OPR | UDCCS0_IPR;

	/* completion */
	if (req->req.actual >= req->req.length)
		return 1;

	/* finished that packet.  the next one may be waiting... */
	return 0;
}

#ifdef	USE_DMA

#define	MAX_IN_DMA	((DCMD_LENGTH + 1) - BULK_FIFO_SIZE)

static void
start_dma_nodesc(struct pxa2xx_ep *ep, struct pxa2xx_request *req, int is_in)
{
	u32	dcmd = req->req.length;
	u32	buf = req->req.dma;
	u32	fifo = io_v2p ((u32)ep->reg_uddr);

	/* caller guarantees there's a packet or more remaining
	 *  - IN may end with a short packet (TSP set separately),
	 *  - OUT is always full length
	 */
	buf += req->req.actual;
	dcmd -= req->req.actual;
	ep->dma_fixup = 0;

	/* no-descriptor mode can be simple for bulk-in, iso-in, iso-out */
	DCSR(ep->dma) = DCSR_NODESC;
	if (is_in) {
		DSADR(ep->dma) = buf;
		DTADR(ep->dma) = fifo;
		if (dcmd > MAX_IN_DMA)
			dcmd = MAX_IN_DMA;
		else
			ep->dma_fixup = (dcmd % ep->ep.maxpacket) != 0;
		dcmd |= DCMD_BURST32 | DCMD_WIDTH1
			| DCMD_FLOWTRG | DCMD_INCSRCADDR;
	} else {
#ifdef USE_OUT_DMA
		DSADR(ep->dma) = fifo;
		DTADR(ep->dma) = buf;
		if (ep->bmAttributes != USB_ENDPOINT_XFER_ISOC)
			dcmd = ep->ep.maxpacket;
		dcmd |= DCMD_BURST32 | DCMD_WIDTH1
			| DCMD_FLOWSRC | DCMD_INCTRGADDR;
#endif
	}
	DCMD(ep->dma) = dcmd;
	DCSR(ep->dma) = DCSR_RUN | DCSR_NODESC
		| (unlikely(is_in)
			? DCSR_STOPIRQEN	/* use dma_nodesc_handler() */
			: 0);			/* use handle_ep() */
}

static void kick_dma(struct pxa2xx_ep *ep, struct pxa2xx_request *req)
{
	int	is_in = ep->bEndpointAddress & USB_DIR_IN;

	if (is_in) {
		/* unaligned tx buffers and zlps only work with PIO */
		if ((req->req.dma & 0x0f) != 0
				|| unlikely((req->req.length - req->req.actual)
						== 0)) {
			pio_irq_enable(ep->bEndpointAddress);
			if ((*ep->reg_udccs & UDCCS_BI_TFS) != 0)
				(void) write_fifo(ep, req);
		} else {
			start_dma_nodesc(ep, req, USB_DIR_IN);
		}
	} else {
		if ((req->req.length - req->req.actual) < ep->ep.maxpacket) {
			DMSG("%s short dma read...\n", ep->ep.name);
			/* we're always set up for pio out */
			read_fifo (ep, req);
		} else {
			*ep->reg_udccs = UDCCS_BO_DME
				| (*ep->reg_udccs & UDCCS_BO_FST);
			start_dma_nodesc(ep, req, USB_DIR_OUT);
		}
	}
}

static void cancel_dma(struct pxa2xx_ep *ep)
{
	struct pxa2xx_request	*req;
	u32			tmp;

	if (DCSR(ep->dma) == 0 || list_empty(&ep->queue))
		return;

	DCSR(ep->dma) = 0;
	while ((DCSR(ep->dma) & DCSR_STOPSTATE) == 0)
		cpu_relax();

	req = list_entry(ep->queue.next, struct pxa2xx_request, queue);
	tmp = DCMD(ep->dma) & DCMD_LENGTH;
	req->req.actual = req->req.length - (tmp & DCMD_LENGTH);

	/* the last tx packet may be incomplete, so flush the fifo.
	 * FIXME correct req.actual if we can
	 */
	if (ep->bEndpointAddress & USB_DIR_IN)
		*ep->reg_udccs = UDCCS_BI_FTF;
}

/* dma channel stopped ... normal tx end (IN), or on error (IN/OUT) */
static void dma_nodesc_handler(int dmach, void *_ep, struct pt_regs *r)
{
	struct pxa2xx_ep	*ep = _ep;
	struct pxa2xx_request	*req;
	u32			tmp, completed;

	local_irq_disable();

	req = list_entry(ep->queue.next, struct pxa2xx_request, queue);

	ep->dma_irqs++;
	ep->dev->stats.irqs++;
	HEX_DISPLAY(ep->dev->stats.irqs);

	/* ack/clear */
	tmp = DCSR(ep->dma);
	DCSR(ep->dma) = tmp;
	if ((tmp & DCSR_STOPSTATE) == 0
			|| (DDADR(ep->dma) & DDADR_STOP) != 0) {
		DBG(DBG_VERBOSE, "%s, dcsr %08x ddadr %08x\n",
			ep->ep.name, DCSR(ep->dma), DDADR(ep->dma));
		goto done;
	}
	DCSR(ep->dma) = 0;	/* clear DCSR_STOPSTATE */

	/* update transfer status */
	completed = tmp & DCSR_BUSERR;
	if (ep->bEndpointAddress & USB_DIR_IN)
		tmp = DSADR(ep->dma);
	else
		tmp = DTADR(ep->dma);
	req->req.actual = tmp - req->req.dma;

	/* FIXME seems we sometimes see partial transfers... */

	if (unlikely(completed != 0))
		req->req.status = -EIO;
	else if (req->req.actual) {
		/* these registers have zeroes in low bits; they miscount
		 * some (end-of-transfer) short packets:  tx 14 as tx 12
		 */
		if (ep->dma_fixup)
			req->req.actual = min(req->req.actual + 3,
						req->req.length);

		tmp = (req->req.length - req->req.actual);
		completed = (tmp == 0);
		if (completed && (ep->bEndpointAddress & USB_DIR_IN)) {

			/* maybe validate final short packet ... */
			if ((req->req.actual % ep->ep.maxpacket) != 0)
				*ep->reg_udccs = UDCCS_BI_TSP/*|UDCCS_BI_TPC*/;

			/* ... or zlp, using pio fallback */
			else if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK
					&& req->req.zero) {
				DMSG("%s zlp terminate ...\n", ep->ep.name);
				completed = 0;
			}
		}
	}

	if (likely(completed)) {
		done(ep, req, 0);

		/* maybe re-activate after completion */
		if (ep->stopped || list_empty(&ep->queue))
			goto done;
		req = list_entry(ep->queue.next, struct pxa2xx_request, queue);
	}
	kick_dma(ep, req);
done:
	local_irq_enable();
}

#endif

/*-------------------------------------------------------------------------*/

static int
pxa2xx_ep_queue(struct usb_ep *_ep, struct usb_request *_req, unsigned gfp_flags)
{
	struct pxa2xx_request	*req;
	struct pxa2xx_ep	*ep;
	struct pxa2xx_udc	*dev;
	unsigned long		flags;

	req = container_of(_req, struct pxa2xx_request, req);
	if (unlikely (!_req || !_req->complete || !_req->buf
			|| !list_empty(&req->queue))) {
		DMSG("%s, bad params\n", __FUNCTION__);
		return -EINVAL;
	}

	ep = container_of(_ep, struct pxa2xx_ep, ep);
	if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) {
		DMSG("%s, bad ep\n", __FUNCTION__);
		return -EINVAL;
	}

	dev = ep->dev;
	if (unlikely (!dev->driver
			|| dev->gadget.speed == USB_SPEED_UNKNOWN)) {
		DMSG("%s, bogus device state\n", __FUNCTION__);
		return -ESHUTDOWN;
	}

	/* iso is always one packet per request, that's the only way
	 * we can report per-packet status.  that also helps with dma.
	 */
	if (unlikely (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC
			&& req->req.length > le16_to_cpu
						(ep->desc->wMaxPacketSize)))
		return -EMSGSIZE;

#ifdef	USE_DMA
	// FIXME caller may already have done the dma mapping
	if (ep->dma >= 0) {
		_req->dma = dma_map_single(dev->dev,
			_req->buf, _req->length,
			((ep->bEndpointAddress & USB_DIR_IN) != 0)
				? DMA_TO_DEVICE
				: DMA_FROM_DEVICE);
	}
#endif

	DBG(DBG_NOISY, "%s queue req %p, len %d buf %p\n",
	     _ep->name, _req, _req->length, _req->buf);

	local_irq_save(flags);

	_req->status = -EINPROGRESS;
	_req->actual = 0;

	/* kickstart this i/o queue? */
	if (list_empty(&ep->queue) && !ep->stopped) {
		if (ep->desc == 0 /* ep0 */) {
			unsigned	length = _req->length;

			switch (dev->ep0state) {
			case EP0_IN_DATA_PHASE:
				dev->stats.write.ops++;
				if (write_ep0_fifo(ep, req))
					req = NULL;
				break;

			case EP0_OUT_DATA_PHASE:
				dev->stats.read.ops++;
				/* messy ... */
				if (dev->req_config) {
					DBG(DBG_VERBOSE, "ep0 config ack%s\n",
						dev->has_cfr ?  "" : " raced");
					if (dev->has_cfr)
						UDCCFR = UDCCFR_AREN|UDCCFR_ACM
							|UDCCFR_MB1;
					done(ep, req, 0);
					dev->ep0state = EP0_END_XFER;
					local_irq_restore (flags);
					return 0;
				}
				if (dev->req_pending)
					ep0start(dev, UDCCS0_IPR, "OUT");
				if (length == 0 || ((UDCCS0 & UDCCS0_RNE) != 0
						&& read_ep0_fifo(ep, req))) {
					ep0_idle(dev);
					done(ep, req, 0);
					req = NULL;
				}
				break;

			default:
				DMSG("ep0 i/o, odd state %d\n", dev->ep0state);
				local_irq_restore (flags);
				return -EL2HLT;
			}
#ifdef	USE_DMA
		/* either start dma or prime pio pump */
		} else if (ep->dma >= 0) {
			kick_dma(ep, req);
#endif
		/* can the FIFO can satisfy the request immediately? */
		} else if ((ep->bEndpointAddress & USB_DIR_IN) != 0) {
			if ((*ep->reg_udccs & UDCCS_BI_TFS) != 0
					&& write_fifo(ep, req))
				req = NULL;
		} else if ((*ep->reg_udccs & UDCCS_BO_RFS) != 0
				&& read_fifo(ep, req)) {
			req = NULL;
		}

		if (likely (req && ep->desc) && ep->dma < 0)
			pio_irq_enable(ep->bEndpointAddress);
	}

	/* pio or dma irq handler advances the queue. */
	if (likely (req != 0))
		list_add_tail(&req->queue, &ep->queue);
	local_irq_restore(flags);

	return 0;
}


/*

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -