regulator: core: Fix off_on_delay handling
authorVincent Whitchurch <vincent.whitchurch@axis.com>
Fri, 23 Apr 2021 11:45:24 +0000 (13:45 +0200)
committerMark Brown <broonie@kernel.org>
Fri, 23 Apr 2021 12:18:35 +0000 (13:18 +0100)
The jiffies-based off_on_delay implementation has a couple of problems
that cause it to sometimes not actually delay for the required time:

 (1) If, for example, the off_on_delay time is equivalent to one jiffy,
     and the ->last_off_jiffy is set just before a new jiffy starts,
     then _regulator_do_enable() does not wait at all since it checks
     using time_before().

 (2) When jiffies overflows, the value of "remaining" becomes higher
     than "max_delay" and the code simply proceeds without waiting.

Fix these problems by changing it to use ktime_t instead.

[Note that since jiffies doesn't start at zero but at INITIAL_JIFFIES
 ("-5 minutes"), (2) above also led to the code not delaying if
 the first regulator_enable() is called when the ->last_off_jiffy is not
 initialised, such as for regulators with ->constraints->boot_on set.
 It's not clear to me if this was intended or not, but I've preserved
 this behaviour explicitly with the check for a non-zero ->last_off.]

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Link: https://lore.kernel.org/r/20210423114524.26414-1-vincent.whitchurch@axis.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/regulator/core.c
include/linux/regulator/driver.h

index 504898b..f192bf1 100644 (file)
@@ -1443,7 +1443,7 @@ static int set_machine_constraints(struct regulator_dev *rdev)
                if (rdev->constraints->always_on)
                        rdev->use_count++;
        } else if (rdev->desc->off_on_delay) {
-               rdev->last_off_jiffy = jiffies;
+               rdev->last_off = ktime_get();
        }
 
        print_constraints(rdev);
@@ -2488,29 +2488,15 @@ static int _regulator_do_enable(struct regulator_dev *rdev)
 
        trace_regulator_enable(rdev_get_name(rdev));
 
-       if (rdev->desc->off_on_delay) {
+       if (rdev->desc->off_on_delay && rdev->last_off) {
                /* if needed, keep a distance of off_on_delay from last time
                 * this regulator was disabled.
                 */
-               unsigned long start_jiffy = jiffies;
-               unsigned long intended, max_delay, remaining;
-
-               max_delay = usecs_to_jiffies(rdev->desc->off_on_delay);
-               intended = rdev->last_off_jiffy + max_delay;
-
-               if (time_before(start_jiffy, intended)) {
-                       /* calc remaining jiffies to deal with one-time
-                        * timer wrapping.
-                        * in case of multiple timer wrapping, either it can be
-                        * detected by out-of-range remaining, or it cannot be
-                        * detected and we get a penalty of
-                        * _regulator_enable_delay().
-                        */
-                       remaining = intended - start_jiffy;
-                       if (remaining <= max_delay)
-                               _regulator_enable_delay(
-                                               jiffies_to_usecs(remaining));
-               }
+               ktime_t end = ktime_add_us(rdev->last_off, rdev->desc->off_on_delay);
+               s64 remaining = ktime_us_delta(end, ktime_get());
+
+               if (remaining > 0)
+                       _regulator_enable_delay(remaining);
        }
 
        if (rdev->ena_pin) {
@@ -2740,11 +2726,8 @@ static int _regulator_do_disable(struct regulator_dev *rdev)
                        return ret;
        }
 
-       /* cares about last_off_jiffy only if off_on_delay is required by
-        * device.
-        */
        if (rdev->desc->off_on_delay)
-               rdev->last_off_jiffy = jiffies;
+               rdev->last_off = ktime_get();
 
        trace_regulator_disable_complete(rdev_get_name(rdev));
 
index 597ed11..4ea520c 100644 (file)
@@ -476,7 +476,7 @@ struct regulator_dev {
        unsigned int is_switch:1;
 
        /* time when this regulator was disabled last time */
-       unsigned long last_off_jiffy;
+       ktime_t last_off;
 };
 
 struct regulator_dev *