Merge branches 'powercap', 'pm-x86', 'pm-opp' and 'pm-misc'

Merge power capping code updates, x86-specific power management pdate,
operating performance points library updates and miscellaneous power
management updates for 6.2-rc1:

 - Fix compiler warnings with make W=1 in the idle_inject power capping
   driver (Srinivas Pandruvada).

 - Use kstrtobool() instead of strtobool() in the power capping sysfs
   interface (Christophe JAILLET).

 - Add SCMI Powercap based power capping driver (Cristian Marussi).

 - Add Emerald Rapids support to the intel-uncore-freq driver (Artem
   Bityutskiy).

 - Repair slips in kernel-doc comments in the generic notifier code
   (Lukas Bulwahn).

 - Fix several DT issues in the OPP library reorganize code around
   opp-microvolt-<named> DT property (Viresh Kumar).

 - Allow any of opp-microvolt, opp-microamp, or opp-microwatt properties
   to be present without the others present (James Calligeros).

 - Fix clock-latency-ns property in DT example (Serge Semin).

* powercap:
  powercap: idle_inject: Fix warnings with make W=1
  powercap: Use kstrtobool() instead of strtobool()
  powercap: arm_scmi: Add SCMI Powercap based driver

* pm-x86:
  platform/x86: intel-uncore-freq: add Emerald Rapids support

* pm-opp:
  dt-bindings: opp-v2: Fix clock-latency-ns prop in example
  OPP: decouple dt properties in opp_parse_supplies()
  OPP: Simplify opp_parse_supplies() by restructuring it
  OPP: Parse named opp-microwatt property too
  dt-bindings: opp: Fix named microwatt property
  dt-bindings: opp: Fix usage of current in microwatt property

* pm-misc:
  notifier: repair slips in kernel-doc comments
This commit is contained in:
Rafael J. Wysocki 2022-12-12 16:23:36 +01:00
11 changed files with 658 additions and 159 deletions

View File

@ -108,7 +108,7 @@ patternProperties:
The power for the OPP in micro-Watts.
Entries for multiple regulators shall be provided in the same field
separated by angular brackets <>. If current values aren't required
separated by angular brackets <>. If power values aren't required
for a regulator, then it shall be filled with 0. If power values
aren't required for any of the regulators, then this field is not
required. The OPP binding doesn't provide any provisions to relate the
@ -230,9 +230,9 @@ patternProperties:
minItems: 1
maxItems: 8 # Should be enough regulators
'^opp-microwatt':
'^opp-microwatt-':
description:
Named opp-microwatt property. Similar to opp-microamp property,
Named opp-microwatt property. Similar to opp-microamp-<name> property,
but for microwatt instead.
$ref: /schemas/types.yaml#/definitions/uint32-array
minItems: 1

View File

@ -155,7 +155,7 @@ examples:
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <1025000>;
opp-microamp = <90000>;
lock-latency-ns = <290000>;
clock-latency-ns = <290000>;
turbo-mode;
};
};

View File

@ -20004,6 +20004,7 @@ F: drivers/clk/clk-sc[mp]i.c
F: drivers/cpufreq/sc[mp]i-cpufreq.c
F: drivers/firmware/arm_scmi/
F: drivers/firmware/arm_scpi.c
F: drivers/powercap/arm_scmi_powercap.c
F: drivers/regulator/scmi-regulator.c
F: drivers/reset/reset-scmi.c
F: include/linux/sc[mp]i_protocol.h

View File

