drivers: regulator: add i2c regulator driver

This commit adds a generic i2c regulator driver, and enables the NXP
PCA9420 PMIC IC using this driver. The regulator driver also exposes an
additional API in include/drivers/regulator/consumer.h, which allows
drivers to implement support for adjusting voltage levels and current
limits, if their device supports it.

Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
This commit is contained in:
Daniel DeGrasse 2021-09-22 08:55:23 -05:00 committed by Christopher Friedt
commit 130f6eb816
9 changed files with 803 additions and 0 deletions

View file

@ -4,3 +4,4 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_PMIC regulator_pmic.c)

View file

@ -13,5 +13,6 @@ module-str = regulator
source "subsys/logging/Kconfig.template.log_config"
source "drivers/regulator/Kconfig.fixed"
source "drivers/regulator/Kconfig.pmic"
endif # REGULATOR

View file

@ -0,0 +1,22 @@
# Copyright (c) 2021, NXP
# SPDX -License-Identifier: Apache-2.0
DT_COMPAT_PMIC_REG := regulator-pmic
config REGULATOR_PMIC
bool "PMIC Regulator Driver"
depends on $(dt_compat_enabled,$(DT_COMPAT_PMIC_REG))
default $(dt_compat_enabled,$(DT_COMPAT_PMIC_REG))
select I2C
help
Enable the pmic regulator driver
config PMIC_REGULATOR_INIT_PRIORITY
int "Regulator Init priority"
depends on REGULATOR_PMIC
default 75
help
I2C based regulator init priority.
Must be greater than KERNEL_INIT_PRIORITY_DEVICE so I2C is initialized,
and less than SDMMC_INIT_PRIORITY, since the SDMMC driver uses an I2C
PMIC regulator

View file

