driver: regulator: Add support for AXP192 power management IC

AXP192 is a small and simple power management IC featuring different
LDOs, DCDCs, AINs and also GPIOs. It also offers functionaltiy for
battery management.
This change includes the basic regulator driver functionaltiy for
LDO2-3 and DCDC1-3 as well as the mfd driver layer. Further drivers
for GPIO and ADC will follow.
Drivers have been developed and tested on M5StackCore2, an ESP32-based
board. Support for M5StackCore2 is still in progress.

Signed-off-by: Martin Kiepfer <mrmarteng@teleschirm.org>
This commit is contained in:
Martin Kiepfer 2023-06-01 23:57:45 +02:00 committed by Fabio Baltieri
parent 820bc9267e
commit 09da4cf89d
12 changed files with 578 additions and 0 deletions

View file

@ -5,3 +5,4 @@ zephyr_library()
zephyr_library_sources_ifdef(CONFIG_MFD_NPM1300 mfd_npm1300.c)
zephyr_library_sources_ifdef(CONFIG_MFD_NPM6001 mfd_npm6001.c)
zephyr_library_sources_ifdef(CONFIG_MFD_AXP192 mfd_axp192.c)

View file

@ -18,6 +18,7 @@ config MFD_INIT_PRIORITY
help
Multi-function devices initialization priority.
source "drivers/mfd/Kconfig.axp192"
source "drivers/mfd/Kconfig.npm1300"
source "drivers/mfd/Kconfig.npm6001"

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023 Martin Kiepfer <mrmarteng@teleschirm.org>
# SPDX -License-Identifier: Apache-2.0
config MFD_AXP192
bool "AXP192 PMIC multi-function device driver"
default y
depends on DT_HAS_X_POWERS_AXP192_ENABLED
select I2C
help
Enable the X-Powers AXP192 PMIC multi-function device driver

61
drivers/mfd/mfd_axp192.c Normal file
View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 Martin Kiepfer <mrmarteng@teleschirm.org>
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT x_powers_axp192
#include <errno.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mfd_axp192, CONFIG_MFD_LOG_LEVEL);
/* Chip ID value */
#define AXP192_CHIP_ID 0x03U
/* Registers definitions */
#define AXP192_REG_CHIP_ID 0x03U
struct mfd_axp192_config {
struct i2c_dt_spec i2c;
};
static int mfd_axp192_init(const struct device *dev)
{
const struct mfd_axp192_config *config = dev->config;
uint8_t chip_id;
int ret;
LOG_DBG("Initializing instance");
if (!i2c_is_ready_dt(&config->i2c)) {
LOG_ERR("I2C bus not ready");
return -ENODEV;
}
/* Check if axp192 chip is available */
ret = i2c_reg_read_byte_dt(&config->i2c, AXP192_REG_CHIP_ID, &chip_id);
if (ret < 0) {
return ret;
}
if (chip_id != AXP192_CHIP_ID) {
LOG_ERR("Invalid Chip detected (%d)", chip_id);
return -EINVAL;
}
return 0;
}
#define MFD_AXP192_DEFINE(inst) \
static const struct mfd_axp192_config config##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, mfd_axp192_init, NULL, NULL, &config##inst, POST_KERNEL, \
CONFIG_MFD_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(MFD_AXP192_DEFINE)

View file

@ -4,6 +4,7 @@
zephyr_library()
zephyr_library_sources(regulator_common.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_AXP192 regulator_axp192.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_ADP5360 regulator_adp5360.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_FAKE regulator_fake.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c)

View file

@ -27,6 +27,7 @@ module = REGULATOR
module-str = regulator
source "subsys/logging/Kconfig.template.log_config"
source "drivers/regulator/Kconfig.axp192"
source "drivers/regulator/Kconfig.adp5360"
source "drivers/regulator/Kconfig.fake"
source "drivers/regulator/Kconfig.fixed"

View file

