123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- // 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_max = pd->table[pd->nr_perf_states - 1].power;
- 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;
- int i;
- for (i = 0; i < pd->nr_perf_states; i++) {
- if (pd->table[i].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;
- 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;
- 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,
- };
|