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