aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Haslam <ahaslam@baylibre.com>2015-10-02 15:18:20 +0200
committerAxel Haslam <ahaslam@baylibre.com>2015-10-02 20:43:30 +0200
commitc0542c9e21838a51e555ef6c899adbf1d30a09f1 (patch)
tree90226a37c5398ea309549829127257bb20172984
parentf78e34306439f2879017c2a55b61e8d357dc2521 (diff)
downloadlinux-genpd-pstates.tar.gz
se opp scale for arm cpusgenpd-pstates
add a new cpufreq driver that will use the runtime pm functions and relly on platform code to do the hard work to set the actual pstate. Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
-rw-r--r--arch/arm/common/domains.c17
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/cpufreq-dt-opp.c266
3 files changed, 282 insertions, 3 deletions
diff --git a/arch/arm/common/domains.c b/arch/arm/common/domains.c
index b4b59d1495a277..737bf67d26f910 100644
--- a/arch/arm/common/domains.c
+++ b/arch/arm/common/domains.c
@@ -17,6 +17,7 @@
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/pm_opp.h>
#include <asm/arm-pd.h>
@@ -143,6 +144,10 @@ static int __init arm_domain_cpu_init(void)
__func__, ret);
pm_runtime_disable(cpu_dev);
}
+
+ ret = opp_scale_register(cpu_dev, NULL, NULL);
+ if (ret)
+ dev_warn(cpu_dev, "Could not register opps\n");
}
return 0;
@@ -156,10 +161,18 @@ int arm_pd_get_next_state(struct generic_pm_domain *genpd,
}
int arm_pd_set_state(struct generic_pm_domain *genpd,
- unsigned int state)
+ unsigned int pstate)
{
+ int ret;
+ struct device *cpu_dev;
- return 0;
+ cpu_dev = get_cpu_device(0);
+
+ ret = opp_scale(cpu_dev, pstate);
+ if (ret)
+ dev_warn(cpu_dev, "Could not scale opp\n");
+
+ return ret;
}
static int __init arm_domain_init(void)
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 2169bf792db76c..a49653dcf747e0 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o
obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o
obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o
-obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o
+obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt-opp.o
##################################################################################
# x86 drivers.
diff --git a/drivers/cpufreq/cpufreq-dt-opp.c b/drivers/cpufreq/cpufreq-dt-opp.c
new file mode 100644
index 00000000000000..905729f60c19b4
--- /dev/null
+++ b/drivers/cpufreq/cpufreq-dt-opp.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ *
+ * Copyright (C) 2014 Linaro.
+ * Viresh Kumar <viresh.kumar@linaro.org>
+ *
+ * The OPP code in function set_target() is reused from
+ * drivers/cpufreq/omap-cpufreq.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+#include <linux/cpufreq.h>
+#include <linux/cpufreq-dt.h>
+#include <linux/cpumask.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_opp.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/pm_runtime.h>
+
+struct private_data {
+ struct device *cpu_dev;
+ struct regulator *cpu_reg;
+ struct thermal_cooling_device *cdev;
+ unsigned int voltage_tolerance; /* in percentage */
+};
+
+static int set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+ struct private_data *priv = policy->driver_data;
+ struct device *cpu_dev = priv->cpu_dev;
+
+ return pm_runtime_pstate_set(cpu_dev, index);
+}
+
+static int allocate_resources(int cpu, struct device **cdev)
+{
+ struct device *cpu_dev;
+
+ cpu_dev = get_cpu_device(cpu);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu%d device\n", cpu);
+ return -ENODEV;
+ }
+
+ *cdev = cpu_dev;
+
+ pm_runtime_scale_allow(cpu_dev);
+
+ return 0;
+}
+
+static int cpufreq_init(struct cpufreq_policy *policy)
+{
+ struct cpufreq_dt_platform_data *pd;
+ struct cpufreq_frequency_table *freq_table;
+ struct device_node *np;
+ struct private_data *priv;
+ struct device *cpu_dev;
+ unsigned int transition_latency;
+ int ret;
+
+ ret = allocate_resources(policy->cpu, &cpu_dev);
+ if (ret) {
+ pr_err("%s: Failed to allocate resources: %d\n", __func__, ret);
+ return ret;
+ }
+
+ np = of_node_get(cpu_dev->of_node);
+ if (!np) {
+ dev_err(cpu_dev, "failed to find cpu%d node\n", policy->cpu);
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* OPPs might be populated at runtime, don't check for error here */
+ of_init_opp_table(cpu_dev);
+
+ /*
+ * But we need OPP table to function so if it is not there let's
+ * give platform code chance to provide it for us.
+ */
+ ret = dev_pm_opp_get_opp_count(cpu_dev);
+ if (ret <= 0) {
+ pr_debug("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;
+ }
+
+ of_property_read_u32(np, "voltage-tolerance", &priv->voltage_tolerance);
+
+ if (of_property_read_u32(np, "clock-latency", &transition_latency))
+ transition_latency = CPUFREQ_ETERNAL;
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ pr_err("failed to init cpufreq table: %d\n", ret);
+ goto out_free_priv;
+ }
+
+ priv->cpu_dev = 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;
+ }
+
+ policy->cpuinfo.transition_latency = transition_latency;
+
+ pd = cpufreq_get_driver_data();
+ if (!pd || !pd->independent_clocks)
+ cpumask_setall(policy->cpus);
+
+ of_node_put(np);
+
+ return 0;
+
+out_free_cpufreq_table:
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+out_free_priv:
+ kfree(priv);
+out_free_opp:
+ of_free_opp_table(cpu_dev);
+ of_node_put(np);
+out:
+
+ return ret;
+}
+
+static int cpufreq_exit(struct cpufreq_policy *policy)
+{
+ struct private_data *priv = policy->driver_data;
+
+ cpufreq_cooling_unregister(priv->cdev);
+ dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+ of_free_opp_table(priv->cpu_dev);
+
+ kfree(priv);
+
+ return 0;
+}
+
+static void cpufreq_ready(struct cpufreq_policy *policy)
+{
+ struct private_data *priv = policy->driver_data;
+ struct device_node *np = of_node_get(priv->cpu_dev->of_node);
+
+ if (WARN_ON(!np))
+ return;
+
+ /*
+ * For now, just loading the cooling device;
+ * thermal DT code takes care of matching them.
+ */
+ if (of_find_property(np, "#cooling-cells", NULL)) {
+ priv->cdev = of_cpufreq_cooling_register(np,
+ policy->related_cpus);
+ if (IS_ERR(priv->cdev)) {
+ dev_err(priv->cpu_dev,
+ "running cpufreq without cooling device: %ld\n",
+ PTR_ERR(priv->cdev));
+
+ priv->cdev = NULL;
+ }
+ }
+
+ of_node_put(np);
+}
+
+unsigned int cpufreq_dt_get(unsigned int cpu)
+{
+ struct device *dev;
+ struct clk *clk;
+ unsigned int rate;
+
+ dev = get_cpu_device(cpu);
+
+ clk = clk_get(dev, NULL);
+
+ rate = clk_get_rate(clk) / 1000;
+
+ clk_put(clk);
+
+ return rate;
+}
+
+
+static struct cpufreq_driver dt_cpufreq_driver = {
+ .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .target_index = set_target,
+ .get = cpufreq_dt_get,
+ .init = cpufreq_init,
+ .exit = cpufreq_exit,
+ .ready = cpufreq_ready,
+ .name = "cpufreq-dt",
+ .attr = cpufreq_generic_attr,
+};
+
+static int dt_cpufreq_probe(struct platform_device *pdev)
+{
+ struct device *cpu_dev;
+ int ret;
+
+ /*
+ * All per-cluster (CPUs sharing clock/voltages) initialization is done
+ * from ->init(). In probe(), we just need to make sure that clk and
+ * regulators are available. Else defer probe and retry.
+ *
+ * FIXME: Is checking this only for CPU0 sufficient ?
+ */
+ ret = allocate_resources(0, &cpu_dev);
+ if (ret)
+ return ret;
+
+ dt_cpufreq_driver.driver_data = dev_get_platdata(&pdev->dev);
+
+ ret = cpufreq_register_driver(&dt_cpufreq_driver);
+ if (ret)
+ dev_err(cpu_dev, "failed register driver: %d\n", ret);
+
+ return ret;
+}
+
+static int dt_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&dt_cpufreq_driver);
+ return 0;
+}
+
+static struct platform_driver dt_cpufreq_platdrv = {
+ .driver = {
+ .name = "cpufreq-dt",
+ },
+ .probe = dt_cpufreq_probe,
+ .remove = dt_cpufreq_remove,
+};
+module_platform_driver(dt_cpufreq_platdrv);
+
+MODULE_ALIAS("platform:cpufreq-dt");
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("Generic cpufreq driver");
+MODULE_LICENSE("GPL");