mirror of
https://github.com/armbian/linux.git
synced 2026-01-06 10:13:00 -08:00
Merge remote-tracking branch 'lsk/v3.10/topic/arm64-cpuidle' into linux-linaro-lsk
Conflicts: drivers/cpuidle/Makefile
This commit is contained in:
@@ -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>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
13
drivers/cpuidle/Kconfig.arm64
Normal file
13
drivers/cpuidle/Kconfig.arm64
Normal 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.
|
||||
@@ -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
|
||||
|
||||
159
drivers/cpuidle/cpuidle-arm64.c
Normal file
159
drivers/cpuidle/cpuidle-arm64.c
Normal 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);
|
||||
274
drivers/cpuidle/of_idle_states.c
Normal file
274
drivers/cpuidle/of_idle_states.c
Normal 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;
|
||||
}
|
||||
8
drivers/cpuidle/of_idle_states.h
Normal file
8
drivers/cpuidle/of_idle_states.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user