2005-04-16 15:20:36 -07:00
/*
2005-05-07 13:21:50 -07:00
* Copyright ( C ) 2001 - 2004 by David Brownell
2005-04-16 15:20:36 -07:00
*
* 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
/* this file is part of ehci-hcd.c */
/*-------------------------------------------------------------------------*/
/*
* EHCI Root Hub . . . the nonsharable stuff
*
* Registers don ' t need cpu_to_le32 , that happens transparently
*/
/*-------------------------------------------------------------------------*/
# ifdef CONFIG_PM
2005-10-13 17:08:02 -04:00
static int ehci_bus_suspend ( struct usb_hcd * hcd )
2005-04-16 15:20:36 -07:00
{
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
int port ;
if ( time_before ( jiffies , ehci - > next_statechange ) )
msleep ( 5 ) ;
port = HCS_N_PORTS ( ehci - > hcs_params ) ;
spin_lock_irq ( & ehci - > lock ) ;
/* stop schedules, clean any completed work */
if ( HC_IS_RUNNING ( hcd - > state ) ) {
ehci_quiesce ( ehci ) ;
hcd - > state = HC_STATE_QUIESCING ;
}
ehci - > command = readl ( & ehci - > regs - > command ) ;
if ( ehci - > reclaim )
ehci - > reclaim_ready = 1 ;
ehci_work ( ehci , NULL ) ;
/* suspend any active/unsuspended ports, maybe allow wakeup */
while ( port - - ) {
u32 __iomem * reg = & ehci - > regs - > port_status [ port ] ;
2005-08-31 10:55:38 -07:00
u32 t1 = readl ( reg ) & ~ PORT_RWC_BITS ;
2005-04-16 15:20:36 -07:00
u32 t2 = t1 ;
if ( ( t1 & PORT_PE ) & & ! ( t1 & PORT_OWNER ) )
t2 | = PORT_SUSPEND ;
if ( hcd - > remote_wakeup )
t2 | = PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E ;
else
t2 & = ~ ( PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E ) ;
if ( t1 ! = t2 ) {
ehci_vdbg ( ehci , " port %d, %08x -> %08x \n " ,
port + 1 , t1 , t2 ) ;
writel ( t2 , reg ) ;
}
}
/* turn off now-idle HC */
2005-05-09 17:23:51 -07:00
del_timer_sync ( & ehci - > watchdog ) ;
2005-04-16 15:20:36 -07:00
ehci_halt ( ehci ) ;
hcd - > state = HC_STATE_SUSPENDED ;
ehci - > next_statechange = jiffies + msecs_to_jiffies ( 10 ) ;
spin_unlock_irq ( & ehci - > lock ) ;
return 0 ;
}
/* caller has locked the root hub, and should reset/reinit on error */
2005-10-13 17:08:02 -04:00
static int ehci_bus_resume ( struct usb_hcd * hcd )
2005-04-16 15:20:36 -07:00
{
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
u32 temp ;
int i ;
int intr_enable ;
if ( time_before ( jiffies , ehci - > next_statechange ) )
msleep ( 5 ) ;
spin_lock_irq ( & ehci - > lock ) ;
2005-11-23 15:45:28 -08:00
/* Ideally and we've got a real resume here, and no port's power
* was lost . ( For PCI , that means Vaux was maintained . ) But we
* could instead be restoring a swsusp snapshot - - so that BIOS was
* the last user of the controller , not reset / pm hardware keeping
* state we gave to it .
*/
2005-04-16 15:20:36 -07:00
/* re-init operational registers in case we lost power */
if ( readl ( & ehci - > regs - > intr_enable ) = = 0 ) {
/* at least some APM implementations will try to deliver
* IRQs right away , so delay them until we ' re ready .
*/
intr_enable = 1 ;
writel ( 0 , & ehci - > regs - > segment ) ;
writel ( ehci - > periodic_dma , & ehci - > regs - > frame_list ) ;
writel ( ( u32 ) ehci - > async - > qh_dma , & ehci - > regs - > async_next ) ;
} else
intr_enable = 0 ;
ehci_dbg ( ehci , " resume root hub%s \n " ,
intr_enable ? " after power loss " : " " ) ;
/* restore CMD_RUN, framelist size, and irq threshold */
writel ( ehci - > command , & ehci - > regs - > command ) ;
/* take ports out of suspend */
i = HCS_N_PORTS ( ehci - > hcs_params ) ;
while ( i - - ) {
temp = readl ( & ehci - > regs - > port_status [ i ] ) ;
2005-08-31 10:55:38 -07:00
temp & = ~ ( PORT_RWC_BITS
| PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E ) ;
2005-04-16 15:20:36 -07:00
if ( temp & PORT_SUSPEND ) {
ehci - > reset_done [ i ] = jiffies + msecs_to_jiffies ( 20 ) ;
temp | = PORT_RESUME ;
}
writel ( temp , & ehci - > regs - > port_status [ i ] ) ;
}
i = HCS_N_PORTS ( ehci - > hcs_params ) ;
mdelay ( 20 ) ;
while ( i - - ) {
temp = readl ( & ehci - > regs - > port_status [ i ] ) ;
if ( ( temp & PORT_SUSPEND ) = = 0 )
continue ;
2005-08-31 10:55:38 -07:00
temp & = ~ ( PORT_RWC_BITS | PORT_RESUME ) ;
2005-04-16 15:20:36 -07:00
writel ( temp , & ehci - > regs - > port_status [ i ] ) ;
ehci_vdbg ( ehci , " resumed port %d \n " , i + 1 ) ;
}
( void ) readl ( & ehci - > regs - > command ) ;
/* maybe re-activate the schedule(s) */
temp = 0 ;
if ( ehci - > async - > qh_next . qh )
temp | = CMD_ASE ;
if ( ehci - > periodic_sched )
temp | = CMD_PSE ;
if ( temp ) {
ehci - > command | = temp ;
writel ( ehci - > command , & ehci - > regs - > command ) ;
}
ehci - > next_statechange = jiffies + msecs_to_jiffies ( 5 ) ;
hcd - > state = HC_STATE_RUNNING ;
/* Now we can safely re-enable irqs */
if ( intr_enable )
writel ( INTR_MASK , & ehci - > regs - > intr_enable ) ;
spin_unlock_irq ( & ehci - > lock ) ;
return 0 ;
}
# else
2005-10-13 17:08:02 -04:00
# define ehci_bus_suspend NULL
# define ehci_bus_resume NULL
2005-04-16 15:20:36 -07:00
# endif /* CONFIG_PM */
/*-------------------------------------------------------------------------*/
static int check_reset_complete (
struct ehci_hcd * ehci ,
int index ,
int port_status
) {
if ( ! ( port_status & PORT_CONNECT ) ) {
ehci - > reset_done [ index ] = 0 ;
return port_status ;
}
/* if reset finished and it's still not enabled -- handoff */
if ( ! ( port_status & PORT_PE ) ) {
/* with integrated TT, there's nobody to hand it to! */
if ( ehci_is_TDI ( ehci ) ) {
ehci_dbg ( ehci ,
" Failed to enable port %d on root hub TT \n " ,
index + 1 ) ;
return port_status ;
}
ehci_dbg ( ehci , " port %d full speed --> companion \n " ,
index + 1 ) ;
// what happens if HCS_N_CC(params) == 0 ?
port_status | = PORT_OWNER ;
2005-08-31 10:55:38 -07:00
port_status & = ~ PORT_RWC_BITS ;
2005-04-16 15:20:36 -07:00
writel ( port_status , & ehci - > regs - > port_status [ index ] ) ;
} else
ehci_dbg ( ehci , " port %d high speed \n " , index + 1 ) ;
return port_status ;
}
/*-------------------------------------------------------------------------*/
/* build "status change" packet (one or two bytes) from HC registers */
static int
ehci_hub_status_data ( struct usb_hcd * hcd , char * buf )
{
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
u32 temp , status = 0 ;
int ports , i , retval = 1 ;
unsigned long flags ;
/* if !USB_SUSPEND, root hub timers won't get shut down ... */
if ( ! HC_IS_RUNNING ( hcd - > state ) )
return 0 ;
/* init status to no-changes */
buf [ 0 ] = 0 ;
ports = HCS_N_PORTS ( ehci - > hcs_params ) ;
if ( ports > 7 ) {
buf [ 1 ] = 0 ;
retval + + ;
}
/* no hub change reports (bit 0) for now (power, ...) */
/* port N changes (bit N)? */
spin_lock_irqsave ( & ehci - > lock , flags ) ;
for ( i = 0 ; i < ports ; i + + ) {
temp = readl ( & ehci - > regs - > port_status [ i ] ) ;
if ( temp & PORT_OWNER ) {
/* don't report this in GetPortStatus */
if ( temp & PORT_CSC ) {
2005-08-31 10:55:38 -07:00
temp & = ~ PORT_RWC_BITS ;
temp | = PORT_CSC ;
2005-04-16 15:20:36 -07:00
writel ( temp , & ehci - > regs - > port_status [ i ] ) ;
}
continue ;
}
if ( ! ( temp & PORT_CONNECT ) )
ehci - > reset_done [ i ] = 0 ;
if ( ( temp & ( PORT_CSC | PORT_PEC | PORT_OCC ) ) ! = 0
// PORT_STAT_C_SUSPEND?
| | ( ( temp & PORT_RESUME ) ! = 0
& & time_after ( jiffies ,
ehci - > reset_done [ i ] ) ) ) {
if ( i < 7 )
buf [ 0 ] | = 1 < < ( i + 1 ) ;
else
buf [ 1 ] | = 1 < < ( i - 7 ) ;
status = STS_PCD ;
}
}
/* FIXME autosuspend idle root hubs */
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
return status ? retval : 0 ;
}
/*-------------------------------------------------------------------------*/
static void
ehci_hub_descriptor (
struct ehci_hcd * ehci ,
struct usb_hub_descriptor * desc
) {
int ports = HCS_N_PORTS ( ehci - > hcs_params ) ;
u16 temp ;
desc - > bDescriptorType = 0x29 ;
desc - > bPwrOn2PwrGood = 10 ; /* ehci 1.0, 2.3.9 says 20ms max */
desc - > bHubContrCurrent = 0 ;
desc - > bNbrPorts = ports ;
temp = 1 + ( ports / 8 ) ;
desc - > bDescLength = 7 + 2 * temp ;
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
memset ( & desc - > bitmap [ 0 ] , 0 , temp ) ;
memset ( & desc - > bitmap [ temp ] , 0xff , temp ) ;
temp = 0x0008 ; /* per-port overcurrent reporting */
if ( HCS_PPC ( ehci - > hcs_params ) )
temp | = 0x0001 ; /* per-port power control */
2005-04-09 09:00:29 -07:00
else
temp | = 0x0002 ; /* no power switching */
2005-04-16 15:20:36 -07:00
#if 0
// re-enable when we support USB_PORT_FEAT_INDICATOR below.
if ( HCS_INDICATOR ( ehci - > hcs_params ) )
temp | = 0x0080 ; /* per-port indicators (LEDs) */
# endif
desc - > wHubCharacteristics = ( __force __u16 ) cpu_to_le16 ( temp ) ;
}
/*-------------------------------------------------------------------------*/
# define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
static int ehci_hub_control (
struct usb_hcd * hcd ,
u16 typeReq ,
u16 wValue ,
u16 wIndex ,
char * buf ,
u16 wLength
) {
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
int ports = HCS_N_PORTS ( ehci - > hcs_params ) ;
u32 temp , status ;
unsigned long flags ;
int retval = 0 ;
/*
* FIXME : support SetPortFeatures USB_PORT_FEAT_INDICATOR .
* HCS_INDICATOR may say we can change LEDs to off / amber / green .
* ( track current state ourselves ) . . . blink for diagnostics ,
* power , " this is the one " , etc . EHCI spec supports this .
*/
spin_lock_irqsave ( & ehci - > lock , flags ) ;
switch ( typeReq ) {
case ClearHubFeature :
switch ( wValue ) {
case C_HUB_LOCAL_POWER :
case C_HUB_OVER_CURRENT :
/* no hub-wide feature/status flags */
break ;
default :
goto error ;
}
break ;
case ClearPortFeature :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
temp = readl ( & ehci - > regs - > port_status [ wIndex ] ) ;
if ( temp & PORT_OWNER )
break ;
switch ( wValue ) {
case USB_PORT_FEAT_ENABLE :
writel ( temp & ~ PORT_PE ,
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_C_ENABLE :
2005-08-31 10:55:38 -07:00
writel ( ( temp & ~ PORT_RWC_BITS ) | PORT_PEC ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_SUSPEND :
if ( temp & PORT_RESET )
goto error ;
if ( temp & PORT_SUSPEND ) {
if ( ( temp & PORT_PE ) = = 0 )
goto error ;
/* resume signaling for 20 msec */
2005-08-31 10:55:38 -07:00
temp & = ~ ( PORT_RWC_BITS | PORT_WAKE_BITS ) ;
writel ( temp | PORT_RESUME ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
ehci - > reset_done [ wIndex ] = jiffies
+ msecs_to_jiffies ( 20 ) ;
}
break ;
case USB_PORT_FEAT_C_SUSPEND :
/* we auto-clear this feature */
break ;
case USB_PORT_FEAT_POWER :
if ( HCS_PPC ( ehci - > hcs_params ) )
2005-08-31 10:55:38 -07:00
writel ( temp & ~ ( PORT_RWC_BITS | PORT_POWER ) ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_C_CONNECTION :
2005-08-31 10:55:38 -07:00
writel ( ( temp & ~ PORT_RWC_BITS ) | PORT_CSC ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_C_OVER_CURRENT :
2005-08-31 10:55:38 -07:00
writel ( ( temp & ~ PORT_RWC_BITS ) | PORT_OCC ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_C_RESET :
/* GetPortStatus clears reset */
break ;
default :
goto error ;
}
readl ( & ehci - > regs - > command ) ; /* unblock posted write */
break ;
case GetHubDescriptor :
ehci_hub_descriptor ( ehci , ( struct usb_hub_descriptor * )
buf ) ;
break ;
case GetHubStatus :
/* no hub-wide feature/status flags */
memset ( buf , 0 , 4 ) ;
//cpu_to_le32s ((u32 *) buf);
break ;
case GetPortStatus :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
status = 0 ;
temp = readl ( & ehci - > regs - > port_status [ wIndex ] ) ;
// wPortChange bits
if ( temp & PORT_CSC )
status | = 1 < < USB_PORT_FEAT_C_CONNECTION ;
if ( temp & PORT_PEC )
status | = 1 < < USB_PORT_FEAT_C_ENABLE ;
if ( temp & PORT_OCC )
status | = 1 < < USB_PORT_FEAT_C_OVER_CURRENT ;
/* whoever resumes must GetPortStatus to complete it!! */
if ( ( temp & PORT_RESUME )
& & time_after ( jiffies ,
ehci - > reset_done [ wIndex ] ) ) {
status | = 1 < < USB_PORT_FEAT_C_SUSPEND ;
ehci - > reset_done [ wIndex ] = 0 ;
/* stop resume signaling */
temp = readl ( & ehci - > regs - > port_status [ wIndex ] ) ;
2005-08-31 10:55:38 -07:00
writel ( temp & ~ ( PORT_RWC_BITS | PORT_RESUME ) ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
retval = handshake (
& ehci - > regs - > port_status [ wIndex ] ,
PORT_RESUME , 0 , 2000 /* 2msec */ ) ;
if ( retval ! = 0 ) {
ehci_err ( ehci , " port %d resume error %d \n " ,
wIndex + 1 , retval ) ;
goto error ;
}
temp & = ~ ( PORT_SUSPEND | PORT_RESUME | ( 3 < < 10 ) ) ;
}
/* whoever resets must GetPortStatus to complete it!! */
if ( ( temp & PORT_RESET )
& & time_after ( jiffies ,
ehci - > reset_done [ wIndex ] ) ) {
status | = 1 < < USB_PORT_FEAT_C_RESET ;
ehci - > reset_done [ wIndex ] = 0 ;
/* force reset to complete */
2005-08-31 10:55:38 -07:00
writel ( temp & ~ ( PORT_RWC_BITS | PORT_RESET ) ,
2005-04-16 15:20:36 -07:00
& ehci - > regs - > port_status [ wIndex ] ) ;
2005-06-13 07:15:28 -07:00
/* REVISIT: some hardware needs 550+ usec to clear
* this bit ; seems too long to spin routinely . . .
*/
2005-04-16 15:20:36 -07:00
retval = handshake (
& ehci - > regs - > port_status [ wIndex ] ,
2005-06-13 07:15:28 -07:00
PORT_RESET , 0 , 750 ) ;
2005-04-16 15:20:36 -07:00
if ( retval ! = 0 ) {
ehci_err ( ehci , " port %d reset error %d \n " ,
wIndex + 1 , retval ) ;
goto error ;
}
/* see what we found out */
temp = check_reset_complete ( ehci , wIndex ,
readl ( & ehci - > regs - > port_status [ wIndex ] ) ) ;
}
// don't show wPortStatus if it's owned by a companion hc
if ( ! ( temp & PORT_OWNER ) ) {
if ( temp & PORT_CONNECT ) {
status | = 1 < < USB_PORT_FEAT_CONNECTION ;
// status may be from integrated TT
status | = ehci_port_speed ( ehci , temp ) ;
}
if ( temp & PORT_PE )
status | = 1 < < USB_PORT_FEAT_ENABLE ;
if ( temp & ( PORT_SUSPEND | PORT_RESUME ) )
status | = 1 < < USB_PORT_FEAT_SUSPEND ;
if ( temp & PORT_OC )
status | = 1 < < USB_PORT_FEAT_OVER_CURRENT ;
if ( temp & PORT_RESET )
status | = 1 < < USB_PORT_FEAT_RESET ;
if ( temp & PORT_POWER )
status | = 1 < < USB_PORT_FEAT_POWER ;
}
# ifndef EHCI_VERBOSE_DEBUG
if ( status & ~ 0xffff ) /* only if wPortChange is interesting */
# endif
dbg_port ( ehci , " GetStatus " , wIndex + 1 , temp ) ;
// we "know" this alignment is good, caller used kmalloc()...
* ( ( __le32 * ) buf ) = cpu_to_le32 ( status ) ;
break ;
case SetHubFeature :
switch ( wValue ) {
case C_HUB_LOCAL_POWER :
case C_HUB_OVER_CURRENT :
/* no hub-wide feature/status flags */
break ;
default :
goto error ;
}
break ;
case SetPortFeature :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
temp = readl ( & ehci - > regs - > port_status [ wIndex ] ) ;
if ( temp & PORT_OWNER )
break ;
2005-08-31 10:55:38 -07:00
temp & = ~ PORT_RWC_BITS ;
2005-04-16 15:20:36 -07:00
switch ( wValue ) {
case USB_PORT_FEAT_SUSPEND :
if ( ( temp & PORT_PE ) = = 0
| | ( temp & PORT_RESET ) ! = 0 )
goto error ;
if ( hcd - > remote_wakeup )
temp | = PORT_WAKE_BITS ;
writel ( temp | PORT_SUSPEND ,
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_POWER :
if ( HCS_PPC ( ehci - > hcs_params ) )
writel ( temp | PORT_POWER ,
& ehci - > regs - > port_status [ wIndex ] ) ;
break ;
case USB_PORT_FEAT_RESET :
if ( temp & PORT_RESUME )
goto error ;
/* line status bits may report this as low speed,
* which can be fine if this root hub has a
* transaction translator built in .
*/
if ( ( temp & ( PORT_PE | PORT_CONNECT ) ) = = PORT_CONNECT
& & ! ehci_is_TDI ( ehci )
& & PORT_USB11 ( temp ) ) {
ehci_dbg ( ehci ,
" port %d low speed --> companion \n " ,
wIndex + 1 ) ;
temp | = PORT_OWNER ;
} else {
ehci_vdbg ( ehci , " port %d reset \n " , wIndex + 1 ) ;
temp | = PORT_RESET ;
temp & = ~ PORT_PE ;
/*
* caller must wait , then call GetPortStatus
* usb 2.0 spec says 50 ms resets on root
*/
ehci - > reset_done [ wIndex ] = jiffies
+ msecs_to_jiffies ( 50 ) ;
}
writel ( temp , & ehci - > regs - > port_status [ wIndex ] ) ;
break ;
default :
goto error ;
}
readl ( & ehci - > regs - > command ) ; /* unblock posted writes */
break ;
default :
error :
/* "stall" on error */
retval = - EPIPE ;
}
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
return retval ;
}