diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/bus/arm-cci.c | 13 | ||||
-rw-r--r-- | drivers/clk/versatile/clk-vexpress-spc.c | 2 | ||||
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 21 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 3 | ||||
-rw-r--r-- | drivers/cpufreq/arm-bl-cpufreq.c | 270 | ||||
-rw-r--r-- | drivers/cpufreq/arm-bl-cpufreq.h | 37 | ||||
-rw-r--r-- | drivers/cpufreq/arm-bl-cpufreq_tests.c | 652 | ||||
-rw-r--r-- | drivers/cpufreq/arm_big_little.c | 378 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq_stats.c | 49 |
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"); |