mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-29 15:43:59 +08:00
7e6fdd4bad
Prepare devfreq core framework to support devices which can idle. When device idleness is detected perhaps through runtime-pm, need some mechanism to suspend devfreq load monitoring and resume back when device is online. Present code continues monitoring unless device is removed from devfreq core. This patch introduces following design changes, - use per device work instead of global work to monitor device load. This enables suspend/resume of device devfreq and reduces monitoring code complexity. - decouple delayed work based load monitoring logic from core by introducing helpers functions to be used by governors. This provides flexibility for governors either to use delayed work based monitoring functions or to implement their own mechanism. - devfreq core interacts with governors via events to perform specific actions. These events include start/stop devfreq. This sets ground for adding suspend/resume events. The devfreq apis are not modified and are kept intact. 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>
647 lines
17 KiB
C
647 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);
|
|
|
|
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");
|