summaryrefslogtreecommitdiff
path: root/drivers/cpuidle
diff options
context:
space:
mode:
authorAlex Van Brunt <avanbrunt@nvidia.com>2013-06-14 12:54:14 -0700
committerTom Cherry <tcherry@nvidia.com>2014-01-21 15:13:56 -0800
commit3b5756ad27e551cd3f769b86439e52de067ee9e3 (patch)
tree3425902b1a41c118cf23f74a98df633b6dc16caa /drivers/cpuidle
parent2f7ca673c1fb5e8ca086418d0b8c4e7ee22b09f6 (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')
-rw-r--r--drivers/cpuidle/Kconfig8
-rw-r--r--drivers/cpuidle/Makefile1
-rw-r--r--drivers/cpuidle/cpuidle-denver.c150
3 files changed, 159 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index 077b07066732..a6f8b693abe5 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -34,6 +34,14 @@ config CPU_IDLE_CALXEDA
depends on ARCH_HIGHBANK
help
Select this to enable cpuidle on Calxeda processors.
+
+config CPU_IDLE_DENVER
+ bool "CPU Idle Driver for NVIDIA Denver processors"
+ depends on DENVER_CPU
+ default y
+ help
+ Select this to enable cpuidle on NVIDIA Denver processors.
+
endif
config ARCH_NEEDS_CPU_IDLE_COUPLED
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 6776a31f7f2a..152fd77d2918 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o
obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o
obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o
+obj-${CONFIG_CPU_IDLE_DENVER} += cpuidle-denver.o
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);