power: supply: max1720x: add read support for nvmem

ModelGauge m5 and device configuration values are stored in nonvolatile
memory to prevent data loss if the IC loses power. Add read support for
the nonvolatile memory on MAX1720X devices.

Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
Link: https://lore.kernel.org/r/20240903063526.222890-1-dima.fedrau@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
This commit is contained in:
Dimitri Fedrau 2024-09-03 08:35:26 +02:00 committed by Sebastian Reichel
parent 9dad0127ad
commit 47271a9356

View File

@ -10,13 +10,16 @@
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <asm/unaligned.h>
/* Nonvolatile registers */
#define MAX1720X_NXTABLE0 0x80
#define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */
#define MAX1720X_NDEVICE_NAME4 0xDF
/* ModelGauge m5 */
#define MAX172XX_STATUS 0x00 /* Status */
@ -46,6 +49,8 @@ static const char *const max17205_model = "MAX17205";
struct max1720x_device_info {
struct regmap *regmap;
struct regmap *regmap_nv;
struct i2c_client *ancillary;
int rsense;
};
@ -106,6 +111,134 @@ static const struct regmap_config max1720x_regmap_cfg = {
.cache_type = REGCACHE_RBTREE,
};
static const struct regmap_range max1720x_nvmem_allow[] = {
regmap_reg_range(MAX1720X_NXTABLE0, MAX1720X_NDEVICE_NAME4),
};
static const struct regmap_range max1720x_nvmem_deny[] = {
regmap_reg_range(0x00, 0x7F),
regmap_reg_range(0xE0, 0xFF),
};
static const struct regmap_access_table max1720x_nvmem_regs = {
.yes_ranges = max1720x_nvmem_allow,
.n_yes_ranges = ARRAY_SIZE(max1720x_nvmem_allow),
.no_ranges = max1720x_nvmem_deny,
.n_no_ranges = ARRAY_SIZE(max1720x_nvmem_deny),
};
static const struct regmap_config max1720x_nvmem_regmap_cfg = {
.reg_bits = 8,
.val_bits = 16,
.max_register = MAX1720X_NDEVICE_NAME4,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.rd_table = &max1720x_nvmem_regs,
};
static const struct nvmem_cell_info max1720x_nvmem_cells[] = {
{ .name = "nXTable0", .offset = 0, .bytes = 2, },
{ .name = "nXTable1", .offset = 2, .bytes = 2, },
{ .name = "nXTable2", .offset = 4, .bytes = 2, },
{ .name = "nXTable3", .offset = 6, .bytes = 2, },
{ .name = "nXTable4", .offset = 8, .bytes = 2, },
{ .name = "nXTable5", .offset = 10, .bytes = 2, },
{ .name = "nXTable6", .offset = 12, .bytes = 2, },
{ .name = "nXTable7", .offset = 14, .bytes = 2, },
{ .name = "nXTable8", .offset = 16, .bytes = 2, },
{ .name = "nXTable9", .offset = 18, .bytes = 2, },
{ .name = "nXTable10", .offset = 20, .bytes = 2, },
{ .name = "nXTable11", .offset = 22, .bytes = 2, },
{ .name = "nUser18C", .offset = 24, .bytes = 2, },
{ .name = "nUser18D", .offset = 26, .bytes = 2, },
{ .name = "nODSCTh", .offset = 28, .bytes = 2, },
{ .name = "nODSCCfg", .offset = 30, .bytes = 2, },
{ .name = "nOCVTable0", .offset = 32, .bytes = 2, },
{ .name = "nOCVTable1", .offset = 34, .bytes = 2, },
{ .name = "nOCVTable2", .offset = 36, .bytes = 2, },
{ .name = "nOCVTable3", .offset = 38, .bytes = 2, },
{ .name = "nOCVTable4", .offset = 40, .bytes = 2, },
{ .name = "nOCVTable5", .offset = 42, .bytes = 2, },
{ .name = "nOCVTable6", .offset = 44, .bytes = 2, },
{ .name = "nOCVTable7", .offset = 46, .bytes = 2, },
{ .name = "nOCVTable8", .offset = 48, .bytes = 2, },
{ .name = "nOCVTable9", .offset = 50, .bytes = 2, },
{ .name = "nOCVTable10", .offset = 52, .bytes = 2, },
{ .name = "nOCVTable11", .offset = 54, .bytes = 2, },
{ .name = "nIChgTerm", .offset = 56, .bytes = 2, },
{ .name = "nFilterCfg", .offset = 58, .bytes = 2, },
{ .name = "nVEmpty", .offset = 60, .bytes = 2, },
{ .name = "nLearnCfg", .offset = 62, .bytes = 2, },
{ .name = "nQRTable00", .offset = 64, .bytes = 2, },
{ .name = "nQRTable10", .offset = 66, .bytes = 2, },
{ .name = "nQRTable20", .offset = 68, .bytes = 2, },
{ .name = "nQRTable30", .offset = 70, .bytes = 2, },
{ .name = "nCycles", .offset = 72, .bytes = 2, },
{ .name = "nFullCapNom", .offset = 74, .bytes = 2, },
{ .name = "nRComp0", .offset = 76, .bytes = 2, },
{ .name = "nTempCo", .offset = 78, .bytes = 2, },
{ .name = "nIAvgEmpty", .offset = 80, .bytes = 2, },
{ .name = "nFullCapRep", .offset = 82, .bytes = 2, },
{ .name = "nVoltTemp", .offset = 84, .bytes = 2, },
{ .name = "nMaxMinCurr", .offset = 86, .bytes = 2, },
{ .name = "nMaxMinVolt", .offset = 88, .bytes = 2, },
{ .name = "nMaxMinTemp", .offset = 90, .bytes = 2, },
{ .name = "nSOC", .offset = 92, .bytes = 2, },
{ .name = "nTimerH", .offset = 94, .bytes = 2, },
{ .name = "nConfig", .offset = 96, .bytes = 2, },
{ .name = "nRippleCfg", .offset = 98, .bytes = 2, },
{ .name = "nMiscCfg", .offset = 100, .bytes = 2, },
{ .name = "nDesignCap", .offset = 102, .bytes = 2, },
{ .name = "nHibCfg", .offset = 104, .bytes = 2, },
{ .name = "nPackCfg", .offset = 106, .bytes = 2, },
{ .name = "nRelaxCfg", .offset = 108, .bytes = 2, },
{ .name = "nConvgCfg", .offset = 110, .bytes = 2, },
{ .name = "nNVCfg0", .offset = 112, .bytes = 2, },
{ .name = "nNVCfg1", .offset = 114, .bytes = 2, },
{ .name = "nNVCfg2", .offset = 116, .bytes = 2, },
{ .name = "nSBSCfg", .offset = 118, .bytes = 2, },
{ .name = "nROMID0", .offset = 120, .bytes = 2, },
{ .name = "nROMID1", .offset = 122, .bytes = 2, },
{ .name = "nROMID2", .offset = 124, .bytes = 2, },
{ .name = "nROMID3", .offset = 126, .bytes = 2, },
{ .name = "nVAlrtTh", .offset = 128, .bytes = 2, },
{ .name = "nTAlrtTh", .offset = 130, .bytes = 2, },
{ .name = "nSAlrtTh", .offset = 132, .bytes = 2, },
{ .name = "nIAlrtTh", .offset = 134, .bytes = 2, },
{ .name = "nUser1C4", .offset = 136, .bytes = 2, },
{ .name = "nUser1C5", .offset = 138, .bytes = 2, },
{ .name = "nFullSOCThr", .offset = 140, .bytes = 2, },
{ .name = "nTTFCfg", .offset = 142, .bytes = 2, },
{ .name = "nCGain", .offset = 144, .bytes = 2, },
{ .name = "nTCurve", .offset = 146, .bytes = 2, },
{ .name = "nTGain", .offset = 148, .bytes = 2, },
{ .name = "nTOff", .offset = 150, .bytes = 2, },
{ .name = "nManfctrName0", .offset = 152, .bytes = 2, },
{ .name = "nManfctrName1", .offset = 154, .bytes = 2, },
{ .name = "nManfctrName2", .offset = 156, .bytes = 2, },
{ .name = "nRSense", .offset = 158, .bytes = 2, },
{ .name = "nUser1D0", .offset = 160, .bytes = 2, },
{ .name = "nUser1D1", .offset = 162, .bytes = 2, },
{ .name = "nAgeFcCfg", .offset = 164, .bytes = 2, },
{ .name = "nDesignVoltage", .offset = 166, .bytes = 2, },
{ .name = "nUser1D4", .offset = 168, .bytes = 2, },
{ .name = "nRFastVShdn", .offset = 170, .bytes = 2, },
{ .name = "nManfctrDate", .offset = 172, .bytes = 2, },
{ .name = "nFirstUsed", .offset = 174, .bytes = 2, },
{ .name = "nSerialNumber0", .offset = 176, .bytes = 2, },
{ .name = "nSerialNumber1", .offset = 178, .bytes = 2, },
{ .name = "nSerialNumber2", .offset = 180, .bytes = 2, },
{ .name = "nDeviceName0", .offset = 182, .bytes = 2, },
{ .name = "nDeviceName1", .offset = 184, .bytes = 2, },
{ .name = "nDeviceName2", .offset = 186, .bytes = 2, },
{ .name = "nDeviceName3", .offset = 188, .bytes = 2, },
{ .name = "nDeviceName4", .offset = 190, .bytes = 2, },
};
static const enum power_supply_property max1720x_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
@ -249,30 +382,81 @@ static int max1720x_battery_get_property(struct power_supply *psy,
return ret;
}
static int max1720x_probe_sense_resistor(struct i2c_client *client,
struct max1720x_device_info *info)
static
int max1720x_nvmem_reg_read(void *priv, unsigned int off, void *val, size_t len)
{
struct max1720x_device_info *info = priv;
unsigned int reg = MAX1720X_NXTABLE0 + (off / 2);
return regmap_bulk_read(info->regmap_nv, reg, val, len / 2);
}
static void max1720x_unregister_ancillary(void *data)
{
struct max1720x_device_info *info = data;
i2c_unregister_device(info->ancillary);
}
static int max1720x_probe_nvmem(struct i2c_client *client,
struct max1720x_device_info *info)
{
struct device *dev = &client->dev;
struct i2c_client *ancillary;
struct nvmem_config nvmem_config = {
.dev = dev,
.name = "max1720x_nvmem",
.cells = max1720x_nvmem_cells,
.ncells = ARRAY_SIZE(max1720x_nvmem_cells),
.read_only = true,
.root_only = true,
.reg_read = max1720x_nvmem_reg_read,
.size = ARRAY_SIZE(max1720x_nvmem_cells) * 2,
.word_size = 2,
.stride = 2,
.priv = info,
};
struct nvmem_device *nvmem;
unsigned int val;
int ret;
ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb);
if (IS_ERR(ancillary)) {
info->ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb);
if (IS_ERR(info->ancillary)) {
dev_err(dev, "Failed to initialize ancillary i2c device\n");
return PTR_ERR(ancillary);
return PTR_ERR(info->ancillary);
}
ret = i2c_smbus_read_word_data(ancillary, MAX1720X_NRSENSE);
i2c_unregister_device(ancillary);
if (ret < 0)
ret = devm_add_action_or_reset(dev, max1720x_unregister_ancillary, info);
if (ret) {
i2c_unregister_device(info->ancillary);
dev_err(dev, "Failed to add unregister callback\n");
return ret;
}
info->rsense = ret;
info->regmap_nv = devm_regmap_init_i2c(info->ancillary,
&max1720x_nvmem_regmap_cfg);
if (IS_ERR(info->regmap_nv)) {
dev_err(dev, "regmap initialization of nvmem failed\n");
return PTR_ERR(info->regmap_nv);
}
ret = regmap_read(info->regmap_nv, MAX1720X_NRSENSE, &val);
if (ret < 0) {
dev_err(dev, "Failed to read sense resistor value\n");
return ret;
}
info->rsense = val;
if (!info->rsense) {
dev_warn(dev, "RSense not calibrated, set 10 mOhms!\n");
info->rsense = 1000; /* in regs in 10^-5 */
}
nvmem = devm_nvmem_register(dev, &nvmem_config);
if (IS_ERR(nvmem)) {
dev_err(dev, "Could not register nvmem!");
return PTR_ERR(nvmem);
}
return 0;
}
@ -299,15 +483,15 @@ static int max1720x_probe(struct i2c_client *client)
psy_cfg.drv_data = info;
psy_cfg.fwnode = dev_fwnode(dev);
i2c_set_clientdata(client, info);
info->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg);
if (IS_ERR(info->regmap))
return dev_err_probe(dev, PTR_ERR(info->regmap),
"regmap initialization failed\n");
ret = max1720x_probe_sense_resistor(client, info);
ret = max1720x_probe_nvmem(client, info);
if (ret)
return dev_err_probe(dev, ret,
"Failed to read sense resistor value\n");
return dev_err_probe(dev, ret, "Failed to probe nvmem\n");
bat = devm_power_supply_register(dev, &max1720x_bat_desc, &psy_cfg);
if (IS_ERR(bat))