satip-axe/kernel/kernel/power/hom.c

272 lines
4.9 KiB
C

/*
* kernel/power/hom.c - Hibernation on Memory core functionality.
*
* Copyright (c) 2010 STMicroelectronics
* Author: Francesco M. Virlinzi <francesco.virlinzi@st.com>
*
* This file is released under the GPLv2
*
* This files reuses most of the code used to manage
* system wide suspend and system wide hibernation
* to support a new kind of system wide power operation:
*
* Hibernation on Memory where the
* - Chip is off
* - DRAM is in self-refresh mode
*
*/
#include <linux/hom.h>
#include <linux/freezer.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/cpu.h>
#include <linux/fs.h>
#include <linux/syscalls.h>
#include <asm-generic/sections.h>
#include "power.h"
static struct platform_hom_ops *hom_ops;
/**
* hom_set_ops - Set the global power method table.
* @ops: Pointer to ops structure.
*/
int hom_set_ops(struct platform_hom_ops *ops)
{
pr_debug("%s\n", __func__);
if (!ops)
return -EINVAL;
mutex_lock(&pm_mutex);
hom_ops = ops;
mutex_unlock(&pm_mutex);
return 0;
}
EXPORT_SYMBOL(hom_set_ops);
static inline int platform_begin(void)
{
if (hom_ops->begin)
return hom_ops->begin();
return 0;
}
static inline int platform_prepare(void)
{
if (hom_ops->prepare)
return hom_ops->prepare();
return 0;
}
#ifndef CONFIG_HMEM_MODE_TEST
static inline int platform_enter(void)
{
if (hom_ops->enter)
return hom_ops->enter();
return 0;
}
#else
static inline int platform_enter(void) { return 0; }
#endif
static inline int platform_complete(void)
{
if (hom_ops->complete)
return hom_ops->complete();
return 0;
}
static inline int platform_end(void)
{
if (hom_ops->end)
return hom_ops->end();
return 0;
}
static int prepare_processes(void)
{
int error = 0;
if (freeze_processes()) {
error = -EBUSY;
thaw_processes();
}
return error;
}
/**
* hom_prepare - Do prep work before entering low-power state.
*
* This is common code that is called for each state that we're entering.
* Run suspend notifiers, allocate a console and stop all processes.
*/
static int hom_prepare(void)
{
int error;
pm_prepare_console();
error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE);
if (error)
goto Exit;
error = usermodehelper_disable();
if (error)
goto Exit;
pr_info("PM: Syncing filesystems ... ");
sys_sync();
pr_info("done.\n");
error = prepare_processes();
if (!error)
return 0;
usermodehelper_enable();
Exit:
pm_notifier_call_chain(PM_POST_HIBERNATION);
pm_restore_console();
return error;
}
/**
* hom_freeze_devices_and_enter -
* freeze devices and enter in hibernation on memory
*/
static int hibernation_on_memory_enter(void)
{
int error = 0;
unsigned long pe_counter;
if (!hom_ops)
return -ENOSYS;
pr_debug("[STM]:[PM]: platform_begin\n");
error = platform_begin();
if (error)
goto Close;
suspend_console();
pr_debug("[STM]:[PM]: Suspend devices\n");
error = dpm_suspend_start(PMSG_FREEZE);
if (error)
goto Resume_devices;
pr_debug("[STM]:[PM]: Suspend devices (noirq)\n");
error = dpm_suspend_noirq(PMSG_FREEZE);
if (error)
goto Resume_devices_noirq;
error = disable_nonboot_cpus();
if (error)
goto Enable_cpus;
local_irq_disable();
pr_debug("[STM]:[PM]: Suspend sysdevices\n");
error = sysdev_suspend(PMSG_FREEZE);
if (error)
goto Enable_irqs;
pr_debug("[STM]:[PM]: platform_prepare\n");
error = platform_prepare();
if (error) {
pr_err("[STM]:[PM]: platform_prepare has refused the HoM\n");
goto Skip_enter;
}
pr_debug("[STM]:[PM]: platform_enter\n");
pe_counter = preempt_count();
platform_enter();
BUG_ON(pe_counter != preempt_count());
Skip_enter:
pr_debug("[STM]:[PM]: platform_complete\n");
platform_complete();
pr_debug("[STM]:[PM]: Resumed sysdevices\n");
sysdev_resume();
Enable_irqs:
local_irq_enable();
Enable_cpus:
enable_nonboot_cpus();
Resume_devices_noirq:
pr_debug("[STM]:[PM]: Resume devices (noirq)\n");
dpm_resume_noirq(PMSG_RESTORE);
Resume_devices:
pr_debug("[STM]:[PM]: Resume devices\n");
dpm_resume_end(PMSG_RESTORE);
resume_console();
Close:
pr_debug("[STM]:[PM]: platform_end\n");
platform_end();
pr_debug("[STM]:[PM]: exit\n");
return error;
}
/**
* hom_finish - Do final work before exiting suspend sequence.
*
* Call platform code to clean up, restart processes, and free the
* console that we've allocated. This is not called for suspend-to-disk.
*/
static void hom_finish(void)
{
pr_debug("%s\n", __func__);
thaw_processes();
usermodehelper_enable();
pm_notifier_call_chain(PM_POST_HIBERNATION);
pm_restore_console();
}
static int hom_enter(void)
{
int err;
mutex_lock(&pm_mutex);
err = hom_prepare();
if (err)
goto Finish;
err = hibernation_on_memory_enter();
hom_finish();
Finish:
mutex_unlock(&pm_mutex);
pr_info("[STM]:[PM]: HoM End...\n");
return err;
}
int hibernate_on_memory(void)
{
return hom_enter();
}
EXPORT_SYMBOL(hibernate_on_memory);