13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0
23d0407baSopenharmony_ci/*
33d0407baSopenharmony_ci * Energy Model of devices
43d0407baSopenharmony_ci *
53d0407baSopenharmony_ci * Copyright (c) 2018-2020, Arm ltd.
63d0407baSopenharmony_ci * Written by: Quentin Perret, Arm ltd.
73d0407baSopenharmony_ci * Improvements provided by: Lukasz Luba, Arm ltd.
83d0407baSopenharmony_ci */
93d0407baSopenharmony_ci
103d0407baSopenharmony_ci#define pr_fmt(fmt) "energy_model: " fmt
113d0407baSopenharmony_ci
123d0407baSopenharmony_ci#include <linux/cpu.h>
133d0407baSopenharmony_ci#include <linux/cpumask.h>
143d0407baSopenharmony_ci#include <linux/debugfs.h>
153d0407baSopenharmony_ci#include <linux/energy_model.h>
163d0407baSopenharmony_ci#include <linux/sched/topology.h>
173d0407baSopenharmony_ci#include <linux/slab.h>
183d0407baSopenharmony_ci
193d0407baSopenharmony_ci#define FILE_PROPERTY 0444
203d0407baSopenharmony_ci
213d0407baSopenharmony_ci/*
223d0407baSopenharmony_ci * Mutex serializing the registrations of performance domains and letting
233d0407baSopenharmony_ci * callbacks defined by drivers sleep.
243d0407baSopenharmony_ci */
253d0407baSopenharmony_cistatic DEFINE_MUTEX(em_pd_mutex);
263d0407baSopenharmony_ci
273d0407baSopenharmony_cistatic bool _is_cpu_device(struct device *dev)
283d0407baSopenharmony_ci{
293d0407baSopenharmony_ci    return (dev->bus == &cpu_subsys);
303d0407baSopenharmony_ci}
313d0407baSopenharmony_ci
323d0407baSopenharmony_ci#ifdef CONFIG_DEBUG_FS
333d0407baSopenharmony_cistatic struct dentry *rootdir;
343d0407baSopenharmony_ci
353d0407baSopenharmony_cistatic void em_debug_create_ps(struct em_perf_state *ps, struct dentry *pd)
363d0407baSopenharmony_ci{
373d0407baSopenharmony_ci    struct dentry *d;
383d0407baSopenharmony_ci    char name[24];
393d0407baSopenharmony_ci
403d0407baSopenharmony_ci    snprintf(name, sizeof(name), "ps:%lu", ps->frequency);
413d0407baSopenharmony_ci
423d0407baSopenharmony_ci    /* Create per-ps directory */
433d0407baSopenharmony_ci    d = debugfs_create_dir(name, pd);
443d0407baSopenharmony_ci    debugfs_create_ulong("frequency", FILE_PROPERTY, d, &ps->frequency);
453d0407baSopenharmony_ci    debugfs_create_ulong("power", FILE_PROPERTY, d, &ps->power);
463d0407baSopenharmony_ci    debugfs_create_ulong("cost", FILE_PROPERTY, d, &ps->cost);
473d0407baSopenharmony_ci}
483d0407baSopenharmony_ci
493d0407baSopenharmony_cistatic int em_debug_cpus_show(struct seq_file *s, void *unused)
503d0407baSopenharmony_ci{
513d0407baSopenharmony_ci    seq_printf(s, "%*pbl\n", cpumask_pr_args(to_cpumask(s->private)));
523d0407baSopenharmony_ci
533d0407baSopenharmony_ci    return 0;
543d0407baSopenharmony_ci}
553d0407baSopenharmony_ciDEFINE_SHOW_ATTRIBUTE(em_debug_cpus);
563d0407baSopenharmony_ci
573d0407baSopenharmony_cistatic int em_debug_units_show(struct seq_file *s, void *unused)
583d0407baSopenharmony_ci{
593d0407baSopenharmony_ci    struct em_perf_domain *pd = s->private;
603d0407baSopenharmony_ci    char *units = pd->milliwatts ? "milliWatts" : "bogoWatts";
613d0407baSopenharmony_ci
623d0407baSopenharmony_ci    seq_printf(s, "%s\n", units);
633d0407baSopenharmony_ci
643d0407baSopenharmony_ci    return 0;
653d0407baSopenharmony_ci}
663d0407baSopenharmony_ciDEFINE_SHOW_ATTRIBUTE(em_debug_units);
673d0407baSopenharmony_ci
683d0407baSopenharmony_cistatic void em_debug_create_pd(struct device *dev)
693d0407baSopenharmony_ci{
703d0407baSopenharmony_ci    struct dentry *d;
713d0407baSopenharmony_ci    int i;
723d0407baSopenharmony_ci
733d0407baSopenharmony_ci    /* Create the directory of the performance domain */
743d0407baSopenharmony_ci    d = debugfs_create_dir(dev_name(dev), rootdir);
753d0407baSopenharmony_ci
763d0407baSopenharmony_ci    if (_is_cpu_device(dev)) {
773d0407baSopenharmony_ci        debugfs_create_file("cpus", FILE_PROPERTY, d, dev->em_pd->cpus, &em_debug_cpus_fops);
783d0407baSopenharmony_ci    }
793d0407baSopenharmony_ci
803d0407baSopenharmony_ci    debugfs_create_file("units", FILE_PROPERTY, d, dev->em_pd, &em_debug_units_fops);
813d0407baSopenharmony_ci
823d0407baSopenharmony_ci    /* Create a sub-directory for each performance state */
833d0407baSopenharmony_ci    for (i = 0; i < dev->em_pd->nr_perf_states; i++) {
843d0407baSopenharmony_ci        em_debug_create_ps(&dev->em_pd->table[i], d);
853d0407baSopenharmony_ci    }
863d0407baSopenharmony_ci}
873d0407baSopenharmony_ci
883d0407baSopenharmony_cistatic void em_debug_remove_pd(struct device *dev)
893d0407baSopenharmony_ci{
903d0407baSopenharmony_ci    struct dentry *debug_dir;
913d0407baSopenharmony_ci
923d0407baSopenharmony_ci    debug_dir = debugfs_lookup(dev_name(dev), rootdir);
933d0407baSopenharmony_ci    debugfs_remove_recursive(debug_dir);
943d0407baSopenharmony_ci}
953d0407baSopenharmony_ci
963d0407baSopenharmony_cistatic int __init em_debug_init(void)
973d0407baSopenharmony_ci{
983d0407baSopenharmony_ci    /* Create /sys/kernel/debug/energy_model directory */
993d0407baSopenharmony_ci    rootdir = debugfs_create_dir("energy_model", NULL);
1003d0407baSopenharmony_ci
1013d0407baSopenharmony_ci    return 0;
1023d0407baSopenharmony_ci}
1033d0407baSopenharmony_cifs_initcall(em_debug_init);
1043d0407baSopenharmony_ci#else /* CONFIG_DEBUG_FS */
1053d0407baSopenharmony_cistatic void em_debug_create_pd(struct device *dev)
1063d0407baSopenharmony_ci{
1073d0407baSopenharmony_ci}
1083d0407baSopenharmony_cistatic void em_debug_remove_pd(struct device *dev)
1093d0407baSopenharmony_ci{
1103d0407baSopenharmony_ci}
1113d0407baSopenharmony_ci#endif
1123d0407baSopenharmony_ci
1133d0407baSopenharmony_cistatic int em_create_perf_table(struct device *dev, struct em_perf_domain *pd, int nr_states,
1143d0407baSopenharmony_ci                                struct em_data_callback *cb)
1153d0407baSopenharmony_ci{
1163d0407baSopenharmony_ci    unsigned long power, freq, prev_freq = 0, prev_cost = ULONG_MAX;
1173d0407baSopenharmony_ci    struct em_perf_state *table;
1183d0407baSopenharmony_ci    int i, ret;
1193d0407baSopenharmony_ci    u64 fmax;
1203d0407baSopenharmony_ci
1213d0407baSopenharmony_ci    table = kcalloc(nr_states, sizeof(*table), GFP_KERNEL);
1223d0407baSopenharmony_ci    if (!table) {
1233d0407baSopenharmony_ci        return -ENOMEM;
1243d0407baSopenharmony_ci    }
1253d0407baSopenharmony_ci
1263d0407baSopenharmony_ci    /* Build the list of performance states for this performance domain */
1273d0407baSopenharmony_ci    for (i = 0, freq = 0; i < nr_states; i++, freq++) {
1283d0407baSopenharmony_ci        /*
1293d0407baSopenharmony_ci         * active_power() is a driver callback which ceils 'freq' to
1303d0407baSopenharmony_ci         * lowest performance state of 'dev' above 'freq' and updates
1313d0407baSopenharmony_ci         * 'power' and 'freq' accordingly.
1323d0407baSopenharmony_ci         */
1333d0407baSopenharmony_ci        ret = cb->active_power(&power, &freq, dev);
1343d0407baSopenharmony_ci        if (ret) {
1353d0407baSopenharmony_ci            dev_err(dev, "EM: invalid perf. state: %d\n", ret);
1363d0407baSopenharmony_ci            goto free_ps_table;
1373d0407baSopenharmony_ci        }
1383d0407baSopenharmony_ci
1393d0407baSopenharmony_ci        /*
1403d0407baSopenharmony_ci         * We expect the driver callback to increase the frequency for
1413d0407baSopenharmony_ci         * higher performance states.
1423d0407baSopenharmony_ci         */
1433d0407baSopenharmony_ci        if (freq <= prev_freq) {
1443d0407baSopenharmony_ci            dev_err(dev, "EM: non-increasing freq: %lu\n", freq);
1453d0407baSopenharmony_ci            goto free_ps_table;
1463d0407baSopenharmony_ci        }
1473d0407baSopenharmony_ci
1483d0407baSopenharmony_ci        /*
1493d0407baSopenharmony_ci         * The power returned by active_state() is expected to be
1503d0407baSopenharmony_ci         * positive, in milli-watts and to fit into 16 bits.
1513d0407baSopenharmony_ci         */
1523d0407baSopenharmony_ci        if (!power || power > EM_MAX_POWER) {
1533d0407baSopenharmony_ci            dev_err(dev, "EM: invalid power: %lu\n", power);
1543d0407baSopenharmony_ci            goto free_ps_table;
1553d0407baSopenharmony_ci        }
1563d0407baSopenharmony_ci
1573d0407baSopenharmony_ci        table[i].power = power;
1583d0407baSopenharmony_ci        table[i].frequency = prev_freq = freq;
1593d0407baSopenharmony_ci        }
1603d0407baSopenharmony_ci
1613d0407baSopenharmony_ci    /* Compute the cost of each performance state. */
1623d0407baSopenharmony_ci    fmax = (u64)table[nr_states - 1].frequency;
1633d0407baSopenharmony_ci    for (i = nr_states - 1; i >= 0; i--) {
1643d0407baSopenharmony_ci        unsigned long power_res = em_scale_power(table[i].power);
1653d0407baSopenharmony_ci
1663d0407baSopenharmony_ci        table[i].cost = div64_u64(fmax * power_res, table[i].frequency);
1673d0407baSopenharmony_ci        if (table[i].cost >= prev_cost) {
1683d0407baSopenharmony_ci            dev_dbg(dev, "EM: OPP:%lu is inefficient\n",
1693d0407baSopenharmony_ci                table[i].frequency);
1703d0407baSopenharmony_ci        } else {
1713d0407baSopenharmony_ci            prev_cost = table[i].cost;
1723d0407baSopenharmony_ci        }
1733d0407baSopenharmony_ci    }
1743d0407baSopenharmony_ci
1753d0407baSopenharmony_ci    pd->table = table;
1763d0407baSopenharmony_ci    pd->nr_perf_states = nr_states;
1773d0407baSopenharmony_ci
1783d0407baSopenharmony_ci    return 0;
1793d0407baSopenharmony_ci
1803d0407baSopenharmony_cifree_ps_table:
1813d0407baSopenharmony_ci    kfree(table);
1823d0407baSopenharmony_ci    return -EINVAL;
1833d0407baSopenharmony_ci}
1843d0407baSopenharmony_ci
1853d0407baSopenharmony_cistatic int em_create_pd(struct device *dev, int nr_states, struct em_data_callback *cb, cpumask_t *cpus)
1863d0407baSopenharmony_ci{
1873d0407baSopenharmony_ci    struct em_perf_domain *pd;
1883d0407baSopenharmony_ci    struct device *cpu_dev;
1893d0407baSopenharmony_ci    int cpu, ret;
1903d0407baSopenharmony_ci
1913d0407baSopenharmony_ci    if (_is_cpu_device(dev)) {
1923d0407baSopenharmony_ci        pd = kzalloc(sizeof(*pd) + cpumask_size(), GFP_KERNEL);
1933d0407baSopenharmony_ci        if (!pd) {
1943d0407baSopenharmony_ci            return -ENOMEM;
1953d0407baSopenharmony_ci        }
1963d0407baSopenharmony_ci
1973d0407baSopenharmony_ci        cpumask_copy(em_span_cpus(pd), cpus);
1983d0407baSopenharmony_ci    } else {
1993d0407baSopenharmony_ci        pd = kzalloc(sizeof(*pd), GFP_KERNEL);
2003d0407baSopenharmony_ci        if (!pd) {
2013d0407baSopenharmony_ci            return -ENOMEM;
2023d0407baSopenharmony_ci        }
2033d0407baSopenharmony_ci    }
2043d0407baSopenharmony_ci
2053d0407baSopenharmony_ci    ret = em_create_perf_table(dev, pd, nr_states, cb);
2063d0407baSopenharmony_ci    if (ret) {
2073d0407baSopenharmony_ci        kfree(pd);
2083d0407baSopenharmony_ci        return ret;
2093d0407baSopenharmony_ci    }
2103d0407baSopenharmony_ci
2113d0407baSopenharmony_ci    if (_is_cpu_device(dev)) {
2123d0407baSopenharmony_ci        for_each_cpu(cpu, cpus)
2133d0407baSopenharmony_ci        {
2143d0407baSopenharmony_ci            cpu_dev = get_cpu_device(cpu);
2153d0407baSopenharmony_ci            cpu_dev->em_pd = pd;
2163d0407baSopenharmony_ci        }
2173d0407baSopenharmony_ci    }
2183d0407baSopenharmony_ci
2193d0407baSopenharmony_ci    dev->em_pd = pd;
2203d0407baSopenharmony_ci
2213d0407baSopenharmony_ci    return 0;
2223d0407baSopenharmony_ci}
2233d0407baSopenharmony_ci
2243d0407baSopenharmony_ci/**
2253d0407baSopenharmony_ci * em_pd_get() - Return the performance domain for a device
2263d0407baSopenharmony_ci * @dev : Device to find the performance domain for
2273d0407baSopenharmony_ci *
2283d0407baSopenharmony_ci * Returns the performance domain to which @dev belongs, or NULL if it doesn't
2293d0407baSopenharmony_ci * exist.
2303d0407baSopenharmony_ci */
2313d0407baSopenharmony_cistruct em_perf_domain *em_pd_get(struct device *dev)
2323d0407baSopenharmony_ci{
2333d0407baSopenharmony_ci    if (IS_ERR_OR_NULL(dev)) {
2343d0407baSopenharmony_ci        return NULL;
2353d0407baSopenharmony_ci    }
2363d0407baSopenharmony_ci
2373d0407baSopenharmony_ci    return dev->em_pd;
2383d0407baSopenharmony_ci}
2393d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(em_pd_get);
2403d0407baSopenharmony_ci
2413d0407baSopenharmony_ci/**
2423d0407baSopenharmony_ci * em_cpu_get() - Return the performance domain for a CPU
2433d0407baSopenharmony_ci * @cpu : CPU to find the performance domain for
2443d0407baSopenharmony_ci *
2453d0407baSopenharmony_ci * Returns the performance domain to which @cpu belongs, or NULL if it doesn't
2463d0407baSopenharmony_ci * exist.
2473d0407baSopenharmony_ci */
2483d0407baSopenharmony_cistruct em_perf_domain *em_cpu_get(int cpu)
2493d0407baSopenharmony_ci{
2503d0407baSopenharmony_ci    struct device *cpu_dev;
2513d0407baSopenharmony_ci
2523d0407baSopenharmony_ci    cpu_dev = get_cpu_device(cpu);
2533d0407baSopenharmony_ci    if (!cpu_dev) {
2543d0407baSopenharmony_ci        return NULL;
2553d0407baSopenharmony_ci    }
2563d0407baSopenharmony_ci
2573d0407baSopenharmony_ci    return em_pd_get(cpu_dev);
2583d0407baSopenharmony_ci}
2593d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(em_cpu_get);
2603d0407baSopenharmony_ci
2613d0407baSopenharmony_ci/**
2623d0407baSopenharmony_ci * em_dev_register_perf_domain() - Register the Energy Model (EM) for a device
2633d0407baSopenharmony_ci * @dev        : Device for which the EM is to register
2643d0407baSopenharmony_ci * @nr_states    : Number of performance states to register
2653d0407baSopenharmony_ci * @cb        : Callback functions providing the data of the Energy Model
2663d0407baSopenharmony_ci * @cpus    : Pointer to cpumask_t, which in case of a CPU device is
2673d0407baSopenharmony_ci *        obligatory. It can be taken from i.e. 'policy->cpus'. For other
2683d0407baSopenharmony_ci *        type of devices this should be set to NULL.
2693d0407baSopenharmony_ci * @milliwatts    : Flag indicating that the power values are in milliWatts or
2703d0407baSopenharmony_ci *        in some other scale. It must be set properly.
2713d0407baSopenharmony_ci *
2723d0407baSopenharmony_ci * Create Energy Model tables for a performance domain using the callbacks
2733d0407baSopenharmony_ci * defined in cb.
2743d0407baSopenharmony_ci *
2753d0407baSopenharmony_ci * The @milliwatts is important to set with correct value. Some kernel
2763d0407baSopenharmony_ci * sub-systems might rely on this flag and check if all devices in the EM are
2773d0407baSopenharmony_ci * using the same scale.
2783d0407baSopenharmony_ci *
2793d0407baSopenharmony_ci * If multiple clients register the same performance domain, all but the first
2803d0407baSopenharmony_ci * registration will be ignored.
2813d0407baSopenharmony_ci *
2823d0407baSopenharmony_ci * Return 0 on success
2833d0407baSopenharmony_ci */
2843d0407baSopenharmony_ciint em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, struct em_data_callback *cb,
2853d0407baSopenharmony_ci                                cpumask_t *cpus, bool milliwatts)
2863d0407baSopenharmony_ci{
2873d0407baSopenharmony_ci    unsigned long cap, prev_cap = 0;
2883d0407baSopenharmony_ci    int cpu, ret;
2893d0407baSopenharmony_ci
2903d0407baSopenharmony_ci    if (!dev || !nr_states || !cb) {
2913d0407baSopenharmony_ci        return -EINVAL;
2923d0407baSopenharmony_ci    }
2933d0407baSopenharmony_ci
2943d0407baSopenharmony_ci    /*
2953d0407baSopenharmony_ci     * Use a mutex to serialize the registration of performance domains and
2963d0407baSopenharmony_ci     * let the driver-defined callback functions sleep.
2973d0407baSopenharmony_ci     */
2983d0407baSopenharmony_ci    mutex_lock(&em_pd_mutex);
2993d0407baSopenharmony_ci
3003d0407baSopenharmony_ci    if (dev->em_pd) {
3013d0407baSopenharmony_ci        ret = -EEXIST;
3023d0407baSopenharmony_ci        goto unlock;
3033d0407baSopenharmony_ci    }
3043d0407baSopenharmony_ci
3053d0407baSopenharmony_ci    if (_is_cpu_device(dev)) {
3063d0407baSopenharmony_ci        if (!cpus) {
3073d0407baSopenharmony_ci            dev_err(dev, "EM: invalid CPU mask\n");
3083d0407baSopenharmony_ci            ret = -EINVAL;
3093d0407baSopenharmony_ci            goto unlock;
3103d0407baSopenharmony_ci        }
3113d0407baSopenharmony_ci
3123d0407baSopenharmony_ci        for_each_cpu(cpu, cpus)
3133d0407baSopenharmony_ci        {
3143d0407baSopenharmony_ci            if (em_cpu_get(cpu)) {
3153d0407baSopenharmony_ci                dev_err(dev, "EM: exists for CPU%d\n", cpu);
3163d0407baSopenharmony_ci                ret = -EEXIST;
3173d0407baSopenharmony_ci                goto unlock;
3183d0407baSopenharmony_ci            }
3193d0407baSopenharmony_ci            /*
3203d0407baSopenharmony_ci             * All CPUs of a domain must have the same
3213d0407baSopenharmony_ci             * micro-architecture since they all share the same
3223d0407baSopenharmony_ci             * table.
3233d0407baSopenharmony_ci             */
3243d0407baSopenharmony_ci            cap = arch_scale_cpu_capacity(cpu);
3253d0407baSopenharmony_ci            if (prev_cap && prev_cap != cap) {
3263d0407baSopenharmony_ci                dev_err(dev, "EM: CPUs of %*pbl must have the same capacity\n", cpumask_pr_args(cpus));
3273d0407baSopenharmony_ci
3283d0407baSopenharmony_ci                ret = -EINVAL;
3293d0407baSopenharmony_ci                goto unlock;
3303d0407baSopenharmony_ci            }
3313d0407baSopenharmony_ci            prev_cap = cap;
3323d0407baSopenharmony_ci        }
3333d0407baSopenharmony_ci    }
3343d0407baSopenharmony_ci
3353d0407baSopenharmony_ci    ret = em_create_pd(dev, nr_states, cb, cpus);
3363d0407baSopenharmony_ci    if (ret) {
3373d0407baSopenharmony_ci        goto unlock;
3383d0407baSopenharmony_ci    }
3393d0407baSopenharmony_ci
3403d0407baSopenharmony_ci    dev->em_pd->milliwatts = milliwatts;
3413d0407baSopenharmony_ci
3423d0407baSopenharmony_ci    em_debug_create_pd(dev);
3433d0407baSopenharmony_ci    dev_info(dev, "EM: created perf domain\n");
3443d0407baSopenharmony_ci
3453d0407baSopenharmony_ciunlock:
3463d0407baSopenharmony_ci    mutex_unlock(&em_pd_mutex);
3473d0407baSopenharmony_ci    return ret;
3483d0407baSopenharmony_ci}
3493d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(em_dev_register_perf_domain);
3503d0407baSopenharmony_ci
3513d0407baSopenharmony_ci/**
3523d0407baSopenharmony_ci * em_dev_unregister_perf_domain() - Unregister Energy Model (EM) for a device
3533d0407baSopenharmony_ci * @dev        : Device for which the EM is registered
3543d0407baSopenharmony_ci *
3553d0407baSopenharmony_ci * Unregister the EM for the specified @dev (but not a CPU device).
3563d0407baSopenharmony_ci */
3573d0407baSopenharmony_civoid em_dev_unregister_perf_domain(struct device *dev)
3583d0407baSopenharmony_ci{
3593d0407baSopenharmony_ci    if (IS_ERR_OR_NULL(dev) || !dev->em_pd) {
3603d0407baSopenharmony_ci        return;
3613d0407baSopenharmony_ci    }
3623d0407baSopenharmony_ci
3633d0407baSopenharmony_ci    if (_is_cpu_device(dev)) {
3643d0407baSopenharmony_ci        return;
3653d0407baSopenharmony_ci    }
3663d0407baSopenharmony_ci
3673d0407baSopenharmony_ci    /*
3683d0407baSopenharmony_ci     * The mutex separates all register/unregister requests and protects
3693d0407baSopenharmony_ci     * from potential clean-up/setup issues in the debugfs directories.
3703d0407baSopenharmony_ci     * The debugfs directory name is the same as device's name.
3713d0407baSopenharmony_ci     */
3723d0407baSopenharmony_ci    mutex_lock(&em_pd_mutex);
3733d0407baSopenharmony_ci    em_debug_remove_pd(dev);
3743d0407baSopenharmony_ci
3753d0407baSopenharmony_ci    kfree(dev->em_pd->table);
3763d0407baSopenharmony_ci    kfree(dev->em_pd);
3773d0407baSopenharmony_ci    dev->em_pd = NULL;
3783d0407baSopenharmony_ci    mutex_unlock(&em_pd_mutex);
3793d0407baSopenharmony_ci}
3803d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(em_dev_unregister_perf_domain);
381