From: sashiko-bot@kernel.org
To: "Paul Louvel" <paul.louvel@bootlin.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v2 4/4] regulator: Add TI LM51772 support
Date: Tue, 27 Oct 2026 10:01:49 +0000 [thread overview]
Message-ID: <sashiko-outbox-186345@kernel.org> (raw)
In-Reply-To: <20261027-paul-lm51772-v2-4-b2a767e34722@bootlin.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] regulator: lm51772: use logical AND in lm51772_get_conv_en()
- [High] regulator: lm51772: fix current limit bounds and max_uA check
- [High] regulator: lm51772: fix STAT_OFF and fault checks in IRQ callbacks
- [Medium] regulator: lm51772: fix cached errors and IRQ order in lm51772_setup_irq()
- [Medium] regulator: lm51772: allow OVP limits below 4V in lm51772_set_ov()
- [Medium] regulator: lm51772: OVP updated after voltage ramp-up trips false OVP fault
- [Medium] regulator: lm51772: state mismatch between priv->iset_pin and hardware default
--
commit 85d9f9bf5e7e89e913ab51cc149fc605a17772db
Author: Paul Louvel <paul.louvel@bootlin.com>
regulator: Add TI LM51772 support
This commit adds a regulator driver for the TI LM51772 I2C buck-boost
voltage regulator and its auxiliary 5 V LDO output.
> diff --git a/drivers/regulator/lm51772-regulator.c b/drivers/regulator/lm51772-regulator.c
> new file mode 100644
> index 0000000000000..66f4541b50994
> --- /dev/null
> +++ b/drivers/regulator/lm51772-regulator.c
[ ... ]
> +static int lm51772_set_conv_en(struct regmap *regmap, bool enable)
> +{
> + int ret;
> +
> + ret = regmap_update_bits(regmap, LM51772_REG_USB_PD_CONTROL_0, LM51772_CONV_EN2,
> + FIELD_PREP(LM51772_CONV_EN2, enable));
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(regmap, LM51772_REG_MFR_SPECIFIC_D0, LM51772_CONV_EN,
> + FIELD_PREP(LM51772_CONV_EN, enable));
> +}
> +
> +static int lm51772_get_conv_en(struct regmap *regmap)
> +{
> + unsigned int pd_ctrl, cfg0;
> + int ret;
> +
> + ret = regmap_read(regmap, LM51772_REG_USB_PD_CONTROL_0, &pd_ctrl);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(regmap, LM51772_REG_MFR_SPECIFIC_D0, &cfg0);
> + if (ret)
> + return ret;
> +
> + return FIELD_GET(LM51772_CONV_EN2, pd_ctrl) || FIELD_GET(LM51772_CONV_EN, cfg0);
> +}
[Severity: High]
Should lm51772_get_conv_en() use && instead of || here?
In lm51772_reg_defaults (and at hardware reset), LM51772_REG_USB_PD_CONTROL_0
is 0x01 (so LM51772_CONV_EN2 is 1) while LM51772_REG_MFR_SPECIFIC_D0 is 0x32
(so LM51772_CONV_EN is 0), and lm51772_set_conv_en() must set both bits to
enable the converter.
When a consumer calls regulator_enable() on vout for the first time:
_regulator_enable()
_regulator_is_enabled()
lm51772_regulator_is_enabled()
lm51772_get_conv_en()
Because lm51772_get_conv_en() uses ||, it returns 1 while LM51772_CONV_EN is
still 0, causing _regulator_enable() to treat vout as already enabled and
skip calling _regulator_do_enable().
[ ... ]
> +static int lm51772_set_ov(struct regulator_dev *rdev, int lim_uV)
> +{
> + static const struct linear_range ovp_ranges[] = {
> + LINEAR_RANGE(4000000, 0, 0x18, 500000),
> + LINEAR_RANGE(16000000, 0x18, 0x3f, 1000000),
> + };
> + struct regmap *regmap = rdev_get_regmap(rdev);
> + struct device *dev = rdev_get_dev(rdev);
> + unsigned int sel;
> + bool found;
> + int ret;
> +
> + ret = linear_range_get_selector_high_array(ovp_ranges, ARRAY_SIZE(ovp_ranges), lim_uV, &sel,
> + &found);
> + if (ret || !found)
> + return -EINVAL;
[Severity: Medium]
Does checking !found in lm51772_set_ov() reject valid over-voltage protection
thresholds when the target voltage plus lim_uV is below 4.0 V?
Because vout supports output voltages down to 1.0 V while ovp_ranges starts
at 4.0 V (4000000 uV), calling linear_range_get_selector_high_array() with
lim_uV < 4000000 sets sel = 0 (4.0 V, the lowest available upper-bound OVP
threshold), sets found = false, and returns 0.
Checking !found causes lm51772_set_ov() to return -EINVAL instead of
selecting 4.0 V, and since fixed_ovp is set on vout, _regulator_do_set_voltage()
will also fail with -EINVAL whenever vout + lim_uV is under 4.0 V.
> +
> + dev_dbg(dev, "OVP set to selector 0x%02x\n", sel);
> +
> + return regmap_write(regmap, LM51772_REG_MFR_SPECIFIC_D5, sel);
> +}
> +
> +static int lm51772_regulator_set_over_voltage_protection(struct regulator_dev *rdev, int lim_uV,
> + int severity, bool enable)
> +{
> + int ret;
> +
> + if (!enable)
> + return -EOPNOTSUPP;
> +
> + if (severity != REGULATOR_SEVERITY_PROT)
> + return -EOPNOTSUPP;
> +
> + /* Use default OVP threshold when no limit is requested. */
> + if (!lim_uV)
> + return regmap_write(rdev_get_regmap(rdev), LM51772_REG_MFR_SPECIFIC_D5,
> + LM51772_OVP_DEFAULT);
> +
> + ret = regulator_get_voltage_rdev(rdev);
> + if (ret < 0)
> + return ret;
> +
> + return lm51772_set_ov(rdev, ret + lim_uV);
> +}
[Severity: Medium]
When regulator-ov-protection-microvolt is configured and vout is raised to a
new voltage above the previous absolute OVP threshold, can this trip a false
hardware OVP fault during the voltage ramp?
With fixed_ovp enabled on vout in lm51772_regulator_descs,
_regulator_do_set_voltage() first writes the new voltage selector via
ops->set_voltage_sel() and sleeps for fsleep(delay) while the output slews,
and only afterwards invokes ops->set_over_voltage_protection():
_regulator_do_set_voltage()
ops->set_voltage_sel()
fsleep(delay)
handle_notify_limits(..., ops->set_over_voltage_protection, ...)
lm51772_regulator_set_over_voltage_protection()
When stepping vout up (for example from 5 V to 20 V with a 1 V OVP margin,
where LM51772_REG_MFR_SPECIFIC_D5 is initially set to 6 V), the converter
ramps past 6 V during fsleep(delay) before LM51772_REG_MFR_SPECIFIC_D5 is
updated to 21 V. Should the OVP threshold be raised before increasing the
output voltage?
[ ... ]
> +static int lm51772_regulator_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA)
> +{
> + struct lm51772_priv *priv = rdev_get_drvdata(rdev);
> + struct regmap *regmap = rdev_get_regmap(rdev);
> + unsigned int current_uA;
> + unsigned int sel;
> +
> + if (priv->iset_pin)
> + return -EOPNOTSUPP;
> +
> + if (min_uA > LM51772_ILIM_MAX_UA || max_uA < LM51772_ILIM_MIN_UA)
> + return -EINVAL;
> +
> + if (max_uA > LM51772_ILIM_MAX_UA)
> + max_uA = LM51772_ILIM_MAX_UA;
> +
> + sel = LM51772_UA_TO_ILIM_SEL(priv->sense_res_micro_ohms, max_uA);
> + sel = clamp_val(sel, LM51772_ILIM_THR_MIN, LM51772_ILIM_THR_MAX);
> +
> + /* Check if the selected value is still within the allowed range after computation. */\n> + current_uA = LM51772_ILIM_SEL_TO_UA(priv->sense_res_micro_ohms, sel);
> + if (current_uA < min_uA)
> + return -EINVAL;
[Severity: High]
Can clamping sel up to LM51772_ILIM_THR_MIN cause
lm51772_regulator_set_current_limit() to program a current limit higher than
max_uA when a smaller sense resistor is used?
For example, if shunt-resistor-micro-ohms is 5000 (where LM51772_ILIM_THR_MIN
0x0A corresponds to 1000000 uA) and the caller requests min_uA = 500000 and
max_uA = 600000, LM51772_UA_TO_ILIM_SEL() computes sel = 6, which clamp_val()
raises to 0x0A. Recomputing current_uA gives 1000000 uA, and because only
current_uA < min_uA is checked (without checking current_uA > max_uA), the
driver programs 1.0 A into LM51772_REG_ILIM_THRESHOLD, exceeding max_uA.
Also, LM51772_ILIM_MIN_UA and LM51772_ILIM_MAX_UA hardcode the current range
for a 10 mOhm resistor rather than scaling with priv->sense_res_micro_ohms,
which rejects valid selectors when a different sense resistor is configured.
[ ... ]
> +static int lm51772_irq_map_event(int irq, struct regulator_irq_data *rid, unsigned long *dev_mask)
> +{
> + struct regmap *regmap = rid->data;
> + unsigned int status, reg, errors;
> + bool dev_off;
> + size_t bit;
> + int ret;
> +
> + ret = regmap_read(regmap, LM51772_REG_STATUS_BYTE, &status);
> + if (ret)
> + return REGULATOR_FAILED_RETRY;
> +
> + errors = lm51772_status2err(status);
> + rid->opaque = status;
> +
> + dev_off = (status & LM51772_STAT_OFF) != 0;
> +
> + ret = regmap_read(regmap, LM51772_REG_CLEAR_FAULTS, ®);
> + if (ret)
> + return REGULATOR_FAILED_RETRY;
> +
> + for (size_t i = 0; i < rid->num_states; i++) {
> + rid->states[i].errors = errors & rid->states[i].possible_errs;
> + rid->states[i].notifs = 0;
> +
> + for_each_set_bit(bit, &rid->states[i].errors, BITS_PER_TYPE(rid->states[i].errors))
> + rid->states[i].notifs |= regulator_err2notif(BIT(bit));
> +
> + if (dev_off)
> + rid->states[i].notifs |= REGULATOR_EVENT_DISABLE;
> +
> + if (rid->states[i].errors || dev_off)
> + *dev_mask |= BIT(i);
> + }
> +
> + return 0;
> +}
> +
> +static int lm51772_irq_renable(struct regulator_irq_data *rid)
> +{
> + struct regmap *regmap = rid->data;
> + unsigned int reg;
> + int ret;
> +
> + ret = regmap_read(regmap, LM51772_REG_STATUS_BYTE, ®);
> + if (ret)
> + return REGULATOR_FAILED_RETRY;
> +
> + if (rid->opaque && rid->opaque == reg) {
> + ret = regmap_read(regmap, LM51772_REG_CLEAR_FAULTS, ®);
> + if (ret)
> + return REGULATOR_FAILED_RETRY;
> +
> + return REGULATOR_ERROR_ON;
> + }
> +
> + rid->opaque = 0;
> +
> + return REGULATOR_ERROR_CLEARED;
> +}
[Severity: High]
Are LM51772_STAT_OFF and rid->opaque handled as intended in
lm51772_irq_map_event() and lm51772_irq_renable()?
In lm51772_irq_map_event(), dev_off reflects LM51772_STAT_OFF for the main
buck-boost converter (vout), including when vout is simply disabled in
software. However, the loop applies dev_off to all regulators in
rid->num_states, which sends REGULATOR_EVENT_DISABLE to the auxiliary vcc1
LDO (and to vout on warning interrupts while vout is software-disabled).
In lm51772_irq_renable(), comparing the raw status byte with
rid->opaque == reg has two issues:
1. If an interrupt occurs when status is only LM51772_STAT_OFF (for example,
an INPUT/VIN_UV event while vout is off), reading LM51772_REG_CLEAR_FAULTS
does not clear LM51772_STAT_OFF, so rid->opaque == reg stays true and
lm51772_irq_renable() returns REGULATOR_ERROR_ON in an infinite polling loop.
2. If multiple status bits were set and one bit changes while other fault
bits remain active in reg, rid->opaque == reg evaluates to false, skipping
LM51772_REG_CLEAR_FAULTS and prematurely returning REGULATOR_ERROR_CLEARED.
> +
> +static int lm51772_vout_of_parse(struct device_node *np, const struct regulator_desc *desc,
> + struct regulator_config *cfg)
> +{
> + u32 sense_res_micro_ohms, active_discharge_strength, pcm_window_low;
> + struct lm51772_priv *priv = cfg->driver_data;
> + int ret;
[ ... ]
> + priv->iset_pin = of_property_read_bool(np, "ti,iset-pin");
[Severity: Medium]
What happens to priv->iset_pin if the optional regulators/vout DT node is
absent?
In lm51772_reg_defaults, LM51772_REG_MFR_SPECIFIC_D9 defaults to 0x2C, which
has LM51772_SEL_ISET_PIN (BIT(5)) set to 1 (selecting the external ISET pin).
However, priv->iset_pin is zero-initialized to false in lm51772_probe() and
LM51772_SEL_ISET_PIN is only updated in lm51772_vout_of_parse().
If the vout child node is omitted (or on non-DT instantiation via
lm51772_id), regulator_of_get_init_data() does not invoke
lm51772_vout_of_parse(), leaving LM51772_SEL_ISET_PIN set to 1 in hardware
while priv->iset_pin is false in software. As a result,
lm51772_regulator_get_current_limit() and
lm51772_regulator_set_current_limit() will read and write the inactive
LM51772_REG_ILIM_THRESHOLD register instead of returning -EOPNOTSUPP (or
clearing LM51772_SEL_ISET_PIN).
[ ... ]
> +static int lm51772_setup_irq(struct device *dev, struct regmap *regmap, int irq,
> + struct regulator_dev *rdev[LM51772_N_REGS], bool nint)
> +{
> + int common_errs = REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN |
> + REGULATOR_ERROR_FAIL;
> + int per_rdev_errs[] = {
> + REGULATOR_ERROR_OVER_CURRENT | REGULATOR_ERROR_OVER_VOLTAGE_WARN,
> + 0,
> + };
> + struct regulator_irq_desc irq_desc = {
> + .name = "lm51772-irq",
> + .data = regmap,
> + .map_event = lm51772_irq_map_event,
> + };
> + void *retp;
> +
> + if (!nint) {
> + irq_desc.renable = lm51772_irq_renable;
> + irq_desc.irq_off_ms = 1000;
> + irq_desc.reread_ms = 200;
> + }
> +
> + retp = devm_regulator_irq_helper(dev, &irq_desc, irq, 0, common_errs, per_rdev_errs, rdev,
> + LM51772_N_REGS);
> + if (IS_ERR(retp))
> + return PTR_ERR(retp);
> +
> + return regmap_update_bits(regmap, LM51772_REG_MFR_SPECIFIC_D1, LM51772_EN_NINT,
> + FIELD_PREP(LM51772_EN_NINT, nint));
> +}
[Severity: Medium]
When nint is true, how do cached error flags in rdev->cached_err get cleared?
Because common_errs and per_rdev_errs are passed to
devm_regulator_irq_helper(), init_rdev_errors() sets rdev->use_cached_err =
true, and regulator_notifier_isr() latches any reported errors via
rdev_flag_err(). However, rdev_clear_err() is only called from
regulator_notifier_isr_work(), which is only scheduled when
irq_desc.irq_off_ms is non-zero. With irq_off_ms left at 0 when nint is true,
_regulator_get_error_flags() will permanently OR the latched rdev->cached_err
flags into every subsequent regulator_get_error_flags() call even after the
fault clears.
Also, should LM51772_EN_NINT be configured via regmap_update_bits() before
calling devm_regulator_irq_helper() so the IRQ handler is not active while
the pin is still in its default nFLT mode?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261027-paul-lm51772-v2-0-b2a767e34722@bootlin.com?part=4
next prev parent reply other threads:[~2026-10-27 10:01 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 9:39 [PATCH v2 0/4] Add TI LM51772 buck-boost voltage regulator Paul Louvel
2026-10-27 9:39 ` [PATCH v2 1/4] regulator: dt-bindings: Add TI LM51772 support Paul Louvel
2026-10-29 8:41 ` Krzysztof Kozlowski
2026-10-29 9:05 ` Paul Louvel
2026-10-29 12:42 ` Krzysztof Kozlowski
2026-10-29 14:13 ` Paul Louvel
2026-10-29 14:17 ` Mark Brown
2026-10-27 9:39 ` [PATCH v2 2/4] regulator: Support regulators where the voltage selector spans two registers Paul Louvel
2026-10-27 9:39 ` [PATCH v2 3/4] regulator: core: Call set_over_voltage_protection() if OVP limit is fixed after setting new voltage Paul Louvel
2026-10-27 9:50 ` sashiko-bot
2026-10-27 9:39 ` [PATCH v2 4/4] regulator: Add TI LM51772 support Paul Louvel
2026-10-27 10:01 ` sashiko-bot [this message]
2026-10-27 11:36 ` Paul Louvel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-186345@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=paul.louvel@bootlin.com \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.