
Signed-off-by: Łukasz Dałek luk0104@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@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@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__ */