--- /dev/null
+/*
+ * 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);