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

📄 goku_udc.c

📁 ARM S3C2410 USB SLAVE LINUX驱动
💻 C
📖 第 1 页 / 共 4 页
字号:
	prefetchw(buf);

	if (unlikely(ep->num == 0 && ep->dev->ep0state != EP0_OUT))
		return -EL2HLT;

	dbuff = (ep->num == 1 || ep->num == 2);
	do {
		/* ack dataset irq matching the status we'll handle */
		if (ep->num != 0)
			writel(~INT_EPxDATASET(ep->num), &regs->int_status);

		set = readl(&regs->DataSet) & DATASET_AB(ep->num);
		size = readl(&regs->EPxSizeLA[ep->num]);
		bufferspace = req->req.length - req->req.actual;

		/* usually do nothing without an OUT packet */
		if (likely(ep->num != 0 || bufferspace != 0)) {
			if (unlikely(set == 0))
				break;
			/* use ep1/ep2 double-buffering for OUT */
			if (!(size & PACKET_ACTIVE))
				size = readl(&regs->EPxSizeLB[ep->num]);
			if (!(size & PACKET_ACTIVE)) 	// "can't happen"
				break;
			size &= DATASIZE;	/* EPxSizeH == 0 */

		/* ep0out no-out-data case for set_config, etc */
		} else
			size = 0;

		/* read all bytes from this packet */
		req->req.actual += size;
		is_short = (size < ep->ep.maxpacket);
#ifdef USB_TRACE
		VDBG(ep->dev, "read %s %u bytes%s OUT req %p %u/%u\n",
			ep->ep.name, size, is_short ? "/S" : "",
			req, req->req.actual, req->req.length);
#endif
		while (likely(size-- != 0)) {
			u8	byte = (u8) readl(ep->reg_fifo);

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

		/* completion */
		if (unlikely(is_short || req->req.actual == req->req.length)) {
			if (unlikely(ep->num == 0)) {
				/* non-control endpoints now usable? */
				if (ep->dev->req_config)
					writel(ep->dev->configured
							? USBSTATE_CONFIGURED
							: 0,
						&regs->UsbState);
				/* ep0out status stage */
				writel(~(1<<0), &regs->EOP);
				ep->stopped = 1;
				ep->dev->ep0state = EP0_STATUS;
			}
			done(ep, req, 0);

			/* empty the second buffer asap */
			if (dbuff && !list_empty(&ep->queue)) {
				req = list_entry(ep->queue.next,
						struct goku_request, queue);
				goto top;
			}
			return 1;
		}
	} while (dbuff);
	return 0;
}

static inline void
pio_irq_enable(struct goku_udc *dev,
		struct goku_udc_regs __iomem *regs, int epnum)
{
	dev->int_enable |= INT_EPxDATASET (epnum);
	writel(dev->int_enable, &regs->int_enable);
	/* write may still be posted */
}

static inline void
pio_irq_disable(struct goku_udc *dev,
		struct goku_udc_regs __iomem *regs, int epnum)
{
	dev->int_enable &= ~INT_EPxDATASET (epnum);
	writel(dev->int_enable, &regs->int_enable);
	/* write may still be posted */
}

static inline void
pio_advance(struct goku_ep *ep)
{
	struct goku_request	*req;

	if (unlikely(list_empty (&ep->queue)))
		return;
	req = list_entry(ep->queue.next, struct goku_request, queue);
	(ep->is_in ? write_fifo : read_fifo)(ep, req);
}


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

