From e2160994bb97eaa2bdd89111fe4c969bd6454956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Gezer?= Date: Thu, 18 Apr 2013 14:07:56 +0300 Subject: [PATCH] Added DVFS CPU hot-plug driver for EXYNOS4x12 Driver launches a repeatitive timer event, and watches policy and fequency changes in order to deploy cpu hot-plugging for power saving. Have tunables to tune the hotplug policy in minimal level. TODO: tunables will be exported to sysfs for easy userland reconfiguration. TODO: anticipation step can be improved. ISSUES: Current cpuidle driver on EXYNOS have extra "POWER DOWN" state for cpu0 when all other cores are down! So if hotplug driver shuts down all cores except core0, then system tries to put cpu0 into power down mode which results in oops. There are two ways to overcome that: 1- setting cpu_min_count of this hotplug driver's tunable to 2, 2- removing this extra state from exynos cpuidle driver. I choosed the first option to be not intrusive at the moment. However tried second approach and it works fine too. --- drivers/cpufreq/Kconfig.arm | 9 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/exynos4x12-dvfs-hotplug.c | 348 ++++++++++++++++++++++ 3 files changed, 358 insertions(+) create mode 100644 drivers/cpufreq/exynos4x12-dvfs-hotplug.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index a0b3661d90b0e9..03dea5c6f636df 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -71,6 +71,15 @@ config ARM_EXYNOS4X12_CPUFREQ This adds the CPUFreq driver for Samsung EXYNOS4X12 SoC (EXYNOS4212 or EXYNOS4412). +config ARM_EXYNOS4x12_DVFS_HOTPLUG + bool "DVFS hotplug driver" + depends on ARM_EXYNOS4X12_CPUFREQ && HOTPLUG_CPU + default y + help + This adds the CPUFreq DVFS hotplug capability for Samsung EXYNOS4x12 + SoC (EXYNOS4212 or EXYNOS4412). + + config ARM_EXYNOS5250_CPUFREQ def_bool SOC_EXYNOS5250 help diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index fadc4d496e2fc5..4093d66a19d100 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -52,6 +52,7 @@ 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_EXYNOS4x12_DVFS_HOTPLUG) += exynos4x12-dvfs-hotplug.o ################################################################################## # PowerPC platform drivers diff --git a/drivers/cpufreq/exynos4x12-dvfs-hotplug.c b/drivers/cpufreq/exynos4x12-dvfs-hotplug.c new file mode 100644 index 00000000000000..a3fdece7fc1108 --- /dev/null +++ b/drivers/cpufreq/exynos4x12-dvfs-hotplug.c @@ -0,0 +1,348 @@ +/* + * drivers/cpufreq/exynos4x12-dvfs-hotplug.c + * + * DVFS cpu-hotplug driver for Samsung Exynos 4x12 SoCs + * + * Author: Gokturk Gezer + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// tunables +static unsigned int hotplug_min_cpu_count; +static unsigned int hotplug_max_cpu_count; +static unsigned int hotplug_freq_load_tolerance; +static unsigned int hotplug_tick_interval; +static unsigned int hotplug_tick_anticipation; + +// cpufreq state +static char governor_name[CPUFREQ_NAME_LEN]; +static unsigned int freq_current; +static unsigned int freq_min; +static unsigned int freq_max; + +// hotplug state +static int freq_out_target; +static int freq_out_limit; +static int freq_in_target; +static int freq_in_limit; +static unsigned int can_hotplug; +static struct delayed_work hotplug_dynamic_tick_work; +static struct delayed_work hotplug_fixed_tick_work; +void (*dynamic_tick_step)(void); +static unsigned int fixed_tick_cpu_count; + +// function declerations +static void dynamic_hotplug_work(); +static void fixed_hotplug_work(); +static void start_hotplug_dynamic_tick(); +static void start_hotplug_fixed_tick(unsigned int); +static void stop_hotplug_ticks(); +static void boot_fixed_cores(); +static void cpu_increase(); +static void cpu_decrease(); +static void hotplug_deploy(struct cpufreq_policy*); + +static void __hotplug_tick_step_freq_track() +{ + unsigned int tolerated_freq_in, tolerated_freq_out; + + tolerated_freq_in = freq_max / 100 * hotplug_freq_load_tolerance; + tolerated_freq_out = freq_max / 100 * (hotplug_freq_load_tolerance - 20); + if (tolerated_freq_out < freq_min) + tolerated_freq_out = freq_min; + + if (freq_current >= tolerated_freq_in) + { + if (freq_out_target > 0) + freq_out_target = 0; + + if (++freq_in_target == freq_in_limit) + { + cpu_increase(); + freq_in_target = 0; + + if (hotplug_tick_anticipation) + freq_out_target = -1 * freq_out_limit; + } + } + else if (freq_current <= tolerated_freq_out) + { + freq_in_target = 0; + if (++freq_out_target == freq_out_limit) + { + cpu_decrease(); + freq_out_target = 0; + } + } +} + +static void dynamic_hotplug_work() +{ + (*dynamic_tick_step)(); + + start_hotplug_dynamic_tick(); +} + +static void fixed_hotplug_work() +{ + boot_fixed_cores(); +} + +static void start_hotplug_dynamic_tick() +{ + schedule_delayed_work_on(0, &hotplug_dynamic_tick_work, + msecs_to_jiffies(hotplug_tick_interval)); +} + +static void start_hotplug_fixed_tick(unsigned int cpu_count) +{ + fixed_tick_cpu_count = cpu_count; + + schedule_delayed_work_on(0, &hotplug_fixed_tick_work, + msecs_to_jiffies(500)); +} + +static void stop_hotplug_ticks() +{ + cancel_delayed_work_sync(&hotplug_dynamic_tick_work); + cancel_delayed_work_sync(&hotplug_fixed_tick_work); +} + +static void boot_fixed_cores() +{ + int operation_count; + unsigned int i,online_count; + + void (*fix_operation)(void) = cpu_increase; + + for(i = 0, online_count = 0; i < 4; i++) + { + if(cpu_online(i)) + online_count++; + } + + operation_count = fixed_tick_cpu_count - online_count; + if(operation_count < 0) + { + operation_count *= -1; + fix_operation = cpu_decrease; + } + + for(i = 0; i < operation_count; i++) + (*fix_operation)(); +} + +static void cpu_increase() +{ + unsigned int i; + + if(num_online_cpus() >= hotplug_max_cpu_count) + return; + + for(i = 0; i < 4; i++) + { + if(!cpu_online(i)) + { + cpu_up(i); + break; + } + } +} + +static void cpu_decrease() +{ + unsigned int i; + + if(num_online_cpus() <= hotplug_min_cpu_count) + return; + + for(i = 3; i >= 0; i--) + { + if(cpu_online(i)) + { + cpu_down(i); + break; + } + } +} + +static void hotplug_deploy(struct cpufreq_policy * policy) +{ + unsigned int cpu; + + + /* + * no governor, no hot-plug, all cores up + */ + if (!policy->governor) + { + stop_hotplug_ticks(); + + for_each_cpu_mask(cpu, policy->cpus[0]) + { + if (!cpu_online(cpu)) + cpu_up(cpu); + } + + return; + } + + freq_max = policy->max; + freq_min = policy->min; + + if( 0 != strnicmp(policy->governor->name, governor_name, CPUFREQ_NAME_LEN)) + { + stop_hotplug_ticks(); + + strncpy(governor_name, policy->governor->name, CPUFREQ_NAME_LEN); + + if (0 == strnicmp(governor_name, "performance", CPUFREQ_NAME_LEN)) + { + start_hotplug_fixed_tick(hotplug_max_cpu_count); + } + else if (0 == strnicmp(governor_name, "powersave", CPUFREQ_NAME_LEN)) + { + start_hotplug_fixed_tick(hotplug_min_cpu_count); + } + else + { + dynamic_tick_step = __hotplug_tick_step_freq_track; + start_hotplug_dynamic_tick(); + } + } +} + +static int hotplug_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_freqs *freqs = (struct cpufreq_freqs *) data; + + if ((val == CPUFREQ_POSTCHANGE)) + freq_current = freqs->new; + + return 0; +} + +static int hotplug_cpufreq_policy(struct notifier_block *nb, unsigned long val, void * data) +{ + struct cpufreq_policy * policy = (struct cpufreq_policy*) data; + + if (val != CPUFREQ_ADJUST) + return 0; + + + hotplug_deploy(policy); + + return 0; +} + +static int hotplug_pm_transition(struct notifier_block *nb, unsigned long val, void *data) +{ + switch (val) { + case PM_SUSPEND_PREPARE: + stop_hotplug_ticks(); + can_hotplug = 0; + freq_out_target = 0; + freq_in_target = 0; + break; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + can_hotplug = 1; + start_hotplug_dynamic_tick(); + break; + } + + return 0; +} + +static struct notifier_block dvfs_hotplug = { .notifier_call = + hotplug_cpufreq_transition, }; + +static struct notifier_block dvfs_policy_change = + { .notifier_call = hotplug_cpufreq_policy, }; + +static struct notifier_block pm_hotplug = + { .notifier_call = hotplug_pm_transition, }; + +/* + * Note : This function should be called after intialization of CPUFreq + * driver for exynos4. The cpufreq_frequency_table for exynos4 should be + * established before calling this function. + */ +static int __init exynos4_dvfs_hotplug_init(void) +{ + int i, register_result = 0; + struct cpufreq_frequency_table *table; + unsigned int freq; + struct cpufreq_policy policy; + + hotplug_min_cpu_count = 2; + if(soc_is_exynos4412()) + hotplug_max_cpu_count = 4; + else + hotplug_max_cpu_count = 2; + hotplug_freq_load_tolerance = 60; + hotplug_tick_interval = 200; + hotplug_tick_anticipation = 1; + + freq_out_target = 0; + freq_out_limit = 3; + freq_in_target = 0; + freq_in_limit = 3; + can_hotplug = 1; + + table = cpufreq_frequency_get_table(0); + if (IS_ERR(table)) + { + printk(KERN_ERR "%s: Check loading cpufreq before\n", __func__); + return PTR_ERR(table); + } + + for (i=0; table[i].frequency != CPUFREQ_TABLE_END; i++) + { + freq = table[i].frequency; + + if (freq != CPUFREQ_ENTRY_INVALID && freq > freq_max) + freq_max = freq; + else if (freq != CPUFREQ_ENTRY_INVALID && freq_min > freq) + freq_min = freq; + } + + freq_current = freq_min; + + INIT_DEFERRABLE_WORK(&hotplug_dynamic_tick_work, dynamic_hotplug_work); + INIT_DEFERRABLE_WORK(&hotplug_fixed_tick_work, fixed_hotplug_work); + + printk(KERN_INFO "%s, max(%d),min(%d)\n", __func__, freq_max, freq_min); + + register_result |= register_pm_notifier(&pm_hotplug); + + register_result |= cpufreq_register_notifier(&dvfs_policy_change, + CPUFREQ_POLICY_NOTIFIER); + + register_result |= cpufreq_register_notifier(&dvfs_hotplug, + CPUFREQ_TRANSITION_NOTIFIER); + + cpufreq_get_policy(&policy, 0); + hotplug_deploy(&policy); + + return register_result; + +} + +late_initcall(exynos4_dvfs_hotplug_init);