summaryrefslogtreecommitdiff
path: root/drivers/usb/musb/musb_udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/musb/musb_udc.c')
-rw-r--r--drivers/usb/musb/musb_udc.c963
1 files changed, 963 insertions, 0 deletions
diff --git a/drivers/usb/musb/musb_udc.c b/drivers/usb/musb/musb_udc.c
new file mode 100644
index 000000000..fc43cf4f0
--- /dev/null
+++ b/drivers/usb/musb/musb_udc.c
@@ -0,0 +1,963 @@
+/*
+ * Copyright (c) 2009 Wind River Systems, Inc.
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * This file is a rewrite of the usb device part of
+ * repository git.omapzoom.org/repo/u-boot.git, branch master,
+ * file cpu/omap3/fastboot.c
+ *
+ * This is the unique part of its copyright :
+ *
+ * -------------------------------------------------------------------------
+ *
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * -------------------------------------------------------------------------
+ *
+ * The details of connecting the device to the uboot usb device subsystem
+ * came from the old omap3 repository www.sakoman.net/u-boot-omap3.git,
+ * branch omap3-dev-usb, file drivers/usb/usbdcore_musb.c
+ *
+ * This is the unique part of its copyright :
+ *
+ * -------------------------------------------------------------------------
+ *
+ * (C) Copyright 2008 Texas Instruments Incorporated.
+ *
+ * Based on
+ * u-boot OMAP1510 USB drivers (drivers/usbdcore_omap1510.c)
+ * twl4030 init based on linux (drivers/i2c/chips/twl4030_usb.c)
+ *
+ * Author: Diego Dompe (diego.dompe@ridgerun.com)
+ * Atin Malaviya (atin.malaviya@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
+ */
+
+#include <common.h>
+#include <usb/musb_udc.h>
+#include "../gadget/ep0.h"
+#include "musb_core.h"
+#if defined(CONFIG_USB_OMAP3)
+#include "omap3.h"
+#elif defined(CONFIG_USB_DAVINCI)
+#include "davinci.h"
+#endif
+
+/* Define MUSB_DEBUG for debugging */
+/* #define MUSB_DEBUG */
+#include "musb_debug.h"
+
+#define MAX_ENDPOINT 15
+
+#define GET_ENDPOINT(dev,ep) \
+(((struct usb_device_instance *)(dev))->bus->endpoint_array + ep)
+
+#define SET_EP0_STATE(s) \
+do { \
+ if ((0 <= (s)) && (SET_ADDRESS >= (s))) { \
+ if ((s) != ep0_state) { \
+ if ((debug_setup) && (debug_level > 1)) \
+ serial_printf("INFO : Changing state " \
+ "from %s to %s in %s at " \
+ "line %d\n", \
+ ep0_state_strings[ep0_state],\
+ ep0_state_strings[s], \
+ __PRETTY_FUNCTION__, \
+ __LINE__); \
+ ep0_state = s; \
+ } \
+ } else { \
+ if (debug_level > 0) \
+ serial_printf("Error at %s %d with setting " \
+ "state %d is invalid\n", \
+ __PRETTY_FUNCTION__, __LINE__, s); \
+ } \
+} while (0)
+
+/* static implies these initialized to 0 or NULL */
+static int debug_setup;
+static int debug_level;
+static struct musb_epinfo epinfo[MAX_ENDPOINT * 2];
+static enum ep0_state_enum {
+ IDLE = 0,
+ TX,
+ RX,
+ SET_ADDRESS
+} ep0_state = IDLE;
+static char *ep0_state_strings[4] = {
+ "IDLE",
+ "TX",
+ "RX",
+ "SET_ADDRESS",
+};
+
+static struct urb *ep0_urb;
+struct usb_endpoint_instance *ep0_endpoint;
+static struct usb_device_instance *udc_device;
+static int enabled;
+
+#ifdef MUSB_DEBUG
+static void musb_db_regs(void)
+{
+ u8 b;
+ u16 w;
+
+ b = readb(&musbr->faddr);
+ serial_printf("\tfaddr 0x%2.2x\n", b);
+
+ b = readb(&musbr->power);
+ musb_print_pwr(b);
+
+ w = readw(&musbr->ep[0].ep0.csr0);
+ musb_print_csr0(w);
+
+ b = readb(&musbr->devctl);
+ musb_print_devctl(b);
+
+ b = readb(&musbr->ep[0].ep0.configdata);
+ musb_print_config(b);
+
+ w = readw(&musbr->frame);
+ serial_printf("\tframe 0x%4.4x\n", w);
+
+ b = readb(&musbr->index);
+ serial_printf("\tindex 0x%2.2x\n", b);
+
+ w = readw(&musbr->ep[1].epN.rxmaxp);
+ musb_print_rxmaxp(w);
+
+ w = readw(&musbr->ep[1].epN.rxcsr);
+ musb_print_rxcsr(w);
+
+ w = readw(&musbr->ep[1].epN.txmaxp);
+ musb_print_txmaxp(w);
+
+ w = readw(&musbr->ep[1].epN.txcsr);
+ musb_print_txcsr(w);
+}
+#else
+#define musb_db_regs()
+#endif /* DEBUG_MUSB */
+
+static void musb_peri_softconnect(void)
+{
+ u8 power, devctl;
+ u8 intrusb;
+ u16 intrrx, intrtx;
+
+ /* Power off MUSB */
+ power = readb(&musbr->power);
+ power &= ~MUSB_POWER_SOFTCONN;
+ writeb(power, &musbr->power);
+
+ /* Read intr to clear */
+ intrusb = readb(&musbr->intrusb);
+ intrrx = readw(&musbr->intrrx);
+ intrtx = readw(&musbr->intrtx);
+
+ udelay(1000 * 1000); /* 1 sec */
+
+ /* Power on MUSB */
+ power = readb(&musbr->power);
+ power |= MUSB_POWER_SOFTCONN;
+ /*
+ * The usb device interface is usb 1.1
+ * Disable 2.0 high speed by clearring the hsenable bit.
+ */
+ power &= ~MUSB_POWER_HSENAB;
+ writeb(power, &musbr->power);
+
+ /* Check if device is in b-peripheral mode */
+ devctl = readb(&musbr->devctl);
+ if (!(devctl & MUSB_DEVCTL_BDEVICE) ||
+ (devctl & MUSB_DEVCTL_HM)) {
+ serial_printf("ERROR : Unsupport USB mode\n");
+ serial_printf("Check that mini-B USB cable is attached "
+ "to the device\n");
+ }
+
+ if (debug_setup && (debug_level > 1))
+ musb_db_regs();
+}
+
+static void musb_peri_reset(void)
+{
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("INFO : %s reset\n", __PRETTY_FUNCTION__);
+
+ if (ep0_endpoint)
+ ep0_endpoint->endpoint_address = 0xff;
+
+ /* Sync sw and hw addresses */
+ writeb(udc_device->address, &musbr->faddr);
+
+ SET_EP0_STATE(IDLE);
+}
+
+static void musb_peri_resume(void)
+{
+ /* noop */
+}
+
+static void musb_peri_ep0_stall(void)
+{
+ u16 csr0;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ csr0 |= MUSB_CSR0_P_SENDSTALL;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("INFO : %s stall\n", __PRETTY_FUNCTION__);
+}
+
+static void musb_peri_ep0_ack_req(void)
+{
+ u16 csr0;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ csr0 |= MUSB_CSR0_P_SVDRXPKTRDY;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+}
+
+static void musb_ep0_tx_ready(void)
+{
+ u16 csr0;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ csr0 |= MUSB_CSR0_TXPKTRDY;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+}
+
+static void musb_ep0_tx_ready_and_last(void)
+{
+ u16 csr0;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ csr0 |= (MUSB_CSR0_TXPKTRDY | MUSB_CSR0_P_DATAEND);
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+}
+
+static void musb_peri_ep0_last(void)
+{
+ u16 csr0;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ csr0 |= MUSB_CSR0_P_DATAEND;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+}
+
+static void musb_peri_ep0_set_address(void)
+{
+ u8 faddr;
+ writeb(udc_device->address, &musbr->faddr);
+
+ /* Verify */
+ faddr = readb(&musbr->faddr);
+ if (udc_device->address == faddr) {
+ SET_EP0_STATE(IDLE);
+ usbd_device_event_irq(udc_device, DEVICE_ADDRESS_ASSIGNED, 0);
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("INFO : %s Address set to %d\n",
+ __PRETTY_FUNCTION__, udc_device->address);
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s Address missmatch "
+ "sw %d vs hw %d\n",
+ __PRETTY_FUNCTION__,
+ udc_device->address, faddr);
+ }
+}
+
+static void musb_peri_rx_ack(unsigned int ep)
+{
+ u16 peri_rxcsr;
+
+ peri_rxcsr = readw(&musbr->ep[ep].epN.rxcsr);
+ peri_rxcsr &= ~MUSB_RXCSR_RXPKTRDY;
+ writew(peri_rxcsr, &musbr->ep[ep].epN.rxcsr);
+}
+
+static void musb_peri_tx_ready(unsigned int ep)
+{
+ u16 peri_txcsr;
+
+ peri_txcsr = readw(&musbr->ep[ep].epN.txcsr);
+ peri_txcsr |= MUSB_TXCSR_TXPKTRDY;
+ writew(peri_txcsr, &musbr->ep[ep].epN.txcsr);
+}
+
+static void musb_peri_ep0_zero_data_request(int err)
+{
+ musb_peri_ep0_ack_req();
+
+ if (err) {
+ musb_peri_ep0_stall();
+ SET_EP0_STATE(IDLE);
+ } else {
+
+ musb_peri_ep0_last();
+
+ /* USBD state */
+ switch (ep0_urb->device_request.bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("INFO : %s received set "
+ "address\n", __PRETTY_FUNCTION__);
+ break;
+
+ case USB_REQ_SET_CONFIGURATION:
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("INFO : %s Configured\n",
+ __PRETTY_FUNCTION__);
+ usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0);
+ break;
+ }
+
+ /* EP0 state */
+ if (USB_REQ_SET_ADDRESS == ep0_urb->device_request.bRequest) {
+ SET_EP0_STATE(SET_ADDRESS);
+ } else {
+ SET_EP0_STATE(IDLE);
+ }
+ }
+}
+
+static void musb_peri_ep0_rx_data_request(void)
+{
+ /*
+ * This is the completion of the data OUT / RX
+ *
+ * Host is sending data to ep0 that is not
+ * part of setup. This comes from the cdc_recv_setup
+ * op that is device specific.
+ *
+ */
+ musb_peri_ep0_ack_req();
+
+ ep0_endpoint->rcv_urb = ep0_urb;
+ ep0_urb->actual_length = 0;
+ SET_EP0_STATE(RX);
+}
+
+static void musb_peri_ep0_tx_data_request(int err)
+{
+ if (err) {
+ musb_peri_ep0_stall();
+ SET_EP0_STATE(IDLE);
+ } else {
+ musb_peri_ep0_ack_req();
+
+ ep0_endpoint->tx_urb = ep0_urb;
+ ep0_endpoint->sent = 0;
+ SET_EP0_STATE(TX);
+ }
+}
+
+static void musb_peri_ep0_idle(void)
+{
+ u16 count0;
+ int err;
+ u16 csr0;
+
+ /*
+ * Verify addresses
+ * A lot of confusion can be caused if the address
+ * in software, udc layer, does not agree with the
+ * hardware. Since the setting of the hardware address
+ * must be set after the set address request, the
+ * usb state machine is out of sync for a few frame.
+ * It is a good idea to run this check when changes
+ * are made to the state machine.
+ */
+ if ((debug_level > 0) &&
+ (ep0_state != SET_ADDRESS)) {
+ u8 faddr;
+
+ faddr = readb(&musbr->faddr);
+ if (udc_device->address != faddr) {
+ serial_printf("ERROR : %s addresses do not"
+ "match sw %d vs hw %d\n",
+ __PRETTY_FUNCTION__,
+ udc_device->address, faddr);
+ udelay(1000 * 1000);
+ hang();
+ }
+ }
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+
+ if (!(MUSB_CSR0_RXPKTRDY & csr0))
+ goto end;
+
+ count0 = readw(&musbr->ep[0].ep0.count0);
+ if (count0 == 0)
+ goto end;
+
+ if (count0 != 8) {
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("WARN : %s SETUP incorrect size %d\n",
+ __PRETTY_FUNCTION__, count0);
+ musb_peri_ep0_stall();
+ goto end;
+ }
+
+ read_fifo(0, count0, &ep0_urb->device_request);
+
+ if (debug_level > 2)
+ print_usb_device_request(&ep0_urb->device_request);
+
+ if (ep0_urb->device_request.wLength == 0) {
+ err = ep0_recv_setup(ep0_urb);
+
+ /* Zero data request */
+ musb_peri_ep0_zero_data_request(err);
+ } else {
+ /* Is data coming or going ? */
+ u8 reqType = ep0_urb->device_request.bmRequestType;
+
+ if (USB_REQ_DEVICE2HOST == (reqType & USB_REQ_DIRECTION_MASK)) {
+ err = ep0_recv_setup(ep0_urb);
+ /* Device to host */
+ musb_peri_ep0_tx_data_request(err);
+ } else {
+ /*
+ * Host to device
+ *
+ * The RX routine will call ep0_recv_setup
+ * when the data packet has arrived.
+ */
+ musb_peri_ep0_rx_data_request();
+ }
+ }
+
+end:
+ return;
+}
+
+static void musb_peri_ep0_rx(void)
+{
+ /*
+ * This is the completion of the data OUT / RX
+ *
+ * Host is sending data to ep0 that is not
+ * part of setup. This comes from the cdc_recv_setup
+ * op that is device specific.
+ *
+ * Pass the data back to driver ep0_recv_setup which
+ * should give the cdc_recv_setup the chance to handle
+ * the rx
+ */
+ u16 csr0;
+ u16 count0;
+
+ if (debug_level > 3) {
+ if (0 != ep0_urb->actual_length) {
+ serial_printf("%s finished ? %d of %d\n",
+ __PRETTY_FUNCTION__,
+ ep0_urb->actual_length,
+ ep0_urb->device_request.wLength);
+ }
+ }
+
+ if (ep0_urb->device_request.wLength == ep0_urb->actual_length) {
+ musb_peri_ep0_last();
+ SET_EP0_STATE(IDLE);
+ ep0_recv_setup(ep0_urb);
+ return;
+ }
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ if (!(MUSB_CSR0_RXPKTRDY & csr0))
+ return;
+
+ count0 = readw(&musbr->ep[0].ep0.count0);
+
+ if (count0) {
+ struct usb_endpoint_instance *endpoint;
+ u32 length;
+ u8 *data;
+
+ endpoint = ep0_endpoint;
+ if (endpoint && endpoint->rcv_urb) {
+ struct urb *urb = endpoint->rcv_urb;
+ unsigned int remaining_space = urb->buffer_length -
+ urb->actual_length;
+
+ if (remaining_space) {
+ int urb_bad = 0; /* urb is good */
+
+ if (count0 > remaining_space)
+ length = remaining_space;
+ else
+ length = count0;
+
+ data = (u8 *) urb->buffer_data;
+ data += urb->actual_length;
+
+ /* The common musb fifo reader */
+ read_fifo(0, length, data);
+
+ musb_peri_ep0_ack_req();
+
+ /*
+ * urb's actual_length is updated in
+ * usbd_rcv_complete
+ */
+ usbd_rcv_complete(endpoint, length, urb_bad);
+
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s no space in "
+ "rcv buffer\n",
+ __PRETTY_FUNCTION__);
+ }
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s problem with "
+ "endpoint\n",
+ __PRETTY_FUNCTION__);
+ }
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s with nothing to do\n",
+ __PRETTY_FUNCTION__);
+ }
+}
+
+static void musb_peri_ep0_tx(void)
+{
+ u16 csr0;
+ int transfer_size = 0;
+ unsigned int p, pm;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+
+ /* Check for pending tx */
+ if (csr0 & MUSB_CSR0_TXPKTRDY)
+ goto end;
+
+ /* Check if this is the last packet sent */
+ if (ep0_endpoint->sent >= ep0_urb->actual_length) {
+ SET_EP0_STATE(IDLE);
+ goto end;
+ }
+
+ transfer_size = ep0_urb->actual_length - ep0_endpoint->sent;
+ /* Is the transfer size negative ? */
+ if (transfer_size <= 0) {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s problem with the"
+ " transfer size %d\n",
+ __PRETTY_FUNCTION__,
+ transfer_size);
+ SET_EP0_STATE(IDLE);
+ goto end;
+ }
+
+ /* Truncate large transfers to the fifo size */
+ if (transfer_size > ep0_endpoint->tx_packetSize)
+ transfer_size = ep0_endpoint->tx_packetSize;
+
+ write_fifo(0, transfer_size, &ep0_urb->buffer[ep0_endpoint->sent]);
+ ep0_endpoint->sent += transfer_size;
+
+ /* Done or more to send ? */
+ if (ep0_endpoint->sent >= ep0_urb->actual_length)
+ musb_ep0_tx_ready_and_last();
+ else
+ musb_ep0_tx_ready();
+
+ /* Wait a bit */
+ pm = 10;
+ for (p = 0; p < pm; p++) {
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+ if (!(csr0 & MUSB_CSR0_TXPKTRDY))
+ break;
+
+ /* Double the delay. */
+ udelay(1 << pm);
+ }
+
+ if ((ep0_endpoint->sent >= ep0_urb->actual_length) && (p < pm))
+ SET_EP0_STATE(IDLE);
+
+end:
+ return;
+}
+
+static void musb_peri_ep0(void)
+{
+ u16 csr0;
+
+ if (SET_ADDRESS == ep0_state)
+ return;
+
+ csr0 = readw(&musbr->ep[0].ep0.csr0);
+
+ /* Error conditions */
+ if (MUSB_CSR0_P_SENTSTALL & csr0) {
+ csr0 &= ~MUSB_CSR0_P_SENTSTALL;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+ SET_EP0_STATE(IDLE);
+ }
+ if (MUSB_CSR0_P_SETUPEND & csr0) {
+ csr0 |= MUSB_CSR0_P_SVDSETUPEND;
+ writew(csr0, &musbr->ep[0].ep0.csr0);
+ SET_EP0_STATE(IDLE);
+ if ((debug_setup) && (debug_level > 1))
+ serial_printf("WARN: %s SETUPEND\n",
+ __PRETTY_FUNCTION__);
+ }
+
+ /* Normal states */
+ if (IDLE == ep0_state)
+ musb_peri_ep0_idle();
+
+ if (TX == ep0_state)
+ musb_peri_ep0_tx();
+
+ if (RX == ep0_state)
+ musb_peri_ep0_rx();
+}
+
+static void musb_peri_rx_ep(unsigned int ep)
+{
+ u16 peri_rxcount = readw(&musbr->ep[ep].epN.rxcount);
+
+ if (peri_rxcount) {
+ struct usb_endpoint_instance *endpoint;
+ u32 length;
+ u8 *data;
+
+ endpoint = GET_ENDPOINT(udc_device, ep);
+ if (endpoint && endpoint->rcv_urb) {
+ struct urb *urb = endpoint->rcv_urb;
+ unsigned int remaining_space = urb->buffer_length -
+ urb->actual_length;
+
+ if (remaining_space) {
+ int urb_bad = 0; /* urb is good */
+
+ if (peri_rxcount > remaining_space)
+ length = remaining_space;
+ else
+ length = peri_rxcount;
+
+ data = (u8 *) urb->buffer_data;
+ data += urb->actual_length;
+
+ /* The common musb fifo reader */
+ read_fifo(ep, length, data);
+
+ musb_peri_rx_ack(ep);
+
+ /*
+ * urb's actual_length is updated in
+ * usbd_rcv_complete
+ */
+ usbd_rcv_complete(endpoint, length, urb_bad);
+
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s %d no space "
+ "in rcv buffer\n",
+ __PRETTY_FUNCTION__, ep);
+ }
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s %d problem with "
+ "endpoint\n",
+ __PRETTY_FUNCTION__, ep);
+ }
+
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s %d with nothing to do\n",
+ __PRETTY_FUNCTION__, ep);
+ }
+}
+
+static void musb_peri_rx(u16 intr)
+{
+ unsigned int ep;
+
+ /* Check for EP0 */
+ if (0x01 & intr)
+ musb_peri_ep0();
+
+ for (ep = 1; ep < 16; ep++) {
+ if ((1 << ep) & intr)
+ musb_peri_rx_ep(ep);
+ }
+}
+
+static void musb_peri_tx(u16 intr)
+{
+ /* Check for EP0 */
+ if (0x01 & intr)
+ musb_peri_ep0_tx();
+
+ /*
+ * Use this in the future when handling epN tx
+ *
+ * u8 ep;
+ *
+ * for (ep = 1; ep < 16; ep++) {
+ * if ((1 << ep) & intr) {
+ * / * handle tx for this endpoint * /
+ * }
+ * }
+ */
+}
+
+void udc_irq(void)
+{
+ /* This is a high freq called function */
+ if (enabled) {
+ u8 intrusb;
+
+ intrusb = readb(&musbr->intrusb);
+
+ /*
+ * See drivers/usb/gadget/mpc8xx_udc.c for
+ * state diagram going from detached through
+ * configuration.
+ */
+ if (MUSB_INTR_RESUME & intrusb) {
+ usbd_device_event_irq(udc_device,
+ DEVICE_BUS_ACTIVITY, 0);
+ musb_peri_resume();
+ }
+
+ musb_peri_ep0();
+
+ if (MUSB_INTR_RESET & intrusb) {
+ usbd_device_event_irq(udc_device, DEVICE_RESET, 0);
+ musb_peri_reset();
+ }
+
+ if (MUSB_INTR_DISCONNECT & intrusb) {
+ /* cable unplugged from hub/host */
+ usbd_device_event_irq(udc_device, DEVICE_RESET, 0);
+ musb_peri_reset();
+ usbd_device_event_irq(udc_device, DEVICE_HUB_RESET, 0);
+ }
+
+ if (MUSB_INTR_SOF & intrusb) {
+ usbd_device_event_irq(udc_device,
+ DEVICE_BUS_ACTIVITY, 0);
+ musb_peri_resume();
+ }
+
+ if (MUSB_INTR_SUSPEND & intrusb) {
+ usbd_device_event_irq(udc_device,
+ DEVICE_BUS_INACTIVE, 0);
+ }
+
+ if (ep0_state != SET_ADDRESS) {
+ u16 intrrx, intrtx;
+
+ intrrx = readw(&musbr->intrrx);
+ intrtx = readw(&musbr->intrtx);
+
+ if (intrrx)
+ musb_peri_rx(intrrx);
+
+ if (intrtx)
+ musb_peri_tx(intrtx);
+ } else {
+ if (MUSB_INTR_SOF & intrusb) {
+ u8 faddr;
+ faddr = readb(&musbr->faddr);
+ /*
+ * Setting of the address can fail.
+ * Normally it succeeds the second time.
+ */
+ if (udc_device->address != faddr)
+ musb_peri_ep0_set_address();
+ }
+ }
+ }
+}
+
+void udc_set_nak(int ep_num)
+{
+ /* noop */
+}
+
+void udc_unset_nak(int ep_num)
+{
+ /* noop */
+}
+
+int udc_endpoint_write(struct usb_endpoint_instance *endpoint)
+{
+ int ret = 0;
+
+ /* Transmit only if the hardware is available */
+ if (endpoint->tx_urb && endpoint->state == 0) {
+ unsigned int ep = endpoint->endpoint_address &
+ USB_ENDPOINT_NUMBER_MASK;
+
+ u16 peri_txcsr = readw(&musbr->ep[ep].epN.txcsr);
+
+ /* Error conditions */
+ if (peri_txcsr & MUSB_TXCSR_P_UNDERRUN) {
+ peri_txcsr &= ~MUSB_TXCSR_P_UNDERRUN;
+ writew(peri_txcsr, &musbr->ep[ep].epN.txcsr);
+ }
+
+ if (debug_level > 1)
+ musb_print_txcsr(peri_txcsr);
+
+ /* Check if a packet is waiting to be sent */
+ if (!(peri_txcsr & MUSB_TXCSR_TXPKTRDY)) {
+ u32 length;
+ u8 *data;
+ struct urb *urb = endpoint->tx_urb;
+ unsigned int remaining_packet = urb->actual_length -
+ endpoint->sent;
+
+ if (endpoint->tx_packetSize < remaining_packet)
+ length = endpoint->tx_packetSize;
+ else
+ length = remaining_packet;
+
+ data = (u8 *) urb->buffer;
+ data += endpoint->sent;
+
+ /* common musb fifo function */
+ write_fifo(ep, length, data);
+
+ musb_peri_tx_ready(ep);
+
+ endpoint->last = length;
+ /* usbd_tx_complete will take care of updating 'sent' */
+ usbd_tx_complete(endpoint);
+ }
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s Problem with urb %p "
+ "or ep state %d\n",
+ __PRETTY_FUNCTION__,
+ endpoint->tx_urb, endpoint->state);
+ }
+
+ return ret;
+}
+
+void udc_setup_ep(struct usb_device_instance *device, unsigned int id,
+ struct usb_endpoint_instance *endpoint)
+{
+ if (0 == id) {
+ /* EP0 */
+ ep0_endpoint = endpoint;
+ ep0_endpoint->endpoint_address = 0xff;
+ ep0_urb = usbd_alloc_urb(device, endpoint);
+ } else if (MAX_ENDPOINT >= id) {
+ int ep_addr;
+
+ /* Check the direction */
+ ep_addr = endpoint->endpoint_address;
+ if (USB_DIR_IN == (ep_addr & USB_ENDPOINT_DIR_MASK)) {
+ /* IN */
+ epinfo[(id * 2) + 1].epsize = endpoint->tx_packetSize;
+ } else {
+ /* OUT */
+ epinfo[id * 2].epsize = endpoint->rcv_packetSize;
+ }
+
+ musb_configure_ep(&epinfo[0],
+ sizeof(epinfo) / sizeof(struct musb_epinfo));
+ } else {
+ if (debug_level > 0)
+ serial_printf("ERROR : %s endpoint request %d "
+ "exceeds maximum %d\n",
+ __PRETTY_FUNCTION__, id, MAX_ENDPOINT);
+ }
+}
+
+void udc_connect(void)
+{
+ /* noop */
+}
+
+void udc_disconnect(void)
+{
+ /* noop */
+}
+
+void udc_enable(struct usb_device_instance *device)
+{
+ /* Save the device structure pointer */
+ udc_device = device;
+
+ enabled = 1;
+}
+
+void udc_disable(void)
+{
+ enabled = 0;
+}
+
+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);
+
+ /* Resets the address to 0 */
+ usbd_device_event_irq(device, DEVICE_RESET, 0);
+
+ udc_enable(device);
+}
+
+int udc_init(void)
+{
+ int ret;
+ int ep_loop;
+
+ ret = musb_platform_init();
+ if (ret < 0)
+ goto end;
+
+ /* Configure all the endpoint FIFO's and start usb controller */
+ musbr = musb_cfg.regs;
+
+ /* Initialize the endpoints */
+ for (ep_loop = 0; ep_loop < MAX_ENDPOINT * 2; ep_loop++) {
+ epinfo[ep_loop].epnum = (ep_loop / 2) + 1;
+ epinfo[ep_loop].epdir = ep_loop % 2; /* OUT, IN */
+ epinfo[ep_loop].epsize = 0;
+ }
+
+ musb_peri_softconnect();
+
+ ret = 0;
+end:
+
+ return ret;
+}