[PATCH RFC 1/2] regulator: introduce regulator monitoring constraints

From: Benjamin Bara
Date: Thu Apr 20 2023 - 06:32:41 EST


From: Benjamin Bara <benjamin.bara@xxxxxxxxxxx>

Add constraints for regulator monitoring. These are useful when the
state of the regulator might change during runtime, but the monitor
state (in hardware) is not implicitly changed with the change of the
regulator state or mode (in hardware).

When used, the core takes care of handling the monitor state. This could
ensure that a monitor does not stay active when its regulator is
disabled.

TODO: depending on the initial state of the regulator, the initial state
of the monitor might not be guaranteed to be correct for now.

Signed-off-by: Benjamin Bara <benjamin.bara@xxxxxxxxxxx>
---
drivers/regulator/core.c | 155 ++++++++++++++++++++++++++++++++++----
include/linux/regulator/machine.h | 34 +++++++++
2 files changed, 175 insertions(+), 14 deletions(-)

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 4fcd36055b02..3ec34c05bda2 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -1360,7 +1360,7 @@ static int notif_set_limit(struct regulator_dev *rdev,

static int handle_notify_limits(struct regulator_dev *rdev,
int (*set)(struct regulator_dev *, int, int, bool),
- struct notification_limit *limits)
+ const struct notification_limit *limits)
{
int ret = 0;

@@ -1385,6 +1385,106 @@ static int handle_notify_limits(struct regulator_dev *rdev,

return ret;
}
+
+static const struct notification_limit disable_limits = {
+ .prot = REGULATOR_NOTIF_LIMIT_DISABLE,
+ .err = REGULATOR_NOTIF_LIMIT_DISABLE,
+ .warn = REGULATOR_NOTIF_LIMIT_DISABLE,
+};
+
+static inline bool should_disable_monitor(const struct monitoring_constraints *mon_c, bool pre,
+ bool enable, bool change, unsigned int mode)
+{
+ bool disable = !enable && !change && mode == REGULATOR_MODE_INVALID;
+
+ if (!pre)
+ return false;
+ return (mon_c->mon_disable_during_reg_set && change) ||
+ (mon_c->mon_disable_during_reg_off && disable) ||
+ (mon_c->mon_disable_pre_reg_idle && mode == REGULATOR_MODE_IDLE) ||
+ (mon_c->mon_disable_pre_reg_standby && mode == REGULATOR_MODE_STANDBY);
+}
+
+static inline bool should_enable_monitor(const struct monitoring_constraints *mon_c, bool pre,
+ bool enable, bool change, unsigned int mode)
+{
+ if (pre)
+ return false;
+ return (mon_c->mon_disable_during_reg_set && change) ||
+ (mon_c->mon_disable_during_reg_off && enable) ||
+ (mon_c->mon_enable_post_reg_normal && mode == REGULATOR_MODE_NORMAL) ||
+ (mon_c->mon_enable_post_reg_fast && mode == REGULATOR_MODE_FAST);
+}
+
+static int handle_monitors(struct regulator_dev *rdev, bool pre, bool enable, bool change,
+ unsigned int mode)
+{
+ const struct regulator_ops *ops = rdev->desc->ops;
+ const struct regulation_constraints *reg_c = rdev->constraints;
+
+ /*
+ * ensure that voltage monitoring is explicitly enabled in the device tree and that the
+ * driver has monitoring constraints and protection ops.
+ */
+ bool handle_ov = reg_c->over_voltage_detection && reg_c->ov_constraints &&
+ ops->set_over_voltage_protection;
+ bool handle_uv = reg_c->under_voltage_detection && reg_c->uv_constraints &&
+ ops->set_under_voltage_protection;
+ int ret = 0;
+
+ if (!handle_ov && !handle_uv)
+ return 0;
+
+ dev_dbg(&rdev->dev, "%s: pre: %d, en: %d, ch: %d, mode: %u\n", __func__, pre, enable,
+ change, mode);
+ if ((enable + change + !!mode) > 1) {
+ dev_err(&rdev->dev, "%s: invalid combination!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (handle_ov) {
+ if (should_disable_monitor(reg_c->ov_constraints, pre, enable, change, mode))
+ ret = handle_notify_limits(rdev, ops->set_over_voltage_protection,
+ &disable_limits);
+ else if (should_enable_monitor(reg_c->ov_constraints, pre, enable, change, mode))
+ ret = handle_notify_limits(rdev, ops->set_over_voltage_protection,
+ &reg_c->over_voltage_limits);
+ }
+ if (ret)
+ return ret;
+
+ if (handle_uv) {
+ if (should_disable_monitor(reg_c->uv_constraints, pre, enable, change, mode))
+ ret = handle_notify_limits(rdev, ops->set_under_voltage_protection,
+ &disable_limits);
+ else if (should_enable_monitor(reg_c->uv_constraints, pre, enable, change, mode))
+ ret = handle_notify_limits(rdev, ops->set_under_voltage_protection,
+ &reg_c->under_voltage_limits);
+ }
+
+ return ret;
+}
+
+static inline int handle_monitors_disable(struct regulator_dev *rdev)
+{
+ return handle_monitors(rdev, true, false, false, REGULATOR_MODE_INVALID);
+}
+
+static inline int handle_monitors_enable(struct regulator_dev *rdev)
+{
+ return handle_monitors(rdev, false, true, false, REGULATOR_MODE_INVALID);
+}
+
+static inline int handle_monitors_set(struct regulator_dev *rdev, bool pre)
+{
+ return handle_monitors(rdev, pre, false, true, REGULATOR_MODE_INVALID);
+}
+
+static inline int handle_monitors_mode(struct regulator_dev *rdev, bool pre, unsigned int mode)
+{
+ return handle_monitors(rdev, pre, false, false, mode);
+}
+
/**
* set_machine_constraints - sets regulator constraints
* @rdev: regulator source
@@ -1512,7 +1612,8 @@ static int set_machine_constraints(struct regulator_dev *rdev)
"IC does not support requested over-current limits\n");
}

- if (rdev->constraints->over_voltage_detection)
+ /* only if we have static monitoring. with dynamic, it will be set according to state. */
+ if (rdev->constraints->over_voltage_detection && !rdev->constraints->ov_constraints)
ret = handle_notify_limits(rdev,
ops->set_over_voltage_protection,
&rdev->constraints->over_voltage_limits);
@@ -1526,7 +1627,8 @@ static int set_machine_constraints(struct regulator_dev *rdev)
"IC does not support requested over voltage limits\n");
}

