aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c')
-rw-r--r--arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c b/arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c
new file mode 100644
index 0000000..7c380ae
--- /dev/null
+++ b/arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c
@@ -0,0 +1,322 @@
+/* linux/arch/arm/mach-exynos/dynamic-dvfs-nr_running-hotplug.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4 - Integrated DVFS CPU hotplug
+ *
+ * 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 <linux/sched.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu.h>
+#include <linux/err.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/suspend.h>
+#include <linux/io.h>
+
+#ifdef CONFIG_SLP
+#include <linux/platform_device.h>
+#endif
+
+#include <plat/cpu.h>
+
+static unsigned int total_num_target_freq;
+static unsigned int ctn_freq_in_trg_cnt; /* continuous frequency hotplug in trigger count */
+static unsigned int ctn_freq_out_trg_cnt; /* continuous frequency hotplug out trigger count */
+static unsigned int ctn_nr_running_over2;
+static unsigned int ctn_nr_running_over3;
+static unsigned int ctn_nr_running_over4;
+static unsigned int ctn_nr_running_under2;
+static unsigned int ctn_nr_running_under3;
+static unsigned int ctn_nr_running_under4;
+static unsigned int freq_max; /* max frequency of the dedicated dvfs table */
+static unsigned int freq_min = -1UL; /* min frequency of the dedicated dvfs table */
+static unsigned int freq_in_trg; /* frequency hotplug in trigger */
+static unsigned int freq_out_trg; /* frequency hotplug out trigger */
+static unsigned int can_hotplug;
+#ifdef CONFIG_SLP
+static unsigned int user_lock; /* Enable/Disable hotplug */
+#endif
+
+static void exynos4_integrated_dvfs_hotplug(unsigned int freq_old,
+ unsigned int freq_new)
+{
+
+ total_num_target_freq++;
+ freq_in_trg = 800000; /* tunnable */
+ freq_out_trg = freq_min; /* tunnable */
+
+ if (nr_running() <= 1) {
+ ctn_nr_running_over2 = 0;
+ ctn_nr_running_over3 = 0;
+ ctn_nr_running_over4 = 0;
+ ctn_nr_running_under2++;
+ ctn_nr_running_under3++;
+ ctn_nr_running_under4++;
+ } else if ((nr_running() > 1) && (nr_running() <= 2)) {
+ ctn_nr_running_over2++;
+ ctn_nr_running_over3 = 0;
+ ctn_nr_running_over4 = 0;
+ ctn_nr_running_under2 = 0;
+ ctn_nr_running_under3++;
+ ctn_nr_running_under4++;
+ } else if ((nr_running() > 2) && (nr_running() <= 3)) {
+ ctn_nr_running_over2++;
+ ctn_nr_running_over3++;
+ ctn_nr_running_over4 = 0;
+ ctn_nr_running_under2 = 0;
+ ctn_nr_running_under3 = 0;
+ ctn_nr_running_under4++;
+ } else if (nr_running() > 3) {
+ ctn_nr_running_over2++;
+ ctn_nr_running_over3++;
+ ctn_nr_running_over4++;
+ ctn_nr_running_under2 = 0;
+ ctn_nr_running_under3 = 0;
+ ctn_nr_running_under4 = 0;
+ }
+
+ if ((freq_old >= freq_in_trg) && (freq_new >= freq_in_trg))
+ ctn_freq_in_trg_cnt++;
+ else
+ ctn_freq_in_trg_cnt = 0;
+ if ((freq_old <= freq_out_trg) && (freq_new <= freq_out_trg))
+ ctn_freq_out_trg_cnt++;
+ else
+ ctn_freq_out_trg_cnt = 0;
+
+ if (soc_is_exynos4412()) {
+ if ((cpu_online(3) == 0) && (nr_running() >= 2) &&
+ ((freq_old >= freq_in_trg) && (freq_new >= freq_in_trg))) {
+ if ((ctn_nr_running_over2 >= 4) &&
+ (ctn_freq_in_trg_cnt >= 5)) {
+ /* over 400ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_up(3);
+ ctn_freq_in_trg_cnt = 0;
+ }
+ } else if ((cpu_online(2) == 0) && (nr_running() >= 3) &&
+ ((freq_old >= freq_in_trg) && (freq_new >= freq_in_trg))) {
+ if ((ctn_nr_running_over3 >= 4) &&
+ (ctn_freq_in_trg_cnt >= 5)) {
+ /* over 400ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_up(2);
+ ctn_freq_in_trg_cnt = 0;
+ }
+ } else if ((cpu_online(1) == 0) && (nr_running() >= 4) &&
+ ((freq_old >= freq_in_trg) && (freq_new >= freq_in_trg))) {
+ if ((ctn_nr_running_over4 >= 8) &&
+ (ctn_freq_in_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_up(1);
+ ctn_freq_in_trg_cnt = 0;
+ }
+ }
+ } else {
+ if ((cpu_online(1) == 0) && ((freq_old >= freq_in_trg) &&
+ (freq_new >= freq_in_trg))) {
+ if ((ctn_nr_running_over2 >= 8) &&
+ (ctn_freq_in_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_up(1);
+ ctn_nr_running_over2 = 0;
+ ctn_freq_in_trg_cnt = 0;
+ }
+ }
+ }
+
+ if (soc_is_exynos4412()) {
+ if ((cpu_online(1) == 1) && (nr_running() < 4) &&
+ ((freq_old <= freq_out_trg) && (freq_new <= freq_out_trg))) {
+ if ((ctn_nr_running_under4 >= 8) &&
+ (ctn_freq_out_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_down(1);
+ ctn_freq_out_trg_cnt = 0;
+ }
+ } else if ((cpu_online(2) == 1) && (nr_running() < 3) &&
+ ((freq_old <= freq_out_trg) && (freq_new <= freq_out_trg))) {
+ if ((ctn_nr_running_under3 >= 8) &&
+ (ctn_freq_out_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_down(2);
+ ctn_freq_out_trg_cnt = 0;
+ }
+ } else if ((cpu_online(3) == 1) && (nr_running() < 2) &&
+ ((freq_old <= freq_out_trg) && (freq_new <= freq_out_trg))) {
+ if ((ctn_nr_running_under2 >= 8) &&
+ (ctn_freq_out_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_down(3);
+ ctn_freq_out_trg_cnt = 0;
+ }
+ }
+ } else {
+ if ((cpu_online(1) == 1) &&
+ ((freq_old <= freq_out_trg) && (freq_new <= freq_out_trg))) {
+ if ((ctn_nr_running_under2 >= 8) &&
+ (ctn_freq_out_trg_cnt >= 5)) {
+ /* over 800ms for nr_running(), over 500ms for frequency, tunnable */
+ cpu_down(1);
+ ctn_nr_running_under2 = 0;
+ ctn_freq_out_trg_cnt = 0;
+ }
+ }
+ }
+}
+
+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) && can_hotplug)
+ exynos4_integrated_dvfs_hotplug(freqs->old, freqs->new);
+
+ return 0;
+}
+
+static struct notifier_block dvfs_hotplug = {
+ .notifier_call = hotplug_cpufreq_transition,
+};
+
+static int hotplug_pm_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ switch (val) {
+ case PM_SUSPEND_PREPARE:
+ can_hotplug = 0;
+ ctn_freq_in_trg_cnt = 0;
+ ctn_freq_out_trg_cnt = 0;
+ break;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+#ifdef CONFIG_SLP
+ if (!user_lock)
+#endif
+ can_hotplug = 1;
+ break;
+ }
+
+ return 0;
+}
+
+static struct notifier_block pm_hotplug = {
+ .notifier_call = hotplug_pm_transition,
+};
+
+#ifdef CONFIG_SLP
+static ssize_t user_lock_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", user_lock);
+}
+
+static ssize_t user_lock_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+
+ ret = sscanf(buf, "%u", &user_lock);
+ if (ret != 1)
+ return -EINVAL;
+
+ if (user_lock)
+ can_hotplug = 0;
+ else
+ can_hotplug = 1;
+
+ return count;
+}
+static DEVICE_ATTR(user_lock, 0644, user_lock_show, user_lock_store);
+
+static int sysfs_pm_hotplug_create(struct device *dev)
+{
+ int ret;
+
+ ret = device_create_file(dev, &dev_attr_user_lock);
+
+ if (ret)
+ device_remove_file(dev, &dev_attr_user_lock);
+
+ return ret;
+}
+
+static void sysfs_pm_hotplug_remove(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_user_lock);
+}
+
+static struct platform_device exynos_pm_hotplug_device = {
+ .name = "exynos-dynamic-cpu-hotplug",
+ .id = -1,
+};
+#endif
+
+/*
+ * 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_integrated_dvfs_hotplug_init(void)
+{
+ int i;
+ struct cpufreq_frequency_table *table;
+ unsigned int freq;
+
+#ifdef CONFIG_SLP
+ int ret;
+#endif
+
+ total_num_target_freq = 0;
+ ctn_freq_in_trg_cnt = 0;
+ ctn_freq_out_trg_cnt = 0;
+ ctn_nr_running_over2 = 0;
+ ctn_nr_running_over3 = 0;
+ ctn_nr_running_over4 = 0;
+ ctn_nr_running_under2 = 0;
+ ctn_nr_running_under3 = 0;
+ ctn_nr_running_under4 = 0;
+
+ 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;
+ }
+
+ printk(KERN_INFO "%s, max(%d),min(%d)\n", __func__, freq_max, freq_min);
+
+#ifdef CONFIG_SLP
+ ret = platform_device_register(&exynos_pm_hotplug_device);
+ if (ret) {
+ printk(KERN_ERR "failed register pd\n");
+ return ret;
+ }
+
+ ret = sysfs_pm_hotplug_create(&exynos_pm_hotplug_device.dev);
+ if (ret)
+ printk(KERN_ERR "failed at(%d)\n", __LINE__);
+#endif
+
+ register_pm_notifier(&pm_hotplug);
+
+ return cpufreq_register_notifier(&dvfs_hotplug,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+late_initcall(exynos4_integrated_dvfs_hotplug_init);