diff --git a/Documentation/ABI/testing/sysfs-class-devfreq b/Documentation/ABI/testing/sysfs-class-devfreq index 0ec855f479ce..23d78b5aab11 100644 --- a/Documentation/ABI/testing/sysfs-class-devfreq +++ b/Documentation/ABI/testing/sysfs-class-devfreq @@ -42,3 +42,11 @@ Description: devfreq-provided central polling (/sys/class/devfreq/.../central_polling is 0), this value may be useless. + +What: /sys/class/devfreq/.../userspace/set_freq +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../userspace/set_freq shows and + sets the requested frequency for the devfreq object if + userspace governor is in effect. diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 1fb42de4f420..643b055ed3cd 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -34,6 +34,42 @@ menuconfig PM_DEVFREQ if PM_DEVFREQ +comment "DEVFREQ Governors" + +config DEVFREQ_GOV_SIMPLE_ONDEMAND + bool "Simple Ondemand" + help + Chooses frequency based on the recent load on the device. Works + similar as ONDEMAND governor of CPUFREQ does. A device with + Simple-Ondemand should be able to provide busy/total counter + values that imply the usage rate. A device may provide tuned + values to the governor with data field at devfreq_add_device(). + +config DEVFREQ_GOV_PERFORMANCE + bool "Performance" + help + Sets the frequency at the maximum available frequency. + This governor always returns UINT_MAX as frequency so that + the DEVFREQ framework returns the highest frequency available + at any time. + +config DEVFREQ_GOV_POWERSAVE + bool "Powersave" + help + Sets the frequency at the minimum available frequency. + This governor always returns 0 as frequency so that + the DEVFREQ framework returns the lowest frequency available + at any time. + +config DEVFREQ_GOV_USERSPACE + bool "Userspace" + help + Sets the frequency at the user specified one. + This governor returns the user configured frequency if there + has been an input to /sys/devices/.../power/devfreq_set_freq. + Otherwise, the governor does not change the frequnecy + given at the initialization. + comment "DEVFREQ Drivers" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 168934a12b38..4564a89e970a 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -1 +1,5 @@ obj-$(CONFIG_PM_DEVFREQ) += devfreq.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o +obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o +obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o +obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c new file mode 100644 index 000000000000..c0596b291761 --- /dev/null +++ b/drivers/devfreq/governor_performance.c @@ -0,0 +1,29 @@ +/* + * linux/drivers/devfreq/governor_performance.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 + +static int devfreq_performance_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get floor value as + * said in devfreq.h + */ + *freq = UINT_MAX; + return 0; +} + +const struct devfreq_governor devfreq_performance = { + .name = "performance", + .get_target_freq = devfreq_performance_func, + .no_central_polling = true, +}; diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c new file mode 100644 index 000000000000..2483a85a266f --- /dev/null +++ b/drivers/devfreq/governor_powersave.c @@ -0,0 +1,29 @@ +/* + * linux/drivers/devfreq/governor_powersave.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 + +static int devfreq_powersave_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get ceiling value as + * said in devfreq.h + */ + *freq = 0; + return 0; +} + +const struct devfreq_governor devfreq_powersave = { + .name = "powersave", + .get_target_freq = devfreq_powersave_func, + .no_central_polling = true, +}; diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c new file mode 100644 index 000000000000..efad8dcf9028 --- /dev/null +++ b/drivers/devfreq/governor_simpleondemand.c @@ -0,0 +1,88 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 + +/* Default constants for DevFreq-Simple-Ondemand (DFSO) */ +#define DFSO_UPTHRESHOLD (90) +#define DFSO_DOWNDIFFERENCTIAL (5) +static int devfreq_simple_ondemand_func(struct devfreq *df, + unsigned long *freq) +{ + struct devfreq_dev_status stat; + int err = df->profile->get_dev_status(df->dev.parent, &stat); + unsigned long long a, b; + unsigned int dfso_upthreshold = DFSO_UPTHRESHOLD; + unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL; + struct devfreq_simple_ondemand_data *data = df->data; + + if (err) + return err; + + if (data) { + if (data->upthreshold) + dfso_upthreshold = data->upthreshold; + if (data->downdifferential) + dfso_downdifferential = data->downdifferential; + } + if (dfso_upthreshold > 100 || + dfso_upthreshold < dfso_downdifferential) + return -EINVAL; + + /* Assume MAX if it is going to be divided by zero */ + if (stat.total_time == 0) { + *freq = UINT_MAX; + return 0; + } + + /* Prevent overflow */ + if (stat.busy_time >= (1 << 24) || stat.total_time >= (1 << 24)) { + stat.busy_time >>= 7; + stat.total_time >>= 7; + } + + /* Set MAX if it's busy enough */ + if (stat.busy_time * 100 > + stat.total_time * dfso_upthreshold) { + *freq = UINT_MAX; + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat.current_frequency == 0) { + *freq = UINT_MAX; + return 0; + } + + /* Keep the current frequency */ + if (stat.busy_time * 100 > + stat.total_time * (dfso_upthreshold - dfso_downdifferential)) { + *freq = stat.current_frequency; + return 0; + } + + /* Set the desired frequency based on the load */ + a = stat.busy_time; + a *= stat.current_frequency; + b = div_u64(a, stat.total_time); + b *= 100; + b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); + *freq = (unsigned long) b; + + return 0; +} + +const struct devfreq_governor devfreq_simple_ondemand = { + .name = "simple_ondemand", + .get_target_freq = devfreq_simple_ondemand_func, +}; diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c new file mode 100644 index 000000000000..4f8b563da782 --- /dev/null +++ b/drivers/devfreq/governor_userspace.c @@ -0,0 +1,116 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 "governor.h" + +struct userspace_data { + unsigned long user_frequency; + bool valid; +}; + +static int devfreq_userspace_func(struct devfreq *df, unsigned long *freq) +{ + struct userspace_data *data = df->data; + + if (!data->valid) + *freq = df->previous_freq; /* No user freq specified yet */ + else + *freq = data->user_frequency; + return 0; +} + +static ssize_t store_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + int err = 0; + + + mutex_lock(&devfreq->lock); + data = devfreq->data; + + sscanf(buf, "%lu", &wanted); + data->user_frequency = wanted; + data->valid = true; + err = update_devfreq(devfreq); + if (err == 0) + err = count; + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t show_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + + if (data->valid) + err = sprintf(buf, "%lu\n", data->user_frequency); + else + err = sprintf(buf, "undefined\n"); + mutex_unlock(&devfreq->lock); + return err; +} + +static DEVICE_ATTR(set_freq, 0644, show_freq, store_freq); +static struct attribute *dev_entries[] = { + &dev_attr_set_freq.attr, + NULL, +}; +static struct attribute_group dev_attr_group = { + .name = "userspace", + .attrs = dev_entries, +}; + +static int userspace_init(struct devfreq *devfreq) +{ + int err = 0; + struct userspace_data *data = kzalloc(sizeof(struct userspace_data), + GFP_KERNEL); + + if (!data) { + err = -ENOMEM; + goto out; + } + data->valid = false; + devfreq->data = data; + + err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); +out: + return err; +} + +static void userspace_exit(struct devfreq *devfreq) +{ + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group); + kfree(devfreq->data); + devfreq->data = NULL; +} + +const struct devfreq_governor devfreq_userspace = { + .name = "userspace", + .get_target_freq = devfreq_userspace_func, + .init = userspace_init, + .exit = userspace_exit, + .no_central_polling = true, +}; diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index b3be3d3cbaa7..afb94583960c 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -166,6 +166,36 @@ extern int devfreq_register_opp_notifier(struct device *dev, extern int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq); +#ifdef CONFIG_DEVFREQ_GOV_POWERSAVE +extern const struct devfreq_governor devfreq_powersave; +#endif +#ifdef CONFIG_DEVFREQ_GOV_PERFORMANCE +extern const struct devfreq_governor devfreq_performance; +#endif +#ifdef CONFIG_DEVFREQ_GOV_USERSPACE +extern const struct devfreq_governor devfreq_userspace; +#endif +#ifdef CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND +extern const struct devfreq_governor devfreq_simple_ondemand; +/** + * struct devfreq_simple_ondemand_data - void *data fed to struct devfreq + * and devfreq_add_device + * @ upthreshold If the load is over this value, the frequency jumps. + * Specify 0 to use the default. Valid value = 0 to 100. + * @ downdifferential If the load is under upthreshold - downdifferential, + * the governor may consider slowing the frequency down. + * Specify 0 to use the default. Valid value = 0 to 100. + * downdifferential < upthreshold must hold. + * + * If the fed devfreq_simple_ondemand_data pointer is NULL to the governor, + * the governor uses the default values. + */ +struct devfreq_simple_ondemand_data { + unsigned int upthreshold; + unsigned int downdifferential; +}; +#endif + #else /* !CONFIG_PM_DEVFREQ */ static struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, @@ -198,6 +228,11 @@ static int devfreq_unregister_opp_notifier(struct device *dev, return -EINVAL; } +#define devfreq_powersave NULL +#define devfreq_performance NULL +#define devfreq_userspace NULL +#define devfreq_simple_ondemand NULL + #endif /* CONFIG_PM_DEVFREQ */ #endif /* __LINUX_DEVFREQ_H__ */