cpufreq: add support for CPU DVFS based on SCMI message protocol
authorSudeep Holla <sudeep.holla@arm.com>
Sun, 18 Jun 2017 08:38:11 +0000 (09:38 +0100)
committerSudeep Holla <sudeep.holla@arm.com>
Wed, 28 Feb 2018 16:37:57 +0000 (16:37 +0000)
On some ARM based systems, a separate Cortex-M based System Control
Processor(SCP) provides the overall power, clock, reset and system
control including CPU DVFS. SCMI Message Protocol is used to
communicate with the SCP.

This patch adds a cpufreq driver for such systems using SCMI interface
to drive CPU DVFS.

Cc: linux-pm@vger.kernel.org
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
MAINTAINERS
drivers/cpufreq/Kconfig.arm
drivers/cpufreq/Makefile
drivers/cpufreq/scmi-cpufreq.c [new file with mode: 0644]

index d6e9e3e..ef766eb 100644 (file)
@@ -13385,7 +13385,7 @@ L:      linux-arm-kernel@lists.infradead.org
 S:     Maintained
 F:     Documentation/devicetree/bindings/arm/arm,sc[mp]i.txt
 F:     drivers/clk/clk-sc[mp]i.c
-F:     drivers/cpufreq/scpi-cpufreq.c
+F:     drivers/cpufreq/sc[mp]i-cpufreq.c
 F:     drivers/firmware/arm_scpi.c
 F:     drivers/firmware/arm_scmi/
 F:     include/linux/sc[mp]i_protocol.h
index 3a88e33..2e49673 100644 (file)
@@ -238,6 +238,17 @@ config ARM_SA1100_CPUFREQ
 config ARM_SA1110_CPUFREQ
        bool
 
+config ARM_SCMI_CPUFREQ
+       tristate "SCMI based CPUfreq driver"
+       depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+       select PM_OPP
+       help
+         This adds the CPUfreq driver support for ARM platforms using SCMI
+         protocol for CPU power management.
+
+         This driver uses SCMI Message Protocol driver to interact with the
+         firmware providing the CPU DVFS functionality.
+
 config ARM_SPEAR_CPUFREQ
        bool "SPEAr CPUFreq support"
        depends on PLAT_SPEAR
index c60c1e1..4987227 100644 (file)
@@ -75,6 +75,7 @@ obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
 obj-$(CONFIG_ARM_S5PV210_CPUFREQ)      += s5pv210-cpufreq.o
 obj-$(CONFIG_ARM_SA1100_CPUFREQ)       += sa1100-cpufreq.o
 obj-$(CONFIG_ARM_SA1110_CPUFREQ)       += sa1110-cpufreq.o
