/* linux/arch/arm/mach-s5pv310/pm-hotplug.c * * Copyright (c) 2010 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * S5PV310 - Dynamic CPU hotpluging * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #define CPUMON 1 #define CHECK_DELAY (.5*HZ) #define TRANS_LOAD_L 20 #define TRANS_LOAD_H 50 #define HOTPLUG_UNLOCKED 0 #define HOTPLUG_LOCKED 1 static struct workqueue_struct *hotplug_wq; static struct delayed_work hotplug_work; static unsigned int hotpluging_rate = CHECK_DELAY; module_param_named(rate, hotpluging_rate, uint, 0644); static unsigned int user_lock; module_param_named(lock, user_lock, uint, 0644); static unsigned int trans_load_l = TRANS_LOAD_L; module_param_named(loadl, trans_load_l, uint, 0644); static unsigned int trans_load_h = TRANS_LOAD_H; module_param_named(loadh, trans_load_h, uint, 0644); struct cpu_time_info { cputime64_t prev_cpu_idle; cputime64_t prev_cpu_wall; unsigned int load; }; static DEFINE_PER_CPU(struct cpu_time_info, hotplug_cpu_time); /* mutex can be used since hotplug_timer does not run in timer(softirq) context but in process context */ static DEFINE_MUTEX(hotplug_lock); static void hotplug_timer(struct work_struct *work) { unsigned int i, avg_load = 0, load = 0; unsigned int cur_freq; mutex_lock(&hotplug_lock); if (user_lock == 1) goto no_hotplug; for_each_online_cpu(i) { struct cpu_time_info *tmp_info; cputime64_t cur_wall_time, cur_idle_time; unsigned int idle_time, wall_time; tmp_info = &per_cpu(hotplug_cpu_time, i); cur_idle_time = get_cpu_idle_time_us(i, &cur_wall_time); idle_time = (unsigned int)cputime64_sub(cur_idle_time, tmp_info->prev_cpu_idle); tmp_info->prev_cpu_idle = cur_idle_time; wall_time = (unsigned int)cputime64_sub(cur_wall_time, tmp_info->prev_cpu_wall); tmp_info->prev_cpu_wall = cur_wall_time; if (wall_time < idle_time) goto no_hotplug; tmp_info->load = 100 * (wall_time - idle_time) / wall_time; load += tmp_info->load; } avg_load = load / num_online_cpus(); cur_freq = cpufreq_get(0); if (((avg_load < trans_load_l) || (cur_freq <= 200 * 1000)) && (cpu_online(1) == 1)) { printk(KERN_INFO "cpu1 turning off!\n"); cpu_down(1); #if CPUMON printk(KERN_ERR "CPUMON D %d\n", avg_load); #endif printk(KERN_INFO "cpu1 off end!\n"); hotpluging_rate = CHECK_DELAY; } else if (((avg_load > trans_load_h) && (cur_freq > 200 * 1000)) && (cpu_online(1) == 0)) { printk(KERN_INFO "cpu1 turning on!\n"); cpu_up(1); #if CPUMON printk(KERN_ERR "CPUMON U %d\n", avg_load); #endif printk(KERN_INFO "cpu1 on end!\n"); hotpluging_rate = CHECK_DELAY * 4; } no_hotplug: queue_delayed_work_on(0, hotplug_wq, &hotplug_work, hotpluging_rate); mutex_unlock(&hotplug_lock); } static int exynos4_pm_hotplug_notifier_event(struct notifier_block *this, unsigned long event, void *ptr) { static unsigned user_lock_saved; switch (event) { case PM_SUSPEND_PREPARE: mutex_lock(&hotplug_lock); user_lock_saved = user_lock; user_lock = 1; pr_info("%s: saving pm_hotplug lock %x\n", __func__, user_lock_saved); mutex_unlock(&hotplug_lock); return NOTIFY_OK; case PM_POST_RESTORE: case PM_POST_SUSPEND: mutex_lock(&hotplug_lock); pr_info("%s: restoring pm_hotplug lock %x\n", __func__, user_lock_saved); user_lock = user_lock_saved; mutex_unlock(&hotplug_lock); return NOTIFY_OK; } return NOTIFY_DONE; } static struct notifier_block exynos4_pm_hotplug_notifier = { .notifier_call = exynos4_pm_hotplug_notifier_event, }; static int hotplug_reboot_notifier_call(struct notifier_block *this, unsigned long code, void *_cmd) { mutex_lock(&hotplug_lock); pr_err("%s: disabling pm hotplug\n", __func__); user_lock = 1; mutex_unlock(&hotplug_lock); return NOTIFY_DONE; } static struct notifier_block hotplug_reboot_notifier = { .notifier_call = hotplug_reboot_notifier_call, }; static int __init exynos4_pm_hotplug_init(void) { printk(KERN_INFO "EXYNOS4 PM-hotplug init function\n"); /* hotplug_wq = create_workqueue("dynamic hotplug"); */ hotplug_wq = alloc_workqueue("dynamic hotplug", 0, 0); if (!hotplug_wq) { printk(KERN_ERR "Creation of hotplug work failed\n"); return -EFAULT; } INIT_DELAYED_WORK(&hotplug_work, hotplug_timer); queue_delayed_work_on(0, hotplug_wq, &hotplug_work, 60 * HZ); register_pm_notifier(&exynos4_pm_hotplug_notifier); register_reboot_notifier(&hotplug_reboot_notifier); return 0; } late_initcall(exynos4_pm_hotplug_init); static struct platform_device exynos4_pm_hotplug_device = { .name = "exynos4-dynamic-cpu-hotplug", .id = -1, }; static int __init exynos4_pm_hotplug_device_init(void) { int ret; ret = platform_device_register(&exynos4_pm_hotplug_device); if (ret) { printk(KERN_ERR "failed at(%d)\n", __LINE__); return ret; } printk(KERN_INFO "exynos4_pm_hotplug_device_init: %d\n", ret); return ret; } late_initcall(exynos4_pm_hotplug_device_init);