diff options
Diffstat (limited to 'drivers/cpufreq/cpufreq_userspace.c')
-rw-r--r-- | drivers/cpufreq/cpufreq_userspace.c | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/drivers/cpufreq/cpufreq_userspace.c b/drivers/cpufreq/cpufreq_userspace.c new file mode 100644 index 000000000000..d32bf3593cd3 --- /dev/null +++ b/drivers/cpufreq/cpufreq_userspace.c @@ -0,0 +1,207 @@ +/* + * linux/drivers/cpufreq/cpufreq_userspace.c + * + * Copyright (C) 2001 Russell King + * (C) 2002 - 2004 Dominik Brodowski <linux@brodo.de> + * + * 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/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/cpufreq.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/sysfs.h> + +#include <asm/uaccess.h> + + +/** + * A few values needed by the userspace governor + */ +static unsigned int cpu_max_freq[NR_CPUS]; +static unsigned int cpu_min_freq[NR_CPUS]; +static unsigned int cpu_cur_freq[NR_CPUS]; /* current CPU freq */ +static unsigned int cpu_set_freq[NR_CPUS]; /* CPU freq desired by userspace */ +static unsigned int cpu_is_managed[NR_CPUS]; +static struct cpufreq_policy current_policy[NR_CPUS]; + +static DECLARE_MUTEX (userspace_sem); + +#define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_GOVERNOR, "userspace", msg) + +/* keep track of frequency transitions */ +static int +userspace_cpufreq_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct cpufreq_freqs *freq = data; + + dprintk("saving cpu_cur_freq of cpu %u to be %u kHz\n", freq->cpu, freq->new); + cpu_cur_freq[freq->cpu] = freq->new; + + return 0; +} + +static struct notifier_block userspace_cpufreq_notifier_block = { + .notifier_call = userspace_cpufreq_notifier +}; + + +/** + * cpufreq_set - set the CPU frequency + * @freq: target frequency in kHz + * @cpu: CPU for which the frequency is to be set + * + * Sets the CPU frequency to freq. + */ +static int cpufreq_set(unsigned int freq, unsigned int cpu) +{ + int ret = -EINVAL; + + dprintk("cpufreq_set for cpu %u, freq %u kHz\n", cpu, freq); + + down(&userspace_sem); + if (!cpu_is_managed[cpu]) + goto err; + + cpu_set_freq[cpu] = freq; + + if (freq < cpu_min_freq[cpu]) + freq = cpu_min_freq[cpu]; + if (freq > cpu_max_freq[cpu]) + freq = cpu_max_freq[cpu]; + + /* + * We're safe from concurrent calls to ->target() here + * as we hold the userspace_sem lock. If we were calling + * cpufreq_driver_target, a deadlock situation might occur: + * A: cpufreq_set (lock userspace_sem) -> cpufreq_driver_target(lock policy->lock) + * B: cpufreq_set_policy(lock policy->lock) -> __cpufreq_governor -> cpufreq_governor_userspace (lock userspace_sem) + */ + ret = __cpufreq_driver_target(¤t_policy[cpu], freq, + CPUFREQ_RELATION_L); + + err: + up(&userspace_sem); + return ret; +} + + +/************************** sysfs interface ************************/ +static ssize_t show_speed (struct cpufreq_policy *policy, char *buf) +{ + return sprintf (buf, "%u\n", cpu_cur_freq[policy->cpu]); +} + +static ssize_t +store_speed (struct cpufreq_policy *policy, const char *buf, size_t count) +{ + unsigned int freq = 0; + unsigned int ret; + + ret = sscanf (buf, "%u", &freq); + if (ret != 1) + return -EINVAL; + + cpufreq_set(freq, policy->cpu); + + return count; +} + +static struct freq_attr freq_attr_scaling_setspeed = +{ + .attr = { .name = "scaling_setspeed", .mode = 0644, .owner = THIS_MODULE }, + .show = show_speed, + .store = store_speed, +}; + +static int cpufreq_governor_userspace(struct cpufreq_policy *policy, + unsigned int event) +{ + unsigned int cpu = policy->cpu; + switch (event) { + case CPUFREQ_GOV_START: + if (!cpu_online(cpu)) + return -EINVAL; + BUG_ON(!policy->cur); + down(&userspace_sem); + cpu_is_managed[cpu] = 1; + cpu_min_freq[cpu] = policy->min; + cpu_max_freq[cpu] = policy->max; + cpu_cur_freq[cpu] = policy->cur; + cpu_set_freq[cpu] = policy->cur; + sysfs_create_file (&policy->kobj, &freq_attr_scaling_setspeed.attr); + memcpy (¤t_policy[cpu], policy, sizeof(struct cpufreq_policy)); + dprintk("managing cpu %u started (%u - %u kHz, currently %u kHz)\n", cpu, cpu_min_freq[cpu], cpu_max_freq[cpu], cpu_cur_freq[cpu]); + up(&userspace_sem); + break; + case CPUFREQ_GOV_STOP: + down(&userspace_sem); + cpu_is_managed[cpu] = 0; + cpu_min_freq[cpu] = 0; + cpu_max_freq[cpu] = 0; + cpu_set_freq[cpu] = 0; + sysfs_remove_file (&policy->kobj, &freq_attr_scaling_setspeed.attr); + dprintk("managing cpu %u stopped\n", cpu); + up(&userspace_sem); + break; + case CPUFREQ_GOV_LIMITS: + down(&userspace_sem); + cpu_min_freq[cpu] = policy->min; + cpu_max_freq[cpu] = policy->max; + dprintk("limit event for cpu %u: %u - %u kHz, currently %u kHz, last set to %u kHz\n", cpu, cpu_min_freq[cpu], cpu_max_freq[cpu], cpu_cur_freq[cpu], cpu_set_freq[cpu]); + if (policy->max < cpu_set_freq[cpu]) { + __cpufreq_driver_target(¤t_policy[cpu], policy->max, + CPUFREQ_RELATION_H); + } else if (policy->min > cpu_set_freq[cpu]) { + __cpufreq_driver_target(¤t_policy[cpu], policy->min, + CPUFREQ_RELATION_L); + } else { + __cpufreq_driver_target(¤t_policy[cpu], cpu_set_freq[cpu], + CPUFREQ_RELATION_L); + } + memcpy (¤t_policy[cpu], policy, sizeof(struct cpufreq_policy)); + up(&userspace_sem); + break; + } + return 0; +} + + +struct cpufreq_governor cpufreq_gov_userspace = { + .name = "userspace", + .governor = cpufreq_governor_userspace, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL(cpufreq_gov_userspace); + +static int __init cpufreq_gov_userspace_init(void) +{ + cpufreq_register_notifier(&userspace_cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); + return cpufreq_register_governor(&cpufreq_gov_userspace); +} + + +static void __exit cpufreq_gov_userspace_exit(void) +{ + cpufreq_unregister_governor(&cpufreq_gov_userspace); + cpufreq_unregister_notifier(&userspace_cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); +} + + +MODULE_AUTHOR ("Dominik Brodowski <linux@brodo.de>, Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION ("CPUfreq policy governor 'userspace'"); +MODULE_LICENSE ("GPL"); + +fs_initcall(cpufreq_gov_userspace_init); +module_exit(cpufreq_gov_userspace_exit); |