aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/bus/arm-cci.c13
-rw-r--r--drivers/clk/versatile/clk-vexpress-spc.c2
-rw-r--r--drivers/cpufreq/Kconfig.arm21
-rw-r--r--drivers/cpufreq/Makefile3
-rw-r--r--drivers/cpufreq/arm-bl-cpufreq.c270
-rw-r--r--drivers/cpufreq/arm-bl-cpufreq.h37
-rw-r--r--drivers/cpufreq/arm-bl-cpufreq_tests.c652
-rw-r--r--drivers/cpufreq/arm_big_little.c378
-rw-r--r--drivers/cpufreq/cpufreq_stats.c49
9 files changed, 1381 insertions, 44 deletions
diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c
index b110645bc56..eee1c5722fd 100644
--- a/drivers/bus/arm-cci.c
+++ b/drivers/bus/arm-cci.c
@@ -156,7 +156,8 @@ static u32 cci_pmu_get_max_counters(void)
return n_cnts + 1;
}
-static struct pmu_hw_events *cci_pmu_get_hw_events(void)
+static struct pmu_hw_events *cci_pmu_get_hw_events(
+ struct arm_pmu *__always_unused pmu)
{
return &cci_hw_events;
}
@@ -233,7 +234,7 @@ static int cci_pmu_request_irq(struct arm_pmu *cci_pmu, irq_handler_t handler)
static irqreturn_t cci_pmu_handle_irq(int irq_num, void *dev)
{
struct arm_pmu *cci_pmu = (struct arm_pmu *)dev;
- struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct pmu_hw_events *events = cci_pmu->get_hw_events(cci_pmu);
struct perf_sample_data data;
struct pt_regs *regs;
int idx;
@@ -285,7 +286,7 @@ static void cci_pmu_enable_event(struct perf_event *event)
{
unsigned long flags;
struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
- struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct pmu_hw_events *events = cci_pmu->get_hw_events(cci_pmu);
struct hw_perf_event *hw_counter = &event->hw;
int idx = hw_counter->idx;
@@ -309,7 +310,7 @@ static void cci_pmu_disable_event(struct perf_event *event)
{
unsigned long flags;
struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
- struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct pmu_hw_events *events = cci_pmu->get_hw_events(cci_pmu);
struct hw_perf_event *hw_counter = &event->hw;
int idx = hw_counter->idx;
@@ -330,7 +331,7 @@ static void cci_pmu_start(struct arm_pmu *cci_pmu)
u32 val;
unsigned long flags;
struct cci_drvdata *info = platform_get_drvdata(cci_pmu->plat_device);
- struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct pmu_hw_events *events = cci_pmu->get_hw_events(cci_pmu);
raw_spin_lock_irqsave(&events->pmu_lock, flags);
@@ -346,7 +347,7 @@ static void cci_pmu_stop(struct arm_pmu *cci_pmu)
u32 val;
unsigned long flags;
struct cci_drvdata *info = platform_get_drvdata(cci_pmu->plat_device);
- struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct pmu_hw_events *events = cci_pmu->get_hw_events(cci_pmu);
raw_spin_lock_irqsave(&events->pmu_lock, flags);
diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c
index d3f8fb44cca..f35b70a0e68 100644
--- a/drivers/clk/versatile/clk-vexpress-spc.c
+++ b/drivers/clk/versatile/clk-vexpress-spc.c
@@ -102,7 +102,7 @@ struct clk *vexpress_clk_register_spc(const char *name, int cluster_id)
#if defined(CONFIG_OF)
void __init vexpress_clk_of_register_spc(void)
{
- char name[9] = "cluster";
+ char name[9] = "clusterX";
struct device_node *node = NULL;
struct clk *clk;
const u32 *val;
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 6ef9c7b0691..204812a5b92 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -2,6 +2,27 @@
# 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_BL_CPUFREQ_TEST
+ depends on ARM_BL_CPUFREQ
+ bool "Unit testing on cpufreq interface for the ARM big.LITTLE switcher"
+ help
+ Make tests on the cpufreq interface for the ARM big.LITTLE
+ switcher before loading it.
+
+ 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..bc633f2d7b0
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq.c
@@ -0,0 +1,270 @@
+/*
+ * 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 __module_pr_fmt(prefix, fmt) MODULE_NAME ": " prefix fmt
+#define pr_fmt(fmt) __module_pr_fmt("", 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>
+
+#include "arm-bl-cpufreq.h"
+
+/*
+ * Include tests prototypes and includes
+ * We need to include this file a second time with ARM_BL_CPUFREQ_DEFINE_TESTS
+ * defined to include functions body.
+ */
+#include "arm-bl-cpufreq_tests.c"
+
+#define ARM_BL_CPUFREQ_DEFINE_TESTS
+#include "arm-bl-cpufreq_tests.c"
+
+/* 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
+
+/* 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;
+ /*
+ * Publish frequency table so that it is available to governors
+ * and sysfs:
+ */
+ cpufreq_frequency_table_get_attr(bl_freqs, policy->cpu);
+
+ cluster = get_current_cluster(cpu);
+ per_cpu(cpu_cur_cluster, cpu) = cluster;
+
+ /*
+ * Ideally, transition_latency should be calibrated here.
+ */
+ policy->cpuinfo.transition_latency = BL_CPUFREQ_FAKE_LATENCY;
+ 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 freq_attr *bl_cpufreq_attrs[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL
+};
+
+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,
+ .attr = bl_cpufreq_attrs,
+ /* what else? */
+};
+
+static int __init bl_cpufreq_module_init(void)
+{
+ int err;
+
+ /* test_config :
+ * - 0: Do not run tests
+ * - 1: Run tests and then register cpufreq driver if tests passed
+ */
+ if ((test_config > 0) && (pre_init_tests() != 0))
+ return -EINVAL;
+
+ 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");
+
+ if ((test_config > 0) && (post_init_tests() != 0)) {
+ cpufreq_unregister_driver(&bl_cpufreq_driver);
+ return -EINVAL;
+ }
+ }
+
+ 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");
diff --git a/drivers/cpufreq/arm-bl-cpufreq.h b/drivers/cpufreq/arm-bl-cpufreq.h
new file mode 100644
index 00000000000..b13bb8c543d
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq.h
@@ -0,0 +1,37 @@
+#ifndef ARM_BL_CPUFREQ_H
+#define ARM_BL_CPUFREQ_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
+
+/*
+ * Switch latency advertised to cpufreq. This value is bogus and will
+ * need to be properly calibrated when running on real hardware.
+ */
+#define BL_CPUFREQ_FAKE_LATENCY 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);
+
+static unsigned int entry_to_freq(struct cpufreq_frequency_table const *entry);
+static unsigned int entry_to_cluster(
+ struct cpufreq_frequency_table const *entry);
+static struct cpufreq_frequency_table const *find_entry_by_cluster(int cluster);
+static unsigned int cluster_to_freq(int cluster);
+static int get_current_cluster(unsigned int cpu);
+static int get_current_cached_cluster(unsigned int cpu);
+static unsigned int get_current_freq(unsigned int cpu);
+static unsigned int bl_cpufreq_get(unsigned int cpu);
+
+#endif /* ! ARM_BL_CPUFREQ_H */
diff --git a/drivers/cpufreq/arm-bl-cpufreq_tests.c b/drivers/cpufreq/arm-bl-cpufreq_tests.c
new file mode 100644
index 00000000000..da349e165f4
--- /dev/null
+++ b/drivers/cpufreq/arm-bl-cpufreq_tests.c
@@ -0,0 +1,652 @@
+/*
+ * arm-bl-cpufreqtests.c: Unit tests on the 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.
+ */
+
+#ifndef ARM_BL_CPUFREQ_DEFINE_TESTS
+#include <linux/cpufreq.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include "arm-bl-cpufreq.h"
+
+static short int test_config;
+
+static int pre_init_tests(void);
+static int post_init_tests(void);
+
+#else /* ! ARM_BL_CPUFREQ_DEFINE_TESTS */
+
+#ifdef CONFIG_ARM_BL_CPUFREQ_TEST
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+#define pr_fmt(fmt) __module_pr_fmt("[test] ", fmt)
+
+#define SWITCH_DELAY 10
+#define SWITCH_TRANSITION_DELAY 200
+#define POST_INIT_TESTS_DELAY 100
+
+static DECLARE_WAIT_QUEUE_HEAD(test_wq);
+static int test_transition_count;
+unsigned int test_transition_freq;
+
+module_param(test_config, short, 1);
+MODULE_PARM_DESC(test_config, "Make tests before registering cpufreq driver. (0 : no tests, 1 : tests and registering driver (default))");
+
+static struct cpufreq_frequency_table const *get_other_entry(
+ struct cpufreq_frequency_table const *entry)
+{
+ if (entry_to_cluster(entry) == CLUSTER_BIG)
+ return find_entry_by_cluster(CLUSTER_LITTLE);
+ else
+ return find_entry_by_cluster(CLUSTER_BIG);
+}
+
+static int test_cpufreq_frequency_table(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ struct cpufreq_frequency_table const *entry;
+
+ /* Get big and little cpufreq_frequency_table entries and check
+ * entry_to_freq() and entry_to_cluster() return corresponding
+ * frequencies and cluster id.
+ */
+ entry = find_entry_by_cluster(CLUSTER_BIG);
+
+ ++nTest;
+ if (entry_to_freq(entry) != FREQ_BIG) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/frequency_table/%d:entry_to_freq(big) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ ++nTest;
+ if (entry_to_cluster(entry) != CLUSTER_BIG) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/frequency_table/%d:entry_to_cluster(big) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ entry = find_entry_by_cluster(CLUSTER_LITTLE);
+
+ ++nTest;
+ if (entry_to_freq(entry) != FREQ_LITTLE) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/frequency_table/%d:entry_to_freq(little) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ ++nTest;
+ if (entry_to_cluster(entry) != CLUSTER_LITTLE) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/frequency_table/%d:entry_to_cluster(little) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ pr_info("name=pre-init/frequency_table run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_cluster_to_freq(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+
+ /* Check if test_cluster_to_freq() result is consistent, ie :
+ * - CLUSTER_BIG => FREQ_BIG
+ * - CLUSTER_LITTLE => FREQ_LITTLE
+ */
+ ++nTest;
+ if (cluster_to_freq(CLUSTER_BIG) != FREQ_BIG) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/cluster_to_freq/%d:cluster_to_freq(big) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ ++nTest;
+ if (cluster_to_freq(CLUSTER_LITTLE) != FREQ_LITTLE) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/cluster_to_freq/%d:cluster_to_freq(little) result=%s\n",
+ nTest, (testResult ? "PASS" : "FAIL"));
+
+ pr_info("name=pre-init/cluster_to_freq run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_get_current_cluster(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cluster, cpu;
+
+ /* Check if get_current_cluster() return a consistent value, ie
+ * CLUSTER_BIG or CLUSTER_LITTLE
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ cluster = get_current_cluster(cpu);
+ ++nTest;
+ if ((cluster != CLUSTER_BIG) && (cluster != CLUSTER_LITTLE)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=pre-init/get_current_cluster/%d:get_current_cluster(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+ }
+
+ pr_info("name=pre-init/get_current_cluster run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_bl_cpufreq_get(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cpu;
+ struct cpufreq_frequency_table const *other_entry = NULL;
+ struct cpufreq_frequency_table const *origin_entry = NULL;
+ struct cpufreq_policy *policy = NULL;
+
+ /*
+ * Check bl_cpufreq_get() return value : for all cores value has to be
+ * the frequency of origin_entry
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ policy = cpufreq_cpu_get(cpu);
+ origin_entry = find_entry_by_cluster(get_current_cluster(cpu));
+ other_entry = get_other_entry(origin_entry);
+
+ ++nTest;
+ if (bl_cpufreq_get(cpu) != entry_to_freq(origin_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/bl_cpufreq_get/%d:origin(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch to "other" cluster, ie cluster not used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(other_entry),
+ CPUFREQ_RELATION_H);
+
+ ++nTest;
+ if (bl_cpufreq_get(cpu) != entry_to_freq(other_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/bl_cpufreq_get/%d:other(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch back to "origin" cluster, ie cluster used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(origin_entry),
+ CPUFREQ_RELATION_H);
+ cpufreq_cpu_put(policy);
+ }
+
+ pr_info("name=post-init/bl_cpufreq_get run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_get_current_freq(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cpu;
+ struct cpufreq_frequency_table const *other_entry = NULL;
+ struct cpufreq_frequency_table const *origin_entry = NULL;
+ struct cpufreq_policy *policy = NULL;
+
+ /*
+ * Check if get_current_freq() return a consistent value, ie
+ * FREQ_BIG while on big cluster and FREQ_LITTLE on little cluster
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ policy = cpufreq_cpu_get(cpu);
+ origin_entry = find_entry_by_cluster(get_current_cluster(cpu));
+ other_entry = get_other_entry(origin_entry);
+
+ ++nTest;
+ if (get_current_freq(cpu) != entry_to_freq(origin_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/get_current_freq/%d:origin(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch to "other" cluster, ie cluster not used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(other_entry),
+ CPUFREQ_RELATION_H);
+
+ ++nTest;
+ if (get_current_freq(cpu) != entry_to_freq(other_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/get_current_freq/%d:other(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch back to "origin" cluster, ie cluster used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(origin_entry),
+ CPUFREQ_RELATION_H);
+ cpufreq_cpu_put(policy);
+ }
+
+ pr_info("name=post-init/get_current_freq run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_get_current_cached_cluster(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cpu, cluster;
+ struct cpufreq_frequency_table const *other_entry = NULL;
+ struct cpufreq_frequency_table const *origin_entry = NULL;
+ struct cpufreq_policy *policy = NULL;
+
+ /*
+ * Check if get_current_cached_cluster() return a consistent value, ie
+ * CLUSTER_BIG while on big cluster and CLUSTER_LITTLE on little cluster
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ policy = cpufreq_cpu_get(cpu);
+ origin_entry = find_entry_by_cluster(get_current_cluster(cpu));
+ other_entry = get_other_entry(origin_entry);
+
+ ++nTest;
+ cluster = get_current_cached_cluster(cpu);
+ if (cluster != entry_to_cluster(origin_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/get_current_cached_cluster/%d:origin(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch to "other" cluster, ie cluster not used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(other_entry),
+ CPUFREQ_RELATION_H);
+
+ ++nTest;
+ cluster = get_current_cached_cluster(cpu);
+ if (cluster != entry_to_cluster(other_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/get_current_cached_cluster/%d:other(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /*
+ * Switch back to "origin" cluster, ie cluster used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(origin_entry),
+ CPUFREQ_RELATION_H);
+ cpufreq_cpu_put(policy);
+ }
+
+ pr_info("name=post-init/get_current_cached_cluster run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int test_cpufreq_driver_target(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cpu;
+ struct cpufreq_frequency_table const *other_entry = NULL;
+ struct cpufreq_frequency_table const *origin_entry = NULL;
+ struct cpufreq_policy *policy = NULL;
+
+ /*
+ * Try to switch between cluster and check if switch was performed with
+ * success
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ policy = cpufreq_cpu_get(cpu);
+ origin_entry = find_entry_by_cluster(get_current_cluster(cpu));
+ other_entry = get_other_entry(origin_entry);
+
+ /* Switch to "other" cluster, ie cluster not used at module
+ * loading time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(other_entry),
+ CPUFREQ_RELATION_H);
+
+ /*
+ * Give the hardware some time to switch between clusters
+ */
+ mdelay(SWITCH_DELAY);
+
+ ++nTest;
+ if (get_current_cluster(cpu) != entry_to_cluster(other_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/cpufreq_driver_target/%d:other(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /* Switch again to "other" cluster
+ */
+ cpufreq_driver_target(policy, entry_to_freq(other_entry),
+ CPUFREQ_RELATION_H);
+ /*
+ * Give the hardware some time to switch between clusters
+ */
+ mdelay(SWITCH_DELAY);
+
+ ++nTest;
+ if (get_current_cluster(cpu) != entry_to_cluster(other_entry)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/cpufreq_driver_target/%d:otherAgain(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /* Switch back to "origin" cluster, ie cluster used at module loading
+ * time
+ */
+ cpufreq_driver_target(policy, entry_to_freq(origin_entry),
+ CPUFREQ_RELATION_H);
+ /*
+ * Give the hardware some time to switch between clusters
+ */
+ mdelay(SWITCH_DELAY);
+
+ ++nTest;
+ if (get_current_cluster(cpu) != entry_to_cluster(origin_entry))
+ {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/cpufreq_driver_target/%d:origin(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /* Switch again to "origin" cluster
+ */
+ cpufreq_driver_target(policy, entry_to_freq(origin_entry),
+ CPUFREQ_RELATION_H);
+ /*
+ * Give the hardware some time to switch between clusters
+ */
+ mdelay(SWITCH_DELAY);
+
+ ++nTest;
+ if (get_current_cluster(cpu) != entry_to_cluster(origin_entry))
+ {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/cpufreq_driver_target/%d:originAgain(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ cpufreq_cpu_put(policy);
+ }
+
+ pr_info("name=post-init/cpufreq_driver_target run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+/* Check that new frequency is expected frequency, increment count and wake up
+ * test function.
+ */
+static int test_arm_bl_cpufreq_notifier(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct cpufreq_freqs *freq = data;
+
+ if (freq->new != test_transition_freq)
+ test_transition_freq = -1;
+
+ ++test_transition_count;
+
+ wake_up(&test_wq);
+
+ return 0;
+}
+static struct notifier_block test_arm_bl_cpufreq_notifier_block = {
+ .notifier_call = test_arm_bl_cpufreq_notifier
+};
+
+static int test_transitions(void)
+{
+ int nTest = 0, failCount = 0, testResult = 0;
+ unsigned int cpu, origin_freq, other_freq;
+ struct cpufreq_frequency_table const *other_entry = NULL;
+ struct cpufreq_frequency_table const *origin_entry = NULL;
+ struct cpufreq_policy *policy = NULL;
+
+ /*
+ * register test_arm_bl_cpufreq_notifier_block as notifier :
+ * test_arm_bl_cpufreq_notifier_block will be called on cluster
+ * change and increment transition_count
+ */
+ cpufreq_register_notifier(&test_arm_bl_cpufreq_notifier_block,
+ CPUFREQ_TRANSITION_NOTIFIER);
+
+ /*
+ * Switch between cluster and check if notifications are received
+ */
+ for_each_cpu(cpu, cpu_present_mask) {
+ policy = cpufreq_cpu_get(cpu);
+ origin_entry = find_entry_by_cluster(get_current_cluster(cpu));
+ other_entry = get_other_entry(origin_entry);
+ origin_freq = entry_to_freq(origin_entry);
+ other_freq = entry_to_freq(other_entry);
+
+ /* Switch on little cluster and check notification
+ */
+ ++nTest;
+ test_transition_count = 0;
+ test_transition_freq = other_freq;
+ cpufreq_driver_target(policy, other_freq, CPUFREQ_RELATION_H);
+ wait_event_timeout(test_wq, (test_transition_count == 2),
+ msecs_to_jiffies(SWITCH_TRANSITION_DELAY));
+
+ if ((test_transition_count != 2)
+ || (test_transition_freq != other_freq)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/transitions/%d:other(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ /* Switch on big cluster and check notification
+ */
+ ++nTest;
+ test_transition_count = 0;
+ test_transition_freq = origin_freq;
+ cpufreq_driver_target(policy, origin_freq, CPUFREQ_RELATION_H);
+ wait_event_timeout(test_wq, (test_transition_count == 2),
+ msecs_to_jiffies(SWITCH_TRANSITION_DELAY));
+
+ if ((test_transition_count != 2)
+ || (test_transition_freq != origin_freq)) {
+ testResult = 0;
+ ++failCount;
+ } else
+ testResult = 1;
+ pr_info("name=post-init/transitions/%d:origin(%u) result=%s\n",
+ nTest, cpu, (testResult ? "PASS" : "FAIL"));
+
+ cpufreq_cpu_put(policy);
+ }
+
+ cpufreq_unregister_notifier(&test_arm_bl_cpufreq_notifier_block,
+ CPUFREQ_TRANSITION_NOTIFIER);
+
+ pr_info("name=post-init/transitions run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int pre_init_tests(void)
+{
+ int nTest = 0, failCount = 0;
+
+ pr_info("Begin pre-init tests");
+
+ ++nTest;
+ if (test_cpufreq_frequency_table() < 0)
+ ++failCount;
+
+ ++nTest;
+ if (test_cluster_to_freq() < 0)
+ ++failCount;
+
+ ++nTest;
+ if (test_get_current_cluster() < 0)
+ ++failCount;
+
+ pr_info("name=pre-init run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+static int post_init_tests(void)
+{
+ /*
+ * Run all post-init tests
+ *
+ * We wait POST_INIT_TESTS_DELAY ms between tests to be sure system is
+ * in a stable state before running a new test.
+ */
+ int nTest = 0, failCount = 0;
+
+
+ mdelay(POST_INIT_TESTS_DELAY);
+ ++nTest;
+ if (test_cpufreq_driver_target() < 0)
+ ++failCount;
+
+ mdelay(POST_INIT_TESTS_DELAY);
+ ++nTest;
+ if (test_transitions() < 0)
+ ++failCount;
+
+ mdelay(POST_INIT_TESTS_DELAY);
+ ++nTest;
+ if (test_get_current_freq() < 0)
+ ++failCount;
+
+ mdelay(POST_INIT_TESTS_DELAY);
+ ++nTest;
+ if (test_bl_cpufreq_get() < 0)
+ ++failCount;
+
+ mdelay(POST_INIT_TESTS_DELAY);
+ ++nTest;
+ if (test_get_current_cached_cluster() < 0)
+ ++failCount;
+
+ pr_info("name=post-init run=%d result=%s pass=%d fail=%d\n",
+ nTest, (failCount == 0 ? "PASS" : "FAIL"),
+ (nTest - failCount), failCount);
+ if (failCount != 0)
+ return -1;
+
+ return 0;
+}
+
+#undef pr_fmt
+#define pr_fmt(fmt) __module_pr_fmt("", fmt)
+#else /* ! CONFIG_ARM_BL_CPUFREQ_TEST */
+
+static int pre_init_tests(void) { return 0; }
+static int post_init_tests(void) { return 0; }
+
+#endif /* CONFIG_ARM_BL_CPUFREQ_TEST */
+#endif /* ARM_BL_CPUFREQ_DEFINE_TESTS */
diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c
index b5601fcd79e..7858957b9e6 100644
--- a/drivers/cpufreq/arm_big_little.c
+++ b/drivers/cpufreq/arm_big_little.c
@@ -23,18 +23,43 @@
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
#include <linux/export.h>
+#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <asm/bL_switcher.h>
#include <asm/topology.h>
#include "arm_big_little.h"
+#ifdef CONFIG_BL_SWITCHER
+static bool bL_switching_enabled;
+#define is_bL_switching_enabled() bL_switching_enabled
+#define set_switching_enabled(x) (bL_switching_enabled = (x))
+#else
+#define is_bL_switching_enabled() false
+#define set_switching_enabled(x) do { } while (0)
+#endif
+
+#define A15_CLUSTER 0
+#define A7_CLUSTER 1
#define MAX_CLUSTERS 2
+#define ACTUAL_FREQ(cluster, freq) ((cluster == A7_CLUSTER) ? freq << 1 : freq)
+#define VIRT_FREQ(cluster, freq) ((cluster == A7_CLUSTER) ? freq >> 1 : freq)
+
static struct cpufreq_arm_bL_ops *arm_bL_ops;
static struct clk *clk[MAX_CLUSTERS];
-static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS];
-static atomic_t cluster_usage[MAX_CLUSTERS] = {ATOMIC_INIT(0), ATOMIC_INIT(0)};
+static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS + 1];
+static int freq_table_cnt[MAX_CLUSTERS];
+static atomic_t cluster_usage[MAX_CLUSTERS + 1] = {ATOMIC_INIT(0), ATOMIC_INIT(0)};
+
+static unsigned int clk_big_min; /* (Big) clock frequencies */
+static unsigned int clk_little_max; /* Maximum clock frequency (Little) */
+
+static DEFINE_PER_CPU(unsigned int, physical_cluster);
+static DEFINE_PER_CPU(unsigned int, cpu_last_req_freq);
+
+static struct mutex cluster_lock[MAX_CLUSTERS];
/*
* Functions to get the current status.
@@ -43,14 +68,113 @@ static atomic_t cluster_usage[MAX_CLUSTERS] = {ATOMIC_INIT(0), ATOMIC_INIT(0)};
*/
static int cpu_to_cluster(int cpu)
{
- return topology_physical_package_id(cpu);
+ return is_bL_switching_enabled() ? MAX_CLUSTERS:
+ topology_physical_package_id(cpu);
}
-static unsigned int bL_cpufreq_get(unsigned int cpu)
+static unsigned int find_cluster_maxfreq(int cluster)
{
- u32 cur_cluster = cpu_to_cluster(cpu);
+ int j;
+ u32 max_freq = 0, cpu_freq;
+
+ for_each_online_cpu(j) {
+ cpu_freq = per_cpu(cpu_last_req_freq, j);
+
+ if ((cluster == per_cpu(physical_cluster, j)) &&
+ (max_freq < cpu_freq))
+ max_freq = cpu_freq;
+ }
+
+ pr_debug("%s: cluster: %d, max freq: %d\n", __func__, cluster,
+ max_freq);
+
+ return max_freq;
+}
+
+static unsigned int clk_get_cpu_rate(unsigned int cpu)
+{
+ u32 cur_cluster = per_cpu(physical_cluster, cpu);
+ u32 rate = clk_get_rate(clk[cur_cluster]) / 1000;
+
+ /* For switcher we use virtual A15 clock rates */
+ if (is_bL_switching_enabled())
+ rate = VIRT_FREQ(cur_cluster, rate);
+
+ pr_debug("%s: cpu: %d, cluster: %d, freq: %u\n", __func__, cpu,
+ cur_cluster, rate);
+
+ return rate;
+}
+
+static unsigned int bL_cpufreq_get_rate(unsigned int cpu)
+{
+ pr_debug("%s: freq: %d\n", __func__, per_cpu(cpu_last_req_freq, cpu));
+
+ return per_cpu(cpu_last_req_freq, cpu);
+}
+
+static unsigned int
+bL_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate)
+{
+ u32 new_rate, prev_rate;
+ int ret;
+
+ mutex_lock(&cluster_lock[new_cluster]);
+
+ prev_rate = per_cpu(cpu_last_req_freq, cpu);
+ per_cpu(cpu_last_req_freq, cpu) = rate;
+ per_cpu(physical_cluster, cpu) = new_cluster;
+
+ if (is_bL_switching_enabled()) {
+ new_rate = find_cluster_maxfreq(new_cluster);
+ new_rate = ACTUAL_FREQ(new_cluster, new_rate);
+ } else {
+ new_rate = rate;
+ }
+
+ pr_debug("%s: cpu: %d, old cluster: %d, new cluster: %d, freq: %d\n",
+ __func__, cpu, old_cluster, new_cluster, new_rate);
- return clk_get_rate(clk[cur_cluster]) / 1000;
+ ret = clk_set_rate(clk[new_cluster], new_rate * 1000);
+ if (WARN_ON(ret)) {
+ pr_err("clk_set_rate failed: %d, new cluster: %d\n", ret,
+ new_cluster);
+ per_cpu(cpu_last_req_freq, cpu) = prev_rate;
+ per_cpu(physical_cluster, cpu) = old_cluster;
+
+ mutex_unlock(&cluster_lock[new_cluster]);
+
+ return ret;
+ }
+
+ mutex_unlock(&cluster_lock[new_cluster]);
+
+ /* Recalc freq for old cluster when switching clusters */
+ if (old_cluster != new_cluster) {
+ pr_debug("%s: cpu: %d, old cluster: %d, new cluster: %d\n",
+ __func__, cpu, old_cluster, new_cluster);
+
+ /* Switch cluster */
+ bL_switch_request(cpu, new_cluster);
+
+ mutex_lock(&cluster_lock[old_cluster]);
+
+ /* Set freq of old cluster if there are cpus left on it */
+ new_rate = find_cluster_maxfreq(old_cluster);
+ new_rate = ACTUAL_FREQ(old_cluster, new_rate);
+
+ if (new_rate) {
+ pr_debug("%s: Updating rate of old cluster: %d, to freq: %d\n",
+ __func__, old_cluster, new_rate);
+
+ if (clk_set_rate(clk[old_cluster], new_rate * 1000))
+ pr_err("%s: clk_set_rate failed: %d, old cluster: %d\n",
+ __func__, ret, old_cluster);
+ }
+ mutex_unlock(&cluster_lock[old_cluster]);
+ }
+
+ return 0;
}
/* Validate policy frequency range */
@@ -67,21 +191,22 @@ static int bL_cpufreq_set_target(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation)
{
struct cpufreq_freqs freqs;
- u32 cpu = policy->cpu, freq_tab_idx, cur_cluster;
+ u32 cpu = policy->cpu, freq_tab_idx, cur_cluster, new_cluster,
+ actual_cluster;
int ret = 0;
/* ASSUMPTION: The cpu can't be hotplugged in this function */
- cur_cluster = cpu_to_cluster(policy->cpu);
+ cur_cluster = cpu_to_cluster(cpu);
+ new_cluster = actual_cluster = per_cpu(physical_cluster, cpu);
- freqs.old = bL_cpufreq_get(policy->cpu);
+ freqs.cpu = cpu;
+ freqs.old = bL_cpufreq_get_rate(cpu);
/* Determine valid target frequency using freq_table */
cpufreq_frequency_table_target(policy, freq_table[cur_cluster],
target_freq, relation, &freq_tab_idx);
freqs.new = freq_table[cur_cluster][freq_tab_idx].frequency;
- freqs.cpu = policy->cpu;
-
pr_debug("%s: cpu: %d, cluster: %d, oldfreq: %d, target freq: %d, new freq: %d\n",
__func__, cpu, cur_cluster, freqs.old, target_freq,
freqs.new);
@@ -89,14 +214,22 @@ static int bL_cpufreq_set_target(struct cpufreq_policy *policy,
if (freqs.old == freqs.new)
return 0;
+ if (is_bL_switching_enabled()) {
+ if ((actual_cluster == A15_CLUSTER) &&
+ (freqs.new < clk_big_min)) {
+ new_cluster = A7_CLUSTER;
+ } else if ((actual_cluster == A7_CLUSTER) &&
+ (freqs.new > clk_little_max)) {
+ new_cluster = A15_CLUSTER;
+ }
+ }
+
for_each_cpu(freqs.cpu, policy->cpus)
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
- ret = clk_set_rate(clk[cur_cluster], freqs.new * 1000);
- if (ret) {
- pr_err("clk_set_rate failed: %d\n", ret);
+ ret = bL_cpufreq_set_rate(cpu, actual_cluster, new_cluster, freqs.new);
+ if (ret)
return ret;
- }
policy->cur = freqs.new;
@@ -106,6 +239,28 @@ static int bL_cpufreq_set_target(struct cpufreq_policy *policy,
return ret;
}
+/* get the minimum frequency in the cpufreq_frequency_table */
+static inline u32 get_table_min(struct cpufreq_frequency_table *table)
+{
+ int i;
+ uint32_t min_freq = ~0;
+ for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++)
+ if (table[i].frequency < min_freq)
+ min_freq = table[i].frequency;
+ return min_freq;
+}
+
+/* get the maximum frequency in the cpufreq_frequency_table */
+static inline u32 get_table_max(struct cpufreq_frequency_table *table)
+{
+ int i;
+ uint32_t max_freq = 0;
+ for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++)
+ if (table[i].frequency > max_freq)
+ max_freq = table[i].frequency;
+ return max_freq;
+}
+
/* translate the integer array into cpufreq_frequency_table entries */
struct cpufreq_frequency_table *
arm_bL_copy_table_from_array(unsigned int *table, int count)
@@ -141,7 +296,41 @@ void arm_bL_free_freq_table(u32 cluster)
}
EXPORT_SYMBOL_GPL(arm_bL_free_freq_table);
-static void put_cluster_clk_and_freq_table(u32 cluster)
+static int merge_cluster_tables(void)
+{
+ int i, j, k = 0, count = 1;
+ struct cpufreq_frequency_table *table;
+
+ for (i = 0; i < MAX_CLUSTERS; i++)
+ count += freq_table_cnt[i];
+
+ table = kzalloc(sizeof(*table) * count, GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+
+ freq_table[MAX_CLUSTERS] = table;
+
+ /* Add in reverse order to get freqs in increasing order */
+ for (i = MAX_CLUSTERS - 1; i >= 0; i--) {
+ for (j = 0; j < freq_table_cnt[i]; j++) {
+ table[k].index = k;
+ table[k].frequency = VIRT_FREQ(i,
+ freq_table[i][j].frequency);
+ pr_debug("%s: index: %d, freq: %d\n", __func__, k,
+ table[k].frequency);
+ k++;
+ }
+ }
+
+ table[k].index = k;
+ table[k].frequency = CPUFREQ_TABLE_END;
+
+ pr_debug("%s: End, table: %p, count: %d\n", __func__, table, k);
+
+ return 0;
+}
+
+static void _put_cluster_clk_and_freq_table(u32 cluster)
{
if (!atomic_dec_return(&cluster_usage[cluster])) {
clk_put(clk[cluster]);
@@ -152,9 +341,26 @@ static void put_cluster_clk_and_freq_table(u32 cluster)
}
}
-static int get_cluster_clk_and_freq_table(u32 cluster)
+static void put_cluster_clk_and_freq_table(u32 cluster)
{
- char name[9] = "cluster";
+ int i;
+
+ if (cluster < MAX_CLUSTERS)
+ return _put_cluster_clk_and_freq_table(cluster);
+
+ if (atomic_dec_return(&cluster_usage[MAX_CLUSTERS]))
+ return;
+
+ for (i = 0; i < MAX_CLUSTERS; i++)
+ _put_cluster_clk_and_freq_table(i);
+
+ /* free virtual table */
+ arm_bL_free_freq_table(MAX_CLUSTERS);
+}
+
+static int _get_cluster_clk_and_freq_table(u32 cluster)
+{
+ char name[9] = "clusterX";
int count;
if (atomic_inc_return(&cluster_usage[cluster]) != 1)
@@ -164,6 +370,8 @@ static int get_cluster_clk_and_freq_table(u32 cluster)
if (!freq_table[cluster])
goto atomic_dec;
+ freq_table_cnt[cluster] = count;
+
name[7] = cluster + '0';
clk[cluster] = clk_get(NULL, name);
if (!IS_ERR_OR_NULL(clk[cluster])) {
@@ -181,6 +389,48 @@ atomic_dec:
return -ENODATA;
}
+static int get_cluster_clk_and_freq_table(u32 cluster)
+{
+ int i, ret;
+
+ if (cluster < MAX_CLUSTERS)
+ return _get_cluster_clk_and_freq_table(cluster);
+
+ if (atomic_inc_return(&cluster_usage[MAX_CLUSTERS]) != 1)
+ return 0;
+
+ /*
+ * Get data for all clusters and fill virtual cluster with a merge of
+ * both
+ */
+ for (i = 0; i < MAX_CLUSTERS; i++) {
+ ret = _get_cluster_clk_and_freq_table(i);
+ if (ret)
+ goto put_clusters;
+ }
+
+ ret = merge_cluster_tables();
+ if (ret)
+ goto put_clusters;
+
+ /* Assuming 2 cluster, set clk_big_min and clk_little_max */
+ clk_big_min = get_table_min(freq_table[0]);
+ clk_little_max = VIRT_FREQ(1, get_table_max(freq_table[1]));
+
+ pr_debug("%s: cluster: %d, clk_big_min: %d, clk_little_max: %d\n",
+ __func__, cluster, clk_big_min, clk_little_max);
+
+ return 0;
+
+put_clusters:
+ while (i)
+ _put_cluster_clk_and_freq_table(--i);
+
+ atomic_dec(&cluster_usage[MAX_CLUSTERS]);
+
+ return ret;
+}
+
/* Per-CPU initialization */
static int bL_cpufreq_init(struct cpufreq_policy *policy)
{
@@ -202,20 +452,22 @@ static int bL_cpufreq_init(struct cpufreq_policy *policy)
cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu);
- policy->cpuinfo.transition_latency = 1000000; /* 1 ms assumed */
- policy->cur = bL_cpufreq_get(policy->cpu);
+ if (cur_cluster < MAX_CLUSTERS) {
+ cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
+ cpumask_copy(policy->related_cpus, policy->cpus);
- cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
- cpumask_copy(policy->related_cpus, policy->cpus);
+ per_cpu(physical_cluster, policy->cpu) = cur_cluster;
+ } else {
+ /* Assumption: during init, we are always running on A15 */
+ per_cpu(physical_cluster, policy->cpu) = A15_CLUSTER;
+ }
- pr_info("CPU %d initialized\n", policy->cpu);
- return 0;
-}
+ policy->cpuinfo.transition_latency = 1000000; /* 1 ms assumed */
+ policy->cur = clk_get_cpu_rate(policy->cpu);
+ per_cpu(cpu_last_req_freq, policy->cpu) = policy->cur;
-static int bL_cpufreq_exit(struct cpufreq_policy *policy)
-{
- put_cluster_clk_and_freq_table(cpu_to_cluster(policy->cpu));
- pr_debug("%s: Exited, cpu: %d\n", __func__, policy->cpu);
+ pr_info("%s: Initialized, cpu: %d, cluster %d\n", __func__,
+ policy->cpu, cur_cluster);
return 0;
}
@@ -231,15 +483,46 @@ static struct cpufreq_driver bL_cpufreq_driver = {
.flags = CPUFREQ_STICKY,
.verify = bL_cpufreq_verify_policy,
.target = bL_cpufreq_set_target,
- .get = bL_cpufreq_get,
+ .get = bL_cpufreq_get_rate,
.init = bL_cpufreq_init,
- .exit = bL_cpufreq_exit,
.attr = bL_cpufreq_attr,
};
+static int bL_cpufreq_switcher_notifier(struct notifier_block *nfb,
+ unsigned long action, void *_arg)
+{
+ pr_debug("%s: action: %ld\n", __func__, action);
+
+ switch (action) {
+ case BL_NOTIFY_PRE_ENABLE:
+ case BL_NOTIFY_PRE_DISABLE:
+ cpufreq_unregister_driver(&bL_cpufreq_driver);
+ break;
+
+ case BL_NOTIFY_POST_ENABLE:
+ set_switching_enabled(true);
+ cpufreq_register_driver(&bL_cpufreq_driver);
+ break;
+
+ case BL_NOTIFY_POST_DISABLE:
+ set_switching_enabled(false);
+ cpufreq_register_driver(&bL_cpufreq_driver);
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block bL_switcher_notifier = {
+ .notifier_call = bL_cpufreq_switcher_notifier,
+};
+
int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops)
{
- int ret;
+ int ret, i;
if (arm_bL_ops) {
pr_debug("%s: Already registered: %s, exiting\n", __func__,
@@ -254,16 +537,29 @@ int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops)
arm_bL_ops = ops;
+ ret = bL_switcher_get_enabled();
+ set_switching_enabled(ret);
+
+ for (i = 0; i < MAX_CLUSTERS; i++)
+ mutex_init(&cluster_lock[i]);
+
ret = cpufreq_register_driver(&bL_cpufreq_driver);
if (ret) {
pr_info("%s: Failed registering platform driver: %s, err: %d\n",
__func__, ops->name, ret);
arm_bL_ops = NULL;
} else {
- pr_info("%s: Registered platform driver: %s\n", __func__,
- ops->name);
+ ret = bL_switcher_register_notifier(&bL_switcher_notifier);
+ if (ret) {
+ cpufreq_unregister_driver(&bL_cpufreq_driver);
+ arm_bL_ops = NULL;
+ } else {
+ pr_info("%s: Registered platform driver: %s\n",
+ __func__, ops->name);
+ }
}
+ bL_switcher_put_enabled();
return ret;
}
EXPORT_SYMBOL_GPL(bL_cpufreq_register);
@@ -275,9 +571,23 @@ void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops)
__func__, arm_bL_ops->name);
}
+ bL_switcher_get_enabled();
+ bL_switcher_unregister_notifier(&bL_switcher_notifier);
cpufreq_unregister_driver(&bL_cpufreq_driver);
+ bL_switcher_put_enabled();
pr_info("%s: Un-registered platform driver: %s\n", __func__,
arm_bL_ops->name);
+
+ /* For saving table get/put on every cpu in/out */
+ if (is_bL_switching_enabled()) {
+ put_cluster_clk_and_freq_table(MAX_CLUSTERS);
+ } else {
+ int i;
+
+ for (i = 0; i < MAX_CLUSTERS; i++)
+ put_cluster_clk_and_freq_table(i);
+ }
+
arm_bL_ops = NULL;
}
EXPORT_SYMBOL_GPL(bL_cpufreq_unregister);
diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c
index bfd6273fd87..1db214b2660 100644
--- a/drivers/cpufreq/cpufreq_stats.c
+++ b/drivers/cpufreq/cpufreq_stats.c
@@ -21,6 +21,7 @@
#include <linux/spinlock.h>
#include <linux/notifier.h>
#include <asm/cputime.h>
+#include <asm/bL_switcher.h>
static spinlock_t cpufreq_stats_lock;
@@ -378,7 +379,7 @@ static struct notifier_block notifier_trans_block = {
.notifier_call = cpufreq_stat_notifier_trans
};
-static int __init cpufreq_stats_init(void)
+static int cpufreq_stats_setup(void)
{
int ret;
unsigned int cpu;
@@ -406,7 +407,8 @@ static int __init cpufreq_stats_init(void)
return 0;
}
-static void __exit cpufreq_stats_exit(void)
+
+static void cpufreq_stats_cleanup(void)
{
unsigned int cpu;
@@ -421,6 +423,49 @@ static void __exit cpufreq_stats_exit(void)
}
}
+static int cpufreq_stats_switcher_notifier(struct notifier_block *nfb,
+ unsigned long action, void *_arg)
+{
+ switch (action) {
+ case BL_NOTIFY_PRE_ENABLE:
+ case BL_NOTIFY_PRE_DISABLE:
+ cpufreq_stats_cleanup();
+ break;
+
+ case BL_NOTIFY_POST_ENABLE:
+ case BL_NOTIFY_POST_DISABLE:
+ cpufreq_stats_setup();
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block switcher_notifier = {
+ .notifier_call = cpufreq_stats_switcher_notifier,
+};
+
+static int __init cpufreq_stats_init(void)
+{
+ int ret;
+ spin_lock_init(&cpufreq_stats_lock);
+
+ ret = cpufreq_stats_setup();
+ if (!ret)
+ bL_switcher_register_notifier(&switcher_notifier);
+
+ return ret;
+}
+
+static void __exit cpufreq_stats_exit(void)
+{
+ bL_switcher_unregister_notifier(&switcher_notifier);
+ cpufreq_stats_cleanup();
+}
+
MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>");
MODULE_DESCRIPTION("'cpufreq_stats' - A driver to export cpufreq stats "
"through sysfs filesystem");