// return:  0 = q running, 1 = q stopped, negative = errno
static int start_dma(struct goku_ep *ep, struct goku_request *req)
{
	struct goku_udc_regs __iomem	*regs = ep->dev->regs;
	u32				master;
	u32				start = req->req.dma;
	u32				end = start + req->req.length - 1;

	master = readl(&regs->dma_master) & MST_RW_BITS;

	/* re-init the bits affecting IN dma; careful with zlps */
	if (likely(ep->is_in)) {
		if (unlikely(master & MST_RD_ENA)) {
			DBG (ep->dev, "start, IN active dma %03x!!\n",
				master);
//			return -EL2HLT;
		}
		writel(end, &regs->in_dma_end);
		writel(start, &regs->in_dma_start);

		master &= ~MST_R_BITS;
		if (unlikely(req->req.length == 0))
			master = MST_RD_ENA | MST_RD_EOPB;
		else if ((req->req.length % ep->ep.maxpacket) != 0
					|| req->req.zero)
			master = MST_RD_ENA | MST_EOPB_ENA;
		else
			master = MST_RD_ENA | MST_EOPB_DIS;

		ep->dev->int_enable |= INT_MSTRDEND;

	/* Goku DMA-OUT merges short packets, which plays poorly with
	 * protocols where short packets mark the transfer boundaries.
	 * The chip supports a nonstandard policy with INT_MSTWRTMOUT,
	 * ending transfers after 3 SOFs; we don't turn it on.
	 */
	} else {
		if (unlikely(master & MST_WR_ENA)) {
			DBG (ep->dev, "start, OUT active dma %03x!!\n",
				master);
//			return -EL2HLT;
		}
		writel(end, &regs->out_dma_end);
		writel(start, &regs->out_dma_start);

		master &= ~MST_W_BITS;
		master |= MST_WR_ENA | MST_TIMEOUT_DIS;

		ep->dev->int_enable |= INT_MSTWREND|INT_MSTWRTMOUT;
	}

	writel(master, &regs->dma_master);
	writel(ep->dev->int_enable, &regs->int_enable);
	return 0;
}

static void dma_advance(struct goku_udc *dev, struct goku_ep *ep)
{
	struct goku_request		*req;
	struct goku_udc_regs __iomem	*regs = ep->dev->regs;
	u32				master;

	master = readl(&regs->dma_master);

	if (unlikely(list_empty(&ep->queue))) {
stop:
		if (ep->is_in)
			dev->int_enable &= ~INT_MSTRDEND;
		else
			dev->int_enable &= ~(INT_MSTWREND|INT_MSTWRTMOUT);
		writel(dev->int_enable, &regs->int_enable);
		return;
	}
	req = list_entry(ep->queue.next, struct goku_request, queue);

	/* normal hw dma completion (not abort) */
	if (likely(ep->is_in)) {
		if (unlikely(master & MST_RD_ENA))
			return;
		req->req.actual = readl(&regs->in_dma_current);
	} else {
		if (unlikely(master & MST_WR_ENA))
			return;

		/* hardware merges short packets, and also hides packet
		 * overruns.  a partial packet MAY be in the fifo here.
		 */
		req->req.actual = readl(&regs->out_dma_current);
	}
	req->req.actual -= req->req.dma;
	req->req.actual++;

#ifdef USB_TRACE
	VDBG(dev, "done %s %s dma, %u/%u bytes, req %p\n",
		ep->ep.name, ep->is_in ? "IN" : "OUT",
		req->req.actual, req->req.length, req);
#endif
	done(ep, req, 0);
	if (list_empty(&ep->queue))
		goto stop;
	req = list_entry(ep->queue.next, struct goku_request, queue);
	(void) start_dma(ep, req);
}

