xref: /openbmc/linux/drivers/powercap/dtpm.c (revision 55ddcd9f)
1a20d0ef9SDaniel Lezcano // SPDX-License-Identifier: GPL-2.0-only
2a20d0ef9SDaniel Lezcano /*
3a20d0ef9SDaniel Lezcano  * Copyright 2020 Linaro Limited
4a20d0ef9SDaniel Lezcano  *
5a20d0ef9SDaniel Lezcano  * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
6a20d0ef9SDaniel Lezcano  *
7a20d0ef9SDaniel Lezcano  * The powercap based Dynamic Thermal Power Management framework
8a20d0ef9SDaniel Lezcano  * provides to the userspace a consistent API to set the power limit
9a20d0ef9SDaniel Lezcano  * on some devices.
10a20d0ef9SDaniel Lezcano  *
11a20d0ef9SDaniel Lezcano  * DTPM defines the functions to create a tree of constraints. Each
12a20d0ef9SDaniel Lezcano  * parent node is a virtual description of the aggregation of the
13a20d0ef9SDaniel Lezcano  * children. It propagates the constraints set at its level to its
14a20d0ef9SDaniel Lezcano  * children and collect the children power information. The leaves of
15a20d0ef9SDaniel Lezcano  * the tree are the real devices which have the ability to get their
16a20d0ef9SDaniel Lezcano  * current power consumption and set their power limit.
17a20d0ef9SDaniel Lezcano  */
18a20d0ef9SDaniel Lezcano #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
19a20d0ef9SDaniel Lezcano 
20a20d0ef9SDaniel Lezcano #include <linux/dtpm.h>
21a20d0ef9SDaniel Lezcano #include <linux/init.h>
22a20d0ef9SDaniel Lezcano #include <linux/kernel.h>
23a20d0ef9SDaniel Lezcano #include <linux/powercap.h>
24a20d0ef9SDaniel Lezcano #include <linux/slab.h>
25a20d0ef9SDaniel Lezcano #include <linux/mutex.h>
263759ec67SDaniel Lezcano #include <linux/of.h>
27a20d0ef9SDaniel Lezcano 
28b9794a82SDaniel Lezcano #include "dtpm_subsys.h"
29b9794a82SDaniel Lezcano 
302185c230SDan Carpenter #define DTPM_POWER_LIMIT_FLAG 0
31a20d0ef9SDaniel Lezcano 
32a20d0ef9SDaniel Lezcano static const char *constraint_name[] = {
33a20d0ef9SDaniel Lezcano 	"Instantaneous",
34a20d0ef9SDaniel Lezcano };
35a20d0ef9SDaniel Lezcano 
36a20d0ef9SDaniel Lezcano static DEFINE_MUTEX(dtpm_lock);
37a20d0ef9SDaniel Lezcano static struct powercap_control_type *pct;
38a20d0ef9SDaniel Lezcano static struct dtpm *root;
39a20d0ef9SDaniel Lezcano 
get_time_window_us(struct powercap_zone * pcz,int cid,u64 * window)40a20d0ef9SDaniel Lezcano static int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window)
41a20d0ef9SDaniel Lezcano {
42a20d0ef9SDaniel Lezcano 	return -ENOSYS;
43a20d0ef9SDaniel Lezcano }
44a20d0ef9SDaniel Lezcano 
set_time_window_us(struct powercap_zone * pcz,int cid,u64 window)45a20d0ef9SDaniel Lezcano static int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window)
46a20d0ef9SDaniel Lezcano {
47a20d0ef9SDaniel Lezcano 	return -ENOSYS;
48a20d0ef9SDaniel Lezcano }
49a20d0ef9SDaniel Lezcano 
get_max_power_range_uw(struct powercap_zone * pcz,u64 * max_power_uw)50a20d0ef9SDaniel Lezcano static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
51a20d0ef9SDaniel Lezcano {
52a20d0ef9SDaniel Lezcano 	struct dtpm *dtpm = to_dtpm(pcz);
53a20d0ef9SDaniel Lezcano 
54a20d0ef9SDaniel Lezcano 	*max_power_uw = dtpm->power_max - dtpm->power_min;
55a20d0ef9SDaniel Lezcano 
56a20d0ef9SDaniel Lezcano 	return 0;
57a20d0ef9SDaniel Lezcano }
58a20d0ef9SDaniel Lezcano 
__get_power_uw(struct dtpm * dtpm,u64 * power_uw)59a20d0ef9SDaniel Lezcano static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
60a20d0ef9SDaniel Lezcano {
61a20d0ef9SDaniel Lezcano 	struct dtpm *child;
62a20d0ef9SDaniel Lezcano 	u64 power;
63a20d0ef9SDaniel Lezcano 	int ret = 0;
64a20d0ef9SDaniel Lezcano 
65a20d0ef9SDaniel Lezcano 	if (dtpm->ops) {
66a20d0ef9SDaniel Lezcano 		*power_uw = dtpm->ops->get_power_uw(dtpm);
67a20d0ef9SDaniel Lezcano 		return 0;
68a20d0ef9SDaniel Lezcano 	}
69a20d0ef9SDaniel Lezcano 
70a20d0ef9SDaniel Lezcano 	*power_uw = 0;
71a20d0ef9SDaniel Lezcano 
72a20d0ef9SDaniel Lezcano 	list_for_each_entry(child, &dtpm->children, sibling) {
73a20d0ef9SDaniel Lezcano 		ret = __get_power_uw(child, &power);
74a20d0ef9SDaniel Lezcano 		if (ret)
75a20d0ef9SDaniel Lezcano 			break;
76a20d0ef9SDaniel Lezcano 		*power_uw += power;
77a20d0ef9SDaniel Lezcano 	}
78a20d0ef9SDaniel Lezcano 
79a20d0ef9SDaniel Lezcano 	return ret;
80a20d0ef9SDaniel Lezcano }
81a20d0ef9SDaniel Lezcano 
get_power_uw(struct powercap_zone * pcz,u64 * power_uw)82a20d0ef9SDaniel Lezcano static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
83a20d0ef9SDaniel Lezcano {
847b75bbdfSDaniel Lezcano 	return __get_power_uw(to_dtpm(pcz), power_uw);
85a20d0ef9SDaniel Lezcano }
86a20d0ef9SDaniel Lezcano 
__dtpm_rebalance_weight(struct dtpm * dtpm)87a20d0ef9SDaniel Lezcano static void __dtpm_rebalance_weight(struct dtpm *dtpm)
88a20d0ef9SDaniel Lezcano {
89a20d0ef9SDaniel Lezcano 	struct dtpm *child;
90a20d0ef9SDaniel Lezcano 
91a20d0ef9SDaniel Lezcano 	list_for_each_entry(child, &dtpm->children, sibling) {
92a20d0ef9SDaniel Lezcano 
93a20d0ef9SDaniel Lezcano 		pr_debug("Setting weight '%d' for '%s'\n",
94a20d0ef9SDaniel Lezcano 			 child->weight, child->zone.name);
95a20d0ef9SDaniel Lezcano 
968f50db4bSDaniel Lezcano 		child->weight = DIV64_U64_ROUND_CLOSEST(
978f50db4bSDaniel Lezcano 			child->power_max * 1024, dtpm->power_max);
98a20d0ef9SDaniel Lezcano 
99a20d0ef9SDaniel Lezcano 		__dtpm_rebalance_weight(child);
100a20d0ef9SDaniel Lezcano 	}
101a20d0ef9SDaniel Lezcano }
102a20d0ef9SDaniel Lezcano 
__dtpm_sub_power(struct dtpm * dtpm)103a20d0ef9SDaniel Lezcano static void __dtpm_sub_power(struct dtpm *dtpm)
104a20d0ef9SDaniel Lezcano {
105a20d0ef9SDaniel Lezcano 	struct dtpm *parent = dtpm->parent;
106a20d0ef9SDaniel Lezcano 
107a20d0ef9SDaniel Lezcano 	while (parent) {
108a20d0ef9SDaniel Lezcano 		parent->power_min -= dtpm->power_min;
109a20d0ef9SDaniel Lezcano 		parent->power_max -= dtpm->power_max;
110a20d0ef9SDaniel Lezcano 		parent->power_limit -= dtpm->power_limit;
111a20d0ef9SDaniel Lezcano 		parent = parent->parent;
112a20d0ef9SDaniel Lezcano 	}
113a20d0ef9SDaniel Lezcano }
114a20d0ef9SDaniel Lezcano 
__dtpm_add_power(struct dtpm * dtpm)115a20d0ef9SDaniel Lezcano static void __dtpm_add_power(struct dtpm *dtpm)
116a20d0ef9SDaniel Lezcano {
117a20d0ef9SDaniel Lezcano 	struct dtpm *parent = dtpm->parent;
118a20d0ef9SDaniel Lezcano 
119a20d0ef9SDaniel Lezcano 	while (parent) {
120a20d0ef9SDaniel Lezcano 		parent->power_min += dtpm->power_min;
121a20d0ef9SDaniel Lezcano 		parent->power_max += dtpm->power_max;
122a20d0ef9SDaniel Lezcano 		parent->power_limit += dtpm->power_limit;
123a20d0ef9SDaniel Lezcano 		parent = parent->parent;
124a20d0ef9SDaniel Lezcano 	}
1254570dddaSDaniel Lezcano }
126a20d0ef9SDaniel Lezcano 
1277b75bbdfSDaniel Lezcano /**
1287b75bbdfSDaniel Lezcano  * dtpm_update_power - Update the power on the dtpm
1297b75bbdfSDaniel Lezcano  * @dtpm: a pointer to a dtpm structure to update
1307b75bbdfSDaniel Lezcano  *
1317b75bbdfSDaniel Lezcano  * Function to update the power values of the dtpm node specified in
1327b75bbdfSDaniel Lezcano  * parameter. These new values will be propagated to the tree.
1337b75bbdfSDaniel Lezcano  *
1347b75bbdfSDaniel Lezcano  * Return: zero on success, -EINVAL if the values are inconsistent
1357b75bbdfSDaniel Lezcano  */
dtpm_update_power(struct dtpm * dtpm)1367b75bbdfSDaniel Lezcano int dtpm_update_power(struct dtpm *dtpm)
1374570dddaSDaniel Lezcano {
1384570dddaSDaniel Lezcano 	int ret;
1394570dddaSDaniel Lezcano 
1404570dddaSDaniel Lezcano 	__dtpm_sub_power(dtpm);
1414570dddaSDaniel Lezcano 
1424570dddaSDaniel Lezcano 	ret = dtpm->ops->update_power_uw(dtpm);
1434570dddaSDaniel Lezcano 	if (ret)
1444570dddaSDaniel Lezcano 		pr_err("Failed to update power for '%s': %d\n",
1454570dddaSDaniel Lezcano 		       dtpm->zone.name, ret);
1464570dddaSDaniel Lezcano 
1474570dddaSDaniel Lezcano 	if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags))
1484570dddaSDaniel Lezcano 		dtpm->power_limit = dtpm->power_max;
1494570dddaSDaniel Lezcano 
1504570dddaSDaniel Lezcano 	__dtpm_add_power(dtpm);
1514570dddaSDaniel Lezcano 
1524570dddaSDaniel Lezcano 	if (root)
153a20d0ef9SDaniel Lezcano 		__dtpm_rebalance_weight(root);
1544570dddaSDaniel Lezcano 
1554570dddaSDaniel Lezcano 	return ret;
156a20d0ef9SDaniel Lezcano }
157a20d0ef9SDaniel Lezcano 
158a20d0ef9SDaniel Lezcano /**
159a20d0ef9SDaniel Lezcano  * dtpm_release_zone - Cleanup when the node is released
160a20d0ef9SDaniel Lezcano  * @pcz: a pointer to a powercap_zone structure
161a20d0ef9SDaniel Lezcano  *
162a20d0ef9SDaniel Lezcano  * Do some housecleaning and update the weight on the tree. The
163a20d0ef9SDaniel Lezcano  * release will be denied if the node has children. This function must
164a20d0ef9SDaniel Lezcano  * be called by the specific release callback of the different
165a20d0ef9SDaniel Lezcano  * backends.
166a20d0ef9SDaniel Lezcano  *
167a20d0ef9SDaniel Lezcano  * Return: 0 on success, -EBUSY if there are children
168a20d0ef9SDaniel Lezcano  */
dtpm_release_zone(struct powercap_zone * pcz)169a20d0ef9SDaniel Lezcano int dtpm_release_zone(struct powercap_zone *pcz)
170a20d0ef9SDaniel Lezcano {
171a20d0ef9SDaniel Lezcano 	struct dtpm *dtpm = to_dtpm(pcz);
172a20d0ef9SDaniel Lezcano 	struct dtpm *parent = dtpm->parent;
173a20d0ef9SDaniel Lezcano 
1747b75bbdfSDaniel Lezcano 	if (!list_empty(&dtpm->children))
175a20d0ef9SDaniel Lezcano 		return -EBUSY;
176a20d0ef9SDaniel Lezcano 
177a20d0ef9SDaniel Lezcano 	if (parent)
178a20d0ef9SDaniel Lezcano 		list_del(&dtpm->sibling);
179a20d0ef9SDaniel Lezcano 
180a20d0ef9SDaniel Lezcano 	__dtpm_sub_power(dtpm);
181a20d0ef9SDaniel Lezcano 
182a20d0ef9SDaniel Lezcano 	if (dtpm->ops)
183a20d0ef9SDaniel Lezcano 		dtpm->ops->release(dtpm);
184690de0b4SDaniel Lezcano 	else
185690de0b4SDaniel Lezcano 		kfree(dtpm);
186a20d0ef9SDaniel Lezcano 
187a20d0ef9SDaniel Lezcano 	return 0;
188a20d0ef9SDaniel Lezcano }
189a20d0ef9SDaniel Lezcano 
get_power_limit_uw(struct powercap_zone * pcz,int cid,u64 * power_limit)190a20d0ef9SDaniel Lezcano static int get_power_limit_uw(struct powercap_zone *pcz,
191a20d0ef9SDaniel Lezcano 			      int cid, u64 *power_limit)
192a20d0ef9SDaniel Lezcano {
1937b75bbdfSDaniel Lezcano 	*power_limit = to_dtpm(pcz)->power_limit;
194a20d0ef9SDaniel Lezcano 
1957b75bbdfSDaniel Lezcano 	return 0;
196a20d0ef9SDaniel Lezcano }
197a20d0ef9SDaniel Lezcano 
198a20d0ef9SDaniel Lezcano /*
199a20d0ef9SDaniel Lezcano  * Set the power limit on the nodes, the power limit is distributed
200a20d0ef9SDaniel Lezcano  * given the weight of the children.
201a20d0ef9SDaniel Lezcano  *
202a20d0ef9SDaniel Lezcano  * The dtpm node lock must be held when calling this function.
203a20d0ef9SDaniel Lezcano  */
__set_power_limit_uw(struct dtpm * dtpm,int cid,u64 power_limit)204a20d0ef9SDaniel Lezcano static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
205a20d0ef9SDaniel Lezcano {
206a20d0ef9SDaniel Lezcano 	struct dtpm *child;
207a20d0ef9SDaniel Lezcano 	int ret = 0;
208a20d0ef9SDaniel Lezcano 	u64 power;
209a20d0ef9SDaniel Lezcano 
210a20d0ef9SDaniel Lezcano 	/*
211a20d0ef9SDaniel Lezcano 	 * A max power limitation means we remove the power limit,
212a20d0ef9SDaniel Lezcano 	 * otherwise we set a constraint and flag the dtpm node.
213a20d0ef9SDaniel Lezcano 	 */
214a20d0ef9SDaniel Lezcano 	if (power_limit == dtpm->power_max) {
215a20d0ef9SDaniel Lezcano 		clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
216a20d0ef9SDaniel Lezcano 	} else {
217a20d0ef9SDaniel Lezcano 		set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
218a20d0ef9SDaniel Lezcano 	}
219a20d0ef9SDaniel Lezcano 
220a20d0ef9SDaniel Lezcano 	pr_debug("Setting power limit for '%s': %llu uW\n",
221a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, power_limit);
222a20d0ef9SDaniel Lezcano 
223a20d0ef9SDaniel Lezcano 	/*
224a20d0ef9SDaniel Lezcano 	 * Only leaves of the dtpm tree has ops to get/set the power
225a20d0ef9SDaniel Lezcano 	 */
226a20d0ef9SDaniel Lezcano 	if (dtpm->ops) {
227a20d0ef9SDaniel Lezcano 		dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
228a20d0ef9SDaniel Lezcano 	} else {
229a20d0ef9SDaniel Lezcano 		dtpm->power_limit = 0;
230a20d0ef9SDaniel Lezcano 
231a20d0ef9SDaniel Lezcano 		list_for_each_entry(child, &dtpm->children, sibling) {
232a20d0ef9SDaniel Lezcano 
233a20d0ef9SDaniel Lezcano 			/*
234a20d0ef9SDaniel Lezcano 			 * Integer division rounding will inevitably
235a20d0ef9SDaniel Lezcano 			 * lead to a different min or max value when
236a20d0ef9SDaniel Lezcano 			 * set several times. In order to restore the
237a20d0ef9SDaniel Lezcano 			 * initial value, we force the child's min or
238a20d0ef9SDaniel Lezcano 			 * max power every time if the constraint is
239a20d0ef9SDaniel Lezcano 			 * at the boundaries.
240a20d0ef9SDaniel Lezcano 			 */
241a20d0ef9SDaniel Lezcano 			if (power_limit == dtpm->power_max) {
242a20d0ef9SDaniel Lezcano 				power = child->power_max;
243a20d0ef9SDaniel Lezcano 			} else if (power_limit == dtpm->power_min) {
244a20d0ef9SDaniel Lezcano 				power = child->power_min;
245a20d0ef9SDaniel Lezcano 			} else {
2468f50db4bSDaniel Lezcano 				power = DIV_ROUND_CLOSEST_ULL(
247a20d0ef9SDaniel Lezcano 					power_limit * child->weight, 1024);
248a20d0ef9SDaniel Lezcano 			}
249a20d0ef9SDaniel Lezcano 
250a20d0ef9SDaniel Lezcano 			pr_debug("Setting power limit for '%s': %llu uW\n",
251a20d0ef9SDaniel Lezcano 				 child->zone.name, power);
252a20d0ef9SDaniel Lezcano 
253a20d0ef9SDaniel Lezcano 			ret = __set_power_limit_uw(child, cid, power);
254a20d0ef9SDaniel Lezcano 			if (!ret)
2557b75bbdfSDaniel Lezcano 				ret = get_power_limit_uw(&child->zone, cid, &power);
256a20d0ef9SDaniel Lezcano 
257a20d0ef9SDaniel Lezcano 			if (ret)
258a20d0ef9SDaniel Lezcano 				break;
259a20d0ef9SDaniel Lezcano 
260a20d0ef9SDaniel Lezcano 			dtpm->power_limit += power;
261a20d0ef9SDaniel Lezcano 		}
262a20d0ef9SDaniel Lezcano 	}
263a20d0ef9SDaniel Lezcano 
264a20d0ef9SDaniel Lezcano 	return ret;
265a20d0ef9SDaniel Lezcano }
266a20d0ef9SDaniel Lezcano 
set_power_limit_uw(struct powercap_zone * pcz,int cid,u64 power_limit)267a20d0ef9SDaniel Lezcano static int set_power_limit_uw(struct powercap_zone *pcz,
268a20d0ef9SDaniel Lezcano 			      int cid, u64 power_limit)
269a20d0ef9SDaniel Lezcano {
270a20d0ef9SDaniel Lezcano 	struct dtpm *dtpm = to_dtpm(pcz);
271a20d0ef9SDaniel Lezcano 	int ret;
272a20d0ef9SDaniel Lezcano 
273a20d0ef9SDaniel Lezcano 	/*
274a20d0ef9SDaniel Lezcano 	 * Don't allow values outside of the power range previously
275a20d0ef9SDaniel Lezcano 	 * set when initializing the power numbers.
276a20d0ef9SDaniel Lezcano 	 */
277a20d0ef9SDaniel Lezcano 	power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
278a20d0ef9SDaniel Lezcano 
279a20d0ef9SDaniel Lezcano 	ret = __set_power_limit_uw(dtpm, cid, power_limit);
280a20d0ef9SDaniel Lezcano 
281a20d0ef9SDaniel Lezcano 	pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
282a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
283a20d0ef9SDaniel Lezcano 
284a20d0ef9SDaniel Lezcano 	return ret;
285a20d0ef9SDaniel Lezcano }
286a20d0ef9SDaniel Lezcano 
get_constraint_name(struct powercap_zone * pcz,int cid)287a20d0ef9SDaniel Lezcano static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
288a20d0ef9SDaniel Lezcano {
289a20d0ef9SDaniel Lezcano 	return constraint_name[cid];
290a20d0ef9SDaniel Lezcano }
291a20d0ef9SDaniel Lezcano 
get_max_power_uw(struct powercap_zone * pcz,int id,u64 * max_power)292a20d0ef9SDaniel Lezcano static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
293a20d0ef9SDaniel Lezcano {
2947b75bbdfSDaniel Lezcano 	*max_power = to_dtpm(pcz)->power_max;
295a20d0ef9SDaniel Lezcano 
296a20d0ef9SDaniel Lezcano 	return 0;
297a20d0ef9SDaniel Lezcano }
298a20d0ef9SDaniel Lezcano 
299a20d0ef9SDaniel Lezcano static struct powercap_zone_constraint_ops constraint_ops = {
300a20d0ef9SDaniel Lezcano 	.set_power_limit_uw = set_power_limit_uw,
301a20d0ef9SDaniel Lezcano 	.get_power_limit_uw = get_power_limit_uw,
302a20d0ef9SDaniel Lezcano 	.set_time_window_us = set_time_window_us,
303a20d0ef9SDaniel Lezcano 	.get_time_window_us = get_time_window_us,
304a20d0ef9SDaniel Lezcano 	.get_max_power_uw = get_max_power_uw,
305a20d0ef9SDaniel Lezcano 	.get_name = get_constraint_name,
306a20d0ef9SDaniel Lezcano };
307a20d0ef9SDaniel Lezcano 
308a20d0ef9SDaniel Lezcano static struct powercap_zone_ops zone_ops = {
309a20d0ef9SDaniel Lezcano 	.get_max_power_range_uw = get_max_power_range_uw,
310a20d0ef9SDaniel Lezcano 	.get_power_uw = get_power_uw,
311a20d0ef9SDaniel Lezcano 	.release = dtpm_release_zone,
312a20d0ef9SDaniel Lezcano };
313a20d0ef9SDaniel Lezcano 
314a20d0ef9SDaniel Lezcano /**
315d2cdc6adSDaniel Lezcano  * dtpm_init - Allocate and initialize a dtpm struct
316d2cdc6adSDaniel Lezcano  * @dtpm: The dtpm struct pointer to be initialized
317d2cdc6adSDaniel Lezcano  * @ops: The dtpm device specific ops, NULL for a virtual node
318a20d0ef9SDaniel Lezcano  */
dtpm_init(struct dtpm * dtpm,struct dtpm_ops * ops)319d2cdc6adSDaniel Lezcano void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
320a20d0ef9SDaniel Lezcano {
321a20d0ef9SDaniel Lezcano 	if (dtpm) {
322a20d0ef9SDaniel Lezcano 		INIT_LIST_HEAD(&dtpm->children);
323a20d0ef9SDaniel Lezcano 		INIT_LIST_HEAD(&dtpm->sibling);
324a20d0ef9SDaniel Lezcano 		dtpm->weight = 1024;
325a20d0ef9SDaniel Lezcano 		dtpm->ops = ops;
326a20d0ef9SDaniel Lezcano 	}
327a20d0ef9SDaniel Lezcano }
328a20d0ef9SDaniel Lezcano 
329a20d0ef9SDaniel Lezcano /**
330a20d0ef9SDaniel Lezcano  * dtpm_unregister - Unregister a dtpm node from the hierarchy tree
331a20d0ef9SDaniel Lezcano  * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
332a20d0ef9SDaniel Lezcano  *
333a20d0ef9SDaniel Lezcano  * Call the underlying powercap unregister function. That will call
334a20d0ef9SDaniel Lezcano  * the release callback of the powercap zone.
335a20d0ef9SDaniel Lezcano  */
dtpm_unregister(struct dtpm * dtpm)336a20d0ef9SDaniel Lezcano void dtpm_unregister(struct dtpm *dtpm)
337a20d0ef9SDaniel Lezcano {
338a20d0ef9SDaniel Lezcano 	powercap_unregister_zone(pct, &dtpm->zone);
339a20d0ef9SDaniel Lezcano 
340c1af85e4SDaniel Lezcano 	pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
341a20d0ef9SDaniel Lezcano }
342a20d0ef9SDaniel Lezcano 
343a20d0ef9SDaniel Lezcano /**
344a20d0ef9SDaniel Lezcano  * dtpm_register - Register a dtpm node in the hierarchy tree
345a20d0ef9SDaniel Lezcano  * @name: a string specifying the name of the node
346a20d0ef9SDaniel Lezcano  * @dtpm: a pointer to a dtpm structure corresponding to the new node
347a20d0ef9SDaniel Lezcano  * @parent: a pointer to a dtpm structure corresponding to the parent node
348a20d0ef9SDaniel Lezcano  *
349a20d0ef9SDaniel Lezcano  * Create a dtpm node in the tree. If no parent is specified, the node
350a20d0ef9SDaniel Lezcano  * is the root node of the hierarchy. If the root node already exists,
351a20d0ef9SDaniel Lezcano  * then the registration will fail. The powercap controller must be
352a20d0ef9SDaniel Lezcano  * initialized before calling this function.
353a20d0ef9SDaniel Lezcano  *
354a20d0ef9SDaniel Lezcano  * The dtpm structure must be initialized with the power numbers
355a20d0ef9SDaniel Lezcano  * before calling this function.
356a20d0ef9SDaniel Lezcano  *
357a20d0ef9SDaniel Lezcano  * Return: zero on success, a negative value in case of error:
358a20d0ef9SDaniel Lezcano  *  -EAGAIN: the function is called before the framework is initialized.
359a20d0ef9SDaniel Lezcano  *  -EBUSY: the root node is already inserted
360a20d0ef9SDaniel Lezcano  *  -EINVAL: * there is no root node yet and @parent is specified
361a20d0ef9SDaniel Lezcano  *           * no all ops are defined
362a20d0ef9SDaniel Lezcano  *           * parent have ops which are reserved for leaves
363a20d0ef9SDaniel Lezcano  *   Other negative values are reported back from the powercap framework
364a20d0ef9SDaniel Lezcano  */
dtpm_register(const char * name,struct dtpm * dtpm,struct dtpm * parent)365a20d0ef9SDaniel Lezcano int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
366a20d0ef9SDaniel Lezcano {
367a20d0ef9SDaniel Lezcano 	struct powercap_zone *pcz;
368a20d0ef9SDaniel Lezcano 
369a20d0ef9SDaniel Lezcano 	if (!pct)
370a20d0ef9SDaniel Lezcano 		return -EAGAIN;
371a20d0ef9SDaniel Lezcano 
372a20d0ef9SDaniel Lezcano 	if (root && !parent)
373a20d0ef9SDaniel Lezcano 		return -EBUSY;
374a20d0ef9SDaniel Lezcano 
375a20d0ef9SDaniel Lezcano 	if (!root && parent)
376a20d0ef9SDaniel Lezcano 		return -EINVAL;
377a20d0ef9SDaniel Lezcano 
378a20d0ef9SDaniel Lezcano 	if (parent && parent->ops)
379a20d0ef9SDaniel Lezcano 		return -EINVAL;
380a20d0ef9SDaniel Lezcano 
381a20d0ef9SDaniel Lezcano 	if (!dtpm)
382a20d0ef9SDaniel Lezcano 		return -EINVAL;
383a20d0ef9SDaniel Lezcano 
384a20d0ef9SDaniel Lezcano 	if (dtpm->ops && !(dtpm->ops->set_power_uw &&
385a20d0ef9SDaniel Lezcano 			   dtpm->ops->get_power_uw &&
3864570dddaSDaniel Lezcano 			   dtpm->ops->update_power_uw &&
387a20d0ef9SDaniel Lezcano 			   dtpm->ops->release))
388a20d0ef9SDaniel Lezcano 		return -EINVAL;
389a20d0ef9SDaniel Lezcano 
390a20d0ef9SDaniel Lezcano 	pcz = powercap_register_zone(&dtpm->zone, pct, name,
391a20d0ef9SDaniel Lezcano 				     parent ? &parent->zone : NULL,
392a20d0ef9SDaniel Lezcano 				     &zone_ops, MAX_DTPM_CONSTRAINTS,
393a20d0ef9SDaniel Lezcano 				     &constraint_ops);
394a20d0ef9SDaniel Lezcano 	if (IS_ERR(pcz))
395a20d0ef9SDaniel Lezcano 		return PTR_ERR(pcz);
396a20d0ef9SDaniel Lezcano 
397a20d0ef9SDaniel Lezcano 	if (parent) {
398a20d0ef9SDaniel Lezcano 		list_add_tail(&dtpm->sibling, &parent->children);
399a20d0ef9SDaniel Lezcano 		dtpm->parent = parent;
400a20d0ef9SDaniel Lezcano 	} else {
401a20d0ef9SDaniel Lezcano 		root = dtpm;
402a20d0ef9SDaniel Lezcano 	}
403a20d0ef9SDaniel Lezcano 
4045d8cb8dbSDaniel Lezcano 	if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
405a20d0ef9SDaniel Lezcano 		__dtpm_add_power(dtpm);
4065d8cb8dbSDaniel Lezcano 		dtpm->power_limit = dtpm->power_max;
4075d8cb8dbSDaniel Lezcano 	}
408a20d0ef9SDaniel Lezcano 
409c1af85e4SDaniel Lezcano 	pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
410a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, dtpm->power_min, dtpm->power_max);
411a20d0ef9SDaniel Lezcano 
412a20d0ef9SDaniel Lezcano 	return 0;
413a20d0ef9SDaniel Lezcano }
414a20d0ef9SDaniel Lezcano 
dtpm_setup_virtual(const struct dtpm_node * hierarchy,struct dtpm * parent)4153759ec67SDaniel Lezcano static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
4163759ec67SDaniel Lezcano 				       struct dtpm *parent)
417a20d0ef9SDaniel Lezcano {
4183759ec67SDaniel Lezcano 	struct dtpm *dtpm;
4193759ec67SDaniel Lezcano 	int ret;
4203759ec67SDaniel Lezcano 
4213759ec67SDaniel Lezcano 	dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
4223759ec67SDaniel Lezcano 	if (!dtpm)
4233759ec67SDaniel Lezcano 		return ERR_PTR(-ENOMEM);
4243759ec67SDaniel Lezcano 	dtpm_init(dtpm, NULL);
4253759ec67SDaniel Lezcano 
4263759ec67SDaniel Lezcano 	ret = dtpm_register(hierarchy->name, dtpm, parent);
4273759ec67SDaniel Lezcano 	if (ret) {
4283759ec67SDaniel Lezcano 		pr_err("Failed to register dtpm node '%s': %d\n",
4293759ec67SDaniel Lezcano 		       hierarchy->name, ret);
4303759ec67SDaniel Lezcano 		kfree(dtpm);
4313759ec67SDaniel Lezcano 		return ERR_PTR(ret);
4323759ec67SDaniel Lezcano 	}
4333759ec67SDaniel Lezcano 
4343759ec67SDaniel Lezcano 	return dtpm;
4353759ec67SDaniel Lezcano }
4363759ec67SDaniel Lezcano 
dtpm_setup_dt(const struct dtpm_node * hierarchy,struct dtpm * parent)4373759ec67SDaniel Lezcano static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
4383759ec67SDaniel Lezcano 				  struct dtpm *parent)
4393759ec67SDaniel Lezcano {
4403759ec67SDaniel Lezcano 	struct device_node *np;
4413759ec67SDaniel Lezcano 	int i, ret;
4423759ec67SDaniel Lezcano 
4433759ec67SDaniel Lezcano 	np = of_find_node_by_path(hierarchy->name);
4443759ec67SDaniel Lezcano 	if (!np) {
4453759ec67SDaniel Lezcano 		pr_err("Failed to find '%s'\n", hierarchy->name);
4463759ec67SDaniel Lezcano 		return ERR_PTR(-ENXIO);
4473759ec67SDaniel Lezcano 	}
4483759ec67SDaniel Lezcano 
4493759ec67SDaniel Lezcano 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
4503759ec67SDaniel Lezcano 
4513759ec67SDaniel Lezcano 		if (!dtpm_subsys[i]->setup)
4523759ec67SDaniel Lezcano 			continue;
4533759ec67SDaniel Lezcano 
4543759ec67SDaniel Lezcano 		ret = dtpm_subsys[i]->setup(parent, np);
4553759ec67SDaniel Lezcano 		if (ret) {
4563759ec67SDaniel Lezcano 			pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
4573759ec67SDaniel Lezcano 			of_node_put(np);
4583759ec67SDaniel Lezcano 			return ERR_PTR(ret);
4593759ec67SDaniel Lezcano 		}
4603759ec67SDaniel Lezcano 	}
4613759ec67SDaniel Lezcano 
4623759ec67SDaniel Lezcano 	of_node_put(np);
4633759ec67SDaniel Lezcano 
4643759ec67SDaniel Lezcano 	/*
4653759ec67SDaniel Lezcano 	 * By returning a NULL pointer, we let know the caller there
4663759ec67SDaniel Lezcano 	 * is no child for us as we are a leaf of the tree
4673759ec67SDaniel Lezcano 	 */
4683759ec67SDaniel Lezcano 	return NULL;
4693759ec67SDaniel Lezcano }
4703759ec67SDaniel Lezcano 
4713759ec67SDaniel Lezcano typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
4723759ec67SDaniel Lezcano 
4735bf19d0aSkernel test robot static dtpm_node_callback_t dtpm_node_callback[] = {
4743759ec67SDaniel Lezcano 	[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
4753759ec67SDaniel Lezcano 	[DTPM_NODE_DT] = dtpm_setup_dt,
4763759ec67SDaniel Lezcano };
4773759ec67SDaniel Lezcano 
dtpm_for_each_child(const struct dtpm_node * hierarchy,const struct dtpm_node * it,struct dtpm * parent)4783759ec67SDaniel Lezcano static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
4793759ec67SDaniel Lezcano 			       const struct dtpm_node *it, struct dtpm *parent)
4803759ec67SDaniel Lezcano {
4813759ec67SDaniel Lezcano 	struct dtpm *dtpm;
4823759ec67SDaniel Lezcano 	int i, ret;
4833759ec67SDaniel Lezcano 
4843759ec67SDaniel Lezcano 	for (i = 0; hierarchy[i].name; i++) {
4853759ec67SDaniel Lezcano 
4863759ec67SDaniel Lezcano 		if (hierarchy[i].parent != it)
4873759ec67SDaniel Lezcano 			continue;
4883759ec67SDaniel Lezcano 
4893759ec67SDaniel Lezcano 		dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
4903759ec67SDaniel Lezcano 
4913759ec67SDaniel Lezcano 		/*
4923759ec67SDaniel Lezcano 		 * A NULL pointer means there is no children, hence we
4933759ec67SDaniel Lezcano 		 * continue without going deeper in the recursivity.
4943759ec67SDaniel Lezcano 		 */
4953759ec67SDaniel Lezcano 		if (!dtpm)
4963759ec67SDaniel Lezcano 			continue;
4973759ec67SDaniel Lezcano 
4983759ec67SDaniel Lezcano 		/*
4993759ec67SDaniel Lezcano 		 * There are multiple reasons why the callback could
5003759ec67SDaniel Lezcano 		 * fail. The generic glue is abstracting the backend
5013759ec67SDaniel Lezcano 		 * and therefore it is not possible to report back or
5023759ec67SDaniel Lezcano 		 * take a decision based on the error.  In any case,
5033759ec67SDaniel Lezcano 		 * if this call fails, it is not critical in the
5043759ec67SDaniel Lezcano 		 * hierarchy creation, we can assume the underlying
5053759ec67SDaniel Lezcano 		 * service is not found, so we continue without this
5063759ec67SDaniel Lezcano 		 * branch in the tree but with a warning to log the
5073759ec67SDaniel Lezcano 		 * information the node was not created.
5083759ec67SDaniel Lezcano 		 */
5093759ec67SDaniel Lezcano 		if (IS_ERR(dtpm)) {
5103759ec67SDaniel Lezcano 			pr_warn("Failed to create '%s' in the hierarchy\n",
5113759ec67SDaniel Lezcano 				hierarchy[i].name);
5123759ec67SDaniel Lezcano 			continue;
5133759ec67SDaniel Lezcano 		}
5143759ec67SDaniel Lezcano 
5153759ec67SDaniel Lezcano 		ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
5163759ec67SDaniel Lezcano 		if (ret)
5173759ec67SDaniel Lezcano 			return ret;
518a20d0ef9SDaniel Lezcano 	}
519a20d0ef9SDaniel Lezcano 
520a20d0ef9SDaniel Lezcano 	return 0;
521a20d0ef9SDaniel Lezcano }
5223759ec67SDaniel Lezcano 
5233759ec67SDaniel Lezcano /**
5243759ec67SDaniel Lezcano  * dtpm_create_hierarchy - Create the dtpm hierarchy
5253759ec67SDaniel Lezcano  * @hierarchy: An array of struct dtpm_node describing the hierarchy
5263759ec67SDaniel Lezcano  *
5273759ec67SDaniel Lezcano  * The function is called by the platform specific code with the
5283759ec67SDaniel Lezcano  * description of the different node in the hierarchy. It creates the
5293759ec67SDaniel Lezcano  * tree in the sysfs filesystem under the powercap dtpm entry.
5303759ec67SDaniel Lezcano  *
5313759ec67SDaniel Lezcano  * The expected tree has the format:
5323759ec67SDaniel Lezcano  *
5333759ec67SDaniel Lezcano  * struct dtpm_node hierarchy[] = {
5343759ec67SDaniel Lezcano  *	[0] { .name = "topmost", type =  DTPM_NODE_VIRTUAL },
5353759ec67SDaniel Lezcano  *	[1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
5363759ec67SDaniel Lezcano  *	[2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5373759ec67SDaniel Lezcano  *	[3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5383759ec67SDaniel Lezcano  *	[4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5393759ec67SDaniel Lezcano  *	[5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5403759ec67SDaniel Lezcano  *	[6] { }
5413759ec67SDaniel Lezcano  * };
5423759ec67SDaniel Lezcano  *
5433759ec67SDaniel Lezcano  * The last element is always an empty one and marks the end of the
5443759ec67SDaniel Lezcano  * array.
5453759ec67SDaniel Lezcano  *
5463759ec67SDaniel Lezcano  * Return: zero on success, a negative value in case of error. Errors
5473759ec67SDaniel Lezcano  * are reported back from the underlying functions.
5483759ec67SDaniel Lezcano  */
dtpm_create_hierarchy(struct of_device_id * dtpm_match_table)5493759ec67SDaniel Lezcano int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
5503759ec67SDaniel Lezcano {
5513759ec67SDaniel Lezcano 	const struct of_device_id *match;
5523759ec67SDaniel Lezcano 	const struct dtpm_node *hierarchy;
5533759ec67SDaniel Lezcano 	struct device_node *np;
5543759ec67SDaniel Lezcano 	int i, ret;
5553759ec67SDaniel Lezcano 
5567b75bbdfSDaniel Lezcano 	mutex_lock(&dtpm_lock);
5577b75bbdfSDaniel Lezcano 
5587b75bbdfSDaniel Lezcano 	if (pct) {
5597b75bbdfSDaniel Lezcano 		ret = -EBUSY;
5607b75bbdfSDaniel Lezcano 		goto out_unlock;
5617b75bbdfSDaniel Lezcano 	}
5623759ec67SDaniel Lezcano 
5633759ec67SDaniel Lezcano 	pct = powercap_register_control_type(NULL, "dtpm", NULL);
5643759ec67SDaniel Lezcano 	if (IS_ERR(pct)) {
5653759ec67SDaniel Lezcano 		pr_err("Failed to register control type\n");
5663759ec67SDaniel Lezcano 		ret = PTR_ERR(pct);
5673759ec67SDaniel Lezcano 		goto out_pct;
5683759ec67SDaniel Lezcano 	}
5693759ec67SDaniel Lezcano 
5703759ec67SDaniel Lezcano 	ret = -ENODEV;
5713759ec67SDaniel Lezcano 	np = of_find_node_by_path("/");
5723759ec67SDaniel Lezcano 	if (!np)
5733759ec67SDaniel Lezcano 		goto out_err;
5743759ec67SDaniel Lezcano 
5753759ec67SDaniel Lezcano 	match = of_match_node(dtpm_match_table, np);
5763759ec67SDaniel Lezcano 
5773759ec67SDaniel Lezcano 	of_node_put(np);
5783759ec67SDaniel Lezcano 
5793759ec67SDaniel Lezcano 	if (!match)
5803759ec67SDaniel Lezcano 		goto out_err;
5813759ec67SDaniel Lezcano 
5823759ec67SDaniel Lezcano 	hierarchy = match->data;
5833759ec67SDaniel Lezcano 	if (!hierarchy) {
5843759ec67SDaniel Lezcano 		ret = -EFAULT;
5853759ec67SDaniel Lezcano 		goto out_err;
5863759ec67SDaniel Lezcano 	}
5873759ec67SDaniel Lezcano 
5883759ec67SDaniel Lezcano 	ret = dtpm_for_each_child(hierarchy, NULL, NULL);
5893759ec67SDaniel Lezcano 	if (ret)
5903759ec67SDaniel Lezcano 		goto out_err;
5913759ec67SDaniel Lezcano 
5923759ec67SDaniel Lezcano 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
5933759ec67SDaniel Lezcano 
5943759ec67SDaniel Lezcano 		if (!dtpm_subsys[i]->init)
5953759ec67SDaniel Lezcano 			continue;
5963759ec67SDaniel Lezcano 
5973759ec67SDaniel Lezcano 		ret = dtpm_subsys[i]->init();
5983759ec67SDaniel Lezcano 		if (ret)
599*55ddcd9fSColin Ian King 			pr_info("Failed to initialize '%s': %d",
6003759ec67SDaniel Lezcano 				dtpm_subsys[i]->name, ret);
6013759ec67SDaniel Lezcano 	}
6023759ec67SDaniel Lezcano 
6037b75bbdfSDaniel Lezcano 	mutex_unlock(&dtpm_lock);
6047b75bbdfSDaniel Lezcano 
6053759ec67SDaniel Lezcano 	return 0;
6063759ec67SDaniel Lezcano 
6073759ec67SDaniel Lezcano out_err:
6083759ec67SDaniel Lezcano 	powercap_unregister_control_type(pct);
6093759ec67SDaniel Lezcano out_pct:
6103759ec67SDaniel Lezcano 	pct = NULL;
6117b75bbdfSDaniel Lezcano out_unlock:
6127b75bbdfSDaniel Lezcano 	mutex_unlock(&dtpm_lock);
6133759ec67SDaniel Lezcano 
6143759ec67SDaniel Lezcano 	return ret;
6153759ec67SDaniel Lezcano }
6163759ec67SDaniel Lezcano EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
617c404c64dSDaniel Lezcano 
__dtpm_destroy_hierarchy(struct dtpm * dtpm)618c404c64dSDaniel Lezcano static void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
619c404c64dSDaniel Lezcano {
620c404c64dSDaniel Lezcano 	struct dtpm *child, *aux;
621c404c64dSDaniel Lezcano 
622c404c64dSDaniel Lezcano 	list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
623c404c64dSDaniel Lezcano 		__dtpm_destroy_hierarchy(child);
624c404c64dSDaniel Lezcano 
625c404c64dSDaniel Lezcano 	/*
626c404c64dSDaniel Lezcano 	 * At this point, we know all children were removed from the
627c404c64dSDaniel Lezcano 	 * recursive call before
628c404c64dSDaniel Lezcano 	 */
629c404c64dSDaniel Lezcano 	dtpm_unregister(dtpm);
630c404c64dSDaniel Lezcano }
631c404c64dSDaniel Lezcano 
dtpm_destroy_hierarchy(void)632c404c64dSDaniel Lezcano void dtpm_destroy_hierarchy(void)
633c404c64dSDaniel Lezcano {
634c404c64dSDaniel Lezcano 	int i;
635c404c64dSDaniel Lezcano 
636c404c64dSDaniel Lezcano 	mutex_lock(&dtpm_lock);
637c404c64dSDaniel Lezcano 
638c404c64dSDaniel Lezcano 	if (!pct)
639c404c64dSDaniel Lezcano 		goto out_unlock;
640c404c64dSDaniel Lezcano 
641c404c64dSDaniel Lezcano 	__dtpm_destroy_hierarchy(root);
642c404c64dSDaniel Lezcano 
643c404c64dSDaniel Lezcano 
644c404c64dSDaniel Lezcano 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
645c404c64dSDaniel Lezcano 
646c404c64dSDaniel Lezcano 		if (!dtpm_subsys[i]->exit)
647c404c64dSDaniel Lezcano 			continue;
648c404c64dSDaniel Lezcano 
649c404c64dSDaniel Lezcano 		dtpm_subsys[i]->exit();
650c404c64dSDaniel Lezcano 	}
651c404c64dSDaniel Lezcano 
652c404c64dSDaniel Lezcano 	powercap_unregister_control_type(pct);
653c404c64dSDaniel Lezcano 
654c404c64dSDaniel Lezcano 	pct = NULL;
655c404c64dSDaniel Lezcano 
6564712a236SDaniel Lezcano 	root = NULL;
6574712a236SDaniel Lezcano 
658c404c64dSDaniel Lezcano out_unlock:
659c404c64dSDaniel Lezcano 	mutex_unlock(&dtpm_lock);
660c404c64dSDaniel Lezcano }
661c404c64dSDaniel Lezcano EXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
662