@ -21,12 +21,14 @@
* MA 02111 - 1307 USA
*/
# include <common.h>
# include <errno.h>
# include <asm/byteorder.h>
# include <asm/unaligned.h>
# include <usb.h>
# include <asm/io.h>
# include <malloc.h>
# include <watchdog.h>
# include <linux/compiler.h>
# include "ehci.h"
@ -39,7 +41,10 @@ static struct ehci_ctrl {
struct ehci_hcor * hcor ;
int rootdev ;
uint16_t portreset ;
struct QH qh_list __attribute__ ( ( aligned ( USB_DMA_MINALIGN ) ) ) ;
struct QH qh_list __aligned ( USB_DMA_MINALIGN ) ;
struct QH periodic_queue __aligned ( USB_DMA_MINALIGN ) ;
uint32_t * periodic_list ;
int ntds ;
} ehcic [ CONFIG_USB_MAX_CONTROLLER_COUNT ] ;
# define ALIGN_END_ADDR(type, ptr, size) \
@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller)
uint32_t reg ;
uint32_t cmd ;
struct QH * qh_list ;
struct QH * periodic ;
int i ;
if ( ehci_hcd_init ( index , & ehcic [ index ] . hccr , & ehcic [ index ] . hcor ) )
return - 1 ;
@ -887,6 +894,40 @@ int usb_lowlevel_init(int index, void **controller)
qh_list - > qh_overlay . qt_token =
cpu_to_hc32 ( QT_TOKEN_STATUS ( QT_TOKEN_STATUS_HALTED ) ) ;
/* Set async. queue head pointer. */
ehci_writel ( & ehcic [ index ] . hcor - > or_asynclistaddr , ( uint32_t ) qh_list ) ;
/*
* Set up periodic list
* Step 1 : Parent QH for all periodic transfers .
*/
periodic = & ehcic [ index ] . periodic_queue ;
memset ( periodic , 0 , sizeof ( * periodic ) ) ;
periodic - > qh_link = cpu_to_hc32 ( QH_LINK_TERMINATE ) ;
periodic - > qh_overlay . qt_next = cpu_to_hc32 ( QT_NEXT_TERMINATE ) ;
periodic - > qh_overlay . qt_altnext = cpu_to_hc32 ( QT_NEXT_TERMINATE ) ;
/*
* Step 2 : Setup frame - list : Every microframe , USB tries the same list .
* In particular , device specifications on polling frequency
* are disregarded . Keyboards seem to send NAK / NYet reliably
* when polled with an empty buffer .
*
* Split Transactions will be spread across microframes using
* S - mask and C - mask .
*/
ehcic [ index ] . periodic_list = memalign ( 4096 , 1024 * 4 ) ;
if ( ! ehcic [ index ] . periodic_list )
return - ENOMEM ;
for ( i = 0 ; i < 1024 ; i + + ) {
ehcic [ index ] . periodic_list [ i ] = ( uint32_t ) periodic
| QH_LINK_TYPE_QH ;
}
/* Set periodic list base address */
ehci_writel ( & ehcic [ index ] . hcor - > or_periodiclistbase ,
( uint32_t ) ehcic [ index ] . periodic_list ) ;
reg = ehci_readl ( & ehcic [ index ] . hccr - > cr_hcsparams ) ;
descriptor . hub . bNbrPorts = HCS_N_PORTS ( reg ) ;
debug ( " Register %x NbrPorts %d \n " , reg , descriptor . hub . bNbrPorts ) ;
@ -956,10 +997,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
return ehci_submit_async ( dev , pipe , buffer , length , setup ) ;
}
struct int_queue {
struct QH * first ;
struct QH * current ;
struct QH * last ;
struct qTD * tds ;
} ;
# define NEXT_QH(qh) (struct QH *)((qh)->qh_link & ~0x1f)
static int
enable_periodic ( struct ehci_ctrl * ctrl )
{
uint32_t cmd ;
struct ehci_hcor * hcor = ctrl - > hcor ;
int ret ;
cmd = ehci_readl ( & hcor - > or_usbcmd ) ;
cmd | = CMD_PSE ;
ehci_writel ( & hcor - > or_usbcmd , cmd ) ;
ret = handshake ( ( uint32_t * ) & hcor - > or_usbsts ,
STS_PSS , STS_PSS , 100 * 1000 ) ;
if ( ret < 0 ) {
printf ( " EHCI failed: timeout when enabling periodic list \n " ) ;
return - ETIMEDOUT ;
}
udelay ( 1000 ) ;
return 0 ;
}
static int
disable_periodic ( struct ehci_ctrl * ctrl )
{
uint32_t cmd ;
struct ehci_hcor * hcor = ctrl - > hcor ;
int ret ;
cmd = ehci_readl ( & hcor - > or_usbcmd ) ;
cmd & = ~ CMD_PSE ;
ehci_writel ( & hcor - > or_usbcmd , cmd ) ;
ret = handshake ( ( uint32_t * ) & hcor - > or_usbsts ,
STS_PSS , 0 , 100 * 1000 ) ;
if ( ret < 0 ) {
printf ( " EHCI failed: timeout when disabling periodic list \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
static int periodic_schedules ;
struct int_queue *
create_int_queue ( struct usb_device * dev , unsigned long pipe , int queuesize ,
int elementsize , void * buffer )
{
struct ehci_ctrl * ctrl = dev - > controller ;
struct int_queue * result = NULL ;
int i ;
debug ( " Enter create_int_queue \n " ) ;
if ( usb_pipetype ( pipe ) ! = PIPE_INTERRUPT ) {
debug ( " non-interrupt pipe (type=%lu) " , usb_pipetype ( pipe ) ) ;
return NULL ;
}
/* limit to 4 full pages worth of data -
* we can safely fit them in a single TD ,
* no matter the alignment
*/
if ( elementsize > = 16384 ) {
debug ( " too large elements for interrupt transfers \n " ) ;
return NULL ;
}
result = malloc ( sizeof ( * result ) ) ;
if ( ! result ) {
debug ( " ehci intr queue: out of memory \n " ) ;
goto fail1 ;
}
result - > first = memalign ( 32 , sizeof ( struct QH ) * queuesize ) ;
if ( ! result - > first ) {
debug ( " ehci intr queue: out of memory \n " ) ;
goto fail2 ;
}
result - > current = result - > first ;
result - > last = result - > first + queuesize - 1 ;
result - > tds = memalign ( 32 , sizeof ( struct qTD ) * queuesize ) ;
if ( ! result - > tds ) {
debug ( " ehci intr queue: out of memory \n " ) ;
goto fail3 ;
}
memset ( result - > first , 0 , sizeof ( struct QH ) * queuesize ) ;
memset ( result - > tds , 0 , sizeof ( struct qTD ) * queuesize ) ;
for ( i = 0 ; i < queuesize ; i + + ) {
struct QH * qh = result - > first + i ;
struct qTD * td = result - > tds + i ;
void * * buf = & qh - > buffer ;
qh - > qh_link = ( uint32_t ) ( qh + 1 ) | QH_LINK_TYPE_QH ;
if ( i = = queuesize - 1 )
qh - > qh_link = QH_LINK_TERMINATE ;
qh - > qh_overlay . qt_next = ( uint32_t ) td ;
qh - > qh_endpt1 = ( 0 < < 28 ) | /* No NAK reload (ehci 4.9) */
( usb_maxpacket ( dev , pipe ) < < 16 ) | /* MPS */
( 1 < < 14 ) |
QH_ENDPT1_EPS ( ehci_encode_speed ( dev - > speed ) ) |
( usb_pipeendpoint ( pipe ) < < 8 ) | /* Endpoint Number */
( usb_pipedevice ( pipe ) < < 0 ) ;
qh - > qh_endpt2 = ( 1 < < 30 ) | /* 1 Tx per mframe */
( 1 < < 0 ) ; /* S-mask: microframe 0 */
if ( dev - > speed = = USB_SPEED_LOW | |
dev - > speed = = USB_SPEED_FULL ) {
debug ( " TT: port: %d, hub address: %d \n " ,
dev - > portnr , dev - > parent - > devnum ) ;
qh - > qh_endpt2 | = ( dev - > portnr < < 23 ) |
( dev - > parent - > devnum < < 16 ) |
( 0x1c < < 8 ) ; /* C-mask: microframes 2-4 */
}
td - > qt_next = QT_NEXT_TERMINATE ;
td - > qt_altnext = QT_NEXT_TERMINATE ;
debug ( " communication direction is '%s' \n " ,
usb_pipein ( pipe ) ? " in " : " out " ) ;
td - > qt_token = ( elementsize < < 16 ) |
( ( usb_pipein ( pipe ) ? 1 : 0 ) < < 8 ) | /* IN/OUT token */
0x80 ; /* active */
td - > qt_buffer [ 0 ] = ( uint32_t ) buffer + i * elementsize ;
td - > qt_buffer [ 1 ] = ( td - > qt_buffer [ 0 ] + 0x1000 ) & ~ 0xfff ;
td - > qt_buffer [ 2 ] = ( td - > qt_buffer [ 0 ] + 0x2000 ) & ~ 0xfff ;
td - > qt_buffer [ 3 ] = ( td - > qt_buffer [ 0 ] + 0x3000 ) & ~ 0xfff ;
td - > qt_buffer [ 4 ] = ( td - > qt_buffer [ 0 ] + 0x4000 ) & ~ 0xfff ;
* buf = buffer + i * elementsize ;
}
if ( disable_periodic ( ctrl ) < 0 ) {
debug ( " FATAL: periodic should never fail, but did " ) ;
goto fail3 ;
}
/* hook up to periodic list */
struct QH * list = & ctrl - > periodic_queue ;
result - > last - > qh_link = list - > qh_link ;
list - > qh_link = ( uint32_t ) result - > first | QH_LINK_TYPE_QH ;
if ( enable_periodic ( ctrl ) < 0 ) {
debug ( " FATAL: periodic should never fail, but did " ) ;
goto fail3 ;
}
periodic_schedules + + ;
debug ( " Exit create_int_queue \n " ) ;
return result ;
fail3 :
if ( result - > tds )
free ( result - > tds ) ;
fail2 :
if ( result - > first )
free ( result - > first ) ;
if ( result )
free ( result ) ;
fail1 :
return NULL ;
}
void * poll_int_queue ( struct usb_device * dev , struct int_queue * queue )
{
struct QH * cur = queue - > current ;
/* depleted queue */
if ( cur = = NULL ) {
debug ( " Exit poll_int_queue with completed queue \n " ) ;
return NULL ;
}
/* still active */
if ( cur - > qh_overlay . qt_token & 0x80 ) {
debug ( " Exit poll_int_queue with no completed intr transfer. "
" token is %x \n " , cur - > qh_overlay . qt_token ) ;
return NULL ;
}
if ( ! ( cur - > qh_link & QH_LINK_TERMINATE ) )
queue - > current + + ;
else
queue - > current = NULL ;
debug ( " Exit poll_int_queue with completed intr transfer. "
" token is %x at %p (first at %p) \n " , cur - > qh_overlay . qt_token ,
& cur - > qh_overlay . qt_token , queue - > first ) ;
return cur - > buffer ;
}
/* Do not free buffers associated with QHs, they're owned by someone else */
int
destroy_int_queue ( struct usb_device * dev , struct int_queue * queue )
{
struct ehci_ctrl * ctrl = dev - > controller ;
int result = - 1 ;
unsigned long timeout ;
if ( disable_periodic ( ctrl ) < 0 ) {
debug ( " FATAL: periodic should never fail, but did " ) ;
goto out ;
}
periodic_schedules - - ;
struct QH * cur = & ctrl - > periodic_queue ;
timeout = get_timer ( 0 ) + 500 ; /* abort after 500ms */
while ( ! ( cur - > qh_link & QH_LINK_TERMINATE ) ) {
debug ( " considering %p, with qh_link %x \n " , cur , cur - > qh_link ) ;
if ( NEXT_QH ( cur ) = = queue - > first ) {
debug ( " found candidate. removing from chain \n " ) ;
cur - > qh_link = queue - > last - > qh_link ;
result = 0 ;
break ;
}
cur = NEXT_QH ( cur ) ;
if ( get_timer ( 0 ) > timeout ) {
printf ( " Timeout destroying interrupt endpoint queue \n " ) ;
result = - 1 ;
goto out ;
}
}
if ( periodic_schedules > 0 ) {
result = enable_periodic ( ctrl ) ;
if ( result < 0 )
debug ( " FATAL: periodic should never fail, but did " ) ;
}
out :
free ( queue - > tds ) ;
free ( queue - > first ) ;
free ( queue ) ;
return result ;
}
int
submit_int_msg ( struct usb_device * dev , unsigned long pipe , void * buffer ,
int length , int interval )
{
void * backbuffer ;
struct int_queue * queue ;
unsigned long timeout ;
int result = 0 , ret ;
debug ( " dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d " ,
dev , pipe , buffer , length , interval ) ;
@ -975,9 +1260,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
* not require more than a single qTD .
*/
if ( length > usb_maxpacket ( dev , pipe ) ) {
printf ( " %s: Interrupt transfers requiring several transactions "
" are not supported. \n " , __func__ ) ;
printf ( " %s: Interrupt transfers requiring several "
" transactions are not supported.\n " , __func__ ) ;
return - 1 ;
}
return ehci_submit_async ( dev , pipe , buffer , length , NULL ) ;
queue = create_int_queue ( dev , pipe , 1 , length , buffer ) ;
timeout = get_timer ( 0 ) + USB_TIMEOUT_MS ( pipe ) ;
while ( ( backbuffer = poll_int_queue ( dev , queue ) ) = = NULL )
if ( get_timer ( 0 ) > timeout ) {
printf ( " Timeout poll on interrupt endpoint \n " ) ;
result = - ETIMEDOUT ;
break ;
}
if ( backbuffer ! = buffer ) {
debug ( " got wrong buffer back (%x instead of %x) \n " ,
( uint32_t ) backbuffer , ( uint32_t ) buffer ) ;
return - EINVAL ;
}
ret = destroy_int_queue ( dev , queue ) ;
if ( ret < 0 )
return ret ;
/* everything worked out fine */
return result ;
}