static void abort_dma(struct goku_ep *ep, int status)
{
	struct goku_udc_regs __iomem	*regs = ep->dev->regs;
	struct goku_request		*req;
	u32				curr, master;

	/* NAK future host requests, hoping the implicit delay lets the
	 * dma engine finish reading (or writing) its latest packet and
	 * empty the dma buffer (up to 16 bytes).
	 *
	 * This avoids needing to clean up a partial packet in the fifo;
	 * we can't do that for IN without side effects to HALT and TOGGLE.
	 */
	command(regs, COMMAND_FIFO_DISABLE, ep->num);
	req = list_entry(ep->queue.next, struct goku_request, queue);
	master = readl(&regs->dma_master) & MST_RW_BITS;

	/* FIXME using these resets isn't usably documented. this may
	 * not work unless it's followed by disabling the endpoint.
	 *
	 * FIXME the OUT reset path doesn't even behave consistently.
	 */
	if (ep->is_in) {
		if (unlikely((readl(&regs->dma_master) & MST_RD_ENA) == 0))
			goto finished;
		curr = readl(&regs->in_dma_current);

		writel(curr, &regs->in_dma_end);
		writel(curr, &regs->in_dma_start);

		master &= ~MST_R_BITS;
		master |= MST_RD_RESET;
		writel(master, &regs->dma_master);

		if (readl(&regs->dma_master) & MST_RD_ENA)
			DBG(ep->dev, "IN dma active after reset!\n");

	} else {
		if (unlikely((readl(&regs->dma_master) & MST_WR_ENA) == 0))
			goto finished;
		curr = readl(&regs->out_dma_current);

		writel(curr, &regs->out_dma_end);
		writel(curr, &regs->out_dma_start);

		master &= ~MST_W_BITS;
		master |= MST_WR_RESET;
		writel(master, &regs->dma_master);

		if (readl(&regs->dma_master) & MST_WR_ENA)
			DBG(ep->dev, "OUT dma active after reset!\n");
	}
	req->req.actual = (curr - req->req.dma) + 1;
	req->req.status = status;

	VDBG(ep->dev, "%s %s %s %d/%d\n", __FUNCTION__, ep->ep.name,
		ep->is_in ? "IN" : "OUT",
		req->req.actual, req->req.length);

	command(regs, COMMAND_FIFO_ENABLE, ep->num);

	return;

finished:
	/* dma already completed; no abort needed */
	command(regs, COMMAND_FIFO_ENABLE, ep->num);
	req->req.actual = req->req.length;
	req->req.status = 0;
}

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

static int
goku_queue(struct usb_ep *_ep, struct usb_request *_req, unsigned gfp_flags)
{
	struct goku_request	*req;
	struct goku_ep		*ep;
	struct goku_udc		*dev;
	unsigned long		flags;
	int			status;

	/* always require a cpu-view buffer so pio works */
	req = container_of(_req, struct goku_request, req);
	if (unlikely(!_req || !_req->complete
			|| !_req->buf || !list_empty(&req->queue)))
		return -EINVAL;
	ep = container_of(_ep, struct goku_ep, ep);
	if (unlikely(!_ep || (!ep->desc && ep->num != 0)))
		return -EINVAL;
	dev = ep->dev;
	if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN))
		return -ESHUTDOWN;

	/* can't touch registers when suspended */
	if (dev->ep0state == EP0_SUSPEND)
		return -EBUSY;

	/* set up dma mapping in case the caller didn't */
	if (ep->dma && _req->dma == DMA_ADDR_INVALID) {
		_req->dma = pci_map_single(dev->pdev, _req->buf, _req->length,
			ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
		req->mapped = 1;
	}

#ifdef USB_TRACE
	VDBG(dev, "%s queue req %p, len %u buf %p\n",
			_ep->name, _req, _req->length, _req->buf);
#endif

	spin_lock_irqsave(&dev->lock, flags);

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

	/* for ep0 IN without premature status, zlp is required and
	 * writing EOP starts the status stage (OUT).
	 */
	if (unlikely(ep->num == 0 && ep->is_in))
		_req->zero = 1;

	/* kickstart this i/o queue? */
	status = 0;
	if (list_empty(&ep->queue) && likely(!ep->stopped)) {
		/* dma:  done after dma completion IRQ (or error)
		 * pio:  done after last fifo operation
		 */
		if (ep->dma)
			status = start_dma(ep, req);
		else
			status = (ep->is_in ? write_fifo : read_fifo)(ep, req);

		if (unlikely(status != 0)) {
			if (status > 0)
				status = 0;
			req = NULL;
		}

	} /* else pio or dma irq handler advances the queue. */

	if (likely(req != 0))
		list_add_tail(&req->queue, &ep->queue);

	if (likely(!list_empty(&ep->queue))
			&& likely(ep->num != 0)
			&& !ep->dma
			&& !(dev->int_enable & INT_EPxDATASET (ep->num)))
		pio_irq_enable(dev, dev->regs, ep->num);

	spin_unlock_irqrestore(&dev->lock, flags);

	/* pci writes may still be posted */
	return status;
}

