diff options
author | Alex Van Brunt <avanbrunt@nvidia.com> | 2013-06-14 12:54:14 -0700 |
---|---|---|
committer | Tom Cherry <tcherry@nvidia.com> | 2014-01-21 15:13:56 -0800 |
commit | 3b5756ad27e551cd3f769b86439e52de067ee9e3 (patch) | |
tree | 3425902b1a41c118cf23f74a98df633b6dc16caa /drivers/cpuidle/cpuidle-denver.c | |
parent | 2f7ca673c1fb5e8ca086418d0b8c4e7ee22b09f6 (diff) |
cpuidle: denver: cpuidle driver for Denver
For now, this only support Denver C states.
Use the device tree to specify the cpuidle parameters for C sates.
Bug 1292618
Change-Id: I9d2e393552c037f44b1c716e22b1f1131f84627f
Signed-off-by: Alex Van Brunt <avanbrunt@nvidia.com>
Signed-off-by: Peng Du <pdu@nvidia.com>
Reviewed-on: http://git-master/r/266198
Diffstat (limited to 'drivers/cpuidle/cpuidle-denver.c')
-rw-r--r-- | drivers/cpuidle/cpuidle-denver.c | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/drivers/cpuidle/cpuidle-denver.c b/drivers/cpuidle/cpuidle-denver.c new file mode 100644 index 000000000000..95adbdca46c6 --- /dev/null +++ b/drivers/cpuidle/cpuidle-denver.c @@ -0,0 +1,150 @@ +/* + * drivers/cpuidle/cpuidle-denver.c + * + * Copyright (C) 2013 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> + +void tegra_pd_in_idle(bool enable) {} + +static int pmstate_map[CPUIDLE_STATE_MAX] = { -1 }; + +static int denver_enter_c_state( + struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + uintptr_t pmstate = pmstate_map[index]; + BUG_ON(pmstate < 0); + + asm volatile("msr actlr_el1, %0\n" : : "r" (pmstate)); + asm volatile("wfi\n"); + + local_irq_enable(); + + return 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; + u32 state_count = 0; + u32 prop; + + 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; + + /* Map index to the actual LP state */ + if (of_property_read_u32(child, "pmstate", &prop) != 0) + continue; + pmstate_map[state_count] = prop; + + 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; + } + } + + 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); |