+obj-$(CONFIG_ARM_SCMI_CPUFREQ)         += scmi-cpufreq.o
 obj-$(CONFIG_ARM_SCPI_CPUFREQ)         += scpi-cpufreq.o
 obj-$(CONFIG_ARM_SPEAR_CPUFREQ)                += spear-cpufreq.o
 obj-$(CONFIG_ARM_STI_CPUFREQ)          += sti-cpufreq.o
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
new file mode 100644 (file)
index 0000000..b04f07f
--- /dev/null
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Power Interface (SCMI) based CPUFreq Interface driver
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ * Sudeep Holla <sudeep.holla@arm.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_cooling.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+struct scmi_data {
+       int domain_id;
+       struct device *cpu_dev;
+       struct thermal_cooling_device *cdev;
+};
+
+static const struct scmi_handle *handle;
+
+static unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
+{
+       struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+       struct scmi_perf_ops *perf_ops = handle->perf_ops;
+       struct scmi_data *priv = policy->driver_data;
+       unsigned long rate;
+       int ret;
+
+       ret = perf_ops->freq_get(handle, priv->domain_id, &rate, false);
+       if (ret)
+               return 0;
+       return rate / 1000;
+}
+
+/*
+ * perf_ops->freq_set is not a synchronous, the actual OPP change will
+ * happen asynchronously and can get notified if the events are
+ * subscribed for by the SCMI firmware
+ */
+static int
+scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+       int ret;
+       struct scmi_data *priv = policy->driver_data;
+       struct scmi_perf_ops *perf_ops = handle->perf_ops;
+       u64 freq = policy->freq_table[index].frequency * 1000;
+
+       ret = perf_ops->freq_set(handle, priv->domain_id, freq, false);
+       if (!ret)
+               arch_set_freq_scale(policy->related_cpus, freq,
+                                   policy->cpuinfo.max_freq);
+       return ret;
+}
+
+static int
+scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
+{
+       int cpu, domain, tdomain;
+       struct device *tcpu_dev;
+
+       domain = handle->perf_ops->device_domain_id(cpu_dev);
+       if (domain < 0)
+               return domain;
+
+       for_each_possible_cpu(cpu) {
+               if (cpu == cpu_dev->id)
+                       continue;
+
+               tcpu_dev = get_cpu_device(cpu);
+               if (!tcpu_dev)
+                       continue;
+
+               tdomain = handle->perf_ops->device_domain_id(tcpu_dev);
+               if (tdomain == domain)
+                       cpumask_set_cpu(cpu, cpumask);
+       }
+
+       return 0;
+}
+
+static int scmi_cpufreq_init(struct cpufreq_policy *policy)
+{
+       int ret;
+       unsigned int latency;
+       struct device *cpu_dev;
+       struct scmi_data *priv;
+       struct cpufreq_frequency_table *freq_table;
+
+       cpu_dev = get_cpu_device(policy->cpu);
+       if (!cpu_dev) {
+               pr_err("failed to get cpu%d device\n", policy->cpu);
+               return -ENODEV;
+       }
+
+       ret = handle->perf_ops->add_opps_to_device(handle, cpu_dev);
+       if (ret) {
+               dev_warn(cpu_dev, "failed to add opps to the device\n");
+               return ret;
+       }
+
+       ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
+       if (ret) {
+               dev_warn(cpu_dev, "failed to get sharing cpumask\n");
+               return ret;
+       }
+
+       ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
+       if (ret) {
+               dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       ret = dev_pm_opp_get_opp_count(cpu_dev);
+       if (ret <= 0) {
+               dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
+               ret = -EPROBE_DEFER;
+               goto out_free_opp;
+       }
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv) {
+               ret = -ENOMEM;
+               goto out_free_opp;
+       }
+
+       ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+       if (ret) {
+               dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+               goto out_free_priv;
+       }
+
+       priv->cpu_dev = cpu_dev;
+       priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
+
+       policy->driver_data = priv;
+
+       ret = cpufreq_table_validate_and_show(policy, freq_table);
+       if (ret) {
+               dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
+                       ret);
+               goto out_free_cpufreq_table;
+       }
+
+       /* SCMI allows DVFS request for any domain from any CPU */
+       policy->dvfs_possible_from_any_cpu = true;
+
+       latency = handle->perf_ops->get_transition_latency(handle, cpu_dev);
+       if (!latency)
+               latency = CPUFREQ_ETERNAL;
+
+       policy->cpuinfo.transition_latency = latency;
+
+       return 0;
+
+out_free_cpufreq_table:
+       dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+out_free_priv:
+       kfree(priv);
+out_free_opp:
+       dev_pm_opp_cpumask_remove_table(policy->cpus);
+
+       return ret;
+}
+
+static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
+{
+       struct scmi_data *priv = policy->driver_data;
+
+       cpufreq_cooling_unregister(priv->cdev);
+       dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+       kfree(priv);
+       dev_pm_opp_cpumask_remove_table(policy->related_cpus);
+
+       return 0;
+}
+
+static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
+{
+       struct scmi_data *priv = policy->driver_data;
+
+       priv->cdev = of_cpufreq_cooling_register(policy);
+}
+
+static struct cpufreq_driver scmi_cpufreq_driver = {
+       .name   = "scmi",
+       .flags  = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
+                 CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+       .verify = cpufreq_generic_frequency_table_verify,
+       .attr   = cpufreq_generic_attr,
+       .target_index   = scmi_cpufreq_set_target,
+       .get    = scmi_cpufreq_get_rate,
+       .init   = scmi_cpufreq_init,
+       .exit   = scmi_cpufreq_exit,
+       .ready  = scmi_cpufreq_ready,
+};
+
+static int scmi_cpufreq_probe(struct scmi_device *sdev)
+{
+       int ret;
+
+       handle = sdev->handle;
+
+       if (!handle || !handle->perf_ops)
+               return -ENODEV;
+
+       ret = cpufreq_register_driver(&scmi_cpufreq_driver);
+       if (ret) {
+               dev_err(&sdev->dev, "%s: registering cpufreq failed, err: %d\n",
+                       __func__, ret);
+       }
+
+       return ret;
+}
+
+static void scmi_cpufreq_remove(struct scmi_device *sdev)
+{
+       cpufreq_unregister_driver(&scmi_cpufreq_driver);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+       { SCMI_PROTOCOL_PERF },
+       { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_cpufreq_drv = {
+       .name           = "scmi-cpufreq",
+       .probe          = scmi_cpufreq_probe,
+       .remove         = scmi_cpufreq_remove,
+       .id_table       = scmi_id_table,
+};
+module_scmi_driver(scmi_cpufreq_drv);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
+MODULE_LICENSE("GPL v2");