162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2020 Linaro Limited
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * The powercap based Dynamic Thermal Power Management framework
862306a36Sopenharmony_ci * provides to the userspace a consistent API to set the power limit
962306a36Sopenharmony_ci * on some devices.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * DTPM defines the functions to create a tree of constraints. Each
1262306a36Sopenharmony_ci * parent node is a virtual description of the aggregation of the
1362306a36Sopenharmony_ci * children. It propagates the constraints set at its level to its
1462306a36Sopenharmony_ci * children and collect the children power information. The leaves of
1562306a36Sopenharmony_ci * the tree are the real devices which have the ability to get their
1662306a36Sopenharmony_ci * current power consumption and set their power limit.
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/dtpm.h>
2162306a36Sopenharmony_ci#include <linux/init.h>
2262306a36Sopenharmony_ci#include <linux/kernel.h>
2362306a36Sopenharmony_ci#include <linux/powercap.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/mutex.h>
2662306a36Sopenharmony_ci#include <linux/of.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include "dtpm_subsys.h"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define DTPM_POWER_LIMIT_FLAG 0
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic const char *constraint_name[] = {
3362306a36Sopenharmony_ci	"Instantaneous",
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic DEFINE_MUTEX(dtpm_lock);
3762306a36Sopenharmony_cistatic struct powercap_control_type *pct;
3862306a36Sopenharmony_cistatic struct dtpm *root;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	return -ENOSYS;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	return -ENOSYS;
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct dtpm *dtpm = to_dtpm(pcz);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	*max_power_uw = dtpm->power_max - dtpm->power_min;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	return 0;
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct dtpm *child;
6262306a36Sopenharmony_ci	u64 power;
6362306a36Sopenharmony_ci	int ret = 0;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (dtpm->ops) {
6662306a36Sopenharmony_ci		*power_uw = dtpm->ops->get_power_uw(dtpm);
6762306a36Sopenharmony_ci		return 0;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	*power_uw = 0;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	list_for_each_entry(child, &dtpm->children, sibling) {
7362306a36Sopenharmony_ci		ret = __get_power_uw(child, &power);
7462306a36Sopenharmony_ci		if (ret)
7562306a36Sopenharmony_ci			break;
7662306a36Sopenharmony_ci		*power_uw += power;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return ret;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	return __get_power_uw(to_dtpm(pcz), power_uw);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic void __dtpm_rebalance_weight(struct dtpm *dtpm)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct dtpm *child;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	list_for_each_entry(child, &dtpm->children, sibling) {
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		pr_debug("Setting weight '%d' for '%s'\n",
9462306a36Sopenharmony_ci			 child->weight, child->zone.name);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		child->weight = DIV64_U64_ROUND_CLOSEST(
9762306a36Sopenharmony_ci			child->power_max * 1024, dtpm->power_max);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		__dtpm_rebalance_weight(child);
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic void __dtpm_sub_power(struct dtpm *dtpm)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	struct dtpm *parent = dtpm->parent;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	while (parent) {
10862306a36Sopenharmony_ci		parent->power_min -= dtpm->power_min;
10962306a36Sopenharmony_ci		parent->power_max -= dtpm->power_max;
11062306a36Sopenharmony_ci		parent->power_limit -= dtpm->power_limit;
11162306a36Sopenharmony_ci		parent = parent->parent;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic void __dtpm_add_power(struct dtpm *dtpm)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct dtpm *parent = dtpm->parent;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	while (parent) {
12062306a36Sopenharmony_ci		parent->power_min += dtpm->power_min;
12162306a36Sopenharmony_ci		parent->power_max += dtpm->power_max;
12262306a36Sopenharmony_ci		parent->power_limit += dtpm->power_limit;
12362306a36Sopenharmony_ci		parent = parent->parent;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci/**
12862306a36Sopenharmony_ci * dtpm_update_power - Update the power on the dtpm
12962306a36Sopenharmony_ci * @dtpm: a pointer to a dtpm structure to update
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci * Function to update the power values of the dtpm node specified in
13262306a36Sopenharmony_ci * parameter. These new values will be propagated to the tree.
13362306a36Sopenharmony_ci *
13462306a36Sopenharmony_ci * Return: zero on success, -EINVAL if the values are inconsistent
13562306a36Sopenharmony_ci */
13662306a36Sopenharmony_ciint dtpm_update_power(struct dtpm *dtpm)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	int ret;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	__dtpm_sub_power(dtpm);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	ret = dtpm->ops->update_power_uw(dtpm);
14362306a36Sopenharmony_ci	if (ret)
14462306a36Sopenharmony_ci		pr_err("Failed to update power for '%s': %d\n",
14562306a36Sopenharmony_ci		       dtpm->zone.name, ret);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags))
14862306a36Sopenharmony_ci		dtpm->power_limit = dtpm->power_max;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	__dtpm_add_power(dtpm);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (root)
15362306a36Sopenharmony_ci		__dtpm_rebalance_weight(root);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return ret;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci/**
15962306a36Sopenharmony_ci * dtpm_release_zone - Cleanup when the node is released
16062306a36Sopenharmony_ci * @pcz: a pointer to a powercap_zone structure
16162306a36Sopenharmony_ci *
16262306a36Sopenharmony_ci * Do some housecleaning and update the weight on the tree. The
16362306a36Sopenharmony_ci * release will be denied if the node has children. This function must
16462306a36Sopenharmony_ci * be called by the specific release callback of the different
16562306a36Sopenharmony_ci * backends.
16662306a36Sopenharmony_ci *
16762306a36Sopenharmony_ci * Return: 0 on success, -EBUSY if there are children
16862306a36Sopenharmony_ci */
16962306a36Sopenharmony_ciint dtpm_release_zone(struct powercap_zone *pcz)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct dtpm *dtpm = to_dtpm(pcz);
17262306a36Sopenharmony_ci	struct dtpm *parent = dtpm->parent;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if (!list_empty(&dtpm->children))
17562306a36Sopenharmony_ci		return -EBUSY;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (parent)
17862306a36Sopenharmony_ci		list_del(&dtpm->sibling);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	__dtpm_sub_power(dtpm);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (dtpm->ops)
18362306a36Sopenharmony_ci		dtpm->ops->release(dtpm);
18462306a36Sopenharmony_ci	else
18562306a36Sopenharmony_ci		kfree(dtpm);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic int get_power_limit_uw(struct powercap_zone *pcz,
19162306a36Sopenharmony_ci			      int cid, u64 *power_limit)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	*power_limit = to_dtpm(pcz)->power_limit;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return 0;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci/*
19962306a36Sopenharmony_ci * Set the power limit on the nodes, the power limit is distributed
20062306a36Sopenharmony_ci * given the weight of the children.
20162306a36Sopenharmony_ci *
20262306a36Sopenharmony_ci * The dtpm node lock must be held when calling this function.
20362306a36Sopenharmony_ci */
20462306a36Sopenharmony_cistatic int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	struct dtpm *child;
20762306a36Sopenharmony_ci	int ret = 0;
20862306a36Sopenharmony_ci	u64 power;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/*
21162306a36Sopenharmony_ci	 * A max power limitation means we remove the power limit,
21262306a36Sopenharmony_ci	 * otherwise we set a constraint and flag the dtpm node.
21362306a36Sopenharmony_ci	 */
21462306a36Sopenharmony_ci	if (power_limit == dtpm->power_max) {
21562306a36Sopenharmony_ci		clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
21662306a36Sopenharmony_ci	} else {
21762306a36Sopenharmony_ci		set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	pr_debug("Setting power limit for '%s': %llu uW\n",
22162306a36Sopenharmony_ci		 dtpm->zone.name, power_limit);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/*
22462306a36Sopenharmony_ci	 * Only leaves of the dtpm tree has ops to get/set the power
22562306a36Sopenharmony_ci	 */
22662306a36Sopenharmony_ci	if (dtpm->ops) {
22762306a36Sopenharmony_ci		dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
22862306a36Sopenharmony_ci	} else {
22962306a36Sopenharmony_ci		dtpm->power_limit = 0;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		list_for_each_entry(child, &dtpm->children, sibling) {
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci			/*
23462306a36Sopenharmony_ci			 * Integer division rounding will inevitably
23562306a36Sopenharmony_ci			 * lead to a different min or max value when
23662306a36Sopenharmony_ci			 * set several times. In order to restore the
23762306a36Sopenharmony_ci			 * initial value, we force the child's min or
23862306a36Sopenharmony_ci			 * max power every time if the constraint is
23962306a36Sopenharmony_ci			 * at the boundaries.
24062306a36Sopenharmony_ci			 */
24162306a36Sopenharmony_ci			if (power_limit == dtpm->power_max) {
24262306a36Sopenharmony_ci				power = child->power_max;
24362306a36Sopenharmony_ci			} else if (power_limit == dtpm->power_min) {
24462306a36Sopenharmony_ci				power = child->power_min;
24562306a36Sopenharmony_ci			} else {
24662306a36Sopenharmony_ci				power = DIV_ROUND_CLOSEST_ULL(
24762306a36Sopenharmony_ci					power_limit * child->weight, 1024);
24862306a36Sopenharmony_ci			}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci			pr_debug("Setting power limit for '%s': %llu uW\n",
25162306a36Sopenharmony_ci				 child->zone.name, power);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci			ret = __set_power_limit_uw(child, cid, power);
25462306a36Sopenharmony_ci			if (!ret)
25562306a36Sopenharmony_ci				ret = get_power_limit_uw(&child->zone, cid, &power);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci			if (ret)
25862306a36Sopenharmony_ci				break;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci			dtpm->power_limit += power;
26162306a36Sopenharmony_ci		}
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return ret;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic int set_power_limit_uw(struct powercap_zone *pcz,
26862306a36Sopenharmony_ci			      int cid, u64 power_limit)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	struct dtpm *dtpm = to_dtpm(pcz);
27162306a36Sopenharmony_ci	int ret;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	/*
27462306a36Sopenharmony_ci	 * Don't allow values outside of the power range previously
27562306a36Sopenharmony_ci	 * set when initializing the power numbers.
27662306a36Sopenharmony_ci	 */
27762306a36Sopenharmony_ci	power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	ret = __set_power_limit_uw(dtpm, cid, power_limit);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
28262306a36Sopenharmony_ci		 dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	return ret;
28562306a36Sopenharmony_ci}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_cistatic const char *get_constraint_name(struct powercap_zone *pcz, int cid)
28862306a36Sopenharmony_ci{
28962306a36Sopenharmony_ci	return constraint_name[cid];
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	*max_power = to_dtpm(pcz)->power_max;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	return 0;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic struct powercap_zone_constraint_ops constraint_ops = {
30062306a36Sopenharmony_ci	.set_power_limit_uw = set_power_limit_uw,
30162306a36Sopenharmony_ci	.get_power_limit_uw = get_power_limit_uw,
30262306a36Sopenharmony_ci	.set_time_window_us = set_time_window_us,
30362306a36Sopenharmony_ci	.get_time_window_us = get_time_window_us,
30462306a36Sopenharmony_ci	.get_max_power_uw = get_max_power_uw,
30562306a36Sopenharmony_ci	.get_name = get_constraint_name,
30662306a36Sopenharmony_ci};
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic struct powercap_zone_ops zone_ops = {
30962306a36Sopenharmony_ci	.get_max_power_range_uw = get_max_power_range_uw,
31062306a36Sopenharmony_ci	.get_power_uw = get_power_uw,
31162306a36Sopenharmony_ci	.release = dtpm_release_zone,
31262306a36Sopenharmony_ci};
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci/**
31562306a36Sopenharmony_ci * dtpm_init - Allocate and initialize a dtpm struct
31662306a36Sopenharmony_ci * @dtpm: The dtpm struct pointer to be initialized
31762306a36Sopenharmony_ci * @ops: The dtpm device specific ops, NULL for a virtual node
31862306a36Sopenharmony_ci */
31962306a36Sopenharmony_civoid dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	if (dtpm) {
32262306a36Sopenharmony_ci		INIT_LIST_HEAD(&dtpm->children);
32362306a36Sopenharmony_ci		INIT_LIST_HEAD(&dtpm->sibling);
32462306a36Sopenharmony_ci		dtpm->weight = 1024;
32562306a36Sopenharmony_ci		dtpm->ops = ops;
32662306a36Sopenharmony_ci	}
32762306a36Sopenharmony_ci}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci/**
33062306a36Sopenharmony_ci * dtpm_unregister - Unregister a dtpm node from the hierarchy tree
33162306a36Sopenharmony_ci * @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
33262306a36Sopenharmony_ci *
33362306a36Sopenharmony_ci * Call the underlying powercap unregister function. That will call
33462306a36Sopenharmony_ci * the release callback of the powercap zone.
33562306a36Sopenharmony_ci */
33662306a36Sopenharmony_civoid dtpm_unregister(struct dtpm *dtpm)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	powercap_unregister_zone(pct, &dtpm->zone);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci/**
34462306a36Sopenharmony_ci * dtpm_register - Register a dtpm node in the hierarchy tree
34562306a36Sopenharmony_ci * @name: a string specifying the name of the node
34662306a36Sopenharmony_ci * @dtpm: a pointer to a dtpm structure corresponding to the new node
34762306a36Sopenharmony_ci * @parent: a pointer to a dtpm structure corresponding to the parent node
34862306a36Sopenharmony_ci *
34962306a36Sopenharmony_ci * Create a dtpm node in the tree. If no parent is specified, the node
35062306a36Sopenharmony_ci * is the root node of the hierarchy. If the root node already exists,
35162306a36Sopenharmony_ci * then the registration will fail. The powercap controller must be
35262306a36Sopenharmony_ci * initialized before calling this function.
35362306a36Sopenharmony_ci *
35462306a36Sopenharmony_ci * The dtpm structure must be initialized with the power numbers
35562306a36Sopenharmony_ci * before calling this function.
35662306a36Sopenharmony_ci *
35762306a36Sopenharmony_ci * Return: zero on success, a negative value in case of error:
35862306a36Sopenharmony_ci *  -EAGAIN: the function is called before the framework is initialized.
35962306a36Sopenharmony_ci *  -EBUSY: the root node is already inserted
36062306a36Sopenharmony_ci *  -EINVAL: * there is no root node yet and @parent is specified
36162306a36Sopenharmony_ci *           * no all ops are defined
36262306a36Sopenharmony_ci *           * parent have ops which are reserved for leaves
36362306a36Sopenharmony_ci *   Other negative values are reported back from the powercap framework
36462306a36Sopenharmony_ci */
36562306a36Sopenharmony_ciint dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	struct powercap_zone *pcz;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (!pct)
37062306a36Sopenharmony_ci		return -EAGAIN;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	if (root && !parent)
37362306a36Sopenharmony_ci		return -EBUSY;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	if (!root && parent)
37662306a36Sopenharmony_ci		return -EINVAL;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (parent && parent->ops)
37962306a36Sopenharmony_ci		return -EINVAL;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	if (!dtpm)
38262306a36Sopenharmony_ci		return -EINVAL;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	if (dtpm->ops && !(dtpm->ops->set_power_uw &&
38562306a36Sopenharmony_ci			   dtpm->ops->get_power_uw &&
38662306a36Sopenharmony_ci			   dtpm->ops->update_power_uw &&
38762306a36Sopenharmony_ci			   dtpm->ops->release))
38862306a36Sopenharmony_ci		return -EINVAL;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	pcz = powercap_register_zone(&dtpm->zone, pct, name,
39162306a36Sopenharmony_ci				     parent ? &parent->zone : NULL,
39262306a36Sopenharmony_ci				     &zone_ops, MAX_DTPM_CONSTRAINTS,
39362306a36Sopenharmony_ci				     &constraint_ops);
39462306a36Sopenharmony_ci	if (IS_ERR(pcz))
39562306a36Sopenharmony_ci		return PTR_ERR(pcz);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (parent) {
39862306a36Sopenharmony_ci		list_add_tail(&dtpm->sibling, &parent->children);
39962306a36Sopenharmony_ci		dtpm->parent = parent;
40062306a36Sopenharmony_ci	} else {
40162306a36Sopenharmony_ci		root = dtpm;
40262306a36Sopenharmony_ci	}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
40562306a36Sopenharmony_ci		__dtpm_add_power(dtpm);
40662306a36Sopenharmony_ci		dtpm->power_limit = dtpm->power_max;
40762306a36Sopenharmony_ci	}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
41062306a36Sopenharmony_ci		 dtpm->zone.name, dtpm->power_min, dtpm->power_max);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	return 0;
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
41662306a36Sopenharmony_ci				       struct dtpm *parent)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct dtpm *dtpm;
41962306a36Sopenharmony_ci	int ret;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
42262306a36Sopenharmony_ci	if (!dtpm)
42362306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
42462306a36Sopenharmony_ci	dtpm_init(dtpm, NULL);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	ret = dtpm_register(hierarchy->name, dtpm, parent);
42762306a36Sopenharmony_ci	if (ret) {
42862306a36Sopenharmony_ci		pr_err("Failed to register dtpm node '%s': %d\n",
42962306a36Sopenharmony_ci		       hierarchy->name, ret);
43062306a36Sopenharmony_ci		kfree(dtpm);
43162306a36Sopenharmony_ci		return ERR_PTR(ret);
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	return dtpm;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_cistatic struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
43862306a36Sopenharmony_ci				  struct dtpm *parent)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	struct device_node *np;
44162306a36Sopenharmony_ci	int i, ret;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	np = of_find_node_by_path(hierarchy->name);
44462306a36Sopenharmony_ci	if (!np) {
44562306a36Sopenharmony_ci		pr_err("Failed to find '%s'\n", hierarchy->name);
44662306a36Sopenharmony_ci		return ERR_PTR(-ENXIO);
44762306a36Sopenharmony_ci	}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci		if (!dtpm_subsys[i]->setup)
45262306a36Sopenharmony_ci			continue;
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci		ret = dtpm_subsys[i]->setup(parent, np);
45562306a36Sopenharmony_ci		if (ret) {
45662306a36Sopenharmony_ci			pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
45762306a36Sopenharmony_ci			of_node_put(np);
45862306a36Sopenharmony_ci			return ERR_PTR(ret);
45962306a36Sopenharmony_ci		}
46062306a36Sopenharmony_ci	}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	of_node_put(np);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	/*
46562306a36Sopenharmony_ci	 * By returning a NULL pointer, we let know the caller there
46662306a36Sopenharmony_ci	 * is no child for us as we are a leaf of the tree
46762306a36Sopenharmony_ci	 */
46862306a36Sopenharmony_ci	return NULL;
46962306a36Sopenharmony_ci}
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_citypedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_cistatic dtpm_node_callback_t dtpm_node_callback[] = {
47462306a36Sopenharmony_ci	[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
47562306a36Sopenharmony_ci	[DTPM_NODE_DT] = dtpm_setup_dt,
47662306a36Sopenharmony_ci};
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_cistatic int dtpm_for_each_child(const struct dtpm_node *hierarchy,
47962306a36Sopenharmony_ci			       const struct dtpm_node *it, struct dtpm *parent)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	struct dtpm *dtpm;
48262306a36Sopenharmony_ci	int i, ret;
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	for (i = 0; hierarchy[i].name; i++) {
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci		if (hierarchy[i].parent != it)
48762306a36Sopenharmony_ci			continue;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci		dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci		/*
49262306a36Sopenharmony_ci		 * A NULL pointer means there is no children, hence we
49362306a36Sopenharmony_ci		 * continue without going deeper in the recursivity.
49462306a36Sopenharmony_ci		 */
49562306a36Sopenharmony_ci		if (!dtpm)
49662306a36Sopenharmony_ci			continue;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci		/*
49962306a36Sopenharmony_ci		 * There are multiple reasons why the callback could
50062306a36Sopenharmony_ci		 * fail. The generic glue is abstracting the backend
50162306a36Sopenharmony_ci		 * and therefore it is not possible to report back or
50262306a36Sopenharmony_ci		 * take a decision based on the error.  In any case,
50362306a36Sopenharmony_ci		 * if this call fails, it is not critical in the
50462306a36Sopenharmony_ci		 * hierarchy creation, we can assume the underlying
50562306a36Sopenharmony_ci		 * service is not found, so we continue without this
50662306a36Sopenharmony_ci		 * branch in the tree but with a warning to log the
50762306a36Sopenharmony_ci		 * information the node was not created.
50862306a36Sopenharmony_ci		 */
50962306a36Sopenharmony_ci		if (IS_ERR(dtpm)) {
51062306a36Sopenharmony_ci			pr_warn("Failed to create '%s' in the hierarchy\n",
51162306a36Sopenharmony_ci				hierarchy[i].name);
51262306a36Sopenharmony_ci			continue;
51362306a36Sopenharmony_ci		}
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci		ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
51662306a36Sopenharmony_ci		if (ret)
51762306a36Sopenharmony_ci			return ret;
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	return 0;
52162306a36Sopenharmony_ci}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci/**
52462306a36Sopenharmony_ci * dtpm_create_hierarchy - Create the dtpm hierarchy
52562306a36Sopenharmony_ci * @hierarchy: An array of struct dtpm_node describing the hierarchy
52662306a36Sopenharmony_ci *
52762306a36Sopenharmony_ci * The function is called by the platform specific code with the
52862306a36Sopenharmony_ci * description of the different node in the hierarchy. It creates the
52962306a36Sopenharmony_ci * tree in the sysfs filesystem under the powercap dtpm entry.
53062306a36Sopenharmony_ci *
53162306a36Sopenharmony_ci * The expected tree has the format:
53262306a36Sopenharmony_ci *
53362306a36Sopenharmony_ci * struct dtpm_node hierarchy[] = {
53462306a36Sopenharmony_ci *	[0] { .name = "topmost", type =  DTPM_NODE_VIRTUAL },
53562306a36Sopenharmony_ci *	[1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
53662306a36Sopenharmony_ci *	[2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
53762306a36Sopenharmony_ci *	[3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
53862306a36Sopenharmony_ci *	[4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
53962306a36Sopenharmony_ci *	[5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
54062306a36Sopenharmony_ci *	[6] { }
54162306a36Sopenharmony_ci * };
54262306a36Sopenharmony_ci *
54362306a36Sopenharmony_ci * The last element is always an empty one and marks the end of the
54462306a36Sopenharmony_ci * array.
54562306a36Sopenharmony_ci *
54662306a36Sopenharmony_ci * Return: zero on success, a negative value in case of error. Errors
54762306a36Sopenharmony_ci * are reported back from the underlying functions.
54862306a36Sopenharmony_ci */
54962306a36Sopenharmony_ciint dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
55062306a36Sopenharmony_ci{
55162306a36Sopenharmony_ci	const struct of_device_id *match;
55262306a36Sopenharmony_ci	const struct dtpm_node *hierarchy;
55362306a36Sopenharmony_ci	struct device_node *np;
55462306a36Sopenharmony_ci	int i, ret;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	mutex_lock(&dtpm_lock);
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	if (pct) {
55962306a36Sopenharmony_ci		ret = -EBUSY;
56062306a36Sopenharmony_ci		goto out_unlock;
56162306a36Sopenharmony_ci	}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	pct = powercap_register_control_type(NULL, "dtpm", NULL);
56462306a36Sopenharmony_ci	if (IS_ERR(pct)) {
56562306a36Sopenharmony_ci		pr_err("Failed to register control type\n");
56662306a36Sopenharmony_ci		ret = PTR_ERR(pct);
56762306a36Sopenharmony_ci		goto out_pct;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	ret = -ENODEV;
57162306a36Sopenharmony_ci	np = of_find_node_by_path("/");
57262306a36Sopenharmony_ci	if (!np)
57362306a36Sopenharmony_ci		goto out_err;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	match = of_match_node(dtpm_match_table, np);
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	of_node_put(np);
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	if (!match)
58062306a36Sopenharmony_ci		goto out_err;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	hierarchy = match->data;
58362306a36Sopenharmony_ci	if (!hierarchy) {
58462306a36Sopenharmony_ci		ret = -EFAULT;
58562306a36Sopenharmony_ci		goto out_err;
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	ret = dtpm_for_each_child(hierarchy, NULL, NULL);
58962306a36Sopenharmony_ci	if (ret)
59062306a36Sopenharmony_ci		goto out_err;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci		if (!dtpm_subsys[i]->init)
59562306a36Sopenharmony_ci			continue;
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci		ret = dtpm_subsys[i]->init();
59862306a36Sopenharmony_ci		if (ret)
59962306a36Sopenharmony_ci			pr_info("Failed to initialize '%s': %d",
60062306a36Sopenharmony_ci				dtpm_subsys[i]->name, ret);
60162306a36Sopenharmony_ci	}
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	mutex_unlock(&dtpm_lock);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	return 0;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ciout_err:
60862306a36Sopenharmony_ci	powercap_unregister_control_type(pct);
60962306a36Sopenharmony_ciout_pct:
61062306a36Sopenharmony_ci	pct = NULL;
61162306a36Sopenharmony_ciout_unlock:
61262306a36Sopenharmony_ci	mutex_unlock(&dtpm_lock);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	return ret;
61562306a36Sopenharmony_ci}
61662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	struct dtpm *child, *aux;
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
62362306a36Sopenharmony_ci		__dtpm_destroy_hierarchy(child);
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	/*
62662306a36Sopenharmony_ci	 * At this point, we know all children were removed from the
62762306a36Sopenharmony_ci	 * recursive call before
62862306a36Sopenharmony_ci	 */
62962306a36Sopenharmony_ci	dtpm_unregister(dtpm);
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_civoid dtpm_destroy_hierarchy(void)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	int i;
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	mutex_lock(&dtpm_lock);
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	if (!pct)
63962306a36Sopenharmony_ci		goto out_unlock;
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	__dtpm_destroy_hierarchy(root);
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci		if (!dtpm_subsys[i]->exit)
64762306a36Sopenharmony_ci			continue;
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci		dtpm_subsys[i]->exit();
65062306a36Sopenharmony_ci	}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	powercap_unregister_control_type(pct);
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	pct = NULL;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	root = NULL;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ciout_unlock:
65962306a36Sopenharmony_ci	mutex_unlock(&dtpm_lock);
66062306a36Sopenharmony_ci}
66162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
662