diff options
| -rw-r--r-- | Documentation/power/runtime_pm.txt | 37 | ||||
| -rw-r--r-- | drivers/base/power/power.h | 1 | ||||
| -rw-r--r-- | drivers/base/power/runtime.c | 54 | ||||
| -rw-r--r-- | drivers/base/power/sysfs.c | 56 | ||||
| -rw-r--r-- | include/linux/pm.h | 7 | ||||
| -rw-r--r-- | include/linux/pm_runtime.h | 2 |
6 files changed, 149 insertions, 8 deletions
diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index 55b859b3bc72..9ba49b21ac86 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | Run-time Power Management Framework for I/O Devices | 1 | Run-time Power Management Framework for I/O Devices |
| 2 | 2 | ||
| 3 | (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. | 3 | (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. |
| 4 | (C) 2010 Alan Stern <stern@rowland.harvard.edu> | ||
| 4 | 5 | ||
| 5 | 1. Introduction | 6 | 1. Introduction |
| 6 | 7 | ||
| @@ -230,6 +231,11 @@ defined in include/linux/pm.h: | |||
| 230 | interface; it may only be modified with the help of the pm_runtime_allow() | 231 | interface; it may only be modified with the help of the pm_runtime_allow() |
| 231 | and pm_runtime_forbid() helper functions | 232 | and pm_runtime_forbid() helper functions |
| 232 | 233 | ||
| 234 | unsigned int no_callbacks; | ||
| 235 | - indicates that the device does not use the run-time PM callbacks (see | ||
| 236 | Section 8); it may be modified only by the pm_runtime_no_callbacks() | ||
| 237 | helper function | ||
| 238 | |||
| 233 | All of the above fields are members of the 'power' member of 'struct device'. | 239 | All of the above fields are members of the 'power' member of 'struct device'. |
| 234 | 240 | ||
| 235 | 4. Run-time PM Device Helper Functions | 241 | 4. Run-time PM Device Helper Functions |
| @@ -349,6 +355,11 @@ drivers/base/power/runtime.c and include/linux/pm_runtime.h: | |||
| 349 | counter (used by the /sys/devices/.../power/control interface to | 355 | counter (used by the /sys/devices/.../power/control interface to |
| 350 | effectively prevent the device from being power managed at run time) | 356 | effectively prevent the device from being power managed at run time) |
| 351 | 357 | ||
| 358 | void pm_runtime_no_callbacks(struct device *dev); | ||
| 359 | - set the power.no_callbacks flag for the device and remove the run-time | ||
| 360 | PM attributes from /sys/devices/.../power (or prevent them from being | ||
| 361 | added when the device is registered) | ||
| 362 | |||
| 352 | It is safe to execute the following helper functions from interrupt context: | 363 | It is safe to execute the following helper functions from interrupt context: |
| 353 | 364 | ||
| 354 | pm_request_idle() | 365 | pm_request_idle() |
| @@ -524,3 +535,29 @@ poweroff and run-time suspend callback, and similarly for system resume, thaw, | |||
| 524 | restore, and run-time resume, can achieve this with the help of the | 535 | restore, and run-time resume, can achieve this with the help of the |
| 525 | UNIVERSAL_DEV_PM_OPS macro defined in include/linux/pm.h (possibly setting its | 536 | UNIVERSAL_DEV_PM_OPS macro defined in include/linux/pm.h (possibly setting its |
| 526 | last argument to NULL). | 537 | last argument to NULL). |
| 538 | |||
| 539 | 8. "No-Callback" Devices | ||
| 540 | |||
| 541 | Some "devices" are only logical sub-devices of their parent and cannot be | ||
| 542 | power-managed on their own. (The prototype example is a USB interface. Entire | ||
| 543 | USB devices can go into low-power mode or send wake-up requests, but neither is | ||
| 544 | possible for individual interfaces.) The drivers for these devices have no | ||
| 545 | need of run-time PM callbacks; if the callbacks did exist, ->runtime_suspend() | ||
| 546 | and ->runtime_resume() would always return 0 without doing anything else and | ||
| 547 | ->runtime_idle() would always call pm_runtime_suspend(). | ||
| 548 | |||
| 549 | Subsystems can tell the PM core about these devices by calling | ||
| 550 | pm_runtime_no_callbacks(). This should be done after the device structure is | ||
| 551 | initialized and before it is registered (although after device registration is | ||
| 552 | also okay). The routine will set the device's power.no_callbacks flag and | ||
| 553 | prevent the non-debugging run-time PM sysfs attributes from being created. | ||
| 554 | |||
| 555 | When power.no_callbacks is set, the PM core will not invoke the | ||
| 556 | ->runtime_idle(), ->runtime_suspend(), or ->runtime_resume() callbacks. | ||
| 557 | Instead it will assume that suspends and resumes always succeed and that idle | ||
| 558 | devices should be suspended. | ||
| 559 | |||
| 560 | As a consequence, the PM core will never directly inform the device's subsystem | ||
| 561 | or driver about run-time power changes. Instead, the driver for the device's | ||
| 562 | parent must take responsibility for telling the device's driver when the | ||
| 563 | parent's power state changes. | ||
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 8b2745c69e2a..698dde742587 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h | |||
| @@ -60,6 +60,7 @@ static inline void device_pm_move_last(struct device *dev) {} | |||
| 60 | 60 | ||
| 61 | extern int dpm_sysfs_add(struct device *); | 61 | extern int dpm_sysfs_add(struct device *); |
| 62 | extern void dpm_sysfs_remove(struct device *); | 62 | extern void dpm_sysfs_remove(struct device *); |
| 63 | extern void rpm_sysfs_remove(struct device *); | ||
| 63 | 64 | ||
| 64 | #else /* CONFIG_PM */ | 65 | #else /* CONFIG_PM */ |
| 65 | 66 | ||
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index ed227b7c1bb5..5bd4daa93ef1 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c | |||
| @@ -10,8 +10,10 @@ | |||
| 10 | #include <linux/sched.h> | 10 | #include <linux/sched.h> |
| 11 | #include <linux/pm_runtime.h> | 11 | #include <linux/pm_runtime.h> |
| 12 | #include <linux/jiffies.h> | 12 | #include <linux/jiffies.h> |
| 13 | #include "power.h" | ||
| 13 | 14 | ||
| 14 | static int rpm_resume(struct device *dev, int rpmflags); | 15 | static int rpm_resume(struct device *dev, int rpmflags); |
| 16 | static int rpm_suspend(struct device *dev, int rpmflags); | ||
| 15 | 17 | ||
| 16 | /** | 18 | /** |
| 17 | * update_pm_runtime_accounting - Update the time accounting of power states | 19 | * update_pm_runtime_accounting - Update the time accounting of power states |
| @@ -148,6 +150,12 @@ static int rpm_idle(struct device *dev, int rpmflags) | |||
| 148 | /* Pending requests need to be canceled. */ | 150 | /* Pending requests need to be canceled. */ |
| 149 | dev->power.request = RPM_REQ_NONE; | 151 | dev->power.request = RPM_REQ_NONE; |
| 150 | 152 | ||
| 153 | if (dev->power.no_callbacks) { | ||
| 154 | /* Assume ->runtime_idle() callback would have suspended. */ | ||
| 155 | retval = rpm_suspend(dev, rpmflags); | ||
| 156 | goto out; | ||
| 157 | } | ||
| 158 | |||
| 151 | /* Carry out an asynchronous or a synchronous idle notification. */ | 159 | /* Carry out an asynchronous or a synchronous idle notification. */ |
| 152 | if (rpmflags & RPM_ASYNC) { | 160 | if (rpmflags & RPM_ASYNC) { |
| 153 | dev->power.request = RPM_REQ_IDLE; | 161 | dev->power.request = RPM_REQ_IDLE; |
| @@ -254,6 +262,10 @@ static int rpm_suspend(struct device *dev, int rpmflags) | |||
| 254 | goto repeat; | 262 | goto repeat; |
| 255 | } | 263 | } |
| 256 | 264 | ||
| 265 | dev->power.deferred_resume = false; | ||
| 266 | if (dev->power.no_callbacks) | ||
| 267 | goto no_callback; /* Assume success. */ | ||
| 268 | |||
| 257 | /* Carry out an asynchronous or a synchronous suspend. */ | 269 | /* Carry out an asynchronous or a synchronous suspend. */ |
| 258 | if (rpmflags & RPM_ASYNC) { | 270 | if (rpmflags & RPM_ASYNC) { |
| 259 | dev->power.request = RPM_REQ_SUSPEND; | 271 | dev->power.request = RPM_REQ_SUSPEND; |
| @@ -265,7 +277,6 @@ static int rpm_suspend(struct device *dev, int rpmflags) | |||
| 265 | } | 277 | } |
| 266 | 278 | ||
| 267 | __update_runtime_status(dev, RPM_SUSPENDING); | 279 | __update_runtime_status(dev, RPM_SUSPENDING); |
| 268 | dev->power.deferred_resume = false; | ||
| 269 | 280 | ||
| 270 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) { | 281 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) { |
| 271 | spin_unlock_irq(&dev->power.lock); | 282 | spin_unlock_irq(&dev->power.lock); |
| @@ -305,6 +316,7 @@ static int rpm_suspend(struct device *dev, int rpmflags) | |||
| 305 | pm_runtime_cancel_pending(dev); | 316 | pm_runtime_cancel_pending(dev); |
| 306 | } | 317 | } |
| 307 | } else { | 318 | } else { |
| 319 | no_callback: | ||
| 308 | __update_runtime_status(dev, RPM_SUSPENDED); | 320 | __update_runtime_status(dev, RPM_SUSPENDED); |
| 309 | pm_runtime_deactivate_timer(dev); | 321 | pm_runtime_deactivate_timer(dev); |
| 310 | 322 | ||
| @@ -409,6 +421,23 @@ static int rpm_resume(struct device *dev, int rpmflags) | |||
| 409 | goto repeat; | 421 | goto repeat; |
| 410 | } | 422 | } |
| 411 | 423 | ||
| 424 | /* | ||
| 425 | * See if we can skip waking up the parent. This is safe only if | ||
| 426 | * power.no_callbacks is set, because otherwise we don't know whether | ||
| 427 | * the resume will actually succeed. | ||
| 428 | */ | ||
| 429 | if (dev->power.no_callbacks && !parent && dev->parent) { | ||
| 430 | spin_lock(&dev->parent->power.lock); | ||
| 431 | if (dev->parent->power.disable_depth > 0 | ||
| 432 | || dev->parent->power.ignore_children | ||
| 433 | || dev->parent->power.runtime_status == RPM_ACTIVE) { | ||
| 434 | atomic_inc(&dev->parent->power.child_count); | ||
| 435 | spin_unlock(&dev->parent->power.lock); | ||
| 436 | goto no_callback; /* Assume success. */ | ||
| 437 | } | ||
| 438 | spin_unlock(&dev->parent->power.lock); | ||
| 439 | } | ||
| 440 | |||
| 412 | /* Carry out an asynchronous or a synchronous resume. */ | 441 | /* Carry out an asynchronous or a synchronous resume. */ |
| 413 | if (rpmflags & RPM_ASYNC) { | 442 | if (rpmflags & RPM_ASYNC) { |
| 414 | dev->power.request = RPM_REQ_RESUME; | 443 | dev->power.request = RPM_REQ_RESUME; |
| @@ -449,6 +478,9 @@ static int rpm_resume(struct device *dev, int rpmflags) | |||
| 449 | goto repeat; | 478 | goto repeat; |
| 450 | } | 479 | } |
| 451 | 480 | ||
| 481 | if (dev->power.no_callbacks) | ||
| 482 | goto no_callback; /* Assume success. */ | ||
| 483 | |||
| 452 | __update_runtime_status(dev, RPM_RESUMING); | 484 | __update_runtime_status(dev, RPM_RESUMING); |
| 453 | 485 | ||
| 454 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) { | 486 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) { |
| @@ -482,6 +514,7 @@ static int rpm_resume(struct device *dev, int rpmflags) | |||
| 482 | __update_runtime_status(dev, RPM_SUSPENDED); | 514 | __update_runtime_status(dev, RPM_SUSPENDED); |
| 483 | pm_runtime_cancel_pending(dev); | 515 | pm_runtime_cancel_pending(dev); |
| 484 | } else { | 516 | } else { |
| 517 | no_callback: | ||
| 485 | __update_runtime_status(dev, RPM_ACTIVE); | 518 | __update_runtime_status(dev, RPM_ACTIVE); |
| 486 | if (parent) | 519 | if (parent) |
| 487 | atomic_inc(&parent->power.child_count); | 520 | atomic_inc(&parent->power.child_count); |
| @@ -955,6 +988,25 @@ void pm_runtime_allow(struct device *dev) | |||
| 955 | EXPORT_SYMBOL_GPL(pm_runtime_allow); | 988 | EXPORT_SYMBOL_GPL(pm_runtime_allow); |
| 956 | 989 | ||
| 957 | /** | 990 | /** |
| 991 | * pm_runtime_no_callbacks - Ignore run-time PM callbacks for a device. | ||
| 992 | * @dev: Device to handle. | ||
| 993 | * | ||
| 994 | * Set the power.no_callbacks flag, which tells the PM core that this | ||
| 995 | * device is power-managed through its parent and has no run-time PM | ||
| 996 | * callbacks of its own. The run-time sysfs attributes will be removed. | ||
| 997 | * | ||
| 998 | */ | ||
| 999 | void pm_runtime_no_callbacks(struct device *dev) | ||
| 1000 | { | ||
| 1001 | spin_lock_irq(&dev->power.lock); | ||
| 1002 | dev->power.no_callbacks = 1; | ||
| 1003 | spin_unlock_irq(&dev->power.lock); | ||
| 1004 | if (device_is_registered(dev)) | ||
| 1005 | rpm_sysfs_remove(dev); | ||
| 1006 | } | ||
| 1007 | EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks); | ||
| 1008 | |||
| 1009 | /** | ||
| 958 | * pm_runtime_init - Initialize run-time PM fields in given device object. | 1010 | * pm_runtime_init - Initialize run-time PM fields in given device object. |
| 959 | * @dev: Device object to initialize. | 1011 | * @dev: Device object to initialize. |
| 960 | */ | 1012 | */ |
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 8859780817e1..b5708c47ce2d 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c | |||
| @@ -81,6 +81,9 @@ | |||
| 81 | static const char enabled[] = "enabled"; | 81 | static const char enabled[] = "enabled"; |
| 82 | static const char disabled[] = "disabled"; | 82 | static const char disabled[] = "disabled"; |
| 83 | 83 | ||
| 84 | const char power_group_name[] = "power"; | ||
| 85 | EXPORT_SYMBOL_GPL(power_group_name); | ||
| 86 | |||
| 84 | #ifdef CONFIG_PM_RUNTIME | 87 | #ifdef CONFIG_PM_RUNTIME |
| 85 | static const char ctrl_auto[] = "auto"; | 88 | static const char ctrl_auto[] = "auto"; |
| 86 | static const char ctrl_on[] = "on"; | 89 | static const char ctrl_on[] = "on"; |
| @@ -390,12 +393,6 @@ static DEVICE_ATTR(async, 0644, async_show, async_store); | |||
| 390 | #endif /* CONFIG_PM_ADVANCED_DEBUG */ | 393 | #endif /* CONFIG_PM_ADVANCED_DEBUG */ |
| 391 | 394 | ||
| 392 | static struct attribute * power_attrs[] = { | 395 | static struct attribute * power_attrs[] = { |
| 393 | #ifdef CONFIG_PM_RUNTIME | ||
| 394 | &dev_attr_control.attr, | ||
| 395 | &dev_attr_runtime_status.attr, | ||
| 396 | &dev_attr_runtime_suspended_time.attr, | ||
| 397 | &dev_attr_runtime_active_time.attr, | ||
| 398 | #endif | ||
| 399 | &dev_attr_wakeup.attr, | 396 | &dev_attr_wakeup.attr, |
| 400 | #ifdef CONFIG_PM_SLEEP | 397 | #ifdef CONFIG_PM_SLEEP |
| 401 | &dev_attr_wakeup_count.attr, | 398 | &dev_attr_wakeup_count.attr, |
| @@ -409,6 +406,7 @@ static struct attribute * power_attrs[] = { | |||
| 409 | #ifdef CONFIG_PM_ADVANCED_DEBUG | 406 | #ifdef CONFIG_PM_ADVANCED_DEBUG |
| 410 | &dev_attr_async.attr, | 407 | &dev_attr_async.attr, |
| 411 | #ifdef CONFIG_PM_RUNTIME | 408 | #ifdef CONFIG_PM_RUNTIME |
| 409 | &dev_attr_runtime_status.attr, | ||
| 412 | &dev_attr_runtime_usage.attr, | 410 | &dev_attr_runtime_usage.attr, |
| 413 | &dev_attr_runtime_active_kids.attr, | 411 | &dev_attr_runtime_active_kids.attr, |
| 414 | &dev_attr_runtime_enabled.attr, | 412 | &dev_attr_runtime_enabled.attr, |
| @@ -417,10 +415,52 @@ static struct attribute * power_attrs[] = { | |||
| 417 | NULL, | 415 | NULL, |
| 418 | }; | 416 | }; |
| 419 | static struct attribute_group pm_attr_group = { | 417 | static struct attribute_group pm_attr_group = { |
| 420 | .name = "power", | 418 | .name = power_group_name, |
| 421 | .attrs = power_attrs, | 419 | .attrs = power_attrs, |
| 422 | }; | 420 | }; |
| 423 | 421 | ||
| 422 | #ifdef CONFIG_PM_RUNTIME | ||
| 423 | |||
| 424 | static struct attribute *runtime_attrs[] = { | ||
| 425 | #ifndef CONFIG_PM_ADVANCED_DEBUG | ||
| 426 | &dev_attr_runtime_status.attr, | ||
| 427 | #endif | ||
| 428 | &dev_attr_control.attr, | ||
| 429 | &dev_attr_runtime_suspended_time.attr, | ||
| 430 | &dev_attr_runtime_active_time.attr, | ||
| 431 | NULL, | ||
| 432 | }; | ||
| 433 | static struct attribute_group pm_runtime_attr_group = { | ||
| 434 | .name = power_group_name, | ||
| 435 | .attrs = runtime_attrs, | ||
| 436 | }; | ||
| 437 | |||
| 438 | int dpm_sysfs_add(struct device *dev) | ||
| 439 | { | ||
| 440 | int rc; | ||
| 441 | |||
| 442 | rc = sysfs_create_group(&dev->kobj, &pm_attr_group); | ||
| 443 | if (rc == 0 && !dev->power.no_callbacks) { | ||
| 444 | rc = sysfs_merge_group(&dev->kobj, &pm_runtime_attr_group); | ||
| 445 | if (rc) | ||
| 446 | sysfs_remove_group(&dev->kobj, &pm_attr_group); | ||
| 447 | } | ||
| 448 | return rc; | ||
| 449 | } | ||
| 450 | |||
| 451 | void rpm_sysfs_remove(struct device *dev) | ||
| 452 | { | ||
| 453 | sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); | ||
| 454 | } | ||
| 455 | |||
| 456 | void dpm_sysfs_remove(struct device *dev) | ||
| 457 | { | ||
| 458 | rpm_sysfs_remove(dev); | ||
| 459 | sysfs_remove_group(&dev->kobj, &pm_attr_group); | ||
| 460 | } | ||
| 461 | |||
| 462 | #else /* CONFIG_PM_RUNTIME */ | ||
| 463 | |||
| 424 | int dpm_sysfs_add(struct device * dev) | 464 | int dpm_sysfs_add(struct device * dev) |
| 425 | { | 465 | { |
| 426 | return sysfs_create_group(&dev->kobj, &pm_attr_group); | 466 | return sysfs_create_group(&dev->kobj, &pm_attr_group); |
| @@ -430,3 +470,5 @@ void dpm_sysfs_remove(struct device * dev) | |||
| 430 | { | 470 | { |
| 431 | sysfs_remove_group(&dev->kobj, &pm_attr_group); | 471 | sysfs_remove_group(&dev->kobj, &pm_attr_group); |
| 432 | } | 472 | } |
| 473 | |||
| 474 | #endif | ||
diff --git a/include/linux/pm.h b/include/linux/pm.h index 1abfe84f447d..abd81ffaba3c 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h | |||
| @@ -41,6 +41,12 @@ extern void (*pm_power_off_prepare)(void); | |||
| 41 | 41 | ||
| 42 | struct device; | 42 | struct device; |
| 43 | 43 | ||
| 44 | #ifdef CONFIG_PM | ||
| 45 | extern const char power_group_name[]; /* = "power" */ | ||
| 46 | #else | ||
| 47 | #define power_group_name NULL | ||
| 48 | #endif | ||
| 49 | |||
| 44 | typedef struct pm_message { | 50 | typedef struct pm_message { |
| 45 | int event; | 51 | int event; |
| 46 | } pm_message_t; | 52 | } pm_message_t; |
| @@ -475,6 +481,7 @@ struct dev_pm_info { | |||
| 475 | unsigned int deferred_resume:1; | 481 | unsigned int deferred_resume:1; |
| 476 | unsigned int run_wake:1; | 482 | unsigned int run_wake:1; |
| 477 | unsigned int runtime_auto:1; | 483 | unsigned int runtime_auto:1; |
| 484 | unsigned int no_callbacks:1; | ||
| 478 | enum rpm_request request; | 485 | enum rpm_request request; |
| 479 | enum rpm_status runtime_status; | 486 | enum rpm_status runtime_status; |
| 480 | int runtime_error; | 487 | int runtime_error; |
diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 5869d87fffac..8ca52f7c357e 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h | |||
| @@ -36,6 +36,7 @@ extern void pm_runtime_forbid(struct device *dev); | |||
| 36 | extern int pm_generic_runtime_idle(struct device *dev); | 36 | extern int pm_generic_runtime_idle(struct device *dev); |
| 37 | extern int pm_generic_runtime_suspend(struct device *dev); | 37 | extern int pm_generic_runtime_suspend(struct device *dev); |
| 38 | extern int pm_generic_runtime_resume(struct device *dev); | 38 | extern int pm_generic_runtime_resume(struct device *dev); |
| 39 | extern void pm_runtime_no_callbacks(struct device *dev); | ||
| 39 | 40 | ||
| 40 | static inline bool pm_children_suspended(struct device *dev) | 41 | static inline bool pm_children_suspended(struct device *dev) |
| 41 | { | 42 | { |
| @@ -110,6 +111,7 @@ static inline bool pm_runtime_suspended(struct device *dev) { return false; } | |||
| 110 | static inline int pm_generic_runtime_idle(struct device *dev) { return 0; } | 111 | static inline int pm_generic_runtime_idle(struct device *dev) { return 0; } |
| 111 | static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; } | 112 | static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; } |
| 112 | static inline int pm_generic_runtime_resume(struct device *dev) { return 0; } | 113 | static inline int pm_generic_runtime_resume(struct device *dev) { return 0; } |
| 114 | static inline void pm_runtime_no_callbacks(struct device *dev) {} | ||
| 113 | 115 | ||
| 114 | #endif /* !CONFIG_PM_RUNTIME */ | 116 | #endif /* !CONFIG_PM_RUNTIME */ |
| 115 | 117 | ||
