Unverified Commit a8ce7bd8 authored by Vincent Whitchurch's avatar Vincent Whitchurch Committed by Mark Brown
Browse files

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: default avatarVincent Whitchurch <vincent.whitchurch@axis.com>
Link: https://lore.kernel.org/r/20210423114524.26414-1-vincent.whitchurch@axis.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 41a36ffc
Loading
Loading
Loading
Loading
+8 −25
Original line number Diff line number Diff line
@@ -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));

+1 −1
Original line number Diff line number Diff line
@@ -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 *