linux/drivers/cpufreq/scmi-cpufreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * System Control and Power Interface (SCMI) based CPUFreq Interface driver
   4 *
   5 * Copyright (C) 2018-2021 ARM Ltd.
   6 * Sudeep Holla <sudeep.holla@arm.com>
   7 */
   8
   9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  10
  11#include <linux/clk-provider.h>
  12#include <linux/cpu.h>
  13#include <linux/cpufreq.h>
  14#include <linux/cpumask.h>
  15#include <linux/energy_model.h>
  16#include <linux/export.h>
  17#include <linux/module.h>
  18#include <linux/pm_opp.h>
  19#include <linux/slab.h>
  20#include <linux/scmi_protocol.h>
  21#include <linux/types.h>
  22
  23struct scmi_data {
  24        int domain_id;
  25        struct device *cpu_dev;
  26};
  27
  28static struct scmi_protocol_handle *ph;
  29static const struct scmi_perf_proto_ops *perf_ops;
  30
  31static unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
  32{
  33        struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
  34        struct scmi_data *priv = policy->driver_data;
  35        unsigned long rate;
  36        int ret;
  37
  38        ret = perf_ops->freq_get(ph, priv->domain_id, &rate, false);
  39        if (ret)
  40                return 0;
  41        return rate / 1000;
  42}
  43
  44/*
  45 * perf_ops->freq_set is not a synchronous, the actual OPP change will
  46 * happen asynchronously and can get notified if the events are
  47 * subscribed for by the SCMI firmware
  48 */
  49static int
  50scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
  51{
  52        struct scmi_data *priv = policy->driver_data;
  53        u64 freq = policy->freq_table[index].frequency;
  54
  55        return perf_ops->freq_set(ph, priv->domain_id, freq * 1000, false);
  56}
  57
  58static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
  59                                             unsigned int target_freq)
  60{
  61        struct scmi_data *priv = policy->driver_data;
  62
  63        if (!perf_ops->freq_set(ph, priv->domain_id,
  64                                target_freq * 1000, true))
  65                return target_freq;
  66
  67        return 0;
  68}
  69
  70static int
  71scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
  72{
  73        int cpu, domain, tdomain;
  74        struct device *tcpu_dev;
  75
  76        domain = perf_ops->device_domain_id(cpu_dev);
  77        if (domain < 0)
  78                return domain;
  79
  80        for_each_possible_cpu(cpu) {
  81                if (cpu == cpu_dev->id)
  82                        continue;
  83
  84                tcpu_dev = get_cpu_device(cpu);
  85                if (!tcpu_dev)
  86                        continue;
  87
  88                tdomain = perf_ops->device_domain_id(tcpu_dev);
  89                if (tdomain == domain)
  90                        cpumask_set_cpu(cpu, cpumask);
  91        }
  92
  93        return 0;
  94}
  95
  96static int __maybe_unused
  97scmi_get_cpu_power(unsigned long *power, unsigned long *KHz,
  98                   struct device *cpu_dev)
  99{
 100        unsigned long Hz;
 101        int ret, domain;
 102
 103        domain = perf_ops->device_domain_id(cpu_dev);
 104        if (domain < 0)
 105                return domain;
 106
 107        /* Get the power cost of the performance domain. */
 108        Hz = *KHz * 1000;
 109        ret = perf_ops->est_power_get(ph, domain, &Hz, power);
 110        if (ret)
 111                return ret;
 112
 113        /* The EM framework specifies the frequency in KHz. */
 114        *KHz = Hz / 1000;
 115
 116        return 0;
 117}
 118
 119static int scmi_cpufreq_init(struct cpufreq_policy *policy)
 120{
 121        int ret, nr_opp;
 122        unsigned int latency;
 123        struct device *cpu_dev;
 124        struct scmi_data *priv;
 125        struct cpufreq_frequency_table *freq_table;
 126        struct em_data_callback em_cb = EM_DATA_CB(scmi_get_cpu_power);
 127        cpumask_var_t opp_shared_cpus;
 128        bool power_scale_mw;
 129
 130        cpu_dev = get_cpu_device(policy->cpu);
 131        if (!cpu_dev) {
 132                pr_err("failed to get cpu%d device\n", policy->cpu);
 133                return -ENODEV;
 134        }
 135
 136        if (!zalloc_cpumask_var(&opp_shared_cpus, GFP_KERNEL))
 137                return -ENOMEM;
 138
 139        /* Obtain CPUs that share SCMI performance controls */
 140        ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
 141        if (ret) {
 142                dev_warn(cpu_dev, "failed to get sharing cpumask\n");
 143                goto out_free_cpumask;
 144        }
 145
 146        /*
 147         * Obtain CPUs that share performance levels.
 148         * The OPP 'sharing cpus' info may come from DT through an empty opp
 149         * table and opp-shared.
 150         */
 151        ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, opp_shared_cpus);
 152        if (ret || !cpumask_weight(opp_shared_cpus)) {
 153                /*
 154                 * Either opp-table is not set or no opp-shared was found.
 155                 * Use the CPU mask from SCMI to designate CPUs sharing an OPP
 156                 * table.
 157                 */
 158                cpumask_copy(opp_shared_cpus, policy->cpus);
 159        }
 160
 161         /*
 162          * A previous CPU may have marked OPPs as shared for a few CPUs, based on
 163          * what OPP core provided. If the current CPU is part of those few, then
 164          * there is no need to add OPPs again.
 165          */
 166        nr_opp = dev_pm_opp_get_opp_count(cpu_dev);
 167        if (nr_opp <= 0) {
 168                ret = perf_ops->device_opps_add(ph, cpu_dev);
 169                if (ret) {
 170                        dev_warn(cpu_dev, "failed to add opps to the device\n");
 171                        goto out_free_cpumask;
 172                }
 173
 174                nr_opp = dev_pm_opp_get_opp_count(cpu_dev);
 175                if (nr_opp <= 0) {
 176                        dev_err(cpu_dev, "%s: No OPPs for this device: %d\n",
 177                                __func__, nr_opp);
 178
 179                        ret = -ENODEV;
 180                        goto out_free_opp;
 181                }
 182
 183                ret = dev_pm_opp_set_sharing_cpus(cpu_dev, opp_shared_cpus);
 184                if (ret) {
 185                        dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
 186                                __func__, ret);
 187
 188                        goto out_free_opp;
 189                }
 190
 191                power_scale_mw = perf_ops->power_scale_mw_get(ph);
 192                em_dev_register_perf_domain(cpu_dev, nr_opp, &em_cb,
 193                                            opp_shared_cpus, power_scale_mw);
 194        }
 195
 196        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 197        if (!priv) {
 198                ret = -ENOMEM;
 199                goto out_free_opp;
 200        }
 201
 202        ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
 203        if (ret) {
 204                dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
 205                goto out_free_priv;
 206        }
 207
 208        priv->cpu_dev = cpu_dev;
 209        priv->domain_id = perf_ops->device_domain_id(cpu_dev);
 210
 211        policy->driver_data = priv;
 212        policy->freq_table = freq_table;
 213
 214        /* SCMI allows DVFS request for any domain from any CPU */
 215        policy->dvfs_possible_from_any_cpu = true;
 216
 217        latency = perf_ops->transition_latency_get(ph, cpu_dev);
 218        if (!latency)
 219                latency = CPUFREQ_ETERNAL;
 220
 221        policy->cpuinfo.transition_latency = latency;
 222
 223        policy->fast_switch_possible =
 224                perf_ops->fast_switch_possible(ph, cpu_dev);
 225
 226        free_cpumask_var(opp_shared_cpus);
 227        return 0;
 228
 229out_free_priv:
 230        kfree(priv);
 231
 232out_free_opp:
 233        dev_pm_opp_remove_all_dynamic(cpu_dev);
 234
 235out_free_cpumask:
 236        free_cpumask_var(opp_shared_cpus);
 237
 238        return ret;
 239}
 240
 241static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
 242{
 243        struct scmi_data *priv = policy->driver_data;
 244
 245        dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
 246        dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
 247        kfree(priv);
 248
 249        return 0;
 250}
 251
 252static struct cpufreq_driver scmi_cpufreq_driver = {
 253        .name   = "scmi",
 254        .flags  = CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
 255                  CPUFREQ_NEED_INITIAL_FREQ_CHECK |
 256                  CPUFREQ_IS_COOLING_DEV,
 257        .verify = cpufreq_generic_frequency_table_verify,
 258        .attr   = cpufreq_generic_attr,
 259        .target_index   = scmi_cpufreq_set_target,
 260        .fast_switch    = scmi_cpufreq_fast_switch,
 261        .get    = scmi_cpufreq_get_rate,
 262        .init   = scmi_cpufreq_init,
 263        .exit   = scmi_cpufreq_exit,
 264};
 265
 266static int scmi_cpufreq_probe(struct scmi_device *sdev)
 267{
 268        int ret;
 269        struct device *dev = &sdev->dev;
 270        const struct scmi_handle *handle;
 271
 272        handle = sdev->handle;
 273
 274        if (!handle)
 275                return -ENODEV;
 276
 277        perf_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_PERF, &ph);
 278        if (IS_ERR(perf_ops))
 279                return PTR_ERR(perf_ops);
 280
 281#ifdef CONFIG_COMMON_CLK
 282        /* dummy clock provider as needed by OPP if clocks property is used */
 283        if (of_find_property(dev->of_node, "#clock-cells", NULL))
 284                devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, NULL);
 285#endif
 286
 287        ret = cpufreq_register_driver(&scmi_cpufreq_driver);
 288        if (ret) {
 289                dev_err(dev, "%s: registering cpufreq failed, err: %d\n",
 290                        __func__, ret);
 291        }
 292
 293        return ret;
 294}
 295
 296static void scmi_cpufreq_remove(struct scmi_device *sdev)
 297{
 298        cpufreq_unregister_driver(&scmi_cpufreq_driver);
 299}
 300
 301static const struct scmi_device_id scmi_id_table[] = {
 302        { SCMI_PROTOCOL_PERF, "cpufreq" },
 303        { },
 304};
 305MODULE_DEVICE_TABLE(scmi, scmi_id_table);
 306
 307static struct scmi_driver scmi_cpufreq_drv = {
 308        .name           = "scmi-cpufreq",
 309        .probe          = scmi_cpufreq_probe,
 310        .remove         = scmi_cpufreq_remove,
 311        .id_table       = scmi_id_table,
 312};
 313module_scmi_driver(scmi_cpufreq_drv);
 314
 315MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
 316MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
 317MODULE_LICENSE("GPL v2");
 318