2007-10-09 12:37:13 -05:00
/*
* MPC83xx suspend support
*
* Author : Scott Wood < scottwood @ freescale . com >
*
* Copyright ( c ) 2006 - 2007 Freescale Semiconductor , Inc .
*
* 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 .
*/
# include <linux/init.h>
# include <linux/pm.h>
# include <linux/types.h>
# include <linux/ioport.h>
# include <linux/interrupt.h>
# include <linux/wait.h>
# include <linux/kthread.h>
# include <linux/freezer.h>
# include <linux/suspend.h>
# include <linux/fsl_devices.h>
# include <linux/of_platform.h>
2011-05-27 10:46:24 -04:00
# include <linux/export.h>
2007-10-09 12:37:13 -05:00
# include <asm/reg.h>
# include <asm/io.h>
# include <asm/time.h>
# include <asm/mpc6xx.h>
2012-03-28 18:30:02 +01:00
# include <asm/switch_to.h>
2007-10-09 12:37:13 -05:00
# include <sysdev/fsl_soc.h>
# define PMCCR1_NEXT_STATE 0x0C /* Next state for power management */
# define PMCCR1_NEXT_STATE_SHIFT 2
# define PMCCR1_CURR_STATE 0x03 /* Current state for power management*/
2009-12-10 21:00:56 +03:00
# define IMMR_SYSCR_OFFSET 0x100
2007-10-09 12:37:13 -05:00
# define IMMR_RCW_OFFSET 0x900
# define RCW_PCI_HOST 0x80000000
void mpc83xx_enter_deep_sleep ( phys_addr_t immrbase ) ;
struct mpc83xx_pmc {
u32 config ;
# define PMCCR_DLPEN 2 /* DDR SDRAM low power enable */
# define PMCCR_SLPEN 1 /* System low power enable */
u32 event ;
u32 mask ;
/* All but PMCI are deep-sleep only */
# define PMCER_GPIO 0x100
# define PMCER_PCI 0x080
# define PMCER_USB 0x040
# define PMCER_ETSEC1 0x020
# define PMCER_ETSEC2 0x010
# define PMCER_TIMER 0x008
# define PMCER_INT1 0x004
# define PMCER_INT2 0x002
# define PMCER_PMCI 0x001
# define PMCER_ALL 0x1FF
/* deep-sleep only */
u32 config1 ;
# define PMCCR1_USE_STATE 0x80000000
# define PMCCR1_PME_EN 0x00000080
# define PMCCR1_ASSERT_PME 0x00000040
# define PMCCR1_POWER_OFF 0x00000020
/* deep-sleep only */
u32 config2 ;
} ;
struct mpc83xx_rcw {
u32 rcwlr ;
u32 rcwhr ;
} ;
struct mpc83xx_clock {
u32 spmr ;
u32 occr ;
u32 sccr ;
} ;
2009-12-10 21:00:56 +03:00
struct mpc83xx_syscr {
__be32 sgprl ;
__be32 sgprh ;
__be32 spridr ;
__be32 : 32 ;
__be32 spcr ;
__be32 sicrl ;
__be32 sicrh ;
} ;
struct mpc83xx_saved {
u32 sicrl ;
u32 sicrh ;
u32 sccr ;
} ;
2007-10-09 12:37:13 -05:00
struct pmc_type {
int has_deep_sleep ;
} ;
2010-07-22 15:52:34 -06:00
static struct platform_device * pmc_dev ;
2007-10-09 12:37:13 -05:00
static int has_deep_sleep , deep_sleeping ;
static int pmc_irq ;
static struct mpc83xx_pmc __iomem * pmc_regs ;
static struct mpc83xx_clock __iomem * clock_regs ;
2009-12-10 21:00:56 +03:00
static struct mpc83xx_syscr __iomem * syscr_regs ;
static struct mpc83xx_saved saved_regs ;
2007-10-09 12:37:13 -05:00
static int is_pci_agent , wake_from_pci ;
static phys_addr_t immrbase ;
static int pci_pm_state ;
static DECLARE_WAIT_QUEUE_HEAD ( agent_wq ) ;
int fsl_deep_sleep ( void )
{
return deep_sleeping ;
}
2009-09-23 22:52:38 +04:00
EXPORT_SYMBOL ( fsl_deep_sleep ) ;
2007-10-09 12:37:13 -05:00
static int mpc83xx_change_state ( void )
{
u32 curr_state ;
u32 reg_cfg1 = in_be32 ( & pmc_regs - > config1 ) ;
if ( is_pci_agent ) {
pci_pm_state = ( reg_cfg1 & PMCCR1_NEXT_STATE ) > >
PMCCR1_NEXT_STATE_SHIFT ;
curr_state = reg_cfg1 & PMCCR1_CURR_STATE ;
if ( curr_state ! = pci_pm_state ) {
reg_cfg1 & = ~ PMCCR1_CURR_STATE ;
reg_cfg1 | = pci_pm_state ;
out_be32 ( & pmc_regs - > config1 , reg_cfg1 ) ;
wake_up ( & agent_wq ) ;
return 1 ;
}
}
return 0 ;
}
static irqreturn_t pmc_irq_handler ( int irq , void * dev_id )
{
u32 event = in_be32 ( & pmc_regs - > event ) ;
int ret = IRQ_NONE ;
if ( mpc83xx_change_state ( ) )
ret = IRQ_HANDLED ;
if ( event ) {
out_be32 ( & pmc_regs - > event , event ) ;
ret = IRQ_HANDLED ;
}
return ret ;
}
2009-12-10 21:00:56 +03:00
static void mpc83xx_suspend_restore_regs ( void )
{
out_be32 ( & syscr_regs - > sicrl , saved_regs . sicrl ) ;
out_be32 ( & syscr_regs - > sicrh , saved_regs . sicrh ) ;
out_be32 ( & clock_regs - > sccr , saved_regs . sccr ) ;
}
static void mpc83xx_suspend_save_regs ( void )
{
saved_regs . sicrl = in_be32 ( & syscr_regs - > sicrl ) ;
saved_regs . sicrh = in_be32 ( & syscr_regs - > sicrh ) ;
saved_regs . sccr = in_be32 ( & clock_regs - > sccr ) ;
}
2007-10-09 12:37:13 -05:00
static int mpc83xx_suspend_enter ( suspend_state_t state )
{
int ret = - EAGAIN ;
/* Don't go to sleep if there's a race where pci_pm_state changes
* between the agent thread checking it and the PM code disabling
* interrupts .
*/
if ( wake_from_pci ) {
if ( pci_pm_state ! = ( deep_sleeping ? 3 : 2 ) )
goto out ;
out_be32 ( & pmc_regs - > config1 ,
in_be32 ( & pmc_regs - > config1 ) | PMCCR1_PME_EN ) ;
}
/* Put the system into low-power mode and the RAM
* into self - refresh mode once the core goes to
* sleep .
*/
out_be32 ( & pmc_regs - > config , PMCCR_SLPEN | PMCCR_DLPEN ) ;
/* If it has deep sleep (i.e. it's an 831x or compatible),
* disable power to the core upon entering sleep mode . This will
* require going through the boot firmware upon a wakeup event .
*/
if ( deep_sleeping ) {
2009-12-10 21:00:56 +03:00
mpc83xx_suspend_save_regs ( ) ;
2007-10-09 12:37:13 -05:00
out_be32 ( & pmc_regs - > mask , PMCER_ALL ) ;
out_be32 ( & pmc_regs - > config1 ,
in_be32 ( & pmc_regs - > config1 ) | PMCCR1_POWER_OFF ) ;
enable_kernel_fp ( ) ;
mpc83xx_enter_deep_sleep ( immrbase ) ;
out_be32 ( & pmc_regs - > config1 ,
in_be32 ( & pmc_regs - > config1 ) & ~ PMCCR1_POWER_OFF ) ;
out_be32 ( & pmc_regs - > mask , PMCER_PMCI ) ;
2009-12-10 21:00:56 +03:00
mpc83xx_suspend_restore_regs ( ) ;
2007-10-09 12:37:13 -05:00
} else {
out_be32 ( & pmc_regs - > mask , PMCER_PMCI ) ;
mpc6xx_enter_standby ( ) ;
}
ret = 0 ;
out :
out_be32 ( & pmc_regs - > config1 ,
in_be32 ( & pmc_regs - > config1 ) & ~ PMCCR1_PME_EN ) ;
return ret ;
}
2009-12-10 21:00:53 +03:00
static void mpc83xx_suspend_end ( void )
2007-10-09 12:37:13 -05:00
{
deep_sleeping = 0 ;
}
static int mpc83xx_suspend_valid ( suspend_state_t state )
{
return state = = PM_SUSPEND_STANDBY | | state = = PM_SUSPEND_MEM ;
}
static int mpc83xx_suspend_begin ( suspend_state_t state )
{
switch ( state ) {
case PM_SUSPEND_STANDBY :
deep_sleeping = 0 ;
return 0 ;
case PM_SUSPEND_MEM :
if ( has_deep_sleep )
deep_sleeping = 1 ;
return 0 ;
default :
return - EINVAL ;
}
}
static int agent_thread_fn ( void * data )
{
while ( 1 ) {
wait_event_interruptible ( agent_wq , pci_pm_state > = 2 ) ;
try_to_freeze ( ) ;
if ( signal_pending ( current ) | | pci_pm_state < 2 )
continue ;
/* With a preemptible kernel (or SMP), this could race with
* a userspace - driven suspend request . It ' s probably best
* to avoid mixing the two with such a configuration ( or
* else fix it by adding a mutex to state_store that we can
* synchronize with ) .
*/
wake_from_pci = 1 ;
pm_suspend ( pci_pm_state = = 3 ? PM_SUSPEND_MEM :
PM_SUSPEND_STANDBY ) ;
wake_from_pci = 0 ;
}
return 0 ;
}
static void mpc83xx_set_agent ( void )
{
out_be32 ( & pmc_regs - > config1 , PMCCR1_USE_STATE ) ;
out_be32 ( & pmc_regs - > mask , PMCER_PMCI ) ;
kthread_run ( agent_thread_fn , NULL , " PCI power mgt " ) ;
}
static int mpc83xx_is_pci_agent ( void )
{
struct mpc83xx_rcw __iomem * rcw_regs ;
int ret ;
rcw_regs = ioremap ( get_immrbase ( ) + IMMR_RCW_OFFSET ,
sizeof ( struct mpc83xx_rcw ) ) ;
if ( ! rcw_regs )
return - ENOMEM ;
ret = ! ( in_be32 ( & rcw_regs - > rcwhr ) & RCW_PCI_HOST ) ;
iounmap ( rcw_regs ) ;
return ret ;
}
2010-11-16 14:14:02 +01:00
static const struct platform_suspend_ops mpc83xx_suspend_ops = {
2007-10-09 12:37:13 -05:00
. valid = mpc83xx_suspend_valid ,
. begin = mpc83xx_suspend_begin ,
. enter = mpc83xx_suspend_enter ,
2009-12-10 21:00:53 +03:00
. end = mpc83xx_suspend_end ,
2007-10-09 12:37:13 -05:00
} ;
2011-05-18 11:19:24 -06:00
static struct of_device_id pmc_match [ ] ;
2011-02-22 19:59:54 -07:00
static int pmc_probe ( struct platform_device * ofdev )
2007-10-09 12:37:13 -05:00
{
2011-05-18 11:19:24 -06:00
const struct of_device_id * match ;
2010-04-13 16:12:29 -07:00
struct device_node * np = ofdev - > dev . of_node ;
2007-10-09 12:37:13 -05:00
struct resource res ;
2012-05-21 21:57:39 +02:00
const struct pmc_type * type ;
2007-10-09 12:37:13 -05:00
int ret = 0 ;
2011-05-18 11:19:24 -06:00
match = of_match_device ( pmc_match , & ofdev - > dev ) ;
if ( ! match )
2011-02-22 19:59:54 -07:00
return - EINVAL ;
2011-05-18 11:19:24 -06:00
type = match - > data ;
2011-02-22 19:59:54 -07:00
2007-10-09 12:37:13 -05:00
if ( ! of_device_is_available ( np ) )
return - ENODEV ;
has_deep_sleep = type - > has_deep_sleep ;
immrbase = get_immrbase ( ) ;
pmc_dev = ofdev ;
is_pci_agent = mpc83xx_is_pci_agent ( ) ;
if ( is_pci_agent < 0 )
return is_pci_agent ;
ret = of_address_to_resource ( np , 0 , & res ) ;
if ( ret )
return - ENODEV ;
pmc_irq = irq_of_parse_and_map ( np , 0 ) ;
if ( pmc_irq ! = NO_IRQ ) {
ret = request_irq ( pmc_irq , pmc_irq_handler , IRQF_SHARED ,
" pmc " , ofdev ) ;
if ( ret )
return - EBUSY ;
}
pmc_regs = ioremap ( res . start , sizeof ( struct mpc83xx_pmc ) ) ;
if ( ! pmc_regs ) {
ret = - ENOMEM ;
goto out ;
}
ret = of_address_to_resource ( np , 1 , & res ) ;
if ( ret ) {
ret = - ENODEV ;
goto out_pmc ;
}
clock_regs = ioremap ( res . start , sizeof ( struct mpc83xx_pmc ) ) ;
if ( ! clock_regs ) {
ret = - ENOMEM ;
goto out_pmc ;
}
2009-12-10 21:00:56 +03:00
if ( has_deep_sleep ) {
syscr_regs = ioremap ( immrbase + IMMR_SYSCR_OFFSET ,
sizeof ( * syscr_regs ) ) ;
if ( ! syscr_regs ) {
ret = - ENOMEM ;
goto out_syscr ;
}
}
2007-10-09 12:37:13 -05:00
if ( is_pci_agent )
mpc83xx_set_agent ( ) ;
suspend_set_ops ( & mpc83xx_suspend_ops ) ;
return 0 ;
2009-12-10 21:00:56 +03:00
out_syscr :
iounmap ( clock_regs ) ;
2007-10-09 12:37:13 -05:00
out_pmc :
iounmap ( pmc_regs ) ;
out :
if ( pmc_irq ! = NO_IRQ )
free_irq ( pmc_irq , ofdev ) ;
return ret ;
}
2010-07-22 15:52:34 -06:00
static int pmc_remove ( struct platform_device * ofdev )
2007-10-09 12:37:13 -05:00
{
return - EPERM ;
} ;
static struct pmc_type pmc_types [ ] = {
{
. has_deep_sleep = 1 ,
} ,
{
. has_deep_sleep = 0 ,
}
} ;
static struct of_device_id pmc_match [ ] = {
{
. compatible = " fsl,mpc8313-pmc " ,
. data = & pmc_types [ 0 ] ,
} ,
{
. compatible = " fsl,mpc8349-pmc " ,
. data = & pmc_types [ 1 ] ,
} ,
{ }
} ;
2011-02-22 19:59:54 -07:00
static struct platform_driver pmc_driver = {
2010-04-13 16:13:02 -07:00
. driver = {
. name = " mpc83xx-pmc " ,
. owner = THIS_MODULE ,
. of_match_table = pmc_match ,
} ,
2007-10-09 12:37:13 -05:00
. probe = pmc_probe ,
. remove = pmc_remove
} ;
static int pmc_init ( void )
{
2011-02-22 19:59:54 -07:00
return platform_driver_register ( & pmc_driver ) ;
2007-10-09 12:37:13 -05:00
}
module_init ( pmc_init ) ;