162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SCMI Powercap support. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2022 ARM Ltd. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/math.h> 1062306a36Sopenharmony_ci#include <linux/limits.h> 1162306a36Sopenharmony_ci#include <linux/list.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/powercap.h> 1462306a36Sopenharmony_ci#include <linux/scmi_protocol.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define to_scmi_powercap_zone(z) \ 1862306a36Sopenharmony_ci container_of(z, struct scmi_powercap_zone, zone) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic const struct scmi_powercap_proto_ops *powercap_ops; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct scmi_powercap_zone { 2362306a36Sopenharmony_ci bool registered; 2462306a36Sopenharmony_ci bool invalid; 2562306a36Sopenharmony_ci unsigned int height; 2662306a36Sopenharmony_ci struct device *dev; 2762306a36Sopenharmony_ci struct scmi_protocol_handle *ph; 2862306a36Sopenharmony_ci const struct scmi_powercap_info *info; 2962306a36Sopenharmony_ci struct scmi_powercap_zone *spzones; 3062306a36Sopenharmony_ci struct powercap_zone zone; 3162306a36Sopenharmony_ci struct list_head node; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct scmi_powercap_root { 3562306a36Sopenharmony_ci unsigned int num_zones; 3662306a36Sopenharmony_ci struct scmi_powercap_zone *spzones; 3762306a36Sopenharmony_ci struct list_head *registered_zones; 3862306a36Sopenharmony_ci struct list_head scmi_zones; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct powercap_control_type *scmi_top_pcntrl; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic int scmi_powercap_zone_release(struct powercap_zone *pz) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci return 0; 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int scmi_powercap_get_max_power_range_uw(struct powercap_zone *pz, 4962306a36Sopenharmony_ci u64 *max_power_range_uw) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci *max_power_range_uw = U32_MAX; 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic int scmi_powercap_get_power_uw(struct powercap_zone *pz, 5662306a36Sopenharmony_ci u64 *power_uw) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 5962306a36Sopenharmony_ci u32 avg_power, pai; 6062306a36Sopenharmony_ci int ret; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (!spz->info->powercap_monitoring) 6362306a36Sopenharmony_ci return -EINVAL; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci ret = powercap_ops->measurements_get(spz->ph, spz->info->id, &avg_power, 6662306a36Sopenharmony_ci &pai); 6762306a36Sopenharmony_ci if (ret) 6862306a36Sopenharmony_ci return ret; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci *power_uw = avg_power; 7162306a36Sopenharmony_ci if (spz->info->powercap_scale_mw) 7262306a36Sopenharmony_ci *power_uw *= 1000; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return 0; 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int scmi_powercap_zone_enable_set(struct powercap_zone *pz, bool mode) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return powercap_ops->cap_enable_set(spz->ph, spz->info->id, mode); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int scmi_powercap_zone_enable_get(struct powercap_zone *pz, bool *mode) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return powercap_ops->cap_enable_get(spz->ph, spz->info->id, mode); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic const struct powercap_zone_ops zone_ops = { 9262306a36Sopenharmony_ci .get_max_power_range_uw = scmi_powercap_get_max_power_range_uw, 9362306a36Sopenharmony_ci .get_power_uw = scmi_powercap_get_power_uw, 9462306a36Sopenharmony_ci .release = scmi_powercap_zone_release, 9562306a36Sopenharmony_ci .set_enable = scmi_powercap_zone_enable_set, 9662306a36Sopenharmony_ci .get_enable = scmi_powercap_zone_enable_get, 9762306a36Sopenharmony_ci}; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz, 10062306a36Sopenharmony_ci u64 power_limit_uw, u32 *norm) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci bool scale_mw = spz->info->powercap_scale_mw; 10362306a36Sopenharmony_ci u64 val; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci val = scale_mw ? DIV_ROUND_UP_ULL(power_limit_uw, 1000) : power_limit_uw; 10662306a36Sopenharmony_ci /* 10762306a36Sopenharmony_ci * This cast is lossless since here @req_power is certain to be within 10862306a36Sopenharmony_ci * the range [min_power_cap, max_power_cap] whose bounds are assured to 10962306a36Sopenharmony_ci * be two unsigned 32bits quantities. 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_ci *norm = clamp_t(u32, val, spz->info->min_power_cap, 11262306a36Sopenharmony_ci spz->info->max_power_cap); 11362306a36Sopenharmony_ci *norm = rounddown(*norm, spz->info->power_cap_step); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci val = (scale_mw) ? *norm * 1000 : *norm; 11662306a36Sopenharmony_ci if (power_limit_uw != val) 11762306a36Sopenharmony_ci dev_dbg(spz->dev, 11862306a36Sopenharmony_ci "Normalized %s:CAP - requested:%llu - normalized:%llu\n", 11962306a36Sopenharmony_ci spz->info->name, power_limit_uw, val); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid, 12362306a36Sopenharmony_ci u64 power_uw) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 12662306a36Sopenharmony_ci u32 norm_power; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!spz->info->powercap_cap_config) 12962306a36Sopenharmony_ci return -EINVAL; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci scmi_powercap_normalize_cap(spz, power_uw, &norm_power); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid, 13762306a36Sopenharmony_ci u64 *power_limit_uw) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 14062306a36Sopenharmony_ci u32 power; 14162306a36Sopenharmony_ci int ret; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power); 14462306a36Sopenharmony_ci if (ret) 14562306a36Sopenharmony_ci return ret; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci *power_limit_uw = power; 14862306a36Sopenharmony_ci if (spz->info->powercap_scale_mw) 14962306a36Sopenharmony_ci *power_limit_uw *= 1000; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz, 15562306a36Sopenharmony_ci u64 time_us, u32 *norm) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci /* 15862306a36Sopenharmony_ci * This cast is lossless since here @time_us is certain to be within the 15962306a36Sopenharmony_ci * range [min_pai, max_pai] whose bounds are assured to be two unsigned 16062306a36Sopenharmony_ci * 32bits quantities. 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_ci *norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai); 16362306a36Sopenharmony_ci *norm = rounddown(*norm, spz->info->pai_step); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (time_us != *norm) 16662306a36Sopenharmony_ci dev_dbg(spz->dev, 16762306a36Sopenharmony_ci "Normalized %s:PAI - requested:%llu - normalized:%u\n", 16862306a36Sopenharmony_ci spz->info->name, time_us, *norm); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid, 17262306a36Sopenharmony_ci u64 time_window_us) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 17562306a36Sopenharmony_ci u32 norm_pai; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (!spz->info->powercap_pai_config) 17862306a36Sopenharmony_ci return -EINVAL; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci scmi_powercap_normalize_time(spz, time_window_us, &norm_pai); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid, 18662306a36Sopenharmony_ci u64 *time_window_us) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 18962306a36Sopenharmony_ci int ret; 19062306a36Sopenharmony_ci u32 pai; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai); 19362306a36Sopenharmony_ci if (ret) 19462306a36Sopenharmony_ci return ret; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci *time_window_us = pai; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid, 20262306a36Sopenharmony_ci u64 *max_power_uw) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci *max_power_uw = spz->info->max_power_cap; 20762306a36Sopenharmony_ci if (spz->info->powercap_scale_mw) 20862306a36Sopenharmony_ci *max_power_uw *= 1000; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci return 0; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid, 21462306a36Sopenharmony_ci u64 *min_power_uw) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci *min_power_uw = spz->info->min_power_cap; 21962306a36Sopenharmony_ci if (spz->info->powercap_scale_mw) 22062306a36Sopenharmony_ci *min_power_uw *= 1000; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return 0; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz, 22662306a36Sopenharmony_ci int cid, u64 *time_window_us) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci *time_window_us = spz->info->max_pai; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return 0; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz, 23662306a36Sopenharmony_ci int cid, u64 *time_window_us) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci *time_window_us = (u64)spz->info->min_pai; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci return 0; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci return "SCMI power-cap"; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic const struct powercap_zone_constraint_ops constraint_ops = { 25162306a36Sopenharmony_ci .set_power_limit_uw = scmi_powercap_set_power_limit_uw, 25262306a36Sopenharmony_ci .get_power_limit_uw = scmi_powercap_get_power_limit_uw, 25362306a36Sopenharmony_ci .set_time_window_us = scmi_powercap_set_time_window_us, 25462306a36Sopenharmony_ci .get_time_window_us = scmi_powercap_get_time_window_us, 25562306a36Sopenharmony_ci .get_max_power_uw = scmi_powercap_get_max_power_uw, 25662306a36Sopenharmony_ci .get_min_power_uw = scmi_powercap_get_min_power_uw, 25762306a36Sopenharmony_ci .get_max_time_window_us = scmi_powercap_get_max_time_window_us, 25862306a36Sopenharmony_ci .get_min_time_window_us = scmi_powercap_get_min_time_window_us, 25962306a36Sopenharmony_ci .get_name = scmi_powercap_get_name, 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci int i; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* Un-register children zones first starting from the leaves */ 26762306a36Sopenharmony_ci for (i = pr->num_zones - 1; i >= 0; i--) { 26862306a36Sopenharmony_ci if (!list_empty(&pr->registered_zones[i])) { 26962306a36Sopenharmony_ci struct scmi_powercap_zone *spz; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci list_for_each_entry(spz, &pr->registered_zones[i], node) 27262306a36Sopenharmony_ci powercap_unregister_zone(scmi_top_pcntrl, 27362306a36Sopenharmony_ci &spz->zone); 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic inline unsigned int 27962306a36Sopenharmony_ciscmi_powercap_get_zone_height(struct scmi_powercap_zone *spz) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID) 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci return spz->spzones[spz->info->parent_id].height + 1; 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic inline struct scmi_powercap_zone * 28862306a36Sopenharmony_ciscmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID) 29162306a36Sopenharmony_ci return NULL; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci return &spz->spzones[spz->info->parent_id]; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic int scmi_powercap_register_zone(struct scmi_powercap_root *pr, 29762306a36Sopenharmony_ci struct scmi_powercap_zone *spz, 29862306a36Sopenharmony_ci struct scmi_powercap_zone *parent) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci int ret = 0; 30162306a36Sopenharmony_ci struct powercap_zone *z; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (spz->invalid) { 30462306a36Sopenharmony_ci list_del(&spz->node); 30562306a36Sopenharmony_ci return -EINVAL; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name, 30962306a36Sopenharmony_ci parent ? &parent->zone : NULL, 31062306a36Sopenharmony_ci &zone_ops, 1, &constraint_ops); 31162306a36Sopenharmony_ci if (!IS_ERR(z)) { 31262306a36Sopenharmony_ci spz->height = scmi_powercap_get_zone_height(spz); 31362306a36Sopenharmony_ci spz->registered = true; 31462306a36Sopenharmony_ci list_move(&spz->node, &pr->registered_zones[spz->height]); 31562306a36Sopenharmony_ci dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n", 31662306a36Sopenharmony_ci spz->info->name, parent ? parent->info->name : "ROOT", 31762306a36Sopenharmony_ci spz->height); 31862306a36Sopenharmony_ci } else { 31962306a36Sopenharmony_ci list_del(&spz->node); 32062306a36Sopenharmony_ci ret = PTR_ERR(z); 32162306a36Sopenharmony_ci dev_err(spz->dev, 32262306a36Sopenharmony_ci "Error registering node:%s - parent:%s - h:%d - ret:%d\n", 32362306a36Sopenharmony_ci spz->info->name, 32462306a36Sopenharmony_ci parent ? parent->info->name : "ROOT", 32562306a36Sopenharmony_ci spz->height, ret); 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci return ret; 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci/** 33262306a36Sopenharmony_ci * scmi_zones_register- Register SCMI powercap zones starting from parent zones 33362306a36Sopenharmony_ci * 33462306a36Sopenharmony_ci * @dev: A reference to the SCMI device 33562306a36Sopenharmony_ci * @pr: A reference to the root powercap zones descriptors 33662306a36Sopenharmony_ci * 33762306a36Sopenharmony_ci * When registering SCMI powercap zones with the powercap framework we should 33862306a36Sopenharmony_ci * take care to always register zones starting from the root ones and to 33962306a36Sopenharmony_ci * deregister starting from the leaves. 34062306a36Sopenharmony_ci * 34162306a36Sopenharmony_ci * Unfortunately we cannot assume that the array of available SCMI powercap 34262306a36Sopenharmony_ci * zones provided by the SCMI platform firmware is built to comply with such 34362306a36Sopenharmony_ci * requirement. 34462306a36Sopenharmony_ci * 34562306a36Sopenharmony_ci * This function, given the set of SCMI powercap zones to register, takes care 34662306a36Sopenharmony_ci * to walk the SCMI powercap zones trees up to the root registering any 34762306a36Sopenharmony_ci * unregistered parent zone before registering the child zones; at the same 34862306a36Sopenharmony_ci * time each registered-zone height in such a tree is accounted for and each 34962306a36Sopenharmony_ci * zone, once registered, is stored in the @registered_zones array that is 35062306a36Sopenharmony_ci * indexed by zone height: this way will be trivial, at unregister time, to walk 35162306a36Sopenharmony_ci * the @registered_zones array backward and unregister all the zones starting 35262306a36Sopenharmony_ci * from the leaves, removing children zones before parents. 35362306a36Sopenharmony_ci * 35462306a36Sopenharmony_ci * While doing this, we prune away any zone marked as invalid (like the ones 35562306a36Sopenharmony_ci * sporting an SCMI abstract power scale) as long as they are positioned as 35662306a36Sopenharmony_ci * leaves in the SCMI powercap zones hierarchy: any non-leaf invalid zone causes 35762306a36Sopenharmony_ci * the entire process to fail since we cannot assume the correctness of an SCMI 35862306a36Sopenharmony_ci * powercap zones hierarchy if some of the internal nodes are missing. 35962306a36Sopenharmony_ci * 36062306a36Sopenharmony_ci * Note that the array of SCMI powercap zones as returned by the SCMI platform 36162306a36Sopenharmony_ci * is known to be sane, i.e. zones relationships have been validated at the 36262306a36Sopenharmony_ci * protocol layer. 36362306a36Sopenharmony_ci * 36462306a36Sopenharmony_ci * Return: 0 on Success 36562306a36Sopenharmony_ci */ 36662306a36Sopenharmony_cistatic int scmi_zones_register(struct device *dev, 36762306a36Sopenharmony_ci struct scmi_powercap_root *pr) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci int ret = 0; 37062306a36Sopenharmony_ci unsigned int sp = 0, reg_zones = 0; 37162306a36Sopenharmony_ci struct scmi_powercap_zone *spz, **zones_stack; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci zones_stack = kcalloc(pr->num_zones, sizeof(spz), GFP_KERNEL); 37462306a36Sopenharmony_ci if (!zones_stack) 37562306a36Sopenharmony_ci return -ENOMEM; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci spz = list_first_entry_or_null(&pr->scmi_zones, 37862306a36Sopenharmony_ci struct scmi_powercap_zone, node); 37962306a36Sopenharmony_ci while (spz) { 38062306a36Sopenharmony_ci struct scmi_powercap_zone *parent; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci parent = scmi_powercap_get_parent_zone(spz); 38362306a36Sopenharmony_ci if (parent && !parent->registered) { 38462306a36Sopenharmony_ci zones_stack[sp++] = spz; 38562306a36Sopenharmony_ci spz = parent; 38662306a36Sopenharmony_ci } else { 38762306a36Sopenharmony_ci ret = scmi_powercap_register_zone(pr, spz, parent); 38862306a36Sopenharmony_ci if (!ret) { 38962306a36Sopenharmony_ci reg_zones++; 39062306a36Sopenharmony_ci } else if (sp) { 39162306a36Sopenharmony_ci /* Failed to register a non-leaf zone. 39262306a36Sopenharmony_ci * Bail-out. 39362306a36Sopenharmony_ci */ 39462306a36Sopenharmony_ci dev_err(dev, 39562306a36Sopenharmony_ci "Failed to register non-leaf zone - ret:%d\n", 39662306a36Sopenharmony_ci ret); 39762306a36Sopenharmony_ci scmi_powercap_unregister_all_zones(pr); 39862306a36Sopenharmony_ci reg_zones = 0; 39962306a36Sopenharmony_ci goto out; 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci /* Pick next zone to process */ 40262306a36Sopenharmony_ci if (sp) 40362306a36Sopenharmony_ci spz = zones_stack[--sp]; 40462306a36Sopenharmony_ci else 40562306a36Sopenharmony_ci spz = list_first_entry_or_null(&pr->scmi_zones, 40662306a36Sopenharmony_ci struct scmi_powercap_zone, 40762306a36Sopenharmony_ci node); 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ciout: 41262306a36Sopenharmony_ci kfree(zones_stack); 41362306a36Sopenharmony_ci dev_info(dev, "Registered %d SCMI Powercap domains !\n", reg_zones); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci return ret; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic int scmi_powercap_probe(struct scmi_device *sdev) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci int ret, i; 42162306a36Sopenharmony_ci struct scmi_powercap_root *pr; 42262306a36Sopenharmony_ci struct scmi_powercap_zone *spz; 42362306a36Sopenharmony_ci struct scmi_protocol_handle *ph; 42462306a36Sopenharmony_ci struct device *dev = &sdev->dev; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci if (!sdev->handle) 42762306a36Sopenharmony_ci return -ENODEV; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci powercap_ops = sdev->handle->devm_protocol_get(sdev, 43062306a36Sopenharmony_ci SCMI_PROTOCOL_POWERCAP, 43162306a36Sopenharmony_ci &ph); 43262306a36Sopenharmony_ci if (IS_ERR(powercap_ops)) 43362306a36Sopenharmony_ci return PTR_ERR(powercap_ops); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci pr = devm_kzalloc(dev, sizeof(*pr), GFP_KERNEL); 43662306a36Sopenharmony_ci if (!pr) 43762306a36Sopenharmony_ci return -ENOMEM; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci ret = powercap_ops->num_domains_get(ph); 44062306a36Sopenharmony_ci if (ret < 0) { 44162306a36Sopenharmony_ci dev_err(dev, "number of powercap domains not found\n"); 44262306a36Sopenharmony_ci return ret; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci pr->num_zones = ret; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci pr->spzones = devm_kcalloc(dev, pr->num_zones, 44762306a36Sopenharmony_ci sizeof(*pr->spzones), GFP_KERNEL); 44862306a36Sopenharmony_ci if (!pr->spzones) 44962306a36Sopenharmony_ci return -ENOMEM; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci /* Allocate for worst possible scenario of maximum tree height. */ 45262306a36Sopenharmony_ci pr->registered_zones = devm_kcalloc(dev, pr->num_zones, 45362306a36Sopenharmony_ci sizeof(*pr->registered_zones), 45462306a36Sopenharmony_ci GFP_KERNEL); 45562306a36Sopenharmony_ci if (!pr->registered_zones) 45662306a36Sopenharmony_ci return -ENOMEM; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci INIT_LIST_HEAD(&pr->scmi_zones); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) { 46162306a36Sopenharmony_ci /* 46262306a36Sopenharmony_ci * Powercap domains are validate by the protocol layer, i.e. 46362306a36Sopenharmony_ci * when only non-NULL domains are returned here, whose 46462306a36Sopenharmony_ci * parent_id is assured to point to another valid domain. 46562306a36Sopenharmony_ci */ 46662306a36Sopenharmony_ci spz->info = powercap_ops->info_get(ph, i); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci spz->dev = dev; 46962306a36Sopenharmony_ci spz->ph = ph; 47062306a36Sopenharmony_ci spz->spzones = pr->spzones; 47162306a36Sopenharmony_ci INIT_LIST_HEAD(&spz->node); 47262306a36Sopenharmony_ci INIT_LIST_HEAD(&pr->registered_zones[i]); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci list_add_tail(&spz->node, &pr->scmi_zones); 47562306a36Sopenharmony_ci /* 47662306a36Sopenharmony_ci * Forcibly skip powercap domains using an abstract scale. 47762306a36Sopenharmony_ci * Note that only leaves domains can be skipped, so this could 47862306a36Sopenharmony_ci * lead later to a global failure. 47962306a36Sopenharmony_ci */ 48062306a36Sopenharmony_ci if (!spz->info->powercap_scale_uw && 48162306a36Sopenharmony_ci !spz->info->powercap_scale_mw) { 48262306a36Sopenharmony_ci dev_warn(dev, 48362306a36Sopenharmony_ci "Abstract power scale not supported. Skip %s.\n", 48462306a36Sopenharmony_ci spz->info->name); 48562306a36Sopenharmony_ci spz->invalid = true; 48662306a36Sopenharmony_ci continue; 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci } 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci /* 49162306a36Sopenharmony_ci * Scan array of retrieved SCMI powercap domains and register them 49262306a36Sopenharmony_ci * recursively starting from the root domains. 49362306a36Sopenharmony_ci */ 49462306a36Sopenharmony_ci ret = scmi_zones_register(dev, pr); 49562306a36Sopenharmony_ci if (ret) 49662306a36Sopenharmony_ci return ret; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci dev_set_drvdata(dev, pr); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci return ret; 50162306a36Sopenharmony_ci} 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_cistatic void scmi_powercap_remove(struct scmi_device *sdev) 50462306a36Sopenharmony_ci{ 50562306a36Sopenharmony_ci struct device *dev = &sdev->dev; 50662306a36Sopenharmony_ci struct scmi_powercap_root *pr = dev_get_drvdata(dev); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci scmi_powercap_unregister_all_zones(pr); 50962306a36Sopenharmony_ci} 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_cistatic const struct scmi_device_id scmi_id_table[] = { 51262306a36Sopenharmony_ci { SCMI_PROTOCOL_POWERCAP, "powercap" }, 51362306a36Sopenharmony_ci { }, 51462306a36Sopenharmony_ci}; 51562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(scmi, scmi_id_table); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_cistatic struct scmi_driver scmi_powercap_driver = { 51862306a36Sopenharmony_ci .name = "scmi-powercap", 51962306a36Sopenharmony_ci .probe = scmi_powercap_probe, 52062306a36Sopenharmony_ci .remove = scmi_powercap_remove, 52162306a36Sopenharmony_ci .id_table = scmi_id_table, 52262306a36Sopenharmony_ci}; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_cistatic int __init scmi_powercap_init(void) 52562306a36Sopenharmony_ci{ 52662306a36Sopenharmony_ci int ret; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci scmi_top_pcntrl = powercap_register_control_type(NULL, "arm-scmi", NULL); 52962306a36Sopenharmony_ci if (IS_ERR(scmi_top_pcntrl)) 53062306a36Sopenharmony_ci return PTR_ERR(scmi_top_pcntrl); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci ret = scmi_register(&scmi_powercap_driver); 53362306a36Sopenharmony_ci if (ret) 53462306a36Sopenharmony_ci powercap_unregister_control_type(scmi_top_pcntrl); 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci return ret; 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_cimodule_init(scmi_powercap_init); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_cistatic void __exit scmi_powercap_exit(void) 54162306a36Sopenharmony_ci{ 54262306a36Sopenharmony_ci scmi_unregister(&scmi_powercap_driver); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci powercap_unregister_control_type(scmi_top_pcntrl); 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_cimodule_exit(scmi_powercap_exit); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ciMODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); 54962306a36Sopenharmony_ciMODULE_DESCRIPTION("ARM SCMI Powercap driver"); 55062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 551