2009-04-27 19:57:12 -07:00
/*
* xHCI host controller driver
*
* Copyright ( C ) 2008 Intel Corp .
*
* Author : Sarah Sharp
* Some code borrowed from the Linux EHCI driver .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* 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 .
*/
# include <asm/unaligned.h>
# include "xhci.h"
2010-10-14 07:23:03 -07:00
# define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E)
# define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \
PORT_RC | PORT_PLC | PORT_PE )
2009-04-27 19:57:12 -07:00
static void xhci_hub_descriptor ( struct xhci_hcd * xhci ,
struct usb_hub_descriptor * desc )
{
int ports ;
u16 temp ;
ports = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
/* USB 3.0 hubs have a different descriptor, but we fake this for now */
desc - > bDescriptorType = 0x29 ;
desc - > bPwrOn2PwrGood = 10 ; /* xhci section 5.4.9 says 20ms max */
desc - > bHubContrCurrent = 0 ;
desc - > bNbrPorts = ports ;
temp = 1 + ( ports / 8 ) ;
desc - > bDescLength = 7 + 2 * temp ;
/* Why does core/hcd.h define bitmap? It's just confusing. */
memset ( & desc - > DeviceRemovable [ 0 ] , 0 , temp ) ;
memset ( & desc - > DeviceRemovable [ temp ] , 0xff , temp ) ;
/* Ugh, these should be #defines, FIXME */
/* Using table 11-13 in USB 2.0 spec. */
temp = 0 ;
/* Bits 1:0 - support port power switching, or power always on */
if ( HCC_PPC ( xhci - > hcc_params ) )
temp | = 0x0001 ;
else
temp | = 0x0002 ;
/* Bit 2 - root hubs are not part of a compound device */
/* Bits 4:3 - individual port over current protection */
temp | = 0x0008 ;
/* Bits 6:5 - no TTs in root ports */
/* Bit 7 - no port indicators */
desc - > wHubCharacteristics = ( __force __u16 ) cpu_to_le16 ( temp ) ;
}
static unsigned int xhci_port_speed ( unsigned int port_status )
{
if ( DEV_LOWSPEED ( port_status ) )
2010-03-04 11:32:30 -05:00
return USB_PORT_STAT_LOW_SPEED ;
2009-04-27 19:57:12 -07:00
if ( DEV_HIGHSPEED ( port_status ) )
2010-03-04 11:32:30 -05:00
return USB_PORT_STAT_HIGH_SPEED ;
2009-04-27 19:57:12 -07:00
if ( DEV_SUPERSPEED ( port_status ) )
2010-03-04 11:32:30 -05:00
return USB_PORT_STAT_SUPER_SPEED ;
2009-04-27 19:57:12 -07:00
/*
* FIXME : Yes , we should check for full speed , but the core uses that as
* a default in portspeed ( ) in usb / core / hub . c ( which is the only place
2010-03-04 11:32:30 -05:00
* USB_PORT_STAT_ * _SPEED is used ) .
2009-04-27 19:57:12 -07:00
*/
return 0 ;
}
/*
* These bits are Read Only ( RO ) and should be saved and written to the
* registers : 0 , 3 , 10 : 13 , 30
* connect status , over - current status , port speed , and device removable .
* connect status and port speed are also sticky - meaning they ' re in
* the AUX well and they aren ' t changed by a hot , warm , or cold reset .
*/
# define XHCI_PORT_RO ((1<<0) | (1<<3) | (0xf<<10) | (1<<30))
/*
* These bits are RW ; writing a 0 clears the bit , writing a 1 sets the bit :
* bits 5 : 8 , 9 , 14 : 15 , 25 : 27
* link state , port power , port indicator state , " wake on " enable state
*/
# define XHCI_PORT_RWS ((0xf<<5) | (1<<9) | (0x3<<14) | (0x7<<25))
/*
* These bits are RW ; writing a 1 sets the bit , writing a 0 has no effect :
* bit 4 ( port reset )
*/
# define XHCI_PORT_RW1S ((1<<4))
/*
* These bits are RW ; writing a 1 clears the bit , writing a 0 has no effect :
* bits 1 , 17 , 18 , 19 , 20 , 21 , 22 , 23
* port enable / disable , and
* change bits : connect , PED , warm port reset changed ( reserved zero for USB 2.0 ports ) ,
* over - current , reset , link state , and L1 change
*/
# define XHCI_PORT_RW1CS ((1<<1) | (0x7f<<17))
/*
* Bit 16 is RW , and writing a ' 1 ' to it causes the link state control to be
* latched in
*/
# define XHCI_PORT_RW ((1<<16))
/*
* These bits are Reserved Zero ( RsvdZ ) and zero should be written to them :
* bits 2 , 24 , 28 : 31
*/
# define XHCI_PORT_RZ ((1<<2) | (1<<24) | (0xf<<28))
/*
* Given a port state , this function returns a value that would result in the
* port being in the same state , if the value was written to the port status
* control register .
* Save Read Only ( RO ) bits and save read / write bits where
* writing a 0 clears the bit and writing a 1 sets the bit ( RWS ) .
* For all other types ( RW1S , RW1CS , RW , and RZ ) , writing a ' 0 ' has no effect .
*/
2010-10-14 07:23:00 -07:00
u32 xhci_port_state_to_neutral ( u32 state )
2009-04-27 19:57:12 -07:00
{
/* Save read-only status and port state */
return ( state & XHCI_PORT_RO ) | ( state & XHCI_PORT_RWS ) ;
}
2010-10-14 07:22:57 -07:00
/*
* find slot id based on port number .
*/
2010-10-14 07:23:00 -07:00
int xhci_find_slot_id_by_port ( struct xhci_hcd * xhci , u16 port )
2010-10-14 07:22:57 -07:00
{
int slot_id ;
int i ;
slot_id = 0 ;
for ( i = 0 ; i < MAX_HC_SLOTS ; i + + ) {
if ( ! xhci - > devs [ i ] )
continue ;
if ( xhci - > devs [ i ] - > port = = port ) {
slot_id = i ;
break ;
}
}
return slot_id ;
}
/*
* Stop device
* It issues stop endpoint command for EP 0 to 30. And wait the last command
* to complete .
* suspend will set to 1 , if suspend bit need to set in command .
*/
static int xhci_stop_device ( struct xhci_hcd * xhci , int slot_id , int suspend )
{
struct xhci_virt_device * virt_dev ;
struct xhci_command * cmd ;
unsigned long flags ;
int timeleft ;
int ret ;
int i ;
ret = 0 ;
virt_dev = xhci - > devs [ slot_id ] ;
cmd = xhci_alloc_command ( xhci , false , true , GFP_NOIO ) ;
if ( ! cmd ) {
xhci_dbg ( xhci , " Couldn't allocate command structure. \n " ) ;
return - ENOMEM ;
}
spin_lock_irqsave ( & xhci - > lock , flags ) ;
for ( i = LAST_EP_INDEX ; i > 0 ; i - - ) {
if ( virt_dev - > eps [ i ] . ring & & virt_dev - > eps [ i ] . ring - > dequeue )
xhci_queue_stop_endpoint ( xhci , slot_id , i , suspend ) ;
}
cmd - > command_trb = xhci - > cmd_ring - > enqueue ;
list_add_tail ( & cmd - > cmd_list , & virt_dev - > cmd_list ) ;
xhci_queue_stop_endpoint ( xhci , slot_id , 0 , suspend ) ;
xhci_ring_cmd_db ( xhci ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
/* Wait for last stop endpoint command to finish */
timeleft = wait_for_completion_interruptible_timeout (
cmd - > completion ,
USB_CTRL_SET_TIMEOUT ) ;
if ( timeleft < = 0 ) {
xhci_warn ( xhci , " %s while waiting for stop endpoint command \n " ,
timeleft = = 0 ? " Timeout " : " Signal " ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
/* The timeout might have raced with the event ring handler, so
* only delete from the list if the item isn ' t poisoned .
*/
if ( cmd - > cmd_list . next ! = LIST_POISON1 )
list_del ( & cmd - > cmd_list ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
ret = - ETIME ;
goto command_cleanup ;
}
command_cleanup :
xhci_free_command ( xhci , cmd ) ;
return ret ;
}
/*
* Ring device , it rings the all doorbells unconditionally .
*/
2010-10-14 07:23:00 -07:00
void xhci_ring_device ( struct xhci_hcd * xhci , int slot_id )
2010-10-14 07:22:57 -07:00
{
int i ;
for ( i = 0 ; i < LAST_EP_INDEX + 1 ; i + + )
if ( xhci - > devs [ slot_id ] - > eps [ i ] . ring & &
xhci - > devs [ slot_id ] - > eps [ i ] . ring - > dequeue )
xhci_ring_ep_doorbell ( xhci , slot_id , i , 0 ) ;
return ;
}
2009-12-09 15:59:11 -08:00
static void xhci_disable_port ( struct xhci_hcd * xhci , u16 wIndex ,
u32 __iomem * addr , u32 port_status )
{
/* Write 1 to disable the port */
xhci_writel ( xhci , port_status | PORT_PE , addr ) ;
port_status = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " disable port, actual port %d status = 0x%x \n " ,
wIndex , port_status ) ;
}
2009-12-09 15:59:08 -08:00
static void xhci_clear_port_change_bit ( struct xhci_hcd * xhci , u16 wValue ,
u16 wIndex , u32 __iomem * addr , u32 port_status )
{
char * port_change_bit ;
u32 status ;
switch ( wValue ) {
case USB_PORT_FEAT_C_RESET :
status = PORT_RC ;
port_change_bit = " reset " ;
break ;
case USB_PORT_FEAT_C_CONNECTION :
status = PORT_CSC ;
port_change_bit = " connect " ;
break ;
case USB_PORT_FEAT_C_OVER_CURRENT :
status = PORT_OCC ;
port_change_bit = " over-current " ;
break ;
2009-12-09 15:59:11 -08:00
case USB_PORT_FEAT_C_ENABLE :
status = PORT_PEC ;
port_change_bit = " enable/disable " ;
break ;
2010-10-14 07:22:57 -07:00
case USB_PORT_FEAT_C_SUSPEND :
status = PORT_PLC ;
port_change_bit = " suspend/resume " ;
break ;
2009-12-09 15:59:08 -08:00
default :
/* Should never happen */
return ;
}
/* Change bits are all write 1 to clear */
xhci_writel ( xhci , port_status | status , addr ) ;
port_status = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " clear port %s change, actual port %d status = 0x%x \n " ,
port_change_bit , wIndex , port_status ) ;
}
2009-04-27 19:57:12 -07:00
int xhci_hub_control ( struct usb_hcd * hcd , u16 typeReq , u16 wValue ,
u16 wIndex , char * buf , u16 wLength )
{
struct xhci_hcd * xhci = hcd_to_xhci ( hcd ) ;
int ports ;
unsigned long flags ;
2010-10-14 07:23:00 -07:00
u32 temp , temp1 , status ;
2009-04-27 19:57:12 -07:00
int retval = 0 ;
u32 __iomem * addr ;
2010-10-14 07:22:57 -07:00
int slot_id ;
2009-04-27 19:57:12 -07:00
ports = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
switch ( typeReq ) {
case GetHubStatus :
/* No power source, over-current reported per port */
memset ( buf , 0 , 4 ) ;
break ;
case GetHubDescriptor :
xhci_hub_descriptor ( xhci , ( struct usb_hub_descriptor * ) buf ) ;
break ;
case GetPortStatus :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
status = 0 ;
addr = & xhci - > op_regs - > port_status_base + NUM_PORT_REGS * ( wIndex & 0xff ) ;
temp = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " get port status, actual port %d status = 0x%x \n " , wIndex , temp ) ;
/* wPortChange bits */
if ( temp & PORT_CSC )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_C_CONNECTION < < 16 ;
2009-04-27 19:57:12 -07:00
if ( temp & PORT_PEC )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_C_ENABLE < < 16 ;
2009-04-27 19:57:12 -07:00
if ( ( temp & PORT_OCC ) )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_C_OVERCURRENT < < 16 ;
2009-04-27 19:57:12 -07:00
/*
2010-10-14 07:22:57 -07:00
* FIXME ignoring reset and USB 2.1 / 3.0 specific
2009-04-27 19:57:12 -07:00
* changes
*/
2010-10-14 07:22:57 -07:00
if ( ( temp & PORT_PLS_MASK ) = = XDEV_U3
& & ( temp & PORT_POWER ) )
status | = 1 < < USB_PORT_FEAT_SUSPEND ;
2010-10-14 07:23:00 -07:00
if ( ( temp & PORT_PLS_MASK ) = = XDEV_RESUME ) {
if ( ( temp & PORT_RESET ) | | ! ( temp & PORT_PE ) )
goto error ;
if ( ! DEV_SUPERSPEED ( temp ) & & time_after_eq ( jiffies ,
xhci - > resume_done [ wIndex ] ) ) {
xhci_dbg ( xhci , " Resume USB2 port %d \n " ,
wIndex + 1 ) ;
xhci - > resume_done [ wIndex ] = 0 ;
temp1 = xhci_port_state_to_neutral ( temp ) ;
temp1 & = ~ PORT_PLS_MASK ;
temp1 | = PORT_LINK_STROBE | XDEV_U0 ;
xhci_writel ( xhci , temp1 , addr ) ;
xhci_dbg ( xhci , " set port %d resume \n " ,
wIndex + 1 ) ;
slot_id = xhci_find_slot_id_by_port ( xhci ,
wIndex + 1 ) ;
if ( ! slot_id ) {
xhci_dbg ( xhci , " slot_id is zero \n " ) ;
goto error ;
}
xhci_ring_device ( xhci , slot_id ) ;
xhci - > port_c_suspend [ wIndex > > 5 ] | =
1 < < ( wIndex & 31 ) ;
xhci - > suspended_ports [ wIndex > > 5 ] & =
~ ( 1 < < ( wIndex & 31 ) ) ;
}
}
2010-10-14 07:22:57 -07:00
if ( ( temp & PORT_PLS_MASK ) = = XDEV_U0
& & ( temp & PORT_POWER )
& & ( xhci - > suspended_ports [ wIndex > > 5 ] &
( 1 < < ( wIndex & 31 ) ) ) ) {
xhci - > suspended_ports [ wIndex > > 5 ] & =
~ ( 1 < < ( wIndex & 31 ) ) ;
xhci - > port_c_suspend [ wIndex > > 5 ] | =
1 < < ( wIndex & 31 ) ;
}
2009-04-27 19:57:12 -07:00
if ( temp & PORT_CONNECT ) {
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_CONNECTION ;
2009-04-27 19:57:12 -07:00
status | = xhci_port_speed ( temp ) ;
}
if ( temp & PORT_PE )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_ENABLE ;
2009-04-27 19:57:12 -07:00
if ( temp & PORT_OC )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_OVERCURRENT ;
2009-04-27 19:57:12 -07:00
if ( temp & PORT_RESET )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_RESET ;
2009-04-27 19:57:12 -07:00
if ( temp & PORT_POWER )
2010-03-04 17:05:08 -05:00
status | = USB_PORT_STAT_POWER ;
2010-10-14 07:22:57 -07:00
if ( xhci - > port_c_suspend [ wIndex > > 5 ] & ( 1 < < ( wIndex & 31 ) ) )
status | = 1 < < USB_PORT_FEAT_C_SUSPEND ;
2009-04-27 19:57:12 -07:00
xhci_dbg ( xhci , " Get port status returned 0x%x \n " , status ) ;
put_unaligned ( cpu_to_le32 ( status ) , ( __le32 * ) buf ) ;
break ;
case SetPortFeature :
wIndex & = 0xff ;
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
addr = & xhci - > op_regs - > port_status_base + NUM_PORT_REGS * ( wIndex & 0xff ) ;
temp = xhci_readl ( xhci , addr ) ;
temp = xhci_port_state_to_neutral ( temp ) ;
switch ( wValue ) {
2010-10-14 07:22:57 -07:00
case USB_PORT_FEAT_SUSPEND :
temp = xhci_readl ( xhci , addr ) ;
/* In spec software should not attempt to suspend
* a port unless the port reports that it is in the
* enabled ( PED = ‘ 1 ’ , PLS < ‘ 3 ’ ) state .
*/
if ( ( temp & PORT_PE ) = = 0 | | ( temp & PORT_RESET )
| | ( temp & PORT_PLS_MASK ) > = XDEV_U3 ) {
xhci_warn ( xhci , " USB core suspending device "
" not in U0/U1/U2. \n " ) ;
goto error ;
}
slot_id = xhci_find_slot_id_by_port ( xhci , wIndex + 1 ) ;
if ( ! slot_id ) {
xhci_warn ( xhci , " slot_id is zero \n " ) ;
goto error ;
}
/* unlock to execute stop endpoint commands */
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
xhci_stop_device ( xhci , slot_id , 1 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_U3 ;
xhci_writel ( xhci , temp , addr ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
msleep ( 10 ) ; /* wait device to enter */
spin_lock_irqsave ( & xhci - > lock , flags ) ;
temp = xhci_readl ( xhci , addr ) ;
xhci - > suspended_ports [ wIndex > > 5 ] | =
1 < < ( wIndex & ( 31 ) ) ;
break ;
2009-04-27 19:57:12 -07:00
case USB_PORT_FEAT_POWER :
/*
* Turn on ports , even if there isn ' t per - port switching .
* HC will report connect events even before this is set .
* However , khubd will ignore the roothub events until
* the roothub is registered .
*/
xhci_writel ( xhci , temp | PORT_POWER , addr ) ;
temp = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " set port power, actual port %d status = 0x%x \n " , wIndex , temp ) ;
break ;
case USB_PORT_FEAT_RESET :
temp = ( temp | PORT_RESET ) ;
xhci_writel ( xhci , temp , addr ) ;
temp = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " set port reset, actual port %d status = 0x%x \n " , wIndex , temp ) ;
break ;
default :
goto error ;
}
temp = xhci_readl ( xhci , addr ) ; /* unblock any posted writes */
break ;
case ClearPortFeature :
if ( ! wIndex | | wIndex > ports )
goto error ;
wIndex - - ;
addr = & xhci - > op_regs - > port_status_base +
NUM_PORT_REGS * ( wIndex & 0xff ) ;
temp = xhci_readl ( xhci , addr ) ;
temp = xhci_port_state_to_neutral ( temp ) ;
switch ( wValue ) {
2010-10-14 07:22:57 -07:00
case USB_PORT_FEAT_SUSPEND :
temp = xhci_readl ( xhci , addr ) ;
xhci_dbg ( xhci , " clear USB_PORT_FEAT_SUSPEND \n " ) ;
xhci_dbg ( xhci , " PORTSC %04x \n " , temp ) ;
if ( temp & PORT_RESET )
goto error ;
if ( temp & XDEV_U3 ) {
if ( ( temp & PORT_PE ) = = 0 )
goto error ;
if ( DEV_SUPERSPEED ( temp ) ) {
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_U0 ;
xhci_writel ( xhci , temp , addr ) ;
xhci_readl ( xhci , addr ) ;
} else {
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_RESUME ;
xhci_writel ( xhci , temp , addr ) ;
spin_unlock_irqrestore ( & xhci - > lock ,
flags ) ;
msleep ( 20 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
temp = xhci_readl ( xhci , addr ) ;
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_U0 ;
xhci_writel ( xhci , temp , addr ) ;
}
xhci - > port_c_suspend [ wIndex > > 5 ] | =
1 < < ( wIndex & 31 ) ;
}
slot_id = xhci_find_slot_id_by_port ( xhci , wIndex + 1 ) ;
if ( ! slot_id ) {
xhci_dbg ( xhci , " slot_id is zero \n " ) ;
goto error ;
}
xhci_ring_device ( xhci , slot_id ) ;
break ;
case USB_PORT_FEAT_C_SUSPEND :
xhci - > port_c_suspend [ wIndex > > 5 ] & =
~ ( 1 < < ( wIndex & 31 ) ) ;
2009-04-27 19:57:12 -07:00
case USB_PORT_FEAT_C_RESET :
case USB_PORT_FEAT_C_CONNECTION :
case USB_PORT_FEAT_C_OVER_CURRENT :
2009-12-09 15:59:11 -08:00
case USB_PORT_FEAT_C_ENABLE :
2009-12-09 15:59:08 -08:00
xhci_clear_port_change_bit ( xhci , wValue , wIndex ,
addr , temp ) ;
2009-04-27 19:57:12 -07:00
break ;
2009-12-09 15:59:11 -08:00
case USB_PORT_FEAT_ENABLE :
xhci_disable_port ( xhci , wIndex , addr , temp ) ;
break ;
2009-04-27 19:57:12 -07:00
default :
goto error ;
}
break ;
default :
error :
/* "stall" on error */
retval = - EPIPE ;
}
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
return retval ;
}
/*
* Returns 0 if the status hasn ' t changed , or the number of bytes in buf .
* Ports are 0 - indexed from the HCD point of view ,
* and 1 - indexed from the USB core pointer of view .
*
* Note that the status change bits will be cleared as soon as a port status
* change event is generated , so we use the saved status from that event .
*/
int xhci_hub_status_data ( struct usb_hcd * hcd , char * buf )
{
unsigned long flags ;
u32 temp , status ;
2010-10-14 07:23:00 -07:00
u32 mask ;
2009-04-27 19:57:12 -07:00
int i , retval ;
struct xhci_hcd * xhci = hcd_to_xhci ( hcd ) ;
int ports ;
u32 __iomem * addr ;
ports = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
/* Initial status is no changes */
2010-05-12 10:20:34 -07:00
retval = ( ports + 8 ) / 8 ;
memset ( buf , 0 , retval ) ;
2009-04-27 19:57:12 -07:00
status = 0 ;
2010-10-14 07:23:00 -07:00
mask = PORT_CSC | PORT_PEC | PORT_OCC ;
2009-04-27 19:57:12 -07:00
spin_lock_irqsave ( & xhci - > lock , flags ) ;
/* For each port, did anything change? If so, set that bit in buf. */
for ( i = 0 ; i < ports ; i + + ) {
addr = & xhci - > op_regs - > port_status_base +
NUM_PORT_REGS * i ;
temp = xhci_readl ( xhci , addr ) ;
2010-10-14 07:23:00 -07:00
if ( ( temp & mask ) ! = 0 | |
( xhci - > port_c_suspend [ i > > 5 ] & 1 < < ( i & 31 ) ) | |
( xhci - > resume_done [ i ] & & time_after_eq (
jiffies , xhci - > resume_done [ i ] ) ) ) {
2010-05-12 10:20:34 -07:00
buf [ ( i + 1 ) / 8 ] | = 1 < < ( i + 1 ) % 8 ;
2009-04-27 19:57:12 -07:00
status = 1 ;
}
}
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
return status ? retval : 0 ;
}
2010-10-14 07:23:03 -07:00
# ifdef CONFIG_PM
int xhci_bus_suspend ( struct usb_hcd * hcd )
{
struct xhci_hcd * xhci = hcd_to_xhci ( hcd ) ;
int port ;
unsigned long flags ;
xhci_dbg ( xhci , " suspend root hub \n " ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
if ( hcd - > self . root_hub - > do_remote_wakeup ) {
port = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
while ( port - - ) {
if ( xhci - > resume_done [ port ] ! = 0 ) {
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
xhci_dbg ( xhci , " suspend failed because "
" port %d is resuming \n " ,
port + 1 ) ;
return - EBUSY ;
}
}
}
port = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
xhci - > bus_suspended = 0 ;
while ( port - - ) {
/* suspend the port if the port is not suspended */
u32 __iomem * addr ;
u32 t1 , t2 ;
int slot_id ;
addr = & xhci - > op_regs - > port_status_base +
NUM_PORT_REGS * ( port & 0xff ) ;
t1 = xhci_readl ( xhci , addr ) ;
t2 = xhci_port_state_to_neutral ( t1 ) ;
if ( ( t1 & PORT_PE ) & & ! ( t1 & PORT_PLS_MASK ) ) {
xhci_dbg ( xhci , " port %d not suspended \n " , port ) ;
slot_id = xhci_find_slot_id_by_port ( xhci , port + 1 ) ;
if ( slot_id ) {
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
xhci_stop_device ( xhci , slot_id , 1 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
}
t2 & = ~ PORT_PLS_MASK ;
t2 | = PORT_LINK_STROBE | XDEV_U3 ;
set_bit ( port , & xhci - > bus_suspended ) ;
}
if ( hcd - > self . root_hub - > do_remote_wakeup ) {
if ( t1 & PORT_CONNECT ) {
t2 | = PORT_WKOC_E | PORT_WKDISC_E ;
t2 & = ~ PORT_WKCONN_E ;
} else {
t2 | = PORT_WKOC_E | PORT_WKCONN_E ;
t2 & = ~ PORT_WKDISC_E ;
}
} else
t2 & = ~ PORT_WAKE_BITS ;
t1 = xhci_port_state_to_neutral ( t1 ) ;
if ( t1 ! = t2 )
xhci_writel ( xhci , t2 , addr ) ;
if ( DEV_HIGHSPEED ( t1 ) ) {
/* enable remote wake up for USB 2.0 */
u32 __iomem * addr ;
u32 tmp ;
addr = & xhci - > op_regs - > port_power_base +
NUM_PORT_REGS * ( port & 0xff ) ;
tmp = xhci_readl ( xhci , addr ) ;
tmp | = PORT_RWE ;
xhci_writel ( xhci , tmp , addr ) ;
}
}
hcd - > state = HC_STATE_SUSPENDED ;
xhci - > next_statechange = jiffies + msecs_to_jiffies ( 10 ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
return 0 ;
}
int xhci_bus_resume ( struct usb_hcd * hcd )
{
struct xhci_hcd * xhci = hcd_to_xhci ( hcd ) ;
int port ;
u32 temp ;
unsigned long flags ;
xhci_dbg ( xhci , " resume root hub \n " ) ;
if ( time_before ( jiffies , xhci - > next_statechange ) )
msleep ( 5 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
if ( ! HCD_HW_ACCESSIBLE ( hcd ) ) {
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
return - ESHUTDOWN ;
}
/* delay the irqs */
temp = xhci_readl ( xhci , & xhci - > op_regs - > command ) ;
temp & = ~ CMD_EIE ;
xhci_writel ( xhci , temp , & xhci - > op_regs - > command ) ;
port = HCS_MAX_PORTS ( xhci - > hcs_params1 ) ;
while ( port - - ) {
/* Check whether need resume ports. If needed
resume port and disable remote wakeup */
u32 __iomem * addr ;
u32 temp ;
int slot_id ;
addr = & xhci - > op_regs - > port_status_base +
NUM_PORT_REGS * ( port & 0xff ) ;
temp = xhci_readl ( xhci , addr ) ;
if ( DEV_SUPERSPEED ( temp ) )
temp & = ~ ( PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS ) ;
else
temp & = ~ ( PORT_RWC_BITS | PORT_WAKE_BITS ) ;
if ( test_bit ( port , & xhci - > bus_suspended ) & &
( temp & PORT_PLS_MASK ) ) {
if ( DEV_SUPERSPEED ( temp ) ) {
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_U0 ;
xhci_writel ( xhci , temp , addr ) ;
} else {
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_RESUME ;
xhci_writel ( xhci , temp , addr ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
msleep ( 20 ) ;
spin_lock_irqsave ( & xhci - > lock , flags ) ;
temp = xhci_readl ( xhci , addr ) ;
temp = xhci_port_state_to_neutral ( temp ) ;
temp & = ~ PORT_PLS_MASK ;
temp | = PORT_LINK_STROBE | XDEV_U0 ;
xhci_writel ( xhci , temp , addr ) ;
}
slot_id = xhci_find_slot_id_by_port ( xhci , port + 1 ) ;
if ( slot_id )
xhci_ring_device ( xhci , slot_id ) ;
} else
xhci_writel ( xhci , temp , addr ) ;
if ( DEV_HIGHSPEED ( temp ) ) {
/* disable remote wake up for USB 2.0 */
u32 __iomem * addr ;
u32 tmp ;
addr = & xhci - > op_regs - > port_power_base +
NUM_PORT_REGS * ( port & 0xff ) ;
tmp = xhci_readl ( xhci , addr ) ;
tmp & = ~ PORT_RWE ;
xhci_writel ( xhci , tmp , addr ) ;
}
}
( void ) xhci_readl ( xhci , & xhci - > op_regs - > command ) ;
xhci - > next_statechange = jiffies + msecs_to_jiffies ( 5 ) ;
hcd - > state = HC_STATE_RUNNING ;
/* re-enable irqs */
temp = xhci_readl ( xhci , & xhci - > op_regs - > command ) ;
temp | = CMD_EIE ;
xhci_writel ( xhci , temp , & xhci - > op_regs - > command ) ;
temp = xhci_readl ( xhci , & xhci - > op_regs - > command ) ;
spin_unlock_irqrestore ( & xhci - > lock , flags ) ;
return 0 ;
}
2010-10-15 14:59:15 -07:00
# endif /* CONFIG_PM */