blob: 24d0dfcc246c92c07641e94930a40e38d737aa80 [file] [log] [blame]
/*
* drivers/misc/idle_test_t132.c
*
* Copyright (C) 2014 NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ktime.h>
#include <linux/interrupt.h>
#include <linux/suspend.h>
#include <linux/tick.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/tick.h>
#include "../../kernel/irq/internals.h"
#include "../../arch/arm64/mach-tegra/pm-tegra132.h"
#include "../../arch/arm64/mach-tegra/denver-knobs.h"
#define DEBUG_HOOKS 0
#if DEBUG_HOOKS
#define DBG_PRINT(fmt, arg...) pr_err(fmt, ## arg)
#else
#define DBG_PRINT(fmt, arg...)
#endif
#define POWER_BACKDOOR_I2C_CC4_VOLTAGE_DOWN 0x14
#define POWER_BACKDOOR_I2C_CC4_VOLTAGE_UP 0x15
static struct dentry *dfs_dir;
static long pmstate = T132_CLUSTER_C4;
static void tegra132_do_idle(ulong pmstate)
{
asm volatile(
" msr actlr_el1, %0\n"
" wfi\n"
:
: "r" (pmstate));
}
static void tegra132_set_voltage(long voltage, ulong power_state)
{
asm volatile(
" sys 0, c11, c0, 1, %0\n"
" sys 0, c11, c0, 0, %1\n"
:
: "r" (voltage), "r" (power_state));
}
void suspend_all_device_irqs(void)
{
struct irq_desc *desc;
int irq;
for_each_irq_desc(irq, desc) {
unsigned long flags;
raw_spin_lock_irqsave(&desc->lock, flags);
__disable_irq(desc, irq, false);
desc->istate |= IRQS_SUSPENDED;
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
for_each_irq_desc(irq, desc)
if (desc->istate & IRQS_SUSPENDED)
synchronize_irq(irq);
}
void resume_all_device_irqs(void)
{
struct irq_desc *desc;
int irq;
for_each_irq_desc(irq, desc) {
unsigned long flags;
raw_spin_lock_irqsave(&desc->lock, flags);
__enable_irq(desc, irq, true);
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
}
static int idle_write(void *data, u64 val)
{
unsigned long timer_interval_us = 0;
ktime_t time, interval, sleep;
int cpu = smp_processor_id();
timer_interval_us = (ulong)val;
preempt_disable();
suspend_all_device_irqs();
tick_nohz_idle_enter();
stop_critical_timings();
local_fiq_disable();
local_irq_disable();
/* Disable bgallow */
denver_set_bg_allowed(0, false);
denver_set_bg_allowed(1, false);
interval = ktime_set(0, (NSEC_PER_USEC * timer_interval_us));
time = ktime_get();
sleep = ktime_add(time, interval);
tick_program_event(sleep, true);
tegra132_do_idle(pmstate);
sleep = ktime_sub(ktime_get(), time);
time = ktime_sub(sleep, interval);
//trace_printk("idle: %lld, exit latency: %lld\n", sleep.tv64, time.tv64);
/* Re-enable bgallow */
denver_set_bg_allowed(1, true);
denver_set_bg_allowed(0, true);
local_fiq_enable();
local_irq_enable();
start_critical_timings();
tick_nohz_idle_exit();
resume_all_device_irqs();
preempt_enable_no_resched();
return 0;
}
/* voltage write file operation */
static int voltage_write(void *data, u64 val)
{
long retention_vol = (long)val;
DBG_PRINT("retention_vol is %ld\n", retention_vol);
tegra132_set_voltage(retention_vol,
POWER_BACKDOOR_I2C_CC4_VOLTAGE_DOWN);
return 0;
}
static int pmstate_write(void *data, u64 val)
{
pmstate = (long)val;
DBG_PRINT("pmstate is %ld\n", pmstate);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(idle_fops, NULL, idle_write, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(voltage_fops, NULL, voltage_write, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(pmstate_fops, NULL, pmstate_write, "%llu\n");
static int __init init_debug(void)
{
struct dentry *dfs_file;
int filevalue;
dfs_dir = debugfs_create_dir("denver_idle_test", NULL);
dfs_file = debugfs_create_file("idlecpu", 0644, dfs_dir,
&filevalue, &idle_fops);
if (!dfs_file) {
DBG_PRINT("error creating idlecpu file");
return -ENODEV;
}
dfs_file = debugfs_create_file("voltage", 0644, dfs_dir,
&filevalue, &voltage_fops);
if (!dfs_file) {
DBG_PRINT("error creating voltage file");
return -ENODEV;
}
dfs_file = debugfs_create_file("pmstate", 0644, dfs_dir,
&filevalue, &pmstate_fops);
if (!dfs_file) {
DBG_PRINT("error creating pmstate file");
return -ENODEV;
}
return 0;
}
module_init(init_debug);
static void __exit exit_debug(void)
{
debugfs_remove_recursive(dfs_dir);
}
module_exit(exit_debug);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kernel HOOKS");