Skip to content

Commit

Permalink
regulator: add property parsing and callbacks to set protection limits
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Matti Vaittinen authored and Mark Brown committed Jun 21, 2021
1 parent 7111c6d commit 89a6a5e
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 9 deletions.
122 changes: 121 additions & 1 deletion drivers/regulator/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1390,16 +1436,90 @@ 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));
return ret;
}
}

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;
Expand Down
58 changes: 58 additions & 0 deletions drivers/regulator/of_regulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
10 changes: 9 additions & 1 deletion drivers/regulator/qcom-labibb-regulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,21 @@ static irqreturn_t qcom_labibb_ocp_isr(int irq, void *chip)
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;
Expand Down
6 changes: 5 additions & 1 deletion drivers/regulator/qcom_spmi-regulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
20 changes: 18 additions & 2 deletions drivers/regulator/stpmic1_regulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
41 changes: 37 additions & 4 deletions include/linux/regulator/driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
{ \
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 *);
Expand Down
Loading

0 comments on commit 89a6a5e

Please sign in to comment.