mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-30 15:03:18 +00:00
f2e81c1d38
If we do not set FIFO buffer address and size for some endpoint which is in use then default programmed address 0x0 would be used which is in conflict with address of FIFO buffer for endpoint 0. Moreover address of FIFO buffer for endpoint 0 cannot be programmed, it is fixed to 0x0. Sharing address space between more endpoints cause data loss and unexpected errors. This patch is fixing transmission of characters over usbtty serial console and allows using of usbtty for debugging purposes on Nokia N900. Signed-off-by: Pali Rohár <pali@kernel.org> Reviewed-by: Lukasz Majewski <lukma@denx.de> Acked-by: Pavel Machek <pavel@ucw.cz>
954 lines
21 KiB
C
954 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* 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)
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <hang.h>
|
|
#include <serial.h>
|
|
#include <usbdevice.h>
|
|
#include <linux/delay.h>
|
|
#include <usb/udc.h>
|
|
#include "../gadget/ep0.h"
|
|
#include "musb_core.h"
|
|
#if defined(CONFIG_USB_OMAP3)
|
|
#include "omap3.h"
|
|
#elif defined(CONFIG_USB_AM35X)
|
|
#include "am35x.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 + 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;
|
|
|
|
static u16 pending_intrrx;
|
|
|
|
#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;
|
|
|
|
/* Power off MUSB */
|
|
power = readb(&musbr->power);
|
|
power &= ~MUSB_POWER_SOFTCONN;
|
|
writeb(power, &musbr->power);
|
|
|
|
/* Read intr to clear */
|
|
readb(&musbr->intrusb);
|
|
readw(&musbr->intrrx);
|
|
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;
|
|
u16 peri_rxcsr = readw(&musbr->ep[ep].epN.rxcsr);
|
|
|
|
if (!(peri_rxcsr & MUSB_RXCSR_RXPKTRDY)) {
|
|
if (debug_level > 0)
|
|
serial_printf("ERROR : %s %d without MUSB_RXCSR_RXPKTRDY set\n",
|
|
__PRETTY_FUNCTION__, ep);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
if (length == peri_rxcount)
|
|
musb_peri_rx_ack(ep);
|
|
else
|
|
pending_intrrx |= (1 << 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);
|
|
|
|
pending_intrrx |= (1 << ep);
|
|
}
|
|
} else {
|
|
if (debug_level > 0)
|
|
serial_printf("ERROR : %s %d problem with "
|
|
"endpoint\n",
|
|
__PRETTY_FUNCTION__, ep);
|
|
|
|
pending_intrrx |= (1 << ep);
|
|
}
|
|
|
|
} else {
|
|
if (debug_level > 0)
|
|
serial_printf("ERROR : %s %d with nothing to do\n",
|
|
__PRETTY_FUNCTION__, ep);
|
|
|
|
musb_peri_rx_ack(ep);
|
|
}
|
|
}
|
|
|
|
static void musb_peri_rx(u16 intr)
|
|
{
|
|
unsigned int ep;
|
|
|
|
/* First bit is reserved and does not indicate interrupt for EP0 */
|
|
|
|
for (ep = 1; ep < 16; ep++) {
|
|
if ((1 << ep) & intr)
|
|
musb_peri_rx_ep(ep);
|
|
}
|
|
}
|
|
|
|
static void musb_peri_tx(u16 intr)
|
|
{
|
|
unsigned int ep;
|
|
|
|
/* Check for EP0: first bit indicates interrupt for both RX and TX */
|
|
if (0x01 & intr)
|
|
musb_peri_ep0();
|
|
|
|
for (ep = 1; ep < 16; ep++) {
|
|
if ((1 << ep) & intr)
|
|
udc_endpoint_write(GET_ENDPOINT(udc_device, ep));
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
|
|
intrrx |= pending_intrrx;
|
|
pending_intrrx = 0;
|
|
|
|
if (intrrx)
|
|
musb_peri_rx(intrrx);
|
|
|
|
if (intrtx)
|
|
musb_peri_tx(intrtx);
|
|
} else {
|
|
if (readw(&musbr->intrtx) & 0x1) {
|
|
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) {
|
|
epinfo[(id * 2) + 0].epsize = endpoint->rcv_packetSize;
|
|
epinfo[(id * 2) + 1].epsize = endpoint->tx_packetSize;
|
|
musb_configure_ep(&epinfo[0], ARRAY_SIZE(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;
|
|
}
|