diff options
-rw-r--r-- | drivers/devfreq/Kconfig | 8 | ||||
-rw-r--r-- | drivers/devfreq/Makefile | 1 | ||||
-rw-r--r-- | drivers/devfreq/governor_passive.c | 207 | ||||
-rw-r--r-- | include/linux/devfreq.h | 33 |
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 | ||
67 | config 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 | |||
67 | comment "DEVFREQ Drivers" | 75 | comment "DEVFREQ Drivers" |
68 | 76 | ||
69 | config ARM_EXYNOS_BUS_DEVFREQ | 77 | 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 | |||
4 | obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o | 4 | obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o |
5 | obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o | 5 | obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o |
6 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o | 6 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o |
7 | obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o | ||
7 | 8 | ||
8 | # DEVFREQ Drivers | 9 | # DEVFREQ Drivers |
9 | obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o | 10 | 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 @@ | |||
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 | |||
18 | static 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 | |||
94 | out: | ||
95 | return ret; | ||
96 | } | ||
97 | |||
98 | static 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 | |||
117 | out: | ||
118 | mutex_unlock(&devfreq->lock); | ||
119 | |||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static 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 | |||
147 | static 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 | |||
180 | static 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 | |||
186 | static int __init devfreq_passive_init(void) | ||
187 | { | ||
188 | return devfreq_add_governor(&devfreq_passive); | ||
189 | } | ||
190 | subsys_initcall(devfreq_passive_init); | ||
191 | |||
192 | static 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 | } | ||
202 | module_exit(devfreq_passive_exit); | ||
203 | |||
204 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); | ||
205 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | ||
206 | MODULE_DESCRIPTION("DEVFREQ Passive governor"); | ||
207 | 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 { | |||
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 | */ | ||
295 | struct 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 */ |
276 | static inline struct devfreq *devfreq_add_device(struct device *dev, | 309 | static inline struct devfreq *devfreq_add_device(struct device *dev, |
277 | struct devfreq_dev_profile *profile, | 310 | struct devfreq_dev_profile *profile, |