|  | // 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); | 
|  | struct em_perf_state *table; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | table = em_perf_state_from_pd(pd); | 
|  |  | 
|  | dtpm->power_min = table[0].power; | 
|  |  | 
|  | dtpm->power_max = table[pd->nr_perf_states - 1].power; | 
|  |  | 
|  | rcu_read_unlock(); | 
|  | 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); | 
|  | struct em_perf_state *table; | 
|  | unsigned long freq; | 
|  | int i; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | table = em_perf_state_from_pd(pd); | 
|  | for (i = 0; i < pd->nr_perf_states; i++) { | 
|  | if (table[i].power > power_limit) | 
|  | break; | 
|  | } | 
|  |  | 
|  | freq = table[i - 1].frequency; | 
|  | power_limit = table[i - 1].power; | 
|  | rcu_read_unlock(); | 
|  |  | 
|  | dev_pm_qos_update_request(&dtpm_devfreq->qos_req, freq); | 
|  |  | 
|  | 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; | 
|  | struct em_perf_state *table; | 
|  | unsigned long freq; | 
|  | u64 power = 0; | 
|  | 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); | 
|  |  | 
|  | rcu_read_lock(); | 
|  | table = em_perf_state_from_pd(pd); | 
|  | for (i = 0; i < pd->nr_perf_states; i++) { | 
|  |  | 
|  | if (table[i].frequency < freq) | 
|  | continue; | 
|  |  | 
|  | power = table[i].power; | 
|  | power *= status.busy_time; | 
|  | power >>= 10; | 
|  |  | 
|  | break; | 
|  | } | 
|  | rcu_read_unlock(); | 
|  |  | 
|  | return power; | 
|  | } | 
|  |  | 
|  | 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, | 
|  | }; |