linux/drivers/powercap/dtpm_cpu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright 2020 Linaro Limited
   4 *
   5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
   6 *
   7 * The DTPM CPU is based on the energy model. It hooks the CPU in the
   8 * DTPM tree which in turns update the power number by propagating the
   9 * power number from the CPU energy model information to the parents.
  10 *
  11 * The association between the power and the performance state, allows
  12 * to set the power of the CPU at the OPP granularity.
  13 *
  14 * The CPU hotplug is supported and the power numbers will be updated
  15 * if a CPU is hot plugged / unplugged.
  16 */
  17#include <linux/cpumask.h>
  18#include <linux/cpufreq.h>
  19#include <linux/cpuhotplug.h>
  20#include <linux/dtpm.h>
  21#include <linux/energy_model.h>
  22#include <linux/pm_qos.h>
  23#include <linux/slab.h>
  24#include <linux/units.h>
  25
  26static struct dtpm *__parent;
  27
  28static DEFINE_PER_CPU(struct dtpm *, dtpm_per_cpu);
  29
  30struct dtpm_cpu {
  31        struct freq_qos_request qos_req;
  32        int cpu;
  33};
  34
  35/*
  36 * When a new CPU is inserted at hotplug or boot time, add the power
  37 * contribution and update the dtpm tree.
  38 */
  39static int power_add(struct dtpm *dtpm, struct em_perf_domain *em)
  40{
  41        u64 power_min, power_max;
  42
  43        power_min = em->table[0].power;
  44        power_min *= MICROWATT_PER_MILLIWATT;
  45        power_min += dtpm->power_min;
  46
  47        power_max = em->table[em->nr_perf_states - 1].power;
  48        power_max *= MICROWATT_PER_MILLIWATT;
  49        power_max += dtpm->power_max;
  50
  51        return dtpm_update_power(dtpm, power_min, power_max);
  52}
  53
  54/*
  55 * When a CPU is unplugged, remove its power contribution from the
  56 * dtpm tree.
  57 */
  58static int power_sub(struct dtpm *dtpm, struct em_perf_domain *em)
  59{
  60        u64 power_min, power_max;
  61
  62        power_min = em->table[0].power;
  63        power_min *= MICROWATT_PER_MILLIWATT;
  64        power_min = dtpm->power_min - power_min;
  65
  66        power_max = em->table[em->nr_perf_states - 1].power;
  67        power_max *= MICROWATT_PER_MILLIWATT;
  68        power_max = dtpm->power_max - power_max;
  69
  70        return dtpm_update_power(dtpm, power_min, power_max);
  71}
  72
  73static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
  74{
  75        struct dtpm_cpu *dtpm_cpu = dtpm->private;
  76        struct em_perf_domain *pd;
  77        struct cpumask cpus;
  78        unsigned long freq;
  79        u64 power;
  80        int i, nr_cpus;
  81
  82        pd = em_cpu_get(dtpm_cpu->cpu);
  83
  84        cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus));
  85
  86        nr_cpus = cpumask_weight(&cpus);
  87
  88        for (i = 0; i < pd->nr_perf_states; i++) {
  89
  90                power = pd->table[i].power * MICROWATT_PER_MILLIWATT * nr_cpus;
  91
  92                if (power > power_limit)
  93                        break;
  94        }
  95
  96        freq = pd->table[i - 1].frequency;
  97
  98        freq_qos_update_request(&dtpm_cpu->qos_req, freq);
  99
 100        power_limit = pd->table[i - 1].power *
 101                MICROWATT_PER_MILLIWATT * nr_cpus;
 102
 103        return power_limit;
 104}
 105
 106static u64 get_pd_power_uw(struct dtpm *dtpm)
 107{
 108        struct dtpm_cpu *dtpm_cpu = dtpm->private;
 109        struct em_perf_domain *pd;
 110        struct cpumask cpus;
 111        unsigned long freq;
 112        int i, nr_cpus;
 113
 114        pd = em_cpu_get(dtpm_cpu->cpu);
 115        freq = cpufreq_quick_get(dtpm_cpu->cpu);
 116        cpumask_and(&cpus, cpu_online_mask, to_cpumask(pd->cpus));
 117        nr_cpus = cpumask_weight(&cpus);
 118
 119        for (i = 0; i < pd->nr_perf_states; i++) {
 120
 121                if (pd->table[i].frequency < freq)
 122                        continue;
 123
 124                return pd->table[i].power *
 125                        MICROWATT_PER_MILLIWATT * nr_cpus;
 126        }
 127
 128        return 0;
 129}
 130
 131static void pd_release(struct dtpm *dtpm)
 132{
 133        struct dtpm_cpu *dtpm_cpu = dtpm->private;
 134
 135        if (freq_qos_request_active(&dtpm_cpu->qos_req))
 136                freq_qos_remove_request(&dtpm_cpu->qos_req);
 137
 138        kfree(dtpm_cpu);
 139}
 140
 141static struct dtpm_ops dtpm_ops = {
 142        .set_power_uw = set_pd_power_limit,
 143        .get_power_uw = get_pd_power_uw,
 144        .release = pd_release,
 145};
 146
 147static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
 148{
 149        struct cpufreq_policy *policy;
 150        struct em_perf_domain *pd;
 151        struct dtpm *dtpm;
 152
 153        policy = cpufreq_cpu_get(cpu);
 154
 155        if (!policy)
 156                return 0;
 157
 158        pd = em_cpu_get(cpu);
 159        if (!pd)
 160                return -EINVAL;
 161
 162        dtpm = per_cpu(dtpm_per_cpu, cpu);
 163
 164        power_sub(dtpm, pd);
 165
 166        if (cpumask_weight(policy->cpus) != 1)
 167                return 0;
 168
 169        for_each_cpu(cpu, policy->related_cpus)
 170                per_cpu(dtpm_per_cpu, cpu) = NULL;
 171
 172        dtpm_unregister(dtpm);
 173
 174        return 0;
 175}
 176
 177static int cpuhp_dtpm_cpu_online(unsigned int cpu)
 178{
 179        struct dtpm *dtpm;
 180        struct dtpm_cpu *dtpm_cpu;
 181        struct cpufreq_policy *policy;
 182        struct em_perf_domain *pd;
 183        char name[CPUFREQ_NAME_LEN];
 184        int ret = -ENOMEM;
 185
 186        policy = cpufreq_cpu_get(cpu);
 187
 188        if (!policy)
 189                return 0;
 190
 191        pd = em_cpu_get(cpu);
 192        if (!pd)
 193                return -EINVAL;
 194
 195        dtpm = per_cpu(dtpm_per_cpu, cpu);
 196        if (dtpm)
 197                return power_add(dtpm, pd);
 198
 199        dtpm = dtpm_alloc(&dtpm_ops);
 200        if (!dtpm)
 201                return -EINVAL;
 202
 203        dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
 204        if (!dtpm_cpu)
 205                goto out_kfree_dtpm;
 206
 207        dtpm->private = dtpm_cpu;
 208        dtpm_cpu->cpu = cpu;
 209
 210        for_each_cpu(cpu, policy->related_cpus)
 211                per_cpu(dtpm_per_cpu, cpu) = dtpm;
 212
 213        sprintf(name, "cpu%d", dtpm_cpu->cpu);
 214
 215        ret = dtpm_register(name, dtpm, __parent);
 216        if (ret)
 217                goto out_kfree_dtpm_cpu;
 218
 219        ret = power_add(dtpm, pd);
 220        if (ret)
 221                goto out_dtpm_unregister;
 222
 223        ret = freq_qos_add_request(&policy->constraints,
 224                                   &dtpm_cpu->qos_req, FREQ_QOS_MAX,
 225                                   pd->table[pd->nr_perf_states - 1].frequency);
 226        if (ret)
 227                goto out_power_sub;
 228
 229        return 0;
 230
 231out_power_sub:
 232        power_sub(dtpm, pd);
 233
 234out_dtpm_unregister:
 235        dtpm_unregister(dtpm);
 236        dtpm_cpu = NULL;
 237        dtpm = NULL;
 238
 239out_kfree_dtpm_cpu:
 240        for_each_cpu(cpu, policy->related_cpus)
 241                per_cpu(dtpm_per_cpu, cpu) = NULL;
 242        kfree(dtpm_cpu);
 243
 244out_kfree_dtpm:
 245        kfree(dtpm);
 246        return ret;
 247}
 248
 249int dtpm_register_cpu(struct dtpm *parent)
 250{
 251        __parent = parent;
 252
 253        return cpuhp_setup_state(CPUHP_AP_DTPM_CPU_ONLINE,
 254                                 "dtpm_cpu:online",
 255                                 cpuhp_dtpm_cpu_online,
 256                                 cpuhp_dtpm_cpu_offline);
 257}
 258