[U-Boot] [PATCH 3/5] pxa255: Add USB CDC ACM driver

Łukasz Dałek luk0104 at gmail.com
Fri Jul 27 19:32:01 CEST 2012


Signed-off-by: Łukasz Dałek <luk0104 at gmail.com>
---
 drivers/serial/usbtty.h         |    2 +
 drivers/usb/gadget/pxa25x_udc.c |  939 +++++++++++++++++++++++++++++++++++++++
 include/usb/pxa25x_udc.h        |   65 +++
 3 files changed, 1006 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/pxa25x_udc.c
 create mode 100644 include/usb/pxa25x_udc.h

diff --git a/drivers/serial/usbtty.h b/drivers/serial/usbtty.h
index eb670da..632b54e 100644
--- a/drivers/serial/usbtty.h
+++ b/drivers/serial/usbtty.h
@@ -31,6 +31,8 @@
 #include <usb/omap1510_udc.h>
 #elif defined(CONFIG_MUSB_UDC)
 #include <usb/musb_udc.h>
+#elif defined(CONFIG_CPU_PXA25X)
+#include <usb/pxa25x_udc.h>
 #elif defined(CONFIG_CPU_PXA27X)
 #include <usb/pxa27x_udc.h>
 #elif defined(CONFIG_DW_UDC)
