2005-04-17 02:20:36 +04:00
/*
* FR - V Power Management Routines
*
* Copyright ( c ) 2004 Red Hat , Inc .
*
* Based on SA1100 version :
* Copyright ( c ) 2001 Cliff Brake < cbrake @ accelent . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License .
*
*/
# include <linux/init.h>
2006-01-08 12:01:19 +03:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/pm.h>
# include <linux/sched.h>
# include <linux/interrupt.h>
# include <linux/sysctl.h>
# include <linux/errno.h>
# include <linux/delay.h>
# include <asm/uaccess.h>
# include <asm/mb86943a.h>
# include "local.h"
/*
* Debug macros
*/
# define DEBUG
int pm_do_suspend ( void )
{
local_irq_disable ( ) ;
__set_LEDS ( 0xb1 ) ;
/* go zzz */
frv_cpu_suspend ( pdm_suspend_mode ) ;
__set_LEDS ( 0xb2 ) ;
local_irq_enable ( ) ;
return 0 ;
}
static unsigned long __irq_mask ;
/*
* Setup interrupt masks , etc to enable wakeup by power switch
*/
static void __default_power_switch_setup ( void )
{
/* default is to mask all interrupt sources. */
__irq_mask = * ( unsigned long * ) 0xfeff9820 ;
* ( unsigned long * ) 0xfeff9820 = 0xfffe0000 ;
}
/*
* Cleanup interrupt masks , etc after wakeup by power switch
*/
static void __default_power_switch_cleanup ( void )
{
* ( unsigned long * ) 0xfeff9820 = __irq_mask ;
}
/*
* Return non - zero if wakeup irq was caused by power switch
*/
static int __default_power_switch_check ( void )
{
return 1 ;
}
void ( * __power_switch_wake_setup ) ( void ) = __default_power_switch_setup ;
int ( * __power_switch_wake_check ) ( void ) = __default_power_switch_check ;
void ( * __power_switch_wake_cleanup ) ( void ) = __default_power_switch_cleanup ;
int pm_do_bus_sleep ( void )
{
local_irq_disable ( ) ;
/*
* Here is where we need some platform - dependent setup
* of the interrupt state so that appropriate wakeup
* sources are allowed and all others are masked .
*/
__power_switch_wake_setup ( ) ;
__set_LEDS ( 0xa1 ) ;
/* go zzz
*
* This is in a loop in case power switch shares an irq with other
* devices . The wake_check ( ) tells us if we need to finish waking
* or go back to sleep .
*/
do {
frv_cpu_suspend ( HSR0_PDM_BUS_SLEEP ) ;
} while ( __power_switch_wake_check & & ! __power_switch_wake_check ( ) ) ;
__set_LEDS ( 0xa2 ) ;
/*
* Here is where we need some platform - dependent restore
* of the interrupt state prior to being called .
*/
__power_switch_wake_cleanup ( ) ;
local_irq_enable ( ) ;
return 0 ;
}
unsigned long sleep_phys_sp ( void * sp )
{
return virt_to_phys ( sp ) ;
}
# ifdef CONFIG_SYSCTL
/*
* Use a temporary sysctl number . Horrid , but will be cleaned up in 2.6
* when all the PM interfaces exist nicely .
*/
# define CTL_PM_SUSPEND 1
# define CTL_PM_CMODE 2
# define CTL_PM_P0 4
# define CTL_PM_CM 5
2006-06-23 13:04:05 +04:00
static int user_atoi ( char __user * ubuf , size_t len )
2005-04-17 02:20:36 +04:00
{
char buf [ 16 ] ;
unsigned long ret ;
if ( len > 15 )
return - EINVAL ;
if ( copy_from_user ( buf , ubuf , len ) )
return - EFAULT ;
buf [ len ] = 0 ;
ret = simple_strtoul ( buf , NULL , 0 ) ;
if ( ret > INT_MAX )
return - ERANGE ;
return ret ;
}
/*
* Send us to sleep .
*/
2009-09-24 02:57:19 +04:00
static int sysctl_pm_do_suspend ( ctl_table * ctl , int write ,
2006-06-23 13:04:05 +04:00
void __user * buffer , size_t * lenp , loff_t * fpos )
2005-04-17 02:20:36 +04:00
{
2012-10-05 04:12:16 +04:00
int mode ;
2005-04-17 02:20:36 +04:00
if ( * lenp < = 0 )
return - EIO ;
mode = user_atoi ( buffer , * lenp ) ;
2012-10-05 04:12:16 +04:00
switch ( mode ) {
case 1 :
return pm_do_suspend ( ) ;
2005-04-17 02:20:36 +04:00
2012-10-05 04:12:16 +04:00
case 5 :
return pm_do_bus_sleep ( ) ;
2005-04-17 02:20:36 +04:00
2012-10-05 04:12:16 +04:00
default :
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
}
static int try_set_cmode ( int new_cmode )
{
if ( new_cmode > 15 )
return - EINVAL ;
if ( ! ( clock_cmodes_permitted & ( 1 < < new_cmode ) ) )
return - EINVAL ;
/* now change cmode */
local_irq_disable ( ) ;
frv_dma_pause_all ( ) ;
frv_change_cmode ( new_cmode ) ;
determine_clocks ( 0 ) ;
time_divisor_init ( ) ;
# ifdef DEBUG
determine_clocks ( 1 ) ;
# endif
frv_dma_resume_all ( ) ;
local_irq_enable ( ) ;
return 0 ;
}
2009-09-24 02:57:19 +04:00
static int cmode_procctl ( ctl_table * ctl , int write ,
2006-06-23 13:04:05 +04:00
void __user * buffer , size_t * lenp , loff_t * fpos )
2005-04-17 02:20:36 +04:00
{
int new_cmode ;
if ( ! write )
2009-09-24 02:57:19 +04:00
return proc_dointvec ( ctl , write , buffer , lenp , fpos ) ;
2005-04-17 02:20:36 +04:00
new_cmode = user_atoi ( buffer , * lenp ) ;
return try_set_cmode ( new_cmode ) ? : * lenp ;
}
static int try_set_p0 ( int new_p0 )
{
unsigned long flags , clkc ;
if ( new_p0 < 0 | | new_p0 > 1 )
return - EINVAL ;
local_irq_save ( flags ) ;
__set_PSR ( flags & ~ PSR_ET ) ;
frv_dma_pause_all ( ) ;
clkc = __get_CLKC ( ) ;
if ( new_p0 )
clkc | = CLKC_P0 ;
else
clkc & = ~ CLKC_P0 ;
__set_CLKC ( clkc ) ;
determine_clocks ( 0 ) ;
time_divisor_init ( ) ;
# ifdef DEBUG
determine_clocks ( 1 ) ;
# endif
frv_dma_resume_all ( ) ;
local_irq_restore ( flags ) ;
return 0 ;
}
static int try_set_cm ( int new_cm )
{
unsigned long flags , clkc ;
if ( new_cm < 0 | | new_cm > 1 )
return - EINVAL ;
local_irq_save ( flags ) ;
__set_PSR ( flags & ~ PSR_ET ) ;
frv_dma_pause_all ( ) ;
clkc = __get_CLKC ( ) ;
clkc & = ~ CLKC_CM ;
clkc | = new_cm ;
__set_CLKC ( clkc ) ;
determine_clocks ( 0 ) ;
time_divisor_init ( ) ;
# if 1 //def DEBUG
determine_clocks ( 1 ) ;
# endif
frv_dma_resume_all ( ) ;
local_irq_restore ( flags ) ;
return 0 ;
}
2009-09-24 02:57:19 +04:00
static int p0_procctl ( ctl_table * ctl , int write ,
2006-06-23 13:04:05 +04:00
void __user * buffer , size_t * lenp , loff_t * fpos )
2005-04-17 02:20:36 +04:00
{
int new_p0 ;
if ( ! write )
2009-09-24 02:57:19 +04:00
return proc_dointvec ( ctl , write , buffer , lenp , fpos ) ;
2005-04-17 02:20:36 +04:00
new_p0 = user_atoi ( buffer , * lenp ) ;
return try_set_p0 ( new_p0 ) ? : * lenp ;
}
2009-09-24 02:57:19 +04:00
static int cm_procctl ( ctl_table * ctl , int write ,
2006-06-23 13:04:05 +04:00
void __user * buffer , size_t * lenp , loff_t * fpos )
2005-04-17 02:20:36 +04:00
{
int new_cm ;
if ( ! write )
2009-09-24 02:57:19 +04:00
return proc_dointvec ( ctl , write , buffer , lenp , fpos ) ;
2005-04-17 02:20:36 +04:00
new_cm = user_atoi ( buffer , * lenp ) ;
return try_set_cm ( new_cm ) ? : * lenp ;
}
static struct ctl_table pm_table [ ] =
{
2007-02-14 11:33:39 +03:00
{
. procname = " suspend " ,
. data = NULL ,
. maxlen = 0 ,
. mode = 0200 ,
2009-11-16 14:11:48 +03:00
. proc_handler = sysctl_pm_do_suspend ,
2007-02-14 11:33:39 +03:00
} ,
{
. procname = " cmode " ,
. data = & clock_cmode_current ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
2009-11-16 14:11:48 +03:00
. proc_handler = cmode_procctl ,
2007-02-14 11:33:39 +03:00
} ,
{
. procname = " p0 " ,
. data = & clock_p0_current ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
2009-11-16 14:11:48 +03:00
. proc_handler = p0_procctl ,
2007-02-14 11:33:39 +03:00
} ,
{
. procname = " cm " ,
. data = & clock_cm_current ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
2009-11-16 14:11:48 +03:00
. proc_handler = cm_procctl ,
2007-02-14 11:33:39 +03:00
} ,
2009-04-03 14:53:38 +04:00
{ }
2005-04-17 02:20:36 +04:00
} ;
static struct ctl_table pm_dir_table [ ] =
{
2007-02-14 11:33:39 +03:00
{
. procname = " pm " ,
. mode = 0555 ,
. child = pm_table ,
} ,
2009-04-03 14:53:38 +04:00
{ }
2005-04-17 02:20:36 +04:00
} ;
/*
* Initialize power interface
*/
static int __init pm_init ( void )
{
2007-02-14 11:34:09 +03:00
register_sysctl_table ( pm_dir_table ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
__initcall ( pm_init ) ;
# endif