diff options
Diffstat (limited to 'drivers/usb/gadget/spr_udc.c')
-rwxr-xr-x | drivers/usb/gadget/spr_udc.c | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/drivers/usb/gadget/spr_udc.c b/drivers/usb/gadget/spr_udc.c new file mode 100755 index 0000000000..f2b06d68e2 --- /dev/null +++ b/drivers/usb/gadget/spr_udc.c @@ -0,0 +1,998 @@ +/* + * Based on drivers/usb/gadget/omap1510_udc.c + * TI OMAP1510 USB bus interface driver + * + * (C) Copyright 2009 + * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 + */ + +#include <common.h> +#include <asm/io.h> + +#include <usbdevice.h> +#include "ep0.h" +#include <usb/spr_udc.h> +#include <asm/arch/hardware.h> +#include <asm/arch/spr_misc.h> + +#define UDC_INIT_MDELAY 80 /* Device settle delay */ + +/* Some kind of debugging output... */ +#ifndef DEBUG_SPRUSBTTY +#define UDCDBG(str) +#define UDCDBGA(fmt, args...) +#else +#define UDCDBG(str) serial_printf(str "\n") +#define UDCDBGA(fmt, args...) serial_printf(fmt "\n", ##args) +#endif + +static struct urb *ep0_urb; +static struct usb_device_instance *udc_device; + +static struct plug_regs *const plug_regs_p = + (struct plug_regs * const)CONFIG_SYS_PLUG_BASE; +static struct udc_regs *const udc_regs_p = + (struct udc_regs * const)CONFIG_SYS_USBD_BASE; +static struct udc_endp_regs *const outep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->out_regs[0]; +static struct udc_endp_regs *const inep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->in_regs[0]; + +/* + * udc_state_transition - Write the next packet to TxFIFO. + * @initial: Initial state. + * @final: Final state. + * + * Helper function to implement device state changes. The device states and + * the events that transition between them are: + * + * STATE_ATTACHED + * || /\ + * \/ || + * DEVICE_HUB_CONFIGURED DEVICE_HUB_RESET + * || /\ + * \/ || + * STATE_POWERED + * || /\ + * \/ || + * DEVICE_RESET DEVICE_POWER_INTERRUPTION + * || /\ + * \/ || + * STATE_DEFAULT + * || /\ + * \/ || + * DEVICE_ADDRESS_ASSIGNED DEVICE_RESET + * || /\ + * \/ || + * STATE_ADDRESSED + * || /\ + * \/ || + * DEVICE_CONFIGURED DEVICE_DE_CONFIGURED + * || /\ + * \/ || + * STATE_CONFIGURED + * + * udc_state_transition transitions up (in the direction from STATE_ATTACHED + * to STATE_CONFIGURED) from the specified initial state to the specified final + * state, passing through each intermediate state on the way. If the initial + * state is at or above (i.e. nearer to STATE_CONFIGURED) the final state, then + * no state transitions will take place. + * + * udc_state_transition also transitions down (in the direction from + * STATE_CONFIGURED to STATE_ATTACHED) from the specified initial state to the + * specified final state, passing through each intermediate state on the way. + * If the initial state is at or below (i.e. nearer to STATE_ATTACHED) the final + * state, then no state transitions will take place. + * + * This function must only be called with interrupts disabled. + */ +static void udc_state_transition(usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial < final) { + switch (initial) { + case STATE_ATTACHED: + usbd_device_event_irq(udc_device, + DEVICE_HUB_CONFIGURED, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_ADDRESS_ASSIGNED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + case STATE_CONFIGURED: + break; + default: + break; + } + } else if (initial > final) { + switch (initial) { + case STATE_CONFIGURED: + usbd_device_event_irq(udc_device, + DEVICE_DE_CONFIGURED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_POWER_INTERRUPTION, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_HUB_RESET, 0); + case STATE_ATTACHED: + break; + default: + break; + } + } +} + +/* Stall endpoint */ +static void udc_stall_ep(u32 ep_num) +{ + writel(readl(&inep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &inep_regs_p[ep_num].endp_cntl); + + writel(readl(&outep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &outep_regs_p[ep_num].endp_cntl); +} + +static void *get_fifo(int ep_num, int in) +{ + u32 *fifo_ptr = (u32 *)CONFIG_SYS_FIFO_BASE; + + switch (ep_num) { + case UDC_EP3: + fifo_ptr += readl(&inep_regs_p[1].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP1: + fifo_ptr += readl(&inep_regs_p[0].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP0: + default: + if (in) { + fifo_ptr += + readl(&outep_regs_p[2].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } else { + break; + } + + case UDC_EP2: + fifo_ptr += readl(&outep_regs_p[0].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } + + return (void *)fifo_ptr; +} + +static int usbgetpckfromfifo(int epNum, u8 *bufp, u32 len) +{ + u8 *fifo_ptr = (u8 *)get_fifo(epNum, 0); + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + + if (readl(&udc_regs_p->dev_stat) & DEV_STAT_RXFIFO_EMPTY) + return -1; + + nw = len / sizeof(u32); + nb = len % sizeof(u32); + + wrdp = (u32 *)bufp; + for (i = 0; i < nw; i++) { + writel(readl(fifo_ptr), wrdp); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(readb(fifo_ptr), bytp); + fifo_ptr++; + bytp++; + } + readl(&outep_regs_p[epNum].write_done); + + return 0; +} + +static void usbputpcktofifo(int epNum, u8 *bufp, u32 len) +{ + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + u8 *fifo_ptr = get_fifo(epNum, 1); + + nw = len / sizeof(int); + nb = len % sizeof(int); + wrdp = (u32 *)bufp; + for (i = 0; i < nw; i++) { + writel(*wrdp, fifo_ptr); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(*bytp, fifo_ptr); + fifo_ptr++; + bytp++; + } +} + +/* + * spear_write_noniso_tx_fifo - Write the next packet to TxFIFO. + * @endpoint: Endpoint pointer. + * + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. The total amount of data in the urb is given + * by urb->actual_length. The maximum amount of data that can be sent in any + * one packet is given by endpoint->tx_packetSize. The number of data bytes + * from this URB that have already been transmitted is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + * + */ +static void spear_write_noniso_tx_fifo(struct usb_endpoint_instance + *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + int align; + + if (urb) { + u32 last; + + UDCDBGA("urb->buffer %p, buffer_length %d, actual_length %d", + urb->buffer, urb->buffer_length, urb->actual_length); + + last = MIN(urb->actual_length - endpoint->sent, + endpoint->tx_packetSize); + + if (last) { + u8 *cp = urb->buffer + endpoint->sent; + + /* + * This ensures that USBD packet fifo is accessed + * - through word aligned pointer or + * - through non word aligned pointer but only + * with a max length to make the next packet + * word aligned + */ + + align = ((ulong)cp % sizeof(int)); + if (align) + last = MIN(last, sizeof(int) - align); + + UDCDBGA("endpoint->sent %d, tx_packetSize %d, last %d", + endpoint->sent, endpoint->tx_packetSize, last); + + usbputpcktofifo(endpoint->endpoint_address & + USB_ENDPOINT_NUMBER_MASK, cp, last); + } + endpoint->last = last; + } +} + +/* + * Handle SETUP USB interrupt. + * This function implements TRM Figure 14-14. + */ +static void spear_udc_setup(struct usb_endpoint_instance *endpoint) +{ + u8 *datap = (u8 *)&ep0_urb->device_request; + int ep_addr = endpoint->endpoint_address; + + UDCDBG("-> Entering device setup"); + usbgetpckfromfifo(ep_addr, datap, 8); + + /* Try to process setup packet */ + if (ep0_recv_setup(ep0_urb)) { + /* Not a setup packet, stall next EP0 transaction */ + udc_stall_ep(0); + UDCDBG("can't parse setup packet, still waiting for setup"); + return; + } + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + UDCDBG("control write on EP0"); + if (le16_to_cpu(ep0_urb->device_request.wLength)) { + /* Stall this request */ + UDCDBG("Stalling unsupported EP0 control write data " + "stage."); + udc_stall_ep(0); + } + } else { + + UDCDBG("control read on EP0"); + /* + * The ep0_recv_setup function has already placed our response + * packet data in ep0_urb->buffer and the packet length in + * ep0_urb->actual_length. + */ + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + /* + * Write packet data to the FIFO. spear_write_noniso_tx_fifo + * will update endpoint->last with the number of bytes written + * to the FIFO. + */ + spear_write_noniso_tx_fifo(endpoint); + + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + + udc_unset_nak(endpoint->endpoint_address); + + UDCDBG("<- Leaving device setup"); +} + +/* + * Handle endpoint 0 RX interrupt + */ +static void spear_udc_ep0_rx(struct usb_endpoint_instance *endpoint) +{ + u8 dummy[64]; + + UDCDBG("RX on EP0"); + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType + & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE) { + /* + * This rx interrupt must be for a control write data + * stage packet. + * + * We don't support control write data stages. + * We should never end up here. + */ + + UDCDBG("Stalling unexpected EP0 control write " + "data stage packet"); + udc_stall_ep(0); + } else { + /* + * This rx interrupt must be for a control read status + * stage packet. + */ + UDCDBG("ACK on EP0 control read status stage packet"); + u32 len = (readl(&outep_regs_p[0].endp_status) >> 11) & 0xfff; + usbgetpckfromfifo(0, dummy, len); + } +} + +/* + * Handle endpoint 0 TX interrupt + */ +static void spear_udc_ep0_tx(struct usb_endpoint_instance *endpoint) +{ + struct usb_device_request *request = &ep0_urb->device_request; + int ep_addr; + + UDCDBG("TX on EP0"); + + /* Check direction */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK) == + USB_REQ_HOST2DEVICE) { + /* + * This tx interrupt must be for a control write status + * stage packet. + */ + UDCDBG("ACK on EP0 control write status stage packet"); + } else { + /* + * This tx interrupt must be for a control read data + * stage packet. + */ + int wLength = le16_to_cpu(request->wLength); + + /* + * Update our count of bytes sent so far in this + * transfer. + */ + endpoint->sent += endpoint->last; + + /* + * We are finished with this transfer if we have sent + * all of the bytes in our tx urb (urb->actual_length) + * unless we need a zero-length terminating packet. We + * need a zero-length terminating packet if we returned + * fewer bytes than were requested (wLength) by the host, + * and the number of bytes we returned is an exact + * multiple of the packet size endpoint->tx_packetSize. + */ + if ((endpoint->sent == ep0_urb->actual_length) && + ((ep0_urb->actual_length == wLength) || + (endpoint->last != endpoint->tx_packetSize))) { + /* Done with control read data stage. */ + UDCDBG("control read data stage complete"); + } else { + /* + * We still have another packet of data to send + * in this control read data stage or else we + * need a zero-length terminating packet. + */ + UDCDBG("ACK control read data stage packet"); + spear_write_noniso_tx_fifo(endpoint); + + ep_addr = endpoint->endpoint_address; + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + } +} + +static struct usb_endpoint_instance *spear_find_ep(int ep) +{ + int i; + + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + if ((udc_device->bus->endpoint_array[i].endpoint_address & + USB_ENDPOINT_NUMBER_MASK) == ep) + return &udc_device->bus->endpoint_array[i]; + } + return NULL; +} + +/* + * Handle RX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 1 to 15. + */ +static void spear_udc_epn_rx(int ep) +{ + int nbytes = 0; + struct urb *urb; + struct usb_endpoint_instance *endpoint = spear_find_ep(ep); + + if (endpoint) { + urb = endpoint->rcv_urb; + + if (urb) { + u8 *cp = urb->buffer + urb->actual_length; + + nbytes = (readl(&outep_regs_p[ep].endp_status) >> 11) & + 0xfff; + usbgetpckfromfifo(ep, cp, nbytes); + usbd_rcv_complete(endpoint, nbytes, 0); + } + } +} + +/* + * Handle TX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 16 to 30. + */ +static void spear_udc_epn_tx(int ep) +{ + struct usb_endpoint_instance *endpoint = spear_find_ep(ep); + + /* + * We need to transmit a terminating zero-length packet now if + * we have sent all of the data in this URB and the transfer + * size was an exact multiple of the packet size. + */ + if (endpoint && endpoint->tx_urb && endpoint->tx_urb->actual_length) { + if (endpoint->last == endpoint->tx_packetSize) { + /* handle zero length packet here */ + writel(0x0, &inep_regs_p[ep].write_done); + } + /* retire the data that was just sent */ + usbd_tx_complete(endpoint); + /* + * Check to see if we have more data ready to transmit + * now. + */ + if (endpoint->tx_urb && endpoint->tx_urb->actual_length) { + /* write data to FIFO */ + spear_write_noniso_tx_fifo(endpoint); + writel(0x0, &inep_regs_p[ep].write_done); + + } else if (endpoint->tx_urb + && (endpoint->tx_urb->actual_length == 0)) { + /* udc_set_nak(ep); */ + } + } +} + +/* + * Start of public functions. + */ + +/* Called to start packet transmission. */ +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + udc_unset_nak(endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK); + return 0; +} + +/* Start to initialize h/w stuff */ +int udc_init(void) +{ + int i; + u32 plug_st; + + udc_device = NULL; + + UDCDBG("starting"); + + readl(&plug_regs_p->plug_pending); + + udc_disconnect(); + + for (i = 0; i < UDC_INIT_MDELAY; i++) + udelay(1000); + + plug_st = readl(&plug_regs_p->plug_state); + writel(plug_st | PLUG_STATUS_EN, &plug_regs_p->plug_state); + + writel(~0x0, &udc_regs_p->endp_int); + writel(~0x0, &udc_regs_p->dev_int_mask); + writel(~0x0, &udc_regs_p->endp_int_mask); + + writel(DEV_CONF_FS_SPEED | DEV_CONF_REMWAKEUP | DEV_CONF_SELFPOW | + /* Dev_Conf_SYNCFRAME | */ + DEV_CONF_PHYINT_16, &udc_regs_p->dev_conf); + + writel(0x0, &udc_regs_p->dev_cntl); + + /* Clear all interrupts pending */ + writel(DEV_INT_MSK, &udc_regs_p->dev_int); + + return 0; +} + +/* + * udc_setup_ep - setup endpoint + * Associate a physical endpoint with endpoint_instance + */ +void udc_setup_ep(struct usb_device_instance *device, + u32 ep, struct usb_endpoint_instance *endpoint) +{ + UDCDBGA("setting up endpoint addr %x", endpoint->endpoint_address); + int ep_addr; + int ep_num, ep_type; + int packet_size; + int buffer_size; + int attributes; + char *tt; + u32 endp_intmask; + + tt = getenv("usbtty"); + if (!tt) + tt = "generic"; + + ep_addr = endpoint->endpoint_address; + ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* IN endpoint */ + packet_size = endpoint->tx_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->tx_attributes; + } else { + /* OUT endpoint */ + packet_size = endpoint->rcv_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->rcv_attributes; + } + + switch (attributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + ep_type = ENDP_EPTYPE_CNTL; + break; + case USB_ENDPOINT_XFER_BULK: + default: + ep_type = ENDP_EPTYPE_BULK; + break; + case USB_ENDPOINT_XFER_INT: + ep_type = ENDP_EPTYPE_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + ep_type = ENDP_EPTYPE_ISO; + break; + } + + struct udc_endp_regs *out_p = &outep_regs_p[ep_num]; + struct udc_endp_regs *in_p = &inep_regs_p[ep_num]; + + if (!ep_addr) { + /* Setup endpoint 0 */ + buffer_size = packet_size; + + writel(readl(&in_p->endp_cntl) | ENDP_CNTL_CNAK, + &in_p->endp_cntl); + + writel(readl(&out_p->endp_cntl) | ENDP_CNTL_CNAK, + &out_p->endp_cntl); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_FLUSH, &in_p->endp_cntl); + + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + + writel(packet_size, &in_p->endp_maxpacksize); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_RRDY, &out_p->endp_cntl); + + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + writel((packet_size << 19) | ENDP_EPTYPE_CNTL, + &udc_regs_p->udc_endp_reg[ep_num]); + + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* Setup the IN endpoint */ + writel(0x0, &in_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &in_p->endp_cntl); + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + writel(packet_size, &in_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + if (ep_type == ENDP_EPTYPE_INT) { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + /* Conf no. 1 Interface no. 1 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + } else { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } else { + /* Setup the OUT endpoint */ + writel(0x0, &out_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &out_p->endp_cntl); + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } + + endp_intmask = readl(&udc_regs_p->endp_int_mask); + endp_intmask &= ~((1 << ep_num) | 0x10000 << ep_num); + writel(endp_intmask, &udc_regs_p->endp_int_mask); +} + +/* Turn on the USB connection by enabling the pullup resistor */ +void udc_connect(void) +{ + u32 plug_st; + + plug_st = readl(&plug_regs_p->plug_state); + plug_st &= ~(PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Turn off the USB connection by disabling the pullup resistor */ +void udc_disconnect(void) +{ + u32 plug_st; + + plug_st = readl(&plug_regs_p->plug_state); + plug_st |= (PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Switch on the UDC */ +void udc_enable(struct usb_device_instance *device) +{ + UDCDBGA("enable device %p, status %d", device, device->status); + + /* Save the device structure pointer */ + udc_device = device; + + /* Setup ep0 urb */ + if (!ep0_urb) { + ep0_urb = + usbd_alloc_urb(udc_device, udc_device->bus->endpoint_array); + } else { + serial_printf("udc_enable: ep0_urb already allocated %p\n", + ep0_urb); + } + + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); +} + +/** + * udc_startup - allow udc code to do any additional startup + */ +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. + * The SPEAr USB client controller has the capability to detect when the + * USB cable is connected to a powered USB bus, so we will defer the + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events until later. + */ + + udc_enable(device); +} + +/* + * Plug detection interrupt handling + */ +void spear_udc_plug_irq(void) +{ + if (readl(&plug_regs_p->plug_state) & PLUG_STATUS_ATTACHED) { + /* + * USB cable attached + * Turn off PHY reset bit (PLUG detect). + * Switch PHY opmode to normal operation (PLUG detect). + */ + udc_connect(); + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); + + UDCDBG("device attached and powered"); + udc_state_transition(udc_device->device_state, STATE_POWERED); + } else { + /* + * USB cable detached + * Reset the PHY and switch the mode. + */ + udc_disconnect(); + writel(~0x0, &udc_regs_p->dev_int_mask); + + UDCDBG("device detached or unpowered"); + udc_state_transition(udc_device->device_state, STATE_ATTACHED); + } +} + +/* + * Device interrupt handling + */ +void spear_udc_dev_irq(void) +{ + if (readl(&udc_regs_p->dev_int) & DEV_INT_USBRESET) { + writel(~0x0, &udc_regs_p->endp_int_mask); + + udc_connect(); + + writel(readl(&inep_regs_p[0].endp_cntl) | ENDP_CNTL_FLUSH, + &inep_regs_p[0].endp_cntl); + + writel(DEV_INT_USBRESET, &udc_regs_p->dev_int); + + UDCDBG("device reset in progess"); + udc_state_transition(udc_device->device_state, STATE_DEFAULT); + } + + /* Device Enumeration completed */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_ENUM) { + writel(DEV_INT_ENUM, &udc_regs_p->dev_int); + + /* Endpoint interrupt enabled for Ctrl IN & Ctrl OUT */ + writel(readl(&udc_regs_p->endp_int_mask) & ~0x10001, + &udc_regs_p->endp_int_mask); + + UDCDBG("default -> addressed"); + udc_state_transition(udc_device->device_state, STATE_ADDRESSED); + } + + /* The USB will be in SUSPEND in 3 ms */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_INACTIVE) { + writel(DEV_INT_INACTIVE, &udc_regs_p->dev_int); + + UDCDBG("entering inactive state"); + /* usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); */ + } + + /* SetConfiguration command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETCFG) { + writel(DEV_INT_SETCFG, &udc_regs_p->dev_int); + + UDCDBG("entering configured state"); + udc_state_transition(udc_device->device_state, + STATE_CONFIGURED); + } + + /* SetInterface command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETINTF) + writel(DEV_INT_SETINTF, &udc_regs_p->dev_int); + + /* USB Suspend detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SUSPUSB) { + writel(DEV_INT_SUSPUSB, &udc_regs_p->dev_int); + + UDCDBG("entering suspended state"); + usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); + } + + /* USB Start-Of-Frame detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SOF) + writel(DEV_INT_SOF, &udc_regs_p->dev_int); +} + +/* + * Endpoint interrupt handling + */ +void spear_udc_endpoint_irq(void) +{ + while (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLOUT) { + + writel(ENDP0_INT_CTRLOUT, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[0].endp_status) & ENDP_STATUS_OUTMSK) + == ENDP_STATUS_OUT_SETUP) { + spear_udc_setup(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_SETUP, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + spear_udc_ep0_rx(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + /* NONE received */ + } + + writel(0x0, &outep_regs_p[0].endp_status); + } + + if (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLIN) { + spear_udc_ep0_tx(udc_device->bus->endpoint_array + 0); + + writel(ENDP_STATUS_IN, &inep_regs_p[0].endp_status); + writel(ENDP0_INT_CTRLIN, &udc_regs_p->endp_int); + } + + while (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOOUT_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOOUT_MSK; + + ep_int >>= 16; + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + writel((1 << 16) << epnum, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + + spear_udc_epn_rx(epnum); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[epnum].endp_status); + } else if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + writel(0x0, &outep_regs_p[epnum].endp_status); + } + } + + if (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOIN_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOIN_MSK; + + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + if (readl(&inep_regs_p[epnum].endp_status) & ENDP_STATUS_IN) { + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + spear_udc_epn_tx(epnum); + + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + } + + writel((1 << epnum), &udc_regs_p->endp_int); + } +} + +/* + * UDC interrupts + */ +void udc_irq(void) +{ + /* + * Loop while we have interrupts. + * If we don't do this, the input chain + * polling delay is likely to miss + * host requests. + */ + while (readl(&plug_regs_p->plug_pending)) + spear_udc_plug_irq(); + + while (readl(&udc_regs_p->dev_int)) + spear_udc_dev_irq(); + + if (readl(&udc_regs_p->endp_int)) + spear_udc_endpoint_irq(); +} + +/* Flow control */ +void udc_set_nak(int epid) +{ + writel(readl(&inep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &inep_regs_p[epid].endp_cntl); + + writel(readl(&outep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &outep_regs_p[epid].endp_cntl); +} + +void udc_unset_nak(int epid) +{ + u32 val; + + val = readl(&inep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &inep_regs_p[epid].endp_cntl); + + val = readl(&outep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &outep_regs_p[epid].endp_cntl); +} |