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 <[email protected]>
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 <[email protected]>
---
 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, &reg, 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>
 

Reply via email to