PM / devfreq: Add PM QoS support

Register notifiers with the PM QoS framework in order to respond to
requests for DEV_PM_QOS_MIN_FREQUENCY and DEV_PM_QOS_MAX_FREQUENCY.

No notifiers are added by this patch but PM QoS constraints can be
imposed externally (for example from other devices).

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Acked-by: Chanwoo Choi <cw00.choi@samsung.com>
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
Tested-by: Matthias Kaehlcke <mka@chromium.org>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
This commit is contained in:
Leonard Crestez 2019-12-05 12:05:06 +02:00 committed by Chanwoo Choi
parent 42a6b25e67
commit 05d7ae15cf
2 changed files with 82 additions and 0 deletions

View File

@ -24,11 +24,14 @@
#include <linux/printk.h>
#include <linux/hrtimer.h>
#include <linux/of.h>
#include <linux/pm_qos.h>
#include "governor.h"
#define CREATE_TRACE_POINTS
#include <trace/events/devfreq.h>
#define HZ_PER_KHZ 1000
static struct class *devfreq_class;
/*
@ -111,6 +114,7 @@ static void get_freq_range(struct devfreq *devfreq,
unsigned long *max_freq)
{
unsigned long *freq_table = devfreq->profile->freq_table;
s32 qos_min_freq, qos_max_freq;
lockdep_assert_held(&devfreq->lock);
@ -127,6 +131,16 @@ static void get_freq_range(struct devfreq *devfreq,
*max_freq = freq_table[0];
}
/* Apply constraints from PM QoS */
qos_min_freq = dev_pm_qos_read_value(devfreq->dev.parent,
DEV_PM_QOS_MIN_FREQUENCY);
qos_max_freq = dev_pm_qos_read_value(devfreq->dev.parent,
DEV_PM_QOS_MAX_FREQUENCY);
*min_freq = max(*min_freq, (unsigned long)HZ_PER_KHZ * qos_min_freq);
if (qos_max_freq != PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE)
*max_freq = min(*max_freq,
(unsigned long)HZ_PER_KHZ * qos_max_freq);
/* Apply constraints from sysfs */
*min_freq = max(*min_freq, devfreq->min_freq);
*max_freq = min(*max_freq, devfreq->max_freq);
@ -626,6 +640,45 @@ out:
return NOTIFY_OK;
}
/**
* qos_notifier_call() - Common handler for QoS constraints.
* @devfreq: the devfreq instance.
*/
static int qos_notifier_call(struct devfreq *devfreq)
{
int err;
mutex_lock(&devfreq->lock);
err = update_devfreq(devfreq);
mutex_unlock(&devfreq->lock);
if (err)
dev_err(devfreq->dev.parent,
"failed to update frequency from PM QoS (%d)\n",
err);
return NOTIFY_OK;
}
/**
* qos_min_notifier_call() - Callback for QoS min_freq changes.
* @nb: Should be devfreq->nb_min
*/
static int qos_min_notifier_call(struct notifier_block *nb,
unsigned long val, void *ptr)
{
return qos_notifier_call(container_of(nb, struct devfreq, nb_min));
}
/**
* qos_max_notifier_call() - Callback for QoS max_freq changes.
* @nb: Should be devfreq->nb_max
*/
static int qos_max_notifier_call(struct notifier_block *nb,
unsigned long val, void *ptr)
{
return qos_notifier_call(container_of(nb, struct devfreq, nb_max));
}
/**
* devfreq_dev_release() - Callback for struct device to release the device.
* @dev: the devfreq device
@ -635,11 +688,23 @@ out:
static void devfreq_dev_release(struct device *dev)
{
struct devfreq *devfreq = to_devfreq(dev);
int err;
mutex_lock(&devfreq_list_lock);
list_del(&devfreq->node);
mutex_unlock(&devfreq_list_lock);
err = dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_max,
DEV_PM_QOS_MAX_FREQUENCY);
if (err && err != -ENOENT)
dev_warn(dev->parent,
"Failed to remove max_freq notifier: %d\n", err);
err = dev_pm_qos_remove_notifier(devfreq->dev.parent, &devfreq->nb_min,
DEV_PM_QOS_MIN_FREQUENCY);
if (err && err != -ENOENT)
dev_warn(dev->parent,
"Failed to remove min_freq notifier: %d\n", err);
if (devfreq->profile->exit)
devfreq->profile->exit(devfreq->dev.parent);
@ -762,6 +827,18 @@ struct devfreq *devfreq_add_device(struct device *dev,
mutex_unlock(&devfreq->lock);
devfreq->nb_min.notifier_call = qos_min_notifier_call;
err = dev_pm_qos_add_notifier(devfreq->dev.parent, &devfreq->nb_min,
DEV_PM_QOS_MIN_FREQUENCY);
if (err)
goto err_devfreq;
devfreq->nb_max.notifier_call = qos_max_notifier_call;
err = dev_pm_qos_add_notifier(devfreq->dev.parent, &devfreq->nb_max,
DEV_PM_QOS_MAX_FREQUENCY);
if (err)
goto err_devfreq;
mutex_lock(&devfreq_list_lock);
governor = try_then_request_governor(devfreq->governor_name);

View File

@ -136,6 +136,8 @@ struct devfreq_dev_profile {
* @time_in_state: Statistics of devfreq states
* @last_stat_updated: The last time stat updated
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
* @nb_min: Notifier block for DEV_PM_QOS_MIN_FREQUENCY
* @nb_max: Notifier block for DEV_PM_QOS_MAX_FREQUENCY
*
* This structure stores the devfreq information for a give device.
*
@ -178,6 +180,9 @@ struct devfreq {
unsigned long last_stat_updated;
struct srcu_notifier_head transition_notifier_list;
struct notifier_block nb_min;
struct notifier_block nb_max;
};
struct devfreq_freqs {