]>
Commit | Line | Data |
---|---|---|
457c8996 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d82b3518 | 2 | /* |
fe52de36 | 3 | * Power Management Quality of Service (PM QoS) support base. |
d82b3518 | 4 | * |
fe52de36 | 5 | * Copyright (C) 2020 Intel Corporation |
d82b3518 | 6 | * |
fe52de36 RW |
7 | * Authors: |
8 | * Mark Gross <[email protected]> | |
9 | * Rafael J. Wysocki <[email protected]> | |
d82b3518 | 10 | * |
fe52de36 RW |
11 | * Provided here is an interface for specifying PM QoS dependencies. It allows |
12 | * entities depending on QoS constraints to register their requests which are | |
13 | * aggregated as appropriate to produce effective constraints (target values) | |
14 | * that can be monitored by entities needing to respect them, either by polling | |
15 | * or through a built-in notification mechanism. | |
d82b3518 | 16 | * |
fe52de36 RW |
17 | * In addition to the basic functionality, more specific interfaces for managing |
18 | * global CPU latency QoS requests and frequency QoS requests are provided. | |
d82b3518 MG |
19 | */ |
20 | ||
ed77134b MG |
21 | /*#define DEBUG*/ |
22 | ||
e8db0be1 | 23 | #include <linux/pm_qos.h> |
d82b3518 MG |
24 | #include <linux/sched.h> |
25 | #include <linux/spinlock.h> | |
26 | #include <linux/slab.h> | |
27 | #include <linux/time.h> | |
28 | #include <linux/fs.h> | |
29 | #include <linux/device.h> | |
30 | #include <linux/miscdevice.h> | |
31 | #include <linux/string.h> | |
32 | #include <linux/platform_device.h> | |
33 | #include <linux/init.h> | |
0775a60a | 34 | #include <linux/kernel.h> |
f5f4eda4 NM |
35 | #include <linux/debugfs.h> |
36 | #include <linux/seq_file.h> | |
d82b3518 MG |
37 | |
38 | #include <linux/uaccess.h> | |
6e5fdeed | 39 | #include <linux/export.h> |
247e9ee0 | 40 | #include <trace/events/power.h> |
d82b3518 MG |
41 | |
42 | /* | |
cc749986 | 43 | * locking rule: all changes to constraints or notifiers lists |
d82b3518 MG |
44 | * or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock |
45 | * held, taken with _irqsave. One lock to rule them all | |
46 | */ | |
5f279845 JB |
47 | static DEFINE_SPINLOCK(pm_qos_lock); |
48 | ||
dcd70ca1 RW |
49 | /** |
50 | * pm_qos_read_value - Return the current effective constraint value. | |
51 | * @c: List of PM QoS constraint requests. | |
52 | */ | |
53 | s32 pm_qos_read_value(struct pm_qos_constraints *c) | |
54 | { | |
a534e924 | 55 | return READ_ONCE(c->target_value); |
dcd70ca1 RW |
56 | } |
57 | ||
58 | static int pm_qos_get_value(struct pm_qos_constraints *c) | |
d82b3518 | 59 | { |
abe98ec2 | 60 | if (plist_head_empty(&c->list)) |
327adaed | 61 | return c->no_constraint_value; |
d82b3518 | 62 | |
abe98ec2 | 63 | switch (c->type) { |
5f279845 | 64 | case PM_QOS_MIN: |
abe98ec2 | 65 | return plist_first(&c->list)->prio; |
d82b3518 | 66 | |
5f279845 | 67 | case PM_QOS_MAX: |
abe98ec2 | 68 | return plist_last(&c->list)->prio; |
d82b3518 | 69 | |
5f279845 | 70 | default: |
dcd70ca1 | 71 | WARN(1, "Unknown PM QoS type in %s\n", __func__); |
c6a57bff | 72 | return PM_QOS_DEFAULT_VALUE; |
5f279845 JB |
73 | } |
74 | } | |
75 | ||
dcd70ca1 | 76 | static void pm_qos_set_value(struct pm_qos_constraints *c, s32 value) |
333c5ae9 | 77 | { |
a534e924 | 78 | WRITE_ONCE(c->target_value, value); |
333c5ae9 TC |
79 | } |
80 | ||
abe98ec2 | 81 | /** |
7b35370b RW |
82 | * pm_qos_update_target - Update a list of PM QoS constraint requests. |
83 | * @c: List of PM QoS requests. | |
84 | * @node: Target list entry. | |
85 | * @action: Action to carry out (add, update or remove). | |
86 | * @value: New request value for the target list entry. | |
abe98ec2 | 87 | * |
7b35370b RW |
88 | * Update the given list of PM QoS constraint requests, @c, by carrying an |
89 | * @action involving the @node list entry and @value on it. | |
90 | * | |
91 | * The recognized values of @action are PM_QOS_ADD_REQ (store @value in @node | |
92 | * and add it to the list), PM_QOS_UPDATE_REQ (remove @node from the list, store | |
93 | * @value in it and add it to the list again), and PM_QOS_REMOVE_REQ (remove | |
94 | * @node from the list, ignore @value). | |
95 | * | |
96 | * Return: 1 if the aggregate constraint value has changed, 0 otherwise. | |
abe98ec2 JP |
97 | */ |
98 | int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node, | |
99 | enum pm_qos_req_action action, int value) | |
d82b3518 | 100 | { |
abe98ec2 | 101 | int prev_value, curr_value, new_value; |
7b35370b | 102 | unsigned long flags; |
d82b3518 MG |
103 | |
104 | spin_lock_irqsave(&pm_qos_lock, flags); | |
7b35370b | 105 | |
abe98ec2 JP |
106 | prev_value = pm_qos_get_value(c); |
107 | if (value == PM_QOS_DEFAULT_VALUE) | |
108 | new_value = c->default_value; | |
109 | else | |
110 | new_value = value; | |
111 | ||
112 | switch (action) { | |
113 | case PM_QOS_REMOVE_REQ: | |
114 | plist_del(node, &c->list); | |
115 | break; | |
116 | case PM_QOS_UPDATE_REQ: | |
5f279845 | 117 | /* |
7b35370b RW |
118 | * To change the list, atomically remove, reinit with new value |
119 | * and add, then see if the aggregate has changed. | |
5f279845 | 120 | */ |
abe98ec2 | 121 | plist_del(node, &c->list); |
df561f66 | 122 | fallthrough; |
abe98ec2 JP |
123 | case PM_QOS_ADD_REQ: |
124 | plist_node_init(node, new_value); | |
125 | plist_add(node, &c->list); | |
126 | break; | |
127 | default: | |
128 | /* no action */ | |
129 | ; | |
d82b3518 | 130 | } |
abe98ec2 JP |
131 | |
132 | curr_value = pm_qos_get_value(c); | |
133 | pm_qos_set_value(c, curr_value); | |
134 | ||
d82b3518 MG |
135 | spin_unlock_irqrestore(&pm_qos_lock, flags); |
136 | ||
247e9ee0 | 137 | trace_pm_qos_update_target(action, prev_value, curr_value); |
7b35370b RW |
138 | |
139 | if (prev_value == curr_value) | |
140 | return 0; | |
141 | ||
142 | if (c->notifiers) | |
143 | blocking_notifier_call_chain(c->notifiers, curr_value, NULL); | |
144 | ||
145 | return 1; | |
d82b3518 MG |
146 | } |
147 | ||
5efbe427 RW |
148 | /** |
149 | * pm_qos_flags_remove_req - Remove device PM QoS flags request. | |
150 | * @pqf: Device PM QoS flags set to remove the request from. | |
151 | * @req: Request to remove from the set. | |
152 | */ | |
153 | static void pm_qos_flags_remove_req(struct pm_qos_flags *pqf, | |
154 | struct pm_qos_flags_request *req) | |
155 | { | |
156 | s32 val = 0; | |
157 | ||
158 | list_del(&req->node); | |
159 | list_for_each_entry(req, &pqf->list, node) | |
160 | val |= req->flags; | |
161 | ||
162 | pqf->effective_flags = val; | |
163 | } | |
164 | ||
165 | /** | |
166 | * pm_qos_update_flags - Update a set of PM QoS flags. | |
7b35370b | 167 | * @pqf: Set of PM QoS flags to update. |
5efbe427 RW |
168 | * @req: Request to add to the set, to modify, or to remove from the set. |
169 | * @action: Action to take on the set. | |
170 | * @val: Value of the request to add or modify. | |
171 | * | |
7b35370b | 172 | * Return: 1 if the aggregate constraint value has changed, 0 otherwise. |
5efbe427 RW |
173 | */ |
174 | bool pm_qos_update_flags(struct pm_qos_flags *pqf, | |
175 | struct pm_qos_flags_request *req, | |
176 | enum pm_qos_req_action action, s32 val) | |
177 | { | |
178 | unsigned long irqflags; | |
179 | s32 prev_value, curr_value; | |
180 | ||
181 | spin_lock_irqsave(&pm_qos_lock, irqflags); | |
182 | ||
183 | prev_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags; | |
184 | ||
185 | switch (action) { | |
186 | case PM_QOS_REMOVE_REQ: | |
187 | pm_qos_flags_remove_req(pqf, req); | |
188 | break; | |
189 | case PM_QOS_UPDATE_REQ: | |
190 | pm_qos_flags_remove_req(pqf, req); | |
df561f66 | 191 | fallthrough; |
5efbe427 RW |
192 | case PM_QOS_ADD_REQ: |
193 | req->flags = val; | |
194 | INIT_LIST_HEAD(&req->node); | |
195 | list_add_tail(&req->node, &pqf->list); | |
196 | pqf->effective_flags |= val; | |
197 | break; | |
198 | default: | |
199 | /* no action */ | |
200 | ; | |
201 | } | |
202 | ||
203 | curr_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags; | |
204 | ||
205 | spin_unlock_irqrestore(&pm_qos_lock, irqflags); | |
206 | ||
247e9ee0 | 207 | trace_pm_qos_update_flags(action, prev_value, curr_value); |
7b35370b | 208 | |
5efbe427 RW |
209 | return prev_value != curr_value; |
210 | } | |
211 | ||
814d51f8 | 212 | #ifdef CONFIG_CPU_IDLE |
2552d352 RW |
213 | /* Definitions related to the CPU latency QoS. */ |
214 | ||
215 | static struct pm_qos_constraints cpu_latency_constraints = { | |
216 | .list = PLIST_HEAD_INIT(cpu_latency_constraints.list), | |
217 | .target_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, | |
218 | .default_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, | |
219 | .no_constraint_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, | |
220 | .type = PM_QOS_MIN, | |
221 | }; | |
222 | ||
5f55836a CL |
223 | static inline bool cpu_latency_qos_value_invalid(s32 value) |
224 | { | |
225 | return value < 0 && value != PM_QOS_DEFAULT_VALUE; | |
226 | } | |
227 | ||
d82b3518 | 228 | /** |
67b06ba0 | 229 | * cpu_latency_qos_limit - Return current system-wide CPU latency QoS limit. |
d82b3518 | 230 | */ |
67b06ba0 | 231 | s32 cpu_latency_qos_limit(void) |
d82b3518 | 232 | { |
2552d352 | 233 | return pm_qos_read_value(&cpu_latency_constraints); |
d82b3518 | 234 | } |
d82b3518 | 235 | |
67b06ba0 RW |
236 | /** |
237 | * cpu_latency_qos_request_active - Check the given PM QoS request. | |
238 | * @req: PM QoS request to check. | |
239 | * | |
240 | * Return: 'true' if @req has been added to the CPU latency QoS list, 'false' | |
241 | * otherwise. | |
242 | */ | |
243 | bool cpu_latency_qos_request_active(struct pm_qos_request *req) | |
82f68251 | 244 | { |
2552d352 | 245 | return req->qos == &cpu_latency_constraints; |
82f68251 | 246 | } |
67b06ba0 | 247 | EXPORT_SYMBOL_GPL(cpu_latency_qos_request_active); |
82f68251 | 248 | |
333eed7d RW |
249 | static void cpu_latency_qos_apply(struct pm_qos_request *req, |
250 | enum pm_qos_req_action action, s32 value) | |
3a4a0042 RW |
251 | { |
252 | int ret = pm_qos_update_target(req->qos, &req->node, action, value); | |
253 | if (ret > 0) | |
254 | wake_up_all_idle_cpus(); | |
255 | } | |
256 | ||
d82b3518 | 257 | /** |
67b06ba0 RW |
258 | * cpu_latency_qos_add_request - Add new CPU latency QoS request. |
259 | * @req: Pointer to a preallocated handle. | |
260 | * @value: Requested constraint value. | |
261 | * | |
262 | * Use @value to initialize the request handle pointed to by @req, insert it as | |
263 | * a new entry to the CPU latency QoS list and recompute the effective QoS | |
264 | * constraint for that list. | |
d82b3518 | 265 | * |
67b06ba0 RW |
266 | * Callers need to save the handle for later use in updates and removal of the |
267 | * QoS request represented by it. | |
d82b3518 | 268 | */ |
67b06ba0 | 269 | void cpu_latency_qos_add_request(struct pm_qos_request *req, s32 value) |
d82b3518 | 270 | { |
5f55836a | 271 | if (!req || cpu_latency_qos_value_invalid(value)) |
abe98ec2 | 272 | return; |
d82b3518 | 273 | |
67b06ba0 RW |
274 | if (cpu_latency_qos_request_active(req)) { |
275 | WARN(1, KERN_ERR "%s called for already added request\n", __func__); | |
82f68251 JB |
276 | return; |
277 | } | |
02c92a37 | 278 | |
333eed7d | 279 | trace_pm_qos_add_request(value); |
02c92a37 | 280 | |
2552d352 | 281 | req->qos = &cpu_latency_constraints; |
333eed7d | 282 | cpu_latency_qos_apply(req, PM_QOS_ADD_REQ, value); |
d82b3518 | 283 | } |
67b06ba0 | 284 | EXPORT_SYMBOL_GPL(cpu_latency_qos_add_request); |
d82b3518 MG |
285 | |
286 | /** | |
67b06ba0 RW |
287 | * cpu_latency_qos_update_request - Modify existing CPU latency QoS request. |
288 | * @req : QoS request to update. | |
289 | * @new_value: New requested constraint value. | |
d82b3518 | 290 | * |
67b06ba0 RW |
291 | * Use @new_value to update the QoS request represented by @req in the CPU |
292 | * latency QoS list along with updating the effective constraint value for that | |
293 | * list. | |
d82b3518 | 294 | */ |
67b06ba0 | 295 | void cpu_latency_qos_update_request(struct pm_qos_request *req, s32 new_value) |
d82b3518 | 296 | { |
5f55836a | 297 | if (!req || cpu_latency_qos_value_invalid(new_value)) |
5f279845 JB |
298 | return; |
299 | ||
67b06ba0 RW |
300 | if (!cpu_latency_qos_request_active(req)) { |
301 | WARN(1, KERN_ERR "%s called for unknown object\n", __func__); | |
82f68251 JB |
302 | return; |
303 | } | |
304 | ||
333eed7d | 305 | trace_pm_qos_update_request(new_value); |
02c92a37 RW |
306 | |
307 | if (new_value == req->node.prio) | |
308 | return; | |
309 | ||
333eed7d | 310 | cpu_latency_qos_apply(req, PM_QOS_UPDATE_REQ, new_value); |
d82b3518 | 311 | } |
67b06ba0 | 312 | EXPORT_SYMBOL_GPL(cpu_latency_qos_update_request); |
d82b3518 MG |
313 | |
314 | /** | |
67b06ba0 RW |
315 | * cpu_latency_qos_remove_request - Remove existing CPU latency QoS request. |
316 | * @req: QoS request to remove. | |
d82b3518 | 317 | * |
67b06ba0 RW |
318 | * Remove the CPU latency QoS request represented by @req from the CPU latency |
319 | * QoS list along with updating the effective constraint value for that list. | |
d82b3518 | 320 | */ |
67b06ba0 | 321 | void cpu_latency_qos_remove_request(struct pm_qos_request *req) |
d82b3518 | 322 | { |
67b06ba0 | 323 | if (!req) |
ed77134b | 324 | return; |
d82b3518 | 325 | |
67b06ba0 RW |
326 | if (!cpu_latency_qos_request_active(req)) { |
327 | WARN(1, KERN_ERR "%s called for unknown object\n", __func__); | |
82f68251 JB |
328 | return; |
329 | } | |
330 | ||
333eed7d | 331 | trace_pm_qos_remove_request(PM_QOS_DEFAULT_VALUE); |
02c92a37 | 332 | |
333eed7d | 333 | cpu_latency_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); |
cc749986 | 334 | memset(req, 0, sizeof(*req)); |
d82b3518 | 335 | } |
67b06ba0 | 336 | EXPORT_SYMBOL_GPL(cpu_latency_qos_remove_request); |
d82b3518 | 337 | |
2552d352 | 338 | /* User space interface to the CPU latency QoS via misc device. */ |
63cffc05 | 339 | |
2552d352 | 340 | static int cpu_latency_qos_open(struct inode *inode, struct file *filp) |
d82b3518 | 341 | { |
63cffc05 | 342 | struct pm_qos_request *req; |
d82b3518 | 343 | |
63cffc05 RW |
344 | req = kzalloc(sizeof(*req), GFP_KERNEL); |
345 | if (!req) | |
346 | return -ENOMEM; | |
82f68251 | 347 | |
67b06ba0 | 348 | cpu_latency_qos_add_request(req, PM_QOS_DEFAULT_VALUE); |
63cffc05 | 349 | filp->private_data = req; |
ed77134b | 350 | |
63cffc05 | 351 | return 0; |
d82b3518 MG |
352 | } |
353 | ||
2552d352 | 354 | static int cpu_latency_qos_release(struct inode *inode, struct file *filp) |
d82b3518 | 355 | { |
299a2298 RW |
356 | struct pm_qos_request *req = filp->private_data; |
357 | ||
358 | filp->private_data = NULL; | |
d82b3518 | 359 | |
67b06ba0 | 360 | cpu_latency_qos_remove_request(req); |
82f68251 | 361 | kfree(req); |
d82b3518 MG |
362 | |
363 | return 0; | |
364 | } | |
365 | ||
2552d352 RW |
366 | static ssize_t cpu_latency_qos_read(struct file *filp, char __user *buf, |
367 | size_t count, loff_t *f_pos) | |
f9b9e806 | 368 | { |
cc749986 | 369 | struct pm_qos_request *req = filp->private_data; |
299a2298 RW |
370 | unsigned long flags; |
371 | s32 value; | |
f9b9e806 | 372 | |
67b06ba0 | 373 | if (!req || !cpu_latency_qos_request_active(req)) |
f9b9e806 TR |
374 | return -EINVAL; |
375 | ||
f9b9e806 | 376 | spin_lock_irqsave(&pm_qos_lock, flags); |
2552d352 | 377 | value = pm_qos_get_value(&cpu_latency_constraints); |
f9b9e806 TR |
378 | spin_unlock_irqrestore(&pm_qos_lock, flags); |
379 | ||
380 | return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32)); | |
381 | } | |
382 | ||
2552d352 RW |
383 | static ssize_t cpu_latency_qos_write(struct file *filp, const char __user *buf, |
384 | size_t count, loff_t *f_pos) | |
d82b3518 MG |
385 | { |
386 | s32 value; | |
ed77134b MG |
387 | |
388 | if (count == sizeof(s32)) { | |
389 | if (copy_from_user(&value, buf, sizeof(s32))) | |
390 | return -EFAULT; | |
d4f7ecf7 | 391 | } else { |
0775a60a RW |
392 | int ret; |
393 | ||
d4f7ecf7 AS |
394 | ret = kstrtos32_from_user(buf, count, 16, &value); |
395 | if (ret) | |
396 | return ret; | |
0775a60a | 397 | } |
d82b3518 | 398 | |
67b06ba0 | 399 | cpu_latency_qos_update_request(filp->private_data, value); |
ed77134b MG |
400 | |
401 | return count; | |
d82b3518 MG |
402 | } |
403 | ||
2552d352 RW |
404 | static const struct file_operations cpu_latency_qos_fops = { |
405 | .write = cpu_latency_qos_write, | |
406 | .read = cpu_latency_qos_read, | |
407 | .open = cpu_latency_qos_open, | |
408 | .release = cpu_latency_qos_release, | |
299a2298 RW |
409 | .llseek = noop_llseek, |
410 | }; | |
411 | ||
02c92a37 RW |
412 | static struct miscdevice cpu_latency_qos_miscdev = { |
413 | .minor = MISC_DYNAMIC_MINOR, | |
414 | .name = "cpu_dma_latency", | |
2552d352 | 415 | .fops = &cpu_latency_qos_fops, |
02c92a37 | 416 | }; |
299a2298 | 417 | |
2552d352 | 418 | static int __init cpu_latency_qos_init(void) |
d82b3518 | 419 | { |
63cffc05 | 420 | int ret; |
d82b3518 | 421 | |
02c92a37 | 422 | ret = misc_register(&cpu_latency_qos_miscdev); |
63cffc05 RW |
423 | if (ret < 0) |
424 | pr_err("%s: %s setup failed\n", __func__, | |
02c92a37 | 425 | cpu_latency_qos_miscdev.name); |
d82b3518 MG |
426 | |
427 | return ret; | |
428 | } | |
2552d352 | 429 | late_initcall(cpu_latency_qos_init); |
814d51f8 | 430 | #endif /* CONFIG_CPU_IDLE */ |
77751a46 RW |
431 | |
432 | /* Definitions related to the frequency QoS below. */ | |
433 | ||
3a8395b5 CY |
434 | static inline bool freq_qos_value_invalid(s32 value) |
435 | { | |
436 | return value < 0 && value != PM_QOS_DEFAULT_VALUE; | |
437 | } | |
438 | ||
77751a46 RW |
439 | /** |
440 | * freq_constraints_init - Initialize frequency QoS constraints. | |
441 | * @qos: Frequency QoS constraints to initialize. | |
442 | */ | |
443 | void freq_constraints_init(struct freq_constraints *qos) | |
444 | { | |
445 | struct pm_qos_constraints *c; | |
446 | ||
447 | c = &qos->min_freq; | |
448 | plist_head_init(&c->list); | |
449 | c->target_value = FREQ_QOS_MIN_DEFAULT_VALUE; | |
450 | c->default_value = FREQ_QOS_MIN_DEFAULT_VALUE; | |
451 | c->no_constraint_value = FREQ_QOS_MIN_DEFAULT_VALUE; | |
452 | c->type = PM_QOS_MAX; | |
453 | c->notifiers = &qos->min_freq_notifiers; | |
454 | BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers); | |
455 | ||
456 | c = &qos->max_freq; | |
457 | plist_head_init(&c->list); | |
458 | c->target_value = FREQ_QOS_MAX_DEFAULT_VALUE; | |
459 | c->default_value = FREQ_QOS_MAX_DEFAULT_VALUE; | |
460 | c->no_constraint_value = FREQ_QOS_MAX_DEFAULT_VALUE; | |
461 | c->type = PM_QOS_MIN; | |
462 | c->notifiers = &qos->max_freq_notifiers; | |
463 | BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers); | |
464 | } | |
465 | ||
466 | /** | |
467 | * freq_qos_read_value - Get frequency QoS constraint for a given list. | |
468 | * @qos: Constraints to evaluate. | |
469 | * @type: QoS request type. | |
470 | */ | |
471 | s32 freq_qos_read_value(struct freq_constraints *qos, | |
472 | enum freq_qos_req_type type) | |
473 | { | |
474 | s32 ret; | |
475 | ||
476 | switch (type) { | |
477 | case FREQ_QOS_MIN: | |
478 | ret = IS_ERR_OR_NULL(qos) ? | |
479 | FREQ_QOS_MIN_DEFAULT_VALUE : | |
480 | pm_qos_read_value(&qos->min_freq); | |
481 | break; | |
482 | case FREQ_QOS_MAX: | |
483 | ret = IS_ERR_OR_NULL(qos) ? | |
484 | FREQ_QOS_MAX_DEFAULT_VALUE : | |
485 | pm_qos_read_value(&qos->max_freq); | |
486 | break; | |
487 | default: | |
488 | WARN_ON(1); | |
489 | ret = 0; | |
490 | } | |
491 | ||
492 | return ret; | |
493 | } | |
494 | ||
495 | /** | |
496 | * freq_qos_apply - Add/modify/remove frequency QoS request. | |
497 | * @req: Constraint request to apply. | |
498 | * @action: Action to perform (add/update/remove). | |
499 | * @value: Value to assign to the QoS request. | |
36a8015f LC |
500 | * |
501 | * This is only meant to be called from inside pm_qos, not drivers. | |
77751a46 | 502 | */ |
36a8015f | 503 | int freq_qos_apply(struct freq_qos_request *req, |
77751a46 RW |
504 | enum pm_qos_req_action action, s32 value) |
505 | { | |
506 | int ret; | |
507 | ||
508 | switch(req->type) { | |
509 | case FREQ_QOS_MIN: | |
510 | ret = pm_qos_update_target(&req->qos->min_freq, &req->pnode, | |
511 | action, value); | |
512 | break; | |
513 | case FREQ_QOS_MAX: | |
514 | ret = pm_qos_update_target(&req->qos->max_freq, &req->pnode, | |
515 | action, value); | |
516 | break; | |
517 | default: | |
518 | ret = -EINVAL; | |
519 | } | |
520 | ||
521 | return ret; | |
522 | } | |
523 | ||
524 | /** | |
525 | * freq_qos_add_request - Insert new frequency QoS request into a given list. | |
526 | * @qos: Constraints to update. | |
527 | * @req: Preallocated request object. | |
528 | * @type: Request type. | |
529 | * @value: Request value. | |
530 | * | |
531 | * Insert a new entry into the @qos list of requests, recompute the effective | |
532 | * QoS constraint value for that list and initialize the @req object. The | |
533 | * caller needs to save that object for later use in updates and removal. | |
534 | * | |
535 | * Return 1 if the effective constraint value has changed, 0 if the effective | |
536 | * constraint value has not changed, or a negative error code on failures. | |
537 | */ | |
538 | int freq_qos_add_request(struct freq_constraints *qos, | |
539 | struct freq_qos_request *req, | |
540 | enum freq_qos_req_type type, s32 value) | |
541 | { | |
542 | int ret; | |
543 | ||
3a8395b5 | 544 | if (IS_ERR_OR_NULL(qos) || !req || freq_qos_value_invalid(value)) |
77751a46 RW |
545 | return -EINVAL; |
546 | ||
547 | if (WARN(freq_qos_request_active(req), | |
548 | "%s() called for active request\n", __func__)) | |
549 | return -EINVAL; | |
550 | ||
551 | req->qos = qos; | |
552 | req->type = type; | |
553 | ret = freq_qos_apply(req, PM_QOS_ADD_REQ, value); | |
554 | if (ret < 0) { | |
555 | req->qos = NULL; | |
556 | req->type = 0; | |
557 | } | |
558 | ||
559 | return ret; | |
560 | } | |
561 | EXPORT_SYMBOL_GPL(freq_qos_add_request); | |
562 | ||
563 | /** | |
564 | * freq_qos_update_request - Modify existing frequency QoS request. | |
565 | * @req: Request to modify. | |
566 | * @new_value: New request value. | |
567 | * | |
568 | * Update an existing frequency QoS request along with the effective constraint | |
569 | * value for the list of requests it belongs to. | |
570 | * | |
571 | * Return 1 if the effective constraint value has changed, 0 if the effective | |
572 | * constraint value has not changed, or a negative error code on failures. | |
573 | */ | |
574 | int freq_qos_update_request(struct freq_qos_request *req, s32 new_value) | |
575 | { | |
3a8395b5 | 576 | if (!req || freq_qos_value_invalid(new_value)) |
77751a46 RW |
577 | return -EINVAL; |
578 | ||
579 | if (WARN(!freq_qos_request_active(req), | |
580 | "%s() called for unknown object\n", __func__)) | |
581 | return -EINVAL; | |
582 | ||
583 | if (req->pnode.prio == new_value) | |
584 | return 0; | |
585 | ||
586 | return freq_qos_apply(req, PM_QOS_UPDATE_REQ, new_value); | |
587 | } | |
588 | EXPORT_SYMBOL_GPL(freq_qos_update_request); | |
589 | ||
590 | /** | |
591 | * freq_qos_remove_request - Remove frequency QoS request from its list. | |
592 | * @req: Request to remove. | |
593 | * | |
594 | * Remove the given frequency QoS request from the list of constraints it | |
595 | * belongs to and recompute the effective constraint value for that list. | |
596 | * | |
597 | * Return 1 if the effective constraint value has changed, 0 if the effective | |
598 | * constraint value has not changed, or a negative error code on failures. | |
599 | */ | |
600 | int freq_qos_remove_request(struct freq_qos_request *req) | |
601 | { | |
05ff1ba4 RW |
602 | int ret; |
603 | ||
77751a46 RW |
604 | if (!req) |
605 | return -EINVAL; | |
606 | ||
607 | if (WARN(!freq_qos_request_active(req), | |
608 | "%s() called for unknown object\n", __func__)) | |
609 | return -EINVAL; | |
610 | ||
05ff1ba4 RW |
611 | ret = freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); |
612 | req->qos = NULL; | |
613 | req->type = 0; | |
614 | ||
615 | return ret; | |
77751a46 RW |
616 | } |
617 | EXPORT_SYMBOL_GPL(freq_qos_remove_request); | |
618 | ||
619 | /** | |
620 | * freq_qos_add_notifier - Add frequency QoS change notifier. | |
621 | * @qos: List of requests to add the notifier to. | |
622 | * @type: Request type. | |
623 | * @notifier: Notifier block to add. | |
624 | */ | |
625 | int freq_qos_add_notifier(struct freq_constraints *qos, | |
626 | enum freq_qos_req_type type, | |
627 | struct notifier_block *notifier) | |
628 | { | |
629 | int ret; | |
630 | ||
631 | if (IS_ERR_OR_NULL(qos) || !notifier) | |
632 | return -EINVAL; | |
633 | ||
634 | switch (type) { | |
635 | case FREQ_QOS_MIN: | |
636 | ret = blocking_notifier_chain_register(qos->min_freq.notifiers, | |
637 | notifier); | |
638 | break; | |
639 | case FREQ_QOS_MAX: | |
640 | ret = blocking_notifier_chain_register(qos->max_freq.notifiers, | |
641 | notifier); | |
642 | break; | |
643 | default: | |
644 | WARN_ON(1); | |
645 | ret = -EINVAL; | |
646 | } | |
647 | ||
648 | return ret; | |
649 | } | |
650 | EXPORT_SYMBOL_GPL(freq_qos_add_notifier); | |
651 | ||
652 | /** | |
653 | * freq_qos_remove_notifier - Remove frequency QoS change notifier. | |
654 | * @qos: List of requests to remove the notifier from. | |
655 | * @type: Request type. | |
656 | * @notifier: Notifier block to remove. | |
657 | */ | |
658 | int freq_qos_remove_notifier(struct freq_constraints *qos, | |
659 | enum freq_qos_req_type type, | |
660 | struct notifier_block *notifier) | |
661 | { | |
662 | int ret; | |
663 | ||
664 | if (IS_ERR_OR_NULL(qos) || !notifier) | |
665 | return -EINVAL; | |
666 | ||
667 | switch (type) { | |
668 | case FREQ_QOS_MIN: | |
669 | ret = blocking_notifier_chain_unregister(qos->min_freq.notifiers, | |
670 | notifier); | |
671 | break; | |
672 | case FREQ_QOS_MAX: | |
673 | ret = blocking_notifier_chain_unregister(qos->max_freq.notifiers, | |
674 | notifier); | |
675 | break; | |
676 | default: | |
677 | WARN_ON(1); | |
678 | ret = -EINVAL; | |
679 | } | |
680 | ||
681 | return ret; | |
682 | } | |
683 | EXPORT_SYMBOL_GPL(freq_qos_remove_notifier); |