From 19a87fdc2aa7a0e37e28ecc147ee696418f3d942 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 17 Jan 2015 01:25:15 +0000 Subject: Revert "drivers: cpuidle: CPU idle ARM64 driver" This reverts commit 08461b57c19fb68dde8907b87ff38f38f2585f64. --- drivers/cpuidle/Kconfig | 5 -- drivers/cpuidle/Kconfig.arm64 | 13 ---- drivers/cpuidle/Makefile | 4 - drivers/cpuidle/cpuidle-arm64.c | 159 ---------------------------------------- 4 files changed, 181 deletions(-) delete mode 100644 drivers/cpuidle/Kconfig.arm64 delete mode 100644 drivers/cpuidle/cpuidle-arm64.c (limited to 'drivers') diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 842d7ba83101..e4d3913fe330 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -48,9 +48,4 @@ 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 deleted file mode 100644 index b83612c67e6d..000000000000 --- a/drivers/cpuidle/Kconfig.arm64 +++ /dev/null @@ -1,13 +0,0 @@ -# -# 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 d3e662cde224..d41366a7838c 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -8,7 +8,3 @@ 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 deleted file mode 100644 index 2cfde6ce3086..000000000000 --- a/drivers/cpuidle/cpuidle-arm64.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * ARM64 generic CPU idle driver. - * - * Copyright (C) 2014 ARM Ltd. - * Author: Lorenzo Pieralisi - * - * 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 -#include -#include -#include -#include -#include - -#include -#include - -#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); -- cgit v1.2.3 From ef6595e9651c6c68265c9bad665df1ba397deceb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 18 Jan 2015 12:39:09 +0000 Subject: Revert "drivers: cpuidle: implement OF based idle states infrastructure" This reverts commit 830b8519a41c5b13b1787ef94f31ad1d3507ec73. --- drivers/cpuidle/Kconfig | 9 -- drivers/cpuidle/Makefile | 1 - drivers/cpuidle/of_idle_states.c | 274 --------------------------------------- drivers/cpuidle/of_idle_states.h | 8 -- 4 files changed, 292 deletions(-) delete mode 100644 drivers/cpuidle/of_idle_states.c delete mode 100644 drivers/cpuidle/of_idle_states.h (limited to 'drivers') diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index e4d3913fe330..c4cc27e5c8a5 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -31,15 +31,6 @@ 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 diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index d41366a7838c..0d8bd55e776f 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -4,7 +4,6 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.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 diff --git a/drivers/cpuidle/of_idle_states.c b/drivers/cpuidle/of_idle_states.c deleted file mode 100644 index eceb1b4c4657..000000000000 --- a/drivers/cpuidle/of_idle_states.c +++ /dev/null @@ -1,274 +0,0 @@ -/* - * OF idle states parsing code. - * - * Copyright (C) 2014 ARM Ltd. - * Author: Lorenzo Pieralisi - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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 deleted file mode 100644 index 049f94ff4428..000000000000 --- a/drivers/cpuidle/of_idle_states.h +++ /dev/null @@ -1,8 +0,0 @@ -#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 -- cgit v1.2.3 From 89f6097555808374113d23e39b00c0d3c3d144c1 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 14 Feb 2014 14:28:39 +0000 Subject: drivers: cpuidle: implement DT based idle states infrastructure On most common ARM systems, the low-power states a CPU can be put into are not discoverable in HW and require device tree bindings to describe power down suspend operations and idle states parameters. In order to enable DT based idle states and configure idle drivers, this patch implements the bulk infrastructure required to parse the device tree idle states bindings and initialize the corresponding CPUidle driver states data. The parsing API accepts a start index that defines the first idle state that should be initialized by the parsing code in order to give new and legacy driver flexibility over which states should be parsed using the new DT mechanism. The idle states node(s) is obtained from the phandle list of the first cpu in the driver cpumask; the kernel checks that the idle state node phandle is the same for all CPUs in the driver cpumask before declaring the idle state as valid and start parsing its content. The idle state enter function pointer is initialized through DT match structures passed in by the CPUidle driver, so that ARM legacy code can cope with platform specific idle entry method based on compatible string matching and the code used to initialize the enter function pointer can be moved to the DT generic layer. Acked-by: Catalin Marinas Acked-by: Daniel Lezcano Signed-off-by: Lorenzo Pieralisi Signed-off-by: Daniel Lezcano (cherry picked from commit 9f14da345599c14b329cf5ac9499ad322056dd32) Signed-off-by: Mark Brown Conflicts: drivers/cpuidle/Kconfig --- drivers/cpuidle/Kconfig | 3 + drivers/cpuidle/Makefile | 1 + drivers/cpuidle/dt_idle_states.c | 213 +++++++++++++++++++++++++++++++++++++++ drivers/cpuidle/dt_idle_states.h | 7 ++ 4 files changed, 224 insertions(+) create mode 100644 drivers/cpuidle/dt_idle_states.c create mode 100644 drivers/cpuidle/dt_idle_states.h (limited to 'drivers') diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index c4cc27e5c8a5..69b8db87e496 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -31,6 +31,9 @@ config CPU_IDLE_GOV_MENU config ARCH_NEEDS_CPU_IDLE_COUPLED def_bool n +config DT_IDLE_STATES + bool + if CPU_IDLE config CPU_IDLE_CALXEDA diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 0d8bd55e776f..879182f031d4 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -4,6 +4,7 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o +obj-$(CONFIG_DT_IDLE_STATES) += dt_idle_states.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o diff --git a/drivers/cpuidle/dt_idle_states.c b/drivers/cpuidle/dt_idle_states.c new file mode 100644 index 000000000000..52f4d11bbf3f --- /dev/null +++ b/drivers/cpuidle/dt_idle_states.c @@ -0,0 +1,213 @@ +/* + * DT idle states parsing code. + * + * Copyright (C) 2014 ARM Ltd. + * Author: Lorenzo Pieralisi + * + * 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. + */ + +#define pr_fmt(fmt) "DT idle-states: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "dt_idle_states.h" + +static int init_state_node(struct cpuidle_state *idle_state, + const struct of_device_id *matches, + struct device_node *state_node) +{ + int err; + const struct of_device_id *match_id; + + match_id = of_match_node(matches, state_node); + if (!match_id) + return -ENODEV; + /* + * CPUidle drivers are expected to initialize the const void *data + * pointer of the passed in struct of_device_id array to the idle + * state enter function. + */ + idle_state->enter = match_id->data; + + err = of_property_read_u32(state_node, "wakeup-latency-us", + &idle_state->exit_latency); + if (err) { + u32 entry_latency, exit_latency; + + err = of_property_read_u32(state_node, "entry-latency-us", + &entry_latency); + if (err) { + pr_debug(" * %s missing entry-latency-us property\n", + state_node->full_name); + return -EINVAL; + } + + err = of_property_read_u32(state_node, "exit-latency-us", + &exit_latency); + if (err) { + pr_debug(" * %s missing exit-latency-us property\n", + state_node->full_name); + return -EINVAL; + } + /* + * If wakeup-latency-us is missing, default to entry+exit + * latencies as defined in idle states bindings + */ + idle_state->exit_latency = entry_latency + exit_latency; + } + + err = of_property_read_u32(state_node, "min-residency-us", + &idle_state->target_residency); + if (err) { + pr_debug(" * %s missing min-residency-us property\n", + state_node->full_name); + return -EINVAL; + } + + idle_state->flags = CPUIDLE_FLAG_TIME_VALID; + if (of_property_read_bool(state_node, "local-timer-stop")) + idle_state->flags |= CPUIDLE_FLAG_TIMER_STOP; + /* + * TODO: + * replace with kstrdup and pointer assignment when name + * and desc become string pointers + */ + strncpy(idle_state->name, state_node->name, CPUIDLE_NAME_LEN - 1); + strncpy(idle_state->desc, state_node->name, CPUIDLE_DESC_LEN - 1); + return 0; +} + +/* + * Check that the idle state is uniform across all CPUs in the CPUidle driver + * cpumask + */ +static bool idle_state_valid(struct device_node *state_node, unsigned int idx, + const cpumask_t *cpumask) +{ + int cpu; + struct device_node *cpu_node, *curr_state_node; + bool valid = true; + + /* + * Compare idle state phandles for index idx on all CPUs in the + * CPUidle driver cpumask. Start from next logical cpu following + * cpumask_first(cpumask) since that's the CPU state_node was + * retrieved from. If a mismatch is found bail out straight + * away since we certainly hit a firmware misconfiguration. + */ + for (cpu = cpumask_next(cpumask_first(cpumask), cpumask); + cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpumask)) { + cpu_node = of_cpu_device_node_get(cpu); + curr_state_node = of_parse_phandle(cpu_node, "cpu-idle-states", + idx); + if (state_node != curr_state_node) + valid = false; + + of_node_put(curr_state_node); + of_node_put(cpu_node); + if (!valid) + break; + } + + return valid; +} + +/** + * dt_init_idle_driver() - Parse the DT idle states and initialize the + * idle driver states array + * @drv: Pointer to CPU idle driver to be initialized + * @matches: Array of of_device_id match structures to search in for + * compatible idle state nodes. The data pointer for each valid + * struct of_device_id entry in the matches array must point to + * a function with the following signature, that corresponds to + * the CPUidle state enter function signature: + * + * int (*)(struct cpuidle_device *dev, + * struct cpuidle_driver *drv, + * int index); + * + * @start_idx: First idle state index to be initialized + * + * If DT idle states are detected and are valid the state count and states + * array entries in the cpuidle driver are initialized accordingly starting + * from index start_idx. + * + * Return: number of valid DT idle states parsed, <0 on failure + */ +int dt_init_idle_driver(struct cpuidle_driver *drv, + const struct of_device_id *matches, + unsigned int start_idx) +{ + struct cpuidle_state *idle_state; + struct device_node *state_node, *cpu_node; + int i, err = 0; + const cpumask_t *cpumask; + unsigned int state_idx = start_idx; + + if (state_idx >= CPUIDLE_STATE_MAX) + return -EINVAL; + /* + * We get the idle states for the first logical cpu in the + * driver mask (or cpu_possible_mask if the driver cpumask is not set) + * and we check through idle_state_valid() if they are uniform + * across CPUs, otherwise we hit a firmware misconfiguration. + */ + cpumask = drv->cpumask ? : cpu_possible_mask; + cpu_node = of_cpu_device_node_get(cpumask_first(cpumask)); + + for (i = 0; ; i++) { + state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); + if (!state_node) + break; + + if (!idle_state_valid(state_node, i, cpumask)) { + pr_warn("%s idle state not valid, bailing out\n", + state_node->full_name); + err = -EINVAL; + break; + } + + if (state_idx == CPUIDLE_STATE_MAX) { + pr_warn("State index reached static CPU idle driver states array size\n"); + break; + } + + idle_state = &drv->states[state_idx++]; + err = init_state_node(idle_state, matches, state_node); + if (err) { + pr_err("Parsing idle state node %s failed with err %d\n", + state_node->full_name, err); + err = -EINVAL; + break; + } + of_node_put(state_node); + } + + of_node_put(state_node); + of_node_put(cpu_node); + if (err) + return err; + /* + * Update the driver state count only if some valid DT idle states + * were detected + */ + if (i) + drv->state_count = state_idx; + + /* + * Return the number of present and valid DT idle states, which can + * also be 0 on platforms with missing DT idle states or legacy DT + * configuration predating the DT idle states bindings. + */ + return i; +} +EXPORT_SYMBOL_GPL(dt_init_idle_driver); diff --git a/drivers/cpuidle/dt_idle_states.h b/drivers/cpuidle/dt_idle_states.h new file mode 100644 index 000000000000..4818134bc65b --- /dev/null +++ b/drivers/cpuidle/dt_idle_states.h @@ -0,0 +1,7 @@ +#ifndef __DT_IDLE_STATES +#define __DT_IDLE_STATES + +int dt_init_idle_driver(struct cpuidle_driver *drv, + const struct of_device_id *matches, + unsigned int start_idx); +#endif -- cgit v1.2.3 From 4bb26215c07fb204ae1d66e54406dd6bae793c20 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 28 Feb 2014 13:03:44 +0000 Subject: drivers: cpuidle: CPU idle ARM64 driver This patch implements a generic CPU idle driver for ARM64 machines. It relies on the DT idle states infrastructure to initialize idle states count and respective parameters. Current code assumes the driver is managing idle states on all possible CPUs but can be easily generalized to support heterogenous systems and build cpumasks at runtime using MIDRs or DT cpu nodes compatible properties. The driver relies on the arm64 CPU operations to call the idle initialization hook used to parse and save suspend back-end specific idle states information upon probing. Idle state index 0 is always initialized as a simple wfi state, ie always considered present and functional on all ARM64 platforms. Idle state indices higher than 0 trigger idle state entry by calling the cpu_suspend function, that triggers the suspend operation through the CPU operations suspend back-end hook. cpu_suspend passes the idle state index as a parameter so that the CPU operations suspend back-end can retrieve the required idle state data by using the idle state index to execute a look-up on its internal data structures. Reviewed-by: Ashwin Chaugule Reviewed-by: Catalin Marinas Signed-off-by: Lorenzo Pieralisi Signed-off-by: Daniel Lezcano (cherry picked from commit 3299b63de384159579143d4abdfb94013e0b5470) Signed-off-by: Mark Brown Conflicts: drivers/cpuidle/Kconfig drivers/cpuidle/Makefile --- drivers/cpuidle/Kconfig | 5 ++ drivers/cpuidle/Kconfig.arm64 | 14 +++++ drivers/cpuidle/Makefile | 4 ++ drivers/cpuidle/cpuidle-arm64.c | 133 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 drivers/cpuidle/Kconfig.arm64 create mode 100644 drivers/cpuidle/cpuidle-arm64.c (limited to 'drivers') diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 69b8db87e496..9625ce7ed5f8 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -36,6 +36,11 @@ config DT_IDLE_STATES if CPU_IDLE +menu "ARM64 CPU Idle Drivers" +depends on ARM64 +source "drivers/cpuidle/Kconfig.arm64" +endmenu + config CPU_IDLE_CALXEDA bool "CPU Idle Driver for Calxeda processors" depends on ARCH_HIGHBANK diff --git a/drivers/cpuidle/Kconfig.arm64 b/drivers/cpuidle/Kconfig.arm64 new file mode 100644 index 000000000000..d0a08ed1b2ee --- /dev/null +++ b/drivers/cpuidle/Kconfig.arm64 @@ -0,0 +1,14 @@ +# +# ARM64 CPU Idle drivers +# + +config ARM64_CPUIDLE + bool "Generic ARM64 CPU idle Driver" + select ARM64_CPU_SUSPEND + select DT_IDLE_STATES + help + Select this to enable generic cpuidle driver for ARM64. + It provides a generic idle driver whose idle states are configured + at run-time through DT nodes. The CPUidle suspend backend is + initialized by calling the CPU operations init idle hook + provided by architecture code. diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 879182f031d4..825433777ef2 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -8,3 +8,7 @@ obj-$(CONFIG_DT_IDLE_STATES) += dt_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..50997ea942fc --- /dev/null +++ b/drivers/cpuidle/cpuidle-arm64.c @@ -0,0 +1,133 @@ +/* + * ARM64 generic CPU idle driver. + * + * Copyright (C) 2014 ARM Ltd. + * Author: Lorenzo Pieralisi + * + * 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. + */ + +#define pr_fmt(fmt) "CPUidle arm64: " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dt_idle_states.h" + +/* + * arm64_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 arm64_enter_idle_state(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + int ret; + + if (!idx) { + cpu_do_idle(); + return idx; + } + + ret = cpu_pm_enter(); + if (!ret) { + /* + * Pass idle state index to cpu_suspend which in turn will + * call the CPU ops suspend protocol with idle index as a + * parameter. + */ + ret = cpu_suspend(idx); + + cpu_pm_exit(); + } + + return ret ? -1 : idx; +} + +static struct cpuidle_driver arm64_idle_driver = { + .name = "arm64_idle", + .owner = THIS_MODULE, + /* + * 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. + */ + .states[0] = { + .enter = arm64_enter_idle_state, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM64 WFI", + } +}; + +static const struct of_device_id arm64_idle_state_match[] __initconst = { + { .compatible = "arm,idle-state", + .data = arm64_enter_idle_state }, + { }, +}; + +/* + * 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 cpu, ret; + struct cpuidle_driver *drv = &arm64_idle_driver; + + /* + * Initialize idle states data, starting at index 1. + * This driver is DT only, if no DT idle states are detected (ret == 0) + * let the driver initialization fail accordingly since there is no + * reason to initialize the idle driver if only wfi is supported. + */ + ret = dt_init_idle_driver(drv, arm64_idle_state_match, 1); + if (ret <= 0) { + if (ret) + pr_err("failed to initialize idle states\n"); + return ret ? : -ENODEV; + } + + /* + * Call arch CPU operations in order to initialize + * idle states suspend back-end specific data + */ + for_each_possible_cpu(cpu) { + ret = cpu_init_idle(cpu); + if (ret) { + pr_err("CPU %d failed to init idle CPU ops\n", cpu); + return ret; + } + } + + ret = cpuidle_register(drv, NULL); + if (ret) { + pr_err("failed to register cpuidle driver\n"); + return ret; + } + + return 0; +} +device_initcall(arm64_idle_init); -- cgit v1.2.3