regulator: add property parsing and callbacks to set protection limits

Add DT property parsing code and setting callback for regulator over/under
voltage, over-current and temperature error limits.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
Link: https://lore.kernel.org/r/e7b8007ba9eae7076178bf3363fb942ccb1cc9a5.1622628334.git.matti.vaittinen@fi.rohmeurope.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Matti Vaittinen 2021-06-03 08:42:12 +03:00 committed by Mark Brown
parent 7111c6d1b3
commit 89a6a5e56c
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
7 changed files with 274 additions and 9 deletions

View File

@ -1305,6 +1305,52 @@ static int machine_constraints_current(struct regulator_dev *rdev,
static int _regulator_do_enable(struct regulator_dev *rdev);
static int notif_set_limit(struct regulator_dev *rdev,
int (*set)(struct regulator_dev *, int, int, bool),
int limit, int severity)
{
bool enable;
if (limit == REGULATOR_NOTIF_LIMIT_DISABLE) {
enable = false;
limit = 0;
} else {
enable = true;
}
if (limit == REGULATOR_NOTIF_LIMIT_ENABLE)
limit = 0;
return set(rdev, limit, severity, enable);
}
static int handle_notify_limits(struct regulator_dev *rdev,
int (*set)(struct regulator_dev *, int, int, bool),
struct notification_limit *limits)
{
int ret = 0;
if (!set)
return -EOPNOTSUPP;
if (limits->prot)
ret = notif_set_limit(rdev, set, limits->prot,
REGULATOR_SEVERITY_PROT);
if (ret)
return ret;
if (limits->err)
ret = notif_set_limit(rdev, set, limits->err,
REGULATOR_SEVERITY_ERR);
if (ret)
return ret;
if (limits->warn)
ret = notif_set_limit(rdev, set, limits->warn,
REGULATOR_SEVERITY_WARN);
return ret;
}
/**
* set_machine_constraints - sets regulator constraints
* @rdev: regulator source
@ -1390,9 +1436,27 @@ static int set_machine_constraints(struct regulator_dev *rdev)
}
}
/*
* Existing logic does not warn if over_current_protection is given as
* a constraint but driver does not support that. I think we should
* warn about this type of issues as it is possible someone changes
* PMIC on board to another type - and the another PMIC's driver does
* not support setting protection. Board composer may happily believe
* the DT limits are respected - especially if the new PMIC HW also
* supports protection but the driver does not. I won't change the logic
* without hearing more experienced opinion on this though.
*
* If warning is seen as a good idea then we can merge handling the
* over-curret protection and detection and get rid of this special
* handling.
*/
if (rdev->constraints->over_current_protection
&& ops->set_over_current_protection) {
ret = ops->set_over_current_protection(rdev);
int lim = rdev->constraints->over_curr_limits.prot;
ret = ops->set_over_current_protection(rdev, lim,
REGULATOR_SEVERITY_PROT,
true);
if (ret < 0) {
rdev_err(rdev, "failed to set over current protection: %pe\n",
ERR_PTR(ret));
@ -1400,6 +1464,62 @@ static int set_machine_constraints(struct regulator_dev *rdev)
}
}
if (rdev->constraints->over_current_detection)
ret = handle_notify_limits(rdev,
ops->set_over_current_protection,
&rdev->constraints->over_curr_limits);
if (ret) {
if (ret != -EOPNOTSUPP) {
rdev_err(rdev, "failed to set over current limits: %pe\n",
ERR_PTR(ret));
return ret;
}
rdev_warn(rdev,
"IC does not support requested over-current limits\n");
}
if (rdev->constraints->over_voltage_detection)
ret = handle_notify_limits(rdev,
ops->set_over_voltage_protection,
&rdev->constraints->over_voltage_limits);
if (ret) {
if (ret != -EOPNOTSUPP) {
rdev_err(rdev, "failed to set over voltage limits %pe\n",
ERR_PTR(ret));
return ret;
}
rdev_warn(rdev,
"IC does not support requested over voltage limits\n");
}
if (rdev->constraints->under_voltage_detection)
ret = handle_notify_limits(rdev,
ops->set_under_voltage_protection,
&rdev->constraints->under_voltage_limits);
if (ret) {
if (ret != -EOPNOTSUPP) {
rdev_err(rdev, "failed to set under voltage limits %pe\n",
ERR_PTR(ret));
return ret;
}
rdev_warn(rdev,
"IC does not support requested under voltage limits\n");
}
if (rdev->constraints->over_temp_detection)
ret = handle_notify_limits(rdev,
ops->set_thermal_protection,
&rdev->constraints->temp_limits);
if (ret) {
if (ret != -EOPNOTSUPP) {
rdev_err(rdev, "failed to set temperature limits %pe\n",
ERR_PTR(ret));
return ret;
}
rdev_warn(rdev,
"IC does not support requested temperature limits\n");
}
if (rdev->constraints->active_discharge && ops->set_active_discharge) {
bool ad_state = (rdev->constraints->active_discharge ==
REGULATOR_ACTIVE_DISCHARGE_ENABLE) ? true : false;

View File

@ -21,6 +21,62 @@ static const char *const regulator_states[PM_SUSPEND_MAX + 1] = {
[PM_SUSPEND_MAX] = "regulator-state-disk",
};
static void fill_limit(int *limit, int val)
{
if (val)
if (val == 1)
*limit = REGULATOR_NOTIF_LIMIT_ENABLE;
else
*limit = val;
else
*limit = REGULATOR_NOTIF_LIMIT_DISABLE;
}
static void of_get_regulator_prot_limits(struct device_node *np,
struct regulation_constraints *constraints)
{
u32 pval;
int i;
static const char *const props[] = {
"regulator-oc-%s-microamp",
"regulator-ov-%s-microvolt",
"regulator-temp-%s-kelvin",
"regulator-uv-%s-microvolt",
};
struct notification_limit *limits[] = {
&constraints->over_curr_limits,
&constraints->over_voltage_limits,
&constraints->temp_limits,
&constraints->under_voltage_limits,
};
bool set[4] = {0};
/* Protection limits: */
for (i = 0; i < ARRAY_SIZE(props); i++) {
char prop[255];
bool found;
int j;
static const char *const lvl[] = {
"protection", "error", "warn"
};
int *l[] = {
&limits[i]->prot, &limits[i]->err, &limits[i]->warn,
};
for (j = 0; j < ARRAY_SIZE(lvl); j++) {
snprintf(prop, 255, props[i], lvl[j]);
found = !of_property_read_u32(np, prop, &pval);
if (found)
fill_limit(l[j], pval);
set[i] |= found;
}
}
constraints->over_current_detection = set[0];
constraints->over_voltage_detection = set[1];
constraints->over_temp_detection = set[2];
constraints->under_voltage_detection = set[3];
}
static int of_get_regulation_constraints(struct device *dev,
struct device_node *np,
struct regulator_init_data **init_data,
@ -188,6 +244,8 @@ static int of_get_regulation_constraints(struct device *dev,
constraints->over_current_protection = of_property_read_bool(np,
"regulator-over-current-protection");
of_get_regulator_prot_limits(np, constraints);
for (i = 0; i < ARRAY_SIZE(regulator_states); i++) {
switch (i) {
case PM_SUSPEND_MEM:

View File

@ -307,13 +307,21 @@ end:
return IRQ_HANDLED;
}
static int qcom_labibb_set_ocp(struct regulator_dev *rdev)
static int qcom_labibb_set_ocp(struct regulator_dev *rdev, int lim,
int severity, bool enable)
{
struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
char *ocp_irq_name;
u32 irq_flags = IRQF_ONESHOT;
int irq_trig_low, ret;
/*
* labibb supports only protection - and does not support setting
* limit. Furthermore, we don't support disabling protection.
*/
if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
return -EINVAL;
/* If there is no OCP interrupt, there's nothing to set */
if (vreg->ocp_irq <= 0)
return -EINVAL;

View File

@ -595,11 +595,15 @@ static int spmi_regulator_vs_enable(struct regulator_dev *rdev)
return regulator_enable_regmap(rdev);
}
static int spmi_regulator_vs_ocp(struct regulator_dev *rdev)
static int spmi_regulator_vs_ocp(struct regulator_dev *rdev, int lim_uA,
int severity, bool enable)
{
struct spmi_regulator *vreg = rdev_get_drvdata(rdev);
u8 reg = SPMI_VS_OCP_OVERRIDE;
if (lim_uA || !enable || severity != REGULATOR_SEVERITY_PROT)
return -EINVAL;
return spmi_vreg_write(vreg, SPMI_VS_REG_OCP, &reg, 1);
}

View File

@ -32,7 +32,8 @@ struct stpmic1_regulator_cfg {
static int stpmic1_set_mode(struct regulator_dev *rdev, unsigned int mode);
static unsigned int stpmic1_get_mode(struct regulator_dev *rdev);
static int stpmic1_set_icc(struct regulator_dev *rdev);
static int stpmic1_set_icc(struct regulator_dev *rdev, int lim, int severity,
bool enable);
static unsigned int stpmic1_map_mode(unsigned int mode);
enum {
@ -491,11 +492,26 @@ static int stpmic1_set_mode(struct regulator_dev *rdev, unsigned int mode)
STPMIC1_BUCK_MODE_LP, value);
}
static int stpmic1_set_icc(struct regulator_dev *rdev)
static int stpmic1_set_icc(struct regulator_dev *rdev, int lim, int severity,
bool enable)
{
struct stpmic1_regulator_cfg *cfg = rdev_get_drvdata(rdev);
struct regmap *regmap = rdev_get_regmap(rdev);
/*
* The code seems like one bit in a register controls whether OCP is
* enabled. So we might be able to turn it off here is if that
* was requested. I won't support this because I don't have the HW.
* Feel free to try and implement if you have the HW and need kernel
* to disable this.
*
* Also, I don't know if limit can be configured or if we support
* error/warning instead of protect. So I just keep existing logic
* and assume no.
*/
if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
return -EINVAL;
/* enable switch off in case of over current */
return regmap_update_bits(regmap, cfg->icc_reg, cfg->icc_mask,
cfg->icc_mask);

View File

@ -40,6 +40,15 @@ enum regulator_status {
REGULATOR_STATUS_UNDEFINED,
};
enum regulator_detection_severity {
/* Hardware shut down voltage outputs if condition is detected */
REGULATOR_SEVERITY_PROT,
/* Hardware is probably damaged/inoperable */
REGULATOR_SEVERITY_ERR,
/* Hardware is still recoverable but recovery action must be taken */
REGULATOR_SEVERITY_WARN,
};
/* Initialize struct linear_range for regulators */
#define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) \
{ \
@ -78,8 +87,25 @@ enum regulator_status {
* @get_current_limit: Get the configured limit for a current-limited regulator.
* @set_input_current_limit: Configure an input limit.
*
* @set_over_current_protection: Support capability of automatically shutting
* down when detecting an over current event.
* @set_over_current_protection: Support enabling of and setting limits for over
* current situation detection. Detection can be configured for three
* levels of severity.
* REGULATOR_SEVERITY_PROT should automatically shut down the regulator(s).
* REGULATOR_SEVERITY_ERR should indicate that over-current situation is
* caused by an unrecoverable error but HW does not perform
* automatic shut down.
* REGULATOR_SEVERITY_WARN should indicate situation where hardware is
* still believed to not be damaged but that a board sepcific
* recovery action is needed. If lim_uA is 0 the limit should not
* be changed but the detection should just be enabled/disabled as
* is requested.
* @set_over_voltage_protection: Support enabling of and setting limits for over
* voltage situation detection. Detection can be configured for same
* severities as over current protection.
* @set_under_voltage_protection: Support enabling of and setting limits for
* under situation detection.
* @set_thermal_protection: Support enabling of and setting limits for over
* temperature situation detection.
*
* @set_active_discharge: Set active discharge enable/disable of regulators.
*
@ -143,8 +169,15 @@ struct regulator_ops {
int (*get_current_limit) (struct regulator_dev *);
int (*set_input_current_limit) (struct regulator_dev *, int lim_uA);
int (*set_over_current_protection) (struct regulator_dev *);
int (*set_active_discharge) (struct regulator_dev *, bool enable);
int (*set_over_current_protection)(struct regulator_dev *, int lim_uA,
int severity, bool enable);
int (*set_over_voltage_protection)(struct regulator_dev *, int lim_uV,
int severity, bool enable);
int (*set_under_voltage_protection)(struct regulator_dev *, int lim_uV,
int severity, bool enable);
int (*set_thermal_protection)(struct regulator_dev *, int lim,
int severity, bool enable);
int (*set_active_discharge)(struct regulator_dev *, bool enable);
/* enable/disable regulator */
int (*enable) (struct regulator_dev *);

View File

@ -83,6 +83,14 @@ struct regulator_state {
bool changeable;
};
#define REGULATOR_NOTIF_LIMIT_DISABLE -1
#define REGULATOR_NOTIF_LIMIT_ENABLE -2
struct notification_limit {
int prot;
int err;
int warn;
};
/**
* struct regulation_constraints - regulator operating constraints.
*
@ -100,6 +108,11 @@ struct regulator_state {
* @ilim_uA: Maximum input current.
* @system_load: Load that isn't captured by any consumer requests.
*
* @over_curr_limits: Limits for acting on over current.
* @over_voltage_limits: Limits for acting on over voltage.
* @under_voltage_limits: Limits for acting on under voltage.
* @temp_limits: Limits for acting on over temperature.
* @max_spread: Max possible spread between coupled regulators
* @max_uV_step: Max possible step change in voltage
* @valid_modes_mask: Mask of modes which may be configured by consumers.
@ -116,6 +129,11 @@ struct regulator_state {
* @pull_down: Enable pull down when regulator is disabled.
* @over_current_protection: Auto disable on over current event.
*
* @over_current_detection: Configure over current limits.
* @over_voltage_detection: Configure over voltage limits.
* @under_voltage_detection: Configure under voltage limits.
* @over_temp_detection: Configure over temperature limits.
*
* @input_uV: Input voltage for regulator when supplied by another regulator.
*
* @state_disk: State for regulator when system is suspended in disk mode.
@ -172,6 +190,10 @@ struct regulation_constraints {
struct regulator_state state_disk;
struct regulator_state state_mem;
struct regulator_state state_standby;
struct notification_limit over_curr_limits;
struct notification_limit over_voltage_limits;
struct notification_limit under_voltage_limits;
struct notification_limit temp_limits;
suspend_state_t initial_state; /* suspend state to set at init */
/* mode to set on startup */
@ -193,6 +215,10 @@ struct regulation_constraints {
unsigned soft_start:1; /* ramp voltage slowly */
unsigned pull_down:1; /* pull down resistor when regulator off */
unsigned over_current_protection:1; /* auto disable on over current */
unsigned over_current_detection:1; /* notify on over current */
unsigned over_voltage_detection:1; /* notify on over voltage */
unsigned under_voltage_detection:1; /* notify on under voltage */
unsigned over_temp_detection:1; /* notify on over temperature */
};
/**