blob: 5a1118709aed62445c5ff1d2ddf40ae8a8a22a75 [file] [log] [blame]
/*
* linux/kernel/cpufreq.c
*
* Copyright (C) 2001 Russell King
* (C) 2002 - 2003 Dominik Brodowski <linux@brodo.de>
*
* $Id: cpufreq.c,v 1.59 2003/01/20 17:31:48 db Exp $
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/notifier.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/slab.h>
#ifdef CONFIG_CPU_FREQ_24_API
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#endif
/**
* The "cpufreq driver" - the arch- or hardware-dependend low
* level driver of CPUFreq support, and its locking mutex.
* cpu_max_freq is in kHz.
*/
static struct cpufreq_driver *cpufreq_driver;
static DECLARE_MUTEX (cpufreq_driver_sem);
/**
* Two notifier lists: the "policy" list is involved in the
* validation process for a new CPU frequency policy; the
* "transition" list for kernel code that needs to handle
* changes to devices when the CPU clock speed changes.
* The mutex locks both lists. If both cpufreq_driver_sem
* and cpufreq_notifier_sem need to be hold, get cpufreq_driver_sem
* first.
*/
static struct notifier_block *cpufreq_policy_notifier_list;
static struct notifier_block *cpufreq_transition_notifier_list;
static DECLARE_MUTEX (cpufreq_notifier_sem);
#ifdef CONFIG_CPU_FREQ_24_API
/**
* A few values needed by the 2.4.-compatible API
*/
static unsigned int cpu_max_freq[NR_CPUS];
static unsigned int cpu_min_freq[NR_CPUS];
static unsigned int cpu_cur_freq[NR_CPUS];
#endif
LIST_HEAD(cpufreq_governor_list);
static int cpufreq_governor(unsigned int cpu, unsigned int event);
/*********************************************************************
* SYSFS INTERFACE *
*********************************************************************/
/**
* cpufreq_parse_governor - parse a governor string
*/
int cpufreq_parse_governor (char *str_governor, unsigned int *policy, struct cpufreq_governor **governor)
{
if (!strnicmp(str_governor, "performance", CPUFREQ_NAME_LEN)) {
*policy = CPUFREQ_POLICY_PERFORMANCE;
return 0;
} else if (!strnicmp(str_governor, "powersave", CPUFREQ_NAME_LEN)) {
*policy = CPUFREQ_POLICY_POWERSAVE;
return 0;
} else {
struct cpufreq_governor *t;
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !cpufreq_driver->target)
goto out;
list_for_each_entry(t, &cpufreq_governor_list, governor_list) {
if (!strnicmp(str_governor,t->name,CPUFREQ_NAME_LEN)) {
*governor = t;
*policy = CPUFREQ_POLICY_GOVERNOR;
up(&cpufreq_driver_sem);
return 0;
}
}
out:
up(&cpufreq_driver_sem);
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(cpufreq_parse_governor);
/* forward declarations */
static int cpufreq_add_dev (struct device * dev);
static int cpufreq_remove_dev (struct intf_data * dev);
/* drivers/base/cpu.c */
extern struct device_class cpu_devclass;
static struct device_interface cpufreq_interface = {
.name = "cpufreq",
.devclass = &cpu_devclass,
.add_device = &cpufreq_add_dev,
.remove_device = &cpufreq_remove_dev,
.kset = { .subsys = &cpu_devclass.subsys, },
.devnum = 0,
};
static inline int to_cpu_nr (struct device *dev)
{
struct sys_device * cpu_sys_dev = container_of(dev, struct sys_device, dev);
return (cpu_sys_dev->id);
}
/**
* cpufreq_per_cpu_attr_read() / show_##file_name() - print out cpufreq information
*
* Write out information from cpufreq_driver->policy[cpu]; object must be
* "unsigned int".
*/
#define cpufreq_per_cpu_attr_read(file_name, object) \
static ssize_t show_##file_name \
(struct device *dev, char *buf) \
{ \
unsigned int value = 0; \
\
if (!dev) \
return 0; \
\
down(&cpufreq_driver_sem); \
if (cpufreq_driver) \
value = cpufreq_driver->policy[to_cpu_nr(dev)].object; \
up(&cpufreq_driver_sem); \
\
return sprintf (buf, "%u\n", value); \
}
/**
* cpufreq_per_cpu_attr_write() / store_##file_name() - sysfs write access
*/
#define cpufreq_per_cpu_attr_write(file_name, object) \
static ssize_t store_##file_name \
(struct device *dev, const char *buf, size_t count) \
{ \
unsigned int ret = -EINVAL; \
struct cpufreq_policy policy; \
\
if (!dev) \
return 0; \
\
ret = cpufreq_get_policy(&policy, to_cpu_nr(dev)); \
if (ret) \
return ret; \
\
ret = sscanf (buf, "%u", &policy.object); \
if (ret != 1) \
return -EINVAL; \
\
ret = cpufreq_set_policy(&policy); \
if (ret) \
return ret; \
\
return count; \
}
/**
* show_scaling_governor - show the current policy for the specified CPU
*/
static ssize_t show_scaling_governor (struct device *dev, char *buf)
{
unsigned int value = 0;
char value2[CPUFREQ_NAME_LEN];
if (!dev)
return 0;
down(&cpufreq_driver_sem);
if (cpufreq_driver)
value = cpufreq_driver->policy[to_cpu_nr(dev)].policy;
if (value == CPUFREQ_POLICY_GOVERNOR)
strncpy(value2, cpufreq_driver->policy[to_cpu_nr(dev)].governor->name, CPUFREQ_NAME_LEN);
up(&cpufreq_driver_sem);
switch (value) {
case CPUFREQ_POLICY_POWERSAVE:
return sprintf(buf, "powersave\n");
case CPUFREQ_POLICY_PERFORMANCE:
return sprintf(buf, "performance\n");
case CPUFREQ_POLICY_GOVERNOR:
return sprintf(buf, "%s\n", value2);
}
return -EINVAL;
}
/**
* store_scaling_governor - store policy for the specified CPU
*/
static ssize_t
store_scaling_governor (struct device *dev, const char *buf, size_t count)
{
unsigned int ret = -EINVAL;
char str_governor[16];
struct cpufreq_policy policy;
if (!dev)
return 0;
ret = cpufreq_get_policy(&policy, to_cpu_nr(dev));
if (ret)
return ret;
ret = sscanf (buf, "%15s", str_governor);
if (ret != 1)
return -EINVAL;
if (cpufreq_parse_governor(str_governor, &policy.policy, &policy.governor))
return -EINVAL;
ret = cpufreq_set_policy(&policy);
if (ret)
return ret;
return count;
}
/**
* show_scaling_governor - show the current policy for the specified CPU
*/
static ssize_t show_scaling_driver (struct device *dev, char *buf)
{
char value[CPUFREQ_NAME_LEN];
if (!dev)
return 0;
down(&cpufreq_driver_sem);
if (cpufreq_driver)
strncpy(value, cpufreq_driver->name, CPUFREQ_NAME_LEN);
up(&cpufreq_driver_sem);
return sprintf(buf, "%s\n", value);
}
/**
* show_available_govs - show the available CPUfreq governors
*/
static ssize_t show_available_govs(struct device *dev, char *buf)
{
ssize_t i = 0;
struct cpufreq_governor *t;
if (!dev)
return 0;
i += sprintf(buf, "performance powersave");
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !cpufreq_driver->target)
goto out;
list_for_each_entry(t, &cpufreq_governor_list, governor_list) {
if (i >= (ssize_t) ((PAGE_SIZE / sizeof(char)) - (CPUFREQ_NAME_LEN + 2)))
goto out;
i += snprintf(&buf[i], CPUFREQ_NAME_LEN, " %s", t->name);
}
out:
up(&cpufreq_driver_sem);
i += sprintf(&buf[i], "\n");
return i;
}
/**
* cpufreq_per_cpu_attr_ro - read-only cpufreq per-CPU file
*/
#define cpufreq_per_cpu_attr_ro(file_name, object) \
cpufreq_per_cpu_attr_read(file_name, object) \
static DEVICE_ATTR(file_name, S_IRUGO, show_##file_name, NULL);
/**
* cpufreq_per_cpu_attr_rw - read-write cpufreq per-CPU file
*/
#define cpufreq_per_cpu_attr_rw(file_name, object) \
cpufreq_per_cpu_attr_read(file_name, object) \
cpufreq_per_cpu_attr_write(file_name, object) \
static DEVICE_ATTR(file_name, (S_IRUGO | S_IWUSR), show_##file_name, store_##file_name);
/* create the file functions */
cpufreq_per_cpu_attr_ro(cpuinfo_min_freq, cpuinfo.min_freq);
cpufreq_per_cpu_attr_ro(cpuinfo_max_freq, cpuinfo.max_freq);
cpufreq_per_cpu_attr_rw(scaling_min_freq, min);
cpufreq_per_cpu_attr_rw(scaling_max_freq, max);
static DEVICE_ATTR(scaling_governor, (S_IRUGO | S_IWUSR), show_scaling_governor, store_scaling_governor);
static DEVICE_ATTR(scaling_driver, S_IRUGO, show_scaling_driver, NULL);
static DEVICE_ATTR(available_scaling_governors, S_IRUGO, show_available_govs, NULL);
/**
* cpufreq_add_dev - add a CPU device
*
* Adds the cpufreq interface for a CPU device.
*/
static int cpufreq_add_dev (struct device * dev)
{
unsigned int cpu = to_cpu_nr(dev);
int ret = 0;
struct cpufreq_policy policy;
down(&cpufreq_driver_sem);
if (!cpufreq_driver) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
/* call driver. From then on the cpufreq must be able
* to accept all calls to ->verify and ->setpolicy for this CPU
*/
cpufreq_driver->policy[cpu].cpu = cpu;
if (cpufreq_driver->init) {
ret = cpufreq_driver->init(&cpufreq_driver->policy[cpu]);
if (ret) {
up(&cpufreq_driver_sem);
return -ENODEV;
}
}
/* set default policy on this CPU */
memcpy(&policy,
&cpufreq_driver->policy[cpu],
sizeof(struct cpufreq_policy));
if (cpufreq_driver->target)
cpufreq_governor(cpu, CPUFREQ_GOV_START);
up(&cpufreq_driver_sem);
ret = cpufreq_set_policy(&policy);
if (ret)
return -EINVAL;
down(&cpufreq_driver_sem);
/* 2.4-API init for this CPU */
#ifdef CONFIG_CPU_FREQ_24_API
cpu_min_freq[cpu] = cpufreq_driver->policy[cpu].cpuinfo.min_freq;
cpu_max_freq[cpu] = cpufreq_driver->policy[cpu].cpuinfo.max_freq;
cpu_cur_freq[cpu] = cpufreq_driver->cpu_cur_freq[cpu];
#endif
/* prepare interface data */
cpufreq_driver->policy[cpu].intf.dev = dev;
cpufreq_driver->policy[cpu].intf.intf = &cpufreq_interface;
strncpy(cpufreq_driver->policy[cpu].intf.kobj.name, cpufreq_interface.name, KOBJ_NAME_LEN);
cpufreq_driver->policy[cpu].intf.kobj.parent = &(dev->kobj);
cpufreq_driver->policy[cpu].intf.kobj.kset = &(cpufreq_interface.kset);
/* add interface */
/* currently commented out due to deadlock */
//ret = interface_add_data(&(cpufreq_driver->policy[cpu].intf));
if (ret) {
up(&cpufreq_driver_sem);
return ret;
}
/* create files */
device_create_file (dev, &dev_attr_cpuinfo_min_freq);
device_create_file (dev, &dev_attr_cpuinfo_max_freq);
device_create_file (dev, &dev_attr_scaling_min_freq);
device_create_file (dev, &dev_attr_scaling_max_freq);
device_create_file (dev, &dev_attr_scaling_governor);
device_create_file (dev, &dev_attr_scaling_driver);
device_create_file (dev, &dev_attr_available_scaling_governors);
up(&cpufreq_driver_sem);
return ret;
}
/**
* cpufreq_remove_dev - remove a CPU device
*
* Removes the cpufreq interface for a CPU device. Is called with
* cpufreq_driver_sem locked.
*/
static int cpufreq_remove_dev (struct intf_data *intf)
{
struct device * dev = intf->dev;
unsigned int cpu = to_cpu_nr(dev);
if (cpufreq_driver->target)
cpufreq_governor(cpu, CPUFREQ_GOV_STOP);
if (cpufreq_driver->exit)
cpufreq_driver->exit(&cpufreq_driver->policy[cpu]);
device_remove_file (dev, &dev_attr_cpuinfo_min_freq);
device_remove_file (dev, &dev_attr_cpuinfo_max_freq);
device_remove_file (dev, &dev_attr_scaling_min_freq);
device_remove_file (dev, &dev_attr_scaling_max_freq);
device_remove_file (dev, &dev_attr_scaling_governor);
device_remove_file (dev, &dev_attr_scaling_driver);
device_remove_file (dev, &dev_attr_available_scaling_governors);
return 0;
}
/*********************************************************************
* /proc/sys/cpu/ INTERFACE *
*********************************************************************/
#ifdef CONFIG_CPU_FREQ_24_API
/**
* 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.
*/
int cpufreq_set(unsigned int freq, unsigned int cpu)
{
struct cpufreq_policy policy;
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !freq || (cpu > NR_CPUS)) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
policy.min = freq;
policy.max = freq;
policy.policy = CPUFREQ_POLICY_POWERSAVE;
policy.cpu = cpu;
up(&cpufreq_driver_sem);
if (policy.cpu == CPUFREQ_ALL_CPUS)
{
unsigned int i;
unsigned int ret = 0;
for (i=0; i<NR_CPUS; i++)
{
policy.cpu = i;
if (cpu_online(i))
ret |= cpufreq_set_policy(&policy);
}
return ret;
}
else
return cpufreq_set_policy(&policy);
}
EXPORT_SYMBOL_GPL(cpufreq_set);
/**
* cpufreq_setmax - set the CPU to the maximum frequency
* @cpu - affected cpu;
*
* Sets the CPU frequency to the maximum frequency supported by
* this CPU.
*/
int cpufreq_setmax(unsigned int cpu)
{
if (!cpu_online(cpu) && (cpu != CPUFREQ_ALL_CPUS))
return -EINVAL;
return cpufreq_set(cpu_max_freq[cpu], cpu);
}
EXPORT_SYMBOL_GPL(cpufreq_setmax);
/**
* cpufreq_get - get the current CPU frequency (in kHz)
* @cpu: CPU number - currently without effect.
*
* Get the CPU current (static) CPU frequency
*/
unsigned int cpufreq_get(unsigned int cpu)
{
if (!cpu_online(cpu))
return -EINVAL;
return cpu_cur_freq[cpu];
}
EXPORT_SYMBOL(cpufreq_get);
#ifdef CONFIG_SYSCTL
/*********************** cpufreq_sysctl interface ********************/
static int
cpufreq_procctl(ctl_table *ctl, int write, struct file *filp,
void *buffer, size_t *lenp)
{
char buf[16], *p;
int cpu = (int) ctl->extra1;
int len, left = *lenp;
if (!left || (filp->f_pos && !write) || !cpu_online(cpu)) {
*lenp = 0;
return 0;
}
if (write) {
unsigned int freq;
len = left;
if (left > sizeof(buf))
left = sizeof(buf);
if (copy_from_user(buf, buffer, left))
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
freq = simple_strtoul(buf, &p, 0);
cpufreq_set(freq, cpu);
} else {
len = sprintf(buf, "%d\n", cpufreq_get(cpu));
if (len > left)
len = left;
if (copy_to_user(buffer, buf, len))
return -EFAULT;
}
*lenp = len;
filp->f_pos += len;
return 0;
}
static int
cpufreq_sysctl(ctl_table *table, int *name, int nlen,
void *oldval, size_t *oldlenp,
void *newval, size_t newlen, void **context)
{
int cpu = (int) table->extra1;
if (!cpu_online(cpu))
return -EINVAL;
if (oldval && oldlenp) {
size_t oldlen;
if (get_user(oldlen, oldlenp))
return -EFAULT;
if (oldlen != sizeof(unsigned int))
return -EINVAL;
if (put_user(cpufreq_get(cpu), (unsigned int *)oldval) ||
put_user(sizeof(unsigned int), oldlenp))
return -EFAULT;
}
if (newval && newlen) {
unsigned int freq;
if (newlen != sizeof(unsigned int))
return -EINVAL;
if (get_user(freq, (unsigned int *)newval))
return -EFAULT;
cpufreq_set(freq, cpu);
}
return 1;
}
/* ctl_table ctl_cpu_vars_{0,1,...,(NR_CPUS-1)} */
/* due to NR_CPUS tweaking, a lot of if/endifs are required, sorry */
CTL_TABLE_CPU_VARS(0);
#if NR_CPUS > 1
CTL_TABLE_CPU_VARS(1);
#endif
#if NR_CPUS > 2
CTL_TABLE_CPU_VARS(2);
#endif
#if NR_CPUS > 3
CTL_TABLE_CPU_VARS(3);
#endif
#if NR_CPUS > 4
CTL_TABLE_CPU_VARS(4);
#endif
#if NR_CPUS > 5
CTL_TABLE_CPU_VARS(5);
#endif
#if NR_CPUS > 6
CTL_TABLE_CPU_VARS(6);
#endif
#if NR_CPUS > 7
CTL_TABLE_CPU_VARS(7);
#endif
#if NR_CPUS > 8
CTL_TABLE_CPU_VARS(8);
#endif
#if NR_CPUS > 9
CTL_TABLE_CPU_VARS(9);
#endif
#if NR_CPUS > 10
CTL_TABLE_CPU_VARS(10);
#endif
#if NR_CPUS > 11
CTL_TABLE_CPU_VARS(11);
#endif
#if NR_CPUS > 12
CTL_TABLE_CPU_VARS(12);
#endif
#if NR_CPUS > 13
CTL_TABLE_CPU_VARS(13);
#endif
#if NR_CPUS > 14
CTL_TABLE_CPU_VARS(14);
#endif
#if NR_CPUS > 15
CTL_TABLE_CPU_VARS(15);
#endif
#if NR_CPUS > 16
CTL_TABLE_CPU_VARS(16);
#endif
#if NR_CPUS > 17
CTL_TABLE_CPU_VARS(17);
#endif
#if NR_CPUS > 18
CTL_TABLE_CPU_VARS(18);
#endif
#if NR_CPUS > 19
CTL_TABLE_CPU_VARS(19);
#endif
#if NR_CPUS > 20
CTL_TABLE_CPU_VARS(20);
#endif
#if NR_CPUS > 21
CTL_TABLE_CPU_VARS(21);
#endif
#if NR_CPUS > 22
CTL_TABLE_CPU_VARS(22);
#endif
#if NR_CPUS > 23
CTL_TABLE_CPU_VARS(23);
#endif
#if NR_CPUS > 24
CTL_TABLE_CPU_VARS(24);
#endif
#if NR_CPUS > 25
CTL_TABLE_CPU_VARS(25);
#endif
#if NR_CPUS > 26
CTL_TABLE_CPU_VARS(26);
#endif
#if NR_CPUS > 27
CTL_TABLE_CPU_VARS(27);
#endif
#if NR_CPUS > 28
CTL_TABLE_CPU_VARS(28);
#endif
#if NR_CPUS > 29
CTL_TABLE_CPU_VARS(29);
#endif
#if NR_CPUS > 30
CTL_TABLE_CPU_VARS(30);
#endif
#if NR_CPUS > 31
CTL_TABLE_CPU_VARS(31);
#endif
#if NR_CPUS > 32
#error please extend CPU enumeration
#endif
/* due to NR_CPUS tweaking, a lot of if/endifs are required, sorry */
static ctl_table ctl_cpu_table[NR_CPUS + 1] = {
CPU_ENUM(0),
#if NR_CPUS > 1
CPU_ENUM(1),
#endif
#if NR_CPUS > 2
CPU_ENUM(2),
#endif
#if NR_CPUS > 3
CPU_ENUM(3),
#endif
#if NR_CPUS > 4
CPU_ENUM(4),
#endif
#if NR_CPUS > 5
CPU_ENUM(5),
#endif
#if NR_CPUS > 6
CPU_ENUM(6),
#endif
#if NR_CPUS > 7
CPU_ENUM(7),
#endif
#if NR_CPUS > 8
CPU_ENUM(8),
#endif
#if NR_CPUS > 9
CPU_ENUM(9),
#endif
#if NR_CPUS > 10
CPU_ENUM(10),
#endif
#if NR_CPUS > 11
CPU_ENUM(11),
#endif
#if NR_CPUS > 12
CPU_ENUM(12),
#endif
#if NR_CPUS > 13
CPU_ENUM(13),
#endif
#if NR_CPUS > 14
CPU_ENUM(14),
#endif
#if NR_CPUS > 15
CPU_ENUM(15),
#endif
#if NR_CPUS > 16
CPU_ENUM(16),
#endif
#if NR_CPUS > 17
CPU_ENUM(17),
#endif
#if NR_CPUS > 18
CPU_ENUM(18),
#endif
#if NR_CPUS > 19
CPU_ENUM(19),
#endif
#if NR_CPUS > 20
CPU_ENUM(20),
#endif
#if NR_CPUS > 21
CPU_ENUM(21),
#endif
#if NR_CPUS > 22
CPU_ENUM(22),
#endif
#if NR_CPUS > 23
CPU_ENUM(23),
#endif
#if NR_CPUS > 24
CPU_ENUM(24),
#endif
#if NR_CPUS > 25
CPU_ENUM(25),
#endif
#if NR_CPUS > 26
CPU_ENUM(26),
#endif
#if NR_CPUS > 27
CPU_ENUM(27),
#endif
#if NR_CPUS > 28
CPU_ENUM(28),
#endif
#if NR_CPUS > 29
CPU_ENUM(29),
#endif
#if NR_CPUS > 30
CPU_ENUM(30),
#endif
#if NR_CPUS > 31
CPU_ENUM(31),
#endif
#if NR_CPUS > 32
#error please extend CPU enumeration
#endif
{
.ctl_name = 0,
}
};
static ctl_table ctl_cpu[2] = {
{
.ctl_name = CTL_CPU,
.procname = "cpu",
.mode = 0555,
.child = ctl_cpu_table,
},
{
.ctl_name = 0,
}
};
struct ctl_table_header *cpufreq_sysctl_table;
static inline void cpufreq_sysctl_init(void)
{
cpufreq_sysctl_table = register_sysctl_table(ctl_cpu, 0);
}
static inline void cpufreq_sysctl_exit(void)
{
unregister_sysctl_table(cpufreq_sysctl_table);
}
#else
#define cpufreq_sysctl_init() do {} while(0)
#define cpufreq_sysctl_exit() do {} while(0)
#endif /* CONFIG_SYSCTL */
#endif /* CONFIG_CPU_FREQ_24_API */
/*********************************************************************
* NOTIFIER LISTS INTERFACE *
*********************************************************************/
/**
* cpufreq_register_notifier - register a driver with cpufreq
* @nb: notifier function to register
* @list: CPUFREQ_TRANSITION_NOTIFIER or CPUFREQ_POLICY_NOTIFIER
*
* Add a driver to one of two lists: either a list of drivers that
* are notified about clock rate changes (once before and once after
* the transition), or a list of drivers that are notified about
* changes in cpufreq policy.
*
* This function may sleep, and has the same return conditions as
* notifier_chain_register.
*/
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list)
{
int ret;
down(&cpufreq_notifier_sem);
switch (list) {
case CPUFREQ_TRANSITION_NOTIFIER:
ret = notifier_chain_register(&cpufreq_transition_notifier_list, nb);
break;
case CPUFREQ_POLICY_NOTIFIER:
ret = notifier_chain_register(&cpufreq_policy_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
up(&cpufreq_notifier_sem);
return ret;
}
EXPORT_SYMBOL(cpufreq_register_notifier);
/**
* cpufreq_unregister_notifier - unregister a driver with cpufreq
* @nb: notifier block to be unregistered
* @list: CPUFREQ_TRANSITION_NOTIFIER or CPUFREQ_POLICY_NOTIFIER
*
* Remove a driver from the CPU frequency notifier list.
*
* This function may sleep, and has the same return conditions as
* notifier_chain_unregister.
*/
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list)
{
int ret;
down(&cpufreq_notifier_sem);
switch (list) {
case CPUFREQ_TRANSITION_NOTIFIER:
ret = notifier_chain_unregister(&cpufreq_transition_notifier_list, nb);
break;
case CPUFREQ_POLICY_NOTIFIER:
ret = notifier_chain_unregister(&cpufreq_policy_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
up(&cpufreq_notifier_sem);
return ret;
}
EXPORT_SYMBOL(cpufreq_unregister_notifier);
/*********************************************************************
* GOVERNORS *
*********************************************************************/
inline int cpufreq_driver_target_l(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
unsigned int ret;
down(&cpufreq_driver_sem);
if (!cpufreq_driver)
ret = -EINVAL;
else
ret = cpufreq_driver->target(policy, target_freq, relation);
up(&cpufreq_driver_sem);
return ret;
}
EXPORT_SYMBOL_GPL(cpufreq_driver_target_l);
inline int cpufreq_driver_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
return cpufreq_driver->target(policy, target_freq, relation);
}
EXPORT_SYMBOL_GPL(cpufreq_driver_target);
static int cpufreq_governor(unsigned int cpu, unsigned int event)
{
int ret = 0;
struct cpufreq_policy *policy = &cpufreq_driver->policy[cpu];
switch (policy->policy) {
case CPUFREQ_POLICY_POWERSAVE:
if ((event == CPUFREQ_GOV_LIMITS) || (event == CPUFREQ_GOV_START))
ret = cpufreq_driver->target(policy, policy->min, CPUFREQ_RELATION_L);
break;
case CPUFREQ_POLICY_PERFORMANCE:
if ((event == CPUFREQ_GOV_LIMITS) || (event == CPUFREQ_GOV_START))
ret = cpufreq_driver->target(policy, policy->max, CPUFREQ_RELATION_H);
break;
case CPUFREQ_POLICY_GOVERNOR:
ret = -EINVAL;
if (event == CPUFREQ_GOV_START)
if (!try_module_get(cpufreq_driver->policy[cpu].governor->owner))
break;
ret = cpufreq_driver->policy[cpu].governor->governor(policy, event);
if ((event == CPUFREQ_GOV_STOP) ||
(ret && (event == CPUFREQ_GOV_START)))
module_put(cpufreq_driver->policy[cpu].governor->owner);
break;
default:
ret = -EINVAL;
}
return ret;
}
int cpufreq_governor_l(unsigned int cpu, unsigned int event)
{
int ret = 0;
down(&cpufreq_driver_sem);
ret = cpufreq_governor(cpu, event);
up(&cpufreq_driver_sem);
return ret;
}
EXPORT_SYMBOL_GPL(cpufreq_governor_l);
int cpufreq_register_governor(struct cpufreq_governor *governor)
{
struct cpufreq_governor *t;
if (!governor)
return -EINVAL;
if (!strnicmp(governor->name,"powersave",CPUFREQ_NAME_LEN))
return -EBUSY;
if (!strnicmp(governor->name,"performance",CPUFREQ_NAME_LEN))
return -EBUSY;
down(&cpufreq_driver_sem);
list_for_each_entry(t, &cpufreq_governor_list, governor_list) {
if (!strnicmp(governor->name,t->name,CPUFREQ_NAME_LEN)) {
up(&cpufreq_driver_sem);
return -EBUSY;
}
}
list_add(&governor->governor_list, &cpufreq_governor_list);
up(&cpufreq_driver_sem);
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_register_governor);
void cpufreq_unregister_governor(struct cpufreq_governor *governor)
{
unsigned int i;
if (!governor)
return;
down(&cpufreq_driver_sem);
/*
* Unless the user uses rmmod -f, we can be safe. But we never
* know, so check whether if it's currently used. If so,
* stop it and replace it with the default governor.
*/
for (i=0; i<NR_CPUS; i++)
{
if (cpufreq_driver &&
(cpufreq_driver->policy[i].policy == CPUFREQ_POLICY_GOVERNOR) &&
(cpufreq_driver->policy[i].governor == governor)) {
cpufreq_governor(i, CPUFREQ_GOV_STOP);
cpufreq_driver->policy[i].policy = CPUFREQ_POLICY_PERFORMANCE;
cpufreq_governor(i, CPUFREQ_GOV_START);
}
}
/* now we can safely remove it from the list */
list_del(&governor->governor_list);
up(&cpufreq_driver_sem);
return;
}
EXPORT_SYMBOL_GPL(cpufreq_unregister_governor);
/*********************************************************************
* POLICY INTERFACE *
*********************************************************************/
/**
* cpufreq_get_policy - get the current cpufreq_policy
* @policy: struct cpufreq_policy into which the current cpufreq_policy is written
*
* Reads the current cpufreq policy.
*/
int cpufreq_get_policy(struct cpufreq_policy *policy, unsigned int cpu)
{
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !policy ||
(cpu >= NR_CPUS) || (!cpu_online(cpu))) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
memcpy(policy,
&cpufreq_driver->policy[cpu],
sizeof(struct cpufreq_policy));
up(&cpufreq_driver_sem);
return 0;
}
EXPORT_SYMBOL(cpufreq_get_policy);
/**
* cpufreq_set_policy - set a new CPUFreq policy
* @policy: policy to be set.
*
* Sets a new CPU frequency and voltage scaling policy.
*/
int cpufreq_set_policy(struct cpufreq_policy *policy)
{
int ret;
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !policy ||
(policy->cpu >= NR_CPUS) || (!cpu_online(policy->cpu))) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
memcpy(&policy->cpuinfo,
&cpufreq_driver->policy[policy->cpu].cpuinfo,
sizeof(struct cpufreq_cpuinfo));
/* verify the cpu speed can be set within this limit */
ret = cpufreq_driver->verify(policy);
if (ret) {
up(&cpufreq_driver_sem);
return ret;
}
down(&cpufreq_notifier_sem);
/* adjust if neccessary - all reasons */
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_ADJUST,
policy);
/* adjust if neccessary - hardware incompatibility*/
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_INCOMPATIBLE,
policy);
/* verify the cpu speed can be set within this limit,
which might be different to the first one */
ret = cpufreq_driver->verify(policy);
if (ret) {
up(&cpufreq_notifier_sem);
up(&cpufreq_driver_sem);
return ret;
}
/* notification of the new policy */
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_NOTIFY,
policy);
up(&cpufreq_notifier_sem);
cpufreq_driver->policy[policy->cpu].min = policy->min;
cpufreq_driver->policy[policy->cpu].max = policy->max;
#ifdef CONFIG_CPU_FREQ_24_API
cpu_cur_freq[policy->cpu] = policy->max;
#endif
if (cpufreq_driver->setpolicy) {
cpufreq_driver->policy[policy->cpu].policy = policy->policy;
ret = cpufreq_driver->setpolicy(policy);
} else {
if ((policy->policy != cpufreq_driver->policy[policy->cpu].policy) ||
((policy->policy == CPUFREQ_POLICY_GOVERNOR) && (policy->governor != cpufreq_driver->policy[policy->cpu].governor))) {
unsigned int old_pol = cpufreq_driver->policy[policy->cpu].policy;
struct cpufreq_governor *old_gov = cpufreq_driver->policy[policy->cpu].governor;
/* end old governor */
cpufreq_governor(policy->cpu, CPUFREQ_GOV_STOP);
cpufreq_driver->policy[policy->cpu].policy = policy->policy;
cpufreq_driver->policy[policy->cpu].governor = policy->governor;
/* start new governor */
if (cpufreq_governor(policy->cpu, CPUFREQ_GOV_START)) {
cpufreq_driver->policy[policy->cpu].policy = old_pol;
cpufreq_driver->policy[policy->cpu].governor = old_gov;
cpufreq_governor(policy->cpu, CPUFREQ_GOV_START);
}
/* might be a policy change, too */
cpufreq_governor(policy->cpu, CPUFREQ_GOV_LIMITS);
} else {
cpufreq_governor(policy->cpu, CPUFREQ_GOV_LIMITS);
}
}
up(&cpufreq_driver_sem);
return ret;
}
EXPORT_SYMBOL(cpufreq_set_policy);
/*********************************************************************
* EXTERNALLY AFFECTING FREQUENCY CHANGES *
*********************************************************************/
/**
* adjust_jiffies - adjust the system "loops_per_jiffy"
*
* This function alters the system "loops_per_jiffy" for the clock
* speed change. Note that loops_per_jiffy cannot be updated on SMP
* systems as each CPU might be scaled differently. So, use the arch
* per-CPU loops_per_jiffy value wherever possible.
*/
#ifndef CONFIG_SMP
static unsigned long l_p_j_ref = 0;
static unsigned int l_p_j_ref_freq = 0;
static inline void adjust_jiffies(unsigned long val, struct cpufreq_freqs *ci)
{
if (!l_p_j_ref_freq) {
l_p_j_ref = loops_per_jiffy;
l_p_j_ref_freq = ci->old;
}
if ((val == CPUFREQ_PRECHANGE && ci->old < ci->new) ||
(val == CPUFREQ_POSTCHANGE && ci->old > ci->new))
loops_per_jiffy = cpufreq_scale(l_p_j_ref, l_p_j_ref_freq, ci->new);
}
#else
#define adjust_jiffies(x...) do {} while (0)
#endif
/**
* cpufreq_notify_transition - call notifier chain and adjust_jiffies on frequency transition
*
* This function calls the transition notifiers and the "adjust_jiffies" function. It is called
* twice on all CPU frequency changes that have external effects.
*/
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state)
{
down(&cpufreq_notifier_sem);
switch (state) {
case CPUFREQ_PRECHANGE:
notifier_call_chain(&cpufreq_transition_notifier_list, CPUFREQ_PRECHANGE, freqs);
adjust_jiffies(CPUFREQ_PRECHANGE, freqs);
break;
case CPUFREQ_POSTCHANGE:
adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
notifier_call_chain(&cpufreq_transition_notifier_list, CPUFREQ_POSTCHANGE, freqs);
#ifdef CONFIG_CPU_FREQ_24_API
cpu_cur_freq[freqs->cpu] = freqs->new;
#endif
break;
}
up(&cpufreq_notifier_sem);
}
EXPORT_SYMBOL_GPL(cpufreq_notify_transition);
/*********************************************************************
* REGISTER / UNREGISTER CPUFREQ DRIVER *
*********************************************************************/
/**
* cpufreq_register_driver - register a CPU Frequency driver
* @driver_data: A struct cpufreq_driver containing the values#
* submitted by the CPU Frequency driver.
*
* Registers a CPU Frequency driver to this core code. This code
* returns zero on success, -EBUSY when another driver got here first
* (and isn't unregistered in the meantime).
*
*/
int cpufreq_register_driver(struct cpufreq_driver *driver_data)
{
int ret = 0;
if (cpufreq_driver)
return -EBUSY;
if (!driver_data || !driver_data->verify ||
((!driver_data->setpolicy) && (!driver_data->target)))
return -EINVAL;
down(&cpufreq_driver_sem);
cpufreq_driver = driver_data;
if (!cpufreq_driver->policy) {
/* then we need per-CPU init */
if (!cpufreq_driver->init) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
cpufreq_driver->policy = kmalloc(NR_CPUS * sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!cpufreq_driver->policy) {
up(&cpufreq_driver_sem);
return -ENOMEM;
}
memset(cpufreq_driver->policy, 0, NR_CPUS * sizeof(struct cpufreq_policy));
}
up(&cpufreq_driver_sem);
#ifdef CONFIG_CPU_FREQ_24_API
cpufreq_sysctl_init();
#endif
ret = interface_register(&cpufreq_interface);
return ret;
}
EXPORT_SYMBOL_GPL(cpufreq_register_driver);
/**
* cpufreq_unregister_driver - unregister the current CPUFreq driver
*
* Unregister the current CPUFreq driver. Only call this if you have
* the right to do so, i.e. if you have succeeded in initialising before!
* Returns zero if successful, and -EINVAL if the cpufreq_driver is
* currently not initialised.
*/
int cpufreq_unregister_driver(struct cpufreq_driver *driver)
{
down(&cpufreq_driver_sem);
if (!cpufreq_driver ||
((driver != cpufreq_driver) && (driver != NULL))) { /* compat */
up(&cpufreq_driver_sem);
return -EINVAL;
}
#ifdef CONFIG_CPU_FREQ_24_API
cpufreq_sysctl_exit();
#endif
/* remove this workaround as soon as interface_add_data works */
{
unsigned int i;
for (i=0; i<NR_CPUS; i++) {
if (cpu_online(i))
cpufreq_remove_dev(&cpufreq_driver->policy[i].intf);
}
}
interface_unregister(&cpufreq_interface);
if (driver)
kfree(cpufreq_driver->policy);
cpufreq_driver = NULL;
up(&cpufreq_driver_sem);
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_unregister_driver);
#ifdef CONFIG_PM
/**
* cpufreq_restore - restore the CPU clock frequency after resume
*
* Restore the CPU clock frequency so that our idea of the current
* frequency reflects the actual hardware.
*/
int cpufreq_restore(void)
{
struct cpufreq_policy policy;
unsigned int i;
unsigned int ret = 0;
if (in_interrupt())
panic("cpufreq_restore() called from interrupt context!");
for (i=0;i<NR_CPUS;i++) {
if (!cpu_online(i))
continue;
down(&cpufreq_driver_sem);
if (!cpufreq_driver) {
up(&cpufreq_driver_sem);
return 0;
}
memcpy(&policy, &cpufreq_driver->policy[i], sizeof(struct cpufreq_policy));
up(&cpufreq_driver_sem);
ret += cpufreq_set_policy(&policy);
}
return ret;
}
EXPORT_SYMBOL_GPL(cpufreq_restore);
#else
#define cpufreq_restore() do {} while (0)
#endif /* CONFIG_PM */