From patchwork Wed Apr 16 15:44:51 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lorenzo Pieralisi X-Patchwork-Id: 28495 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-ie0-f200.google.com (mail-ie0-f200.google.com [209.85.223.200]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 4A8922036A for ; Wed, 16 Apr 2014 15:43:22 +0000 (UTC) Received: by mail-ie0-f200.google.com with SMTP id lx4sf60413252iec.11 for ; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:delivered-to:from:to:cc:subject :date:message-id:in-reply-to:references:sender:precedence:list-id :x-original-sender:x-original-authentication-results:mailing-list :list-post:list-help:list-archive:list-unsubscribe:content-type :content-transfer-encoding; bh=v3BY6rAmY372tIm67BZZlpgNAu8CYU30xq8VHRmlzkU=; b=jwFxcr0iLblt8XOlNEm6U85KBdmt0BSY5VH3ZOM8qAsq4Tgin+jGY3G43P0acPGeI9 dh2nx5XYezsgOQM7A6++FAjxV9aMdujPoeopnJPyRlx/naCxdWJiDa/w2wsPtfnbNSXM NZr6sM3/tgv0KZJhwlneSDjPGsIlqkCbJ8vMmjrAYS8wiZmaK8kifw0K2VFWjQLsDVPU Yx0WLzepjMzi5aoItw1f6XlrEVJMlaAd9br/yzecMuwlC/A3+GiX5he283ixaMgnUwuV uyBcySvoiwkfHnupNyF4f2LQjuCsqyXthgGWT8SBVbe2ycgGvoxnk9FIjvQt+isYuMPy DnuA== X-Gm-Message-State: ALoCoQlIbkkyLVNvLjPxM9347vnEEIVQU2Gy6v6TYagJ4aQU5rf3/ELetSqefoyd0PfrWprJ7uD0 X-Received: by 10.182.16.199 with SMTP id i7mr4256734obd.42.1397663001579; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.98.68 with SMTP id n62ls678553qge.28.gmail; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) X-Received: by 10.220.98.143 with SMTP id q15mr538271vcn.38.1397663001390; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) Received: from mail-ve0-f169.google.com (mail-ve0-f169.google.com [209.85.128.169]) by mx.google.com with ESMTPS id ys8si3931143veb.214.2014.04.16.08.43.21 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Wed, 16 Apr 2014 08:43:21 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.128.169 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) client-ip=209.85.128.169; Received: by mail-ve0-f169.google.com with SMTP id pa12so10961346veb.14 for ; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) X-Received: by 10.58.185.145 with SMTP id fc17mr6849915vec.14.1397663001294; Wed, 16 Apr 2014 08:43:21 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.220.221.72 with SMTP id ib8csp322224vcb; Wed, 16 Apr 2014 08:43:20 -0700 (PDT) X-Received: by 10.66.250.202 with SMTP id ze10mr9212521pac.153.1397663000409; Wed, 16 Apr 2014 08:43:20 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id zj1si3585609pbb.237.2014.04.16.08.43.19; Wed, 16 Apr 2014 08:43:19 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-pm-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1161834AbaDPPnR (ORCPT + 12 others); Wed, 16 Apr 2014 11:43:17 -0400 Received: from service87.mimecast.com ([91.220.42.44]:43484 "EHLO service87.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1161785AbaDPPnO (ORCPT ); Wed, 16 Apr 2014 11:43:14 -0400 Received: from cam-owa1.Emea.Arm.com (fw-tnat.cambridge.arm.com [217.140.96.21]) by service87.mimecast.com; Wed, 16 Apr 2014 16:43:12 +0100 Received: from red-moon.cambridge.arm.com ([10.1.255.212]) by cam-owa1.Emea.Arm.com with Microsoft SMTPSVC(6.0.3790.3959); Wed, 16 Apr 2014 16:43:26 +0100 From: Lorenzo Pieralisi To: linux-arm-kernel@lists.infradead.org, linux-pm@vger.kernel.org Cc: devicetree@vger.kernel.org, Lorenzo Pieralisi , Mark Rutland , Sudeep Holla , Catalin Marinas , Charles Garcia Tobin , Nicolas Pitre , Rob Herring , Grant Likely , Peter De Schrijver , Santosh Shilimkar , Daniel Lezcano , Amit Kucheria , Vincent Guittot , Antti Miettinen , Stephen Boyd , Kevin Hilman , Sebastian Capella , Tomasz Figa Subject: [PATCH RFC v2 1/4] drivers: cpuidle: implement OF based idle states infrastructure Date: Wed, 16 Apr 2014 16:44:51 +0100 Message-Id: <1397663094-28027-2-git-send-email-lorenzo.pieralisi@arm.com> X-Mailer: git-send-email 1.8.4 In-Reply-To: <1397663094-28027-1-git-send-email-lorenzo.pieralisi@arm.com> References: <1397663094-28027-1-git-send-email-lorenzo.pieralisi@arm.com> X-OriginalArrivalTime: 16 Apr 2014 15:43:26.0071 (UTC) FILETIME=[9B2B7870:01CF598A] X-MC-Unique: 114041616431208701 Sender: linux-pm-owner@vger.kernel.org Precedence: list List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: lorenzo.pieralisi@arm.com X-Original-Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.128.169 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , 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 the respective power domains, power down protocol 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. Code that initializes idle states checks the CPU idle driver cpumask so that multiple CPU idle drivers can be initialized through it in the kernel. The CPU idle driver cpumask defines which idle states should be considered valid for the driver, ie idle states that are valid on a set of cpus the idle driver manages. Signed-off-by: Lorenzo Pieralisi --- 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 insertions(+) create mode 100644 drivers/cpuidle/of_idle_states.c create mode 100644 drivers/cpuidle/of_idle_states.h diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index f04e25f..995654e 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -30,6 +30,15 @@ config CPU_IDLE_GOV_MENU bool "Menu governor (for tickless system)" default y +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. + menu "ARM CPU Idle Drivers" depends on ARM source "drivers/cpuidle/Kconfig.arm" diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index f71ae1b..2fc5139 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_OF_IDLE_STATES) += of_idle_states.o ################################################################################## # ARM SoC drivers diff --git a/drivers/cpuidle/of_idle_states.c b/drivers/cpuidle/of_idle_states.c new file mode 100644 index 0000000..eceb1b4 --- /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 + * + * 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 new file mode 100644 index 0000000..049f94f --- /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