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