blob: 1a57021a6c9e65a7dea58b080e0b8d3009ec7702 [file] [log] [blame]
/*
* IMG Comet Power Management
*
* Copyright 2010 Imagination Technologies Ltd.
*
*/
#include <linux/kernel.h>
#include <linux/suspend.h>
#include <linux/completion.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <asm/core_reg.h>
#include <asm/coremem.h>
#include <asm/global_lock.h>
#include <asm/mmu_context.h>
#include <asm/soc-tz1090/bootprot.h>
#include <asm/soc-tz1090/defs.h>
#include <asm/soc-tz1090/pdc.h>
#include <asm/soc-tz1090/pm.h>
#include <asm/soc-tz1090/suspend.h>
#include <asm/suspend.h>
#include <asm/tbx.h>
/**
* comet_safe_mode() - Find whether the SoC is in SAFE mode.
*
* Returns: non zero if SAFE mode is active, 0 otherwise.
*/
static inline int comet_safe_mode(void)
{
u32 soc_bootstrap = readl(PDC_BASE_ADDR + PDC_SOC_BOOTSTRAP);
return soc_bootstrap & PDC_SOC_BOOTSTRAP_SAFE_MODE;
}
/**
* comet_pdc_restart() - Restart SoC using watchdog reset.
*
* Watchdog reset the SoC.
*/
void comet_pdc_restart(void)
{
writel(1, PDC_BASE_ADDR + PDC_WD_SW_RESET);
}
/**
* comet_pdc_power_off() - Power off the SoC if possible using EXT_POWER.
*
* If the SoC isn't in SAFE mode, power off the SoC using the PDC to control
* EXT_POWER.
*
* Returns: 0 on success, -errno on failure.
*/
int comet_pdc_power_off(void)
{
if (comet_safe_mode()) {
pr_info("SoC SAFE mode active, cannot power off SoC\n");
return -ENODEV;
}
writel(0, PDC_BASE_ADDR + PDC_SOC_POWER);
return 0;
}
#ifdef CONFIG_SUSPEND
/* Mask of CR_TOP_CLKENAB that is disabled in comet_pm_standby. */
#define CLKENAB_CLKOUT_MASK ((1 << CR_TOP_CLKOUT1_3_EN_BIT) | \
(1 << CR_TOP_CLKOUT0_3_EN_BIT))
int (*board_suspend)(suspend_state_t state);
void (*board_resume)(suspend_state_t state);
/**
* comet_pm_standby() - Go into standby mode.
*
* This copies the standby code into core memory, does some final power saving
* tasks and jumps into core memory, where the DDR memory is put into self
* refresh mode and clocked off, and the Meta is clocked down.
* When a wake interrupt is detected, the Meta is powered back up to it's
* previous state.
*/
static int comet_pm_standby(void)
{
struct metag_coremem_region *reg;
void (*standby_func)(unsigned int txmask);
unsigned int flags;
unsigned int perip_clk;
unsigned int top_clk;
int thread = hard_processor_id();
int ret = 0;
/* Get the address of some core memory */
reg = metag_coremem_alloc(METAG_COREMEM_ICACHE, metag_comet_standby_sz);
if (!reg) {
ret = -ENOMEM;
goto out_finish;
}
/* Copy the standby code into core memory */
standby_func = metag_coremem_push(reg, metag_comet_standby,
metag_comet_standby_sz);
if (!standby_func) {
ret = -ENOMEM;
goto out_finish_coremem;
}
__global_lock2(flags);
/* Disable all peripheral clocks */
perip_clk = readl(CR_PERIP_CLK_EN);
writel(0, CR_PERIP_CLK_EN);
/* Disable clk_out_0 and clk_out_1 */
/* keep value of top_clk so we can restore those bits */
top_clk = readl(CR_TOP_CLKENAB);
writel(top_clk & ~CLKENAB_CLKOUT_MASK, CR_TOP_CLKENAB);
__global_unlock2(flags);
/* Run the code from core memory, waking on trigger */
wmb();
standby_func(TBI_TRIG_BIT(TBID_SIGNUM_TR2(thread)));
__global_lock2(flags);
/* Re-enable clk_out_0 and clk_out_1 */
writel(top_clk, CR_TOP_CLKENAB);
/* Re-enable the peripheral clocks */
writel(perip_clk, CR_PERIP_CLK_EN);
__global_unlock2(flags);
/* finish using the core memory */
out_finish_coremem:
metag_coremem_free(reg);
out_finish:
return ret;
}
#ifdef CONFIG_METAG_SUSPEND_MEM
#ifdef CONFIG_COMET_SUSPEND_MEM_SAFE
/* if safe suspend support is enabled then we always allow suspend */
#define allow_suspend_mem() 1
#else
/* without safe suspend support we don't support suspend in SAFE mode */
#define allow_suspend_mem() (!comet_safe_mode())
#endif
/* jump buffer for use by suspend to RAM */
static struct metag_suspend_jmpbuf s2r_jmpbuf;
/**
* meta_pm_mem_resume() - Main resume entry point.
* @data: Data pointer to jump buffer.
*
* This is the main entry point back into the kernel on resume.
*
* Returns: 1 on error, otherwise never returns.
*/
static int meta_pm_mem_resume(void *data)
{
struct metag_suspend_jmpbuf *jmp = data;
#ifdef CONFIG_METAG_DSP
__core_reg_set(D0.8, 0);
#endif
setup_priv();
/* make sure the soft reset protected registers are cleared */
bootprot_resume_ram(PDC_BASE_ADDR + PDC_SOC_SW_PROT);
/* jump back to where we suspended from */
metag_resume_longjmp(jmp, 1);
/* should never get here, return to bootloader */
return 1;
}
/*
* Core suspend data that is needed during successful resume.
* used by comet_pm_do_suspend().
* used by comet_pm_resume().
*/
struct comet_pm_suspend_data {
struct metag_coremem_region *reg;
};
static int comet_pm_do_suspend(volatile struct comet_pm_suspend_data *d)
{
void (*suspend_func)(unsigned int);
int thread = hard_processor_id();
int ret = 0;
/* Get the address of some core memory */
d->reg = metag_coremem_alloc(METAG_COREMEM_ICACHE,
metag_comet_suspend_sz);
if (!d->reg) {
ret = -ENOMEM;
goto out_finish;
}
/* Copy the suspend code into core memory */
suspend_func = metag_coremem_push(d->reg, metag_comet_suspend,
metag_comet_suspend_sz);
if (!suspend_func) {
ret = -ENOMEM;
goto out_finish_coremem;
}
/* Run the code from core memory, waking on TR2 */
wmb();
suspend_func(TBI_TRIG_BIT(TBID_SIGNUM_TR2(thread)));
/*
* If we've got here then something went wrong, the power down (or reset
* in the case of SAFE mode) never happened.
*/
pr_err("Power down failed\n");
ret = -ENODEV;
/* finish using the core memory */
out_finish_coremem:
metag_coremem_free(d->reg);
out_finish:
return ret;
}
/* clean up after a successful suspend */
static noinline void comet_pm_resume(volatile struct comet_pm_suspend_data *d)
{
metag_coremem_free(d->reg);
}
static int comet_pm_suspend(int (*resume)(void *),
void *data,
volatile struct comet_pm_suspend_data *d)
{
int err;
/*
* Set up the PDC soft reset protected registers for resuming form
* suspend-to-RAM.
*/
bootprot_suspend_ram(PDC_BASE_ADDR + PDC_SOC_SW_PROT, resume, data);
/* Put DDR RAM into self refresh and switch off the power. */
err = comet_pm_do_suspend(d);
if (err) {
/*
* Something went wrong. we don't want to leave the magic values
* lying around though.
*/
bootprot_resume_ram(PDC_BASE_ADDR + PDC_SOC_SW_PROT);
}
return err;
}
static int metag_pm_mem(void)
{
struct mm_struct *mm = current->active_mm;
int ret;
volatile struct comet_pm_suspend_data d;
comet_prepare_reset();
ret = traps_save_context();
if (unlikely(ret))
goto err_traps;
/* Store the core registers, so after wakeup we'll jump back to here. */
if (!metag_suspend_setjmp(&s2r_jmpbuf)) {
/*
* Do SoC specifics to power switch core off safely.
* This won't return unless something went wrong.
*/
ret = comet_pm_suspend(meta_pm_mem_resume, &s2r_jmpbuf, &d);
if (unlikely(ret))
goto err_suspend;
} else {
comet_pm_resume(&d);
}
/* We've suspended, so resume safely */
switch_mmu(&init_mm, mm);
err_suspend:
traps_restore_context();
err_traps:
return ret;
}
#else /* CONFIG_METAG_SUSPEND_MEM */
#define allow_suspend_mem() 0
#define metag_pm_mem() (-EINVAL)
#endif
/* Begin the suspend process. */
static int comet_pm_begin(suspend_state_t state)
{
#ifdef CONFIG_COMET_SUSPEND_MEM_SAFE
if (state == PM_SUSPEND_MEM && comet_safe_mode())
pr_warn("WARNING: SoC SAFE mode active, Suspend to RAM will be faked using watchdog reset\n");
#endif
return 0;
}
/* Enter a suspend mode. */
static int comet_pm_enter(suspend_state_t state)
{
int ret;
if (board_suspend) {
ret = board_suspend(state);
if (ret)
return ret;
}
switch (state) {
case PM_SUSPEND_STANDBY:
ret = comet_pm_standby();
break;
case PM_SUSPEND_MEM:
ret = metag_pm_mem();
break;
default:
ret = -EINVAL;
break;
}
if (board_resume)
board_resume(state);
return ret;
}
/* Specify which suspend modes are valid. */
static int comet_pm_valid(suspend_state_t state)
{
return state == PM_SUSPEND_STANDBY ||
(state == PM_SUSPEND_MEM && allow_suspend_mem());
}
static struct platform_suspend_ops comet_pm_ops = {
.begin = comet_pm_begin,
.enter = comet_pm_enter,
.valid = comet_pm_valid,
};
static int __init comet_pm_init(void)
{
suspend_set_ops(&comet_pm_ops);
return 0;
}
device_initcall(comet_pm_init);
#endif /* CONFIG_SUSPEND */