// SPDX-License-Identifier: GPL-2.0 /* * SCMI Powercap support. * * Copyright (C) 2022 ARM Ltd. */ #include #include #include #include #include #include #include #include #define to_scmi_powercap_zone(z) \ container_of(z, struct scmi_powercap_zone, zone) static const struct scmi_powercap_proto_ops *powercap_ops; struct scmi_powercap_zone { bool registered; bool invalid; unsigned int height; struct device *dev; struct scmi_protocol_handle *ph; const struct scmi_powercap_info *info; struct scmi_powercap_zone *spzones; struct powercap_zone zone; struct list_head node; }; struct scmi_powercap_root { unsigned int num_zones; struct scmi_powercap_zone *spzones; struct list_head *registered_zones; struct list_head scmi_zones; }; static struct powercap_control_type *scmi_top_pcntrl; static int scmi_powercap_zone_release(struct powercap_zone *pz) { return 0; } static int scmi_powercap_get_max_power_range_uw(struct powercap_zone *pz, u64 *max_power_range_uw) { *max_power_range_uw = U32_MAX; return 0; } static int scmi_powercap_get_power_uw(struct powercap_zone *pz, u64 *power_uw) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); u32 avg_power, pai; int ret; if (!spz->info->powercap_monitoring) return -EINVAL; ret = powercap_ops->measurements_get(spz->ph, spz->info->id, &avg_power, &pai); if (ret) return ret; *power_uw = avg_power; if (spz->info->powercap_scale_mw) *power_uw *= 1000; return 0; } static int scmi_powercap_zone_enable_set(struct powercap_zone *pz, bool mode) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); return powercap_ops->cap_enable_set(spz->ph, spz->info->id, mode); } static int scmi_powercap_zone_enable_get(struct powercap_zone *pz, bool *mode) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); return powercap_ops->cap_enable_get(spz->ph, spz->info->id, mode); } static const struct powercap_zone_ops zone_ops = { .get_max_power_range_uw = scmi_powercap_get_max_power_range_uw, .get_power_uw = scmi_powercap_get_power_uw, .release = scmi_powercap_zone_release, .set_enable = scmi_powercap_zone_enable_set, .get_enable = scmi_powercap_zone_enable_get, }; static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz, u64 power_limit_uw, u32 *norm) { bool scale_mw = spz->info->powercap_scale_mw; u64 val; val = scale_mw ? DIV_ROUND_UP_ULL(power_limit_uw, 1000) : power_limit_uw; /* * This cast is lossless since here @req_power is certain to be within * the range [min_power_cap, max_power_cap] whose bounds are assured to * be two unsigned 32bits quantities. */ *norm = clamp_t(u32, val, spz->info->min_power_cap, spz->info->max_power_cap); *norm = rounddown(*norm, spz->info->power_cap_step); val = (scale_mw) ? *norm * 1000 : *norm; if (power_limit_uw != val) dev_dbg(spz->dev, "Normalized %s:CAP - requested:%llu - normalized:%llu\n", spz->info->name, power_limit_uw, val); } static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid, u64 power_uw) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); u32 norm_power; if (!spz->info->powercap_cap_config) return -EINVAL; scmi_powercap_normalize_cap(spz, power_uw, &norm_power); return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false); } static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid, u64 *power_limit_uw) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); u32 power; int ret; ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power); if (ret) return ret; *power_limit_uw = power; if (spz->info->powercap_scale_mw) *power_limit_uw *= 1000; return 0; } static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz, u64 time_us, u32 *norm) { /* * This cast is lossless since here @time_us is certain to be within the * range [min_pai, max_pai] whose bounds are assured to be two unsigned * 32bits quantities. */ *norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai); *norm = rounddown(*norm, spz->info->pai_step); if (time_us != *norm) dev_dbg(spz->dev, "Normalized %s:PAI - requested:%llu - normalized:%u\n", spz->info->name, time_us, *norm); } static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid, u64 time_window_us) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); u32 norm_pai; if (!spz->info->powercap_pai_config) return -EINVAL; scmi_powercap_normalize_time(spz, time_window_us, &norm_pai); return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai); } static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid, u64 *time_window_us) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); int ret; u32 pai; ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai); if (ret) return ret; *time_window_us = pai; return 0; } static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid, u64 *max_power_uw) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); *max_power_uw = spz->info->max_power_cap; if (spz->info->powercap_scale_mw) *max_power_uw *= 1000; return 0; } static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid, u64 *min_power_uw) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); *min_power_uw = spz->info->min_power_cap; if (spz->info->powercap_scale_mw) *min_power_uw *= 1000; return 0; } static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz, int cid, u64 *time_window_us) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); *time_window_us = spz->info->max_pai; return 0; } static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz, int cid, u64 *time_window_us) { struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz); *time_window_us = (u64)spz->info->min_pai; return 0; } static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid) { return "SCMI power-cap"; } static const struct powercap_zone_constraint_ops constraint_ops = { .set_power_limit_uw = scmi_powercap_set_power_limit_uw, .get_power_limit_uw = scmi_powercap_get_power_limit_uw, .set_time_window_us = scmi_powercap_set_time_window_us, .get_time_window_us = scmi_powercap_get_time_window_us, .get_max_power_uw = scmi_powercap_get_max_power_uw, .get_min_power_uw = scmi_powercap_get_min_power_uw, .get_max_time_window_us = scmi_powercap_get_max_time_window_us, .get_min_time_window_us = scmi_powercap_get_min_time_window_us, .get_name = scmi_powercap_get_name, }; static void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr) { int i; /* Un-register children zones first starting from the leaves */ for (i = pr->num_zones - 1; i >= 0; i--) { if (!list_empty(&pr->registered_zones[i])) { struct scmi_powercap_zone *spz; list_for_each_entry(spz, &pr->registered_zones[i], node) powercap_unregister_zone(scmi_top_pcntrl, &spz->zone); } } } static inline unsigned int scmi_powercap_get_zone_height(struct scmi_powercap_zone *spz) { if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID) return 0; return spz->spzones[spz->info->parent_id].height + 1; } static inline struct scmi_powercap_zone * scmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz) { if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID) return NULL; return &spz->spzones[spz->info->parent_id]; } static int scmi_powercap_register_zone(struct scmi_powercap_root *pr, struct scmi_powercap_zone *spz, struct scmi_powercap_zone *parent) { int ret = 0; struct powercap_zone *z; if (spz->invalid) { list_del(&spz->node); return -EINVAL; } z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name, parent ? &parent->zone : NULL, &zone_ops, 1, &constraint_ops); if (!IS_ERR(z)) { spz->height = scmi_powercap_get_zone_height(spz); spz->registered = true; list_move(&spz->node, &pr->registered_zones[spz->height]); dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n", spz->info->name, parent ? parent->info->name : "ROOT", spz->height); } else { list_del(&spz->node); ret = PTR_ERR(z); dev_err(spz->dev, "Error registering node:%s - parent:%s - h:%d - ret:%d\n", spz->info->name, parent ? parent->info->name : "ROOT", spz->height, ret); } return ret; } /** * scmi_zones_register- Register SCMI powercap zones starting from parent zones * * @dev: A reference to the SCMI device * @pr: A reference to the root powercap zones descriptors * * When registering SCMI powercap zones with the powercap framework we should * take care to always register zones starting from the root ones and to * deregister starting from the leaves. * * Unfortunately we cannot assume that the array of available SCMI powercap * zones provided by the SCMI platform firmware is built to comply with such * requirement. * * This function, given the set of SCMI powercap zones to register, takes care * to walk the SCMI powercap zones trees up to the root registering any * unregistered parent zone before registering the child zones; at the same * time each registered-zone height in such a tree is accounted for and each * zone, once registered, is stored in the @registered_zones array that is * indexed by zone height: this way will be trivial, at unregister time, to walk * the @registered_zones array backward and unregister all the zones starting * from the leaves, removing children zones before parents. * * While doing this, we prune away any zone marked as invalid (like the ones * sporting an SCMI abstract power scale) as long as they are positioned as * leaves in the SCMI powercap zones hierarchy: any non-leaf invalid zone causes * the entire process to fail since we cannot assume the correctness of an SCMI * powercap zones hierarchy if some of the internal nodes are missing. * * Note that the array of SCMI powercap zones as returned by the SCMI platform * is known to be sane, i.e. zones relationships have been validated at the * protocol layer. * * Return: 0 on Success */ static int scmi_zones_register(struct device *dev, struct scmi_powercap_root *pr) { int ret = 0; unsigned int sp = 0, reg_zones = 0; struct scmi_powercap_zone *spz, **zones_stack; zones_stack = kcalloc(pr->num_zones, sizeof(spz), GFP_KERNEL); if (!zones_stack) return -ENOMEM; spz = list_first_entry_or_null(&pr->scmi_zones, struct scmi_powercap_zone, node); while (spz) { struct scmi_powercap_zone *parent; parent = scmi_powercap_get_parent_zone(spz); if (parent && !parent->registered) { zones_stack[sp++] = spz; spz = parent; } else { ret = scmi_powercap_register_zone(pr, spz, parent); if (!ret) { reg_zones++; } else if (sp) { /* Failed to register a non-leaf zone. * Bail-out. */ dev_err(dev, "Failed to register non-leaf zone - ret:%d\n", ret); scmi_powercap_unregister_all_zones(pr); reg_zones = 0; goto out; } /* Pick next zone to process */ if (sp) spz = zones_stack[--sp]; else spz = list_first_entry_or_null(&pr->scmi_zones, struct scmi_powercap_zone, node); } } out: kfree(zones_stack); dev_info(dev, "Registered %d SCMI Powercap domains !\n", reg_zones); return ret; } static int scmi_powercap_probe(struct scmi_device *sdev) { int ret, i; struct scmi_powercap_root *pr; struct scmi_powercap_zone *spz; struct scmi_protocol_handle *ph; struct device *dev = &sdev->dev; if (!sdev->handle) return -ENODEV; powercap_ops = sdev->handle->devm_protocol_get(sdev, SCMI_PROTOCOL_POWERCAP, &ph); if (IS_ERR(powercap_ops)) return PTR_ERR(powercap_ops); pr = devm_kzalloc(dev, sizeof(*pr), GFP_KERNEL); if (!pr) return -ENOMEM; ret = powercap_ops->num_domains_get(ph); if (ret < 0) { dev_err(dev, "number of powercap domains not found\n"); return ret; } pr->num_zones = ret; pr->spzones = devm_kcalloc(dev, pr->num_zones, sizeof(*pr->spzones), GFP_KERNEL); if (!pr->spzones) return -ENOMEM; /* Allocate for worst possible scenario of maximum tree height. */ pr->registered_zones = devm_kcalloc(dev, pr->num_zones, sizeof(*pr->registered_zones), GFP_KERNEL); if (!pr->registered_zones) return -ENOMEM; INIT_LIST_HEAD(&pr->scmi_zones); for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) { /* * Powercap domains are validate by the protocol layer, i.e. * when only non-NULL domains are returned here, whose * parent_id is assured to point to another valid domain. */ spz->info = powercap_ops->info_get(ph, i); spz->dev = dev; spz->ph = ph; spz->spzones = pr->spzones; INIT_LIST_HEAD(&spz->node); INIT_LIST_HEAD(&pr->registered_zones[i]); list_add_tail(&spz->node, &pr->scmi_zones); /* * Forcibly skip powercap domains using an abstract scale. * Note that only leaves domains can be skipped, so this could * lead later to a global failure. */ if (!spz->info->powercap_scale_uw && !spz->info->powercap_scale_mw) { dev_warn(dev, "Abstract power scale not supported. Skip %s.\n", spz->info->name); spz->invalid = true; continue; } } /* * Scan array of retrieved SCMI powercap domains and register them * recursively starting from the root domains. */ ret = scmi_zones_register(dev, pr); if (ret) return ret; dev_set_drvdata(dev, pr); return ret; } static void scmi_powercap_remove(struct scmi_device *sdev) { struct device *dev = &sdev->dev; struct scmi_powercap_root *pr = dev_get_drvdata(dev); scmi_powercap_unregister_all_zones(pr); } static const struct scmi_device_id scmi_id_table[] = { { SCMI_PROTOCOL_POWERCAP, "powercap" }, { }, }; MODULE_DEVICE_TABLE(scmi, scmi_id_table); static struct scmi_driver scmi_powercap_driver = { .name = "scmi-powercap", .probe = scmi_powercap_probe, .remove = scmi_powercap_remove, .id_table = scmi_id_table, }; static int __init scmi_powercap_init(void) { int ret; scmi_top_pcntrl = powercap_register_control_type(NULL, "arm-scmi", NULL); if (IS_ERR(scmi_top_pcntrl)) return PTR_ERR(scmi_top_pcntrl); ret = scmi_register(&scmi_powercap_driver); if (ret) powercap_unregister_control_type(scmi_top_pcntrl); return ret; } module_init(scmi_powercap_init); static void __exit scmi_powercap_exit(void) { scmi_unregister(&scmi_powercap_driver); powercap_unregister_control_type(scmi_top_pcntrl); } module_exit(scmi_powercap_exit); MODULE_AUTHOR("Cristian Marussi "); MODULE_DESCRIPTION("ARM SCMI Powercap driver"); MODULE_LICENSE("GPL");