diff options
Diffstat (limited to 'drivers/cpufreq')
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 42 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 8 | ||||
-rw-r--r-- | drivers/cpufreq/acpi-cpufreq.c | 4 | ||||
-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 | 593 | ||||
-rw-r--r-- | drivers/cpufreq/arm_big_little.h | 38 | ||||
-rw-r--r-- | drivers/cpufreq/arm_dt_big_little.c | 101 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq_stats.c | 49 | ||||
-rw-r--r-- | drivers/cpufreq/intel_pstate.c | 67 | ||||
-rw-r--r-- | drivers/cpufreq/vexpress_big_little.c | 74 |
12 files changed, 1882 insertions, 53 deletions
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 030ddf6dd3f..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 @@ -113,3 +134,24 @@ config ARM_HIGHBANK_CPUFREQ based boards. If in doubt, say N. + +config ARM_BIG_LITTLE_CPUFREQ + tristate + depends on ARM_CPU_TOPOLOGY + +config ARM_DT_BL_CPUFREQ + tristate "Generic ARM big LITTLE CPUfreq driver probed via DT" + select ARM_BIG_LITTLE_CPUFREQ + depends on OF && BIG_LITTLE + default y + help + This enables the Generic CPUfreq driver for ARM big.LITTLE platform. + This gets frequency tables from DT. + +config ARM_VEXPRESS_BL_CPUFREQ + tristate "ARM Vexpress big LITTLE CPUfreq driver" + select ARM_BIG_LITTLE_CPUFREQ + depends on ARM_SPC && BIG_LITTLE + help + This enables the CPUfreq driver for ARM Vexpress big.LITTLE platform. + If in doubt, say N. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index b27cc1cc592..3f0443fcf24 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -45,7 +45,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 @@ -58,6 +59,11 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o +obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) += arm_big_little.o +obj-$(CONFIG_ARM_VEXPRESS_BL_CPUFREQ) += vexpress_big_little.o +#Keep DT_BL_CPUFREQ as the last entry in all big LITTLE drivers, so that it is +#probed last. +obj-$(CONFIG_ARM_DT_BL_CPUFREQ) += arm_dt_big_little.o ################################################################################## # PowerPC platform drivers diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 57a8774f0b4..bb5939bbef5 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -347,11 +347,11 @@ static u32 get_cur_val(const struct cpumask *mask) switch (per_cpu(acfreq_data, cpumask_first(mask))->cpu_feature) { case SYSTEM_INTEL_MSR_CAPABLE: cmd.type = SYSTEM_INTEL_MSR_CAPABLE; - cmd.addr.msr.reg = MSR_IA32_PERF_STATUS; + cmd.addr.msr.reg = MSR_IA32_PERF_CTL; break; case SYSTEM_AMD_MSR_CAPABLE: cmd.type = SYSTEM_AMD_MSR_CAPABLE; - cmd.addr.msr.reg = MSR_AMD_PERF_STATUS; + cmd.addr.msr.reg = MSR_AMD_PERF_CTL; break; case SYSTEM_IO_CAPABLE: cmd.type = SYSTEM_IO_CAPABLE; 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 new file mode 100644 index 00000000000..7858957b9e6 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.c @@ -0,0 +1,593 @@ +/* + * ARM big.LITTLE Platforms CPUFreq support + * + * Copyright (C) 2012 ARM Ltd. + * Author: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * + * Copyright (C) 2012 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk.h> +#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 + 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. + * + * Beware that the cluster for another CPU may change unexpectedly. + */ +static int cpu_to_cluster(int cpu) +{ + return is_bL_switching_enabled() ? MAX_CLUSTERS: + topology_physical_package_id(cpu); +} + +static unsigned int find_cluster_maxfreq(int cluster) +{ + 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); + + 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 */ +static int bL_cpufreq_verify_policy(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + /* This call takes care of it all using freq_table */ + return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]); +} + +/* Set clock frequency */ +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, new_cluster, + actual_cluster; + int ret = 0; + + /* ASSUMPTION: The cpu can't be hotplugged in this function */ + cur_cluster = cpu_to_cluster(cpu); + new_cluster = actual_cluster = per_cpu(physical_cluster, 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; + + 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); + + 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 = bL_cpufreq_set_rate(cpu, actual_cluster, new_cluster, freqs.new); + if (ret) + return ret; + + policy->cur = freqs.new; + + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + 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) +{ + int i; + + struct cpufreq_frequency_table *freq_table; + + pr_debug("%s: table: %p, count: %d\n", __func__, table, count); + + freq_table = kmalloc(sizeof(*freq_table) * (count + 1), GFP_KERNEL); + if (!freq_table) + return NULL; + + for (i = 0; i < count; i++) { + pr_debug("%s: index: %d, freq: %d\n", __func__, i, table[i]); + freq_table[i].index = i; + freq_table[i].frequency = table[i]; /* in kHZ */ + } + + freq_table[i].index = count; + freq_table[i].frequency = CPUFREQ_TABLE_END; + + return freq_table; +} +EXPORT_SYMBOL_GPL(arm_bL_copy_table_from_array); + +void arm_bL_free_freq_table(u32 cluster) +{ + pr_debug("%s: free freq table\n", __func__); + + kfree(freq_table[cluster]); +} +EXPORT_SYMBOL_GPL(arm_bL_free_freq_table); + +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]); + clk[cluster] = NULL; + arm_bL_ops->put_freq_tbl(cluster); + freq_table[cluster] = NULL; + pr_debug("%s: cluster: %d\n", __func__, cluster); + } +} + +static void put_cluster_clk_and_freq_table(u32 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) + return 0; + + freq_table[cluster] = arm_bL_ops->get_freq_tbl(cluster, &count); + 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])) { + pr_debug("%s: clk: %p & freq table: %p, cluster: %d\n", + __func__, clk[cluster], freq_table[cluster], + cluster); + return 0; + } + + arm_bL_ops->put_freq_tbl(cluster); + +atomic_dec: + atomic_dec(&cluster_usage[cluster]); + pr_err("%s: Failed to get data for cluster: %d\n", __func__, cluster); + 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) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + int result; + + result = get_cluster_clk_and_freq_table(cur_cluster); + if (result) + return result; + + result = cpufreq_frequency_table_cpuinfo(policy, + freq_table[cur_cluster]); + if (result) { + pr_err("CPU %d, cluster: %d invalid freq table\n", policy->cpu, + cur_cluster); + put_cluster_clk_and_freq_table(cur_cluster); + return result; + } + + cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu); + + if (cur_cluster < MAX_CLUSTERS) { + 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; + } + + 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; + + pr_info("%s: Initialized, cpu: %d, cluster %d\n", __func__, + policy->cpu, cur_cluster); + + return 0; +} + +/* Export freq_table to sysfs */ +static struct freq_attr *bL_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver bL_cpufreq_driver = { + .name = "arm-big-little", + .flags = CPUFREQ_STICKY, + .verify = bL_cpufreq_verify_policy, + .target = bL_cpufreq_set_target, + .get = bL_cpufreq_get_rate, + .init = bL_cpufreq_init, + .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, i; + + if (arm_bL_ops) { + pr_debug("%s: Already registered: %s, exiting\n", __func__, + arm_bL_ops->name); + return -EBUSY; + } + + if (!ops || !strlen(ops->name) || !ops->get_freq_tbl) { + pr_err("%s: Invalid arm_bL_ops, exiting\n", __func__); + return -ENODEV; + } + + 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 { + 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); + +void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops) +{ + if (arm_bL_ops != ops) { + pr_info("%s: Registered with: %s, can't unregister, exiting\n", + __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/arm_big_little.h b/drivers/cpufreq/arm_big_little.h new file mode 100644 index 00000000000..6712a501198 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.h @@ -0,0 +1,38 @@ +/* + * ARM big.LITTLE platform's CPUFreq header file + * + * Copyright (C) 2012 ARM Ltd. + * Author: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * + * Copyright (C) 2012 ARM Ltd. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef CPUFREQ_ARM_BIG_LITTLE_H +#define CPUFREQ_ARM_BIG_LITTLE_H + +#include <linux/cpufreq.h> +#include <linux/types.h> + +struct cpufreq_arm_bL_ops { + char name[CPUFREQ_NAME_LEN]; + struct cpufreq_frequency_table *(*get_freq_tbl)(u32 cluster, int *count); + void (*put_freq_tbl)(u32 cluster); +}; + +struct cpufreq_frequency_table * +arm_bL_copy_table_from_array(unsigned int *table, int count); +void arm_bL_free_freq_table(u32 cluster); + +int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops); +void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops); + +#endif /* CPUFREQ_ARM_BIG_LITTLE_H */ diff --git a/drivers/cpufreq/arm_dt_big_little.c b/drivers/cpufreq/arm_dt_big_little.c new file mode 100644 index 00000000000..fabfb9c5c37 --- /dev/null +++ b/drivers/cpufreq/arm_dt_big_little.c @@ -0,0 +1,101 @@ +/* + * Generic big.LITTLE CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver and gets + * Frequency information from Device Tree. Freq table in DT must be in KHz. + * + * Copyright (C) 2012 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/cpufreq.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/types.h> +#include "arm_big_little.h" + +static struct cpufreq_frequency_table *generic_get_freq_tbl(u32 cluster, + int *count) +{ + struct device_node *np = NULL; + const struct property *pp; + unsigned int *table = NULL; + int cluster_id; + struct cpufreq_frequency_table *cpufreq_table; + + while ((np = of_find_node_by_name(np, "cluster"))) { + if (of_property_read_u32(np, "reg", &cluster_id)) + continue; + + if (cluster_id != cluster) + continue; + + pp = of_find_property(np, "freqs", NULL); + if (!pp) + continue; + + *count = pp->length / sizeof(u32); + if (!*count) + continue; + + table = kmalloc(sizeof(*table) * (*count), GFP_KERNEL); + if (!table) { + pr_err("%s: Failed to allocate memory for table\n", + __func__); + return NULL; + } + + of_property_read_u32_array(np, "freqs", table, *count); + break; + } + + if (!table) { + pr_err("%s: Unable to retrieve Freq table from Device Tree", + __func__); + return NULL; + } + + cpufreq_table = arm_bL_copy_table_from_array(table, *count); + kfree(table); + + return cpufreq_table; +} + +static void generic_put_freq_tbl(u32 cluster) +{ + arm_bL_free_freq_table(cluster); +} + +static struct cpufreq_arm_bL_ops generic_bL_ops = { + .name = "generic-bl", + .get_freq_tbl = generic_get_freq_tbl, + .put_freq_tbl = generic_put_freq_tbl, +}; + +static int generic_bL_init(void) +{ + return bL_cpufreq_register(&generic_bL_ops); +} +module_init(generic_bL_init); + +static void generic_bL_exit(void) +{ + return bL_cpufreq_unregister(&generic_bL_ops); +} +module_exit(generic_bL_exit); + +MODULE_DESCRIPTION("Generic ARM big LITTLE cpufreq driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c index 6c287ae7bbb..7dc9c4efbcf 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; @@ -403,7 +404,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; @@ -431,7 +432,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; @@ -446,6 +448,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"); diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 6133ef5cf67..d8a8c9bfd4f 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -48,12 +48,7 @@ static inline int32_t div_fp(int32_t x, int32_t y) } struct sample { - ktime_t start_time; - ktime_t end_time; int core_pct_busy; - int pstate_pct_busy; - u64 duration_us; - u64 idletime_us; u64 aperf; u64 mperf; int freq; @@ -91,8 +86,6 @@ struct cpudata { int min_pstate_count; int idle_mode; - ktime_t prev_sample; - u64 prev_idle_time_us; u64 prev_aperf; u64 prev_mperf; int sample_ptr; @@ -124,6 +117,8 @@ struct perf_limits { int min_perf_pct; int32_t max_perf; int32_t min_perf; + int max_policy_pct; + int max_sysfs_pct; }; static struct perf_limits limits = { @@ -132,6 +127,8 @@ static struct perf_limits limits = { .max_perf = int_tofp(1), .min_perf_pct = 0, .min_perf = 0, + .max_policy_pct = 100, + .max_sysfs_pct = 100, }; static inline void pid_reset(struct _pid *pid, int setpoint, int busy, @@ -302,7 +299,8 @@ static ssize_t store_max_perf_pct(struct kobject *a, struct attribute *b, if (ret != 1) return -EINVAL; - limits.max_perf_pct = clamp_t(int, input, 0 , 100); + limits.max_sysfs_pct = clamp_t(int, input, 0 , 100); + limits.max_perf_pct = min(limits.max_policy_pct, limits.max_sysfs_pct); limits.max_perf = div_fp(int_tofp(limits.max_perf_pct), int_tofp(100)); return count; } @@ -450,48 +448,26 @@ static inline void intel_pstate_calc_busy(struct cpudata *cpu, struct sample *sample) { u64 core_pct; - sample->pstate_pct_busy = 100 - div64_u64( - sample->idletime_us * 100, - sample->duration_us); core_pct = div64_u64(sample->aperf * 100, sample->mperf); sample->freq = cpu->pstate.max_pstate * core_pct * 1000; - sample->core_pct_busy = div_s64((sample->pstate_pct_busy * core_pct), - 100); + sample->core_pct_busy = core_pct; } static inline void intel_pstate_sample(struct cpudata *cpu) { - ktime_t now; - u64 idle_time_us; u64 aperf, mperf; - now = ktime_get(); - idle_time_us = get_cpu_idle_time_us(cpu->cpu, NULL); - rdmsrl(MSR_IA32_APERF, aperf); rdmsrl(MSR_IA32_MPERF, mperf); - /* for the first sample, don't actually record a sample, just - * set the baseline */ - if (cpu->prev_idle_time_us > 0) { - cpu->sample_ptr = (cpu->sample_ptr + 1) % SAMPLE_COUNT; - cpu->samples[cpu->sample_ptr].start_time = cpu->prev_sample; - cpu->samples[cpu->sample_ptr].end_time = now; - cpu->samples[cpu->sample_ptr].duration_us = - ktime_us_delta(now, cpu->prev_sample); - cpu->samples[cpu->sample_ptr].idletime_us = - idle_time_us - cpu->prev_idle_time_us; - - cpu->samples[cpu->sample_ptr].aperf = aperf; - cpu->samples[cpu->sample_ptr].mperf = mperf; - cpu->samples[cpu->sample_ptr].aperf -= cpu->prev_aperf; - cpu->samples[cpu->sample_ptr].mperf -= cpu->prev_mperf; - - intel_pstate_calc_busy(cpu, &cpu->samples[cpu->sample_ptr]); - } + cpu->sample_ptr = (cpu->sample_ptr + 1) % SAMPLE_COUNT; + cpu->samples[cpu->sample_ptr].aperf = aperf; + cpu->samples[cpu->sample_ptr].mperf = mperf; + cpu->samples[cpu->sample_ptr].aperf -= cpu->prev_aperf; + cpu->samples[cpu->sample_ptr].mperf -= cpu->prev_mperf; + + intel_pstate_calc_busy(cpu, &cpu->samples[cpu->sample_ptr]); - cpu->prev_sample = now; - cpu->prev_idle_time_us = idle_time_us; cpu->prev_aperf = aperf; cpu->prev_mperf = mperf; } @@ -575,22 +551,16 @@ static void intel_pstate_timer_func(unsigned long __data) struct cpudata *cpu = (struct cpudata *) __data; intel_pstate_sample(cpu); + intel_pstate_adjust_busy_pstate(cpu); - if (!cpu->idle_mode) - intel_pstate_adjust_busy_pstate(cpu); - else - intel_pstate_adjust_idle_pstate(cpu); - -#if defined(XPERF_FIX) if (cpu->pstate.current_pstate == cpu->pstate.min_pstate) { cpu->min_pstate_count++; if (!(cpu->min_pstate_count % 5)) { intel_pstate_set_pstate(cpu, cpu->pstate.max_pstate); - intel_pstate_idle_mode(cpu); } } else cpu->min_pstate_count = 0; -#endif + intel_pstate_set_sample_time(cpu); } @@ -670,8 +640,9 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) limits.min_perf_pct = clamp_t(int, limits.min_perf_pct, 0 , 100); limits.min_perf = div_fp(int_tofp(limits.min_perf_pct), int_tofp(100)); - limits.max_perf_pct = policy->max * 100 / policy->cpuinfo.max_freq; - limits.max_perf_pct = clamp_t(int, limits.max_perf_pct, 0 , 100); + limits.max_policy_pct = policy->max * 100 / policy->cpuinfo.max_freq; + limits.max_policy_pct = clamp_t(int, limits.max_policy_pct, 0 , 100); + limits.max_perf_pct = min(limits.max_policy_pct, limits.max_sysfs_pct); limits.max_perf = div_fp(int_tofp(limits.max_perf_pct), int_tofp(100)); if (policy->policy == CPUFREQ_POLICY_PERFORMANCE) { diff --git a/drivers/cpufreq/vexpress_big_little.c b/drivers/cpufreq/vexpress_big_little.c new file mode 100644 index 00000000000..66648c3fc94 --- /dev/null +++ b/drivers/cpufreq/vexpress_big_little.c @@ -0,0 +1,74 @@ +/* + * Vexpress big.LITTLE CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver and gets + * information from spc controller. + * + * Copyright (C) 2012 ARM Ltd. + * Author: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * + * Copyright (C) 2012 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/cpufreq.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/vexpress.h> +#include "arm_big_little.h" + +static struct cpufreq_frequency_table *vexpress_get_freq_tbl(u32 cluster, + int *count) +{ + unsigned int *table = vexpress_spc_get_freq_table(cluster, count); + + if (!table || !*count) { + pr_err("SPC controller returned invalid freq table"); + return NULL; + } + + return arm_bL_copy_table_from_array(table, *count); +} + +static void vexpress_put_freq_tbl(u32 cluster) +{ + arm_bL_free_freq_table(cluster); +} + +static struct cpufreq_arm_bL_ops vexpress_bL_ops = { + .name = "vexpress-bL", + .get_freq_tbl = vexpress_get_freq_tbl, + .put_freq_tbl = vexpress_put_freq_tbl, +}; + +static int vexpress_bL_init(void) +{ + if (!vexpress_spc_check_loaded()) { + pr_info("%s: No SPC found\n", __func__); + return -ENOENT; + } + + return bL_cpufreq_register(&vexpress_bL_ops); +} +module_init(vexpress_bL_init); + +static void vexpress_bL_exit(void) +{ + return bL_cpufreq_unregister(&vexpress_bL_ops); +} +module_exit(vexpress_bL_exit); + +MODULE_DESCRIPTION("ARM Vexpress big LITTLE cpufreq driver"); +MODULE_LICENSE("GPL"); |