From a8ce7bd89689997537dd22dcbced46cf23dc19da Mon Sep 17 00:00:00 2001 From: Vincent Whitchurch Date: Fri, 23 Apr 2021 13:45:24 +0200 Subject: regulator: core: Fix off_on_delay handling 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 Link: https://lore.kernel.org/r/20210423114524.26414-1-vincent.whitchurch@axis.com Signed-off-by: Mark Brown --- drivers/regulator/core.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) (limited to 'drivers/regulator/core.c') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 504898ba265e..f192bf19492e 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -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)); -- cgit 1.4.1