- Added dtpm hierarchy description (Daniel Lezcano)
- Changed the locking scheme (Daniel Lezcano) - Fixed dtpm_cpu cleanup at exit time and missing virtual dtpm pointer release (Daniel Lezcano) -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEGn3N4YVz0WNVyHskqDIjiipP6E8FAmIWg2YACgkQqDIjiipP 6E8kYAf/YGJpBz5tWEUyGZWD4kPHxDiij6mu++qrXmRSBR3Baozq94WaR7wynC3U DNo9Zw3dJ4H9t7YtxDKdSV93oeKD5erXllqkLiq1lhNQR50p3gL3rypbR4VCr5y0 B7scPdBtHuLuFU3TPZTkmzF2krsncH4WdhcIm4/h6jrvYDkCs9v4fEHqujgmBtFa 4yB6EoZQp+9hUcjQUkVwolpCW6BlkSOaIIabzEiuBfLs+6Hy64RIzguMo3yg036P 88ed1o7tvK/bV3aLXQ1UufUadhaxkMp/mdBuJ3lUaRiJyeYtVR9EKlxa7/0bNzc5 N+ZAlpzqAY7q/1/J7wL7gIo7g1ZeFQ== =5pr0 -----END PGP SIGNATURE----- Merge tag 'dtpm-v5.18' of https://git.linaro.org/people/daniel.lezcano/linux Pull DTPM (Dynamic Thermal Power Management) changes for 5.18-rc1 from Daniel Lezcano: "- Added dtpm hierarchy description (Daniel Lezcano) - Changed the locking scheme (Daniel Lezcano) - Fixed dtpm_cpu cleanup at exit time and missing virtual dtpm pointer release (Daniel Lezcano)" * tag 'dtpm-v5.18' of https://git.linaro.org/people/daniel.lezcano/linux: dtpm/soc/rk3399: Add the ability to unload the module powercap/dtpm_cpu: Add exit function powercap/dtpm: Move the 'root' reset place powercap/dtpm: Destroy hierarchy function powercap/dtpm: Fixup kfree for virtual node powercap/dtpm_cpu: Reset per_cpu variable in the release function powercap/dtpm: Change locking scheme rockchip/soc/drivers: Add DTPM description for rk3399 powercap/drivers/dtpm: Add dtpm devfreq with energy model support powercap/drivers/dtpm: Add CPU DT initialization support powercap/drivers/dtpm: Add hierarchy creation powercap/drivers/dtpm: Convert the init table section to a simple array
This commit is contained in:
commit
46501add15
@ -46,6 +46,7 @@ config IDLE_INJECT
|
||||
|
||||
config DTPM
|
||||
bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
|
||||
depends on OF
|
||||
help
|
||||
This enables support for the power capping for the dynamic
|
||||
thermal power management userspace engine.
|
||||
@ -56,4 +57,11 @@ config DTPM_CPU
|
||||
help
|
||||
This enables support for CPU power limitation based on
|
||||
energy model.
|
||||
|
||||
config DTPM_DEVFREQ
|
||||
bool "Add device power capping based on the energy model"
|
||||
depends on DTPM && ENERGY_MODEL
|
||||
help
|
||||
This enables support for device power limitation based on
|
||||
energy model.
|
||||
endif
|
||||
|
@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_DTPM) += dtpm.o
|
||||
obj-$(CONFIG_DTPM_CPU) += dtpm_cpu.o
|
||||
obj-$(CONFIG_DTPM_DEVFREQ) += dtpm_devfreq.o
|
||||
obj-$(CONFIG_POWERCAP) += powercap_sys.o
|
||||
obj-$(CONFIG_INTEL_RAPL_CORE) += intel_rapl_common.o
|
||||
obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o
|
||||
|
@ -23,6 +23,9 @@
|
||||
#include <linux/powercap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "dtpm_subsys.h"
|
||||
|
||||
#define DTPM_POWER_LIMIT_FLAG 0
|
||||
|
||||
@ -48,9 +51,7 @@ static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
|
||||
{
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
*max_power_uw = dtpm->power_max - dtpm->power_min;
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -80,14 +81,7 @@ static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
|
||||
|
||||
static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
|
||||
{
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
ret = __get_power_uw(dtpm, power_uw);
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return ret;
|
||||
return __get_power_uw(to_dtpm(pcz), power_uw);
|
||||
}
|
||||
|
||||
static void __dtpm_rebalance_weight(struct dtpm *dtpm)
|
||||
@ -130,7 +124,16 @@ static void __dtpm_add_power(struct dtpm *dtpm)
|
||||
}
|
||||
}
|
||||
|
||||
static int __dtpm_update_power(struct dtpm *dtpm)
|
||||
/**
|
||||
* dtpm_update_power - Update the power on the dtpm
|
||||
* @dtpm: a pointer to a dtpm structure to update
|
||||
*
|
||||
* Function to update the power values of the dtpm node specified in
|
||||
* parameter. These new values will be propagated to the tree.
|
||||
*
|
||||
* Return: zero on success, -EINVAL if the values are inconsistent
|
||||
*/
|
||||
int dtpm_update_power(struct dtpm *dtpm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -152,26 +155,6 @@ static int __dtpm_update_power(struct dtpm *dtpm)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dtpm_update_power - Update the power on the dtpm
|
||||
* @dtpm: a pointer to a dtpm structure to update
|
||||
*
|
||||
* Function to update the power values of the dtpm node specified in
|
||||
* parameter. These new values will be propagated to the tree.
|
||||
*
|
||||
* Return: zero on success, -EINVAL if the values are inconsistent
|
||||
*/
|
||||
int dtpm_update_power(struct dtpm *dtpm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
ret = __dtpm_update_power(dtpm);
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dtpm_release_zone - Cleanup when the node is released
|
||||
* @pcz: a pointer to a powercap_zone structure
|
||||
@ -188,48 +171,28 @@ int dtpm_release_zone(struct powercap_zone *pcz)
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
struct dtpm *parent = dtpm->parent;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
|
||||
if (!list_empty(&dtpm->children)) {
|
||||
mutex_unlock(&dtpm_lock);
|
||||
if (!list_empty(&dtpm->children))
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (parent)
|
||||
list_del(&dtpm->sibling);
|
||||
|
||||
__dtpm_sub_power(dtpm);
|
||||
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
if (dtpm->ops)
|
||||
dtpm->ops->release(dtpm);
|
||||
else
|
||||
kfree(dtpm);
|
||||
|
||||
if (root == dtpm)
|
||||
root = NULL;
|
||||
|
||||
kfree(dtpm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __get_power_limit_uw(struct dtpm *dtpm, int cid, u64 *power_limit)
|
||||
{
|
||||
*power_limit = dtpm->power_limit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_power_limit_uw(struct powercap_zone *pcz,
|
||||
int cid, u64 *power_limit)
|
||||
{
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
ret = __get_power_limit_uw(dtpm, cid, power_limit);
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return ret;
|
||||
*power_limit = to_dtpm(pcz)->power_limit;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -289,7 +252,7 @@ static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
|
||||
|
||||
ret = __set_power_limit_uw(child, cid, power);
|
||||
if (!ret)
|
||||
ret = __get_power_limit_uw(child, cid, &power);
|
||||
ret = get_power_limit_uw(&child->zone, cid, &power);
|
||||
|
||||
if (ret)
|
||||
break;
|
||||
@ -307,8 +270,6 @@ static int set_power_limit_uw(struct powercap_zone *pcz,
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
|
||||
/*
|
||||
* Don't allow values outside of the power range previously
|
||||
* set when initializing the power numbers.
|
||||
@ -320,8 +281,6 @@ static int set_power_limit_uw(struct powercap_zone *pcz,
|
||||
pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
|
||||
dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
|
||||
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -332,11 +291,7 @@ static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
|
||||
|
||||
static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
|
||||
{
|
||||
struct dtpm *dtpm = to_dtpm(pcz);
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
*max_power = dtpm->power_max;
|
||||
mutex_unlock(&dtpm_lock);
|
||||
*max_power = to_dtpm(pcz)->power_max;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -439,8 +394,6 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
|
||||
if (IS_ERR(pcz))
|
||||
return PTR_ERR(pcz);
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
|
||||
if (parent) {
|
||||
list_add_tail(&dtpm->sibling, &parent->children);
|
||||
dtpm->parent = parent;
|
||||
@ -456,19 +409,253 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
|
||||
pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
|
||||
dtpm->zone.name, dtpm->power_min, dtpm->power_max);
|
||||
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init init_dtpm(void)
|
||||
static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
|
||||
struct dtpm *parent)
|
||||
{
|
||||
pct = powercap_register_control_type(NULL, "dtpm", NULL);
|
||||
if (IS_ERR(pct)) {
|
||||
pr_err("Failed to register control type\n");
|
||||
return PTR_ERR(pct);
|
||||
struct dtpm *dtpm;
|
||||
int ret;
|
||||
|
||||
dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
|
||||
if (!dtpm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
dtpm_init(dtpm, NULL);
|
||||
|
||||
ret = dtpm_register(hierarchy->name, dtpm, parent);
|
||||
if (ret) {
|
||||
pr_err("Failed to register dtpm node '%s': %d\n",
|
||||
hierarchy->name, ret);
|
||||
kfree(dtpm);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return dtpm;
|
||||
}
|
||||
|
||||
static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
|
||||
struct dtpm *parent)
|
||||
{
|
||||
struct device_node *np;
|
||||
int i, ret;
|
||||
|
||||
np = of_find_node_by_path(hierarchy->name);
|
||||
if (!np) {
|
||||
pr_err("Failed to find '%s'\n", hierarchy->name);
|
||||
return ERR_PTR(-ENXIO);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
|
||||
|
||||
if (!dtpm_subsys[i]->setup)
|
||||
continue;
|
||||
|
||||
ret = dtpm_subsys[i]->setup(parent, np);
|
||||
if (ret) {
|
||||
pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
|
||||
of_node_put(np);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
}
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
/*
|
||||
* By returning a NULL pointer, we let know the caller there
|
||||
* is no child for us as we are a leaf of the tree
|
||||
*/
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
|
||||
|
||||
dtpm_node_callback_t dtpm_node_callback[] = {
|
||||
[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
|
||||
[DTPM_NODE_DT] = dtpm_setup_dt,
|
||||
};
|
||||
|
||||
static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
|
||||
const struct dtpm_node *it, struct dtpm *parent)
|
||||
{
|
||||
struct dtpm *dtpm;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; hierarchy[i].name; i++) {
|
||||
|
||||
if (hierarchy[i].parent != it)
|
||||
continue;
|
||||
|
||||
dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
|
||||
|
||||
/*
|
||||
* A NULL pointer means there is no children, hence we
|
||||
* continue without going deeper in the recursivity.
|
||||
*/
|
||||
if (!dtpm)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* There are multiple reasons why the callback could
|
||||
* fail. The generic glue is abstracting the backend
|
||||
* and therefore it is not possible to report back or
|
||||
* take a decision based on the error. In any case,
|
||||
* if this call fails, it is not critical in the
|
||||
* hierarchy creation, we can assume the underlying
|
||||
* service is not found, so we continue without this
|
||||
* branch in the tree but with a warning to log the
|
||||
* information the node was not created.
|
||||
*/
|
||||
if (IS_ERR(dtpm)) {
|
||||
pr_warn("Failed to create '%s' in the hierarchy\n",
|
||||
hierarchy[i].name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(init_dtpm);
|
||||
|
||||
/**
|
||||
* dtpm_create_hierarchy - Create the dtpm hierarchy
|
||||
* @hierarchy: An array of struct dtpm_node describing the hierarchy
|
||||
*
|
||||
* The function is called by the platform specific code with the
|
||||
* description of the different node in the hierarchy. It creates the
|
||||
* tree in the sysfs filesystem under the powercap dtpm entry.
|
||||
*
|
||||
* The expected tree has the format:
|
||||
*
|
||||
* struct dtpm_node hierarchy[] = {
|
||||
* [0] { .name = "topmost", type = DTPM_NODE_VIRTUAL },
|
||||
* [1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
|
||||
* [2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
|
||||
* [3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
|
||||
* [4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
|
||||
* [5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
|
||||
* [6] { }
|
||||
* };
|
||||
*
|
||||
* The last element is always an empty one and marks the end of the
|
||||
* array.
|
||||
*
|
||||
* Return: zero on success, a negative value in case of error. Errors
|
||||
* are reported back from the underlying functions.
|
||||
*/
|
||||
int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
const struct dtpm_node *hierarchy;
|
||||
struct device_node *np;
|
||||
int i, ret;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
|
||||
if (pct) {
|
||||
ret = -EBUSY;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
pct = powercap_register_control_type(NULL, "dtpm", NULL);
|
||||
if (IS_ERR(pct)) {
|
||||
pr_err("Failed to register control type\n");
|
||||
ret = PTR_ERR(pct);
|
||||
goto out_pct;
|
||||
}
|
||||
|
||||
ret = -ENODEV;
|
||||
np = of_find_node_by_path("/");
|
||||
if (!np)
|
||||
goto out_err;
|
||||
|
||||
match = of_match_node(dtpm_match_table, np);
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
if (!match)
|
||||
goto out_err;
|
||||
|
||||
hierarchy = match->data;
|
||||
if (!hierarchy) {
|
||||
ret = -EFAULT;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
ret = dtpm_for_each_child(hierarchy, NULL, NULL);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
|
||||
|
||||
if (!dtpm_subsys[i]->init)
|
||||
continue;
|
||||
|
||||
ret = dtpm_subsys[i]->init();
|
||||
if (ret)
|
||||
pr_info("Failed to initialze '%s': %d",
|
||||
dtpm_subsys[i]->name, ret);
|
||||
}
|
||||
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
powercap_unregister_control_type(pct);
|
||||
out_pct:
|
||||
pct = NULL;
|
||||
out_unlock:
|
||||
mutex_unlock(&dtpm_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
|
||||
|
||||
static void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
|
||||
{
|
||||
struct dtpm *child, *aux;
|
||||
|
||||
list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
|
||||
__dtpm_destroy_hierarchy(child);
|
||||
|
||||
/*
|
||||
* At this point, we know all children were removed from the
|
||||
* recursive call before
|
||||
*/
|
||||
dtpm_unregister(dtpm);
|
||||
}
|
||||
|
||||
void dtpm_destroy_hierarchy(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
mutex_lock(&dtpm_lock);
|
||||
|
||||
if (!pct)
|
||||
goto out_unlock;
|
||||
|
||||
__dtpm_destroy_hierarchy(root);
|
||||
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
|
||||
|
||||
if (!dtpm_subsys[i]->exit)
|
||||
continue;
|
||||
|
||||
dtpm_subsys[i]->exit();
|
||||
}
|
||||
|
||||
powercap_unregister_control_type(pct);
|
||||
|
||||
pct = NULL;
|
||||
|
||||
root = NULL;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&dtpm_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/dtpm.h>
|
||||
#include <linux/energy_model.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/units.h>
|
||||
@ -150,10 +151,17 @@ static int update_pd_power_uw(struct dtpm *dtpm)
|
||||
static void pd_release(struct dtpm *dtpm)
|
||||
{
|
||||
struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
|
||||
struct cpufreq_policy *policy;
|
||||
|
||||
if (freq_qos_request_active(&dtpm_cpu->qos_req))
|
||||
freq_qos_remove_request(&dtpm_cpu->qos_req);
|
||||
|
||||
policy = cpufreq_cpu_get(dtpm_cpu->cpu);
|
||||
if (policy) {
|
||||
for_each_cpu(dtpm_cpu->cpu, policy->related_cpus)
|
||||
per_cpu(dtpm_per_cpu, dtpm_cpu->cpu) = NULL;
|
||||
}
|
||||
|
||||
kfree(dtpm_cpu);
|
||||
}
|
||||
|
||||
@ -176,6 +184,17 @@ static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
|
||||
}
|
||||
|
||||
static int cpuhp_dtpm_cpu_online(unsigned int cpu)
|
||||
{
|
||||
struct dtpm_cpu *dtpm_cpu;
|
||||
|
||||
dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
|
||||
if (dtpm_cpu)
|
||||
return dtpm_update_power(&dtpm_cpu->dtpm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __dtpm_cpu_setup(int cpu, struct dtpm *parent)
|
||||
{
|
||||
struct dtpm_cpu *dtpm_cpu;
|
||||
struct cpufreq_policy *policy;
|
||||
@ -183,6 +202,10 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
|
||||
char name[CPUFREQ_NAME_LEN];
|
||||
int ret = -ENOMEM;
|
||||
|
||||
dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
|
||||
if (dtpm_cpu)
|
||||
return 0;
|
||||
|
||||
policy = cpufreq_cpu_get(cpu);
|
||||
if (!policy)
|
||||
return 0;
|
||||
@ -191,10 +214,6 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
|
||||
if (!pd)
|
||||
return -EINVAL;
|
||||
|
||||
dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
|
||||
if (dtpm_cpu)
|
||||
return dtpm_update_power(&dtpm_cpu->dtpm);
|
||||
|
||||
dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
|
||||
if (!dtpm_cpu)
|
||||
return -ENOMEM;
|
||||
@ -207,7 +226,7 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
|
||||
|
||||
snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);
|
||||
|
||||
ret = dtpm_register(name, &dtpm_cpu->dtpm, NULL);
|
||||
ret = dtpm_register(name, &dtpm_cpu->dtpm, parent);
|
||||
if (ret)
|
||||
goto out_kfree_dtpm_cpu;
|
||||
|
||||
@ -231,7 +250,18 @@ out_kfree_dtpm_cpu:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init dtpm_cpu_init(void)
|
||||
static int dtpm_cpu_setup(struct dtpm *dtpm, struct device_node *np)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
cpu = of_cpu_node_to_id(np);
|
||||
if (cpu < 0)
|
||||
return 0;
|
||||
|
||||
return __dtpm_cpu_setup(cpu, dtpm);
|
||||
}
|
||||
|
||||
static int dtpm_cpu_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -269,4 +299,15 @@ static int __init dtpm_cpu_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init);
|
||||
static void dtpm_cpu_exit(void)
|
||||
{
|
||||
cpuhp_remove_state_nocalls(CPUHP_AP_ONLINE_DYN);
|
||||
cpuhp_remove_state_nocalls(CPUHP_AP_DTPM_CPU_DEAD);
|
||||
}
|
||||
|
||||
struct dtpm_subsys_ops dtpm_cpu_ops = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.init = dtpm_cpu_init,
|
||||
.exit = dtpm_cpu_exit,
|
||||
.setup = dtpm_cpu_setup,
|
||||
};
|
||||
|
203
drivers/powercap/dtpm_devfreq.c
Normal file
203
drivers/powercap/dtpm_devfreq.c
Normal file
@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2021 Linaro Limited
|
||||
*
|
||||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
* The devfreq device combined with the energy model and the load can
|
||||
* give an estimation of the power consumption as well as limiting the
|
||||
* power.
|
||||
*
|
||||
*/
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/dtpm.h>
|
||||
#include <linux/energy_model.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
struct dtpm_devfreq {
|
||||
struct dtpm dtpm;
|
||||
struct dev_pm_qos_request qos_req;
|
||||
struct devfreq *devfreq;
|
||||
};
|
||||
|
||||
static struct dtpm_devfreq *to_dtpm_devfreq(struct dtpm *dtpm)
|
||||
{
|
||||
return container_of(dtpm, struct dtpm_devfreq, dtpm);
|
||||
}
|
||||
|
||||
static int update_pd_power_uw(struct dtpm *dtpm)
|
||||
{
|
||||
struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
|
||||
struct devfreq *devfreq = dtpm_devfreq->devfreq;
|
||||
struct device *dev = devfreq->dev.parent;
|
||||
struct em_perf_domain *pd = em_pd_get(dev);
|
||||
|
||||
dtpm->power_min = pd->table[0].power;
|
||||
dtpm->power_min *= MICROWATT_PER_MILLIWATT;
|
||||
|
||||
dtpm->power_max = pd->table[pd->nr_perf_states - 1].power;
|
||||
dtpm->power_max *= MICROWATT_PER_MILLIWATT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
|
||||
{
|
||||
struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
|
||||
struct devfreq *devfreq = dtpm_devfreq->devfreq;
|
||||
struct device *dev = devfreq->dev.parent;
|
||||
struct em_perf_domain *pd = em_pd_get(dev);
|
||||
unsigned long freq;
|
||||
u64 power;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pd->nr_perf_states; i++) {
|
||||
|
||||
power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
|
||||
if (power > power_limit)
|
||||
break;
|
||||
}
|
||||
|
||||
freq = pd->table[i - 1].frequency;
|
||||
|
||||
dev_pm_qos_update_request(&dtpm_devfreq->qos_req, freq);
|
||||
|
||||
power_limit = pd->table[i - 1].power * MICROWATT_PER_MILLIWATT;
|
||||
|
||||
return power_limit;
|
||||
}
|
||||
|
||||
static void _normalize_load(struct devfreq_dev_status *status)
|
||||
{
|
||||
if (status->total_time > 0xfffff) {
|
||||
status->total_time >>= 10;
|
||||
status->busy_time >>= 10;
|
||||
}
|
||||
|
||||
status->busy_time <<= 10;
|
||||
status->busy_time /= status->total_time ? : 1;
|
||||
|
||||
status->busy_time = status->busy_time ? : 1;
|
||||
status->total_time = 1024;
|
||||
}
|
||||
|
||||
static u64 get_pd_power_uw(struct dtpm *dtpm)
|
||||
{
|
||||
struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
|
||||
struct devfreq *devfreq = dtpm_devfreq->devfreq;
|
||||
struct device *dev = devfreq->dev.parent;
|
||||
struct em_perf_domain *pd = em_pd_get(dev);
|
||||
struct devfreq_dev_status status;
|
||||
unsigned long freq;
|
||||
u64 power;
|
||||
int i;
|
||||
|
||||
mutex_lock(&devfreq->lock);
|
||||
status = devfreq->last_status;
|
||||
mutex_unlock(&devfreq->lock);
|
||||
|
||||
freq = DIV_ROUND_UP(status.current_frequency, HZ_PER_KHZ);
|
||||
_normalize_load(&status);
|
||||
|
||||
for (i = 0; i < pd->nr_perf_states; i++) {
|
||||
|
||||
if (pd->table[i].frequency < freq)
|
||||
continue;
|
||||
|
||||
power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
|
||||
power *= status.busy_time;
|
||||
power >>= 10;
|
||||
|
||||
return power;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pd_release(struct dtpm *dtpm)
|
||||
{
|
||||
struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
|
||||
|
||||
if (dev_pm_qos_request_active(&dtpm_devfreq->qos_req))
|
||||
dev_pm_qos_remove_request(&dtpm_devfreq->qos_req);
|
||||
|
||||
kfree(dtpm_devfreq);
|
||||
}
|
||||
|
||||
static struct dtpm_ops dtpm_ops = {
|
||||
.set_power_uw = set_pd_power_limit,
|
||||
.get_power_uw = get_pd_power_uw,
|
||||
.update_power_uw = update_pd_power_uw,
|
||||
.release = pd_release,
|
||||
};
|
||||
|
||||
static int __dtpm_devfreq_setup(struct devfreq *devfreq, struct dtpm *parent)
|
||||
{
|
||||
struct device *dev = devfreq->dev.parent;
|
||||
struct dtpm_devfreq *dtpm_devfreq;
|
||||
struct em_perf_domain *pd;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
pd = em_pd_get(dev);
|
||||
if (!pd) {
|
||||
ret = dev_pm_opp_of_register_em(dev, NULL);
|
||||
if (ret) {
|
||||
pr_err("No energy model available for '%s'\n", dev_name(dev));
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
dtpm_devfreq = kzalloc(sizeof(*dtpm_devfreq), GFP_KERNEL);
|
||||
if (!dtpm_devfreq)
|
||||
return -ENOMEM;
|
||||
|
||||
dtpm_init(&dtpm_devfreq->dtpm, &dtpm_ops);
|
||||
|
||||
dtpm_devfreq->devfreq = devfreq;
|
||||
|
||||
ret = dtpm_register(dev_name(dev), &dtpm_devfreq->dtpm, parent);
|
||||
if (ret) {
|
||||
pr_err("Failed to register '%s': %d\n", dev_name(dev), ret);
|
||||
kfree(dtpm_devfreq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dev_pm_qos_add_request(dev, &dtpm_devfreq->qos_req,
|
||||
DEV_PM_QOS_MAX_FREQUENCY,
|
||||
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
|
||||
if (ret) {
|
||||
pr_err("Failed to add QoS request: %d\n", ret);
|
||||
goto out_dtpm_unregister;
|
||||
}
|
||||
|
||||
dtpm_update_power(&dtpm_devfreq->dtpm);
|
||||
|
||||
return 0;
|
||||
|
||||
out_dtpm_unregister:
|
||||
dtpm_unregister(&dtpm_devfreq->dtpm);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dtpm_devfreq_setup(struct dtpm *dtpm, struct device_node *np)
|
||||
{
|
||||
struct devfreq *devfreq;
|
||||
|
||||
devfreq = devfreq_get_devfreq_by_node(np);
|
||||
if (IS_ERR(devfreq))
|
||||
return 0;
|
||||
|
||||
return __dtpm_devfreq_setup(devfreq, dtpm);
|
||||
}
|
||||
|
||||
struct dtpm_subsys_ops dtpm_devfreq_ops = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.setup = dtpm_devfreq_setup,
|
||||
};
|
22
drivers/powercap/dtpm_subsys.h
Normal file
22
drivers/powercap/dtpm_subsys.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2022 Linaro Ltd
|
||||
*
|
||||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*/
|
||||
#ifndef ___DTPM_SUBSYS_H__
|
||||
#define ___DTPM_SUBSYS_H__
|
||||
|
||||
extern struct dtpm_subsys_ops dtpm_cpu_ops;
|
||||
extern struct dtpm_subsys_ops dtpm_devfreq_ops;
|
||||
|
||||
struct dtpm_subsys_ops *dtpm_subsys[] = {
|
||||
#ifdef CONFIG_DTPM_CPU
|
||||
&dtpm_cpu_ops,
|
||||
#endif
|
||||
#ifdef CONFIG_DTPM_DEVFREQ
|
||||
&dtpm_devfreq_ops,
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@ -34,4 +34,12 @@ config ROCKCHIP_PM_DOMAINS
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config ROCKCHIP_DTPM
|
||||
tristate "Rockchip DTPM hierarchy"
|
||||
depends on DTPM && m
|
||||
help
|
||||
Describe the hierarchy for the Dynamic Thermal Power
|
||||
Management tree on this platform. That will create all the
|
||||
power capping capable devices.
|
||||
|
||||
endif
|
||||
|
@ -5,3 +5,4 @@
|
||||
obj-$(CONFIG_ROCKCHIP_GRF) += grf.o
|
||||
obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o
|
||||
obj-$(CONFIG_ROCKCHIP_PM_DOMAINS) += pm_domains.o
|
||||
obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o
|
||||
|
65
drivers/soc/rockchip/dtpm.c
Normal file
65
drivers/soc/rockchip/dtpm.c
Normal file
@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2021 Linaro Limited
|
||||
*
|
||||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
* DTPM hierarchy description
|
||||
*/
|
||||
#include <linux/dtpm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
static struct dtpm_node __initdata rk3399_hierarchy[] = {
|
||||
[0]{ .name = "rk3399",
|
||||
.type = DTPM_NODE_VIRTUAL },
|
||||
[1]{ .name = "package",
|
||||
.type = DTPM_NODE_VIRTUAL,
|
||||
.parent = &rk3399_hierarchy[0] },
|
||||
[2]{ .name = "/cpus/cpu@0",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[3]{ .name = "/cpus/cpu@1",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[4]{ .name = "/cpus/cpu@2",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[5]{ .name = "/cpus/cpu@3",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[6]{ .name = "/cpus/cpu@100",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[7]{ .name = "/cpus/cpu@101",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[8]{ .name = "/gpu@ff9a0000",
|
||||
.type = DTPM_NODE_DT,
|
||||
.parent = &rk3399_hierarchy[1] },
|
||||
[9]{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct of_device_id __initdata rockchip_dtpm_match_table[] = {
|
||||
{ .compatible = "rockchip,rk3399", .data = rk3399_hierarchy },
|
||||
{},
|
||||
};
|
||||
|
||||
static int __init rockchip_dtpm_init(void)
|
||||
{
|
||||
return dtpm_create_hierarchy(rockchip_dtpm_match_table);
|
||||
}
|
||||
module_init(rockchip_dtpm_init);
|
||||
|
||||
static void __exit rockchip_dtpm_exit(void)
|
||||
{
|
||||
return dtpm_destroy_hierarchy();
|
||||
}
|
||||
module_exit(rockchip_dtpm_exit);
|
||||
|
||||
MODULE_SOFTDEP("pre: panfrost cpufreq-dt");
|
||||
MODULE_DESCRIPTION("Rockchip DTPM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:dtpm");
|
||||
MODULE_AUTHOR("Daniel Lezcano <daniel.lezcano@kernel.org");
|
@ -321,16 +321,6 @@
|
||||
#define THERMAL_TABLE(name)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DTPM
|
||||
#define DTPM_TABLE() \
|
||||
. = ALIGN(8); \
|
||||
__dtpm_table = .; \
|
||||
KEEP(*(__dtpm_table)) \
|
||||
__dtpm_table_end = .;
|
||||
#else
|
||||
#define DTPM_TABLE()
|
||||
#endif
|
||||
|
||||
#define KERNEL_DTB() \
|
||||
STRUCT_ALIGN(); \
|
||||
__dtb_start = .; \
|
||||
@ -723,7 +713,6 @@
|
||||
ACPI_PROBE_TABLE(irqchip) \
|
||||
ACPI_PROBE_TABLE(timer) \
|
||||
THERMAL_TABLE(governor) \
|
||||
DTPM_TABLE() \
|
||||
EARLYCON_TABLE() \
|
||||
LSM_TABLE() \
|
||||
EARLY_LSM_TABLE() \
|
||||
|
@ -32,28 +32,25 @@ struct dtpm_ops {
|
||||
void (*release)(struct dtpm *);
|
||||
};
|
||||
|
||||
typedef int (*dtpm_init_t)(void);
|
||||
struct device_node;
|
||||
|
||||
struct dtpm_descr {
|
||||
dtpm_init_t init;
|
||||
struct dtpm_subsys_ops {
|
||||
const char *name;
|
||||
int (*init)(void);
|
||||
void (*exit)(void);
|
||||
int (*setup)(struct dtpm *, struct device_node *);
|
||||
};
|
||||
|
||||
/* Init section thermal table */
|
||||
extern struct dtpm_descr __dtpm_table[];
|
||||
extern struct dtpm_descr __dtpm_table_end[];
|
||||
enum DTPM_NODE_TYPE {
|
||||
DTPM_NODE_VIRTUAL = 0,
|
||||
DTPM_NODE_DT,
|
||||
};
|
||||
|
||||
#define DTPM_TABLE_ENTRY(name, __init) \
|
||||
static struct dtpm_descr __dtpm_table_entry_##name \
|
||||
__used __section("__dtpm_table") = { \
|
||||
.init = __init, \
|
||||
}
|
||||
|
||||
#define DTPM_DECLARE(name, init) DTPM_TABLE_ENTRY(name, init)
|
||||
|
||||
#define for_each_dtpm_table(__dtpm) \
|
||||
for (__dtpm = __dtpm_table; \
|
||||
__dtpm < __dtpm_table_end; \
|
||||
__dtpm++)
|
||||
struct dtpm_node {
|
||||
enum DTPM_NODE_TYPE type;
|
||||
const char *name;
|
||||
struct dtpm_node *parent;
|
||||
};
|
||||
|
||||
static inline struct dtpm *to_dtpm(struct powercap_zone *zone)
|
||||
{
|
||||
@ -70,4 +67,7 @@ void dtpm_unregister(struct dtpm *dtpm);
|
||||
|
||||
int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent);
|
||||
|
||||
int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table);
|
||||
|
||||
void dtpm_destroy_hierarchy(void);
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user