aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@linaro.org>2014-05-30 11:14:25 +0100
committerMark Brown <broonie@linaro.org>2014-05-30 11:14:25 +0100
commit1dd270862f84e52fd00ad3e0c6af5ca6c8895a50 (patch)
tree0ddc3f89349c3d57f20e6ed9292b3fe2f4b01870 /drivers
parentb21788e7092677d023988a197534b31df4566593 (diff)
parent6fe70bb73ef2fa814d88220c38f2ca07342aea75 (diff)
Merge branch 'linux-linaro-lsk' into linux-linaro-lsk-android
Diffstat (limited to 'drivers')
-rw-r--r--drivers/cpuidle/Kconfig14
-rw-r--r--drivers/cpuidle/Kconfig.arm6413
-rw-r--r--drivers/cpuidle/Makefile6
-rw-r--r--drivers/cpuidle/cpuidle-arm64.c159
-rw-r--r--drivers/cpuidle/of_idle_states.c274
-rw-r--r--drivers/cpuidle/of_idle_states.h8
6 files changed, 474 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index c4cc27e5c8a5..842d7ba83101 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -31,6 +31,15 @@ config CPU_IDLE_GOV_MENU
config ARCH_NEEDS_CPU_IDLE_COUPLED
def_bool n
+config OF_IDLE_STATES
+ bool "Idle states DT support"
+ depends on ARM || ARM64
+ default n
+ help
+ Allows the CPU idle framework to initialize CPU idle drivers
+ state data by using DT provided nodes compliant with idle states
+ device tree bindings.
+
if CPU_IDLE
config CPU_IDLE_CALXEDA
@@ -39,4 +48,9 @@ config CPU_IDLE_CALXEDA
help
Select this to enable cpuidle on Calxeda processors.
+menu "ARM64 CPU Idle Drivers"
+depends on ARM64
+source "drivers/cpuidle/Kconfig.arm64"
+endmenu
+
endif
diff --git a/drivers/cpuidle/Kconfig.arm64 b/drivers/cpuidle/Kconfig.arm64
new file mode 100644
index 000000000000..b83612c67e6d
--- /dev/null
+++ b/drivers/cpuidle/Kconfig.arm64
@@ -0,0 +1,13 @@
+#
+# ARM64 CPU Idle drivers
+#
+
+config ARM64_CPUIDLE
+ bool "Generic ARM64 CPU idle Driver"
+ select OF_IDLE_STATES
+ help
+ Select this to enable generic cpuidle driver for ARM v8.
+ It provides a generic idle driver whose idle states are configured
+ at run-time through DT nodes. The CPUidle suspend backend is
+ initialized by the device tree parsing code on matching the entry
+ method to the respective CPU operations.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 7d8256a5ea97..2d97bcfecd00 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -5,5 +5,11 @@
obj-y += cpuidle.o driver.o governor.o sysfs.o governors/
obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o
obj-$(CONFIG_BIG_LITTLE) += arm_big_little.o
+obj-$(CONFIG_OF_IDLE_STATES) += of_idle_states.o
+
obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o
obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o
+
+###############################################################################
+# ARM64 drivers
+obj-$(CONFIG_ARM64_CPUIDLE) += cpuidle-arm64.o
diff --git a/drivers/cpuidle/cpuidle-arm64.c b/drivers/cpuidle/cpuidle-arm64.c
new file mode 100644
index 000000000000..2cfde6ce3086
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-arm64.c
@@ -0,0 +1,159 @@
+/*
+ * ARM64 generic CPU idle driver.
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/cpuidle.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_pm.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <asm/psci.h>
+#include <asm/suspend.h>
+
+#include "of_idle_states.h"
+
+typedef int (*suspend_init_fn)(struct cpuidle_driver *,
+ struct device_node *[]);
+
+struct cpu_suspend_ops {
+ const char *id;
+ suspend_init_fn init_fn;
+};
+
+static const struct cpu_suspend_ops suspend_operations[] __initconst = {
+ {"arm,psci", psci_dt_register_idle_states},
+ {}
+};
+
+static __init const struct cpu_suspend_ops *get_suspend_ops(const char *str)
+{
+ int i;
+
+ if (!str)
+ return NULL;
+
+ for (i = 0; suspend_operations[i].id; i++)
+ if (!strcmp(suspend_operations[i].id, str))
+ return &suspend_operations[i];
+
+ return NULL;
+}
+
+/*
+ * arm_enter_idle_state - Programs CPU to enter the specified state
+ *
+ * @dev: cpuidle device
+ * @drv: cpuidle driver
+ * @idx: state index
+ *
+ * Called from the CPUidle framework to program the device to the
+ * specified target state selected by the governor.
+ */
+static int arm_enter_idle_state(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int idx)
+{
+ int ret;
+
+ if (!idx) {
+ cpu_do_idle();
+ return idx;
+ }
+
+ cpu_pm_enter();
+ /*
+ * Pass idle state index to cpu_suspend which in turn will call
+ * the CPU ops suspend protocol with idle index as a parameter.
+ *
+ * Some states would not require context to be saved and flushed
+ * to DRAM, so calling cpu_suspend would not be stricly necessary.
+ * When power domains specifications for ARM CPUs are finalized then
+ * this code can be optimized to prevent saving registers if not
+ * needed.
+ */
+ ret = cpu_suspend(idx);
+
+ cpu_pm_exit();
+
+ return ret ? -1 : idx;
+}
+
+struct cpuidle_driver arm64_idle_driver = {
+ .name = "arm64_idle",
+ .owner = THIS_MODULE,
+};
+
+static struct device_node *state_nodes[CPUIDLE_STATE_MAX] __initdata;
+
+/*
+ * arm64_idle_init
+ *
+ * Registers the arm64 specific cpuidle driver with the cpuidle
+ * framework. It relies on core code to parse the idle states
+ * and initialize them using driver data structures accordingly.
+ */
+static int __init arm64_idle_init(void)
+{
+ int i, ret;
+ const char *entry_method;
+ struct device_node *idle_states_node;
+ const struct cpu_suspend_ops *suspend_init;
+ struct cpuidle_driver *drv = &arm64_idle_driver;
+
+ idle_states_node = of_find_node_by_path("/cpus/idle-states");
+ if (!idle_states_node)
+ return -ENOENT;
+
+ if (of_property_read_string(idle_states_node, "entry-method",
+ &entry_method)) {
+ pr_warn(" * %s missing entry-method property\n",
+ idle_states_node->full_name);
+ of_node_put(idle_states_node);
+ return -EOPNOTSUPP;
+ }
+
+ suspend_init = get_suspend_ops(entry_method);
+ if (!suspend_init) {
+ pr_warn("Missing suspend initializer\n");
+ of_node_put(idle_states_node);
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * State at index 0 is standby wfi and considered standard
+ * on all ARM platforms. If in some platforms simple wfi
+ * can't be used as "state 0", DT bindings must be implemented
+ * to work around this issue and allow installing a special
+ * handler for idle state index 0.
+ */
+ drv->states[0].exit_latency = 1;
+ drv->states[0].target_residency = 1;
+ drv->states[0].flags = CPUIDLE_FLAG_TIME_VALID;
+ strncpy(drv->states[0].name, "ARM WFI", CPUIDLE_NAME_LEN);
+ strncpy(drv->states[0].desc, "ARM WFI", CPUIDLE_DESC_LEN);
+
+ drv->cpumask = (struct cpumask *) cpu_possible_mask;
+ /*
+ * Start at index 1, request idle state nodes to be filled
+ */
+ ret = of_init_idle_driver(drv, state_nodes, 1, true);
+ if (ret)
+ return ret;
+
+ if (suspend_init->init_fn(drv, state_nodes))
+ return -EOPNOTSUPP;
+
+ for (i = 0; i < drv->state_count; i++)
+ drv->states[i].enter = arm_enter_idle_state;
+
+ return cpuidle_register(drv, NULL);
+}
+device_initcall(arm64_idle_init);
diff --git a/drivers/cpuidle/of_idle_states.c b/drivers/cpuidle/of_idle_states.c
new file mode 100644
index 000000000000..eceb1b4c4657
--- /dev/null
+++ b/drivers/cpuidle/of_idle_states.c
@@ -0,0 +1,274 @@
+/*
+ * OF idle states parsing code.
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/cpuidle.h>
+#include <linux/cpumask.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include "of_idle_states.h"
+
+struct state_elem {
+ struct list_head list;
+ struct device_node *node;
+ int val;
+};
+
+static struct list_head head __initdata = LIST_HEAD_INIT(head);
+
+static bool __init state_cpu_valid(struct device_node *state_node,
+ struct device_node *cpu_node)
+{
+ int i = 0;
+ struct device_node *cpu_state;
+
+ while ((cpu_state = of_parse_phandle(cpu_node,
+ "cpu-idle-states", i++))) {
+ if (cpu_state && state_node == cpu_state) {
+ of_node_put(cpu_state);
+ return true;
+ }
+ of_node_put(cpu_state);
+ }
+ return false;
+}
+
+static bool __init state_cpus_valid(const cpumask_t *cpus,
+ struct device_node *state_node)
+{
+ int cpu;
+ struct device_node *cpu_node;
+
+ /*
+ * Check if state is valid on driver cpumask cpus
+ */
+ for_each_cpu(cpu, cpus) {
+ cpu_node = of_get_cpu_node(cpu, NULL);
+
+ if (!cpu_node) {
+ pr_err("Missing device node for CPU %d\n", cpu);
+ return false;
+ }
+
+ if (!state_cpu_valid(state_node, cpu_node))
+ return false;
+ }
+
+ return true;
+}
+
+static int __init state_cmp(void *priv, struct list_head *a,
+ struct list_head *b)
+{
+ struct state_elem *ela, *elb;
+
+ ela = container_of(a, struct state_elem, list);
+ elb = container_of(b, struct state_elem, list);
+
+ return ela->val - elb->val;
+}
+
+static int __init add_state_node(cpumask_t *cpumask,
+ struct device_node *state_node)
+{
+ struct state_elem *el;
+ u32 val;
+
+ pr_debug(" * %s...\n", state_node->full_name);
+
+ if (!state_cpus_valid(cpumask, state_node))
+ return -EINVAL;
+ /*
+ * Parse just the value required to sort the states.
+ */
+ if (of_property_read_u32(state_node, "min-residency-us",
+ &val)) {
+ pr_debug(" * %s missing min-residency-us property\n",
+ state_node->full_name);
+ return -EINVAL;
+ }
+
+ el = kmalloc(sizeof(*el), GFP_KERNEL);
+ if (!el) {
+ pr_err("%s failed to allocate memory\n", __func__);
+ return -ENOMEM;
+ }
+
+ el->node = state_node;
+ el->val = val;
+ list_add_tail(&el->list, &head);
+
+ return 0;
+}
+
+static void __init init_state_node(struct cpuidle_driver *drv,
+ struct device_node *state_node,
+ int *cnt)
+{
+ struct cpuidle_state *idle_state;
+
+ pr_debug(" * %s...\n", state_node->full_name);
+
+ idle_state = &drv->states[*cnt];
+
+ if (of_property_read_u32(state_node, "exit-latency-us",
+ &idle_state->exit_latency)) {
+ pr_debug(" * %s missing exit-latency-us property\n",
+ state_node->full_name);
+ return;
+ }
+
+ if (of_property_read_u32(state_node, "min-residency-us",
+ &idle_state->target_residency)) {
+ pr_debug(" * %s missing min-residency-us property\n",
+ state_node->full_name);
+ return;
+ }
+ /*
+ * It is unknown to the idle driver if and when the tick_device
+ * loses context when the CPU enters the idle states. To solve
+ * this issue the tick device must be linked to a power domain
+ * so that the idle driver can check on which states the device
+ * loses its context. Current code takes the conservative choice
+ * of defining the idle state as one where the tick device always
+ * loses its context. On platforms where tick device never loses
+ * its context (ie it is not a C3STOP device) this turns into
+ * a nop. On platforms where the tick device does lose context in some
+ * states, this code can be optimized, when power domain specifications
+ * for ARM CPUs are finalized.
+ */
+ idle_state->flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TIMER_STOP;
+
+ strncpy(idle_state->name, state_node->name, CPUIDLE_NAME_LEN);
+ strncpy(idle_state->desc, state_node->name, CPUIDLE_NAME_LEN);
+
+ (*cnt)++;
+}
+
+static int __init init_idle_states(struct cpuidle_driver *drv,
+ struct device_node *state_nodes[],
+ unsigned int start_idx, bool init_nodes)
+{
+ struct state_elem *el;
+ struct list_head *curr, *tmp;
+ unsigned int cnt = start_idx;
+
+ list_for_each_entry(el, &head, list) {
+ /*
+ * Check if the init function has to fill the
+ * state_nodes array on behalf of the CPUidle driver.
+ */
+ if (init_nodes)
+ state_nodes[cnt] = el->node;
+ /*
+ * cnt is updated on return if a state was added.
+ */
+ init_state_node(drv, el->node, &cnt);
+
+ if (cnt == CPUIDLE_STATE_MAX) {
+ pr_warn("State index reached static CPU idle state limit\n");
+ break;
+ }
+ }
+
+ drv->state_count = cnt;
+
+ list_for_each_safe(curr, tmp, &head) {
+ list_del(curr);
+ kfree(container_of(curr, struct state_elem, list));
+ }
+
+ /*
+ * If no idle states are detected, return an error and let the idle
+ * driver initialization fail accordingly.
+ */
+ return (cnt > start_idx) ? 0 : -ENODATA;
+}
+
+static void __init add_idle_states(struct cpuidle_driver *drv,
+ struct device_node *idle_states)
+{
+ struct device_node *state_node;
+
+ for_each_child_of_node(idle_states, state_node) {
+ if ((!of_device_is_compatible(state_node, "arm,idle-state"))) {
+ pr_warn(" * %s: children of /cpus/idle-states must be \"arm,idle-state\" compatible\n",
+ state_node->full_name);
+ continue;
+ }
+ /*
+ * If memory allocation fails, better bail out.
+ * Initialized nodes are freed at initialization
+ * completion in of_init_idle_driver().
+ */
+ if ((add_state_node(drv->cpumask, state_node) == -ENOMEM))
+ break;
+ }
+ /*
+ * Sort the states list before initializing the CPUidle driver
+ * states array.
+ */
+ list_sort(NULL, &head, state_cmp);
+}
+
+/*
+ * of_init_idle_driver - Parse the DT idle states and initialize the
+ * idle driver states array
+ *
+ * @drv: Pointer to CPU idle driver to be initialized
+ * @state_nodes: Array of struct device_nodes to be initialized if
+ * init_nodes == true. Must be sized CPUIDLE_STATE_MAX
+ * @start_idx: First idle state index to be initialized
+ * @init_nodes: Boolean to request device nodes initialization
+ *
+ * Returns:
+ * 0 on success
+ * <0 on failure
+ *
+ * On success the states array in the cpuidle driver contains
+ * initialized entries in the states array, starting from index start_idx.
+ * If init_nodes == true, on success the state_nodes array is initialized
+ * with idle state DT node pointers, starting from index start_idx,
+ * in a 1:1 relation with the idle driver states array.
+ */
+int __init of_init_idle_driver(struct cpuidle_driver *drv,
+ struct device_node *state_nodes[],
+ unsigned int start_idx, bool init_nodes)
+{
+ struct device_node *idle_states_node;
+ int ret;
+
+ if (start_idx >= CPUIDLE_STATE_MAX) {
+ pr_warn("State index exceeds static CPU idle driver states array size\n");
+ return -EINVAL;
+ }
+
+ if (WARN(init_nodes && !state_nodes,
+ "Requested nodes stashing in an invalid nodes container\n"))
+ return -EINVAL;
+
+ idle_states_node = of_find_node_by_path("/cpus/idle-states");
+ if (!idle_states_node)
+ return -ENOENT;
+
+ add_idle_states(drv, idle_states_node);
+
+ ret = init_idle_states(drv, state_nodes, start_idx, init_nodes);
+
+ of_node_put(idle_states_node);
+
+ return ret;
+}
diff --git a/drivers/cpuidle/of_idle_states.h b/drivers/cpuidle/of_idle_states.h
new file mode 100644
index 000000000000..049f94ff4428
--- /dev/null
+++ b/drivers/cpuidle/of_idle_states.h
@@ -0,0 +1,8 @@
+#ifndef __OF_IDLE_STATES
+#define __OF_IDLE_STATES
+
+int __init of_init_idle_driver(struct cpuidle_driver *drv,
+ struct device_node *state_nodes[],
+ unsigned int start_idx,
+ bool init_nodes);
+#endif