lkml.org 
[lkml]   [2021]   [Apr]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[PATCH v4 4/7] 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>
    ---
    No changes since RFC-v3

    drivers/regulator/core.c | 122 +++++++++++++++++++++-
    drivers/regulator/of_regulator.c | 58 ++++++++++
    drivers/regulator/qcom-labibb-regulator.c | 10 +-
    drivers/regulator/qcom_spmi-regulator.c | 6 +-
    drivers/regulator/stpmic1_regulator.c | 20 +++-
    include/linux/regulator/driver.h | 41 +++++++-
    include/linux/regulator/machine.h | 26 +++++
    7 files changed, 274 insertions(+), 9 deletions(-)

    diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
    index fabc83288e1b..5a3b932dc8a5 100644
    --- a/drivers/regulator/core.c
    +++ b/drivers/regulator/core.c
    @@ -1312,6 +1312,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
    @@ -1397,9 +1443,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));
    @@ -1407,6 +1471,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;
    diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c
    index 564f928eb1db..b70fdc5c7000 100644
    --- a/drivers/regulator/of_regulator.c
    +++ b/drivers/regulator/of_regulator.c
    @@ -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:
    diff --git a/drivers/regulator/qcom-labibb-regulator.c b/drivers/regulator/qcom-labibb-regulator.c
    index de25e3279b4b..b3da0dc58782 100644
    --- a/drivers/regulator/qcom-labibb-regulator.c
    +++ b/drivers/regulator/qcom-labibb-regulator.c
    @@ -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;
    diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c
    index e62e1d72d943..45b29c8856bd 100644
    --- a/drivers/regulator/qcom_spmi-regulator.c
    +++ b/drivers/regulator/qcom_spmi-regulator.c
    @@ -594,11 +594,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);
    }

    diff --git a/drivers/regulator/stpmic1_regulator.c b/drivers/regulator/stpmic1_regulator.c
    index cf10fdb72e32..2d7597c76e4a 100644
    --- a/drivers/regulator/stpmic1_regulator.c
    +++ b/drivers/regulator/stpmic1_regulator.c
    @@ -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);
    diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h
    index 03a8eee9fca9..ded1933ae17a 100644
    --- a/include/linux/regulator/driver.h
    +++ b/include/linux/regulator/driver.h
    @@ -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 *);
    diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h
    index 8a56f033b6cd..68b4a514a410 100644
    --- a/include/linux/regulator/machine.h
    +++ b/include/linux/regulator/machine.h
    @@ -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 */
    };

    /**
    --
    2.25.4

    --
    Matti Vaittinen, Linux device drivers
    ROHM Semiconductors, Finland SWDC
    Kiviharjunlenkki 1E
    90220 OULU
    FINLAND

    ~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
    Simon says - in Latin please.
    ~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
    Thanks to Simon Glass for the translation =]

    \
     
     \ /
      Last update: 2021-04-06 09:14    [W:2.551 / U:0.228 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site