aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChanwoo Choi <cw00.choi@samsung.com>2016-03-22 00:44:03 -0400
committerMyungJoo Ham <myungjoo.ham@samsung.com>2016-05-02 22:20:07 -0400
commit996133119f57334c38b020dbfaaac5b5eb127e29 (patch)
treee197b5d3bc8b7816db57c3bff4b509c6eb111a1f
parent0fe3a66410a3ba96679be903f1e287d7a0a264a9 (diff)
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 <myungjoo.ham@samsung.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com> [tjakobi: Reported RCU locking issue and cw00.choi fix it] Reported-by: Tobias Jakobi <tjakobi@math.uni-bielefeld.de> [linux.amoon: Reported possible recursive locking and cw00.choi fix it] Reported-by: Anand Moon <linux.amoon@gmail.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Acked-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
-rw-r--r--drivers/devfreq/Kconfig8
-rw-r--r--drivers/devfreq/Makefile1
-rw-r--r--drivers/devfreq/governor_passive.c207
-rw-r--r--include/linux/devfreq.h33
4 files changed, 249 insertions, 0 deletions
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
64 Otherwise, the governor does not change the frequency 64 Otherwise, the governor does not change the frequency
65 given at the initialization. 65 given at the initialization.
66 66
67config DEVFREQ_GOV_PASSIVE
68 tristate "Passive"
69 help
70 Sets the frequency based on the frequency of its parent devfreq
71 device. This governor does not change the frequency by itself
72 through sysfs entries. The passive governor recommends that
73 devfreq device uses the OPP table to get the frequency/voltage.
74
67comment "DEVFREQ Drivers" 75comment "DEVFREQ Drivers"
68 76
69config ARM_EXYNOS_BUS_DEVFREQ 77config 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
4obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o 4obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
5obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o 5obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
6obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o 6obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
7obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
7 8
8# DEVFREQ Drivers 9# DEVFREQ Drivers
9obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o 10obj-$(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 @@
1/*
2 * linux/drivers/devfreq/governor_passive.c
3 *
4 * Copyright (C) 2016 Samsung Electronics
5 * Author: Chanwoo Choi <cw00.choi@samsung.com>
6 * Author: MyungJoo Ham <myungjoo.ham@samsung.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 */
12
13#include <linux/module.h>
14#include <linux/device.h>
15#include <linux/devfreq.h>
16#include "governor.h"
17
18static int devfreq_passive_get_target_freq(struct devfreq *devfreq,
19 unsigned long *freq)
20{
21 struct devfreq_passive_data *p_data
22 = (struct devfreq_passive_data *)devfreq->data;
23 struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent;
24 unsigned long child_freq = ULONG_MAX;
25 struct dev_pm_opp *opp;
26 int i, count, ret = 0;
27
28 /*
29 * If the devfreq device with passive governor has the specific method
30 * to determine the next frequency, should use the get_target_freq()
31 * of struct devfreq_passive_data.
32 */
33 if (p_data->get_target_freq) {
34 ret = p_data->get_target_freq(devfreq, freq);
35 goto out;
36 }
37
38 /*
39 * If the parent and passive devfreq device uses the OPP table,
40 * get the next frequency by using the OPP table.
41 */
42
43 /*
44 * - parent devfreq device uses the governors except for passive.
45 * - passive devfreq device uses the passive governor.
46 *
47 * Each devfreq has the OPP table. After deciding the new frequency
48 * from the governor of parent devfreq device, the passive governor
49 * need to get the index of new frequency on OPP table of parent
50 * device. And then the index is used for getting the suitable
51 * new frequency for passive devfreq device.
52 */
53 if (!devfreq->profile || !devfreq->profile->freq_table
54 || devfreq->profile->max_state <= 0)
55 return -EINVAL;
56
57 /*
58 * The passive governor have to get the correct frequency from OPP
59 * list of parent device. Because in this case, *freq is temporary
60 * value which is decided by ondemand governor.
61 */
62 rcu_read_lock();
63 opp = devfreq_recommended_opp(parent_devfreq->dev.parent, freq, 0);
64 rcu_read_unlock();
65 if (IS_ERR(opp)) {
66 ret = PTR_ERR(opp);
67 goto out;
68 }
69
70 /*
71 * Get the OPP table's index of decided freqeuncy by governor
72 * of parent device.
73 */
74 for (i = 0; i < parent_devfreq->profile->max_state; i++)
75 if (parent_devfreq->profile->freq_table[i] == *freq)
76 break;
77
78 if (i == parent_devfreq->profile->max_state) {
79 ret = -EINVAL;
80 goto out;
81 }
82
83 /* Get the suitable frequency by using index of parent device. */
84 if (i < devfreq->profile->max_state) {
85 child_freq = devfreq->profile->freq_table[i];
86 } else {
87 count = devfreq->profile->max_state;
88 child_freq = devfreq->profile->freq_table[count - 1];
89 }
90
91 /* Return the suitable frequency for passive device. */
92 *freq = child_freq;
93
94out:
95 return ret;
96}
97
98static int update_devfreq_passive(struct devfreq *devfreq, unsigned long freq)
99{
100 int ret;
101
102 if (!devfreq->governor)
103 return -EINVAL;
104
105 mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING);
106
107 ret = devfreq->governor->get_target_freq(devfreq, &freq);
108 if (ret < 0)
109 goto out;
110
111 ret = devfreq->profile->target(devfreq->dev.parent, &freq, 0);
112 if (ret < 0)
113 goto out;
114
115 devfreq->previous_freq = freq;
116
117out:
118 mutex_unlock(&devfreq->lock);
119
120 return 0;
121}
122
123static int devfreq_passive_notifier_call(struct notifier_block *nb,
124 unsigned long event, void *ptr)
125{
126 struct devfreq_passive_data *data
127 = container_of(nb, struct devfreq_passive_data, nb);
128 struct devfreq *devfreq = (struct devfreq *)data->this;
129 struct devfreq *parent = (struct devfreq *)data->parent;
130 struct devfreq_freqs *freqs = (struct devfreq_freqs *)ptr;
131 unsigned long freq = freqs->new;
132
133 switch (event) {
134 case DEVFREQ_PRECHANGE:
135 if (parent->previous_freq > freq)
136 update_devfreq_passive(devfreq, freq);
137 break;
138 case DEVFREQ_POSTCHANGE:
139 if (parent->previous_freq < freq)
140 update_devfreq_passive(devfreq, freq);
141 break;
142 }
143
144 return NOTIFY_DONE;
145}
146
147static int devfreq_passive_event_handler(struct devfreq *devfreq,
148 unsigned int event, void *data)
149{
150 struct device *dev = devfreq->dev.parent;
151 struct devfreq_passive_data *p_data
152 = (struct devfreq_passive_data *)devfreq->data;
153 struct devfreq *parent = (struct devfreq *)p_data->parent;
154 struct notifier_block *nb = &p_data->nb;
155 int ret = 0;
156
157 if (!parent)
158 return -EPROBE_DEFER;
159
160 switch (event) {
161 case DEVFREQ_GOV_START:
162 if (!p_data->this)
163 p_data->this = devfreq;
164
165 nb->notifier_call = devfreq_passive_notifier_call;
166 ret = devm_devfreq_register_notifier(dev, parent, nb,
167 DEVFREQ_TRANSITION_NOTIFIER);
168 break;
169 case DEVFREQ_GOV_STOP:
170 devm_devfreq_unregister_notifier(dev, parent, nb,
171 DEVFREQ_TRANSITION_NOTIFIER);
172 break;
173 default:
174 break;
175 }
176
177 return ret;
178}
179
180static struct devfreq_governor devfreq_passive = {
181 .name = "passive",
182 .get_target_freq = devfreq_passive_get_target_freq,
183 .event_handler = devfreq_passive_event_handler,
184};
185
186static int __init devfreq_passive_init(void)
187{
188 return devfreq_add_governor(&devfreq_passive);
189}
190subsys_initcall(devfreq_passive_init);
191
192static void __exit devfreq_passive_exit(void)
193{
194 int ret;
195
196 ret = devfreq_remove_governor(&devfreq_passive);
197 if (ret)
198 pr_err("%s: failed remove governor %d\n", __func__, ret);
199
200 return;
201}
202module_exit(devfreq_passive_exit);
203
204MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
205MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
206MODULE_DESCRIPTION("DEVFREQ Passive governor");
207MODULE_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 {
272}; 272};
273#endif 273#endif
274 274
275#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE)
276/**
277 * struct devfreq_passive_data - void *data fed to struct devfreq
278 * and devfreq_add_device
279 * @parent: the devfreq instance of parent device.
280 * @get_target_freq: Optional callback, Returns desired operating frequency
281 * for the device using passive governor. That is called
282 * when passive governor should decide the next frequency
283 * by using the new frequency of parent devfreq device
284 * using governors except for passive governor.
285 * If the devfreq device has the specific method to decide
286 * the next frequency, should use this callback.
287 * @this: the devfreq instance of own device.
288 * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER list
289 *
290 * The devfreq_passive_data have to set the devfreq instance of parent
291 * device with governors except for the passive governor. But, don't need to
292 * initialize the 'this' and 'nb' field because the devfreq core will handle
293 * them.
294 */
295struct devfreq_passive_data {
296 /* Should set the devfreq instance of parent device */
297 struct devfreq *parent;
298
299 /* Optional callback to decide the next frequency of passvice device */
300 int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
301
302 /* For passive governor's internal use. Don't need to set them */
303 struct devfreq *this;
304 struct notifier_block nb;
305};
306#endif
307
275#else /* !CONFIG_PM_DEVFREQ */ 308#else /* !CONFIG_PM_DEVFREQ */
276static inline struct devfreq *devfreq_add_device(struct device *dev, 309static inline struct devfreq *devfreq_add_device(struct device *dev,
277 struct devfreq_dev_profile *profile, 310 struct devfreq_dev_profile *profile,