diff --git a/drivers/usb/gadget/pxa25x_udc.c b/drivers/usb/gadget/pxa25x_udc.c
new file mode 100644
index 0000000..4ff98cc
--- /dev/null
+++ b/drivers/usb/gadget/pxa25x_udc.c
@@ -0,0 +1,939 @@
+/*
+ * PXA25x USB device driver for u-boot.
+ *
+ * Copyright (C) 2012 Łukasz Dałek <luk0104 at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * based on drivers/usb/gadget/pxa27x_udc.c
+ *
+ */
+
+#include <common.h>
+#include <config.h>
+#include <usb/pxa25x_udc.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include "ep0.h"
+
+struct pxa25x_endpoint {
+	u8	num;
+	u32	uddr;
+	u32	udccs;
+	u32	ubcr;
+};
+
+static struct pxa25x_endpoint eps[] = {
+	{ 0, UDDR0, UDCCS(0), 0 },
+	{ 1, UDDR1, UDCCS(1), 0 },
+	{ 2, UDDR2, UDCCS(2), UBCR2 },
+	{ 3, UDDR3, UDCCS(3), 0 },
+	{ 4, UDDR4, UDCCS(4), UBCR4 },
+	{ 5, UDDR5, UDCCS(5), 0 },
+	{ 6, UDDR6, UDCCS(6), 0 },
+	{ 7, UDDR7, UDCCS(7), UBCR7 },
+	{ 8, UDDR8, UDCCS(8), 0 },
+	{ 9, UDDR9, UDCCS(9), UBCR9 },
+	{ 10, UDDR10, UDCCS(10), 0 },
+	{ 11, UDDR11, UDCCS(11), 0 },
+	{ 12, UDDR12, UDCCS(12), UBCR12 },
+	{ 13, UDDR13, UDCCS(13), 0 },
+	{ 14, UDDR14, UDCCS(14), UBCR14 },
+	{ 15, UDDR15, UDCCS(15), 0 },
+};
+
+static struct usb_device_instance *udc_device;
+static struct urb *ep0_urb;
+static int ep0state = EP0_IDLE;
+static int ep0laststate = EP0_IDLE;
+
+static inline void udc_set_reg(u32 reg, u32 mask)
+{
+	u32 val;
+
+	val = readl(reg);
+	val |= mask;
+	writel(val, reg);
+}
+
+static inline void udc_clear_reg(u32 reg, u32 mask)
+{
+	u32 val;
+
+	val = readl(reg);
+	val &= ~mask;
+	writel(val, reg);
+}
+
+/* static void udc_dump_buffer(char *name, u8 *buf, int len)
+{
+	usbdbg("%s - buf %p, len %d", name, buf, len);
+	print_buffer(0, buf, 1, len, 0);
+} */
+
+static void udc_dump_buffer(u8 *data, int len)
+{
+	u8 buff[8 * 5 + 1]; /* 8 characters 0x??_ + null */
+	int i;
+
+	for (i = 0; i < len; ++i) {
+		int n = i % 8;
+		buff[n * 5 + 0] = '0';
+		buff[n * 5 + 1] = 'x';
+
+		u8 ch = data[i] >> 4;
+		buff[n * 5 + 2] = (ch < 10)?(ch + '0'):(ch - 10 + 'a');
+		ch = data[i] & 0xf;
+		buff[n * 5 + 3] = (ch < 10)?(ch + '0'):(ch - 10 + 'a');
+		buff[n * 5 + 4] = ' ';
+
+		buff[n * 5 + 5] = 0x0;
+
+		if (n == 7)
+			usbdbg("%s", buff);
+	}
+
+}
+
+static void udc_flush_fifo(struct usb_endpoint_instance *endpoint)
+{
+	int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK,
+	    isout = 
+		    (endpoint->endpoint_address & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT;
+	int ep_type;
+	u32 val;
+
+	if (ep_num > 15) {
+		usberr("%s: endpoint out of range %d", __func__, ep_num);
+		return ;
+	}
+
+	if (!ep_num) {
+		while (readl(UDCCS0) & UDCCS_CTRL_RNE)
+			readl(UDDR0);
+		writel(UDCCS_CTRL_FTF, UDCCS0);
+		usbdbg("flushed endpoint 0 (udccs0 0x%02X)",
+			readl(UDCCS0) & 0xff);
+		return ;
+	}
+
+	if (isout) {
+		while (readl(eps[ep_num].udccs) & UDCCS_BULK_OUT_RNE)
+			readl(eps[ep_num].uddr);
+		usbdbg("out endpoint %d flushed", ep_num);
+		return ;
+	}
+
+	ep_type = endpoint->tx_attributes;
+
+	val = UDCCS_BULK_IN_TPC | UDCCS_BULK_IN_FTF | UDCCS_BULK_IN_TUR;
+	if (ep_type == USB_ENDPOINT_XFER_BULK ||
+			ep_type == USB_ENDPOINT_XFER_INT) {
+		val |= UDCCS_BULK_IN_SST;
+	}
+	writel(val, eps[ep_num].udccs);
+	usbdbg("in endpoint %d flushed", ep_num);
+}
+
+/* static void udc_stall_ep(int num)
+{
+	writel(UDCCS_BULK_IN_FST, eps[num].udccs);
+	usbdbg("forced stall on ep %d", num);
+} */
+
+static int udc_write_urb(struct usb_endpoint_instance *endpoint)
+{
+	int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK;
+	int i, length;
+	struct urb *urb = endpoint->tx_urb;
+	u8 *data;
+	/* int timeout = 2000; */
+
+	if (!urb)
+		return -1;
+
+	if (!urb->actual_length) {
+		usbdbg("clearing tpc and tur bits");
+		writel(UDCCS_BULK_IN_TPC | UDCCS_BULK_IN_TUR, eps[ep_num].udccs);
+		return -1;
+	}
+
+
+	/* FIXME: check TFS bit for udc transsmion ready */
+
+	usbdbg("urb->actual_length %d, endpoint->sent %d, endpoint 0x%p, "
+		"urb 0x%p", urb->actual_length, endpoint->sent,
+		endpoint, urb);
+
+	length = MIN(urb->actual_length - endpoint->sent, endpoint->tx_packetSize);
+	if (length <= 0) {
+		usbdbg("nothing to sent");
+		return -1;
+	}
+
+	/* clear tpc and tur bit */
+	writel(UDCCS_BULK_IN_TPC | UDCCS_BULK_IN_TUR, eps[ep_num].udccs);
+
+	data = (u8 *)urb->buffer + endpoint->sent;
+	for (i = 0; i < length; ++i)
+		writel(data[i], eps[ep_num].uddr);
+
+	usbdbg("prepare to sent %d bytes on ep %d, udccs 0x%02X",
+		length, ep_num, readl(eps[ep_num].udccs));
+
+	/* short packet? */
+	if (length != endpoint->tx_packetSize) {
+		usbdbg("ep_num %d, sending short packet", ep_num);
+		writel(UDCCS_BULK_IN_TSP, eps[ep_num].udccs);
+	}
+
+	/* wait for data */
+#if 0
+	while ( !(readl(eps[ep_num].udccs) & UDCCS_BULK_IN_TPC) ) {
+		if (!--timeout) {
+			usberr("timeout on ep %d, (udccs 0x%02X)",
+				ep_num, readl(eps[ep_num].udccs));
+			udc_stall_ep(ep_num);
+			return -1;
+		} else
+			udelay(1);
+	}
+#endif
+
+	/* from pxa27x_udc.c */
+	endpoint->last = length;
+	usbd_tx_complete(endpoint);
+	if (endpoint->sent >= urb->actual_length) {
+		urb->actual_length = 0;
+		endpoint->sent = 0;
+		endpoint->last = 0;
+	}
+
+	usbdbg("sent %d bytes on ep %d (udccs 0x%02X",
+		length, ep_num, readl(eps[ep_num].udccs));
+	return 0;
+}
+
+static int udc_read_urb(struct usb_endpoint_instance *endpoint)
+{
+	int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK;
+	int i, length;
+	struct urb *urb = endpoint->rcv_urb;
+	u8 *data;
+
+	if (readl(eps[ep_num].udccs) & UDCCS_BULK_OUT_RNE) {
+		usbdbg("%d ubcr %p", ep_num, (void *)eps[ep_num].ubcr);
+		length = readl(eps[ep_num].ubcr) & 0xff;
+		length += 1;
+		usbdbg("out packet: %d data ready", length);
+	} else {
+		length = 0;
+		usbdbg("out packet: zero-length");
+	}
+
+	data = (u8 *)urb->buffer + urb->actual_length;
+	for (i = 0; i < length; ++i)
+		data[i] = readl(eps[ep_num].uddr);
+
+	udc_dump_buffer(data, length);
+	usbd_rcv_complete(endpoint, length, 0);
+	writel(UDCCS_BULK_OUT_RPC, eps[ep_num].udccs);
+
+	usbdbg("read packet %d length on ep %d", length, ep_num);
+	return 0;
+}
+
+static int udc_handle_ep(struct usb_endpoint_instance *endpoint)
+{
+	int ep_addr = endpoint->endpoint_address;
+	int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK;
+	int ep_dir = ep_addr & USB_REQ_DIRECTION_MASK;
+	int ret;
+
+	/* clear flags */
+	u32 flags = readl(eps[ep_num].udccs);
+	usbdbg("udccs on ep %d 0x%02X", ep_num, flags);
+	flags &= UDCCS_BULK_IN_SST;
+	if (flags) {
+		writel(flags, eps[ep_num].udccs);
+	}
+
+	if (ep_dir == USB_DIR_IN) {
+		ret = udc_write_urb(endpoint);
+	} else {
+		ret = udc_read_urb(endpoint);
+	}
+
+	usbdbg("udccs on ep %d 0x%02X", ep_num, readl(eps[ep_num].udccs));
+	return ret;
+}
+
+static inline void udc_stall_ep0(void)
+{
+	u32 reg;
+
+	reg = readl(UDCCS0);
+
+	reg |= UDCCS_CTRL_OPR | UDCCS_CTRL_FTF | UDCCS_CTRL_FST | UDCCS_CTRL_SA;
+	writel(reg, UDCCS0);
+
+	usbdbg("forced stall on ep0");
+}
+
+static int udc_read_urb0(struct usb_endpoint_instance *endpoint)
+{
+	struct urb *urb = ep0_urb;
+	int i, n;
+	u8 *data;
+
+	/* FIXME: endpoint, urb and buffer can be nulls, check them */
+
+	data = (u8 *)urb->buffer + urb->actual_length;
+	n = urb->buffer_length - urb->actual_length;
+	for (i = 0; (readl(UDCCS0)&UDCCS_CTRL_RNE) && i < n; ++i)
+		data[i] = readl(UDDR0) & 0xff;
+
+	urb->actual_length += i;
+	
+	/* read to accept new OUT packet and Status IN packets */
+	writel(UDCCS_CTRL_IPR | UDCCS_CTRL_OPR, UDCCS0);
+
+	usbdbg("out packet actual_length %d", urb->actual_length);
+
+	return 0;
+}
+
+static int udc_write_urb0(struct usb_endpoint_instance *endpoint)
+{
+	struct urb *urb = endpoint->tx_urb; /* practically this is ep0_urb */
+	int length, i;
+	u8 *data;
+
+	usbdbg("endpoint->sent %d, urb->actual_length %d",
+		endpoint->sent, urb->actual_length);
+
+	/* did we sent everything? */
+	if (endpoint->sent >= urb->actual_length) {
+		/* send zero length packet */
+		usbdbg("send zero length packet");
+		writel(UDCCS_CTRL_IPR, UDCCS0);
+		ep0laststate = ep0state;
+		ep0state = EP0_END_XFER;
+		return 0;
+	}
+
+	/* send less then packet size */
+	length = MIN(urb->actual_length - endpoint->sent, endpoint->tx_packetSize);
+	if (length <=0) {
+		usberr("there's no data that could be sent");
+		udc_stall_ep0();
+		return -1;
+	}
+
+	data = (u8 *)urb->buffer + endpoint->sent;
+	for (i = 0; i < length; ++i)
+		writel(data[i], UDDR0);
+
+	usbdbg("sent packet with");
+	udc_dump_buffer(data, length);
+
+	if (length != endpoint->tx_packetSize) {
+		usbdbg("write IPR to UDCCS0");
+		writel(UDCCS_CTRL_IPR, UDCCS0);
+		ep0laststate = ep0state;
+		ep0state = EP0_END_XFER;
+	}
+
+	endpoint->sent += length;
+	endpoint->last = length;
+
+	/* short packet? */
+//	if (length != endpoint->tx_packetSize) {
+		/* forget about documentation, force EP0_IDLE */
+		//usbdbg("short packet, set up state to EP0_IDLE");
+		//ep0state = EP0_IDLE;
+//	}
+
+	usbdbg("sent in packet %d bytes", length);
+	return 0;
+}
+
+static int read_setup_packet(u8 *data, u8 flag)
+{
+	int i;
+
+	for (i = 0; i < 8; ++i) {
+		if ( flag && !(readl(UDCCS0) & UDCCS_CTRL_RNE) ) {
+			usberr("setup packet too short, onl %d bytes", i);
+			udc_stall_ep0();
+			return -1;
+		}
+		data[i] = readl(UDDR0);
+	}
+
+	usbdbg("setup: %02X %02X %02X %02X %02X %02X %02X %02X",
+		data[0], data[1], data[2], data[3],
+		data[4], data[5], data[6], data[7]);
+
+	return i;
+}
+
+static int udc_handle_ep0_idle_state(struct usb_endpoint_instance *endpoint)
+{
+	u8 *data = (u8 *)&ep0_urb->device_request;
+	int i, packet_ready = 0;
+
+	/* check if there is out packet (setup transaction) */
+	if ( (readl(UDCCS0) & (UDCCS_CTRL_SA | UDCCS_CTRL_OPR)) !=
+			(UDCCS_CTRL_SA | UDCCS_CTRL_OPR) ) {
+		/* FIXME: little experiment */
+		if (ep0laststate == EP0_END_XFER) {
+			u8 tmp[8];
+			usbdbg("testing for missing packet...");
+			read_setup_packet(tmp, 0);
+		}
+		return -1;
+	}
+
+	usbdbg("udccs0: 0x%02X", readl(UDCCS0) & 0xff);
+
+	if ( !(readl(UDCCS0) & (UDCCS_CTRL_RNE)) ) {
+		/* pxa210/250 erratum 131 for B0/B1 says RNE lies.
+		 * still observed on a pxa255 a0.
+		 */
+		usbdbg("RNE sometimes lays, check packet anyway... (e131)");
+		/* read SETUP data, but don't trust it too much */
+		i = read_setup_packet(data, 0);
+		if ( (ep0_urb->device_request.bmRequestType &
+				USB_REQ_RECIPIENT_MASK) > 0x03) {
+			usbdbg(" -> packet is broken, eh");
+			udc_stall_ep0();
+			return -1;
+		}
+		if ( ((u32 *)data)[0] == 0 && ((u32 *)data)[1] ) {
+			usbdbg(" -> packet is broken, eh");
+			udc_stall_ep0();
+			return -1;
+		}
+		usbdbg(" -> packet looks good, rne lies");
+		packet_ready = 1;
+	}
+
+	/* for (i = 0; i < 8 && (readl(UDCCS0) & UDCCS_CTRL_RNE); ++i) {
+		data[i] = readl(UDDR0);
+	} */
+
+	usbdbg("udccs0: 0x%02X", readl(UDCCS0) & 0xff);
+
+	if (!packet_ready) {
+		i = read_setup_packet(data, 1);
+		if (i < 0)
+			return -1;
+	}
+
+	if ( (i!=8) || (readl(UDCCS0)&UDCCS_CTRL_RNE) ) {
+		usberr("broken setup packet (propably too long)");
+		udc_stall_ep0();
+		return -1;
+	}
+
+	//writel(UDCCS_CTRL_SA | UDCCS_CTRL_OPR, UDCCS0);
+
+	//udc_dump_buffer("setup_packet", data, i);
+
+	if (ep0_urb->device_request.wLength == 0) {
+		/* No-data command */
+		usbdbg("No data command packet");
+		if (ep0_recv_setup(ep0_urb)) {
+			usberr("Wrong no data command");
+			udc_stall_ep0();
+			return -1;
+		}
+		ep0laststate = ep0state;
+		ep0state = EP0_IDLE;
+		writel(UDCCS_CTRL_SA | UDCCS_CTRL_OPR, UDCCS0);
+		/* FIXME: check is this is vendor or class or standard no data
+		 * command, by the way we will set IPR bit */
+		writel(UDCCS_CTRL_IPR, UDCCS0);
+
+		u8 cmp[] = { 0x21, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
+		int i;
+		for (i = 0; i < sizeof cmp; ++i) {
+			if (cmp[i] != data[i])
+				break;
+		}
+		if (i == sizeof cmp) {
+			usbdbg("we have got packet one after set_conf, lets try device configured on u-boot usb stack");
+			usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0);
+		}
+		return 0;
+	}
+
+	/* check direction of the packet */
+	if ( (ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK)
+			== USB_REQ_DEVICE2HOST ) {
+		usbdbg("Control IN packet");
+		/* Setup control IN packet (to host) */
+		if (ep0_recv_setup(ep0_urb)) {
+			usberr("Wrong setup in packet");
+			udc_stall_ep0();
+			return -1;
+		}
+		usbdbg("packet wLength %d", ep0_urb->device_request.wLength);
+		ep0laststate = ep0state;
+		ep0state = EP0_IN_DATA;
+		endpoint->tx_urb = ep0_urb;
+		endpoint->sent = 0;
+		int ret = udc_write_urb0(endpoint);
+		writel(UDCCS_CTRL_SA | UDCCS_CTRL_OPR, UDCCS0);
+		return ret;
+	} else {
+		/* setup control OUT packet (to device) */
+		usbdbg("Control OUT packet");
+		ep0laststate = ep0state;
+		ep0state = EP0_OUT_DATA;
+		ep0_urb->buffer = (u8 *)ep0_urb->buffer_data;
+		ep0_urb->buffer_length = sizeof ep0_urb->buffer_data;
+		ep0_urb->actual_length = 0;
+		udc_set_reg(UDCCS0, UDCCS_CTRL_SA | UDCCS_CTRL_OPR);
+		udc_set_reg(UDCCS0, UDCCS_CTRL_IPR); /* allow out packet */
+	}
+
+	/* clear sa and opr bits */
+	/* writel(readl(UDCCS0) | UDCCS_CTRL_SA | UDCCS_CTRL_OPR, UDCCS0); */
+
+	return 0;
+}
+
+static int udc_handle_ep0(void)
+{
+	struct usb_endpoint_instance *ep;
+	int ret;
+	u32 udccs0;
+
+	ep = udc_device->bus->endpoint_array; /* Control is 0 */
+
+	/* reset stall */
+	if (readl(UDCCS0) & UDCCS_CTRL_SST) {
+		ep0laststate = ep0state;
+		ep0state = EP0_IDLE;
+		writel(readl(UDCCS0) | UDCCS_CTRL_SST, UDCCS0);
+		udc_flush_fifo(ep);
+		usbdbg("reseted stall condition on ep0");
+	}
+
+	/* "Whatever happens - stick together" - Gladiator
+	 * no matters what state we are, no matters what is going on
+	 * if host sends a new setup packet, we have to terminate all
+	 * and take care of new setup transaction
+	 */
+	if ( ep0state != EP0_IDLE && 
+		(readl(UDCCS0) & (UDCCS_CTRL_SA | UDCCS_CTRL_OPR)) ==
+			(UDCCS_CTRL_SA | UDCCS_CTRL_OPR) ) {
+		ep0laststate = ep0state;
+		ep0state = EP0_IDLE; /* take care of new setup transaction */
+		usbdbg("Reseted to EP0_IDLE");
+	}
+
+	switch (ep0state)
+	{
+		case EP0_IDLE:
+			usbdbg("state: EP0_IDLE");
+			ret = udc_handle_ep0_idle_state(ep);
+			usbdbg("udccs0: 0x%02X", readl(UDCCS0) & 0xff);
+			return ret;
+
+		case EP0_IN_DATA:
+			usbdbg("state: EP0_IN_DATA");
+			if (readl(UDCCS0) & UDCCS_CTRL_OPR) {
+				/* premature status stage */
+				usbdbg("Premature status packet");
+				ep0laststate = ep0state;
+				ep0state = EP0_IDLE;
+				writel(UDCCS_CTRL_OPR | UDCCS_CTRL_FTF, UDCCS0);
+				return 0;
+			}
+			ret = udc_write_urb0(ep);
+//			if (readl(UDCCS0) & (UDCCS_CTRL_OPR | UDCCS_CTRL_SA)) {
+//				usbdbg("cleared one of OPR or SA bits");
+//				writel(readl(UDCCS0) &
+//					(UDCCS_CTRL_OPR | UDCCS_CTRL_SA), UDCCS0);
+//			}
+			usbdbg("in stage: udccs0: 0x%02X", readl(UDCCS0));
+			return ret;
+
+		case EP0_OUT_DATA:
+			usbdbg("state: EP0_OUT_DATA");
+			udccs0 = readl(UDCCS0);
+			usbdbg("udccs0: 0x%02X", udccs0);
+			if (udccs0 & UDCCS_CTRL_OPR) {
+				/* IN packet */
+				usbdbg("new OUT packet");
+				udc_read_urb0(ep);
+				usbdbg("EP0_OUT after read udccs0 0x%02X",
+					readl(UDCCS0));
+
+				/* we could do a packet parse and stall the
+				 * endpoint but pxa255 manual says we should
+				 * set IPR bit and eventually discard data
+				 * and we would do this. Packet parsing would
+				 * have place when status in packet occurs
+				 */
+			} else if ( !(udccs0 & UDCCS_CTRL_OPR) &&
+					!(udccs0 & UDCCS_CTRL_IPR) ) {
+				/* status in packet */
+				usbdbg("Status IN packet occured");
+				if (ep0_urb->actual_length ==
+					ep0_urb->device_request.wLength) {
+					/* whole data received,
+					 * everyting is ok */
+					ep0laststate = ep0state;
+					ep0state = EP0_IDLE;
+					if (ep0_recv_setup(ep0_urb)) {
+						/* world is so wrong... */
+						usberr("IN transaction have errors, ignoring");
+						udc_stall_ep0();
+						return -1;
+					}
+					return 0; /* everything is fine */
+				}
+
+				usbdbg("Status IN packet is premature");
+				ep0laststate = ep0state;
+				ep0state = EP0_IDLE;
+				/* we are ready to accept new data */
+				udc_flush_fifo(ep);
+				return 0;
+			}
+			break;
+
+		case EP0_END_XFER:
+			usbdbg("state: EP0_END_XFER");
+			if ( (readl(UDCCS0)&(UDCCS_CTRL_OPR | UDCCS_CTRL_SA))
+					== UDCCS_CTRL_OPR ) {
+				/* everything ok */
+				usbdbg("status out packet, host accepted data");
+				ep0laststate = ep0state;
+				ep0state = EP0_IDLE;
+				writel(UDCCS_CTRL_OPR, UDCCS0);
+				return 0;
+			}
+			usbdbg("unknown situation 0x%02X", readl(UDCCS0));
+			return -1;
+	}
+
+	return 0;
+}
+
+/* from pxa27x_udc.c */
+extern void udc_irq(void)
+{
+	int handled;
+	struct usb_endpoint_instance *endpoint;
+
+	do {
+		handled = 0;
+
+		/* Suspend Interrupt Request */
+		if (readl(UDCCR) & UDCCR_SUSIR) {
+			usbdbg("USB suspend");
+			udc_set_reg(UDCCR, UDCCR_SUSIR);
+			handled = 1;
+			ep0laststate = ep0state;
+			ep0state = EP0_IDLE;
+		}
+
+		/* Resume Interrupt Request */
+		if (readl(UDCCR) & UDCCR_RESIR) {
+			udc_set_reg(UDCCR, UDCCR_RESIR);
+			handled = 1;
+			usbdbg("USB resume");
+		}
+
+		/* Reset Interrupt Request */
+		if (readl(UDCCR) & UDCCR_RSTIR) {
+			udc_set_reg(UDCCR, UDCCR_RSTIR);
+			handled = 1;
+			usbdbg("USB reset");
+			//if ( !(readl(UDCCR) & UDCCR_UDA) ) {
+				/* wait for negotation */
+			//	return ;
+			//}
+			usbd_device_event_irq(udc_device, DEVICE_RESET, 0);
+		} else {
+			u32 usir0 = readl(USIR0) & 0xff;
+			u32 usir1 = readl(USIR1) & 0xff;
+			u32 ep_num;
+			int i;
+
+			if (!usir0 && !usir1)
+				continue;
+			
+			usbdbg("UISR0: %x, USIR1: %x", usir0, usir1);
+
+			/* control traffic */
+			if (usir0 & USIR0_IR0) {
+				writel(USIR0_IR0, USIR0);
+				udc_handle_ep0();
+				usbdbg("handled ep0");
+				handled = 1;
+			}
+
+			/* endpoint data transfers */
+			endpoint = udc_device->bus->endpoint_array;
+			for (i = 0; i < udc_device->bus->max_endpoints; ++i) {
+				u32 tmp;
+				ep_num = (endpoint[i].endpoint_address) &
+					USB_ENDPOINT_NUMBER_MASK;
+
+				if (!ep_num) /* skip control endpoint */
+					continue ;
+
+				if (ep_num > 15) {
+					usberr("Wrong ep num %d", ep_num);
+					continue ;
+				}
+
+				if (ep_num < 8) {
+					tmp = 1 << ep_num;
+					if (! (usir0 & tmp) )
+						continue ;
+
+					usbdbg("irq on endpoint %d", ep_num);
+					usir0 &= ~tmp;
+					writel(tmp, USIR0);
+					udc_handle_ep(&endpoint[i]);
+					handled = 1;
+				} else {
+					tmp = 1 << (ep_num - 8);
+					if (! (usir1 & tmp) )
+						continue ;
+					
+					usbdbg("irq on endpoint %d", ep_num);
+					usir1 &= ~tmp;
+					writel(tmp, USIR1);
+					udc_handle_ep(&endpoint[i]);
+					handled = 1;
+				}
+
+			}
+		}
+
+	} while (handled);
+}
+
+extern int udc_init(void)
+{
+	u32 reg;
+
+	udc_device = NULL;
+
+	/* Enable clock for usb controller */
+	reg = readl(CKEN);
+	reg |= CKEN11_USB;
+	writel(reg, CKEN);
+
+	/* Disable UDC */
+	udc_clear_reg(UDCCR, UDCCR_UDE);
+	udc_set_reg(UDCCR, UDCCR_SUSIR | UDCCR_RESIR);
+
+	/* Disable interrupts */
+	udc_set_reg(UICR0, 0xf);
+	udc_set_reg(UICR1, 0xf);
+
+	usbdbg("PXA25X udc start");
+
+	return 0;
+}
+
+extern void udc_connect(void)
+{
+	u32 reg;
+
+	/* setup pin as output */
+	reg = readl(GPDR(CONFIG_USB_DEV_PULLUP_GPIO));
+	reg |= GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO);
+	writel(reg, GPDR(CONFIG_USB_DEV_PULLUP_GPIO));
+
+	/* enable pullup */
+	writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO),
+			GPCR(CONFIG_USB_DEV_PULLUP_GPIO));
+
+	usbdbg("PXA25X UDC connected");
+}
+
+extern void udc_disconnect(void)
+{
+	u32 reg;
+
+	/* disable pullup resistor */
+	writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO),
+			GPSR(CONFIG_USB_DEV_PULLUP_GPIO));
+
+	/* setup pin as input, line will float */
+	reg = readl(GPDR(CONFIG_USB_DEV_PULLUP_GPIO));
+	reg &= ~GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO);
+	writel(reg, GPDR(CONFIG_USB_DEV_PULLUP_GPIO));
+
+	usbdbg("PXA25X UDC disconnected");
+}
+
+extern void udc_set_nak(int epid)
+{
+	/* FIXME: not implemented */
+}
+
+extern void udc_unset_nak(int epid)
+{
+	/* FIXME: not implemented */
+}
+
+static void udc_irq_enable(int num)
+{
+	u32 reg, uicr = UICR0, usir = USIR0;
+
+	if (num > 15) {
+		usberr("endpoint out of range %d\n", num);
+		return ;
+	}
+
+	if (num > 7) {
+		num -= 8;
+		uicr = UICR1;
+		usir = USIR1;
+	}
+
+	/* clear interrupt */
+	//	reg = (1 << num);
+	//	writel(reg, usir);
+
+	/* enable interrupt */
+	reg = readl(uicr) & 0xff;
+	reg &= ~(1 << num);
+	writel(reg, uicr);
+
+	usbdbg("uicr0: 0x%02X uicr1: 0x%02X", readl(UICR0), readl(UICR1));
+}
+
+extern void udc_setup_ep(struct usb_device_instance *device,
+		unsigned int ep, struct usb_endpoint_instance *endpoint)
+{
+	int ep_num, ep_addr, ep_isout, ep_type, ep_size;
+
+	usbdbg("setting up endpoint id %d", ep);
+
+	if (!endpoint) {
+		usberr("endpoint void!");
+		return ;
+	}
+
+	ep_addr = endpoint->endpoint_address;
+	ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK;
+	if (ep_num >= UDC_MAX_ENDPOINTS) {
+		usberr("unable to setup ep %d!\n", ep_num);
+		return ;
+	}
+
+	udc_irq_enable(ep_num);
+	if (ep_num == 0) {
+		/* clear opr, sst, sa */
+		writel(UDCCS_CTRL_OPR | UDCCS_CTRL_SST |
+				UDCCS_CTRL_SA, UDCCS0);
+		/* Done for ep0 */
+		return ;
+	}
+
+	ep_isout = (ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT;
+	ep_type = ep_isout ? endpoint->rcv_attributes : endpoint->tx_attributes;
+	ep_size = ep_isout ? endpoint->rcv_packetSize : endpoint->tx_packetSize;
+
+	usbdbg("addr %x, num %d, dir %s, type %s, packet size %d",
+			ep_addr, ep_num,
+			ep_isout ? "out" : "in",
+			ep_type == USB_ENDPOINT_XFER_ISOC ? "isoc" :
+			ep_type == USB_ENDPOINT_XFER_BULK ? "bulk" :
+			ep_type == USB_ENDPOINT_XFER_INT ? "int" : "???",
+			ep_size
+	      );
+
+	/* flush fifo */
+	udc_flush_fifo(endpoint);
+
+	usbdbg("UDC endpoint %d set up", ep);
+}
+
+static void udc_enable(struct usb_device_instance *device)
+{
+	/* enable intterupts for 0,1,2,5 */
+	//writel(0x000000027, UICR0);
+
+	/* clear intterupts */
+	//writel(USIR0_MASK, USIR0);
+	//writel(USIR0_MASK, USIR0);
+
+	/* enable UDC */
+	udc_set_reg(UDCCR, UDCCR_UDE);
+
+	if (! (UDCCR & UDCCR_UDA) ) {
+		usbdbg("Reset is already pending");
+		udc_set_reg(UDCCR, UDCCR_RSTIR);
+	}
+
+	/* enable reset irq */
+	udc_clear_reg(UDCCR, UDCCR_REM);
+
+	writel(UDCCFR_MB1, UDCCFR);
+
+	udc_device = device;
+	if (!ep0_urb)
+		ep0_urb = usbd_alloc_urb(device,
+				device->bus->endpoint_array);
+	else
+		usbinfo("ep0_urb not null!");
+
+	udc_irq_enable(0);
+
+	usbdbg("PXA25X UDC enabled");
+}
+
+/* from pxa27x_udc.c */
+extern void udc_startup_events(struct usb_device_instance *device)
+{
+	/* The DEVICE_INIT event puts the USB device in the state STATE_INIT */
+	usbd_device_event_irq(device, DEVICE_INIT, 0);
+
+	/* The DEVICE_CREATE event puts the USB device in the state
+	 * STATE_ATTACHED */
+	usbd_device_event_irq(device, DEVICE_CREATE, 0);
+
+	/* Some USB controller driver implementations signal
+	 * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here.
+	 * DEVICE_HUB_CONFIGURED causes a transition to the state
+	 * STATE_POWERED, and DEVICE_RESET causes a transition to
+	 * the state STATE_DEFAULT.
+	 */
+	udc_enable(device);
+}
+
+extern int udc_endpoint_write(struct usb_endpoint_instance *endpoint)
+{
+	int ret;
+	/* int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; */
+
+	ret = udc_write_urb(endpoint);
+	usbdbg("wrote new packet on %d (ret %d)", ep_num, ret);
+	return ret;
+}
diff --git a/include/usb/pxa25x_udc.h b/include/usb/pxa25x_udc.h
new file mode 100644
index 0000000..72ff346
--- /dev/null
+++ b/include/usb/pxa25x_udc.h
@@ -0,0 +1,65 @@
+/*
+ * PXA25x register declarations and HCD data structures
+ *
+ * Copyright (C) 2012 Lukasz Dalek <luk0104 at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef __PXA25X_UDC_H__
+#define __PXA25X_UDC_H__
+
+#include <usbdevice.h>
+
+#define EP0_IDLE		0
+#define EP0_IN_DATA		1
+#define EP0_OUT_DATA		2
+#define EP0_END_XFER		3
+
+#define UDC_MAX_ENDPOINTS	16
+
+#define MAX_ENDPOINTS		4
+#define EP0_MAX_PACKET_SIZE	16
+
+#define UDC_INT_ENDPOINT	0x5
+#define UDC_INT_PACKET_SIZE	8
+
+#define UDC_IN_ENDPOINT		0x1
+#define UDC_IN_PACKET_SIZE	64 /* double buffered */
+
+#define UDC_OUT_ENDPOINT	0x2
+#define UDC_OUT_PACKET_SIZE	64 /* double buffered */
+
+#define UDC_BULK_PACKET_SIZE	64 /* double bufered */
+
+extern void udc_irq(void);
+
+extern int udc_init(void);
+extern void udc_connect(void);
+extern void udc_disconnect(void);
+
+/* Flow control */
+extern void udc_set_nak(int epid);
+extern void udc_unset_nak(int epid);
+extern void udc_setup_ep(struct usb_device_instance *device,
+	unsigned int ep, struct usb_endpoint_instance *endpoint);
+
+extern void udc_startup_events(struct usb_device_instance *device);
+
+/* Higher level functions for abstracting away from specific device */
+extern int udc_endpoint_write(struct usb_endpoint_instance *endpoint);
+
+#endif /* __PXA25X_UDC_H__ */
-- 
1.7.3.4



More information about the U-Boot mailing list