@ -578,169 +578,140 @@ static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
return false;
}
static u32 *_parse_named_prop(struct dev_pm_opp *opp, struct device *dev,
struct opp_table *opp_table,
const char *prop_type, bool *triplet)
{
struct property *prop = NULL;
char name[NAME_MAX];
int count, ret;
u32 *out;
/* Search for "opp-<prop_type>-<name>" */
if (opp_table->prop_name) {
snprintf(name, sizeof(name), "opp-%s-%s", prop_type,
opp_table->prop_name);
prop = of_find_property(opp->np, name, NULL);
}
if (!prop) {
/* Search for "opp-<prop_type>" */
snprintf(name, sizeof(name), "opp-%s", prop_type);
prop = of_find_property(opp->np, name, NULL);
if (!prop)
return NULL;
}
count = of_property_count_u32_elems(opp->np, name);
if (count < 0) {
dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, name,
count);
return ERR_PTR(count);
}
/*
* Initialize regulator_count, if regulator information isn't provided
* by the platform. Now that one of the properties is available, fix the
* regulator_count to 1.
*/
if (unlikely(opp_table->regulator_count == -1))
opp_table->regulator_count = 1;
if (count != opp_table->regulator_count &&
(!triplet || count != opp_table->regulator_count * 3)) {
dev_err(dev, "%s: Invalid number of elements in %s property (%u) with supplies (%d)\n",
__func__, prop_type, count, opp_table->regulator_count);
return ERR_PTR(-EINVAL);
}
out = kmalloc_array(count, sizeof(*out), GFP_KERNEL);
if (!out)
return ERR_PTR(-EINVAL);
ret = of_property_read_u32_array(opp->np, name, out, count);
if (ret) {
dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret);
kfree(out);
return ERR_PTR(-EINVAL);
}
if (triplet)
*triplet = count != opp_table->regulator_count;
return out;
}
static u32 *opp_parse_microvolt(struct dev_pm_opp *opp, struct device *dev,
struct opp_table *opp_table, bool *triplet)
{
u32 *microvolt;
microvolt = _parse_named_prop(opp, dev, opp_table, "microvolt", triplet);
if (IS_ERR(microvolt))
return microvolt;
if (!microvolt) {
/*
* Missing property isn't a problem, but an invalid
* entry is. This property isn't optional if regulator
* information is provided. Check only for the first OPP, as
* regulator_count may get initialized after that to a valid
* value.
*/
if (list_empty(&opp_table->opp_list) &&
opp_table->regulator_count > 0) {
dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n",
__func__);
return ERR_PTR(-EINVAL);
}
}
return microvolt;
}
static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
struct opp_table *opp_table)
{
u32 *microvolt, *microamp = NULL, *microwatt = NULL;
int supplies = opp_table->regulator_count;
int vcount, icount, pcount, ret, i, j;
struct property *prop = NULL;
char name[NAME_MAX];
u32 *microvolt, *microamp, *microwatt;
int ret = 0, i, j;
bool triplet;
/* Search for "opp-microvolt-<name>" */
if (opp_table->prop_name) {
snprintf(name, sizeof(name), "opp-microvolt-%s",
opp_table->prop_name);
prop = of_find_property(opp->np, name, NULL);
}
microvolt = opp_parse_microvolt(opp, dev, opp_table, &triplet);
if (IS_ERR(microvolt))
return PTR_ERR(microvolt);
if (!prop) {
/* Search for "opp-microvolt" */
sprintf(name, "opp-microvolt");
prop = of_find_property(opp->np, name, NULL);
/* Missing property isn't a problem, but an invalid entry is */
if (!prop) {
if (unlikely(supplies == -1)) {
/* Initialize regulator_count */
opp_table->regulator_count = 0;
return 0;
}
if (!supplies)
return 0;
dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n",
__func__);
return -EINVAL;
}
}
if (unlikely(supplies == -1)) {
/* Initialize regulator_count */
supplies = opp_table->regulator_count = 1;
} else if (unlikely(!supplies)) {
dev_err(dev, "%s: opp-microvolt wasn't expected\n", __func__);
return -EINVAL;
}
vcount = of_property_count_u32_elems(opp->np, name);
if (vcount < 0) {
dev_err(dev, "%s: Invalid %s property (%d)\n",
__func__, name, vcount);
return vcount;
}
/* There can be one or three elements per supply */
if (vcount != supplies && vcount != supplies * 3) {
dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
__func__, name, vcount, supplies);
return -EINVAL;
}
microvolt = kmalloc_array(vcount, sizeof(*microvolt), GFP_KERNEL);
if (!microvolt)
return -ENOMEM;
ret = of_property_read_u32_array(opp->np, name, microvolt, vcount);
if (ret) {
dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret);
ret = -EINVAL;
microamp = _parse_named_prop(opp, dev, opp_table, "microamp", NULL);
if (IS_ERR(microamp)) {
ret = PTR_ERR(microamp);
goto free_microvolt;
}
/* Search for "opp-microamp-<name>" */
prop = NULL;
if (opp_table->prop_name) {
snprintf(name, sizeof(name), "opp-microamp-%s",
opp_table->prop_name);
prop = of_find_property(opp->np, name, NULL);
microwatt = _parse_named_prop(opp, dev, opp_table, "microwatt", NULL);
if (IS_ERR(microwatt)) {
ret = PTR_ERR(microwatt);
goto free_microamp;
}
if (!prop) {
/* Search for "opp-microamp" */
sprintf(name, "opp-microamp");
prop = of_find_property(opp->np, name, NULL);
/*
* Initialize regulator_count if it is uninitialized and no properties
* are found.
*/
if (unlikely(opp_table->regulator_count == -1)) {
opp_table->regulator_count = 0;
return 0;
}
if (prop) {
icount = of_property_count_u32_elems(opp->np, name);
if (icount < 0) {
dev_err(dev, "%s: Invalid %s property (%d)\n", __func__,
name, icount);
ret = icount;
goto free_microvolt;
}
for (i = 0, j = 0; i < opp_table->regulator_count; i++) {
if (microvolt) {
opp->supplies[i].u_volt = microvolt[j++];
if (icount != supplies) {
dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
__func__, name, icount, supplies);
ret = -EINVAL;
goto free_microvolt;
}
microamp = kmalloc_array(icount, sizeof(*microamp), GFP_KERNEL);
if (!microamp) {
ret = -EINVAL;
goto free_microvolt;
}
ret = of_property_read_u32_array(opp->np, name, microamp,
icount);
if (ret) {
dev_err(dev, "%s: error parsing %s: %d\n", __func__,
name, ret);
ret = -EINVAL;
goto free_microamp;
}
}
/* Search for "opp-microwatt" */
sprintf(name, "opp-microwatt");
prop = of_find_property(opp->np, name, NULL);
if (prop) {
pcount = of_property_count_u32_elems(opp->np, name);
if (pcount < 0) {
dev_err(dev, "%s: Invalid %s property (%d)\n", __func__,
name, pcount);
ret = pcount;
goto free_microamp;
}
if (pcount != supplies) {
dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
__func__, name, pcount, supplies);
ret = -EINVAL;
goto free_microamp;
}
microwatt = kmalloc_array(pcount, sizeof(*microwatt),
GFP_KERNEL);
if (!microwatt) {
ret = -EINVAL;
goto free_microamp;
}
ret = of_property_read_u32_array(opp->np, name, microwatt,
pcount);
if (ret) {
dev_err(dev, "%s: error parsing %s: %d\n", __func__,
name, ret);
ret = -EINVAL;
goto free_microwatt;
}
}
for (i = 0, j = 0; i < supplies; i++) {
opp->supplies[i].u_volt = microvolt[j++];
if (vcount == supplies) {
opp->supplies[i].u_volt_min = opp->supplies[i].u_volt;
opp->supplies[i].u_volt_max = opp->supplies[i].u_volt;
} else {
opp->supplies[i].u_volt_min = microvolt[j++];
opp->supplies[i].u_volt_max = microvolt[j++];
if (triplet) {
opp->supplies[i].u_volt_min = microvolt[j++];
opp->supplies[i].u_volt_max = microvolt[j++];
} else {
opp->supplies[i].u_volt_min = opp->supplies[i].u_volt;
opp->supplies[i].u_volt_max = opp->supplies[i].u_volt;
}
}
if (microamp)
@ -750,7 +721,6 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
opp->supplies[i].u_watt = microwatt[i];
}
free_microwatt:
kfree(microwatt);
free_microamp:
kfree(microamp);

View File

@ -203,6 +203,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL),
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
X86_MATCH_INTEL_FAM6_MODEL(EMERALDRAPIDS_X, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids);

View File

@ -44,6 +44,19 @@ config IDLE_INJECT
synchronously on a set of specified CPUs or alternatively
on a per CPU basis.
config ARM_SCMI_POWERCAP
tristate "ARM SCMI Powercap driver"
depends on ARM_SCMI_PROTOCOL
help
This enables support for the ARM Powercap based on ARM SCMI
Powercap protocol.
ARM SCMI Powercap protocol allows power limits to be enforced
and monitored against the SCMI Powercap domains advertised as
available by the SCMI platform firmware.
When compiled as module it will be called arm_scmi_powercap.ko.
config DTPM
bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
depends on OF

View File

@ -6,3 +6,4 @@ obj-$(CONFIG_POWERCAP) += powercap_sys.o
obj-$(CONFIG_INTEL_RAPL_CORE) += intel_rapl_common.o
obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o
obj-$(CONFIG_IDLE_INJECT) += idle_inject.o
obj-$(CONFIG_ARM_SCMI_POWERCAP) += arm_scmi_powercap.o

View File

@ -0,0 +1,509 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SCMI Powercap support.
*
* Copyright (C) 2022 ARM Ltd.
*/
#include <linux/device.h>
#include <linux/math.h>
#include <linux/limits.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/powercap.h>
#include <linux/scmi_protocol.h>
#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 {
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;
};
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 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,
};
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 bool
scmi_powercap_is_zone_registered(struct scmi_powercap_zone *spz)
{
return !list_empty(&spz->node);
}
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];
}
/**
* scmi_powercap_register_zone - Register an SCMI powercap zone recursively
*
* @pr: A reference to the root powercap zones descriptors
* @spz: A reference to the SCMI powercap zone to register
*
* 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 an SCMI powercap zone to register, takes care to walk
* the SCMI powercap zones tree up to the root looking recursively for
* unregistered parent zones before registering the provided zone; 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_powercap_register_zone(struct scmi_powercap_root *pr,
struct scmi_powercap_zone *spz)
{
int ret = 0;
struct scmi_powercap_zone *parent;
if (!spz->info)
return ret;
parent = scmi_powercap_get_parent_zone(spz);
if (parent && !scmi_powercap_is_zone_registered(parent)) {
/*
* Bail out if a parent domain was marked as unsupported:
* only domains participating as leaves can be skipped.
*/
if (!parent->info)
return -ENODEV;
ret = scmi_powercap_register_zone(pr, parent);
if (ret)
return ret;
}
if (!scmi_powercap_is_zone_registered(spz)) {
struct powercap_zone *z;
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);
list_add(&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);
ret = 0;
} else {
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;
}
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;
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]);
/*
* 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->info = NULL;
continue;
}
}
/*
* Scan array of retrieved SCMI powercap domains and register them
* recursively starting from the root domains.
*/
for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) {
ret = scmi_powercap_register_zone(pr, spz);
if (ret) {
dev_err(dev,
"Failed to register powercap zone %s - ret:%d\n",
spz->info->name, ret);
scmi_powercap_unregister_all_zones(pr);
return ret;
}
}
dev_set_drvdata(dev, pr);
dev_info(dev, "Registered %d SCMI Powercap domains !\n", pr->num_zones);
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 <cristian.marussi@arm.com>");
MODULE_DESCRIPTION("ARM SCMI Powercap driver");
MODULE_LICENSE("GPL");

