blob: 6a4911135a50cb908d45c65f2e850184f42fd2ec [file] [log] [blame]
/*
* arch/arm/mach-feroceon-kw/cpuidle.c
*
* CPU idle Marvell Kirkwood SoCs
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*
* The cpu idle uses wait-for-interrupt and DDR self refresh in order
* to implement two idle states -
* #1 wait-for-interrupt
* #2 wait-for-interrupt and DDR self refresh
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/cpuidle.h>
#include <linux/io.h>
#include <asm/proc-fns.h>
#include <linux/proc_fs.h>
#include <config/mvSysHwConfig.h>
#include "mvCommon.h"
#include "mvOs.h"
#define CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE
#ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE
#include "ctrlEnv/mvCtrlEnvLib.h"
#endif
#define KIRKWOOD_MAX_STATES 2
extern void mv_kw2_cpu_idle_enter(void);
#ifdef CONFIG_MV_PMU_PROC
extern struct proc_dir_entry *mv_pm_proc_entry;
struct proc_dir_entry *cpu_idle_proc;
#endif /* CONFIG_MV_PMU_PROC */
static struct cpuidle_device *kirkwood_cpu_idle_device;
static struct cpuidle_driver kirkwood_idle_driver = {
.name = "kirkwood_idle",
.owner = THIS_MODULE,
};
static DEFINE_PER_CPU(struct cpuidle_device, kirkwood_cpuidle_device);
static int device_registered;
int support_dram_self_refresh = 0;
/* Actual code that puts the SoC in different idle states */
static int kirkwood_enter_idle(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
struct timeval before, after;
int idle_time;
MV_BOOL pwr_save_on;
#ifdef CONFIG_JTAG_DEBUG
local_irq_enable();
return 0;
#endif
#ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE
/* Set CPU to fast frequency */
pwr_save_on = mvCtrlIsPwrSaveOn();
mvCtrlPwrSaveOff();
#endif /* CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE */
local_irq_disable();
do_gettimeofday(&before);
if (state == &dev->states[0]) {
/* Wait for interrupt state */
cpu_do_idle();
} else if (state == &dev->states[1]) {
#ifdef CONFIG_MV_PM_IDLE_WFI_SR
/*
* Following write will put DDR in self refresh.
* Note that we have 256 cycles before DDR puts it
* self in self-refresh, so the wait-for-interrupt
* call afterwards won't get the DDR from self refresh
* mode.
*/
writel(0x7, DDR_OPERATION_BASE);
cpu_do_idle();
#endif
#ifdef CONFIG_MV_PM_IDLE_DEEPIDLE_SR
/*
* Following write will put DDR in self refresh.
* Note that we have 256 cycles before DDR puts it
* self in self-refresh, so the wait-for-interrupt
* call afterwards won't get the DDR from self refresh
* mode.
*/
mv_kw2_cpu_idle_enter();
#endif
}
do_gettimeofday(&after);
local_irq_enable();
#ifdef CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE
/* Set CPU to fast frequency */
if (pwr_save_on == MV_TRUE)
mvCtrlPwrSaveOn();
#endif /* CONFIG_CPUFREQ_TOGGLE_ON_DEEPIDLE */
idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
(after.tv_usec - before.tv_usec);
return idle_time;
}
#ifdef CONFIG_MV_PMU_PROC
static int mv_cpu_idle_write(struct file *file, const char *buffer,
unsigned long count, void *data)
{
MV_U32 regs[4];
/* Reading / Writing from system controller internal registers */
if (!strncmp (buffer, "enable", strlen("enable"))) {
if(device_registered == 0) {
device_registered = 1;
if (cpuidle_register_device(kirkwood_cpu_idle_device)) {
printk(KERN_ERR "mv_cpu_idle_write: Failed registering\n");
return -EIO;
}
}
cpuidle_enable_device(kirkwood_cpu_idle_device);
} else if (!strncmp (buffer, "disable", strlen("disable"))) {
cpuidle_disable_device(kirkwood_cpu_idle_device);
} else if (!strncmp (buffer, "test", strlen("test"))) {
buffer += strlen("test ");
/* Store Interrupt mask registers. */
regs[0] = MV_REG_READ(MV_IRQ_MASK_LOW_REG);
regs[1] = MV_REG_READ(MV_IRQ_MASK_HIGH_REG);
regs[2] = MV_REG_READ(MV_IRQ_MASK_ERROR_REG);
/* Disable all interrupts . */
MV_REG_WRITE(MV_IRQ_MASK_LOW_REG, 0x0);
MV_REG_WRITE(MV_IRQ_MASK_HIGH_REG, 0x0);
MV_REG_WRITE(MV_IRQ_MASK_ERROR_REG, 0x0);
/* Enable only the UART interrupt. */
MV_REG_BIT_SET(MV_IRQ_MASK_HIGH_REG, 1 << (UART_IRQ_NUM(0) - 32));
if (!strncmp (buffer, "idle", strlen("idle"))) {
printk(KERN_INFO "Press any key to leave deep idle:");
mv_kw2_cpu_idle_enter();
} else if (!strncmp (buffer, "wfi", strlen("wfi"))) {
printk(KERN_INFO "Press any key to leave WFI:");
cpu_do_idle();
}
/* Restore Interrupt mask registers. */
MV_REG_WRITE(MV_IRQ_MASK_LOW_REG, regs[0]);
MV_REG_WRITE(MV_IRQ_MASK_HIGH_REG, regs[1]);
MV_REG_WRITE(MV_IRQ_MASK_ERROR_REG, regs[2]);
} else if (!strncmp (buffer, "sr_enable", strlen("sr_enable"))) {
local_irq_disable();
support_dram_self_refresh = 1;
local_irq_enable();
} else if (!strncmp (buffer, "sr_disable", strlen("sr_disable"))) {
local_irq_disable();
support_dram_self_refresh = 0;
local_irq_enable();
}
return count;
}
static int mv_cpu_idle_read(char *buffer, char **buffer_location, off_t offset,
int buffer_length, int *zero, void *ptr)
{
if (offset > 0)
return 0;
return sprintf(buffer, "enable - Enable CPU Idle framework.\n"
"disable - Disable CPU idle framework.\n"
"test (idle / wfi) - Manually enter CPU Idle state, exit by key stroke (DEBUG ONLY).\n"
"sr_enable / sr_disable - Enable / Disable DRAM Self-Refresh when entering deel-idle.\n");
}
#endif /* CONFIG_MV_PMU_PROC */
/* Initialize CPU idle by registering the idle states */
static int kw_cpuidle_probe(struct platform_device *pdev)
{
struct cpuidle_device *device;
cpuidle_register_driver(&kirkwood_idle_driver);
device = &per_cpu(kirkwood_cpuidle_device, smp_processor_id());
device->state_count = KIRKWOOD_MAX_STATES;
/* Wait for interrupt state */
device->states[0].enter = kirkwood_enter_idle;
device->states[0].exit_latency = 1;
device->states[0].target_residency = 100;
device->states[0].flags = CPUIDLE_FLAG_TIME_VALID;
strcpy(device->states[0].name, "WFI");
strcpy(device->states[0].desc, "Wait for interrupt");
#ifdef CONFIG_MV_PM_IDLE_WFI_SR
/* Wait for interrupt and DDR self refresh state */
device->states[1].enter = kirkwood_enter_idle;
device->states[1].exit_latency = 10;
device->states[1].target_residency = 1000;
device->states[1].flags = CPUIDLE_FLAG_TIME_VALID;
strcpy(device->states[1].name, "DDR SR");
strcpy(device->states[1].desc, "WFI and DDR Self Refresh");
#endif
#ifdef CONFIG_MV_PM_IDLE_DEEPIDLE_SR
/* CPU Deep Idle state */
device->states[1].enter = kirkwood_enter_idle;
device->states[1].exit_latency = 10;
device->states[1].target_residency = 5000;
device->states[1].flags = CPUIDLE_FLAG_TIME_VALID;
strcpy(device->states[1].name, "DEEP IDLE");
strcpy(device->states[1].desc, "CPU Deep Idle");
#endif
#if 0
if (cpuidle_register_device(device)) {
printk(KERN_ERR "kirkwood_init_cpuidle: Failed registering\n");
return -EIO;
}
#endif
kirkwood_cpu_idle_device = device;
#ifdef CONFIG_MV_PMU_PROC
/* Create proc entry. */
cpu_idle_proc = create_proc_entry("cpu_idle", 0666, mv_pm_proc_entry);
cpu_idle_proc->read_proc = mv_cpu_idle_read;
cpu_idle_proc->write_proc = mv_cpu_idle_write;
cpu_idle_proc->nlink = 1;
#endif /* CONFIG_MV_PMU_PROC */
return 0;
}
static int kw_cpuidle_remove(struct platform_device *pdev)
{
#ifdef CONFIG_MV_PMU_PROC
remove_proc_entry("cpu_idle", cpu_idle_proc);
#endif
cpuidle_unregister_device(kirkwood_cpu_idle_device);
return 0;
}
struct platform_driver kw_cpuidle_driver = {
.probe = kw_cpuidle_probe,
.remove = kw_cpuidle_remove,
.driver = {
.name = "kw_cpuidle",
.owner = THIS_MODULE,
},
};
static int __init kw_cpuidle_drv_init(void)
{
if (MV_6601_DEV_ID == mvCtrlModelGet())
return 0;
device_registered = 0;
return platform_driver_register(&kw_cpuidle_driver);
}
static void __exit kw_cpuidle_drv_cleanup(void)
{
platform_driver_unregister(&kw_cpuidle_driver);
}
module_init(kw_cpuidle_drv_init);
module_exit(kw_cpuidle_drv_cleanup);