@ -0,0 +1,23 @@
# Copyright (c) 2023 Martin Kiepfer
# SPDX -License-Identifier: Apache-2.0
config REGULATOR_AXP192
bool "X-Power AXP192 PMIC regulator driver"
default y
depends on DT_HAS_X_POWERS_AXP192_REGULATOR_ENABLED
depends on DT_HAS_X_POWERS_AXP192_ENABLED
select I2C
select MFD
help
Enable the AXP PMIC regulator driver
if REGULATOR_AXP192
config REGULATOR_AXP192_INIT_PRIORITY
int "AXP192 regulator driver init priority"
default 76
help
Init priority for the axp192 regulator driver. It must be
greater than MFD_INIT_PRIORITY.
endif

View file

@ -0,0 +1,360 @@
/*
* Copyright (c) 2021 NXP
* Copyright (c) 2023 Martin Kiepfer <mrmarteng@teleschirm.org>
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT x_powers_axp192_regulator
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/sys/linear_range.h>
#include <zephyr/sys/util.h>
#include <zephyr/dt-bindings/regulator/axp192.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_instance.h>
LOG_MODULE_REGISTER(regulator_axp192, CONFIG_REGULATOR_LOG_LEVEL);
/* Output control registers */
#define AXP192_REG_EXTEN_DCDC2_CONTROL 0x10U
#define AXP192_REG_DCDC123_LDO23_CONTROL 0x12U
#define AXP192_REG_DCDC2_VOLTAGE 0x23U
#define AXP192_REG_DCDC2_SLOPE 0x25U
#define AXP192_REG_DCDC1_VOLTAGE 0x26U
#define AXP192_REG_DCDC3_VOLTAGE 0x27U
#define AXP192_REG_LDO23_VOLTAGE 0x28U
#define AXP192_REG_DCDC123_WORKMODE 0x80U
struct regulator_axp192_desc {
const uint8_t enable_reg;
const uint8_t enable_mask;
const uint8_t enable_val;
const uint8_t vsel_reg;
const uint8_t vsel_mask;
const uint8_t vsel_bitpos;
const int32_t max_ua;
const uint8_t workmode_reg;
const uint8_t workmode_mask;
const uint8_t workmode_pwm_val;
const uint8_t num_ranges;
const struct linear_range *ranges;
};
struct regulator_axp192_data {
struct regulator_common_data data;
};
struct regulator_axp192_config {
struct regulator_common_config common;
const struct regulator_axp192_desc *desc;
const struct device *mfd;
const struct i2c_dt_spec i2c;
LOG_INSTANCE_PTR_DECLARE(log);
};
static const struct linear_range dcdc1_ranges[] = {
LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x7FU),
};
static const struct regulator_axp192_desc dcdc1_desc = {
.enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL,
.enable_mask = 0x01U,
.enable_val = 0x01U,
.vsel_reg = AXP192_REG_DCDC1_VOLTAGE,
.vsel_mask = 0x7FU,
.vsel_bitpos = 0U,
.max_ua = 1200000U,
.workmode_reg = AXP192_REG_DCDC123_WORKMODE,
.workmode_mask = 0x08U,
.workmode_pwm_val = 0x08U,
.ranges = dcdc1_ranges,
.num_ranges = ARRAY_SIZE(dcdc1_ranges),
};
static const struct linear_range dcdc2_ranges[] = {
LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x3FU),
};
static const struct regulator_axp192_desc dcdc2_desc = {
.enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL,
.enable_mask = 0x10U,
.enable_val = 0x10U,
.vsel_reg = AXP192_REG_DCDC2_VOLTAGE,
.vsel_mask = 0x3FU,
.vsel_bitpos = 0U,
.max_ua = 1600000U,
.workmode_reg = AXP192_REG_DCDC123_WORKMODE,
.workmode_mask = 0x04U,
.workmode_pwm_val = 0x04U,
.ranges = dcdc2_ranges,
.num_ranges = ARRAY_SIZE(dcdc2_ranges),
};
static const struct linear_range dcdc3_ranges[] = {
LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x7FU),
};
static const struct regulator_axp192_desc dcdc3_desc = {
.enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL,
.enable_mask = 0x02U,
.enable_val = 0x02U,
.vsel_reg = AXP192_REG_DCDC3_VOLTAGE,
.vsel_mask = 0x7FU,
.vsel_bitpos = 0U,
.max_ua = 700000U,
.workmode_reg = AXP192_REG_DCDC123_WORKMODE,
.workmode_mask = 0x02U,
.workmode_pwm_val = 0x02U,
.ranges = dcdc3_ranges,
.num_ranges = ARRAY_SIZE(dcdc3_ranges),
};
static const struct linear_range ldo2_ranges[] = {
LINEAR_RANGE_INIT(1800000U, 100000U, 0x00U, 0x0FU),
};
static const struct regulator_axp192_desc ldo2_desc = {
.enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL,
.enable_mask = 0x04U,
.enable_val = 0x04U,
.vsel_reg = AXP192_REG_LDO23_VOLTAGE,
.vsel_mask = 0xF0U,
.vsel_bitpos = 4U,
.max_ua = 200000U,
.workmode_reg = 0U,
.workmode_mask = 0U,
.ranges = ldo2_ranges,
.num_ranges = ARRAY_SIZE(ldo2_ranges),
};
static const struct linear_range ldo3_ranges[] = {
LINEAR_RANGE_INIT(1800000U, 100000U, 0x00U, 0x0FU),
};
static const struct regulator_axp192_desc ldo3_desc = {
.enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL,
.enable_mask = 0x08U,
.enable_val = 0x08U,
.vsel_reg = AXP192_REG_LDO23_VOLTAGE,
.vsel_mask = 0x0FU,
.vsel_bitpos = 0U,
.max_ua = 200000U,
.workmode_reg = 0U,
.workmode_mask = 0U,
.ranges = ldo3_ranges,
.num_ranges = ARRAY_SIZE(ldo3_ranges),
};
static int axp192_enable(const struct device *dev)
{
const struct regulator_axp192_config *config = dev->config;
int ret;
LOG_INST_DBG(config->log, "Enabling regulator");
LOG_INST_DBG(config->log, "[0x%02x]=0x%02x mask=0x%02x", config->desc->enable_reg,
config->desc->enable_val, config->desc->enable_mask);
ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->enable_reg,
config->desc->enable_mask, config->desc->enable_val);
if (ret != 0) {
LOG_INST_ERR(config->log, "Failed to enable regulator");
}
return ret;
}
static int axp192_disable(const struct device *dev)
{
const struct regulator_axp192_config *config = dev->config;
int ret;
LOG_INST_DBG(config->log, "Disabling regulator");
LOG_INST_DBG(config->log, "[0x%02x]=0 mask=0x%x", config->desc->enable_reg,
config->desc->enable_mask);
ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->enable_reg,
config->desc->enable_mask, 0u);
if (ret != 0) {
LOG_INST_ERR(config->log, "Failed to disable regulator");
}
return ret;
}
static unsigned int axp192_count_voltages(const struct device *dev)
{
const struct regulator_axp192_config *config = dev->config;
return linear_range_group_values_count(config->desc->ranges, config->desc->num_ranges);
}
static int axp192_list_voltage(const struct device *dev, unsigned int idx, int32_t *volt_uv)
{
const struct regulator_axp192_config *config = dev->config;
return linear_range_group_get_value(config->desc->ranges, config->desc->num_ranges, idx,
volt_uv);
}
static int axp192_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv)
{
const struct regulator_axp192_config *config = dev->config;
uint16_t idx;
int ret;
LOG_INST_DBG(config->log, "voltage = [min=%d, max=%d]", min_uv, max_uv);
/* set voltage */
ret = linear_range_group_get_win_index(config->desc->ranges, config->desc->num_ranges,
min_uv, max_uv, &idx);
if (ret != 0) {
LOG_INST_ERR(config->log, "No voltage range window could be detected");
return ret;
}
idx <<= config->desc->vsel_bitpos;
LOG_INST_DBG(config->log, "[0x%x]=0x%x mask=0x%x", config->desc->vsel_reg, idx,
config->desc->vsel_mask);
ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->vsel_reg, config->desc->vsel_mask,
(uint8_t)idx);
if (ret != 0) {
LOG_INST_ERR(config->log, "Failed to set regulator voltage");
}
return ret;
}
static int axp192_get_voltage(const struct device *dev, int32_t *volt_uv)
{
const struct regulator_axp192_config *config = dev->config;
int ret;
uint8_t raw_reg;
/* read voltage */
ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->vsel_reg, &raw_reg);
if (ret != 0) {
return ret;
}
raw_reg = (raw_reg & config->desc->vsel_mask) >> config->desc->vsel_bitpos;
ret = linear_range_group_get_value(config->desc->ranges, config->desc->num_ranges, raw_reg,
volt_uv);
return ret;
}
static int axp192_set_mode(const struct device *dev, regulator_mode_t mode)
{
const struct regulator_axp192_config *config = dev->config;
int ret;
/* setting workmode is only possible for DCDC1-3 */
if ((mode == AXP192_DCDC_MODE_PWM) && (config->desc->workmode_reg != 0)) {
/* configure PWM mode */
LOG_INST_DBG(config->log, "PWM mode enabled");
ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->workmode_reg,
config->desc->workmode_mask,
config->desc->workmode_pwm_val);
if (ret != 0) {
return ret;
}
} else if (mode == AXP192_DCDC_MODE_AUTO) {
/* configure AUTO mode (default) */
if (config->desc->workmode_reg != 0) {
ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->workmode_reg,
config->desc->workmode_mask, 0u);
if (ret != 0) {
return ret;
}
} else {
/* AUTO is default mode for LDOs that cannot be configured */
return 0;
}
} else {
LOG_INST_ERR(config->log, "Setting DCDC workmode failed");
return -ENOTSUP;
}
return 0;
}
static int axp192_get_current_limit(const struct device *dev, int32_t *curr_ua)
{
const struct regulator_axp192_config *config = dev->config;
*curr_ua = config->desc->max_ua;
return 0;
}
static struct regulator_driver_api api = {
.enable = axp192_enable,
.disable = axp192_disable,
.count_voltages = axp192_count_voltages,
.list_voltage = axp192_list_voltage,
.set_voltage = axp192_set_voltage,
.get_voltage = axp192_get_voltage,
.set_mode = axp192_set_mode,
.get_current_limit = axp192_get_current_limit,
};
static int regulator_axp192_init(const struct device *dev)
{
const struct regulator_axp192_config *config = dev->config;
uint8_t enabled_val;
bool is_enabled;
int ret = 0;
regulator_common_data_init(dev);
if (!device_is_ready(config->mfd)) {
LOG_INST_ERR(config->log, "Parent instance not ready!");
return -ENODEV;
}
/* read regulator state */
ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->enable_reg, &enabled_val);
if (ret != 0) {
LOG_INST_ERR(config->log, "Reading enable status failed!");
return ret;
}
is_enabled = ((enabled_val & config->desc->enable_mask) == config->desc->enable_val);
LOG_INST_DBG(config->log, "is_enabled: %d", is_enabled);
return regulator_common_init(dev, is_enabled);
}
#define REGULATOR_AXP192_DEFINE(node_id, id, name) \
static struct regulator_axp192_data data_##id; \
LOG_INSTANCE_REGISTER(name, node_id, CONFIG_REGULATOR_LOG_LEVEL); \
static const struct regulator_axp192_config config_##id = { \
.common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \
.desc = &name##_desc, \
.mfd = DEVICE_DT_GET(DT_GPARENT(node_id)), \
.i2c = I2C_DT_SPEC_GET(DT_GPARENT(node_id)), \
LOG_INSTANCE_PTR_INIT(log, name, node_id)}; \
DEVICE_DT_DEFINE(node_id, regulator_axp192_init, NULL, &data_##id, &config_##id, \
POST_KERNEL, CONFIG_REGULATOR_AXP192_INIT_PRIORITY, &api);
#define REGULATOR_AXP192_DEFINE_COND(inst, child) \
COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \
(REGULATOR_AXP192_DEFINE(DT_INST_CHILD(inst, child), child##inst, child)), ())
#define REGULATOR_AXP192_DEFINE_ALL(inst) \
REGULATOR_AXP192_DEFINE_COND(inst, dcdc1) \
REGULATOR_AXP192_DEFINE_COND(inst, dcdc2) \
REGULATOR_AXP192_DEFINE_COND(inst, dcdc3) \
REGULATOR_AXP192_DEFINE_COND(inst, ldo2) \
REGULATOR_AXP192_DEFINE_COND(inst, ldo3)
DT_INST_FOREACH_STATUS_OKAY(REGULATOR_AXP192_DEFINE_ALL)

View file

@ -0,0 +1,12 @@
# Copyright (c) 2023, Martin Kiepfer <mrmarteng@teleschirm.org>
# SPDX-License-Identifier: Apache-2.0
description: X-Powers AXP192
compatible: "x-powers,axp192"
include: i2c-device.yaml
properties:
reg:
required: true

View file

@ -0,0 +1,64 @@
# Copyright (c), 2021 NXP
# Copyright (c), 2023 Martin Kiepfer <mrmarteng@teleschirm.org>
# SPDX -License-Identifier: Apache-2.0
description: |
AXP192 PMIC
The PMIC has three DCDC converters and two LDOs (LDO1 cannot be disabled).
All need to be defined as children nodes.
For example:
i2c {
pmic@34 {
reg = <0x34>;
...
regulators {
compatible = "x-powers,axp192-regulator";
DCDC1 {
/* all properties for DCDC1 */
};
DCDC2 {
/* all properties for DCDC2 */
};
DCDC3 {
/* all properties for DCDC3 */
};
LDO2 {
/* all properties for LDO2 */
};
LDO3 {
/* all properties for LDO3 */
};
};
};
};
compatible: "x-powers,axp192-regulator"
include: base.yaml
child-binding:
include:
- name: regulator.yaml
property-allowlist:
- regulator-init-microvolt
- regulator-min-microvolt
- regulator-max-microvolt
- regulator-always-on
- regulator-boot-on
- regulator-initial-mode
- regulator-allowed-modes
properties:
regulator-initial-mode:
type: int
default: 0
enum:
- 0
- 1
description: |
Initial operating mode. AXP192 supports 2 different power modes:
AXP192_DCDC_MODE_AUTO: Auto (0, default)
AXP192_DCDC_MODE_PWM: PWM

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 Martin Kiepfer
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_
/**
* @defgroup regulator_axp192 AXP192 Devicetree helpers.
* @ingroup regulator_interface
* @{
*/
/**
* @name AXP192 Regulator modes
* @{
*/
/* DCDCs */
#define AXP192_DCDC_MODE_AUTO 0x00U
#define AXP192_DCDC_MODE_PWM 0x01U
/** @} */
/** @} */
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_ */

View file

@ -58,3 +58,19 @@ apd356x@3 {
BUCKBOOST {};
};
};
axp192@4 {
compatible = "x-powers,axp192";
reg = <0x4>;
regulators {
compatible = "x-powers,axp192-regulator";
DCDC1 {};
DCDC2 {};
DCDC3 {};
LDO1 {};
LDO2 {};
LDO3 {};
};
};