xref: /openbmc/linux/drivers/powercap/dtpm.c (revision 690de0b4)
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 
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 
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 
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 
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 
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 
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 
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 
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  */
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  */
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);
184*690de0b4SDaniel Lezcano 	else
185*690de0b4SDaniel Lezcano 		kfree(dtpm);
186a20d0ef9SDaniel Lezcano 
187f3c14105SDaniel Lezcano 	if (root == dtpm)
188f3c14105SDaniel Lezcano 		root = NULL;
189f3c14105SDaniel Lezcano 
190a20d0ef9SDaniel Lezcano 	return 0;
191a20d0ef9SDaniel Lezcano }
192a20d0ef9SDaniel Lezcano 
193a20d0ef9SDaniel Lezcano static int get_power_limit_uw(struct powercap_zone *pcz,
194a20d0ef9SDaniel Lezcano 			      int cid, u64 *power_limit)
195a20d0ef9SDaniel Lezcano {
1967b75bbdfSDaniel Lezcano 	*power_limit = to_dtpm(pcz)->power_limit;
197a20d0ef9SDaniel Lezcano 
1987b75bbdfSDaniel Lezcano 	return 0;
199a20d0ef9SDaniel Lezcano }
200a20d0ef9SDaniel Lezcano 
201a20d0ef9SDaniel Lezcano /*
202a20d0ef9SDaniel Lezcano  * Set the power limit on the nodes, the power limit is distributed
203a20d0ef9SDaniel Lezcano  * given the weight of the children.
204a20d0ef9SDaniel Lezcano  *
205a20d0ef9SDaniel Lezcano  * The dtpm node lock must be held when calling this function.
206a20d0ef9SDaniel Lezcano  */
207a20d0ef9SDaniel Lezcano static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
208a20d0ef9SDaniel Lezcano {
209a20d0ef9SDaniel Lezcano 	struct dtpm *child;
210a20d0ef9SDaniel Lezcano 	int ret = 0;
211a20d0ef9SDaniel Lezcano 	u64 power;
212a20d0ef9SDaniel Lezcano 
213a20d0ef9SDaniel Lezcano 	/*
214a20d0ef9SDaniel Lezcano 	 * A max power limitation means we remove the power limit,
215a20d0ef9SDaniel Lezcano 	 * otherwise we set a constraint and flag the dtpm node.
216a20d0ef9SDaniel Lezcano 	 */
217a20d0ef9SDaniel Lezcano 	if (power_limit == dtpm->power_max) {
218a20d0ef9SDaniel Lezcano 		clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
219a20d0ef9SDaniel Lezcano 	} else {
220a20d0ef9SDaniel Lezcano 		set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
221a20d0ef9SDaniel Lezcano 	}
222a20d0ef9SDaniel Lezcano 
223a20d0ef9SDaniel Lezcano 	pr_debug("Setting power limit for '%s': %llu uW\n",
224a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, power_limit);
225a20d0ef9SDaniel Lezcano 
226a20d0ef9SDaniel Lezcano 	/*
227a20d0ef9SDaniel Lezcano 	 * Only leaves of the dtpm tree has ops to get/set the power
228a20d0ef9SDaniel Lezcano 	 */
229a20d0ef9SDaniel Lezcano 	if (dtpm->ops) {
230a20d0ef9SDaniel Lezcano 		dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
231a20d0ef9SDaniel Lezcano 	} else {
232a20d0ef9SDaniel Lezcano 		dtpm->power_limit = 0;
233a20d0ef9SDaniel Lezcano 
234a20d0ef9SDaniel Lezcano 		list_for_each_entry(child, &dtpm->children, sibling) {
235a20d0ef9SDaniel Lezcano 
236a20d0ef9SDaniel Lezcano 			/*
237a20d0ef9SDaniel Lezcano 			 * Integer division rounding will inevitably
238a20d0ef9SDaniel Lezcano 			 * lead to a different min or max value when
239a20d0ef9SDaniel Lezcano 			 * set several times. In order to restore the
240a20d0ef9SDaniel Lezcano 			 * initial value, we force the child's min or
241a20d0ef9SDaniel Lezcano 			 * max power every time if the constraint is
242a20d0ef9SDaniel Lezcano 			 * at the boundaries.
243a20d0ef9SDaniel Lezcano 			 */
244a20d0ef9SDaniel Lezcano 			if (power_limit == dtpm->power_max) {
245a20d0ef9SDaniel Lezcano 				power = child->power_max;
246a20d0ef9SDaniel Lezcano 			} else if (power_limit == dtpm->power_min) {
247a20d0ef9SDaniel Lezcano 				power = child->power_min;
248a20d0ef9SDaniel Lezcano 			} else {
2498f50db4bSDaniel Lezcano 				power = DIV_ROUND_CLOSEST_ULL(
250a20d0ef9SDaniel Lezcano 					power_limit * child->weight, 1024);
251a20d0ef9SDaniel Lezcano 			}
252a20d0ef9SDaniel Lezcano 
253a20d0ef9SDaniel Lezcano 			pr_debug("Setting power limit for '%s': %llu uW\n",
254a20d0ef9SDaniel Lezcano 				 child->zone.name, power);
255a20d0ef9SDaniel Lezcano 
256a20d0ef9SDaniel Lezcano 			ret = __set_power_limit_uw(child, cid, power);
257a20d0ef9SDaniel Lezcano 			if (!ret)
2587b75bbdfSDaniel Lezcano 				ret = get_power_limit_uw(&child->zone, cid, &power);
259a20d0ef9SDaniel Lezcano 
260a20d0ef9SDaniel Lezcano 			if (ret)
261a20d0ef9SDaniel Lezcano 				break;
262a20d0ef9SDaniel Lezcano 
263a20d0ef9SDaniel Lezcano 			dtpm->power_limit += power;
264a20d0ef9SDaniel Lezcano 		}
265a20d0ef9SDaniel Lezcano 	}
266a20d0ef9SDaniel Lezcano 
267a20d0ef9SDaniel Lezcano 	return ret;
268a20d0ef9SDaniel Lezcano }
269a20d0ef9SDaniel Lezcano 
270a20d0ef9SDaniel Lezcano static int set_power_limit_uw(struct powercap_zone *pcz,
271a20d0ef9SDaniel Lezcano 			      int cid, u64 power_limit)
272a20d0ef9SDaniel Lezcano {
273a20d0ef9SDaniel Lezcano 	struct dtpm *dtpm = to_dtpm(pcz);
274a20d0ef9SDaniel Lezcano 	int ret;
275a20d0ef9SDaniel Lezcano 
276a20d0ef9SDaniel Lezcano 	/*
277a20d0ef9SDaniel Lezcano 	 * Don't allow values outside of the power range previously
278a20d0ef9SDaniel Lezcano 	 * set when initializing the power numbers.
279a20d0ef9SDaniel Lezcano 	 */
280a20d0ef9SDaniel Lezcano 	power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
281a20d0ef9SDaniel Lezcano 
282a20d0ef9SDaniel Lezcano 	ret = __set_power_limit_uw(dtpm, cid, power_limit);
283a20d0ef9SDaniel Lezcano 
284a20d0ef9SDaniel Lezcano 	pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
285a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
286a20d0ef9SDaniel Lezcano 
287a20d0ef9SDaniel Lezcano 	return ret;
288a20d0ef9SDaniel Lezcano }
289a20d0ef9SDaniel Lezcano 
290a20d0ef9SDaniel Lezcano static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
291a20d0ef9SDaniel Lezcano {
292a20d0ef9SDaniel Lezcano 	return constraint_name[cid];
293a20d0ef9SDaniel Lezcano }
294a20d0ef9SDaniel Lezcano 
295a20d0ef9SDaniel Lezcano static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
296a20d0ef9SDaniel Lezcano {
2977b75bbdfSDaniel Lezcano 	*max_power = to_dtpm(pcz)->power_max;
298a20d0ef9SDaniel Lezcano 
299a20d0ef9SDaniel Lezcano 	return 0;
300a20d0ef9SDaniel Lezcano }
301a20d0ef9SDaniel Lezcano 
302a20d0ef9SDaniel Lezcano static struct powercap_zone_constraint_ops constraint_ops = {
303a20d0ef9SDaniel Lezcano 	.set_power_limit_uw = set_power_limit_uw,
304a20d0ef9SDaniel Lezcano 	.get_power_limit_uw = get_power_limit_uw,
305a20d0ef9SDaniel Lezcano 	.set_time_window_us = set_time_window_us,
306a20d0ef9SDaniel Lezcano 	.get_time_window_us = get_time_window_us,
307a20d0ef9SDaniel Lezcano 	.get_max_power_uw = get_max_power_uw,
308a20d0ef9SDaniel Lezcano 	.get_name = get_constraint_name,
309a20d0ef9SDaniel Lezcano };
310a20d0ef9SDaniel Lezcano 
311a20d0ef9SDaniel Lezcano static struct powercap_zone_ops zone_ops = {
312a20d0ef9SDaniel Lezcano 	.get_max_power_range_uw = get_max_power_range_uw,
313a20d0ef9SDaniel Lezcano 	.get_power_uw = get_power_uw,
314a20d0ef9SDaniel Lezcano 	.release = dtpm_release_zone,
315a20d0ef9SDaniel Lezcano };
316a20d0ef9SDaniel Lezcano 
317a20d0ef9SDaniel Lezcano /**
318d2cdc6adSDaniel Lezcano  * dtpm_init - Allocate and initialize a dtpm struct
319d2cdc6adSDaniel Lezcano  * @dtpm: The dtpm struct pointer to be initialized
320d2cdc6adSDaniel Lezcano  * @ops: The dtpm device specific ops, NULL for a virtual node
321a20d0ef9SDaniel Lezcano  */
322d2cdc6adSDaniel Lezcano void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
323a20d0ef9SDaniel Lezcano {
324a20d0ef9SDaniel Lezcano 	if (dtpm) {
325a20d0ef9SDaniel Lezcano 		INIT_LIST_HEAD(&dtpm->children);
326a20d0ef9SDaniel Lezcano 		INIT_LIST_HEAD(&dtpm->sibling);
327a20d0ef9SDaniel Lezcano 		dtpm->weight = 1024;
328a20d0ef9SDaniel Lezcano 		dtpm->ops = ops;
329a20d0ef9SDaniel Lezcano 	}
330a20d0ef9SDaniel Lezcano }
331a20d0ef9SDaniel Lezcano 
332a20d0ef9SDaniel Lezcano /**
333a20d0ef9SDaniel Lezcano  * dtpm_unregister - Unregister a dtpm node from the hierarchy tree
334a20d0ef9SDaniel Lezcano  * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
335a20d0ef9SDaniel Lezcano  *
336a20d0ef9SDaniel Lezcano  * Call the underlying powercap unregister function. That will call
337a20d0ef9SDaniel Lezcano  * the release callback of the powercap zone.
338a20d0ef9SDaniel Lezcano  */
339a20d0ef9SDaniel Lezcano void dtpm_unregister(struct dtpm *dtpm)
340a20d0ef9SDaniel Lezcano {
341a20d0ef9SDaniel Lezcano 	powercap_unregister_zone(pct, &dtpm->zone);
342a20d0ef9SDaniel Lezcano 
343c1af85e4SDaniel Lezcano 	pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
344a20d0ef9SDaniel Lezcano }
345a20d0ef9SDaniel Lezcano 
346a20d0ef9SDaniel Lezcano /**
347a20d0ef9SDaniel Lezcano  * dtpm_register - Register a dtpm node in the hierarchy tree
348a20d0ef9SDaniel Lezcano  * @name: a string specifying the name of the node
349a20d0ef9SDaniel Lezcano  * @dtpm: a pointer to a dtpm structure corresponding to the new node
350a20d0ef9SDaniel Lezcano  * @parent: a pointer to a dtpm structure corresponding to the parent node
351a20d0ef9SDaniel Lezcano  *
352a20d0ef9SDaniel Lezcano  * Create a dtpm node in the tree. If no parent is specified, the node
353a20d0ef9SDaniel Lezcano  * is the root node of the hierarchy. If the root node already exists,
354a20d0ef9SDaniel Lezcano  * then the registration will fail. The powercap controller must be
355a20d0ef9SDaniel Lezcano  * initialized before calling this function.
356a20d0ef9SDaniel Lezcano  *
357a20d0ef9SDaniel Lezcano  * The dtpm structure must be initialized with the power numbers
358a20d0ef9SDaniel Lezcano  * before calling this function.
359a20d0ef9SDaniel Lezcano  *
360a20d0ef9SDaniel Lezcano  * Return: zero on success, a negative value in case of error:
361a20d0ef9SDaniel Lezcano  *  -EAGAIN: the function is called before the framework is initialized.
362a20d0ef9SDaniel Lezcano  *  -EBUSY: the root node is already inserted
363a20d0ef9SDaniel Lezcano  *  -EINVAL: * there is no root node yet and @parent is specified
364a20d0ef9SDaniel Lezcano  *           * no all ops are defined
365a20d0ef9SDaniel Lezcano  *           * parent have ops which are reserved for leaves
366a20d0ef9SDaniel Lezcano  *   Other negative values are reported back from the powercap framework
367a20d0ef9SDaniel Lezcano  */
368a20d0ef9SDaniel Lezcano int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
369a20d0ef9SDaniel Lezcano {
370a20d0ef9SDaniel Lezcano 	struct powercap_zone *pcz;
371a20d0ef9SDaniel Lezcano 
372a20d0ef9SDaniel Lezcano 	if (!pct)
373a20d0ef9SDaniel Lezcano 		return -EAGAIN;
374a20d0ef9SDaniel Lezcano 
375a20d0ef9SDaniel Lezcano 	if (root && !parent)
376a20d0ef9SDaniel Lezcano 		return -EBUSY;
377a20d0ef9SDaniel Lezcano 
378a20d0ef9SDaniel Lezcano 	if (!root && parent)
379a20d0ef9SDaniel Lezcano 		return -EINVAL;
380a20d0ef9SDaniel Lezcano 
381a20d0ef9SDaniel Lezcano 	if (parent && parent->ops)
382a20d0ef9SDaniel Lezcano 		return -EINVAL;
383a20d0ef9SDaniel Lezcano 
384a20d0ef9SDaniel Lezcano 	if (!dtpm)
385a20d0ef9SDaniel Lezcano 		return -EINVAL;
386a20d0ef9SDaniel Lezcano 
387a20d0ef9SDaniel Lezcano 	if (dtpm->ops && !(dtpm->ops->set_power_uw &&
388a20d0ef9SDaniel Lezcano 			   dtpm->ops->get_power_uw &&
3894570dddaSDaniel Lezcano 			   dtpm->ops->update_power_uw &&
390a20d0ef9SDaniel Lezcano 			   dtpm->ops->release))
391a20d0ef9SDaniel Lezcano 		return -EINVAL;
392a20d0ef9SDaniel Lezcano 
393a20d0ef9SDaniel Lezcano 	pcz = powercap_register_zone(&dtpm->zone, pct, name,
394a20d0ef9SDaniel Lezcano 				     parent ? &parent->zone : NULL,
395a20d0ef9SDaniel Lezcano 				     &zone_ops, MAX_DTPM_CONSTRAINTS,
396a20d0ef9SDaniel Lezcano 				     &constraint_ops);
397a20d0ef9SDaniel Lezcano 	if (IS_ERR(pcz))
398a20d0ef9SDaniel Lezcano 		return PTR_ERR(pcz);
399a20d0ef9SDaniel Lezcano 
400a20d0ef9SDaniel Lezcano 	if (parent) {
401a20d0ef9SDaniel Lezcano 		list_add_tail(&dtpm->sibling, &parent->children);
402a20d0ef9SDaniel Lezcano 		dtpm->parent = parent;
403a20d0ef9SDaniel Lezcano 	} else {
404a20d0ef9SDaniel Lezcano 		root = dtpm;
405a20d0ef9SDaniel Lezcano 	}
406a20d0ef9SDaniel Lezcano 
4075d8cb8dbSDaniel Lezcano 	if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
408a20d0ef9SDaniel Lezcano 		__dtpm_add_power(dtpm);
4095d8cb8dbSDaniel Lezcano 		dtpm->power_limit = dtpm->power_max;
4105d8cb8dbSDaniel Lezcano 	}
411a20d0ef9SDaniel Lezcano 
412c1af85e4SDaniel Lezcano 	pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
413a20d0ef9SDaniel Lezcano 		 dtpm->zone.name, dtpm->power_min, dtpm->power_max);
414a20d0ef9SDaniel Lezcano 
415a20d0ef9SDaniel Lezcano 	return 0;
416a20d0ef9SDaniel Lezcano }
417a20d0ef9SDaniel Lezcano 
4183759ec67SDaniel Lezcano static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
4193759ec67SDaniel Lezcano 				       struct dtpm *parent)
420a20d0ef9SDaniel Lezcano {
4213759ec67SDaniel Lezcano 	struct dtpm *dtpm;
4223759ec67SDaniel Lezcano 	int ret;
4233759ec67SDaniel Lezcano 
4243759ec67SDaniel Lezcano 	dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
4253759ec67SDaniel Lezcano 	if (!dtpm)
4263759ec67SDaniel Lezcano 		return ERR_PTR(-ENOMEM);
4273759ec67SDaniel Lezcano 	dtpm_init(dtpm, NULL);
4283759ec67SDaniel Lezcano 
4293759ec67SDaniel Lezcano 	ret = dtpm_register(hierarchy->name, dtpm, parent);
4303759ec67SDaniel Lezcano 	if (ret) {
4313759ec67SDaniel Lezcano 		pr_err("Failed to register dtpm node '%s': %d\n",
4323759ec67SDaniel Lezcano 		       hierarchy->name, ret);
4333759ec67SDaniel Lezcano 		kfree(dtpm);
4343759ec67SDaniel Lezcano 		return ERR_PTR(ret);
4353759ec67SDaniel Lezcano 	}
4363759ec67SDaniel Lezcano 
4373759ec67SDaniel Lezcano 	return dtpm;
4383759ec67SDaniel Lezcano }
4393759ec67SDaniel Lezcano 
4403759ec67SDaniel Lezcano static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
4413759ec67SDaniel Lezcano 				  struct dtpm *parent)
4423759ec67SDaniel Lezcano {
4433759ec67SDaniel Lezcano 	struct device_node *np;
4443759ec67SDaniel Lezcano 	int i, ret;
4453759ec67SDaniel Lezcano 
4463759ec67SDaniel Lezcano 	np = of_find_node_by_path(hierarchy->name);
4473759ec67SDaniel Lezcano 	if (!np) {
4483759ec67SDaniel Lezcano 		pr_err("Failed to find '%s'\n", hierarchy->name);
4493759ec67SDaniel Lezcano 		return ERR_PTR(-ENXIO);
4503759ec67SDaniel Lezcano 	}
4513759ec67SDaniel Lezcano 
4523759ec67SDaniel Lezcano 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
4533759ec67SDaniel Lezcano 
4543759ec67SDaniel Lezcano 		if (!dtpm_subsys[i]->setup)
4553759ec67SDaniel Lezcano 			continue;
4563759ec67SDaniel Lezcano 
4573759ec67SDaniel Lezcano 		ret = dtpm_subsys[i]->setup(parent, np);
4583759ec67SDaniel Lezcano 		if (ret) {
4593759ec67SDaniel Lezcano 			pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
4603759ec67SDaniel Lezcano 			of_node_put(np);
4613759ec67SDaniel Lezcano 			return ERR_PTR(ret);
4623759ec67SDaniel Lezcano 		}
4633759ec67SDaniel Lezcano 	}
4643759ec67SDaniel Lezcano 
4653759ec67SDaniel Lezcano 	of_node_put(np);
4663759ec67SDaniel Lezcano 
4673759ec67SDaniel Lezcano 	/*
4683759ec67SDaniel Lezcano 	 * By returning a NULL pointer, we let know the caller there
4693759ec67SDaniel Lezcano 	 * is no child for us as we are a leaf of the tree
4703759ec67SDaniel Lezcano 	 */
4713759ec67SDaniel Lezcano 	return NULL;
4723759ec67SDaniel Lezcano }
4733759ec67SDaniel Lezcano 
4743759ec67SDaniel Lezcano typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
4753759ec67SDaniel Lezcano 
4763759ec67SDaniel Lezcano dtpm_node_callback_t dtpm_node_callback[] = {
4773759ec67SDaniel Lezcano 	[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
4783759ec67SDaniel Lezcano 	[DTPM_NODE_DT] = dtpm_setup_dt,
4793759ec67SDaniel Lezcano };
4803759ec67SDaniel Lezcano 
4813759ec67SDaniel Lezcano static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
4823759ec67SDaniel Lezcano 			       const struct dtpm_node *it, struct dtpm *parent)
4833759ec67SDaniel Lezcano {
4843759ec67SDaniel Lezcano 	struct dtpm *dtpm;
4853759ec67SDaniel Lezcano 	int i, ret;
4863759ec67SDaniel Lezcano 
4873759ec67SDaniel Lezcano 	for (i = 0; hierarchy[i].name; i++) {
4883759ec67SDaniel Lezcano 
4893759ec67SDaniel Lezcano 		if (hierarchy[i].parent != it)
4903759ec67SDaniel Lezcano 			continue;
4913759ec67SDaniel Lezcano 
4923759ec67SDaniel Lezcano 		dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
4933759ec67SDaniel Lezcano 
4943759ec67SDaniel Lezcano 		/*
4953759ec67SDaniel Lezcano 		 * A NULL pointer means there is no children, hence we
4963759ec67SDaniel Lezcano 		 * continue without going deeper in the recursivity.
4973759ec67SDaniel Lezcano 		 */
4983759ec67SDaniel Lezcano 		if (!dtpm)
4993759ec67SDaniel Lezcano 			continue;
5003759ec67SDaniel Lezcano 
5013759ec67SDaniel Lezcano 		/*
5023759ec67SDaniel Lezcano 		 * There are multiple reasons why the callback could
5033759ec67SDaniel Lezcano 		 * fail. The generic glue is abstracting the backend
5043759ec67SDaniel Lezcano 		 * and therefore it is not possible to report back or
5053759ec67SDaniel Lezcano 		 * take a decision based on the error.  In any case,
5063759ec67SDaniel Lezcano 		 * if this call fails, it is not critical in the
5073759ec67SDaniel Lezcano 		 * hierarchy creation, we can assume the underlying
5083759ec67SDaniel Lezcano 		 * service is not found, so we continue without this
5093759ec67SDaniel Lezcano 		 * branch in the tree but with a warning to log the
5103759ec67SDaniel Lezcano 		 * information the node was not created.
5113759ec67SDaniel Lezcano 		 */
5123759ec67SDaniel Lezcano 		if (IS_ERR(dtpm)) {
5133759ec67SDaniel Lezcano 			pr_warn("Failed to create '%s' in the hierarchy\n",
5143759ec67SDaniel Lezcano 				hierarchy[i].name);
5153759ec67SDaniel Lezcano 			continue;
5163759ec67SDaniel Lezcano 		}
5173759ec67SDaniel Lezcano 
5183759ec67SDaniel Lezcano 		ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
5193759ec67SDaniel Lezcano 		if (ret)
5203759ec67SDaniel Lezcano 			return ret;
521a20d0ef9SDaniel Lezcano 	}
522a20d0ef9SDaniel Lezcano 
523a20d0ef9SDaniel Lezcano 	return 0;
524a20d0ef9SDaniel Lezcano }
5253759ec67SDaniel Lezcano 
5263759ec67SDaniel Lezcano /**
5273759ec67SDaniel Lezcano  * dtpm_create_hierarchy - Create the dtpm hierarchy
5283759ec67SDaniel Lezcano  * @hierarchy: An array of struct dtpm_node describing the hierarchy
5293759ec67SDaniel Lezcano  *
5303759ec67SDaniel Lezcano  * The function is called by the platform specific code with the
5313759ec67SDaniel Lezcano  * description of the different node in the hierarchy. It creates the
5323759ec67SDaniel Lezcano  * tree in the sysfs filesystem under the powercap dtpm entry.
5333759ec67SDaniel Lezcano  *
5343759ec67SDaniel Lezcano  * The expected tree has the format:
5353759ec67SDaniel Lezcano  *
5363759ec67SDaniel Lezcano  * struct dtpm_node hierarchy[] = {
5373759ec67SDaniel Lezcano  *	[0] { .name = "topmost", type =  DTPM_NODE_VIRTUAL },
5383759ec67SDaniel Lezcano  *	[1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
5393759ec67SDaniel Lezcano  *	[2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5403759ec67SDaniel Lezcano  *	[3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5413759ec67SDaniel Lezcano  *	[4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5423759ec67SDaniel Lezcano  *	[5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
5433759ec67SDaniel Lezcano  *	[6] { }
5443759ec67SDaniel Lezcano  * };
5453759ec67SDaniel Lezcano  *
5463759ec67SDaniel Lezcano  * The last element is always an empty one and marks the end of the
5473759ec67SDaniel Lezcano  * array.
5483759ec67SDaniel Lezcano  *
5493759ec67SDaniel Lezcano  * Return: zero on success, a negative value in case of error. Errors
5503759ec67SDaniel Lezcano  * are reported back from the underlying functions.
5513759ec67SDaniel Lezcano  */
5523759ec67SDaniel Lezcano int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
5533759ec67SDaniel Lezcano {
5543759ec67SDaniel Lezcano 	const struct of_device_id *match;
5553759ec67SDaniel Lezcano 	const struct dtpm_node *hierarchy;
5563759ec67SDaniel Lezcano 	struct device_node *np;
5573759ec67SDaniel Lezcano 	int i, ret;
5583759ec67SDaniel Lezcano 
5597b75bbdfSDaniel Lezcano 	mutex_lock(&dtpm_lock);
5607b75bbdfSDaniel Lezcano 
5617b75bbdfSDaniel Lezcano 	if (pct) {
5627b75bbdfSDaniel Lezcano 		ret = -EBUSY;
5637b75bbdfSDaniel Lezcano 		goto out_unlock;
5647b75bbdfSDaniel Lezcano 	}
5653759ec67SDaniel Lezcano 
5663759ec67SDaniel Lezcano 	pct = powercap_register_control_type(NULL, "dtpm", NULL);
5673759ec67SDaniel Lezcano 	if (IS_ERR(pct)) {
5683759ec67SDaniel Lezcano 		pr_err("Failed to register control type\n");
5693759ec67SDaniel Lezcano 		ret = PTR_ERR(pct);
5703759ec67SDaniel Lezcano 		goto out_pct;
5713759ec67SDaniel Lezcano 	}
5723759ec67SDaniel Lezcano 
5733759ec67SDaniel Lezcano 	ret = -ENODEV;
5743759ec67SDaniel Lezcano 	np = of_find_node_by_path("/");
5753759ec67SDaniel Lezcano 	if (!np)
5763759ec67SDaniel Lezcano 		goto out_err;
5773759ec67SDaniel Lezcano 
5783759ec67SDaniel Lezcano 	match = of_match_node(dtpm_match_table, np);
5793759ec67SDaniel Lezcano 
5803759ec67SDaniel Lezcano 	of_node_put(np);
5813759ec67SDaniel Lezcano 
5823759ec67SDaniel Lezcano 	if (!match)
5833759ec67SDaniel Lezcano 		goto out_err;
5843759ec67SDaniel Lezcano 
5853759ec67SDaniel Lezcano 	hierarchy = match->data;
5863759ec67SDaniel Lezcano 	if (!hierarchy) {
5873759ec67SDaniel Lezcano 		ret = -EFAULT;
5883759ec67SDaniel Lezcano 		goto out_err;
5893759ec67SDaniel Lezcano 	}
5903759ec67SDaniel Lezcano 
5913759ec67SDaniel Lezcano 	ret = dtpm_for_each_child(hierarchy, NULL, NULL);
5923759ec67SDaniel Lezcano 	if (ret)
5933759ec67SDaniel Lezcano 		goto out_err;
5943759ec67SDaniel Lezcano 
5953759ec67SDaniel Lezcano 	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
5963759ec67SDaniel Lezcano 
5973759ec67SDaniel Lezcano 		if (!dtpm_subsys[i]->init)
5983759ec67SDaniel Lezcano 			continue;
5993759ec67SDaniel Lezcano 
6003759ec67SDaniel Lezcano 		ret = dtpm_subsys[i]->init();
6013759ec67SDaniel Lezcano 		if (ret)
6023759ec67SDaniel Lezcano 			pr_info("Failed to initialze '%s': %d",
6033759ec67SDaniel Lezcano 				dtpm_subsys[i]->name, ret);
6043759ec67SDaniel Lezcano 	}
6053759ec67SDaniel Lezcano 
6067b75bbdfSDaniel Lezcano 	mutex_unlock(&dtpm_lock);
6077b75bbdfSDaniel Lezcano 
6083759ec67SDaniel Lezcano 	return 0;
6093759ec67SDaniel Lezcano 
6103759ec67SDaniel Lezcano out_err:
6113759ec67SDaniel Lezcano 	powercap_unregister_control_type(pct);
6123759ec67SDaniel Lezcano out_pct:
6133759ec67SDaniel Lezcano 	pct = NULL;
6147b75bbdfSDaniel Lezcano out_unlock:
6157b75bbdfSDaniel Lezcano 	mutex_unlock(&dtpm_lock);
6163759ec67SDaniel Lezcano 
6173759ec67SDaniel Lezcano 	return ret;
6183759ec67SDaniel Lezcano }
6193759ec67SDaniel Lezcano EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
620