PM / devfreq: Add new DEVFREQ_TRANSITION_NOTIFIER notifier

This patch adds the new DEVFREQ_TRANSITION_NOTIFIER notifier to send
the notification when the frequency of device is changed.
This notifier has two state as following:
- DEVFREQ_PRECHANGE  : Notify it before chaning the frequency of device
- DEVFREQ_POSTCHANGE : Notify it after changed the frequency of device

And this patch adds the resourced-managed function to release the resource
automatically when error happen.

Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
[m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board]
Tested-by: Markus Reichl <m.reichl@fivetechno.de>
Tested-by: Anand Moon <linux.amoon@gmail.com>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Acked-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
This commit is contained in:
Chanwoo Choi 2016-01-26 13:21:26 +09:00 committed by MyungJoo Ham
parent 8f510aeb22
commit 0fe3a66410
2 changed files with 220 additions and 2 deletions

View File

@ -189,6 +189,29 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
return ERR_PTR(-ENODEV); return ERR_PTR(-ENODEV);
} }
static int devfreq_notify_transition(struct devfreq *devfreq,
struct devfreq_freqs *freqs, unsigned int state)
{
if (!devfreq)
return -EINVAL;
switch (state) {
case DEVFREQ_PRECHANGE:
srcu_notifier_call_chain(&devfreq->transition_notifier_list,
DEVFREQ_PRECHANGE, freqs);
break;
case DEVFREQ_POSTCHANGE:
srcu_notifier_call_chain(&devfreq->transition_notifier_list,
DEVFREQ_POSTCHANGE, freqs);
break;
default:
return -EINVAL;
}
return 0;
}
/* Load monitoring helper functions for governors use */ /* Load monitoring helper functions for governors use */
/** /**
@ -200,7 +223,8 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
*/ */
int update_devfreq(struct devfreq *devfreq) int update_devfreq(struct devfreq *devfreq)
{ {
unsigned long freq; struct devfreq_freqs freqs;
unsigned long freq, cur_freq;
int err = 0; int err = 0;
u32 flags = 0; u32 flags = 0;
@ -234,10 +258,22 @@ int update_devfreq(struct devfreq *devfreq)
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */ flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
} }
if (devfreq->profile->get_cur_freq)
devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
else
cur_freq = devfreq->previous_freq;
freqs.old = cur_freq;
freqs.new = freq;
devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
err = devfreq->profile->target(devfreq->dev.parent, &freq, flags); err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
if (err) if (err)
return err; return err;
freqs.new = freq;
devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
if (devfreq->profile->freq_table) if (devfreq->profile->freq_table)
if (devfreq_update_status(devfreq, freq)) if (devfreq_update_status(devfreq, freq))
dev_err(&devfreq->dev, dev_err(&devfreq->dev,
@ -542,6 +578,8 @@ struct devfreq *devfreq_add_device(struct device *dev,
goto err_out; goto err_out;
} }
srcu_init_notifier_head(&devfreq->transition_notifier_list);
mutex_unlock(&devfreq->lock); mutex_unlock(&devfreq->lock);
mutex_lock(&devfreq_list_lock); mutex_lock(&devfreq_list_lock);
@ -1310,6 +1348,129 @@ void devm_devfreq_unregister_opp_notifier(struct device *dev,
} }
EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier); EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
/**
* devfreq_register_notifier() - Register a driver with devfreq
* @devfreq: The devfreq object.
* @nb: The notifier block to register.
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
int devfreq_register_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
int ret = 0;
if (!devfreq)
return -EINVAL;
switch (list) {
case DEVFREQ_TRANSITION_NOTIFIER:
ret = srcu_notifier_chain_register(
&devfreq->transition_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL(devfreq_register_notifier);
/*
* devfreq_unregister_notifier() - Unregister a driver with devfreq
* @devfreq: The devfreq object.
* @nb: The notifier block to be unregistered.
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
int devfreq_unregister_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
int ret = 0;
if (!devfreq)
return -EINVAL;
switch (list) {
case DEVFREQ_TRANSITION_NOTIFIER:
ret = srcu_notifier_chain_unregister(
&devfreq->transition_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL(devfreq_unregister_notifier);
struct devfreq_notifier_devres {
struct devfreq *devfreq;
struct notifier_block *nb;
unsigned int list;
};
static void devm_devfreq_notifier_release(struct device *dev, void *res)
{
struct devfreq_notifier_devres *this = res;
devfreq_unregister_notifier(this->devfreq, this->nb, this->list);
}
/**
* devm_devfreq_register_notifier()
- Resource-managed devfreq_register_notifier()
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
* @nb: The notifier block to be unregistered.
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
int devm_devfreq_register_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
struct devfreq_notifier_devres *ptr;
int ret;
ptr = devres_alloc(devm_devfreq_notifier_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = devfreq_register_notifier(devfreq, nb, list);
if (ret) {
devres_free(ptr);
return ret;
}
ptr->devfreq = devfreq;
ptr->nb = nb;
ptr->list = list;
devres_add(dev, ptr);
return 0;
}
EXPORT_SYMBOL(devm_devfreq_register_notifier);
/**
* devm_devfreq_unregister_notifier()
- Resource-managed devfreq_unregister_notifier()
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
* @nb: The notifier block to be unregistered.
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
void devm_devfreq_unregister_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
WARN_ON(devres_release(dev, devm_devfreq_notifier_release,
devm_devfreq_dev_match, devfreq));
}
EXPORT_SYMBOL(devm_devfreq_unregister_notifier);
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("devfreq class support"); MODULE_DESCRIPTION("devfreq class support");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");

View File

@ -19,6 +19,13 @@
#define DEVFREQ_NAME_LEN 16 #define DEVFREQ_NAME_LEN 16
/* DEVFREQ notifier interface */
#define DEVFREQ_TRANSITION_NOTIFIER (0)
/* Transition notifiers of DEVFREQ_TRANSITION_NOTIFIER */
#define DEVFREQ_PRECHANGE (0)
#define DEVFREQ_POSTCHANGE (1)
struct devfreq; struct devfreq;
/** /**
@ -143,6 +150,7 @@ struct devfreq_governor {
* @trans_table: Statistics of devfreq transitions * @trans_table: Statistics of devfreq transitions
* @time_in_state: Statistics of devfreq states * @time_in_state: Statistics of devfreq states
* @last_stat_updated: The last time stat updated * @last_stat_updated: The last time stat updated
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
* *
* This structure stores the devfreq information for a give device. * This structure stores the devfreq information for a give device.
* *
@ -177,6 +185,13 @@ struct devfreq {
unsigned int *trans_table; unsigned int *trans_table;
unsigned long *time_in_state; unsigned long *time_in_state;
unsigned long last_stat_updated; unsigned long last_stat_updated;
struct srcu_notifier_head transition_notifier_list;
};
struct devfreq_freqs {
unsigned long old;
unsigned long new;
}; };
#if defined(CONFIG_PM_DEVFREQ) #if defined(CONFIG_PM_DEVFREQ)
@ -207,7 +222,20 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev,
struct devfreq *devfreq); struct devfreq *devfreq);
extern void devm_devfreq_unregister_opp_notifier(struct device *dev, extern void devm_devfreq_unregister_opp_notifier(struct device *dev,
struct devfreq *devfreq); struct devfreq *devfreq);
extern int devfreq_register_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list);
extern int devfreq_unregister_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list);
extern int devm_devfreq_register_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list);
extern void devm_devfreq_unregister_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list);
extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
int index); int index);
@ -310,6 +338,35 @@ static inline void devm_devfreq_unregister_opp_notifier(struct device *dev,
{ {
} }
static inline int devfreq_register_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
return 0;
}
static inline int devfreq_unregister_notifier(struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
return 0;
}
static inline int devm_devfreq_register_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
return 0;
}
static inline void devm_devfreq_unregister_notifier(struct device *dev,
struct devfreq *devfreq,
struct notifier_block *nb,
unsigned int list)
{
}
static inline struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, static inline struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
int index) int index)
{ {