@ -0,0 +1,197 @@
/*
* Copyright (c) 2021 NXP
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief PMIC Regulator Driver
* This driver implements the regulator API within Zephyr, and additionally
* implements support for a broader API. Most consumers will want to use
* the API provided in drivers/regulator/consumer.h to manipulate the voltage
* levels of the regulator device.
* manipulate.
*/
#define DT_DRV_COMPAT regulator_pmic
#include <kernel.h>
#include <drivers/regulator.h>
#include <drivers/regulator/consumer.h>
#include <drivers/i2c.h>
#include <errno.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(pmic_regulator, CONFIG_REGULATOR_LOG_LEVEL);
struct regulator_data {
struct onoff_sync_service srv;
};
struct __packed voltage_range {
int uV; /* Voltage in uV */
int reg_val; /* Register value for voltage */
};
struct regulator_config {
struct voltage_range *voltages;
int num_voltages;
uint8_t vsel_reg;
uint8_t vsel_mask;
uint32_t max_uV;
uint32_t min_uV;
uint8_t enable_reg;
uint8_t enable_mask;
uint8_t enable_val;
bool enable_inverted;
uint8_t i2c_address;
const struct device *i2c_dev;
uint32_t voltage_array[];
};
/**
* Modifies a register within the PMIC
* Returns 0 on success, or errno on error
*/
static int regulator_modify_register(const struct regulator_config *conf,
uint8_t reg, uint8_t reg_mask, uint8_t reg_val)
{
uint8_t reg_current;
int rc;
rc = i2c_reg_read_byte(conf->i2c_dev, conf->i2c_address,
reg, &reg_current);
if (rc) {
return rc;
}
reg_current &= ~reg_mask;
reg_current |= reg_val;
return i2c_reg_write_byte(conf->i2c_dev, conf->i2c_address, reg,
reg_current);
}
/**
* Part of the extended regulator consumer API
* Returns the number of supported voltages
*/
int regulator_count_voltages(const struct device *dev)
{
return ((struct regulator_config *)dev->config)->num_voltages;
}
/**
* Part of the extended regulator consumer API
* Returns the supported voltage in uV for a given selector value
*/
int regulator_list_voltages(const struct device *dev, unsigned int selector)
{
const struct regulator_config *config = dev->config;
if (config->num_voltages <= selector) {
return -ENODEV;
}
return config->voltages[selector].uV;
}
int regulator_is_supported_voltage(const struct device *dev,
int min_uV, int max_uV)
{
const struct regulator_config *config = dev->config;
return !((config->max_uV < min_uV) || (config->min_uV > max_uV));
}
static int enable_regulator(const struct device *dev, struct onoff_client *cli)
{
k_spinlock_key_t key;
int rc;
uint8_t en_val;
struct regulator_data *data = dev->data;
const struct regulator_config *config = dev->config;
LOG_DBG("Enabling regulator");
rc = onoff_sync_lock(&data->srv, &key);
if (rc) {
/* Request has already enabled PMIC */
return onoff_sync_finalize(&data->srv, key, cli, rc, true);
}
en_val = config->enable_inverted ? 0 : config->enable_val;
rc = regulator_modify_register(config, config->enable_reg,
config->enable_reg, en_val);
if (rc) {
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
}
return onoff_sync_finalize(&data->srv, key, cli, rc, true);
}
static int disable_regulator(const struct device *dev)
{
struct regulator_data *data = dev->data;
const struct regulator_config *config = dev->config;
k_spinlock_key_t key;
uint8_t dis_val;
int rc;
LOG_DBG("Disabling regulator");
rc = onoff_sync_lock(&data->srv, &key);
if (rc == 0) {
rc = -EINVAL;
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
}
dis_val = config->enable_inverted ? config->enable_val : 0;
rc = regulator_modify_register(config, config->enable_reg,
config->enable_reg, dis_val);
if (rc) {
/* Error writing configs */
return onoff_sync_finalize(&data->srv, key, NULL, rc, true);
}
return onoff_sync_finalize(&data->srv, key, NULL, rc, true);
}
static int pmic_reg_init(const struct device *dev)
{
struct regulator_config *config = (struct regulator_config *)dev->config;
LOG_INF("PMIC regulator initializing");
/* Cast the voltage array set at compile time to the voltage range
* struct
*/
config->voltages = (struct voltage_range *)config->voltage_array;
/* Check to verify we have a valid I2C device */
if (config->i2c_dev == NULL || !device_is_ready(config->i2c_dev)) {
return -ENODEV;
}
return 0;
}
static const struct regulator_driver_api api = {
.enable = enable_regulator,
.disable = disable_regulator
};
#define CONFIGURE_REGULATOR(id) \
static struct regulator_data pmic_reg_##id##_data; \
static struct regulator_config pmic_reg_##id##_cfg = { \
.vsel_mask = DT_INST_PROP(id, vsel_mask), \
.vsel_reg = DT_INST_PROP(id, vsel_reg), \
.num_voltages = DT_INST_PROP(id, num_voltages), \
.enable_reg = DT_INST_PROP(id, enable_reg), \
.enable_mask = DT_INST_PROP(id, enable_mask), \
.enable_val = DT_INST_PROP(id, enable_val), \
.min_uV = DT_INST_PROP(id, min_uv), \
.max_uV = DT_INST_PROP(id, max_uv), \
.enable_inverted = DT_INST_PROP(id, enable_inverted), \
.i2c_address = DT_REG_ADDR(DT_PARENT(DT_DRV_INST(id))), \
.i2c_dev = DEVICE_DT_GET(DT_BUS(DT_PARENT(DT_DRV_INST(id)))), \
.voltage_array = DT_INST_PROP(id, voltage_range), \
}; \
DEVICE_DT_INST_DEFINE(id, pmic_reg_init, NULL, \
&pmic_reg_##id##_data, &pmic_reg_##id##_cfg, \
POST_KERNEL, CONFIG_PMIC_REGULATOR_INIT_PRIORITY, \
&api); \
DT_INST_FOREACH_STATUS_OKAY(CONFIGURE_REGULATOR)