| /* | |
| * 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); | |
| } |