You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/10/21 11:28:34 UTC
(nuttx) 02/03: drivers/power: add a TI LM51772 buck-boost SMPS driver
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit c22cd2c76f284fb4c52f37e31c0dcee81dbb3931
Author: Daniel P. Carvalho <da...@gmail.com>
AuthorDate: Tue Oct 20 21:06:27 2026 -0300
drivers/power: add a TI LM51772 buck-boost SMPS driver
Add a lower-half driver for the Texas Instruments LM51772 buck-boost
controller, controlled over I2C, on top of the common SMPS interface
(nuttx/power/smps.h).
The converter regulates a constant output voltage (SMPS_OPMODE_CV, the
only mode) with an output current limit. The controller is enabled
through a board callback (the EN/UVLO pin) and configured on the first
PWRIOC_START: the register list of the board is written, the step of the
output voltage is read back from MFR_SPECIFIC_D8, and the output target
and the current limit are applied before the power stage is turned on
(CONV_EN and CONV_EN2). PWRIOC_STOP stops switching and discharges the
output, and the last close disables the controller. Faults come from
STATUS_BYTE.
The board describes the hardware in lm51772_config_s: the I2C bus,
frequency and address, the output sense resistor, the enable callback,
the registers written at start (the register bit fields are in
lm51772.h) and optional absolute maximum ratings: minimum and maximum
output voltage, output current and output power. The limits and the
parameters set through the SMPS interface are refused when they exceed
the ratings, including a v_out * i_out above the power rating.
smps.h now includes nuttx/mutex.h, which it already needs for the close
lock.
Tested on an LM51772 on a custom STM32G4 board, with a 24 V input, no
load and a 400 kHz I2C bus: repeated start and stop at 9, 12, 15, 20 and
24 V without a power cycle, readback of the configured registers and of
STATUS_BYTE, refusal of voltage, current and power above the ratings, and
a start with a controller that does not answer (the error is returned by
PWRIOC_START and the controller is left disabled). With the input
lowered, VIN_UV is reported as SMPS_FAULT_OTHER and the state becomes
SMPS_STATE_FAULT; the output stopped at about 6.7 V and came back at about
8 V (VDET set to 6.9 V falling and 8.0 V rising), and VIN_UV stays latched
until the faults are cleaned. Not tested: load (regulation, current limit,
hiccup protection), an overcurrent fault, I2C failure with the converter
running and the input overvoltage protection.
Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Daniel P. Carvalho <da...@gmail.com>
---
drivers/power/supply/CMakeLists.txt | 4 +
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Make.defs | 4 +
drivers/power/supply/lm51772.c | 857 ++++++++++++++++++++++++++++++++++++
include/nuttx/power/lm51772.h | 314 +++++++++++++
include/nuttx/power/smps.h | 1 +
6 files changed, 1189 insertions(+)
diff --git a/drivers/power/supply/CMakeLists.txt b/drivers/power/supply/CMakeLists.txt
index 35d88d20d8f..3ff2b5f97d2 100644
--- a/drivers/power/supply/CMakeLists.txt
+++ b/drivers/power/supply/CMakeLists.txt
@@ -34,6 +34,10 @@ if(CONFIG_DRIVERS_SMPS)
list(APPEND SRCS smps.c)
endif()
+if(CONFIG_SMPS_LM51772)
+ list(APPEND SRCS lm51772.c)
+endif()
+
if(CONFIG_REGULATOR)
list(APPEND SRCS regulator.c)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index bf29ccaf964..4d2edaf3663 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -47,6 +47,15 @@ config SMPS_HAVE_EFFICIENCY
bool "Have Power Efficiency"
default n
+config SMPS_LM51772
+ bool "TI LM51772 buck-boost controller"
+ default n
+ depends on I2C
+ ---help---
+ Lower half driver for the Texas Instruments LM51772 buck-boost
+ controller, controlled over I2C. It regulates the output voltage
+ (constant voltage mode) with an output current limit.
+
endif
config REGULATOR
diff --git a/drivers/power/supply/Make.defs b/drivers/power/supply/Make.defs
index 743cfc02eb0..e618d5f05c2 100644
--- a/drivers/power/supply/Make.defs
+++ b/drivers/power/supply/Make.defs
@@ -28,6 +28,10 @@ ifeq ($(CONFIG_DRIVERS_SMPS),y)
CSRCS += smps.c
endif
+ifeq ($(CONFIG_SMPS_LM51772),y)
+CSRCS += lm51772.c
+endif
+
ifeq ($(CONFIG_REGULATOR),y)
CSRCS += regulator.c
diff --git a/drivers/power/supply/lm51772.c b/drivers/power/supply/lm51772.c
new file mode 100644
index 00000000000..bc2ab762a23
--- /dev/null
+++ b/drivers/power/supply/lm51772.c
@@ -0,0 +1,857 @@
+/****************************************************************************
+ * drivers/power/supply/lm51772.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/mutex.h>
+#include <nuttx/signal.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/power/lm51772.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Time for VCC2 and the power stage supplies to settle after EN goes high */
+
+#define LM51772_ENABLE_DELAY_US 15000
+
+/* Output voltage target: 12 bits, step set by SEL_FB_DIV20 */
+
+#define LM51772_VOUT_MASK 0x0fff
+#define LM51772_VOUT_STEP_DIV20 20 /* mV */
+#define LM51772_VOUT_STEP_DIV10 10 /* mV */
+
+/* Output current limit register: LM51772_ILIM_THRESHOLD() in the header.
+ * The register saturates at 0x8c (70 mV across the sense resistor): a limit
+ * above it is refused instead of being silently lowered by the chip.
+ */
+
+#define LM51772_ILIM_MAX 0x8c
+
+/* Current limit in amperes of an ILIM_THRESHOLD value */
+
+#define LM51772_ILIM_A(val, mohm) ((float)(val) * 0.5f / (float)(mohm))
+
+/* I2C address when the board does not set one (ADDR pin open) */
+
+#define LM51772_ADDR_DEFAULT 0x6a
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct lm51772_s
+{
+ struct smps_s smps; /* Upper half data, must be first */
+ struct lm51772_config_s config;
+ struct i2c_config_s i2c; /* I2C frequency and address */
+ mutex_t lock; /* Serializes register sequences */
+ float init_ilim; /* A, current limit of the init list */
+ uint8_t vout_step; /* mV, known once configured */
+ bool configured; /* Powered and registers written */
+ bool running; /* Power stage on */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int lm51772_setup(FAR struct smps_dev_s *dev);
+static int lm51772_shutdown(FAR struct smps_dev_s *dev);
+static int lm51772_stop(FAR struct smps_dev_s *dev);
+static int lm51772_start(FAR struct smps_dev_s *dev);
+static int lm51772_params_set(FAR struct smps_dev_s *dev,
+ FAR struct smps_params_s *param);
+static int lm51772_mode_set(FAR struct smps_dev_s *dev, uint8_t mode);
+static int lm51772_limits_set(FAR struct smps_dev_s *dev,
+ FAR struct smps_limits_s *limits);
+static int lm51772_fault_set(FAR struct smps_dev_s *dev, uint8_t fault);
+static int lm51772_state_get(FAR struct smps_dev_s *dev,
+ FAR struct smps_state_s *state);
+static int lm51772_fault_get(FAR struct smps_dev_s *dev,
+ FAR uint8_t *fault);
+static int lm51772_fault_clean(FAR struct smps_dev_s *dev, uint8_t fault);
+static int lm51772_ioctl(FAR struct smps_dev_s *dev, int cmd,
+ unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct smps_ops_s g_lm51772_ops =
+{
+ .setup = lm51772_setup,
+ .shutdown = lm51772_shutdown,
+ .stop = lm51772_stop,
+ .start = lm51772_start,
+ .params_set = lm51772_params_set,
+ .mode_set = lm51772_mode_set,
+ .limits_set = lm51772_limits_set,
+ .fault_set = lm51772_fault_set,
+ .state_get = lm51772_state_get,
+ .fault_get = lm51772_fault_get,
+ .fault_clean = lm51772_fault_clean,
+ .ioctl = lm51772_ioctl
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: lm51772_reg_write / lm51772_reg_read
+ ****************************************************************************/
+
+static int lm51772_reg_write(FAR struct lm51772_s *priv, uint8_t reg,
+ uint8_t val)
+{
+ uint8_t buf[2];
+
+ buf[0] = reg;
+ buf[1] = val;
+
+ return i2c_write(priv->config.i2c, &priv->i2c, buf, 2);
+}
+
+static int lm51772_reg_read(FAR struct lm51772_s *priv, uint8_t reg,
+ FAR uint8_t *val)
+{
+ return i2c_writeread(priv->config.i2c, &priv->i2c, ®, 1, val, 1);
+}
+
+/****************************************************************************
+ * Name: lm51772_reg_modify
+ *
+ * Description:
+ * Read a register, clear the bits in clr, set the bits in set, write it
+ * back.
+ *
+ ****************************************************************************/
+
+static int lm51772_reg_modify(FAR struct lm51772_s *priv, uint8_t reg,
+ uint8_t clr, uint8_t set)
+{
+ uint8_t val;
+ int ret;
+
+ ret = lm51772_reg_read(priv, reg, &val);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ return lm51772_reg_write(priv, reg, (val & ~clr) | set);
+}
+
+/****************************************************************************
+ * Name: lm51772_vout_reg
+ *
+ * Description:
+ * Convert an output voltage in volts to the target register value, with
+ * the step of the chip.
+ *
+ ****************************************************************************/
+
+static uint32_t lm51772_vout_reg(FAR struct lm51772_s *priv, float v_out)
+{
+ uint32_t mv = (uint32_t)(v_out * 1000.0f + 0.5f);
+
+ return mv / priv->vout_step;
+}
+
+/****************************************************************************
+ * Name: lm51772_ilim_reg
+ *
+ * Description:
+ * Convert an output current limit in amperes to the ILIM_THRESHOLD
+ * value, or a negated errno if it does not fit in the register.
+ *
+ ****************************************************************************/
+
+static int lm51772_ilim_reg(FAR struct lm51772_s *priv, float i_out)
+{
+ uint32_t ma = (uint32_t)(i_out * 1000.0f + 0.5f);
+ uint32_t val = LM51772_ILIM_THRESHOLD(ma, priv->config.r_isns_mohm);
+
+ return val > LM51772_ILIM_MAX ? -EINVAL : (int)val;
+}
+
+/****************************************************************************
+ * Name: lm51772_check_ratings
+ *
+ * Description:
+ * Check an output voltage and current limit against the absolute
+ * maximum ratings of the board. An i_out of 0 keeps the current limit of
+ * the init list, or the maximum current if there is none.
+ *
+ ****************************************************************************/
+
+static int lm51772_check_ratings(FAR struct lm51772_s *priv, float v_out,
+ float i_out)
+{
+ FAR const struct lm51772_config_s *cfg = &priv->config;
+ float i_lim = i_out;
+
+ if ((cfg->v_out_min > 0.0f && v_out < cfg->v_out_min) ||
+ (cfg->v_out_max > 0.0f && v_out > cfg->v_out_max) ||
+ (cfg->i_out_max > 0.0f && i_out > cfg->i_out_max))
+ {
+ return -EINVAL;
+ }
+
+ if (cfg->p_out_max > 0.0f)
+ {
+ if (i_lim <= 0.0f)
+ {
+ i_lim = priv->init_ilim > 0.0f ? priv->init_ilim : cfg->i_out_max;
+ }
+
+ if (v_out * i_lim > cfg->p_out_max)
+ {
+ return -EINVAL;
+ }
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_apply_params
+ *
+ * Description:
+ * Write the output voltage target and, if set, the current limit. The
+ * chip must be configured and its voltage step known.
+ *
+ ****************************************************************************/
+
+static int lm51772_apply_params(FAR struct lm51772_s *priv)
+{
+ uint32_t vout = lm51772_vout_reg(priv, priv->smps.param.v_out);
+ int ilim = -1; /* Negative: keep the limit of the chip */
+ int ret;
+
+ if (vout == 0 || vout > LM51772_VOUT_MASK)
+ {
+ return -EINVAL;
+ }
+
+ if (priv->smps.param.i_out > 0.0f)
+ {
+ ilim = lm51772_ilim_reg(priv, priv->smps.param.i_out);
+ if (ilim < 0)
+ {
+ return ilim;
+ }
+ }
+
+ ret = lm51772_reg_write(priv, LM51772_REG_VOUT_TARGET1_LSB,
+ vout & 0xff);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ ret = lm51772_reg_write(priv, LM51772_REG_VOUT_TARGET1_MSB,
+ (vout >> 8) & 0x0f);
+ if (ret < 0 || ilim < 0)
+ {
+ return ret;
+ }
+
+ return lm51772_reg_write(priv, LM51772_REG_ILIM_THRESHOLD, ilim);
+}
+
+/****************************************************************************
+ * Name: lm51772_configure
+ *
+ * Description:
+ * Enable the controller (EN high), check that it answers and write the
+ * register list and the parameters. The power stage does
+ * not switch yet. Called with the lock held.
+ *
+ ****************************************************************************/
+
+static int lm51772_configure(FAR struct lm51772_s *priv)
+{
+ uint8_t val;
+ size_t i;
+ int ret;
+
+ priv->config.enable(priv->config.arg, true);
+ nxsig_usleep(LM51772_ENABLE_DELAY_US);
+
+ ret = lm51772_reg_read(priv, LM51772_REG_STATUS_BYTE, &val);
+ if (ret < 0)
+ {
+ _err("ERROR: not answering at 0x%02x: %d\n", priv->i2c.address, ret);
+ goto errout;
+ }
+
+ for (i = 0; i < priv->config.ninit; i++)
+ {
+ val = priv->config.init[i].val;
+
+ /* The power stage starts in lm51772_start() */
+
+ if (priv->config.init[i].reg == LM51772_REG_MFR_SPECIFIC_D0)
+ {
+ val &= ~LM51772_D0_CONV_EN;
+ }
+
+ ret = lm51772_reg_write(priv, priv->config.init[i].reg, val);
+ if (ret < 0)
+ {
+ goto errout;
+ }
+ }
+
+ ret = lm51772_reg_read(priv, LM51772_REG_MFR_SPECIFIC_D8, &val);
+ if (ret < 0)
+ {
+ goto errout;
+ }
+
+ priv->vout_step = (val & LM51772_D8_SEL_FB_DIV20) != 0 ?
+ LM51772_VOUT_STEP_DIV20 : LM51772_VOUT_STEP_DIV10;
+
+ ret = lm51772_apply_params(priv);
+ if (ret < 0)
+ {
+ goto errout;
+ }
+
+ /* Drop the faults latched while the input was settling (VIN_UV) */
+
+ ret = lm51772_reg_write(priv, LM51772_REG_CLEAR_FAULTS, 0);
+ if (ret < 0)
+ {
+ goto errout;
+ }
+
+ priv->configured = true;
+ return OK;
+
+errout:
+ priv->config.enable(priv->config.arg, false);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: lm51772_stop_locked
+ *
+ * Description:
+ * Stop switching and discharge the output. Both writes are tried even
+ * if the first fails.
+ *
+ ****************************************************************************/
+
+static int lm51772_stop_locked(FAR struct lm51772_s *priv)
+{
+ int ret;
+ int ret2;
+
+ ret = lm51772_reg_modify(priv, LM51772_REG_MFR_SPECIFIC_D0,
+ LM51772_D0_CONV_EN, 0);
+ ret2 = lm51772_reg_modify(priv, LM51772_REG_USB_PD_CONTROL_0,
+ LM51772_PD0_CONV_EN2, LM51772_PD0_FORCE_DISCH);
+ priv->running = false;
+
+ return ret < 0 ? ret : ret2;
+}
+
+/****************************************************************************
+ * Name: lm51772_faults
+ *
+ * Description:
+ * Map STATUS_BYTE to smps_fault_e bits. BUSY and OFF are not faults.
+ *
+ ****************************************************************************/
+
+static uint8_t lm51772_faults(uint8_t status)
+{
+ uint8_t fault = 0;
+
+ if ((status & LM51772_STATUS_VOUT) != 0)
+ {
+ fault |= SMPS_FAULT_OVERVOLTAGE;
+ }
+
+ if ((status & LM51772_STATUS_IOUT) != 0)
+ {
+ fault |= SMPS_FAULT_OVERCURRENT;
+ }
+
+ if ((status & LM51772_STATUS_TEMPERATURE) != 0)
+ {
+ fault |= SMPS_FAULT_OVERTEMP;
+ }
+
+ if ((status & (LM51772_STATUS_INPUT | LM51772_STATUS_CML |
+ LM51772_STATUS_OTHER)) != 0)
+ {
+ fault |= SMPS_FAULT_OTHER;
+ }
+
+ return fault;
+}
+
+/****************************************************************************
+ * Name: lm51772_status_get
+ *
+ * Description:
+ * Read the faults. Without power in the controller there is nothing to
+ * read and there are no faults.
+ *
+ ****************************************************************************/
+
+static int lm51772_status_get(FAR struct lm51772_s *priv,
+ FAR uint8_t *fault)
+{
+ uint8_t status;
+ int ret;
+
+ *fault = 0;
+
+ ret = nxmutex_lock(&priv->lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (priv->configured)
+ {
+ ret = lm51772_reg_read(priv, LM51772_REG_STATUS_BYTE, &status);
+ if (ret >= 0)
+ {
+ *fault = lm51772_faults(status);
+ }
+ }
+
+ nxmutex_unlock(&priv->lock);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: lm51772_setup
+ *
+ * Description:
+ * Called by the upper half on the first open, with interrupts disabled,
+ * so no I2C here: the controller is brought up in lm51772_start().
+ *
+ ****************************************************************************/
+
+static int lm51772_setup(FAR struct smps_dev_s *dev)
+{
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_shutdown
+ *
+ * Description:
+ * Called by the upper half on the last close, with interrupts disabled:
+ * disable the controller (EN low). This stops the converter, so a
+ * program that starts it has to keep the device open while it runs.
+ * The chip may keep its registers (they depend on nRST), so they are
+ * written again at the next start.
+ *
+ ****************************************************************************/
+
+static int lm51772_shutdown(FAR struct smps_dev_s *dev)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+
+ priv->config.enable(priv->config.arg, false);
+ priv->configured = false;
+ priv->running = false;
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_stop
+ ****************************************************************************/
+
+static int lm51772_stop(FAR struct smps_dev_s *dev)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ int ret;
+
+ ret = nxmutex_lock(&priv->lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (priv->configured)
+ {
+ ret = lm51772_stop_locked(priv);
+ }
+
+ nxmutex_unlock(&priv->lock);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: lm51772_start
+ ****************************************************************************/
+
+static int lm51772_start(FAR struct smps_dev_s *dev)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ int ret;
+
+ ret = nxmutex_lock(&priv->lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (priv->running)
+ {
+ goto out;
+ }
+
+ if (priv->smps.param.v_out <= 0.0f)
+ {
+ ret = -EPERM;
+ goto out;
+ }
+
+ if (!priv->configured)
+ {
+ ret = lm51772_configure(priv);
+ if (ret < 0)
+ {
+ goto out;
+ }
+ }
+
+ ret = lm51772_reg_modify(priv, LM51772_REG_MFR_SPECIFIC_D0, 0,
+ LM51772_D0_CONV_EN);
+ if (ret >= 0)
+ {
+ ret = lm51772_reg_modify(priv, LM51772_REG_USB_PD_CONTROL_0,
+ LM51772_PD0_FORCE_DISCH,
+ LM51772_PD0_CONV_EN2);
+ }
+
+ if (ret < 0)
+ {
+ lm51772_stop_locked(priv);
+ goto out;
+ }
+
+ /* Drop the faults latched while the output was still zero */
+
+ lm51772_reg_write(priv, LM51772_REG_CLEAR_FAULTS, 0);
+ priv->running = true;
+
+out:
+ nxmutex_unlock(&priv->lock);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: lm51772_params_set
+ *
+ * Description:
+ * Store the output voltage and the output current limit. They are
+ * written to the chip right away if it is configured, or at the start
+ * otherwise, when the range of the voltage is checked against the step
+ * of the chip. An i_out of 0 keeps the limit of the chip.
+ *
+ ****************************************************************************/
+
+static int lm51772_params_set(FAR struct smps_dev_s *dev,
+ FAR struct smps_params_s *param)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ struct smps_params_s old;
+ int ret = OK;
+
+ if (param->v_out <= 0.0f || param->i_out < 0.0f)
+ {
+ return -EINVAL;
+ }
+
+ ret = lm51772_check_ratings(priv, param->v_out, param->i_out);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ ret = nxmutex_lock(&priv->lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ old = priv->smps.param;
+ priv->smps.param = *param;
+
+ if (param->i_out > 0.0f)
+ {
+ ret = lm51772_ilim_reg(priv, param->i_out);
+ }
+
+ if (ret >= 0 && priv->configured)
+ {
+ ret = lm51772_apply_params(priv);
+ }
+
+ if (ret < 0)
+ {
+ priv->smps.param = old;
+ }
+
+ nxmutex_unlock(&priv->lock);
+ return ret < 0 ? ret : OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_mode_set
+ *
+ * Description:
+ * The controller regulates the output voltage: only constant voltage.
+ *
+ ****************************************************************************/
+
+static int lm51772_mode_set(FAR struct smps_dev_s *dev, uint8_t mode)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+
+ if (mode != SMPS_OPMODE_CV)
+ {
+ return -ENOTSUP;
+ }
+
+ priv->smps.opmode = mode;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_limits_set
+ *
+ * Description:
+ * Keep the limits for the upper half checks (params against limits).
+ * The output voltage limit is required. Limits above the absolute
+ * maximum ratings of the board are refused, and a current or power
+ * limit left at 0 (none for the upper half) becomes the rating.
+ *
+ ****************************************************************************/
+
+static int lm51772_limits_set(FAR struct smps_dev_s *dev,
+ FAR struct smps_limits_s *limits)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ FAR const struct lm51772_config_s *cfg = &priv->config;
+
+ if (limits->v_out <= 0.0f ||
+ (cfg->v_out_max > 0.0f && limits->v_out > cfg->v_out_max) ||
+ (cfg->i_out_max > 0.0f && limits->i_out > cfg->i_out_max) ||
+ (cfg->p_out_max > 0.0f && limits->p_out > cfg->p_out_max))
+ {
+ return -EINVAL;
+ }
+
+ priv->smps.limits = *limits;
+
+ if (limits->i_out <= 0.0f)
+ {
+ priv->smps.limits.i_out = cfg->i_out_max;
+ }
+
+ if (limits->p_out <= 0.0f)
+ {
+ priv->smps.limits.p_out = cfg->p_out_max;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_fault_set
+ ****************************************************************************/
+
+static int lm51772_fault_set(FAR struct smps_dev_s *dev, uint8_t fault)
+{
+ return -ENOTSUP;
+}
+
+/****************************************************************************
+ * Name: lm51772_state_get
+ *
+ * Description:
+ * The state and the faults come from the controller. The feedback is
+ * left empty: the board measures the load current and the battery
+ * voltage with the ADC, and the converter has no other telemetry.
+ *
+ ****************************************************************************/
+
+static int lm51772_state_get(FAR struct smps_dev_s *dev,
+ FAR struct smps_state_s *state)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ int ret;
+
+ memset(state, 0, sizeof(*state));
+
+ ret = lm51772_status_get(priv, &state->fault);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ state->state = state->fault != 0 ? SMPS_STATE_FAULT :
+ priv->running ? SMPS_STATE_RUN : SMPS_STATE_IDLE;
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: lm51772_fault_get
+ ****************************************************************************/
+
+static int lm51772_fault_get(FAR struct smps_dev_s *dev, FAR uint8_t *fault)
+{
+ return lm51772_status_get(dev->lower, fault);
+}
+
+/****************************************************************************
+ * Name: lm51772_fault_clean
+ ****************************************************************************/
+
+static int lm51772_fault_clean(FAR struct smps_dev_s *dev, uint8_t fault)
+{
+ FAR struct lm51772_s *priv = dev->lower;
+ int ret;
+
+ ret = nxmutex_lock(&priv->lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ /* The chip clears all the latched faults at once */
+
+ if (priv->configured)
+ {
+ ret = lm51772_reg_write(priv, LM51772_REG_CLEAR_FAULTS, 0);
+ }
+
+ nxmutex_unlock(&priv->lock);
+ return ret;
+}
+
+/****************************************************************************
+ * Name: lm51772_ioctl
+ ****************************************************************************/
+
+static int lm51772_ioctl(FAR struct smps_dev_s *dev, int cmd,
+ unsigned long arg)
+{
+ return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: lm51772_register
+ *
+ * Description:
+ * Register a LM51772 as an SMPS device. The controller stays powered
+ * down until the first PWRIOC_START.
+ *
+ ****************************************************************************/
+
+int lm51772_register(FAR const char *path,
+ FAR const struct lm51772_config_s *config)
+{
+ FAR struct smps_dev_s *dev;
+ FAR struct lm51772_s *priv;
+ size_t i;
+ int ret;
+
+ DEBUGASSERT(path != NULL && config != NULL);
+ DEBUGASSERT(config->i2c != NULL && config->enable != NULL);
+ DEBUGASSERT(config->r_isns_mohm > 0);
+
+ priv = kmm_zalloc(sizeof(struct lm51772_s));
+ dev = kmm_zalloc(sizeof(struct smps_dev_s));
+ if (priv == NULL || dev == NULL)
+ {
+ ret = -ENOMEM;
+ goto errout;
+ }
+
+ priv->config = *config;
+
+ /* The current limit the chip keeps when the parameters have none */
+
+ for (i = 0; i < config->ninit; i++)
+ {
+ if (config->init[i].reg == LM51772_REG_ILIM_THRESHOLD)
+ {
+ priv->init_ilim = LM51772_ILIM_A(config->init[i].val,
+ config->r_isns_mohm);
+ }
+ }
+
+ priv->i2c.address = config->addr != 0 ? config->addr :
+ LM51772_ADDR_DEFAULT;
+ priv->i2c.addrlen = 7;
+ priv->i2c.frequency = config->frequency;
+ nxmutex_init(&priv->lock);
+
+ dev->ops = &g_lm51772_ops;
+ dev->priv = &priv->smps;
+
+ ret = smps_register(path, dev, priv);
+ if (ret < 0)
+ {
+ nxmutex_destroy(&priv->lock);
+ goto errout;
+ }
+
+ return OK;
+
+errout:
+ kmm_free(priv);
+ kmm_free(dev);
+ return ret;
+}
diff --git a/include/nuttx/power/lm51772.h b/include/nuttx/power/lm51772.h
new file mode 100644
index 00000000000..bdaa77b35d9
--- /dev/null
+++ b/include/nuttx/power/lm51772.h
@@ -0,0 +1,314 @@
+/****************************************************************************
+ * include/nuttx/power/lm51772.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_POWER_LM51772_H
+#define __INCLUDE_NUTTX_POWER_LM51772_H
+
+/* Lower half driver for the Texas Instruments LM51772 buck-boost
+ * controller (SNVSC22D), controlled over I2C, on top of the SMPS upper
+ * half (include/nuttx/power/smps.h).
+ *
+ * The controller regulates the output voltage, so only the constant
+ * voltage mode is supported. In smps_params_s, v_out sets the output
+ * voltage and i_out the output current limit (0 keeps the value of the
+ * chip); p_out is ignored. The feedback in smps_state_s is not filled.
+ *
+ * The driver does not touch the controller until the first
+ * PWRIOC_START: it then powers it (enable callback), writes the optional
+ * register list, the output voltage and the current limit, and starts
+ * the power stage. Closing the device for the last time powers it down.
+ * The SMPS upper half calls setup and shutdown with interrupts disabled,
+ * so they never do I2C.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <nuttx/mutex.h>
+#include <nuttx/power/smps.h>
+
+#if defined(CONFIG_I2C) && defined(CONFIG_SMPS_LM51772)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Registers */
+
+#define LM51772_REG_CLEAR_FAULTS 0x03
+#define LM51772_REG_ILIM_THRESHOLD 0x0a /* 0.5 mV across RISNS per LSB */
+#define LM51772_REG_VOUT_TARGET1_LSB 0x0c
+#define LM51772_REG_VOUT_TARGET1_MSB 0x0d /* Bits 3:0 */
+#define LM51772_REG_STATUS_BYTE 0x78
+#define LM51772_REG_USB_PD_CONTROL_0 0x81
+#define LM51772_REG_MFR_SPECIFIC_D0 0xd0
+#define LM51772_REG_MFR_SPECIFIC_D1 0xd1
+#define LM51772_REG_MFR_SPECIFIC_D2 0xd2
+#define LM51772_REG_MFR_SPECIFIC_D3 0xd3
+#define LM51772_REG_MFR_SPECIFIC_D4 0xd4
+#define LM51772_REG_MFR_SPECIFIC_D5 0xd5
+#define LM51772_REG_MFR_SPECIFIC_D6 0xd6
+#define LM51772_REG_MFR_SPECIFIC_D7 0xd7
+#define LM51772_REG_MFR_SPECIFIC_D8 0xd8
+#define LM51772_REG_MFR_SPECIFIC_D9 0xd9
+#define LM51772_REG_IVP_VOLTAGE 0xda
+
+/* STATUS_BYTE */
+
+#define LM51772_STATUS_OTHER (1 << 0)
+#define LM51772_STATUS_CML (1 << 1)
+#define LM51772_STATUS_TEMPERATURE (1 << 2)
+#define LM51772_STATUS_INPUT (1 << 3) /* VIN_UV */
+#define LM51772_STATUS_IOUT (1 << 4) /* IOUT_OC */
+#define LM51772_STATUS_VOUT (1 << 5) /* VOUT_OV */
+#define LM51772_STATUS_OFF (1 << 6)
+#define LM51772_STATUS_BUSY (1 << 7)
+
+/* USB_PD_CONTROL_0 */
+
+#define LM51772_PD0_CONV_EN2 (1 << 0)
+#define LM51772_PD0_FORCE_DISCH (1 << 1)
+
+/* ILIM_THRESHOLD (0x0a) for a limit of ma milliamperes across a sense
+ * resistor of mohm milliohms: 0.5 mV across the resistor per LSB, rounded
+ */
+
+#define LM51772_ILIM_THRESHOLD(ma, mohm) (((ma) * (mohm) + 250) / 500)
+
+/* MFR_SPECIFIC_D0 (0xd0, reset 0x32): converter options */
+
+#define LM51772_D0_CONV_EN (1 << 0) /* Enable the power stage */
+#define LM51772_D0_USLEEP_EN (1 << 1) /* Micro sleep, PSM only */
+#define LM51772_D0_DRSS_EN (1 << 2) /* Dual random spread spectrum */
+#define LM51772_D0_HICCUP_EN (1 << 3) /* Hiccup short circuit
+ * protection */
+#define LM51772_D0_IMON_LIMITER_EN (1 << 4) /* IMON in limiter config */
+#define LM51772_D0_EN_VCC1 (1 << 5) /* VCC1 auxiliary LDO */
+#define LM51772_D0_EN_NEG_CL_LIMIT (1 << 6) /* Negative current limit */
+
+/* MFR_SPECIFIC_D1 (0xd1, reset 0x09): thermal warning and start-up */
+
+#define LM51772_D1_EN_BB_2P_PSM (1 << 0) /* 2 phase BB switching, PSM */
+#define LM51772_D1_EN_BB_2P_FPWM (1 << 1) /* 2 phase BB switching, fPWM */
+#define LM51772_D1_FORCE_BIASPIN (1 << 2) /* Supply VCC2 from BIAS */
+#define LM51772_D1_EN_DTRK_STARTOVER (1 << 3) /* Start without waiting for
+ * the PWM of the DTRK pin */
+#define LM51772_D1_EN_NINT (1 << 4) /* nFLT pin as interrupt */
+#define LM51772_D1_THW_140C (0 << 5) /* Thermal warning threshold */
+#define LM51772_D1_THW_125C (1 << 5)
+#define LM51772_D1_THW_110C (2 << 5)
+#define LM51772_D1_THW_95C (3 << 5)
+#define LM51772_D1_EN_THER_WARN (1 << 7) /* Thermal warning */
+
+/* MFR_SPECIFIC_D2 (0xd2, reset 0x40): voltage scaling and discharge */
+
+#define LM51772_D2_DISCHARGE_CONFIG1 (1 << 0) /* Discharge until VTH_DISCH */
+#define LM51772_D2_DISCHARGE_CONFIG0 (1 << 1) /* Discharge with CONV_EN */
+#define LM51772_D2_DISCH_25MA (0 << 2) /* Discharge current */
+#define LM51772_D2_DISCH_50MA (1 << 2)
+#define LM51772_D2_DISCH_75MA (2 << 2)
+#define LM51772_D2_DVS_40MV_US (0 << 4) /* Vout slew rate, DVS */
+#define LM51772_D2_DVS_20MV_US (1 << 4)
+#define LM51772_D2_DVS_1MV_US (2 << 4)
+#define LM51772_D2_DVS_0P5MV_US (3 << 4)
+#define LM51772_D2_EN_ACTIVE_DVS (1 << 6) /* Active discharge on a DVS
+ * down ramp */
+
+/* MFR_SPECIFIC_D3 (0xd3, reset 0x20): input protection and VDET */
+
+/* VDET falling threshold in mV, 2700 to 8900 in steps of 200 */
+
+#define LM51772_D3_VDET_FALL_MV(mv) (((mv) - 2700) / 200)
+
+#define LM51772_D3_VDET_EN (1 << 5) /* Internal input UVLO */
+#define LM51772_D3_SEL_IVR (1 << 6) /* Regulate VIN, not protect */
+#define LM51772_D3_EN_IVP (1 << 7) /* Input voltage protection */
+
+/* IVP_VOLTAGE (0xda, reset 0xff): input overvoltage protection threshold.
+ * With EN_IVP (D3) the protection stops the reverse current, from the output
+ * to the input, when VIN reaches it. The value is in mV, 4750 to 36625 in
+ * steps of 125 (the reset value is 36.6 V).
+ */
+
+#define LM51772_IVP_VOLTAGE_MV(mv) (((mv) - 4750) / 125)
+
+/* MFR_SPECIFIC_D4 (0xd4, reset 0x03): VDET rising threshold */
+
+/* VDET rising threshold in mV, 2800 to 9000 in steps of 200 */
+
+#define LM51772_D4_VDET_RISE_MV(mv) (((mv) - 2800) / 200)
+
+/* MFR_SPECIFIC_D6 (0xd6, reset 0x15): dead time and minimum switching time */
+
+#define LM51772_D6_BB_MIN_TIME_0P75 (0 << 0) /* Scale of BB min Ton/Toff */
+#define LM51772_D6_BB_MIN_TIME_1P00 (1 << 0)
+#define LM51772_D6_BB_MIN_TIME_1P25 (2 << 0)
+#define LM51772_D6_BB_MIN_TIME_1P50 (3 << 0)
+#define LM51772_D6_MIN_DEADTIME_10NS (0 << 2) /* Dead time at 2 MHz */
+#define LM51772_D6_MIN_DEADTIME_20NS (1 << 2)
+#define LM51772_D6_MIN_DEADTIME_40NS (2 << 2)
+#define LM51772_D6_MIN_DEADTIME_60NS (3 << 2)
+#define LM51772_D6_SEL_SCALE_DT (1 << 4) /* Scale dead time with fsw */
+#define LM51772_D6_EN_CONST_TDEAD (1 << 5) /* Constant dead time */
+#define LM51772_D6_SYNC_IN_RISE (0 << 6) /* SYNC pin function */
+#define LM51772_D6_SYNC_IN_FALL (1 << 6)
+#define LM51772_D6_SYNC_OUT_RISE (2 << 6)
+#define LM51772_D6_SYNC_OUT_FALL (3 << 6)
+
+/* MFR_SPECIFIC_D7 (0xd7, reset 0x28): PSM derating and slope compensation */
+
+/* Slope compensation current, ratio of the RT current: 0x8 is 1.5 */
+
+#define LM51772_D7_SLOPE_COMP_1P5 0x08
+
+#define LM51772_D7_DERATE_OFF (0 << 4) /* Inductor derating, PSM */
+#define LM51772_D7_DERATE_20 (1 << 4)
+#define LM51772_D7_DERATE_30 (2 << 4)
+#define LM51772_D7_DERATE_40 (3 << 4)
+
+/* MFR_SPECIFIC_D8 (0xd8, reset 0x84): feedback divider and DRV1 */
+
+#define LM51772_D8_SEL_DRV1_SUP_OD (0 << 0) /* DRV1 driver configuration */
+#define LM51772_D8_SEL_DRV1_SUP_VOUT (1 << 0)
+#define LM51772_D8_SEL_DRV1_SUP_VBIAS (2 << 0)
+#define LM51772_D8_SEL_DRV1_SUP_VCC2 (3 << 0)
+#define LM51772_D8_DRV1_SEQ_OFF (0 << 2) /* Pull low/CP while converter
+ * is off */
+#define LM51772_D8_DRV1_SEQ_ON (1 << 2) /* Pull low/CP while converter
+ * is on */
+#define LM51772_D8_DRV1_SEQ_ACTIVE (2 << 2) /* Forced active */
+#define LM51772_D8_DRV1_SEQ_FORCE_OFF (3 << 2) /* Forced off */
+#define LM51772_D8_CDC_GAIN_0P25V (0 << 4) /* Cable drop compensation */
+#define LM51772_D8_CDC_GAIN_0P5V (1 << 4)
+#define LM51772_D8_CDC_GAIN_1V (2 << 4)
+#define LM51772_D8_CDC_GAIN_2V (3 << 4)
+#define LM51772_D8_EN_CDC (1 << 6) /* Cable drop compensation */
+#define LM51772_D8_SEL_FB_DIV20 (1 << 7) /* 20 mV step, 3.3 to 48 V */
+
+/* MFR_SPECIFIC_D9 (0xd9, reset 0x2c): ILIM source and PCM window */
+
+/* Lower PCM window in percent of VOUT, 0 to 77 in steps of 2.5 (use
+ * multiples of 5)
+ */
+
+#define LM51772_D9_PCM_WINDOW_PCT(p) ((p) * 2 / 5)
+
+/* ISET pin in the I2C config, ILIM DAC disabled */
+
+#define LM51772_D9_SEL_ISET_PIN (1 << 5)
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* A register and the value written to it at configuration time */
+
+struct lm51772_reg_s
+{
+ uint8_t reg;
+ uint8_t val;
+};
+
+/* Board configuration */
+
+struct lm51772_config_s
+{
+ FAR struct i2c_master_s *i2c; /* I2C bus */
+ uint32_t frequency; /* I2C frequency */
+ uint8_t addr; /* 7-bit address, 0x6a if 0 */
+ uint16_t r_isns_mohm; /* Output sense resistor, mohm */
+
+ /* Absolute maximum ratings of the board, 0 for none. The limits and
+ * the parameters set by the application are checked against them and
+ * refused with -EINVAL if they exceed them: the application adjusts the
+ * output inside this envelope. With p_out_max the power v_out * i_out
+ * is checked too, i_out being the current limit of the parameters, or
+ * the one of the ILIM_THRESHOLD entry of the init list if the parameters
+ * keep it.
+ */
+
+ float v_out_min; /* Minimum output voltage, V */
+ float v_out_max; /* Maximum output voltage, V */
+ float i_out_max; /* Maximum output current, A */
+ float p_out_max; /* Maximum output power, W */
+
+ /* Drives the enable of the controller (EN/UVLO pin). Called with
+ * interrupts disabled when switching off, so it must not sleep.
+ */
+
+ CODE void (*enable)(FAR void *arg, bool enable);
+ FAR void *arg;
+
+ /* Registers written, in order, after the controller is powered and
+ * before the output voltage. Optional, the chip is loaded from its
+ * nonvolatile memory otherwise. MFR_SPECIFIC_D0 is written with
+ * CONV_EN cleared, the power stage only starts with PWRIOC_START.
+ * Include the step of the output voltage (MFR_SPECIFIC_D8) here if it
+ * is not the default one: the driver reads it back.
+ */
+
+ FAR const struct lm51772_reg_s *init;
+ size_t ninit;
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/****************************************************************************
+ * Name: lm51772_register
+ *
+ * Description:
+ * Register a LM51772 as an SMPS device. The controller stays powered
+ * down until the first PWRIOC_START.
+ *
+ * Input Parameters:
+ * path - Device path, for example "/dev/smps0".
+ * config - Board configuration. The structure is copied; the init list
+ * is not and must stay valid.
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int lm51772_register(FAR const char *path,
+ FAR const struct lm51772_config_s *config);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONFIG_I2C && CONFIG_SMPS_LM51772 */
+#endif /* __INCLUDE_NUTTX_POWER_LM51772_H */
diff --git a/include/nuttx/power/smps.h b/include/nuttx/power/smps.h
index 54969cccc6e..7a2c2e07807 100644
--- a/include/nuttx/power/smps.h
+++ b/include/nuttx/power/smps.h
@@ -46,6 +46,7 @@
#include <nuttx/config.h>
#include <nuttx/compiler.h>
+#include <nuttx/mutex.h>
#include <nuttx/power/power_ioctl.h>
#include <nuttx/semaphore.h>