/* dequeue ALL requests */
static void nuke(struct goku_ep *ep, int status)
{
	struct goku_request	*req;

	ep->stopped = 1;
	if (list_empty(&ep->queue))
		return;
	if (ep->dma)
		abort_dma(ep, status);
	while (!list_empty(&ep->queue)) {
		req = list_entry(ep->queue.next, struct goku_request, queue);
		done(ep, req, status);
	}
}

/* dequeue JUST ONE request */
static int goku_dequeue(struct usb_ep *_ep, struct usb_request *_req)
{
	struct goku_request	*req;
	struct goku_ep		*ep;
	struct goku_udc		*dev;
	unsigned long		flags;

	ep = container_of(_ep, struct goku_ep, ep);
	if (!_ep || !_req || (!ep->desc && ep->num != 0))
		return -EINVAL;
	dev = ep->dev;
	if (!dev->driver)
		return -ESHUTDOWN;

	/* we can't touch (dma) registers when suspended */
	if (dev->ep0state == EP0_SUSPEND)
		return -EBUSY;

	VDBG(dev, "%s %s %s %s %p\n", __FUNCTION__, _ep->name,
		ep->is_in ? "IN" : "OUT",
		ep->dma ? "dma" : "pio",
		_req);

	spin_lock_irqsave(&dev->lock, flags);

	/* make sure it's actually queued on this endpoint */
	list_for_each_entry (req, &ep->queue, queue) {
		if (&req->req == _req)
			break;
	}
	if (&req->req != _req) {
		spin_unlock_irqrestore (&dev->lock, flags);
		return -EINVAL;
	}

	if (ep->dma && ep->queue.next == &req->queue && !ep->stopped) {
		abort_dma(ep, -ECONNRESET);
		done(ep, req, -ECONNRESET);
		dma_advance(dev, ep);
	} else if (!list_empty(&req->queue))
		done(ep, req, -ECONNRESET);
	else
		req = NULL;
	spin_unlock_irqrestore(&dev->lock, flags);

	return req ? 0 : -EOPNOTSUPP;
}

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

static void goku_clear_halt(struct goku_ep *ep)
{
	// assert (ep->num !=0)
	VDBG(ep->dev, "%s clear halt\n", ep->ep.name);
	command(ep->dev->regs, COMMAND_SETDATA0, ep->num);
	command(ep->dev->regs, COMMAND_STALL_CLEAR, ep->num);
	if (ep->stopped) {
		ep->stopped = 0;
		if (ep->dma) {
			struct goku_request	*req;

			if (list_empty(&ep->queue))
				return;
			req = list_entry(ep->queue.next, struct goku_request,
						queue);
			(void) start_dma(ep, req);
		} else
			pio_advance(ep);
	}
}

static int goku_set_halt(struct usb_ep *_ep, int value)
{
	struct goku_ep	*ep;
	unsigned long	flags;
	int		retval = 0;

	if (!_ep)
		return -ENODEV;
	ep = container_of (_ep, struct goku_ep, ep);

	if (ep->num == 0) {
		if (value) {
			ep->dev->ep0state = EP0_STALL;
			ep->dev->ep[0].stopped = 1;
		} else
			return -EINVAL;

	/* don't change EPxSTATUS_EP_INVALID to READY */
	} else if (!ep->desc) {
		DBG(ep->dev, "%s %s inactive?\n", __FUNCTION__, ep->ep.name);
		return -EINVAL;
	}

	spin_lock_irqsave(&ep->dev->lock, flags);
	if (!list_empty(&ep->queue))
		retval = -EAGAIN;
	else if (ep->is_in && value
			/* data in (either) packet buffer? */
			&& (readl(&ep->dev->regs->DataSet)
					& DATASET_AB(ep->num)))
		retval = -EAGAIN;
	else if (!value)
		goku_clear_halt(ep);
	else {
		ep->stopped = 1;
		VDBG(ep->dev, "%s set halt\n", ep->ep.name);

⌨️ 快捷键说明

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