2008-07-24 13:27:36 +04:00
/*
* MUSB OTG driver virtual root hub support
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright ( C ) 2005 - 2006 by Texas Instruments
* Copyright ( C ) 2006 - 2007 Nokia Corporation
*
* 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 . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*
* THIS SOFTWARE IS PROVIDED " AS IS " AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
* INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF
* USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/errno.h>
# include <linux/time.h>
# include <linux/timer.h>
# include <asm/unaligned.h>
# include "musb_core.h"
2013-12-18 23:23:46 +04:00
void musb_host_finish_resume ( struct work_struct * work )
{
struct musb * musb ;
unsigned long flags ;
u8 power ;
2013-12-20 13:47:15 +04:00
musb = container_of ( work , struct musb , finish_resume_work . work ) ;
2013-12-18 23:23:46 +04:00
spin_lock_irqsave ( & musb - > lock , flags ) ;
power = musb_readb ( musb - > mregs , MUSB_POWER ) ;
power & = ~ MUSB_POWER_RESUME ;
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " root port resume stopped, power %02x " , power ) ;
2013-12-18 23:23:46 +04:00
musb_writeb ( musb - > mregs , MUSB_POWER , power ) ;
/*
* ISSUE : DaVinci ( RTL 1.300 ) disconnects after
* resume of high speed peripherals ( but not full
* speed ones ) .
*/
musb - > is_active = 1 ;
musb - > port1_status & = ~ ( USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME ) ;
musb - > port1_status | = USB_PORT_STAT_C_SUSPEND < < 16 ;
usb_hcd_poll_rh_status ( musb - > hcd ) ;
/* NOTE: it might really be A_WAIT_BCON ... */
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_A_HOST ;
2013-12-18 23:23:46 +04:00
spin_unlock_irqrestore ( & musb - > lock , flags ) ;
}
2013-11-26 01:26:41 +04:00
void musb_port_suspend ( struct musb * musb , bool do_suspend )
2008-07-24 13:27:36 +04:00
{
2012-02-13 15:24:15 +04:00
struct usb_otg * otg = musb - > xceiv - > otg ;
2008-07-24 13:27:36 +04:00
u8 power ;
void __iomem * mbase = musb - > mregs ;
if ( ! is_host_active ( musb ) )
return ;
/* NOTE: this doesn't necessarily put PHY into low power mode,
* turning off its clock ; that ' s a function of PHY integration and
* MUSB_POWER_ENSUSPEND . PHY may need a clock ( sigh ) to detect
* SE0 changing to connect ( J ) or wakeup ( K ) states .
*/
power = musb_readb ( mbase , MUSB_POWER ) ;
if ( do_suspend ) {
int retries = 10000 ;
power & = ~ MUSB_POWER_RESUME ;
power | = MUSB_POWER_SUSPENDM ;
musb_writeb ( mbase , MUSB_POWER , power ) ;
/* Needed for OPT A tests */
power = musb_readb ( mbase , MUSB_POWER ) ;
while ( power & MUSB_POWER_SUSPENDM ) {
power = musb_readb ( mbase , MUSB_POWER ) ;
if ( retries - - < 1 )
break ;
}
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " Root port suspended, power %02x " , power ) ;
2008-07-24 13:27:36 +04:00
musb - > port1_status | = USB_PORT_STAT_SUSPEND ;
2014-10-30 20:41:13 +03:00
switch ( musb - > xceiv - > otg - > state ) {
2008-07-24 13:27:36 +04:00
case OTG_STATE_A_HOST :
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_A_SUSPEND ;
2011-11-24 17:46:26 +04:00
musb - > is_active = otg - > host - > b_hnp_enable ;
2009-03-31 23:35:09 +04:00
if ( musb - > is_active )
mod_timer ( & musb - > otg_timer , jiffies
+ msecs_to_jiffies (
OTG_TIME_A_AIDL_BDIS ) ) ;
2008-07-24 13:27:36 +04:00
musb_platform_try_idle ( musb , 0 ) ;
break ;
case OTG_STATE_B_HOST :
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_B_WAIT_ACON ;
2011-11-24 17:46:26 +04:00
musb - > is_active = otg - > host - > b_hnp_enable ;
2008-07-24 13:27:36 +04:00
musb_platform_try_idle ( musb , 0 ) ;
break ;
default :
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " bogus rh suspend? %s " ,
2014-10-30 20:41:13 +03:00
usb_otg_state_string ( musb - > xceiv - > otg - > state ) ) ;
2008-07-24 13:27:36 +04:00
}
} else if ( power & MUSB_POWER_SUSPENDM ) {
power & = ~ MUSB_POWER_SUSPENDM ;
power | = MUSB_POWER_RESUME ;
musb_writeb ( mbase , MUSB_POWER , power ) ;
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " Root port resuming, power %02x " , power ) ;
2008-07-24 13:27:36 +04:00
/* later, GetPortStatus will stop RESUME signaling */
musb - > port1_status | = MUSB_PORT_STAT_RESUME ;
2014-02-14 23:49:47 +04:00
schedule_delayed_work ( & musb - > finish_resume_work ,
2015-02-13 23:46:27 +03:00
msecs_to_jiffies ( USB_RESUME_TIMEOUT ) ) ;
2008-07-24 13:27:36 +04:00
}
}
2013-11-26 16:31:14 +04:00
void musb_port_reset ( struct musb * musb , bool do_reset )
2008-07-24 13:27:36 +04:00
{
u8 power ;
void __iomem * mbase = musb - > mregs ;
2014-10-30 20:41:13 +03:00
if ( musb - > xceiv - > otg - > state = = OTG_STATE_B_IDLE ) {
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " HNP: Returning from HNP; no hub reset from b_idle " ) ;
2008-07-24 13:27:36 +04:00
musb - > port1_status & = ~ USB_PORT_STAT_RESET ;
return ;
}
if ( ! is_host_active ( musb ) )
return ;
/* NOTE: caller guarantees it will turn off the reset when
* the appropriate amount of time has passed
*/
power = musb_readb ( mbase , MUSB_POWER ) ;
if ( do_reset ) {
/*
* If RESUME is set , we must make sure it stays minimum 20 ms .
* Then we must clear RESUME and wait a bit to let musb start
* generating SOFs . If we don ' t do this , OPT HS A 6.8 tests
* fail with " Error! Did not receive an SOF before suspend
* detected " .
*/
if ( power & MUSB_POWER_RESUME ) {
2014-02-14 23:49:46 +04:00
long remain = ( unsigned long ) musb - > rh_timer - jiffies ;
if ( musb - > rh_timer > 0 & & remain > 0 ) {
/* take into account the minimum delay after resume */
schedule_delayed_work (
2014-02-14 23:49:47 +04:00
& musb - > deassert_reset_work , remain ) ;
2014-02-14 23:49:46 +04:00
return ;
}
2008-07-24 13:27:36 +04:00
musb_writeb ( mbase , MUSB_POWER ,
2014-02-14 23:49:46 +04:00
power & ~ MUSB_POWER_RESUME ) ;
/* Give the core 1 ms to clear MUSB_POWER_RESUME */
2014-02-14 23:49:47 +04:00
schedule_delayed_work ( & musb - > deassert_reset_work ,
msecs_to_jiffies ( 1 ) ) ;
2014-02-14 23:49:46 +04:00
return ;
2008-07-24 13:27:36 +04:00
}
power & = 0xf0 ;
musb_writeb ( mbase , MUSB_POWER ,
power | MUSB_POWER_RESET ) ;
musb - > port1_status | = USB_PORT_STAT_RESET ;
musb - > port1_status & = ~ USB_PORT_STAT_ENABLE ;
2014-02-14 23:49:47 +04:00
schedule_delayed_work ( & musb - > deassert_reset_work ,
msecs_to_jiffies ( 50 ) ) ;
2008-07-24 13:27:36 +04:00
} else {
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " root port reset stopped " ) ;
2015-03-29 13:50:47 +03:00
musb_platform_pre_root_reset_end ( musb ) ;
2008-07-24 13:27:36 +04:00
musb_writeb ( mbase , MUSB_POWER ,
power & ~ MUSB_POWER_RESET ) ;
2015-03-29 13:50:47 +03:00
musb_platform_post_root_reset_end ( musb ) ;
2008-07-24 13:27:36 +04:00
power = musb_readb ( mbase , MUSB_POWER ) ;
if ( power & MUSB_POWER_HSMODE ) {
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " high-speed device connected " ) ;
2008-07-24 13:27:36 +04:00
musb - > port1_status | = USB_PORT_STAT_HIGH_SPEED ;
}
musb - > port1_status & = ~ USB_PORT_STAT_RESET ;
musb - > port1_status | = USB_PORT_STAT_ENABLE
| ( USB_PORT_STAT_C_RESET < < 16 )
| ( USB_PORT_STAT_C_ENABLE < < 16 ) ;
2013-04-10 23:55:50 +04:00
usb_hcd_poll_rh_status ( musb - > hcd ) ;
2008-07-24 13:27:36 +04:00
musb - > vbuserr_retry = VBUSERR_RETRY_COUNT ;
}
}
void musb_root_disconnect ( struct musb * musb )
{
2012-02-13 15:24:15 +04:00
struct usb_otg * otg = musb - > xceiv - > otg ;
2010-03-05 01:05:08 +03:00
musb - > port1_status = USB_PORT_STAT_POWER
| ( USB_PORT_STAT_C_CONNECTION < < 16 ) ;
2008-07-24 13:27:36 +04:00
2013-04-10 23:55:50 +04:00
usb_hcd_poll_rh_status ( musb - > hcd ) ;
2008-07-24 13:27:36 +04:00
musb - > is_active = 0 ;
2014-10-30 20:41:13 +03:00
switch ( musb - > xceiv - > otg - > state ) {
2008-07-24 13:27:36 +04:00
case OTG_STATE_A_SUSPEND :
2011-11-24 17:46:26 +04:00
if ( otg - > host - > b_hnp_enable ) {
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_A_PERIPHERAL ;
2009-04-02 21:16:11 +04:00
musb - > g . is_a_peripheral = 1 ;
break ;
}
/* FALLTHROUGH */
case OTG_STATE_A_HOST :
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_A_WAIT_BCON ;
2008-07-24 13:27:36 +04:00
musb - > is_active = 0 ;
break ;
case OTG_STATE_A_WAIT_VFALL :
2014-10-30 20:41:13 +03:00
musb - > xceiv - > otg - > state = OTG_STATE_B_IDLE ;
2008-07-24 13:27:36 +04:00
break ;
default :
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " host disconnect (%s) " ,
2014-10-30 20:41:13 +03:00
usb_otg_state_string ( musb - > xceiv - > otg - > state ) ) ;
2008-07-24 13:27:36 +04:00
}
}
2016-09-17 13:08:10 +03:00
EXPORT_SYMBOL_GPL ( musb_root_disconnect ) ;
2008-07-24 13:27:36 +04:00
/*---------------------------------------------------------------------*/
/* Caller may or may not hold musb->lock */
int musb_hub_status_data ( struct usb_hcd * hcd , char * buf )
{
struct musb * musb = hcd_to_musb ( hcd ) ;
int retval = 0 ;
/* called in_irq() via usb_hcd_poll_rh_status() */
if ( musb - > port1_status & 0xffff0000 ) {
* buf = 0x02 ;
retval = 1 ;
}
return retval ;
}
2013-10-15 20:29:22 +04:00
static int musb_has_gadget ( struct musb * musb )
{
/*
* In host - only mode we start a connection right away . In OTG mode
* we have to wait until we loaded a gadget . We don ' t really need a
* gadget if we operate as a host but we should not start a session
* as a device without a gadget or else we explode .
*/
# ifdef CONFIG_USB_MUSB_HOST
return 1 ;
# else
2015-06-02 21:03:36 +03:00
return musb - > port_mode = = MUSB_PORT_MODE_HOST ;
2013-10-15 20:29:22 +04:00
# endif
}
2008-07-24 13:27:36 +04:00
int musb_hub_control (
struct usb_hcd * hcd ,
u16 typeReq ,
u16 wValue ,
u16 wIndex ,
char * buf ,
u16 wLength )
{
struct musb * musb = hcd_to_musb ( hcd ) ;
u32 temp ;
int retval = 0 ;
unsigned long flags ;
2016-08-27 00:28:39 +03:00
bool start_musb = false ;
2008-07-24 13:27:36 +04:00
spin_lock_irqsave ( & musb - > lock , flags ) ;
2010-06-23 00:39:10 +04:00
if ( unlikely ( ! HCD_HW_ACCESSIBLE ( hcd ) ) ) {
2008-07-24 13:27:36 +04:00
spin_unlock_irqrestore ( & musb - > lock , flags ) ;
return - ESHUTDOWN ;
}
/* hub features: always zero, setting is a NOP
* port features : reported , sometimes updated when host is active
* no indicators
*/
switch ( typeReq ) {
case ClearHubFeature :
case SetHubFeature :
switch ( wValue ) {
case C_HUB_OVER_CURRENT :
case C_HUB_LOCAL_POWER :
break ;
default :
goto error ;
}
break ;
case ClearPortFeature :
if ( ( wIndex & 0xff ) ! = 1 )
goto error ;
switch ( wValue ) {
case USB_PORT_FEAT_ENABLE :
break ;
case USB_PORT_FEAT_SUSPEND :
musb_port_suspend ( musb , false ) ;
break ;
case USB_PORT_FEAT_POWER :
2011-11-24 17:46:26 +04:00
if ( ! hcd - > self . is_b_host )
2010-12-01 14:22:05 +03:00
musb_platform_set_vbus ( musb , 0 ) ;
2008-07-24 13:27:36 +04:00
break ;
case USB_PORT_FEAT_C_CONNECTION :
case USB_PORT_FEAT_C_ENABLE :
case USB_PORT_FEAT_C_OVER_CURRENT :
case USB_PORT_FEAT_C_RESET :
case USB_PORT_FEAT_C_SUSPEND :
break ;
default :
goto error ;
}
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " clear feature %d " , wValue ) ;
2008-07-24 13:27:36 +04:00
musb - > port1_status & = ~ ( 1 < < wValue ) ;
break ;
case GetHubDescriptor :
{
struct usb_hub_descriptor * desc = ( void * ) buf ;
desc - > bDescLength = 9 ;
2015-03-29 01:42:06 +03:00
desc - > bDescriptorType = USB_DT_HUB ;
2008-07-24 13:27:36 +04:00
desc - > bNbrPorts = 1 ;
2009-02-12 01:11:36 +03:00
desc - > wHubCharacteristics = cpu_to_le16 (
2015-01-19 01:57:18 +03:00
HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */
| HUB_CHAR_NO_OCPM /* no overcurrent reporting */
) ;
2008-07-24 13:27:36 +04:00
desc - > bPwrOn2PwrGood = 5 ; /* msec/2 */
desc - > bHubContrCurrent = 0 ;
/* workaround bogus struct definition */
2001-09-17 11:00:00 +04:00
desc - > u . hs . DeviceRemovable [ 0 ] = 0x02 ; /* port 1 */
desc - > u . hs . DeviceRemovable [ 1 ] = 0xff ;
2008-07-24 13:27:36 +04:00
}
break ;
case GetHubStatus :
temp = 0 ;
* ( __le32 * ) buf = cpu_to_le32 ( temp ) ;
break ;
case GetPortStatus :
if ( wIndex ! = 1 )
goto error ;
put_unaligned ( cpu_to_le32 ( musb - > port1_status
& ~ MUSB_PORT_STAT_RESUME ) ,
( __le32 * ) buf ) ;
/* port change status is more interesting */
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " port status %08x " , musb - > port1_status ) ;
2008-07-24 13:27:36 +04:00
break ;
case SetPortFeature :
if ( ( wIndex & 0xff ) ! = 1 )
goto error ;
switch ( wValue ) {
case USB_PORT_FEAT_POWER :
/* NOTE: this controller has a strange state machine
* that involves " requesting sessions " according to
* magic side effects from incompletely - described
* rules about startup . . .
*
* This call is what really starts the host mode ; be
* very careful about side effects if you reorder any
* initialization logic , e . g . for OTG , or change any
* logic relating to VBUS power - up .
*/
2013-10-15 20:29:22 +04:00
if ( ! hcd - > self . is_b_host & & musb_has_gadget ( musb ) )
2016-08-27 00:28:39 +03:00
start_musb = true ;
2008-07-24 13:27:36 +04:00
break ;
case USB_PORT_FEAT_RESET :
musb_port_reset ( musb , true ) ;
break ;
case USB_PORT_FEAT_SUSPEND :
musb_port_suspend ( musb , true ) ;
break ;
case USB_PORT_FEAT_TEST :
if ( unlikely ( is_host_active ( musb ) ) )
goto error ;
wIndex > > = 8 ;
switch ( wIndex ) {
case 1 :
pr_debug ( " TEST_J \n " ) ;
temp = MUSB_TEST_J ;
break ;
case 2 :
pr_debug ( " TEST_K \n " ) ;
temp = MUSB_TEST_K ;
break ;
case 3 :
pr_debug ( " TEST_SE0_NAK \n " ) ;
temp = MUSB_TEST_SE0_NAK ;
break ;
case 4 :
pr_debug ( " TEST_PACKET \n " ) ;
temp = MUSB_TEST_PACKET ;
musb_load_testpacket ( musb ) ;
break ;
case 5 :
pr_debug ( " TEST_FORCE_ENABLE \n " ) ;
temp = MUSB_TEST_FORCE_HOST
| MUSB_TEST_FORCE_HS ;
musb_writeb ( musb - > mregs , MUSB_DEVCTL ,
MUSB_DEVCTL_SESSION ) ;
break ;
case 6 :
pr_debug ( " TEST_FIFO_ACCESS \n " ) ;
temp = MUSB_TEST_FIFO_ACCESS ;
break ;
default :
goto error ;
}
musb_writeb ( musb - > mregs , MUSB_TESTMODE , temp ) ;
break ;
default :
goto error ;
}
2016-06-30 20:12:22 +03:00
musb_dbg ( musb , " set feature %d " , wValue ) ;
2008-07-24 13:27:36 +04:00
musb - > port1_status | = 1 < < wValue ;
break ;
default :
error :
/* "protocol stall" on error */
retval = - EPIPE ;
}
spin_unlock_irqrestore ( & musb - > lock , flags ) ;
2016-08-27 00:28:39 +03:00
if ( start_musb )
musb_start ( musb ) ;
2008-07-24 13:27:36 +04:00
return retval ;
}