2010-07-07 12:31:02 +00:00
/*
* Copyright ( C ) 2010 Brian King IBM Corporation
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/delay.h>
# include <linux/suspend.h>
2011-05-27 13:27:45 -04:00
# include <linux/stat.h>
2010-07-07 12:31:02 +00:00
# include <asm/firmware.h>
# include <asm/hvcall.h>
# include <asm/machdep.h>
# include <asm/mmu.h>
# include <asm/rtas.h>
2012-01-11 06:56:04 +00:00
# include <asm/topology.h>
2010-07-07 12:31:02 +00:00
static u64 stream_id ;
2011-12-21 15:09:52 -08:00
static struct device suspend_dev ;
2010-07-07 12:31:02 +00:00
static DECLARE_COMPLETION ( suspend_work ) ;
static struct rtas_suspend_me_data suspend_data ;
static atomic_t suspending ;
/**
* pseries_suspend_begin - First phase of hibernation
*
* Check to ensure we are in a valid state to hibernate
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_suspend_begin ( suspend_state_t state )
{
long vasi_state , rc ;
unsigned long retbuf [ PLPAR_HCALL_BUFSIZE ] ;
/* Make sure the state is valid */
rc = plpar_hcall ( H_VASI_STATE , retbuf , stream_id ) ;
vasi_state = retbuf [ 0 ] ;
if ( rc ) {
pr_err ( " pseries_suspend_begin: vasi_state returned %ld \n " , rc ) ;
return rc ;
} else if ( vasi_state = = H_VASI_ENABLED ) {
return - EAGAIN ;
} else if ( vasi_state ! = H_VASI_SUSPENDING ) {
pr_err ( " pseries_suspend_begin: vasi_state returned state %ld \n " ,
vasi_state ) ;
return - EIO ;
}
return 0 ;
}
/**
* pseries_suspend_cpu - Suspend a single CPU
*
* Makes the H_JOIN call to suspend the CPU
*
* */
static int pseries_suspend_cpu ( void )
{
if ( atomic_read ( & suspending ) )
return rtas_suspend_cpu ( & suspend_data ) ;
return 0 ;
}
/**
* pseries_suspend_enter - Final phase of hibernation
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_suspend_enter ( suspend_state_t state )
{
int rc = rtas_suspend_last_cpu ( & suspend_data ) ;
atomic_set ( & suspending , 0 ) ;
atomic_set ( & suspend_data . done , 1 ) ;
return rc ;
}
/**
* pseries_prepare_late - Prepare to suspend all other CPUs
*
* Return value :
* 0 on success / other on failure
* */
static int pseries_prepare_late ( void )
{
atomic_set ( & suspending , 1 ) ;
atomic_set ( & suspend_data . working , 0 ) ;
atomic_set ( & suspend_data . done , 0 ) ;
atomic_set ( & suspend_data . error , 0 ) ;
suspend_data . complete = & suspend_work ;
INIT_COMPLETION ( suspend_work ) ;
return 0 ;
}
/**
* store_hibernate - Initiate partition hibernation
2011-12-21 15:09:52 -08:00
* @ dev : subsys root device
* @ attr : device attribute struct
2010-07-07 12:31:02 +00:00
* @ buf : buffer
* @ count : buffer size
*
* Write the stream ID received from the HMC to this file
* to trigger hibernating the partition
*
* Return value :
* number of bytes printed to buffer / other on failure
* */
2011-12-21 15:09:52 -08:00
static ssize_t store_hibernate ( struct device * dev ,
struct device_attribute * attr ,
2010-07-07 12:31:02 +00:00
const char * buf , size_t count )
{
int rc ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
stream_id = simple_strtoul ( buf , NULL , 16 ) ;
do {
rc = pseries_suspend_begin ( PM_SUSPEND_MEM ) ;
if ( rc = = - EAGAIN )
ssleep ( 1 ) ;
} while ( rc = = - EAGAIN ) ;
2012-01-11 06:56:04 +00:00
if ( ! rc ) {
stop_topology_update ( ) ;
2010-07-07 12:31:02 +00:00
rc = pm_suspend ( PM_SUSPEND_MEM ) ;
2012-01-11 06:56:04 +00:00
start_topology_update ( ) ;
}
2010-07-07 12:31:02 +00:00
stream_id = 0 ;
if ( ! rc )
rc = count ;
return rc ;
}
2011-12-21 15:09:52 -08:00
static DEVICE_ATTR ( hibernate , S_IWUSR , NULL , store_hibernate ) ;
2010-07-07 12:31:02 +00:00
2011-12-21 15:09:52 -08:00
static struct bus_type suspend_subsys = {
2010-07-07 12:31:02 +00:00
. name = " power " ,
2011-12-21 15:09:52 -08:00
. dev_name = " power " ,
2010-07-07 12:31:02 +00:00
} ;
2010-11-16 14:14:02 +01:00
static const struct platform_suspend_ops pseries_suspend_ops = {
2010-07-07 12:31:02 +00:00
. valid = suspend_valid_only_mem ,
. begin = pseries_suspend_begin ,
. prepare_late = pseries_prepare_late ,
. enter = pseries_suspend_enter ,
} ;
/**
* pseries_suspend_sysfs_register - Register with sysfs
*
* Return value :
* 0 on success / other on failure
* */
2011-12-21 15:09:52 -08:00
static int pseries_suspend_sysfs_register ( struct device * dev )
2010-07-07 12:31:02 +00:00
{
int rc ;
2011-12-21 15:09:52 -08:00
if ( ( rc = subsys_system_register ( & suspend_subsys , NULL ) ) )
2010-07-07 12:31:02 +00:00
return rc ;
2011-12-21 15:09:52 -08:00
dev - > id = 0 ;
dev - > bus = & suspend_subsys ;
2010-07-07 12:31:02 +00:00
2011-12-21 15:09:52 -08:00
if ( ( rc = device_create_file ( suspend_subsys . dev_root , & dev_attr_hibernate ) ) )
goto subsys_unregister ;
2010-07-07 12:31:02 +00:00
return 0 ;
2011-12-21 15:09:52 -08:00
subsys_unregister :
bus_unregister ( & suspend_subsys ) ;
2010-07-07 12:31:02 +00:00
return rc ;
}
/**
* pseries_suspend_init - initcall for pSeries suspend
*
* Return value :
* 0 on success / other on failure
* */
static int __init pseries_suspend_init ( void )
{
int rc ;
if ( ! machine_is ( pseries ) | | ! firmware_has_feature ( FW_FEATURE_LPAR ) )
return 0 ;
suspend_data . token = rtas_token ( " ibm,suspend-me " ) ;
if ( suspend_data . token = = RTAS_UNKNOWN_SERVICE )
return 0 ;
2011-12-21 15:09:52 -08:00
if ( ( rc = pseries_suspend_sysfs_register ( & suspend_dev ) ) )
2010-07-07 12:31:02 +00:00
return rc ;
ppc_md . suspend_disable_cpu = pseries_suspend_cpu ;
suspend_set_ops ( & pseries_suspend_ops ) ;
return 0 ;
}
__initcall ( pseries_suspend_init ) ;