aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/cpu-freq/cpufreq-arm-bl.txt47
-rw-r--r--drivers/cpufreq/Kconfig.arm12
-rw-r--r--drivers/cpufreq/Makefile3
-rw-r--r--drivers/cpufreq/arm-bl-cpufreq.c244
4 files changed, 305 insertions, 1 deletions
diff --git a/Documentation/cpu-freq/cpufreq-arm-bl.txt b/Documentation/cpu-freq/cpufreq-arm-bl.txt
new file mode 100644
index 00000000000..52e2f3ad761
--- /dev/null
+++ b/Documentation/cpu-freq/cpufreq-arm-bl.txt
@@ -0,0 +1,47 @@
+Synchronous cluster switching interface for the ARM big.LITTLE switcher
+-----------------------------------------------------------------------
+
+The arm-bl-cpufreq driver provides a simple interface which models two
+clusters as two performance points.
+
+Within each CPU's cpufreq directory in sysfs
+(/sys/devices/system/cpu/cpu?/cpufreq/):
+
+cpuinfo_max_freq:
+
+ reports the dummy frequency value which corresponds to the "big"
+ cluster.
+
+cpuinfo_min_freq:
+
+ reports the dummy frequency value which corresponds to the
+ "little" cluster.
+
+cpuinfo_cur_freq:
+
+ reports the dummy frequency corresponding to the currently
+ running cluster.
+
+
+To switch clusters, either the built-in "powersave" or "performance"
+governors can be used to force the "little" or "big" cluster
+respectively; or alternatively the "userspace" governor can be used,
+
+The following script fragment demonstrates how the userspace governor
+can be used to switch:
+
+
+for x in /sys/devices/system/cpu/cpu[0-9]*; do
+ echo userspace >$x/cpufreq/scaling_governor
+done
+
+big_freq=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq`
+little_freq=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq`
+
+switch_to_big () {
+ echo $big_freq >/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
+}
+
+switch_to_little () {
+ echo $little_freq >/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
+}
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 6ef9c7b0691..97dd3fc49a3 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -2,6 +2,18 @@
# ARM CPU Frequency scaling drivers
#
+config ARM_BL_CPUFREQ
+ depends on EXPERIMENTAL
+ depends on BL_SWITCHER
+ tristate "Simple cpufreq interface for the ARM big.LITTLE switcher"
+ help
+ Provides a simple cpufreq interface to control the ARM
+ big.LITTLE switcher.
+
+ Refer to Documentation/cpu-freq/cpufreq-arm-bl.txt for details.
+
+ If unsure, say N.
+
config ARM_OMAP2PLUS_CPUFREQ
bool "TI OMAP2+"
depends on ARCH_OMAP2PLUS
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 6148eac24fc..3bcc6a11589 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -44,7 +44,8 @@ obj-$(CONFIG_X86_INTEL_PSTATE) += intel_pstate.o
##################################################################################
# ARM SoC drivers
-obj-$(CONFIG_UX500_SOC_DB8500) += dbx500-cpufreq.o
+obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o
+obj-$(CONFIG_ARM_BL_CPUFREQ) += arm-bl-cpufreq.o
obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o
obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
diff --git a/drivers/cpufreq/arm-bl-cpufreq.c b/drivers/cpufreq/arm-bl-cpufreq.c
new file mode 100644
index 00000000000..e37140f0f1b
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq.c
@@ -0,0 +1,244 @@
+/*
+ * arm-bl-cpufreq.c: Simple cpufreq backend for the ARM big.LITTLE switcher
+ * Copyright (C) 2012 Linaro Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define MODULE_NAME "arm-bl-cpufreq"
+#define pr_fmt(fmt) MODULE_NAME ": " fmt
+
+#include <linux/bug.h>
+#include <linux/cache.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+
+#include <asm/bL_switcher.h>
+
+
+/* Dummy frequencies representing the big and little clusters: */
+#define FREQ_BIG 1000000
+#define FREQ_LITTLE 100000
+
+/* Cluster numbers */
+#define CLUSTER_BIG 0
+#define CLUSTER_LITTLE 1
+
+static struct cpufreq_frequency_table __read_mostly bl_freqs[] = {
+ { CLUSTER_BIG, FREQ_BIG },
+ { CLUSTER_LITTLE, FREQ_LITTLE },
+ { 0, CPUFREQ_TABLE_END },
+};
+
+/* Cached current cluster for each CPU to save on IPIs */
+static DEFINE_PER_CPU(unsigned int, cpu_cur_cluster);
+
+
+/* Miscellaneous helpers */
+
+static unsigned int entry_to_freq(
+ struct cpufreq_frequency_table const *entry)
+{
+ return entry->frequency;
+}
+
+static unsigned int entry_to_cluster(
+ struct cpufreq_frequency_table const *entry)
+{
+ return entry->index;
+}
+
+static struct cpufreq_frequency_table const *find_entry_by_cluster(int cluster)
+{
+ unsigned int i;
+
+ for(i = 0; entry_to_freq(&bl_freqs[i]) != CPUFREQ_TABLE_END; i++)
+ if(entry_to_cluster(&bl_freqs[i]) == cluster)
+ return &bl_freqs[i];
+
+ WARN(1, pr_fmt("%s(): invalid cluster number %d, assuming 0\n"),
+ __func__, cluster);
+ return &bl_freqs[0];
+}
+
+static unsigned int cluster_to_freq(int cluster)
+{
+ return entry_to_freq(find_entry_by_cluster(cluster));
+}
+
+/*
+ * Functions to get the current status.
+ *
+ * Beware that the cluster for another CPU may change unexpectedly.
+ */
+
+static unsigned int get_local_cluster(void)
+{
+ unsigned int mpidr;
+ asm ("mrc\tp15, 0, %0, c0, c0, 5" : "=r" (mpidr));
+ return MPIDR_AFFINITY_LEVEL(mpidr, 1);
+}
+
+static void __get_current_cluster(void *_data)
+{
+ unsigned int *_cluster = _data;
+ *_cluster = get_local_cluster();
+}
+
+static int get_current_cluster(unsigned int cpu)
+{
+ unsigned int cluster = 0;
+ smp_call_function_single(cpu, __get_current_cluster, &cluster, 1);
+ return cluster;
+}
+
+static int get_current_cached_cluster(unsigned int cpu)
+{
+ return per_cpu(cpu_cur_cluster, cpu);
+}
+
+static unsigned int get_current_freq(unsigned int cpu)
+{
+ return cluster_to_freq(get_current_cluster(cpu));
+}
+
+/*
+ * Switch to the requested cluster.
+ */
+static void switch_to_entry(unsigned int cpu,
+ struct cpufreq_frequency_table const *target)
+{
+ int old_cluster, new_cluster;
+ struct cpufreq_freqs freqs;
+
+ old_cluster = get_current_cached_cluster(cpu);
+ new_cluster = entry_to_cluster(target);
+
+ pr_debug("Switching to cluster %d on CPU %d\n", new_cluster, cpu);
+
+ if(new_cluster == old_cluster)
+ return;
+
+ freqs.cpu = cpu;
+ freqs.old = cluster_to_freq(old_cluster);
+ freqs.new = entry_to_freq(target);
+
+ cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+ bL_switch_request(cpu, new_cluster);
+ per_cpu(cpu_cur_cluster, cpu) = new_cluster;
+ cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+}
+
+
+/* Cpufreq methods and module code */
+
+static int bl_cpufreq_init(struct cpufreq_policy *policy)
+{
+ unsigned int cluster, cpu = policy->cpu;
+ int err;
+
+ /*
+ * Set CPU and policy min and max frequencies based on bl_freqs:
+ */
+ err = cpufreq_frequency_table_cpuinfo(policy, bl_freqs);
+ if (err)
+ goto error;
+
+ cluster = get_current_cluster(cpu);
+ per_cpu(cpu_cur_cluster, cpu) = cluster;
+
+ /*
+ * Ideally, transition_latency should be calibrated here.
+ */
+ policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
+ policy->cur = cluster_to_freq(cluster);
+ policy->shared_type = CPUFREQ_SHARED_TYPE_NONE;
+
+ pr_info("cpufreq initialised successfully\n");
+ return 0;
+
+error:
+ pr_warning("cpufreq initialisation failed (%d)\n", err);
+ return err;
+}
+
+static int bl_cpufreq_verify(struct cpufreq_policy *policy)
+{
+ return cpufreq_frequency_table_verify(policy, bl_freqs);
+}
+
+static int bl_cpufreq_target(struct cpufreq_policy *policy,
+ unsigned int target_freq,
+ unsigned int relation)
+{
+ int err;
+ int index;
+
+ err = cpufreq_frequency_table_target(policy, bl_freqs, target_freq,
+ relation, &index);
+ if(err)
+ return err;
+
+ switch_to_entry(policy->cpu, &bl_freqs[index]);
+ return 0;
+}
+
+static unsigned int bl_cpufreq_get(unsigned int cpu)
+{
+ return get_current_freq(cpu);
+}
+
+static struct cpufreq_driver __read_mostly bl_cpufreq_driver = {
+ .owner = THIS_MODULE,
+ .name = MODULE_NAME,
+
+ .init = bl_cpufreq_init,
+ .verify = bl_cpufreq_verify,
+ .target = bl_cpufreq_target,
+ .get = bl_cpufreq_get,
+ /* what else? */
+};
+
+static int __init bl_cpufreq_module_init(void)
+{
+ int err;
+
+ err = cpufreq_register_driver(&bl_cpufreq_driver);
+ if(err)
+ pr_info("cpufreq backend driver registration failed (%d)\n",
+ err);
+ else
+ pr_info("cpufreq backend driver registered.\n");
+
+ return err;
+}
+module_init(bl_cpufreq_module_init);
+
+static void __exit bl_cpufreq_module_exit(void)
+{
+ cpufreq_unregister_driver(&bl_cpufreq_driver);
+ pr_info("cpufreq backend driver unloaded.\n");
+}
+module_exit(bl_cpufreq_module_exit);
+
+
+MODULE_AUTHOR("Dave Martin");
+MODULE_DESCRIPTION("Simple cpufreq interface for the ARM big.LITTLE switcher");
+MODULE_LICENSE("GPL");