Merge remote-tracking branch 'lsk/v3.10/topic/arm64-cpuidle' into linux-linaro-lsk

Conflicts:
	drivers/cpuidle/Makefile
This commit is contained in:
Mark Brown
2014-05-30 11:14:13 +01:00
10 changed files with 622 additions and 8 deletions

View File

@@ -27,37 +27,70 @@
serial3 = &v2m_serial3;
};
psci {
compatible = "arm,psci";
method = "smc";
/*
* Function IDs usage and compliancy with PSCI v0.2 still
* under discussion. Current IDs should be considered
* temporary for demonstration purposes.
*/
cpu_suspend = <0x84000001>;
cpu_off = <0x84000002>;
cpu_on = <0x84000003>;
};
cpus {
#address-cells = <2>;
#size-cells = <0>;
idle-states {
entry-method = "arm,psci";
CPU_SLEEP_0: cpu-sleep-0 {
compatible = "arm,idle-state";
entry-method-param = <0x0010000>;
entry-latency-us = <40>;
exit-latency-us = <100>;
min-residency-us = <150>;
};
CLUSTER_SLEEP_0: cluster-sleep-0 {
compatible = "arm,idle-state";
entry-method-param = <0x1010000>;
entry-latency-us = <500>;
exit-latency-us = <1000>;
min-residency-us = <2500>;
};
};
cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x0>;
enable-method = "spin-table";
cpu-release-addr = <0x0 0x8000fff8>;
enable-method = "psci";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
cpu@1 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x1>;
enable-method = "spin-table";
cpu-release-addr = <0x0 0x8000fff8>;
enable-method = "psci";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
cpu@2 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x2>;
enable-method = "spin-table";
cpu-release-addr = <0x0 0x8000fff8>;
enable-method = "psci";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
cpu@3 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x3>;
enable-method = "spin-table";
cpu-release-addr = <0x0 0x8000fff8>;
enable-method = "psci";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
};

View File

@@ -14,6 +14,10 @@
#ifndef __ASM_PSCI_H
#define __ASM_PSCI_H
struct cpuidle_driver;
void psci_init(void);
int __init psci_dt_register_idle_states(struct cpuidle_driver *,
struct device_node *[]);
#endif /* __ASM_PSCI_H */

View File

@@ -15,15 +15,18 @@
#define pr_fmt(fmt) "psci: " fmt
#include <linux/cpuidle.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/smp.h>
#include <linux/slab.h>
#include <asm/compiler.h>
#include <asm/cpu_ops.h>
#include <asm/errno.h>
#include <asm/psci.h>
#include <asm/smp_plat.h>
#include <asm/suspend.h>
#define PSCI_POWER_STATE_TYPE_STANDBY 0
#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1
@@ -54,6 +57,8 @@ enum psci_function {
PSCI_FN_MAX,
};
static DEFINE_PER_CPU_READ_MOSTLY(struct psci_power_state *, psci_power_state);
static u32 psci_function_id[PSCI_FN_MAX];
#define PSCI_RET_SUCCESS 0
@@ -94,6 +99,17 @@ static u32 psci_power_state_pack(struct psci_power_state state)
<< PSCI_POWER_STATE_AFFL_SHIFT);
}
static void psci_power_state_unpack(u32 power_state,
struct psci_power_state *state)
{
state->id = (power_state >> PSCI_POWER_STATE_ID_SHIFT)
& PSCI_POWER_STATE_ID_MASK;
state->type = (power_state >> PSCI_POWER_STATE_TYPE_SHIFT)
& PSCI_POWER_STATE_TYPE_MASK;
state->affinity_level = (power_state >> PSCI_POWER_STATE_AFFL_SHIFT)
& PSCI_POWER_STATE_AFFL_MASK;
}
/*
* The following two functions are invoked via the invoke_psci_fn pointer
* and will not be inlined, allowing us to piggyback on the AAPCS.
@@ -176,6 +192,77 @@ static const struct of_device_id psci_of_match[] __initconst = {
{},
};
int __init psci_dt_register_idle_states(struct cpuidle_driver *drv,
struct device_node *state_nodes[])
{
int cpu, i;
struct psci_power_state *psci_states;
const struct cpu_operations *cpu_ops_ptr;
if (!state_nodes)
return -EINVAL;
/*
* This is belt-and-braces: make sure that if the idle
* specified protocol is psci, the cpu_ops have been
* initialized to psci operations. Anything else is
* a recipe for mayhem.
*/
for_each_cpu(cpu, drv->cpumask) {
cpu_ops_ptr = cpu_ops[cpu];
if (WARN_ON(!cpu_ops_ptr || strcmp(cpu_ops_ptr->name, "psci")))
return -EOPNOTSUPP;
}
psci_states = kcalloc(drv->state_count, sizeof(*psci_states),
GFP_KERNEL);
if (!psci_states) {
pr_warn("psci idle state allocation failed\n");
return -ENOMEM;
}
for_each_cpu(cpu, drv->cpumask) {
if (per_cpu(psci_power_state, cpu)) {
pr_warn("idle states already initialized on cpu %u\n",
cpu);
continue;
}
per_cpu(psci_power_state, cpu) = psci_states;
}
for (i = 0; i < drv->state_count; i++) {
u32 psci_power_state;
if (!state_nodes[i]) {
/*
* An index with a missing node pointer falls back to
* simple STANDBYWFI
*/
psci_states[i].type = PSCI_POWER_STATE_TYPE_STANDBY;
continue;
}
if (of_property_read_u32(state_nodes[i], "entry-method-param",
&psci_power_state)) {
pr_warn(" * %s missing entry-method-param property\n",
state_nodes[i]->full_name);
/*
* If entry-method-param property is missing, fall
* back to STANDBYWFI state
*/
psci_states[i].type = PSCI_POWER_STATE_TYPE_STANDBY;
continue;
}
pr_debug("psci-power-state %#x index %u\n",
psci_power_state, i);
psci_power_state_unpack(psci_power_state, &psci_states[i]);
}
return 0;
}
void __init psci_init(void)
{
struct device_node *np;
@@ -279,6 +366,18 @@ static void cpu_psci_cpu_die(unsigned int cpu)
}
#endif
#ifdef CONFIG_ARM64_CPU_SUSPEND
static int cpu_psci_cpu_suspend(unsigned long index)
{
struct psci_power_state *state = __get_cpu_var(psci_power_state);
if (!state)
return -EOPNOTSUPP;
return psci_ops.cpu_suspend(state[index], virt_to_phys(cpu_resume));
}
#endif
const struct cpu_operations cpu_psci_ops = {
.name = "psci",
.cpu_init = cpu_psci_cpu_init,
@@ -288,6 +387,9 @@ const struct cpu_operations cpu_psci_ops = {
.cpu_disable = cpu_psci_cpu_disable,
.cpu_die = cpu_psci_cpu_die,
#endif
#ifdef CONFIG_ARM64_CPU_SUSPEND
.cpu_suspend = cpu_psci_cpu_suspend,
#endif
};
#endif

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -111,6 +111,7 @@ struct cpuidle_driver {
struct cpuidle_state states[CPUIDLE_STATE_MAX];
int state_count;
int safe_state_index;
struct cpumask *cpumask;
};
#ifdef CONFIG_CPU_IDLE