From 598a7acb19af3763aa17926168e24d8131f3a57d Mon Sep 17 00:00:00 2001
From: Brendan Jackman <brendan.jackman@arm.com>
Date: Mon, 25 Jul 2016 14:26:08 +0100
Subject: [PATCH] cpuidle: dt: Parse states from power domain nodes

This commit allows bindings similar to those in [1] to be used without
requiring CPU PM domains. The cpuidle DT parsing code will first read
idle states from the "cpu-idle-states" property, then walk up the
power-domains tree (assuming that it is indeed a tree) for the CPU and
read idle states from the "power-states" property.

This is a temporary hack to enable a prototype EAS energy model in the
device tree, for which we want to use CPU power-domains to describe
energy costs in a topologically-aware way.

[1] https://patchwork.kernel.org/patch/9193651/
---
 drivers/cpuidle/dt_idle_states.c | 85 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 82 insertions(+), 3 deletions(-)

diff --git a/drivers/cpuidle/dt_idle_states.c b/drivers/cpuidle/dt_idle_states.c
index a5c111b..70fe30b 100644
--- a/drivers/cpuidle/dt_idle_states.c
+++ b/drivers/cpuidle/dt_idle_states.c
@@ -21,6 +21,8 @@
 
 #include "dt_idle_states.h"
 
+#define MAX_PD_DEPTH 8
+
 static int init_state_node(struct cpuidle_state *idle_state,
 			   const struct of_device_id *matches,
 			   struct device_node *state_node)
@@ -92,6 +94,84 @@ static int init_state_node(struct cpuidle_state *idle_state,
 }
 
 /*
+ * First look in cpu-idle-states, then walk up the power-domains tree, returning
+ * the idx'th idle state node encountered, or NULL if it can't be found.
+ *
+ * Returns the device_node pointer with refcount incremented.  Use
+ * of_node_put() on it when done.
+ */
+static struct device_node *get_idle_state_node(struct device_node *cpu_node,
+					       int idx)
+{
+	struct device_node *state_node, *consumer_node;
+	int num_cpu_idle_states, num_pd_idle_states;
+	int idx_pd;
+	unsigned int i;
+
+	state_node = of_parse_phandle(cpu_node, "cpu-idle-states", idx);
+	if (state_node)
+		return state_node;
+
+	num_cpu_idle_states = of_property_count_elems_of_size(cpu_node,
+							      "cpu-idle-states",
+							      4);
+	if (num_cpu_idle_states < 1) {
+		pr_devel("%s: no cpu-idle-states for %s\n", __func__,
+			 cpu_node->name);
+		num_cpu_idle_states = 0;
+	}
+
+	pr_devel("%s: Looking in %s's power domains tree for state %d\n",
+		 __func__, cpu_node->full_name, idx);
+
+	BUG_ON(idx < num_cpu_idle_states);
+	idx_pd = idx - num_cpu_idle_states;
+
+	/* Use a loop counter so we don't infiniloop if there's a cycle in the
+	 * power-domains graph. */
+	consumer_node = cpu_node;
+	for (i = 0; i < MAX_PD_DEPTH; i++) {
+		int num_pd_providers;
+		struct device_node *pd_node = of_parse_phandle(
+			consumer_node, "power-domains", 0);
+		if (!pd_node) {
+			return NULL;
+		}
+
+		/* We're assuming the power-domains graph is a tree, complain if
+		 * not. */
+		num_pd_providers = of_property_count_elems_of_size(
+			consumer_node, "power-domains", 4);
+		BUG_ON(num_pd_providers < 1);
+		if (num_pd_providers > 1)
+			pr_warn("power-domains graph for %s not a tree "
+				"(%s has multiple power-domains)\n",
+				cpu_node->full_name, consumer_node->full_name);
+
+
+		state_node = of_parse_phandle(pd_node, "power-states", idx_pd);
+		if (state_node) {
+			of_node_put(pd_node);
+			return state_node;
+		}
+
+		num_pd_idle_states = of_property_count_elems_of_size(
+			pd_node, "power-states", 4);
+		if (num_pd_idle_states < 1)
+			num_pd_idle_states = 0;
+
+		BUG_ON(idx_pd < num_pd_idle_states);
+		idx_pd -= num_pd_idle_states;
+
+		of_node_put(pd_node);
+		consumer_node = pd_node;
+	}
+
+	WARN(1, "DT CPU power domain graph too deep. Probably has a cycle.");
+	return NULL;
+}
+
+/*
  * Check that the idle state is uniform across all CPUs in the CPUidle driver
  * cpumask
  */
@@ -112,8 +192,7 @@ static bool idle_state_valid(struct device_node *state_node, unsigned int idx,
 	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);
+		curr_state_node = get_idle_state_node(cpu_node, idx);
 		if (state_node != curr_state_node)
 			valid = false;
 
@@ -170,7 +249,7 @@ int dt_init_idle_driver(struct cpuidle_driver *drv,
 	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);
+		state_node = get_idle_state_node(cpu_node,i);
 		if (!state_node)
 			break;
 
-- 
1.9.1