View File

@ -147,6 +147,7 @@ static void idle_inject_fn(unsigned int cpu)
/**
* idle_inject_set_duration - idle and run duration update helper
* @ii_dev: idle injection control device structure
* @run_duration_us: CPU run time to allow in microseconds
* @idle_duration_us: CPU idle time to inject in microseconds
*/
@ -162,6 +163,7 @@ void idle_inject_set_duration(struct idle_inject_device *ii_dev,
/**
* idle_inject_get_duration - idle and run duration retrieval helper
* @ii_dev: idle injection control device structure
* @run_duration_us: memory location to store the current CPU run time
* @idle_duration_us: memory location to store the current CPU idle time
*/
@ -175,6 +177,7 @@ void idle_inject_get_duration(struct idle_inject_device *ii_dev,
/**
* idle_inject_set_latency - set the maximum latency allowed
* @ii_dev: idle injection control device structure
* @latency_us: set the latency requirement for the idle state
*/
void idle_inject_set_latency(struct idle_inject_device *ii_dev,

View File

@ -7,6 +7,7 @@
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kstrtox.h>
#include <linux/slab.h>
#include <linux/powercap.h>
@ -446,7 +447,7 @@ static ssize_t enabled_store(struct device *dev,
{
bool mode;
if (strtobool(buf, &mode))
if (kstrtobool(buf, &mode))
return -EINVAL;
if (dev->parent) {
struct powercap_zone *power_zone = to_powercap_zone(dev);

View File

@ -62,7 +62,7 @@ static int notifier_chain_unregister(struct notifier_block **nl,
* value of this parameter is -1.
* @nr_calls: Records the number of notifications sent. Don't care
* value of this field is NULL.
* @returns: notifier_call_chain returns the value returned by the
* Return: notifier_call_chain returns the value returned by the
* last notifier function called.
*/
static int notifier_call_chain(struct notifier_block **nl,
@ -105,13 +105,13 @@ NOKPROBE_SYMBOL(notifier_call_chain);
* @val_up: Value passed unmodified to the notifier function
* @val_down: Value passed unmodified to the notifier function when recovering
* from an error on @val_up
* @v Pointer passed unmodified to the notifier function
* @v: Pointer passed unmodified to the notifier function
*
* NOTE: It is important the @nl chain doesn't change between the two
* invocations of notifier_call_chain() such that we visit the
* exact same notifier callbacks; this rules out any RCU usage.
*
* Returns: the return value of the @val_up call.
* Return: the return value of the @val_up call.
*/
static int notifier_call_chain_robust(struct notifier_block **nl,
unsigned long val_up, unsigned long val_down,