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, ®, 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>
