linux_dsm_epyc7002/drivers/devfreq/devfreq.c
Rajagopal Venkat 206c30cfeb PM / devfreq: Add suspend and resume apis
Add devfreq suspend/resume apis for devfreq users. This patch
supports suspend and resume of devfreq load monitoring, required
for devices which can idle.

Signed-off-by: Rajagopal Venkat <rajagopal.venkat@linaro.org>
Acked-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2012-11-15 00:35:04 +01:00

675 lines
17 KiB
C

/*
* devfreq: Generic Dynamic Voltage and Frequency Scaling (DVFS) Framework
* for Non-CPU Devices.
*
* Copyright (C) 2011 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
*
* 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/sched.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/opp.h>
#include <linux/devfreq.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/printk.h>
#include <linux/hrtimer.h>
#include "governor.h"
struct class *devfreq_class;
/*
* devfreq core provides delayed work based load monitoring helper
* functions. Governors can use these or can implement their own
* monitoring mechanism.
*/
static struct workqueue_struct *devfreq_wq;
/* The list of all device-devfreq */
static LIST_HEAD(devfreq_list);
static DEFINE_MUTEX(devfreq_list_lock);
/**
* find_device_devfreq() - find devfreq struct using device pointer
* @dev: device pointer used to lookup device devfreq.
*
* Search the list of device devfreqs and return the matched device's
* devfreq info. devfreq_list_lock should be held by the caller.
*/
static struct devfreq *find_device_devfreq(struct device *dev)
{
struct devfreq *tmp_devfreq;
if (unlikely(IS_ERR_OR_NULL(dev))) {
pr_err("DEVFREQ: %s: Invalid parameters\n", __func__);
return ERR_PTR(-EINVAL);
}
WARN(!mutex_is_locked(&devfreq_list_lock),
"devfreq_list_lock must be locked.");
list_for_each_entry(tmp_devfreq, &devfreq_list, node) {
if (tmp_devfreq->dev.parent == dev)
return tmp_devfreq;
}
return ERR_PTR(-ENODEV);
}
/* Load monitoring helper functions for governors use */
/**
* update_devfreq() - Reevaluate the device and configure frequency.
* @devfreq: the devfreq instance.
*
* Note: Lock devfreq->lock before calling update_devfreq
* This function is exported for governors.
*/
int update_devfreq(struct devfreq *devfreq)
{
unsigned long freq;
int err = 0;
u32 flags = 0;
if (!mutex_is_locked(&devfreq->lock)) {
WARN(true, "devfreq->lock must be locked by the caller.\n");
return -EINVAL;
}
/* Reevaluate the proper frequency */
err = devfreq->governor->get_target_freq(devfreq, &freq);
if (err)
return err;
/*
* Adjust the freuqency with user freq and QoS.
*
* List from the highest proiority
* max_freq (probably called by thermal when it's too hot)
* min_freq
*/
if (devfreq->min_freq && freq < devfreq->min_freq) {
freq = devfreq->min_freq;
flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
}
if (devfreq->max_freq && freq > devfreq->max_freq) {
freq = devfreq->max_freq;
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
}
err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
if (err)
return err;
devfreq->previous_freq = freq;
return err;
}
/**
* devfreq_monitor() - Periodically poll devfreq objects.
* @work: the work struct used to run devfreq_monitor periodically.
*
*/
static void devfreq_monitor(struct work_struct *work)
{
int err;
struct devfreq *devfreq = container_of(work,
struct devfreq, work.work);
mutex_lock(&devfreq->lock);
err = update_devfreq(devfreq);
if (err)
dev_err(&devfreq->dev, "dvfs failed with (%d) error\n", err);
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
mutex_unlock(&devfreq->lock);
}
/**
* devfreq_monitor_start() - Start load monitoring of devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function for starting devfreq device load monitoing. By
* default delayed work based monitoring is supported. Function
* to be called from governor in response to DEVFREQ_GOV_START
* event when device is added to devfreq framework.
*/
void devfreq_monitor_start(struct devfreq *devfreq)
{
INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor);
if (devfreq->profile->polling_ms)
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
}
/**
* devfreq_monitor_stop() - Stop load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to stop devfreq device load monitoing. Function
* to be called from governor in response to DEVFREQ_GOV_STOP
* event when device is removed from devfreq framework.
*/
void devfreq_monitor_stop(struct devfreq *devfreq)
{
cancel_delayed_work_sync(&devfreq->work);
}
/**
* devfreq_monitor_suspend() - Suspend load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to suspend devfreq device load monitoing. Function
* to be called from governor in response to DEVFREQ_GOV_SUSPEND
* event or when polling interval is set to zero.
*
* Note: Though this function is same as devfreq_monitor_stop(),
* intentionally kept separate to provide hooks for collecting
* transition statistics.
*/
void devfreq_monitor_suspend(struct devfreq *devfreq)
{
mutex_lock(&devfreq->lock);
if (devfreq->stop_polling) {
mutex_unlock(&devfreq->lock);
return;
}
devfreq->stop_polling = true;
mutex_unlock(&devfreq->lock);
cancel_delayed_work_sync(&devfreq->work);
}
/**
* devfreq_monitor_resume() - Resume load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to resume devfreq device load monitoing. Function
* to be called from governor in response to DEVFREQ_GOV_RESUME
* event or when polling interval is set to non-zero.
*/
void devfreq_monitor_resume(struct devfreq *devfreq)
{
mutex_lock(&devfreq->lock);
if (!devfreq->stop_polling)
goto out;
if (!delayed_work_pending(&devfreq->work) &&
devfreq->profile->polling_ms)
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
devfreq->stop_polling = false;
out:
mutex_unlock(&devfreq->lock);
}
/**
* devfreq_interval_update() - Update device devfreq monitoring interval
* @devfreq: the devfreq instance.
* @delay: new polling interval to be set.
*
* Helper function to set new load monitoring polling interval. Function
* to be called from governor in response to DEVFREQ_GOV_INTERVAL event.
*/
void devfreq_interval_update(struct devfreq *devfreq, unsigned int *delay)
{
unsigned int cur_delay = devfreq->profile->polling_ms;
unsigned int new_delay = *delay;
mutex_lock(&devfreq->lock);
devfreq->profile->polling_ms = new_delay;
if (devfreq->stop_polling)
goto out;
/* if new delay is zero, stop polling */
if (!new_delay) {
mutex_unlock(&devfreq->lock);
cancel_delayed_work_sync(&devfreq->work);
return;
}
/* if current delay is zero, start polling with new delay */
if (!cur_delay) {
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
goto out;
}
/* if current delay is greater than new delay, restart polling */
if (cur_delay > new_delay) {
mutex_unlock(&devfreq->lock);
cancel_delayed_work_sync(&devfreq->work);
mutex_lock(&devfreq->lock);
if (!devfreq->stop_polling)
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
}
out:
mutex_unlock(&devfreq->lock);
}
/**
* devfreq_notifier_call() - Notify that the device frequency requirements
* has been changed out of devfreq framework.
* @nb the notifier_block (supposed to be devfreq->nb)
* @type not used
* @devp not used
*
* Called by a notifier that uses devfreq->nb.
*/
static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
void *devp)
{
struct devfreq *devfreq = container_of(nb, struct devfreq, nb);
int ret;
mutex_lock(&devfreq->lock);
ret = update_devfreq(devfreq);
mutex_unlock(&devfreq->lock);
return ret;
}
/**
* _remove_devfreq() - Remove devfreq from the list and release its resources.
* @devfreq: the devfreq struct
* @skip: skip calling device_unregister().
*/
static void _remove_devfreq(struct devfreq *devfreq, bool skip)
{
mutex_lock(&devfreq_list_lock);
if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) {
mutex_unlock(&devfreq_list_lock);
dev_warn(&devfreq->dev, "releasing devfreq which doesn't exist\n");
return;
}
list_del(&devfreq->node);
mutex_unlock(&devfreq_list_lock);
devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_STOP, NULL);
if (devfreq->profile->exit)
devfreq->profile->exit(devfreq->dev.parent);
if (!skip && get_device(&devfreq->dev)) {
device_unregister(&devfreq->dev);
put_device(&devfreq->dev);
}
mutex_destroy(&devfreq->lock);
kfree(devfreq);
}
/**
* devfreq_dev_release() - Callback for struct device to release the device.
* @dev: the devfreq device
*
* This calls _remove_devfreq() if _remove_devfreq() is not called.
* Note that devfreq_dev_release() could be called by _remove_devfreq() as
* well as by others unregistering the device.
*/
static void devfreq_dev_release(struct device *dev)
{
struct devfreq *devfreq = to_devfreq(dev);
_remove_devfreq(devfreq, true);
}
/**
* devfreq_add_device() - Add devfreq feature to the device
* @dev: the device to add devfreq feature.
* @profile: device-specific profile to run devfreq.
* @governor: the policy to choose frequency.
* @data: private data for the governor. The devfreq framework does not
* touch this value.
*/
struct devfreq *devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
const struct devfreq_governor *governor,
void *data)
{
struct devfreq *devfreq;
int err = 0;
if (!dev || !profile || !governor) {
dev_err(dev, "%s: Invalid parameters.\n", __func__);
return ERR_PTR(-EINVAL);
}
mutex_lock(&devfreq_list_lock);
devfreq = find_device_devfreq(dev);
mutex_unlock(&devfreq_list_lock);
if (!IS_ERR(devfreq)) {
dev_err(dev, "%s: Unable to create devfreq for the device. It already has one.\n", __func__);
err = -EINVAL;
goto err_out;
}
devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL);
if (!devfreq) {
dev_err(dev, "%s: Unable to create devfreq for the device\n",
__func__);
err = -ENOMEM;
goto err_out;
}
mutex_init(&devfreq->lock);
mutex_lock(&devfreq->lock);
devfreq->dev.parent = dev;
devfreq->dev.class = devfreq_class;
devfreq->dev.release = devfreq_dev_release;
devfreq->profile = profile;
devfreq->governor = governor;
devfreq->previous_freq = profile->initial_freq;
devfreq->data = data;
devfreq->nb.notifier_call = devfreq_notifier_call;
dev_set_name(&devfreq->dev, dev_name(dev));
err = device_register(&devfreq->dev);
if (err) {
put_device(&devfreq->dev);
mutex_unlock(&devfreq->lock);
goto err_dev;
}
mutex_unlock(&devfreq->lock);
mutex_lock(&devfreq_list_lock);
list_add(&devfreq->node, &devfreq_list);
mutex_unlock(&devfreq_list_lock);
err = devfreq->governor->event_handler(devfreq,
DEVFREQ_GOV_START, NULL);
if (err) {
dev_err(dev, "%s: Unable to start governor for the device\n",
__func__);
goto err_init;
}
return devfreq;
err_init:
list_del(&devfreq->node);
device_unregister(&devfreq->dev);
err_dev:
kfree(devfreq);
err_out:
return ERR_PTR(err);
}
EXPORT_SYMBOL(devfreq_add_device);
/**
* devfreq_remove_device() - Remove devfreq feature from a device.
* @devfreq the devfreq instance to be removed
*/
int devfreq_remove_device(struct devfreq *devfreq)
{
if (!devfreq)
return -EINVAL;
_remove_devfreq(devfreq, false);
return 0;
}
EXPORT_SYMBOL(devfreq_remove_device);
/**
* devfreq_suspend_device() - Suspend devfreq of a device.
* @devfreq: the devfreq instance to be suspended
*/
int devfreq_suspend_device(struct devfreq *devfreq)
{
if (!devfreq)
return -EINVAL;
return devfreq->governor->event_handler(devfreq,
DEVFREQ_GOV_SUSPEND, NULL);
}
EXPORT_SYMBOL(devfreq_suspend_device);
/**
* devfreq_resume_device() - Resume devfreq of a device.
* @devfreq: the devfreq instance to be resumed
*/
int devfreq_resume_device(struct devfreq *devfreq)
{
if (!devfreq)
return -EINVAL;
return devfreq->governor->event_handler(devfreq,
DEVFREQ_GOV_RESUME, NULL);
}
EXPORT_SYMBOL(devfreq_resume_device);
static ssize_t show_governor(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", to_devfreq(dev)->governor->name);
}
static ssize_t show_freq(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->previous_freq);
}
static ssize_t show_polling_interval(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", to_devfreq(dev)->profile->polling_ms);
}
static ssize_t store_polling_interval(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
unsigned int value;
int ret;
ret = sscanf(buf, "%u", &value);
if (ret != 1)
goto out;
df->governor->event_handler(df, DEVFREQ_GOV_INTERVAL, &value);
ret = count;
out:
return ret;
}
static ssize_t store_min_freq(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
unsigned long value;
int ret;
unsigned long max;
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
goto out;
mutex_lock(&df->lock);
max = df->max_freq;
if (value && max && value > max) {
ret = -EINVAL;
goto unlock;
}
df->min_freq = value;
update_devfreq(df);
ret = count;
unlock:
mutex_unlock(&df->lock);
out:
return ret;
}
static ssize_t show_min_freq(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->min_freq);
}
static ssize_t store_max_freq(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
unsigned long value;
int ret;
unsigned long min;
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
goto out;
mutex_lock(&df->lock);
min = df->min_freq;
if (value && min && value < min) {
ret = -EINVAL;
goto unlock;
}
df->max_freq = value;
update_devfreq(df);
ret = count;
unlock:
mutex_unlock(&df->lock);
out:
return ret;
}
static ssize_t show_max_freq(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->max_freq);
}
static struct device_attribute devfreq_attrs[] = {
__ATTR(governor, S_IRUGO, show_governor, NULL),
__ATTR(cur_freq, S_IRUGO, show_freq, NULL),
__ATTR(polling_interval, S_IRUGO | S_IWUSR, show_polling_interval,
store_polling_interval),
__ATTR(min_freq, S_IRUGO | S_IWUSR, show_min_freq, store_min_freq),
__ATTR(max_freq, S_IRUGO | S_IWUSR, show_max_freq, store_max_freq),
{ },
};
static int __init devfreq_init(void)
{
devfreq_class = class_create(THIS_MODULE, "devfreq");
if (IS_ERR(devfreq_class)) {
pr_err("%s: couldn't create class\n", __FILE__);
return PTR_ERR(devfreq_class);
}
devfreq_wq = create_freezable_workqueue("devfreq_wq");
if (IS_ERR(devfreq_wq)) {
class_destroy(devfreq_class);
pr_err("%s: couldn't create workqueue\n", __FILE__);
return PTR_ERR(devfreq_wq);
}
devfreq_class->dev_attrs = devfreq_attrs;
return 0;
}
subsys_initcall(devfreq_init);
static void __exit devfreq_exit(void)
{
class_destroy(devfreq_class);
destroy_workqueue(devfreq_wq);
}
module_exit(devfreq_exit);
/*
* The followings are helper functions for devfreq user device drivers with
* OPP framework.
*/
/**
* devfreq_recommended_opp() - Helper function to get proper OPP for the
* freq value given to target callback.
* @dev The devfreq user device. (parent of devfreq)
* @freq The frequency given to target function
* @flags Flags handed from devfreq framework.
*
*/
struct opp *devfreq_recommended_opp(struct device *dev, unsigned long *freq,
u32 flags)
{
struct opp *opp;
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) {
/* The freq is an upper bound. opp should be lower */
opp = opp_find_freq_floor(dev, freq);
/* If not available, use the closest opp */
if (opp == ERR_PTR(-ENODEV))
opp = opp_find_freq_ceil(dev, freq);
} else {
/* The freq is an lower bound. opp should be higher */
opp = opp_find_freq_ceil(dev, freq);
/* If not available, use the closest opp */
if (opp == ERR_PTR(-ENODEV))
opp = opp_find_freq_floor(dev, freq);
}
return opp;
}
/**
* devfreq_register_opp_notifier() - Helper function to get devfreq notified
* for any changes in the OPP availability
* changes
* @dev The devfreq user device. (parent of devfreq)
* @devfreq The devfreq object.
*/
int devfreq_register_opp_notifier(struct device *dev, struct devfreq *devfreq)
{
struct srcu_notifier_head *nh = opp_get_notifier(dev);
if (IS_ERR(nh))
return PTR_ERR(nh);
return srcu_notifier_chain_register(nh, &devfreq->nb);
}
/**
* devfreq_unregister_opp_notifier() - Helper function to stop getting devfreq
* notified for any changes in the OPP
* availability changes anymore.
* @dev The devfreq user device. (parent of devfreq)
* @devfreq The devfreq object.
*
* At exit() callback of devfreq_dev_profile, this must be included if
* devfreq_recommended_opp is used.
*/
int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq)
{
struct srcu_notifier_head *nh = opp_get_notifier(dev);
if (IS_ERR(nh))
return PTR_ERR(nh);
return srcu_notifier_chain_unregister(nh, &devfreq->nb);
}
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("devfreq class support");
MODULE_LICENSE("GPL");