From: Xiao Feng Date: Tue, 20 Jan 2015 08:20:44 +0000 (+0800) Subject: rk3368: add big little cpufreq support X-Git-Tag: firefly_0821_release~4158^2~447^2~27 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=2e4acc14a08db80dfd9a58e011c2291d7d97fe7f;p=firefly-linux-kernel-4.4.55.git rk3368: add big little cpufreq support Signed-off-by: Xiao Feng --- diff --git a/arch/arm64/boot/dts/rk3368.dtsi b/arch/arm64/boot/dts/rk3368.dtsi index d521d9415adf..1fbfe56fd8c8 100755 --- a/arch/arm64/boot/dts/rk3368.dtsi +++ b/arch/arm64/boot/dts/rk3368.dtsi @@ -878,6 +878,11 @@ rockchip,grf = <&grf>; }; + cpufreq { + compatible = "rockchip,rk3368-cpufreq"; + rockchip,grf = <&grf>; + }; + dvfs { vd_arm: vd_arm { diff --git a/arch/arm64/configs/rockchip_defconfig b/arch/arm64/configs/rockchip_defconfig index fece209c092f..c736e2db2bcb 100644 --- a/arch/arm64/configs/rockchip_defconfig +++ b/arch/arm64/configs/rockchip_defconfig @@ -64,6 +64,8 @@ CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_ARM_BIG_LITTLE_CPUFREQ=y CONFIG_ARM_DT_BL_CPUFREQ=y +# CONFIG_ARM_ROCKCHIP_CPUFREQ is not set +CONFIG_ARM_ROCKCHIP_BL_CPUFREQ=y CONFIG_CPU_IDLE=y CONFIG_ARM64_CPUIDLE=y CONFIG_NET=y diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 699bc1a24dab..0c8acee9c779 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -113,6 +113,13 @@ config ARM_ROCKCHIP_CPUFREQ This enables the CPUfreq driver for Rockchips CPUs. If in doubt, say Y. +config ARM_ROCKCHIP_BL_CPUFREQ + bool "CPUfreq driver for Rockchip big LITTLE CPUs" + depends on ARCH_ROCKCHIP + help + This enables the CPUfreq driver for Rockchips big LITTLE CPUs. + If in doubt, say Y. + config ARM_S3C2416_CPUFREQ bool "S3C2416 CPU Frequency scaling support" depends on CPU_S3C2416 diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index d40d7da9374d..8878b8675f49 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_PXA25x) += pxa2xx-cpufreq.o obj-$(CONFIG_PXA27x) += pxa2xx-cpufreq.o obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o obj-$(CONFIG_ARM_ROCKCHIP_CPUFREQ) += rockchip-cpufreq.o +obj-$(CONFIG_ARM_ROCKCHIP_BL_CPUFREQ) += rockchip_big_little.o obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o diff --git a/drivers/cpufreq/rockchip_big_little.c b/drivers/cpufreq/rockchip_big_little.c new file mode 100644 index 000000000000..930e52318ff4 --- /dev/null +++ b/drivers/cpufreq/rockchip_big_little.c @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2015 Fuzhou Rockchip Electronics Co., Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#define pr_fmt(fmt) "cpufreq: " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../drivers/clk/rockchip/clk-pd.h" + +#define RK3368_GRF_CPU_CON(n) (0x500 + 4*n) + + +#define VERSION "1.0" +#define RK_MAX_CLUSTERS 2 + +#ifdef DEBUG +#define FREQ_DBG(fmt, args...) pr_debug(fmt, ## args) +#define FREQ_LOG(fmt, args...) pr_debug(fmt, ## args) +#else +#define FREQ_DBG(fmt, args...) do {} while (0) +#define FREQ_LOG(fmt, args...) do {} while (0) +#endif +#define FREQ_ERR(fmt, args...) pr_err(fmt, ## args) + +/* Frequency table index must be sequential starting at 0 */ +static struct cpufreq_frequency_table default_freq_table[] = { + {.frequency = 312 * 1000, .index = 875 * 1000}, + {.frequency = 504 * 1000, .index = 925 * 1000}, + {.frequency = 816 * 1000, .index = 975 * 1000}, + {.frequency = 1008 * 1000, .index = 1075 * 1000}, + {.frequency = 1200 * 1000, .index = 1150 * 1000}, + {.frequency = 1416 * 1000, .index = 1250 * 1000}, + {.frequency = 1608 * 1000, .index = 1350 * 1000}, + {.frequency = CPUFREQ_TABLE_END}, +}; +static struct cpufreq_frequency_table *freq_table[RK_MAX_CLUSTERS + 1]; +/*********************************************************/ +/* additional symantics for "relation" in cpufreq with pm */ +#define DISABLE_FURTHER_CPUFREQ 0x10 +#define ENABLE_FURTHER_CPUFREQ 0x20 +#define MASK_FURTHER_CPUFREQ 0x30 +/* With 0x00(NOCHANGE), it depends on the previous "further" status */ +#define CPUFREQ_PRIVATE 0x100 +static unsigned int no_cpufreq_access[RK_MAX_CLUSTERS] = {0}; +static unsigned int suspend_freq[RK_MAX_CLUSTERS] = {816 * 1000, 816 * 1000}; +static unsigned int suspend_volt = 1100000; +static unsigned int low_battery_freq[RK_MAX_CLUSTERS] = {600 * 1000, + 600 * 1000}; +static unsigned int low_battery_capacity = 5; +static bool is_booting = true; +static DEFINE_MUTEX(cpufreq_mutex); +static struct dvfs_node *clk_cpu_dvfs_node[RK_MAX_CLUSTERS]; +static struct dvfs_node *clk_gpu_dvfs_node; +static struct dvfs_node *clk_ddr_dvfs_node; +static u32 cluster_policy_cpu[RK_MAX_CLUSTERS]; +static unsigned int big_little = 1; + +/*******************************************************/ +static int cpu_to_cluster(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static unsigned int cpufreq_bl_get_rate(unsigned int cpu) +{ + u32 cur_cluster = cpu_to_cluster(cpu); + + if (clk_cpu_dvfs_node[cur_cluster]) + return clk_get_rate(clk_cpu_dvfs_node[cur_cluster]->clk) / 1000; + + return 0; +} + +static bool cpufreq_is_ondemand(struct cpufreq_policy *policy) +{ + char c = 0; + + if (policy && policy->governor) + c = policy->governor->name[0]; + return (c == 'o' || c == 'i' || c == 'c' || c == 'h'); +} + +static unsigned int get_freq_from_table(unsigned int max_freq, + unsigned int cluster) +{ + unsigned int i; + unsigned int target_freq = 0; + + for (i = 0; freq_table[cluster][i].frequency != + CPUFREQ_TABLE_END; i++) { + unsigned int freq = freq_table[cluster][i].frequency; + + if (freq <= max_freq && target_freq < freq) + target_freq = freq; + } + if (!target_freq) + target_freq = max_freq; + return target_freq; +} + +static int cpufreq_notifier_policy(struct notifier_block *nb, unsigned long val, + void *data) +{ + static unsigned int min_rate = 0, max_rate = -1; + struct cpufreq_policy *policy = data; + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (val != CPUFREQ_ADJUST) + return 0; + + if (cpufreq_is_ondemand(policy)) { + FREQ_DBG("queue work\n"); + dvfs_clk_enable_limit(clk_cpu_dvfs_node[cur_cluster], + min_rate, max_rate); + } else { + FREQ_DBG("cancel work\n"); + dvfs_clk_get_limit(clk_cpu_dvfs_node[cur_cluster], + &min_rate, &max_rate); + } + + return 0; +} + +static struct notifier_block notifier_policy_block = { + .notifier_call = cpufreq_notifier_policy +}; + +static int cpufreq_bl_verify(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (!freq_table[cur_cluster]) + return -EINVAL; + return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]); +} + +static int clk_node_get_cluster_id(struct clk *clk) +{ + int i; + + for (i = 0; i < RK_MAX_CLUSTERS; i++) { + if (clk_cpu_dvfs_node[i]->clk == clk) + return i; + } + return 0; +} + +static int cpufreq_bl_scale_rate_for_dvfs(struct clk *clk, unsigned long rate) +{ + int ret; + struct cpufreq_freqs freqs; + struct cpufreq_policy *policy; + u32 cur_cluster; + + cur_cluster = clk_node_get_cluster_id(clk); + policy = cpufreq_cpu_get(cluster_policy_cpu[cur_cluster]); + + freqs.new = rate / 1000; + freqs.old = clk_get_rate(clk) / 1000; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + FREQ_DBG("cpufreq_scale_rate_for_dvfs(%lu)\n", rate); + + ret = clk_set_rate(clk, rate); + + freqs.new = clk_get_rate(clk) / 1000; + /* notifiers */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + cpufreq_cpu_put(policy); + return ret; +} + +static int cluster_cpus_freq_dvfs_init(u32 cluster_id, char *dvfs_name) +{ + clk_cpu_dvfs_node[cluster_id] = clk_get_dvfs_node(dvfs_name); + + if (!clk_cpu_dvfs_node[cluster_id]) { + FREQ_ERR("%s:cluster_id=%d,get dvfs err\n", + __func__, cluster_id); + return -EINVAL; + } + dvfs_clk_register_set_rate_callback(clk_cpu_dvfs_node[cluster_id], + cpufreq_bl_scale_rate_for_dvfs); + freq_table[cluster_id] = + dvfs_get_freq_volt_table(clk_cpu_dvfs_node[cluster_id]); + if (freq_table[cluster_id] == NULL) { + freq_table[cluster_id] = default_freq_table; + } else { + int v = INT_MAX; + int i; + + for (i = 0; freq_table[cluster_id][i].frequency != + CPUFREQ_TABLE_END; i++) { + if (freq_table[cluster_id][i].index >= suspend_volt && + v > freq_table[cluster_id][i].index) { + suspend_freq[cluster_id] = + freq_table[cluster_id][i].frequency; + v = freq_table[cluster_id][i].index; + } + } + } + low_battery_freq[cluster_id] = + get_freq_from_table(low_battery_freq[cluster_id], cluster_id); + clk_enable_dvfs(clk_cpu_dvfs_node[cluster_id]); + return 0; +} + +static int cpufreq_bl_init_cpu0(struct cpufreq_policy *policy) +{ + clk_gpu_dvfs_node = clk_get_dvfs_node("clk_gpu"); + if (clk_gpu_dvfs_node) + clk_enable_dvfs(clk_gpu_dvfs_node); + + clk_ddr_dvfs_node = clk_get_dvfs_node("clk_ddr"); + if (clk_ddr_dvfs_node) + clk_enable_dvfs(clk_ddr_dvfs_node); + + if (big_little == 1) { + cluster_cpus_freq_dvfs_init(0, "clk_core_b"); + cluster_cpus_freq_dvfs_init(1, "clk_core_l"); + } else { + cluster_cpus_freq_dvfs_init(0, "clk_core_l"); + cluster_cpus_freq_dvfs_init(1, "clk_core_b"); + } + + cpufreq_register_notifier(¬ifier_policy_block, + CPUFREQ_POLICY_NOTIFIER); + + pr_info("cpufreq version " VERSION ", suspend freq %d %d MHz\n", + suspend_freq[0] / 1000, suspend_freq[1] / 1000); + return 0; +} + +static int cpufreq_bl_init(struct cpufreq_policy *policy) +{ + static int cpu0_err; + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (policy->cpu == 0) + cpu0_err = cpufreq_bl_init_cpu0(policy); + if (cpu0_err) + return cpu0_err; + + cluster_policy_cpu[cur_cluster] = policy->cpu; + + /* set freq min max */ + cpufreq_frequency_table_cpuinfo(policy, freq_table[cur_cluster]); + /* sys nod */ + cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu); + + if (cur_cluster < RK_MAX_CLUSTERS) { + /* int cpu; */ + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); + /* for_each_cpu(cpu, policy->cpus) { + pr_info("%s:policy->cpu=%d,cpu=%d,%02x\n", + __func__, policy->cpu, cpu, policy->cpus[0]); + } */ + } + + policy->cur = clk_get_rate(clk_cpu_dvfs_node[cur_cluster]->clk) / 1000; + + /* make ondemand default sampling_rate to 40000 */ + policy->cpuinfo.transition_latency = 40 * NSEC_PER_USEC; + + return 0; +} + +static int cpufreq_bl_exit(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (policy->cpu == 0) { + cpufreq_unregister_notifier(¬ifier_policy_block, + CPUFREQ_POLICY_NOTIFIER); + } + cpufreq_frequency_table_cpuinfo(policy, freq_table[cur_cluster]); + clk_put_dvfs_node(clk_cpu_dvfs_node[cur_cluster]); + + return 0; +} + +static struct freq_attr *cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +#ifdef CONFIG_CHARGER_DISPLAY +extern int rk_get_system_battery_capacity(void); +#else +static int rk_get_system_battery_capacity(void) { return 100; } +#endif + +static unsigned int cpufreq_bl_scale_limit(unsigned int target_freq, + struct cpufreq_policy *policy, + bool is_private) +{ + bool is_ondemand = cpufreq_is_ondemand(policy); + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (!is_ondemand) + return target_freq; + + if (is_booting) { + s64 boottime_ms = ktime_to_ms(ktime_get_boottime()); + + if (boottime_ms > 60 * MSEC_PER_SEC) { + is_booting = false; + } else if (target_freq > low_battery_freq[cur_cluster] && + rk_get_system_battery_capacity() <= + low_battery_capacity) { + target_freq = low_battery_freq[cur_cluster]; + } + } + + return target_freq; +} + +static int cpufreq_bl_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + unsigned int i, new_freq = target_freq, new_rate, cur_rate; + int ret = 0; + bool is_private; + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + if (!freq_table[cur_cluster]) { + FREQ_ERR("no freq table!\n"); + return -EINVAL; + } + + mutex_lock(&cpufreq_mutex); + + is_private = relation & CPUFREQ_PRIVATE; + relation &= ~CPUFREQ_PRIVATE; + + if ((relation & ENABLE_FURTHER_CPUFREQ) && + no_cpufreq_access[cur_cluster]) + no_cpufreq_access[cur_cluster]--; + if (no_cpufreq_access[cur_cluster]) { + FREQ_LOG("denied access to %s as it is disabled temporarily\n", + __func__); + ret = -EINVAL; + goto out; + } + if (relation & DISABLE_FURTHER_CPUFREQ) + no_cpufreq_access[cur_cluster]++; + relation &= ~MASK_FURTHER_CPUFREQ; + + ret = cpufreq_frequency_table_target(policy, freq_table[cur_cluster], + target_freq, relation, &i); + if (ret) { + FREQ_ERR("no freq match for %d(ret=%d)\n", target_freq, ret); + goto out; + } + new_freq = freq_table[cur_cluster][i].frequency; + if (!no_cpufreq_access[cur_cluster]) + new_freq = cpufreq_bl_scale_limit(new_freq, policy, is_private); + + new_rate = new_freq * 1000; + cur_rate = dvfs_clk_get_rate(clk_cpu_dvfs_node[cur_cluster]); + FREQ_LOG("req = %7u new = %7u (was = %7u)\n", target_freq, + new_freq, cur_rate / 1000); + if (new_rate == cur_rate) + goto out; + ret = dvfs_clk_set_rate(clk_cpu_dvfs_node[cur_cluster], new_rate); + +out: + FREQ_DBG("set freq (%7u) end, ret %d\n", new_freq, ret); + mutex_unlock(&cpufreq_mutex); + return ret; +} + +static int cpufreq_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int ret = NOTIFY_DONE; + int i; + + for (i = 0; i < RK_MAX_CLUSTERS; i++) { + struct cpufreq_policy *policy = + cpufreq_cpu_get(cluster_policy_cpu[i]); + + if (!policy) + return ret; + + if (!cpufreq_is_ondemand(policy)) + goto out; + + switch (event) { + case PM_SUSPEND_PREPARE: + policy->cur++; + ret = cpufreq_driver_target(policy, suspend_freq[i], + DISABLE_FURTHER_CPUFREQ | + CPUFREQ_RELATION_H); + if (ret < 0) { + ret = NOTIFY_BAD; + goto out; + } + ret = NOTIFY_OK; + break; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + /* if (target_freq == policy->cur) then + cpufreq_driver_target will return, and + our target will not be called, it casue + ENABLE_FURTHER_CPUFREQ flag invalid, + avoid that. */ + policy->cur++; + cpufreq_driver_target(policy, suspend_freq[i], + ENABLE_FURTHER_CPUFREQ | + CPUFREQ_RELATION_H); + ret = NOTIFY_OK; + break; + } +out: + cpufreq_cpu_put(policy); + } + return ret; +} + +static struct notifier_block cpufreq_pm_notifier = { + .notifier_call = cpufreq_pm_notifier_event, +}; + +static int rockchip_bl_cpufreq_reboot_limit_freq(void) +{ + struct regulator *regulator; + int volt = 0; + u32 rate; + int i; + + dvfs_disable_temp_limit(); + + for (i = 0; i < RK_MAX_CLUSTERS; i++) { + dvfs_clk_enable_limit(clk_cpu_dvfs_node[i], + 1000*suspend_freq[i], + 1000*suspend_freq[i]); + rate = dvfs_clk_get_rate(clk_cpu_dvfs_node[i]); + } + + regulator = dvfs_get_regulator("vdd_arm"); + if (regulator) + volt = regulator_get_voltage(regulator); + else + pr_info("cpufreq: get arm regulator failed\n"); + pr_info("cpufreq: reboot set cluster0 rate=%lu, cluster1 rate=%lu, volt=%d\n", + dvfs_clk_get_rate(clk_cpu_dvfs_node[0]), + dvfs_clk_get_rate(clk_cpu_dvfs_node[1]), volt); + + return 0; +} + +static int cpufreq_reboot_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + rockchip_set_system_status(SYS_STATUS_REBOOT); + rockchip_bl_cpufreq_reboot_limit_freq(); + + return NOTIFY_OK; +}; + +static struct notifier_block cpufreq_reboot_notifier = { + .notifier_call = cpufreq_reboot_notifier_event, +}; + +static struct cpufreq_driver cpufreq_driver = { + .flags = CPUFREQ_CONST_LOOPS, + .verify = cpufreq_bl_verify, + .target = cpufreq_bl_target, + .get = cpufreq_bl_get_rate, + .init = cpufreq_bl_init, + .exit = cpufreq_bl_exit, + .name = "rockchip-big-little", + .have_governor_per_policy = true, + .attr = cpufreq_attr, +}; + +static const struct of_device_id cpufreq_match[] = { + { + .compatible = "rockchip,rk3368-cpufreq", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpufreq_match); + +static int cpufreq_probe(struct platform_device *pdev) +{ + struct device_node *np; + struct regmap *grf_regmap; + unsigned int big_bits, litt_bits; + int ret; + + np = pdev->dev.of_node; + if (!np) + return -ENODEV; + + grf_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(grf_regmap)) { + FREQ_ERR("Cpufreq couldn't find grf regmap\n"); + return PTR_ERR(grf_regmap); + } + ret = regmap_read(grf_regmap, RK3368_GRF_CPU_CON(3), &big_bits); + if (ret != 0) { + FREQ_ERR("Cpufreq couldn't read to GRF\n"); + return -1; + } + ret = regmap_read(grf_regmap, RK3368_GRF_CPU_CON(1), + &litt_bits); + if (ret != 0) { + FREQ_ERR("Cpufreq couldn't read to GRF\n"); + return -1; + } + + big_bits = (big_bits >> 8) & 0x03; + litt_bits = (litt_bits >> 8) & 0x03; + + if (big_bits == 0x01 && litt_bits == 0x00) + big_little = 1; + else if (big_bits == 0x0 && litt_bits == 0x01) + big_little = 0; + pr_info("cpufreq: boot from %d\n", big_little); + + register_reboot_notifier(&cpufreq_reboot_notifier); + register_pm_notifier(&cpufreq_pm_notifier); + + return cpufreq_register_driver(&cpufreq_driver); +} + +static int cpufreq_remove(struct platform_device *pdev) +{ + cpufreq_unregister_driver(&cpufreq_driver); + return 0; +} + +static struct platform_driver cpufreq_platdrv = { + .driver = { + .name = "rockchip-bl-cpufreq", + .owner = THIS_MODULE, + .of_match_table = cpufreq_match, + }, + .probe = cpufreq_probe, + .remove = cpufreq_remove, +}; +module_platform_driver(cpufreq_platdrv); + +MODULE_AUTHOR("Xiao Feng "); +MODULE_LICENSE("GPL");