blob: c6c9b49cf2997653360a609f5cc8539c957e9c3d [file] [log] [blame]
/*
* drivers/cpuidle/cpuidle-denver.c
*
* Copyright (C) 2013-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/kernel.h>
#include <linux/cpuidle.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/debugfs.h>
#include <linux/tegra-soc.h>
#include <linux/tegra-fuse.h>
void tegra_pd_in_idle(bool enable) {}
static u32 pmstate_map[CPUIDLE_STATE_MAX] = { -1 };
static int denver_enter_c_state(
struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
asm volatile("msr actlr_el1, %0\n" : : "r" (pmstate_map[index]));
asm volatile("wfi\n");
local_irq_enable();
return pmstate_map[index] ? index : 0;
}
static struct cpuidle_driver denver_idle_driver = {
.name = "denver_idle",
.owner = THIS_MODULE,
};
static int __init denver_power_states_init(void)
{
struct device_node *of_states;
struct device_node *child;
struct cpuidle_state *state;
const char *name;
struct dentry *cpuidle_denver_dir;
struct dentry *idle_node;
u32 state_count = 0;
u32 prop;
cpuidle_denver_dir = debugfs_create_dir("cpuidle_denver", NULL);
if (!cpuidle_denver_dir) {
pr_err("%s: Couldn't create the \"cpuidle_denver\" debugfs "
"node.\n",
__func__);
return -1;
}
of_states = of_find_node_by_name(NULL, "denver_power_states");
if (!of_states)
return -ENODEV;
for_each_child_of_node(of_states, child) {
state = &denver_idle_driver.states[state_count];
if (of_property_read_string(child, "state-name", &name))
continue;
snprintf(state->name, CPUIDLE_NAME_LEN, child->name);
snprintf(state->desc, CPUIDLE_DESC_LEN, name);
if (of_property_read_u32(child, "latency", &prop) == 0)
state->exit_latency = prop;
if (of_property_read_u32(
child, "residency", &prop) == 0) {
state->flags = CPUIDLE_FLAG_TIME_VALID;
state->target_residency = prop;
}
if (of_property_read_u32(child, "power", &prop) == 0)
state->exit_latency = prop;
state->enter = denver_enter_c_state;
/* Bringup all states except clock gating in disabled mode */
if (of_property_read_u32(child, "pmstate", &prop) == 0) {
if (prop == 0)
state->disabled = false;
else
state->disabled = true;
if ((prop == 9) && (tegra_revision == TEGRA_REVISION_A01))
prop = 0;
} else
continue;
/* Map index to the actual LP state */
pmstate_map[state_count] = prop;
/* Create a debugfs node for the idle state */
idle_node = debugfs_create_dir(child->name,
cpuidle_denver_dir);
if (!idle_node) {
return -1;
}
if (!debugfs_create_x32("pmstate", S_IRUGO | S_IWUSR, idle_node,
&pmstate_map[state_count])) {
pr_err("%s: Couldn't create the pmstate debugfs node"
"for %s.\n",__func__, child->name);
return -1;
}
state_count++;
}
denver_idle_driver.state_count = state_count;
return cpuidle_register_driver(&denver_idle_driver);
}
static int __init denver_cpuidle_devices_init(void)
{
struct device_node *cpu = NULL;
struct device_node *of_states;
struct cpuidle_device *dev;
u64 cpu_reg;
for_each_node_by_type(cpu, "cpu") {
if (!of_device_is_compatible(cpu, "nvidia,denver"))
continue;
of_states = of_parse_phandle(cpu, "power-states", 0);
if (!of_states || !of_device_is_compatible(
of_states, "nvidia,denver"))
continue;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
BUG_ON(of_property_read_u64(cpu, "reg", &cpu_reg));
dev->cpu = (unsigned long)cpu_reg;
dev->state_count = denver_idle_driver.state_count;
if (cpuidle_register_device(dev)) {
pr_err("%s: failed to register idle device\n",
cpu->full_name);
kfree(dev);
return -EIO;
}
#ifndef CONFIG_SMP
break;
#endif
}
return 0;
}
static int __init denver_cpuidle_init(void)
{
int e;
e = denver_power_states_init();
if (e) {
pr_err("%s: failed to init cpuidle power states.\n", __func__);
return e;
}
e = denver_cpuidle_devices_init();
if (e) {
pr_err("%s: failed to init cpuidle devices.\n", __func__);
return e;
}
return 0;
}
device_initcall(denver_cpuidle_init);