| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Devices PM QoS constraints management |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2011 Texas Instruments, Inc. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 8 | | - * published by the Free Software Foundation. |
|---|
| 9 | | - * |
|---|
| 10 | 6 | * |
|---|
| 11 | 7 | * This module exposes the interface to kernel space for specifying |
|---|
| 12 | 8 | * per-device PM QoS dependencies. It provides infrastructure for registration |
|---|
| .. | .. |
|---|
| 22 | 18 | * per-device constraint data struct. |
|---|
| 23 | 19 | * |
|---|
| 24 | 20 | * Note about the per-device constraint data struct allocation: |
|---|
| 25 | | - * . The per-device constraints data struct ptr is tored into the device |
|---|
| 21 | + * . The per-device constraints data struct ptr is stored into the device |
|---|
| 26 | 22 | * dev_pm_info. |
|---|
| 27 | 23 | * . To minimize the data usage by the per-device constraints, the data struct |
|---|
| 28 | 24 | * is only allocated at the first call to dev_pm_qos_add_request. |
|---|
| .. | .. |
|---|
| 94 | 90 | EXPORT_SYMBOL_GPL(dev_pm_qos_flags); |
|---|
| 95 | 91 | |
|---|
| 96 | 92 | /** |
|---|
| 97 | | - * __dev_pm_qos_read_value - Get PM QoS constraint for a given device. |
|---|
| 93 | + * __dev_pm_qos_resume_latency - Get resume latency constraint for a given device. |
|---|
| 98 | 94 | * @dev: Device to get the PM QoS constraint value for. |
|---|
| 99 | 95 | * |
|---|
| 100 | 96 | * This routine must be called with dev->power.lock held. |
|---|
| 101 | 97 | */ |
|---|
| 102 | | -s32 __dev_pm_qos_read_value(struct device *dev) |
|---|
| 98 | +s32 __dev_pm_qos_resume_latency(struct device *dev) |
|---|
| 103 | 99 | { |
|---|
| 104 | 100 | lockdep_assert_held(&dev->power.lock); |
|---|
| 105 | 101 | |
|---|
| 106 | | - return dev_pm_qos_raw_read_value(dev); |
|---|
| 102 | + return dev_pm_qos_raw_resume_latency(dev); |
|---|
| 107 | 103 | } |
|---|
| 108 | 104 | |
|---|
| 109 | 105 | /** |
|---|
| 110 | 106 | * dev_pm_qos_read_value - Get PM QoS constraint for a given device (locked). |
|---|
| 111 | 107 | * @dev: Device to get the PM QoS constraint value for. |
|---|
| 108 | + * @type: QoS request type. |
|---|
| 112 | 109 | */ |
|---|
| 113 | | -s32 dev_pm_qos_read_value(struct device *dev) |
|---|
| 110 | +s32 dev_pm_qos_read_value(struct device *dev, enum dev_pm_qos_req_type type) |
|---|
| 114 | 111 | { |
|---|
| 112 | + struct dev_pm_qos *qos = dev->power.qos; |
|---|
| 115 | 113 | unsigned long flags; |
|---|
| 116 | 114 | s32 ret; |
|---|
| 117 | 115 | |
|---|
| 118 | 116 | spin_lock_irqsave(&dev->power.lock, flags); |
|---|
| 119 | | - ret = __dev_pm_qos_read_value(dev); |
|---|
| 117 | + |
|---|
| 118 | + switch (type) { |
|---|
| 119 | + case DEV_PM_QOS_RESUME_LATENCY: |
|---|
| 120 | + ret = IS_ERR_OR_NULL(qos) ? PM_QOS_RESUME_LATENCY_NO_CONSTRAINT |
|---|
| 121 | + : pm_qos_read_value(&qos->resume_latency); |
|---|
| 122 | + break; |
|---|
| 123 | + case DEV_PM_QOS_MIN_FREQUENCY: |
|---|
| 124 | + ret = IS_ERR_OR_NULL(qos) ? PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE |
|---|
| 125 | + : freq_qos_read_value(&qos->freq, FREQ_QOS_MIN); |
|---|
| 126 | + break; |
|---|
| 127 | + case DEV_PM_QOS_MAX_FREQUENCY: |
|---|
| 128 | + ret = IS_ERR_OR_NULL(qos) ? PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE |
|---|
| 129 | + : freq_qos_read_value(&qos->freq, FREQ_QOS_MAX); |
|---|
| 130 | + break; |
|---|
| 131 | + default: |
|---|
| 132 | + WARN_ON(1); |
|---|
| 133 | + ret = 0; |
|---|
| 134 | + } |
|---|
| 135 | + |
|---|
| 120 | 136 | spin_unlock_irqrestore(&dev->power.lock, flags); |
|---|
| 121 | 137 | |
|---|
| 122 | 138 | return ret; |
|---|
| 123 | 139 | } |
|---|
| 140 | +EXPORT_SYMBOL_GPL(dev_pm_qos_read_value); |
|---|
| 124 | 141 | |
|---|
| 125 | 142 | /** |
|---|
| 126 | 143 | * apply_constraint - Add/modify/remove device PM QoS request. |
|---|
| .. | .. |
|---|
| 143 | 160 | value = 0; |
|---|
| 144 | 161 | |
|---|
| 145 | 162 | ret = pm_qos_update_target(&qos->resume_latency, |
|---|
| 146 | | - &req->data.pnode, action, value, |
|---|
| 147 | | - true); |
|---|
| 163 | + &req->data.pnode, action, value); |
|---|
| 148 | 164 | break; |
|---|
| 149 | 165 | case DEV_PM_QOS_LATENCY_TOLERANCE: |
|---|
| 150 | 166 | ret = pm_qos_update_target(&qos->latency_tolerance, |
|---|
| 151 | | - &req->data.pnode, action, value, |
|---|
| 152 | | - true); |
|---|
| 167 | + &req->data.pnode, action, value); |
|---|
| 153 | 168 | if (ret) { |
|---|
| 154 | 169 | value = pm_qos_read_value(&qos->latency_tolerance); |
|---|
| 155 | 170 | req->dev->power.set_latency_tolerance(req->dev, value); |
|---|
| 156 | 171 | } |
|---|
| 172 | + break; |
|---|
| 173 | + case DEV_PM_QOS_MIN_FREQUENCY: |
|---|
| 174 | + case DEV_PM_QOS_MAX_FREQUENCY: |
|---|
| 175 | + ret = freq_qos_apply(&req->data.freq, action, value); |
|---|
| 157 | 176 | break; |
|---|
| 158 | 177 | case DEV_PM_QOS_FLAGS: |
|---|
| 159 | 178 | ret = pm_qos_update_flags(&qos->flags, &req->data.flr, |
|---|
| .. | .. |
|---|
| 183 | 202 | if (!qos) |
|---|
| 184 | 203 | return -ENOMEM; |
|---|
| 185 | 204 | |
|---|
| 186 | | - n = kzalloc(sizeof(*n), GFP_KERNEL); |
|---|
| 205 | + n = kzalloc(3 * sizeof(*n), GFP_KERNEL); |
|---|
| 187 | 206 | if (!n) { |
|---|
| 188 | 207 | kfree(qos); |
|---|
| 189 | 208 | return -ENOMEM; |
|---|
| 190 | 209 | } |
|---|
| 191 | | - BLOCKING_INIT_NOTIFIER_HEAD(n); |
|---|
| 192 | 210 | |
|---|
| 193 | 211 | c = &qos->resume_latency; |
|---|
| 194 | 212 | plist_head_init(&c->list); |
|---|
| .. | .. |
|---|
| 197 | 215 | c->no_constraint_value = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT; |
|---|
| 198 | 216 | c->type = PM_QOS_MIN; |
|---|
| 199 | 217 | c->notifiers = n; |
|---|
| 218 | + BLOCKING_INIT_NOTIFIER_HEAD(n); |
|---|
| 200 | 219 | |
|---|
| 201 | 220 | c = &qos->latency_tolerance; |
|---|
| 202 | 221 | plist_head_init(&c->list); |
|---|
| .. | .. |
|---|
| 204 | 223 | c->default_value = PM_QOS_LATENCY_TOLERANCE_DEFAULT_VALUE; |
|---|
| 205 | 224 | c->no_constraint_value = PM_QOS_LATENCY_TOLERANCE_NO_CONSTRAINT; |
|---|
| 206 | 225 | c->type = PM_QOS_MIN; |
|---|
| 226 | + |
|---|
| 227 | + freq_constraints_init(&qos->freq); |
|---|
| 207 | 228 | |
|---|
| 208 | 229 | INIT_LIST_HEAD(&qos->flags.list); |
|---|
| 209 | 230 | |
|---|
| .. | .. |
|---|
| 258 | 279 | apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); |
|---|
| 259 | 280 | memset(req, 0, sizeof(*req)); |
|---|
| 260 | 281 | } |
|---|
| 282 | + |
|---|
| 261 | 283 | c = &qos->latency_tolerance; |
|---|
| 262 | 284 | plist_for_each_entry_safe(req, tmp, &c->list, data.pnode) { |
|---|
| 263 | 285 | apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); |
|---|
| 264 | 286 | memset(req, 0, sizeof(*req)); |
|---|
| 265 | 287 | } |
|---|
| 288 | + |
|---|
| 289 | + c = &qos->freq.min_freq; |
|---|
| 290 | + plist_for_each_entry_safe(req, tmp, &c->list, data.freq.pnode) { |
|---|
| 291 | + apply_constraint(req, PM_QOS_REMOVE_REQ, |
|---|
| 292 | + PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE); |
|---|
| 293 | + memset(req, 0, sizeof(*req)); |
|---|
| 294 | + } |
|---|
| 295 | + |
|---|
| 296 | + c = &qos->freq.max_freq; |
|---|
| 297 | + plist_for_each_entry_safe(req, tmp, &c->list, data.freq.pnode) { |
|---|
| 298 | + apply_constraint(req, PM_QOS_REMOVE_REQ, |
|---|
| 299 | + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); |
|---|
| 300 | + memset(req, 0, sizeof(*req)); |
|---|
| 301 | + } |
|---|
| 302 | + |
|---|
| 266 | 303 | f = &qos->flags; |
|---|
| 267 | 304 | list_for_each_entry_safe(req, tmp, &f->list, data.flr.node) { |
|---|
| 268 | 305 | apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); |
|---|
| .. | .. |
|---|
| 308 | 345 | ret = dev_pm_qos_constraints_allocate(dev); |
|---|
| 309 | 346 | |
|---|
| 310 | 347 | trace_dev_pm_qos_add_request(dev_name(dev), type, value); |
|---|
| 311 | | - if (!ret) { |
|---|
| 312 | | - req->dev = dev; |
|---|
| 313 | | - req->type = type; |
|---|
| 348 | + if (ret) |
|---|
| 349 | + return ret; |
|---|
| 350 | + |
|---|
| 351 | + req->dev = dev; |
|---|
| 352 | + req->type = type; |
|---|
| 353 | + if (req->type == DEV_PM_QOS_MIN_FREQUENCY) |
|---|
| 354 | + ret = freq_qos_add_request(&dev->power.qos->freq, |
|---|
| 355 | + &req->data.freq, |
|---|
| 356 | + FREQ_QOS_MIN, value); |
|---|
| 357 | + else if (req->type == DEV_PM_QOS_MAX_FREQUENCY) |
|---|
| 358 | + ret = freq_qos_add_request(&dev->power.qos->freq, |
|---|
| 359 | + &req->data.freq, |
|---|
| 360 | + FREQ_QOS_MAX, value); |
|---|
| 361 | + else |
|---|
| 314 | 362 | ret = apply_constraint(req, PM_QOS_ADD_REQ, value); |
|---|
| 315 | | - } |
|---|
| 363 | + |
|---|
| 316 | 364 | return ret; |
|---|
| 317 | 365 | } |
|---|
| 318 | 366 | |
|---|
| .. | .. |
|---|
| 375 | 423 | case DEV_PM_QOS_RESUME_LATENCY: |
|---|
| 376 | 424 | case DEV_PM_QOS_LATENCY_TOLERANCE: |
|---|
| 377 | 425 | curr_value = req->data.pnode.prio; |
|---|
| 426 | + break; |
|---|
| 427 | + case DEV_PM_QOS_MIN_FREQUENCY: |
|---|
| 428 | + case DEV_PM_QOS_MAX_FREQUENCY: |
|---|
| 429 | + curr_value = req->data.freq.pnode.prio; |
|---|
| 378 | 430 | break; |
|---|
| 379 | 431 | case DEV_PM_QOS_FLAGS: |
|---|
| 380 | 432 | curr_value = req->data.flr.flags; |
|---|
| .. | .. |
|---|
| 473 | 525 | * |
|---|
| 474 | 526 | * @dev: target device for the constraint |
|---|
| 475 | 527 | * @notifier: notifier block managed by caller. |
|---|
| 528 | + * @type: request type. |
|---|
| 476 | 529 | * |
|---|
| 477 | 530 | * Will register the notifier into a notification chain that gets called |
|---|
| 478 | 531 | * upon changes to the target value for the device. |
|---|
| .. | .. |
|---|
| 480 | 533 | * If the device's constraints object doesn't exist when this routine is called, |
|---|
| 481 | 534 | * it will be created (or error code will be returned if that fails). |
|---|
| 482 | 535 | */ |
|---|
| 483 | | -int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) |
|---|
| 536 | +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier, |
|---|
| 537 | + enum dev_pm_qos_req_type type) |
|---|
| 484 | 538 | { |
|---|
| 485 | 539 | int ret = 0; |
|---|
| 486 | 540 | |
|---|
| .. | .. |
|---|
| 491 | 545 | else if (!dev->power.qos) |
|---|
| 492 | 546 | ret = dev_pm_qos_constraints_allocate(dev); |
|---|
| 493 | 547 | |
|---|
| 494 | | - if (!ret) |
|---|
| 548 | + if (ret) |
|---|
| 549 | + goto unlock; |
|---|
| 550 | + |
|---|
| 551 | + switch (type) { |
|---|
| 552 | + case DEV_PM_QOS_RESUME_LATENCY: |
|---|
| 495 | 553 | ret = blocking_notifier_chain_register(dev->power.qos->resume_latency.notifiers, |
|---|
| 496 | 554 | notifier); |
|---|
| 555 | + break; |
|---|
| 556 | + case DEV_PM_QOS_MIN_FREQUENCY: |
|---|
| 557 | + ret = freq_qos_add_notifier(&dev->power.qos->freq, |
|---|
| 558 | + FREQ_QOS_MIN, notifier); |
|---|
| 559 | + break; |
|---|
| 560 | + case DEV_PM_QOS_MAX_FREQUENCY: |
|---|
| 561 | + ret = freq_qos_add_notifier(&dev->power.qos->freq, |
|---|
| 562 | + FREQ_QOS_MAX, notifier); |
|---|
| 563 | + break; |
|---|
| 564 | + default: |
|---|
| 565 | + WARN_ON(1); |
|---|
| 566 | + ret = -EINVAL; |
|---|
| 567 | + } |
|---|
| 497 | 568 | |
|---|
| 569 | +unlock: |
|---|
| 498 | 570 | mutex_unlock(&dev_pm_qos_mtx); |
|---|
| 499 | 571 | return ret; |
|---|
| 500 | 572 | } |
|---|
| .. | .. |
|---|
| 506 | 578 | * |
|---|
| 507 | 579 | * @dev: target device for the constraint |
|---|
| 508 | 580 | * @notifier: notifier block to be removed. |
|---|
| 581 | + * @type: request type. |
|---|
| 509 | 582 | * |
|---|
| 510 | 583 | * Will remove the notifier from the notification chain that gets called |
|---|
| 511 | 584 | * upon changes to the target value. |
|---|
| 512 | 585 | */ |
|---|
| 513 | 586 | int dev_pm_qos_remove_notifier(struct device *dev, |
|---|
| 514 | | - struct notifier_block *notifier) |
|---|
| 587 | + struct notifier_block *notifier, |
|---|
| 588 | + enum dev_pm_qos_req_type type) |
|---|
| 515 | 589 | { |
|---|
| 516 | | - int retval = 0; |
|---|
| 590 | + int ret = 0; |
|---|
| 517 | 591 | |
|---|
| 518 | 592 | mutex_lock(&dev_pm_qos_mtx); |
|---|
| 519 | 593 | |
|---|
| 520 | 594 | /* Silently return if the constraints object is not present. */ |
|---|
| 521 | | - if (!IS_ERR_OR_NULL(dev->power.qos)) |
|---|
| 522 | | - retval = blocking_notifier_chain_unregister(dev->power.qos->resume_latency.notifiers, |
|---|
| 523 | | - notifier); |
|---|
| 595 | + if (IS_ERR_OR_NULL(dev->power.qos)) |
|---|
| 596 | + goto unlock; |
|---|
| 524 | 597 | |
|---|
| 598 | + switch (type) { |
|---|
| 599 | + case DEV_PM_QOS_RESUME_LATENCY: |
|---|
| 600 | + ret = blocking_notifier_chain_unregister(dev->power.qos->resume_latency.notifiers, |
|---|
| 601 | + notifier); |
|---|
| 602 | + break; |
|---|
| 603 | + case DEV_PM_QOS_MIN_FREQUENCY: |
|---|
| 604 | + ret = freq_qos_remove_notifier(&dev->power.qos->freq, |
|---|
| 605 | + FREQ_QOS_MIN, notifier); |
|---|
| 606 | + break; |
|---|
| 607 | + case DEV_PM_QOS_MAX_FREQUENCY: |
|---|
| 608 | + ret = freq_qos_remove_notifier(&dev->power.qos->freq, |
|---|
| 609 | + FREQ_QOS_MAX, notifier); |
|---|
| 610 | + break; |
|---|
| 611 | + default: |
|---|
| 612 | + WARN_ON(1); |
|---|
| 613 | + ret = -EINVAL; |
|---|
| 614 | + } |
|---|
| 615 | + |
|---|
| 616 | +unlock: |
|---|
| 525 | 617 | mutex_unlock(&dev_pm_qos_mtx); |
|---|
| 526 | | - return retval; |
|---|
| 618 | + return ret; |
|---|
| 527 | 619 | } |
|---|
| 528 | 620 | EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); |
|---|
| 529 | 621 | |
|---|
| .. | .. |
|---|
| 583 | 675 | req = dev->power.qos->flags_req; |
|---|
| 584 | 676 | dev->power.qos->flags_req = NULL; |
|---|
| 585 | 677 | break; |
|---|
| 678 | + default: |
|---|
| 679 | + WARN_ON(1); |
|---|
| 680 | + return; |
|---|
| 586 | 681 | } |
|---|
| 587 | 682 | __dev_pm_qos_remove_request(req); |
|---|
| 588 | 683 | kfree(req); |
|---|