- if (rdev->constraints->under_voltage_detection)
+ /* only if we have static monitoring. with dynamic, it will be set according to state. */
+ if (rdev->constraints->under_voltage_detection && !rdev->constraints->uv_constraints)
ret = handle_notify_limits(rdev,
ops->set_under_voltage_protection,
&rdev->constraints->under_voltage_limits);
@@ -2734,7 +2836,7 @@ static int _regulator_do_enable(struct regulator_dev *rdev)

trace_regulator_enable_complete(rdev_get_name(rdev));

- return 0;
+ return handle_monitors_enable(rdev);
}

/**
@@ -2895,6 +2997,10 @@ static int _regulator_do_disable(struct regulator_dev *rdev)
{
int ret;

+ ret = handle_monitors_disable(rdev);
+ if (ret)
+ return ret;
+
trace_regulator_disable(rdev_get_name(rdev));

if (rdev->ena_pin) {
@@ -3406,7 +3512,7 @@ static int _regulator_call_set_voltage(struct regulator_dev *rdev,
unsigned *selector)
{
struct pre_voltage_change_data data;
- int ret;
+ int ret, err = 0;

data.old_uV = regulator_get_voltage_rdev(rdev);
data.min_uV = min_uV;
@@ -3416,12 +3522,18 @@ static int _regulator_call_set_voltage(struct regulator_dev *rdev,
if (ret & NOTIFY_STOP_MASK)
return -EINVAL;

- ret = rdev->desc->ops->set_voltage(rdev, min_uV, max_uV, selector);
- if (ret >= 0)
+ ret = handle_monitors_set(rdev, true);
+ if (ret)
return ret;

- _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE,
- (void *)data.old_uV);
+ ret = rdev->desc->ops->set_voltage(rdev, min_uV, max_uV, selector);
+ if (ret >= 0)
+ err = handle_monitors_set(rdev, false);
+ else
+ _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE,
+ (void *)data.old_uV);
+ if (err)
+ return err;

return ret;
}
@@ -3430,7 +3542,7 @@ static int _regulator_call_set_voltage_sel(struct regulator_dev *rdev,
int uV, unsigned selector)
{
struct pre_voltage_change_data data;
- int ret;
+ int ret, err = 0;

data.old_uV = regulator_get_voltage_rdev(rdev);
data.min_uV = uV;
@@ -3440,12 +3552,18 @@ static int _regulator_call_set_voltage_sel(struct regulator_dev *rdev,
if (ret & NOTIFY_STOP_MASK)
return -EINVAL;

- ret = rdev->desc->ops->set_voltage_sel(rdev, selector);
- if (ret >= 0)
+ ret = handle_monitors_set(rdev, true);
+ if (ret)
return ret;

- _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE,
- (void *)data.old_uV);
+ ret = rdev->desc->ops->set_voltage_sel(rdev, selector);
+ if (ret >= 0)
+ err = handle_monitors_set(rdev, false);
+ else
+ _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE,
+ (void *)data.old_uV);
+ if (err)
+ return err;

return ret;
}
@@ -4545,7 +4663,16 @@ int regulator_set_mode(struct regulator *regulator, unsigned int mode)
if (ret < 0)
goto out;

+ ret = handle_monitors_mode(rdev, true, mode);
+ if (ret)
+ goto out;
+
ret = rdev->desc->ops->set_mode(rdev, mode);
+ if (ret)
+ goto out;
+
+ ret = handle_monitors_mode(rdev, false, mode);
+
out:
regulator_unlock(rdev);
return ret;
diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h
index 621b7f4a3639..1cfd10ec13a5 100644
--- a/include/linux/regulator/machine.h
+++ b/include/linux/regulator/machine.h
@@ -83,6 +83,36 @@ struct regulator_state {
bool changeable;
};

+/**
+ * struct monitoring_constraints - regulator monitoring constraints.
+ *
+ * This struct describes monitoring specific constraints.
+ *
+ * The constraints should be set by a driver if an enable/disable or regulator MODE switch does not
+ * change the state of the monitor implicitly. When used, the core handles the monitoring of a
+ * dynamic regulator implicitly on state/mode change, based on this configuration. This should
+ * avoid that the monitor reaches an invalid state.
+ *
+ * @mon_disable_during_reg_set: Monitor should be disabled before and enabled after the regulators'
+ * value is changed
+ * @mon_disable_during_reg_off: Monitor should be disabled before a regulator disable and enabled
+ * after a regulator enable
+ *
+ * @mon_disable_pre_reg_idle: Monitor should be disabled before a switch to MODE_IDLE
+ * @mon_disable_pre_reg_standby: Monitor should be disabled before a switch to MODE_STANDBY
+ * @mon_enable_post_reg_normal: Monitor should be enabled after a switch to MODE_NORMAL
+ * @mon_enable_post_reg_fast: Monitor should be enabled after a switch to MODE_FAST
+ */
+struct monitoring_constraints {
+ unsigned mon_disable_during_reg_set:1;
+ unsigned mon_disable_during_reg_off:1;
+
+ unsigned mon_disable_pre_reg_idle:1;
+ unsigned mon_disable_pre_reg_standby:1;
+ unsigned mon_enable_post_reg_normal:1;
+ unsigned mon_enable_post_reg_fast:1;
+};
+
#define REGULATOR_NOTIF_LIMIT_DISABLE -1
#define REGULATOR_NOTIF_LIMIT_ENABLE -2
struct notification_limit {
@@ -207,6 +237,10 @@ struct regulation_constraints {

unsigned int active_discharge;

+ /* monitoring constraints */
+ const struct monitoring_constraints *ov_constraints;
+ const struct monitoring_constraints *uv_constraints;
+
/* constraint flags */
unsigned always_on:1; /* regulator never off when system is on */
unsigned boot_on:1; /* bootloader/firmware enabled regulator */

--
2.34.1