#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/dtpm.h>
#include <linux/init.h>
#include <linux/kernel.h>
#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
static const char *constraint_name[] = {
"Instantaneous",
};
static DEFINE_MUTEX(dtpm_lock);
static struct powercap_control_type *pct;
static struct dtpm *root;
static int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window)
{
return -ENOSYS;
}
static int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window)
{
return -ENOSYS;
}
static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
{
struct dtpm *dtpm = to_dtpm(pcz);
*max_power_uw = dtpm->power_max - dtpm->power_min;
return 0;
}
static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
{
struct dtpm *child;
u64 power;
int ret = 0;
if (dtpm->ops) {
*power_uw = dtpm->ops->get_power_uw(dtpm);
return 0;
}
*power_uw = 0;
list_for_each_entry(child, &dtpm->children, sibling) {
ret = __get_power_uw(child, &power);
if (ret)
break;
*power_uw += power;
}
return ret;
}
static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
{
return __get_power_uw(to_dtpm(pcz), power_uw);
}
static void __dtpm_rebalance_weight(struct dtpm *dtpm)
{
struct dtpm *child;
list_for_each_entry(child, &dtpm->children, sibling) {
pr_debug("Setting weight '%d' for '%s'\n",
child->weight, child->zone.name);
child->weight = DIV64_U64_ROUND_CLOSEST(
child->power_max * 1024, dtpm->power_max);
__dtpm_rebalance_weight(child);
}
}
static void __dtpm_sub_power(struct dtpm *dtpm)
{
struct dtpm *parent = dtpm->parent;
while (parent) {
parent->power_min -= dtpm->power_min;
parent->power_max -= dtpm->power_max;
parent->power_limit -= dtpm->power_limit;
parent = parent->parent;
}
}
static void __dtpm_add_power(struct dtpm *dtpm)
{
struct dtpm *parent = dtpm->parent;
while (parent) {
parent->power_min += dtpm->power_min;
parent->power_max += dtpm->power_max;
parent->power_limit += dtpm->power_limit;
parent = parent->parent;
}
}
int dtpm_update_power(struct dtpm *dtpm)
{
int ret;
__dtpm_sub_power(dtpm);
ret = dtpm->ops->update_power_uw(dtpm);
if (ret)
pr_err("Failed to update power for '%s': %d\n",
dtpm->zone.name, ret);
if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags))
dtpm->power_limit = dtpm->power_max;
__dtpm_add_power(dtpm);
if (root)
__dtpm_rebalance_weight(root);
return ret;
}
int dtpm_release_zone(struct powercap_zone *pcz)
{
struct dtpm *dtpm = to_dtpm(pcz);
struct dtpm *parent = dtpm->parent;
if (!list_empty(&dtpm->children))
return -EBUSY;
if (parent)
list_del(&dtpm->sibling);
__dtpm_sub_power(dtpm);
if (dtpm->ops)
dtpm->ops->release(dtpm);
else
kfree(dtpm);
return 0;
}
static int get_power_limit_uw(struct powercap_zone *pcz,
int cid, u64 *power_limit)
{
*power_limit = to_dtpm(pcz)->power_limit;
return 0;
}
static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
{
struct dtpm *child;
int ret = 0;
u64 power;
if (power_limit == dtpm->power_max) {
clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
} else {
set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
}
pr_debug("Setting power limit for '%s': %llu uW\n",
dtpm->zone.name, power_limit);
if (dtpm->ops) {
dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
} else {
dtpm->power_limit = 0;
list_for_each_entry(child, &dtpm->children, sibling) {
if (power_limit == dtpm->power_max) {
power = child->power_max;
} else if (power_limit == dtpm->power_min) {
power = child->power_min;
} else {
power = DIV_ROUND_CLOSEST_ULL(
power_limit * child->weight, 1024);
}
pr_debug("Setting power limit for '%s': %llu uW\n",
child->zone.name, power);
ret = __set_power_limit_uw(child, cid, power);
if (!ret)
ret = get_power_limit_uw(&child->zone, cid, &power);
if (ret)
break;
dtpm->power_limit += power;
}
}
return ret;
}
static int set_power_limit_uw(struct powercap_zone *pcz,
int cid, u64 power_limit)
{
struct dtpm *dtpm = to_dtpm(pcz);
int ret;
power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
ret = __set_power_limit_uw(dtpm, cid, power_limit);
pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
return ret;
}
static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
{
return constraint_name[cid];
}
static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
{
*max_power = to_dtpm(pcz)->power_max;
return 0;
}
static struct powercap_zone_constraint_ops constraint_ops = {
.set_power_limit_uw = set_power_limit_uw,
.get_power_limit_uw = get_power_limit_uw,
.set_time_window_us = set_time_window_us,
.get_time_window_us = get_time_window_us,
.get_max_power_uw = get_max_power_uw,
.get_name = get_constraint_name,
};
static struct powercap_zone_ops zone_ops = {
.get_max_power_range_uw = get_max_power_range_uw,
.get_power_uw = get_power_uw,
.release = dtpm_release_zone,
};
void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
{
if (dtpm) {
INIT_LIST_HEAD(&dtpm->children);
INIT_LIST_HEAD(&dtpm->sibling);
dtpm->weight = 1024;
dtpm->ops = ops;
}
}
void dtpm_unregister(struct dtpm *dtpm)
{
powercap_unregister_zone(pct, &dtpm->zone);
pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
}
int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
{
struct powercap_zone *pcz;
if (!pct)
return -EAGAIN;
if (root && !parent)
return -EBUSY;
if (!root && parent)
return -EINVAL;
if (parent && parent->ops)
return -EINVAL;
if (!dtpm)
return -EINVAL;
if (dtpm->ops && !(dtpm->ops->set_power_uw &&
dtpm->ops->get_power_uw &&
dtpm->ops->update_power_uw &&
dtpm->ops->release))
return -EINVAL;
pcz = powercap_register_zone(&dtpm->zone, pct, name,
parent ? &parent->zone : NULL,
&zone_ops, MAX_DTPM_CONSTRAINTS,
&constraint_ops);
if (IS_ERR(pcz))
return PTR_ERR(pcz);
if (parent) {
list_add_tail(&dtpm->sibling, &parent->children);
dtpm->parent = parent;
} else {
root = dtpm;
}
if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
__dtpm_add_power(dtpm);
dtpm->power_limit = dtpm->power_max;
}
pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
dtpm->zone.name, dtpm->power_min, dtpm->power_max);
return 0;
}
static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
struct dtpm *parent)
{
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);
return NULL;
}
typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
static 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);
if (!dtpm)
continue;
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;
}
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 initialize '%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);
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