[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