forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
4.7 KiB
203 lines
4.7 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright 2021 Linaro Limited |
|
* |
|
* Author: Daniel Lezcano <[email protected]> |
|
* |
|
* 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, |
|
};
|
|
|