From 8f510aeb223b26c4ffbece9fa92e4befea470f57 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 10 Nov 2015 20:31:07 +0900 Subject: PM / devfreq: Add devfreq_get_devfreq_by_phandle() This patch adds the new devfreq_get_devfreq_by_phandle() OF helper function which can find the instance of devfreq device by using phandle ("devfreq"). Signed-off-by: Chanwoo Choi Signed-off-by: MyungJoo Ham [m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board] Tested-by: Markus Reichl Tested-by: Anand Moon Acked-by: Krzysztof Kozlowski --- include/linux/devfreq.h | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'include') diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index 6fa02a20eb63..aa0b8424ebc3 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -208,6 +208,9 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev, extern void devm_devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq); +extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, + int index); + /** * devfreq_update_stats() - update the last_status pointer in struct devfreq * @df: the devfreq instance whose status needs updating @@ -307,6 +310,12 @@ static inline void devm_devfreq_unregister_opp_notifier(struct device *dev, { } +static inline struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, + int index) +{ + return ERR_PTR(-ENODEV); +} + static inline int devfreq_update_stats(struct devfreq *df) { return -EINVAL; -- cgit v1.2.3 From 0fe3a66410a3ba96679be903f1e287d7a0a264a9 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 26 Jan 2016 13:21:26 +0900 Subject: 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 [m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board] Tested-by: Markus Reichl Tested-by: Anand Moon Signed-off-by: MyungJoo Ham Acked-by: Krzysztof Kozlowski --- drivers/devfreq/devfreq.c | 163 +++++++++++++++++++++++++++++++++++++++++++++- include/linux/devfreq.h | 59 ++++++++++++++++- 2 files changed, 220 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 20a9422c2552..1d6c803804d5 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -189,6 +189,29 @@ static struct devfreq_governor *find_devfreq_governor(const char *name) 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 */ /** @@ -200,7 +223,8 @@ static struct devfreq_governor *find_devfreq_governor(const char *name) */ int update_devfreq(struct devfreq *devfreq) { - unsigned long freq; + struct devfreq_freqs freqs; + unsigned long freq, cur_freq; int err = 0; u32 flags = 0; @@ -234,10 +258,22 @@ int update_devfreq(struct devfreq *devfreq) 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); if (err) return err; + freqs.new = freq; + devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE); + if (devfreq->profile->freq_table) if (devfreq_update_status(devfreq, freq)) dev_err(&devfreq->dev, @@ -542,6 +578,8 @@ struct devfreq *devfreq_add_device(struct device *dev, goto err_out; } + srcu_init_notifier_head(&devfreq->transition_notifier_list); + mutex_unlock(&devfreq->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); +/** + * 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 "); MODULE_DESCRIPTION("devfreq class support"); MODULE_LICENSE("GPL"); diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index aa0b8424ebc3..98c699304e12 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -19,6 +19,13 @@ #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; /** @@ -143,6 +150,7 @@ struct devfreq_governor { * @trans_table: Statistics of devfreq transitions * @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 * * This structure stores the devfreq information for a give device. * @@ -177,6 +185,13 @@ struct devfreq { unsigned int *trans_table; unsigned long *time_in_state; 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) @@ -207,7 +222,20 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev, struct devfreq *devfreq); extern void devm_devfreq_unregister_opp_notifier(struct device *dev, 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, 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, int index) { -- cgit v1.2.3 From 996133119f57334c38b020dbfaaac5b5eb127e29 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 22 Mar 2016 13:44:03 +0900 Subject: PM / devfreq: Add new passive governor This patch adds the new passive governor for DEVFREQ framework. The following governors are already present and used for DVFS (Dynamic Voltage and Frequency Scaling) drivers. The following governors are independently used for one device driver which don't give the influence to other device drviers and also don't receive the effect from other device drivers. - ondemand / performance / powersave / userspace The passive governor depends on operation of parent driver with specific governos extremely and is not able to decide the new frequency by oneself. According to the decided new frequency of parent driver with governor, the passive governor uses it to decide the appropriate frequency for own device driver. The passive governor must need the following information from device tree: - the source clock and OPP tables - the instance of parent device For exameple, there are one more devfreq device drivers which need to change their source clock according to their utilization on runtime. But, they share the same power line (e.g., regulator). So, specific device driver is operated as parent with ondemand governor and then the rest device driver with passive governor is influenced by parent device. Suggested-by: Myungjoo Ham Signed-off-by: Chanwoo Choi [tjakobi: Reported RCU locking issue and cw00.choi fix it] Reported-by: Tobias Jakobi [linux.amoon: Reported possible recursive locking and cw00.choi fix it] Reported-by: Anand Moon Signed-off-by: MyungJoo Ham Acked-by: Krzysztof Kozlowski --- drivers/devfreq/Kconfig | 8 ++ drivers/devfreq/Makefile | 1 + drivers/devfreq/governor_passive.c | 207 +++++++++++++++++++++++++++++++++++++ include/linux/devfreq.h | 33 ++++++ 4 files changed, 249 insertions(+) create mode 100644 drivers/devfreq/governor_passive.c (limited to 'include') diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index cedda8f1e5eb..d260cd0219f6 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -64,6 +64,14 @@ config DEVFREQ_GOV_USERSPACE Otherwise, the governor does not change the frequency given at the initialization. +config DEVFREQ_GOV_PASSIVE + tristate "Passive" + help + Sets the frequency based on the frequency of its parent devfreq + device. This governor does not change the frequency by itself + through sysfs entries. The passive governor recommends that + devfreq device uses the OPP table to get the frequency/voltage. + comment "DEVFREQ Drivers" config ARM_EXYNOS_BUS_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 8af8aaf922a8..2633087d5c63 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -4,6 +4,7 @@ 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 +obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o # DEVFREQ Drivers obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c new file mode 100644 index 000000000000..a4b0b02ee797 --- /dev/null +++ b/drivers/devfreq/governor_passive.c @@ -0,0 +1,207 @@ +/* + * linux/drivers/devfreq/governor_passive.c + * + * Copyright (C) 2016 Samsung Electronics + * Author: Chanwoo Choi + * Author: 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 "governor.h" + +static int devfreq_passive_get_target_freq(struct devfreq *devfreq, + unsigned long *freq) +{ + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent; + unsigned long child_freq = ULONG_MAX; + struct dev_pm_opp *opp; + int i, count, ret = 0; + + /* + * If the devfreq device with passive governor has the specific method + * to determine the next frequency, should use the get_target_freq() + * of struct devfreq_passive_data. + */ + if (p_data->get_target_freq) { + ret = p_data->get_target_freq(devfreq, freq); + goto out; + } + + /* + * If the parent and passive devfreq device uses the OPP table, + * get the next frequency by using the OPP table. + */ + + /* + * - parent devfreq device uses the governors except for passive. + * - passive devfreq device uses the passive governor. + * + * Each devfreq has the OPP table. After deciding the new frequency + * from the governor of parent devfreq device, the passive governor + * need to get the index of new frequency on OPP table of parent + * device. And then the index is used for getting the suitable + * new frequency for passive devfreq device. + */ + if (!devfreq->profile || !devfreq->profile->freq_table + || devfreq->profile->max_state <= 0) + return -EINVAL; + + /* + * The passive governor have to get the correct frequency from OPP + * list of parent device. Because in this case, *freq is temporary + * value which is decided by ondemand governor. + */ + rcu_read_lock(); + opp = devfreq_recommended_opp(parent_devfreq->dev.parent, freq, 0); + rcu_read_unlock(); + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + goto out; + } + + /* + * Get the OPP table's index of decided freqeuncy by governor + * of parent device. + */ + for (i = 0; i < parent_devfreq->profile->max_state; i++) + if (parent_devfreq->profile->freq_table[i] == *freq) + break; + + if (i == parent_devfreq->profile->max_state) { + ret = -EINVAL; + goto out; + } + + /* Get the suitable frequency by using index of parent device. */ + if (i < devfreq->profile->max_state) { + child_freq = devfreq->profile->freq_table[i]; + } else { + count = devfreq->profile->max_state; + child_freq = devfreq->profile->freq_table[count - 1]; + } + + /* Return the suitable frequency for passive device. */ + *freq = child_freq; + +out: + return ret; +} + +static int update_devfreq_passive(struct devfreq *devfreq, unsigned long freq) +{ + int ret; + + if (!devfreq->governor) + return -EINVAL; + + mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING); + + ret = devfreq->governor->get_target_freq(devfreq, &freq); + if (ret < 0) + goto out; + + ret = devfreq->profile->target(devfreq->dev.parent, &freq, 0); + if (ret < 0) + goto out; + + devfreq->previous_freq = freq; + +out: + mutex_unlock(&devfreq->lock); + + return 0; +} + +static int devfreq_passive_notifier_call(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct devfreq_passive_data *data + = container_of(nb, struct devfreq_passive_data, nb); + struct devfreq *devfreq = (struct devfreq *)data->this; + struct devfreq *parent = (struct devfreq *)data->parent; + struct devfreq_freqs *freqs = (struct devfreq_freqs *)ptr; + unsigned long freq = freqs->new; + + switch (event) { + case DEVFREQ_PRECHANGE: + if (parent->previous_freq > freq) + update_devfreq_passive(devfreq, freq); + break; + case DEVFREQ_POSTCHANGE: + if (parent->previous_freq < freq) + update_devfreq_passive(devfreq, freq); + break; + } + + return NOTIFY_DONE; +} + +static int devfreq_passive_event_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + struct device *dev = devfreq->dev.parent; + struct devfreq_passive_data *p_data + = (struct devfreq_passive_data *)devfreq->data; + struct devfreq *parent = (struct devfreq *)p_data->parent; + struct notifier_block *nb = &p_data->nb; + int ret = 0; + + if (!parent) + return -EPROBE_DEFER; + + switch (event) { + case DEVFREQ_GOV_START: + if (!p_data->this) + p_data->this = devfreq; + + nb->notifier_call = devfreq_passive_notifier_call; + ret = devm_devfreq_register_notifier(dev, parent, nb, + DEVFREQ_TRANSITION_NOTIFIER); + break; + case DEVFREQ_GOV_STOP: + devm_devfreq_unregister_notifier(dev, parent, nb, + DEVFREQ_TRANSITION_NOTIFIER); + break; + default: + break; + } + + return ret; +} + +static struct devfreq_governor devfreq_passive = { + .name = "passive", + .get_target_freq = devfreq_passive_get_target_freq, + .event_handler = devfreq_passive_event_handler, +}; + +static int __init devfreq_passive_init(void) +{ + return devfreq_add_governor(&devfreq_passive); +} +subsys_initcall(devfreq_passive_init); + +static void __exit devfreq_passive_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_passive); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_passive_exit); + +MODULE_AUTHOR("Chanwoo Choi "); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("DEVFREQ Passive governor"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index 98c699304e12..2de4e2eea180 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -272,6 +272,39 @@ struct devfreq_simple_ondemand_data { }; #endif +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE) +/** + * struct devfreq_passive_data - void *data fed to struct devfreq + * and devfreq_add_device + * @parent: the devfreq instance of parent device. + * @get_target_freq: Optional callback, Returns desired operating frequency + * for the device using passive governor. That is called + * when passive governor should decide the next frequency + * by using the new frequency of parent devfreq device + * using governors except for passive governor. + * If the devfreq device has the specific method to decide + * the next frequency, should use this callback. + * @this: the devfreq instance of own device. + * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER list + * + * The devfreq_passive_data have to set the devfreq instance of parent + * device with governors except for the passive governor. But, don't need to + * initialize the 'this' and 'nb' field because the devfreq core will handle + * them. + */ +struct devfreq_passive_data { + /* Should set the devfreq instance of parent device */ + struct devfreq *parent; + + /* Optional callback to decide the next frequency of passvice device */ + int (*get_target_freq)(struct devfreq *this, unsigned long *freq); + + /* For passive governor's internal use. Don't need to set them */ + struct devfreq *this; + struct notifier_block nb; +}; +#endif + #else /* !CONFIG_PM_DEVFREQ */ static inline struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, -- cgit v1.2.3