diff options
author | Jon Medhurst <tixy@linaro.org> | 2013-01-21 18:38:31 +0000 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2013-01-21 18:38:31 +0000 |
commit | d21ffe111eac3c6383e38af98499844b7bb07357 (patch) | |
tree | ae35f4dc0d2eba347bb97e38e93d18e4f394da8b | |
parent | c60aee266730dd27882c07fa4f93635b3dda6ac1 (diff) | |
parent | 0db5555637570fdac43ac8ca86f1b56289991a17 (diff) |
Merge branch 'tracking-armlt-tc2-pm-new-cpufreq' into integration-linaro-vexpresstracking-integration-linaro-vexpress-ll-20130122.1tracking-integration-linaro-vexpress-ll-20130122.0
-rw-r--r-- | arch/arm/boot/dts/vexpress-v2p-ca15-tc2.dts | 2 | ||||
-rw-r--r-- | arch/arm/kernel/topology.c | 2 | ||||
-rw-r--r-- | drivers/clk/versatile/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/versatile/clk-vexpress-spc.c | 130 | ||||
-rw-r--r-- | drivers/clk/versatile/clk-vexpress.c | 1 | ||||
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 22 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 6 | ||||
-rw-r--r-- | drivers/cpufreq/arm_big_little.c | 283 | ||||
-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/vexpress_bL_cpufreq.c | 282 | ||||
-rw-r--r-- | drivers/cpufreq/vexpress_big_little.c | 74 | ||||
-rw-r--r-- | drivers/misc/vexpress/arm-spc.c | 12 | ||||
-rw-r--r-- | include/linux/vexpress.h | 10 |
14 files changed, 673 insertions, 292 deletions
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc2.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc2.dts index 56269451116d..b261bde759a5 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc2.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc2.dts @@ -37,7 +37,6 @@ cluster0: cluster@0 { reg = <0>; - freqs = <500000000 600000000 700000000 800000000 900000000 1000000000 1100000000 1200000000>; cores { #address-cells = <1>; #size-cells = <0>; @@ -55,7 +54,6 @@ cluster1: cluster@1 { reg = <1>; - freqs = <350000000 400000000 500000000 600000000 700000000 800000000 900000000 1000000000>; cores { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm/kernel/topology.c b/arch/arm/kernel/topology.c index b6e04a002cfb..181ee5d974d0 100644 --- a/arch/arm/kernel/topology.c +++ b/arch/arm/kernel/topology.c @@ -13,6 +13,7 @@ #include <linux/cpu.h> #include <linux/cpumask.h> +#include <linux/export.h> #include <linux/init.h> #include <linux/percpu.h> #include <linux/node.h> @@ -200,6 +201,7 @@ static inline void update_cpu_power(unsigned int cpuid, unsigned int mpidr) {} * cpu topology table */ struct cputopo_arm cpu_topology[NR_CPUS]; +EXPORT_SYMBOL_GPL(cpu_topology); int arch_sd_local_flags(int level) { diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile index ec3b88fe3e6d..d1359f440434 100644 --- a/drivers/clk/versatile/Makefile +++ b/drivers/clk/versatile/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o -obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o +obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o clk-vexpress-spc.o diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c new file mode 100644 index 000000000000..3ee7d410ddfe --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress-spc.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012 ARM Limited + * Copyright (C) 2012 Linaro + * + * Author: Viresh Kumar <viresh.kumar@linaro.org> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +/* SPC clock programming interface for Vexpress cpus */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/vexpress.h> + +struct clk_spc { + struct clk_hw hw; + spinlock_t *lock; + int cluster; +}; + +#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw) + +static unsigned long spc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + u32 freq; + + if (vexpress_spc_get_performance(spc->cluster, &freq)) { + return -EIO; + pr_err("%s: Failed", __func__); + } + + return freq * 1000; +} + +static long spc_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *parent_rate) +{ + return drate; +} + +static int spc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + + return vexpress_spc_set_performance(spc->cluster, rate / 1000); +} + +static struct clk_ops clk_spc_ops = { + .recalc_rate = spc_recalc_rate, + .round_rate = spc_round_rate, + .set_rate = spc_set_rate, +}; + +struct clk *vexpress_clk_register_spc(const char *name, int cluster_id) +{ + struct clk_init_data init; + struct clk_spc *spc; + struct clk *clk; + + if (!name) { + pr_err("Invalid name passed"); + return ERR_PTR(-EINVAL); + } + + spc = kzalloc(sizeof(*spc), GFP_KERNEL); + if (!spc) { + pr_err("could not allocate spc clk\n"); + return ERR_PTR(-ENOMEM); + } + + spc->hw.init = &init; + spc->cluster = cluster_id; + + init.name = name; + init.ops = &clk_spc_ops; + init.flags = CLK_IS_ROOT | CLK_GET_RATE_NOCACHE; + init.num_parents = 0; + + clk = clk_register(NULL, &spc->hw); + if (!IS_ERR_OR_NULL(clk)) + return clk; + + pr_err("clk register failed\n"); + kfree(spc); + + return NULL; +} + +#if defined(CONFIG_OF) +void __init vexpress_clk_of_register_spc(void) +{ + char name[9] = "cluster"; + struct device_node *node = NULL; + struct clk *clk; + const u32 *val; + int cluster_id = 0, len; + + if (!of_find_compatible_node(NULL, NULL, "arm,spc")) { + pr_debug("%s: No SPC found, Exiting!!\n", __func__); + return; + } + + while ((node = of_find_node_by_name(node, "cluster"))) { + val = of_get_property(node, "reg", &len); + if (val && len == 4) + cluster_id = be32_to_cpup(val); + + name[7] = cluster_id + '0'; + clk = vexpress_clk_register_spc(name, cluster_id); + if (IS_ERR(clk)) + return; + + pr_debug("Registered clock '%s'\n", name); + clk_register_clkdev(clk, name, NULL); + } +} +#endif diff --git a/drivers/clk/versatile/clk-vexpress.c b/drivers/clk/versatile/clk-vexpress.c index c742ac7c60bb..e59714c2be46 100644 --- a/drivers/clk/versatile/clk-vexpress.c +++ b/drivers/clk/versatile/clk-vexpress.c @@ -112,6 +112,7 @@ void __init vexpress_clk_of_init(void) struct clk *refclk, *timclk; of_clk_init(vexpress_fixed_clk_match); + vexpress_clk_of_register_spc(); node = of_find_compatible_node(NULL, NULL, "arm,sp810"); vexpress_sp810_init(of_iomap(node, 0)); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 8db6f17b7498..7985dcd2f5c1 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -84,11 +84,23 @@ config ARM_SPEAR_CPUFREQ help This adds the CPUFreq driver support for SPEAr SOCs. -config ARM_VEXPRESS_BL_CPUFREQ - tristate "CPUfreq driver for ARM Vexpress big.LITTLE CPUs" - depends on ARCH_VEXPRESS && CPU_FREQ +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 + default y help - This enables the CPUfreq driver for ARM Vexpress big.LITTLE - platform. + 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 + 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 fb5bb4423b85..8847c965094a 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -53,7 +53,11 @@ obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o -obj-$(CONFIG_ARM_VEXPRESS_BL_CPUFREQ) += vexpress_bL_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/arm_big_little.c b/drivers/cpufreq/arm_big_little.c new file mode 100644 index 000000000000..b5601fcd79e2 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.c @@ -0,0 +1,283 @@ +/* + * 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/of_platform.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <asm/topology.h> +#include "arm_big_little.h" + +#define MAX_CLUSTERS 2 + +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)}; + +/* + * Functions to get the current status. + * + * Beware that the cluster for another CPU may change unexpectedly. + */ +static int cpu_to_cluster(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static unsigned int bL_cpufreq_get(unsigned int cpu) +{ + u32 cur_cluster = cpu_to_cluster(cpu); + + return clk_get_rate(clk[cur_cluster]) / 1000; +} + +/* 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; + int ret = 0; + + /* ASSUMPTION: The cpu can't be hotplugged in this function */ + cur_cluster = cpu_to_cluster(policy->cpu); + + freqs.old = bL_cpufreq_get(policy->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); + + if (freqs.old == freqs.new) + return 0; + + 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); + return ret; + } + + policy->cur = freqs.new; + + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +/* 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 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 int get_cluster_clk_and_freq_table(u32 cluster) +{ + char name[9] = "cluster"; + 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; + + 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; +} + +/* 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); + + policy->cpuinfo.transition_latency = 1000000; /* 1 ms assumed */ + policy->cur = bL_cpufreq_get(policy->cpu); + + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); + cpumask_copy(policy->related_cpus, policy->cpus); + + pr_info("CPU %d initialized\n", policy->cpu); + return 0; +} + +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); + + 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, + .init = bL_cpufreq_init, + .exit = bL_cpufreq_exit, + .attr = bL_cpufreq_attr, +}; + +int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops) +{ + int ret; + + 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 = 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); + } + + 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); + } + + cpufreq_unregister_driver(&bL_cpufreq_driver); + pr_info("%s: Un-registered platform driver: %s\n", __func__, + arm_bL_ops->name); + 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 000000000000..6712a5011986 --- /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 000000000000..fabfb9c5c376 --- /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/vexpress_bL_cpufreq.c b/drivers/cpufreq/vexpress_bL_cpufreq.c deleted file mode 100644 index 5718bbecd984..000000000000 --- a/drivers/cpufreq/vexpress_bL_cpufreq.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Vexpress big.LITTLE CPUFreq support - * Based on mach-integrator - * - * Copyright (C) 2012 ARM Ltd. - * Author: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> - * - * 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. - */ -#include <linux/cpufreq.h> -#include <linux/cpumask.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of_platform.h> -#include <linux/sched.h> -#include <linux/slab.h> -#include <linux/smp.h> -#include <linux/spinlock.h> -#include <linux/sysfs.h> -#include <linux/types.h> - -#include <linux/vexpress.h> - -#define VEXPRESS_MAX_CLUSTER 2 - -static struct cpufreq_frequency_table *freq_table[VEXPRESS_MAX_CLUSTER]; -static atomic_t freq_table_users = ATOMIC_INIT(0); - -/* Cached current cluster for each CPU to save on IPIs */ -static DEFINE_PER_CPU(unsigned int, cpu_cur_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 >> 8) & 0xf; -} - -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); -} - -/* Validate policy frequency range */ -static int vexpress_cpufreq_verify_policy(struct cpufreq_policy *policy) -{ - uint32_t cur_cluster = get_current_cached_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 vexpress_cpufreq_set_target(struct cpufreq_policy *policy, - unsigned int target_freq, unsigned int relation) -{ - uint32_t cpu = policy->cpu; - struct cpufreq_freqs freqs; - uint32_t freq_tab_idx; - uint32_t cur_cluster; - int ret = 0; - - /* Read current clock rate */ - cur_cluster = get_current_cached_cluster(cpu); - - if (vexpress_spc_get_performance(cur_cluster, &freqs.old)) - return -EIO; - - /* Make sure that target_freq is within supported range */ - if (target_freq > policy->max) - target_freq = policy->max; - if (target_freq < policy->min) - target_freq = policy->min; - - /* 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; - - if (freqs.old == freqs.new) - return 0; - - pr_debug("Requested Freq %d cpu %d\n", freqs.new, cpu); - - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - - ret = vexpress_spc_set_performance(cur_cluster, freqs.new); - if (ret) { - pr_err("Error %d while setting required OPP\n", ret); - return ret; - } - - policy->cur = freqs.new; - - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return ret; -} - -/* Get current clock frequency */ -static unsigned int vexpress_cpufreq_get(unsigned int cpu) -{ - uint32_t freq = 0; - uint32_t cur_cluster = get_current_cached_cluster(cpu); - - /* - * Read current clock rate with vexpress_spc call - */ - if (vexpress_spc_get_performance(cur_cluster, &freq)) - return -EIO; - - return freq; -} - -/* translate the integer array into cpufreq_frequency_table entries */ -static inline void _cpufreq_copy_table_from_array(uint32_t *table, - struct cpufreq_frequency_table *freq_table, int size) -{ - int i; - for (i = 0; i < size; i++) { - freq_table[i].index = i; - freq_table[i].frequency = table[i] / 1000; /* in kHZ */ - } - freq_table[i].index = size; - freq_table[i].frequency = CPUFREQ_TABLE_END; -} - -static int vexpress_cpufreq_of_init(void) -{ - uint32_t cpu_opp_num; - struct cpufreq_frequency_table *freqtable[VEXPRESS_MAX_CLUSTER]; - uint32_t *cpu_freqs; - int ret = 0, cluster_id = 0, len; - struct device_node *cluster = NULL; - const struct property *pp; - const u32 *hwid; - - while ((cluster = of_find_node_by_name(cluster, "cluster"))) { - hwid = of_get_property(cluster, "reg", &len); - if (hwid && len == 4) - cluster_id = be32_to_cpup(hwid); - - pp = of_find_property(cluster, "freqs", NULL); - if (!pp) - return -EINVAL; - cpu_opp_num = pp->length / sizeof(u32); - if (!cpu_opp_num) - return -ENODATA; - - cpu_freqs = kzalloc(sizeof(uint32_t) * cpu_opp_num, GFP_KERNEL); - freqtable[cluster_id] = - kzalloc(sizeof(struct cpufreq_frequency_table) * - (cpu_opp_num + 1), GFP_KERNEL); - if (!cpu_freqs || !freqtable[cluster_id]) { - ret = -ENOMEM; - goto free_mem; - } - of_property_read_u32_array(cluster, "freqs", - cpu_freqs, cpu_opp_num); - _cpufreq_copy_table_from_array(cpu_freqs, - freqtable[cluster_id], cpu_opp_num); - freq_table[cluster_id] = freqtable[cluster_id]; - - kfree(cpu_freqs); - } - return ret; -free_mem: - while (cluster_id >= 0) - kfree(freqtable[cluster_id--]); - kfree(cpu_freqs); - return ret; -} - -/* Per-CPU initialization */ -static int vexpress_cpufreq_init(struct cpufreq_policy *policy) -{ - int result = 0; - uint32_t cur_cluster = get_current_cluster(policy->cpu); - - if (atomic_inc_return(&freq_table_users) == 1) - result = vexpress_cpufreq_of_init(); - - if (freq_table[cur_cluster] == NULL) - result = -ENODATA; - - if (result) { - atomic_dec_return(&freq_table_users); - pr_err("CPUFreq - CPU %d failed to initialize\n", policy->cpu); - return result; - } - - result = - cpufreq_frequency_table_cpuinfo(policy, freq_table[cur_cluster]); - if (result) - return result; - - cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu); - - per_cpu(cpu_cur_cluster, policy->cpu) = cur_cluster; - - /* set default policy and cpuinfo */ - policy->min = policy->cpuinfo.min_freq; - policy->max = policy->cpuinfo.max_freq; - - policy->cpuinfo.transition_latency = 1000000; /* 1 ms assumed */ - policy->cur = vexpress_cpufreq_get(policy->cpu); - - cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); - cpumask_copy(policy->related_cpus, policy->cpus); - - pr_info("CPUFreq for CPU %d initialized\n", policy->cpu); - return result; -} - -/* Export freq_table to sysfs */ -static struct freq_attr *vexpress_cpufreq_attr[] = { - &cpufreq_freq_attr_scaling_available_freqs, - NULL, -}; - -static struct cpufreq_driver vexpress_cpufreq_driver = { - .flags = CPUFREQ_STICKY, - .verify = vexpress_cpufreq_verify_policy, - .target = vexpress_cpufreq_set_target, - .get = vexpress_cpufreq_get, - .init = vexpress_cpufreq_init, - .name = "vexpress-spc", - .attr = vexpress_cpufreq_attr, -}; - -static int __init vexpress_cpufreq_modinit(void) -{ - if (!vexpress_spc_check_loaded()) { - pr_info("vexpress cpufreq not initialised because no SPC found\n"); - return -ENODEV; - } - - return cpufreq_register_driver(&vexpress_cpufreq_driver); -} - -static void __exit vexpress_cpufreq_modexit(void) -{ - cpufreq_unregister_driver(&vexpress_cpufreq_driver); -} - -MODULE_DESCRIPTION("cpufreq driver for ARM vexpress big.LITTLE platform"); -MODULE_LICENSE("GPL"); - -module_init(vexpress_cpufreq_modinit); -module_exit(vexpress_cpufreq_modexit); diff --git a/drivers/cpufreq/vexpress_big_little.c b/drivers/cpufreq/vexpress_big_little.c new file mode 100644 index 000000000000..66648c3fc949 --- /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"); diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 5708bb35a107..913dd0872828 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -117,6 +117,7 @@ struct vexpress_spc_drvdata { struct semaphore lock; struct completion done; uint32_t freqs[MAX_CLUSTERS][MAX_OPPS]; + int freqs_cnt[MAX_CLUSTERS]; }; static struct vexpress_spc_drvdata *info; @@ -278,7 +279,7 @@ static int vexpress_spc_find_perf_index(int cluster, u32 freq) { int idx; /* Hash function would be ideal, based on hashtable in v3.8?? */ - for (idx = 0; idx < MAX_OPPS; idx++) + for (idx = 0; idx < info->freqs_cnt[cluster]; idx++) if (info->freqs[cluster][idx] == freq) break; return idx; @@ -613,9 +614,18 @@ static int vexpress_spc_populate_opps(uint32_t cluster) break; } + info->freqs_cnt[cluster] = j; return ret; } +unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count) +{ + + *count = info->freqs_cnt[cluster]; + return info->freqs[cluster]; +} +EXPORT_SYMBOL_GPL(vexpress_spc_get_freq_table); + static int vexpress_spc_init(void) { struct device_node *node = of_find_compatible_node(NULL, NULL, diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index ae05b033f2ed..203c241cd5fb 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -15,6 +15,7 @@ #define _LINUX_VEXPRESS_H #include <linux/device.h> +#include <linux/err.h> #define VEXPRESS_SITE_MB 0 #define VEXPRESS_SITE_DB1 1 @@ -115,6 +116,9 @@ void vexpress_restart(char str, const char *cmd); struct clk *vexpress_osc_setup(struct device *dev); void vexpress_osc_of_setup(struct device_node *node); +struct clk *vexpress_clk_register_spc(const char *name, int cluster_id); +void vexpress_clk_of_register_spc(void); + void vexpress_clk_init(void __iomem *sp810_base); void vexpress_clk_of_init(void); @@ -139,6 +143,7 @@ extern int vexpress_spc_standbywfi_status(int cluster, int cpu); extern int vexpress_spc_standbywfil2_status(int cluster); extern int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set); extern int vexpress_spc_set_global_wakeup_intr(u32 set); +extern unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count); extern int vexpress_spc_get_performance(int cluster, u32 *freq); extern int vexpress_spc_set_performance(int cluster, u32 freq); extern int vexpress_spc_wfi_cpustat(int cluster); @@ -201,6 +206,11 @@ static inline u32 vexpress_scc_read_rststat(int cluster) return 0; } +static inline unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count) +{ + return ERR_PTR(-ENOSYS); +} + static inline int vexpress_spc_get_performance(int cluster, u32 *freq) { return -ENOSYS; |