1 // SPDX-License-Identifier: GPL-2.0+
3 * Fuel gauge driver for Maxim 17201/17205
5 * based on max1721x_battery.c
7 * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
10 #include <linux/bitfield.h>
11 #include <linux/i2c.h>
12 #include <linux/module.h>
13 #include <linux/nvmem-provider.h>
14 #include <linux/power_supply.h>
15 #include <linux/regmap.h>
17 #include <linux/unaligned.h>
19 /* Nonvolatile registers */
20 #define MAX1720X_NXTABLE0 0x80
21 #define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */
22 #define MAX1720X_NDEVICE_NAME4 0xDF
25 #define MAX172XX_STATUS 0x00 /* Status */
26 #define MAX172XX_STATUS_BAT_ABSENT BIT(3) /* Battery absent */
27 #define MAX172XX_REPCAP 0x05 /* Average capacity */
28 #define MAX172XX_REPSOC 0x06 /* Percentage of charge */
29 #define MAX172XX_TEMP 0x08 /* Temperature */
30 #define MAX172XX_CURRENT 0x0A /* Actual current */
31 #define MAX172XX_AVG_CURRENT 0x0B /* Average current */
32 #define MAX172XX_TTE 0x11 /* Time to empty */
33 #define MAX172XX_AVG_TA 0x16 /* Average temperature */
34 #define MAX172XX_CYCLES 0x17
35 #define MAX172XX_DESIGN_CAP 0x18 /* Design capacity */
36 #define MAX172XX_AVG_VCELL 0x19
37 #define MAX172XX_TTF 0x20 /* Time to full */
38 #define MAX172XX_DEV_NAME 0x21 /* Device name */
39 #define MAX172XX_DEV_NAME_TYPE_MASK GENMASK(3, 0)
40 #define MAX172XX_DEV_NAME_TYPE_MAX17201 BIT(0)
41 #define MAX172XX_DEV_NAME_TYPE_MAX17205 (BIT(0) | BIT(2))
42 #define MAX172XX_QR_TABLE10 0x22
43 #define MAX172XX_BATT 0xDA /* Battery voltage */
44 #define MAX172XX_ATAVCAP 0xDF
46 static const char *const max1720x_manufacturer
= "Maxim Integrated";
47 static const char *const max17201_model
= "MAX17201";
48 static const char *const max17205_model
= "MAX17205";
50 struct max1720x_device_info
{
51 struct regmap
*regmap
;
52 struct regmap
*regmap_nv
;
53 struct i2c_client
*ancillary
;
58 * Model Gauge M5 Algorithm output register
59 * Volatile data (must not be cached)
61 static const struct regmap_range max1720x_volatile_allow
[] = {
62 regmap_reg_range(MAX172XX_STATUS
, MAX172XX_CYCLES
),
63 regmap_reg_range(MAX172XX_AVG_VCELL
, MAX172XX_TTF
),
64 regmap_reg_range(MAX172XX_QR_TABLE10
, MAX172XX_ATAVCAP
),
67 static const struct regmap_range max1720x_readable_allow
[] = {
68 regmap_reg_range(MAX172XX_STATUS
, MAX172XX_ATAVCAP
),
71 static const struct regmap_range max1720x_readable_deny
[] = {
72 /* unused registers */
73 regmap_reg_range(0x24, 0x26),
74 regmap_reg_range(0x30, 0x31),
75 regmap_reg_range(0x33, 0x34),
76 regmap_reg_range(0x37, 0x37),
77 regmap_reg_range(0x3B, 0x3C),
78 regmap_reg_range(0x40, 0x41),
79 regmap_reg_range(0x43, 0x44),
80 regmap_reg_range(0x47, 0x49),
81 regmap_reg_range(0x4B, 0x4C),
82 regmap_reg_range(0x4E, 0xAF),
83 regmap_reg_range(0xB1, 0xB3),
84 regmap_reg_range(0xB5, 0xB7),
85 regmap_reg_range(0xBF, 0xD0),
86 regmap_reg_range(0xDB, 0xDB),
87 regmap_reg_range(0xE0, 0xFF),
90 static const struct regmap_access_table max1720x_readable_regs
= {
91 .yes_ranges
= max1720x_readable_allow
,
92 .n_yes_ranges
= ARRAY_SIZE(max1720x_readable_allow
),
93 .no_ranges
= max1720x_readable_deny
,
94 .n_no_ranges
= ARRAY_SIZE(max1720x_readable_deny
),
97 static const struct regmap_access_table max1720x_volatile_regs
= {
98 .yes_ranges
= max1720x_volatile_allow
,
99 .n_yes_ranges
= ARRAY_SIZE(max1720x_volatile_allow
),
100 .no_ranges
= max1720x_readable_deny
,
101 .n_no_ranges
= ARRAY_SIZE(max1720x_readable_deny
),
104 static const struct regmap_config max1720x_regmap_cfg
= {
107 .max_register
= MAX172XX_ATAVCAP
,
108 .val_format_endian
= REGMAP_ENDIAN_LITTLE
,
109 .rd_table
= &max1720x_readable_regs
,
110 .volatile_table
= &max1720x_volatile_regs
,
111 .cache_type
= REGCACHE_RBTREE
,
114 static const struct regmap_range max1720x_nvmem_allow
[] = {
115 regmap_reg_range(MAX1720X_NXTABLE0
, MAX1720X_NDEVICE_NAME4
),
118 static const struct regmap_range max1720x_nvmem_deny
[] = {
119 regmap_reg_range(0x00, 0x7F),
120 regmap_reg_range(0xE0, 0xFF),
123 static const struct regmap_access_table max1720x_nvmem_regs
= {
124 .yes_ranges
= max1720x_nvmem_allow
,
125 .n_yes_ranges
= ARRAY_SIZE(max1720x_nvmem_allow
),
126 .no_ranges
= max1720x_nvmem_deny
,
127 .n_no_ranges
= ARRAY_SIZE(max1720x_nvmem_deny
),
130 static const struct regmap_config max1720x_nvmem_regmap_cfg
= {
133 .max_register
= MAX1720X_NDEVICE_NAME4
,
134 .val_format_endian
= REGMAP_ENDIAN_LITTLE
,
135 .rd_table
= &max1720x_nvmem_regs
,
138 static const struct nvmem_cell_info max1720x_nvmem_cells
[] = {
139 { .name
= "nXTable0", .offset
= 0, .bytes
= 2, },
140 { .name
= "nXTable1", .offset
= 2, .bytes
= 2, },
141 { .name
= "nXTable2", .offset
= 4, .bytes
= 2, },
142 { .name
= "nXTable3", .offset
= 6, .bytes
= 2, },
143 { .name
= "nXTable4", .offset
= 8, .bytes
= 2, },
144 { .name
= "nXTable5", .offset
= 10, .bytes
= 2, },
145 { .name
= "nXTable6", .offset
= 12, .bytes
= 2, },
146 { .name
= "nXTable7", .offset
= 14, .bytes
= 2, },
147 { .name
= "nXTable8", .offset
= 16, .bytes
= 2, },
148 { .name
= "nXTable9", .offset
= 18, .bytes
= 2, },
149 { .name
= "nXTable10", .offset
= 20, .bytes
= 2, },
150 { .name
= "nXTable11", .offset
= 22, .bytes
= 2, },
151 { .name
= "nUser18C", .offset
= 24, .bytes
= 2, },
152 { .name
= "nUser18D", .offset
= 26, .bytes
= 2, },
153 { .name
= "nODSCTh", .offset
= 28, .bytes
= 2, },
154 { .name
= "nODSCCfg", .offset
= 30, .bytes
= 2, },
156 { .name
= "nOCVTable0", .offset
= 32, .bytes
= 2, },
157 { .name
= "nOCVTable1", .offset
= 34, .bytes
= 2, },
158 { .name
= "nOCVTable2", .offset
= 36, .bytes
= 2, },
159 { .name
= "nOCVTable3", .offset
= 38, .bytes
= 2, },
160 { .name
= "nOCVTable4", .offset
= 40, .bytes
= 2, },
161 { .name
= "nOCVTable5", .offset
= 42, .bytes
= 2, },
162 { .name
= "nOCVTable6", .offset
= 44, .bytes
= 2, },
163 { .name
= "nOCVTable7", .offset
= 46, .bytes
= 2, },
164 { .name
= "nOCVTable8", .offset
= 48, .bytes
= 2, },
165 { .name
= "nOCVTable9", .offset
= 50, .bytes
= 2, },
166 { .name
= "nOCVTable10", .offset
= 52, .bytes
= 2, },
167 { .name
= "nOCVTable11", .offset
= 54, .bytes
= 2, },
168 { .name
= "nIChgTerm", .offset
= 56, .bytes
= 2, },
169 { .name
= "nFilterCfg", .offset
= 58, .bytes
= 2, },
170 { .name
= "nVEmpty", .offset
= 60, .bytes
= 2, },
171 { .name
= "nLearnCfg", .offset
= 62, .bytes
= 2, },
173 { .name
= "nQRTable00", .offset
= 64, .bytes
= 2, },
174 { .name
= "nQRTable10", .offset
= 66, .bytes
= 2, },
175 { .name
= "nQRTable20", .offset
= 68, .bytes
= 2, },
176 { .name
= "nQRTable30", .offset
= 70, .bytes
= 2, },
177 { .name
= "nCycles", .offset
= 72, .bytes
= 2, },
178 { .name
= "nFullCapNom", .offset
= 74, .bytes
= 2, },
179 { .name
= "nRComp0", .offset
= 76, .bytes
= 2, },
180 { .name
= "nTempCo", .offset
= 78, .bytes
= 2, },
181 { .name
= "nIAvgEmpty", .offset
= 80, .bytes
= 2, },
182 { .name
= "nFullCapRep", .offset
= 82, .bytes
= 2, },
183 { .name
= "nVoltTemp", .offset
= 84, .bytes
= 2, },
184 { .name
= "nMaxMinCurr", .offset
= 86, .bytes
= 2, },
185 { .name
= "nMaxMinVolt", .offset
= 88, .bytes
= 2, },
186 { .name
= "nMaxMinTemp", .offset
= 90, .bytes
= 2, },
187 { .name
= "nSOC", .offset
= 92, .bytes
= 2, },
188 { .name
= "nTimerH", .offset
= 94, .bytes
= 2, },
190 { .name
= "nConfig", .offset
= 96, .bytes
= 2, },
191 { .name
= "nRippleCfg", .offset
= 98, .bytes
= 2, },
192 { .name
= "nMiscCfg", .offset
= 100, .bytes
= 2, },
193 { .name
= "nDesignCap", .offset
= 102, .bytes
= 2, },
194 { .name
= "nHibCfg", .offset
= 104, .bytes
= 2, },
195 { .name
= "nPackCfg", .offset
= 106, .bytes
= 2, },
196 { .name
= "nRelaxCfg", .offset
= 108, .bytes
= 2, },
197 { .name
= "nConvgCfg", .offset
= 110, .bytes
= 2, },
198 { .name
= "nNVCfg0", .offset
= 112, .bytes
= 2, },
199 { .name
= "nNVCfg1", .offset
= 114, .bytes
= 2, },
200 { .name
= "nNVCfg2", .offset
= 116, .bytes
= 2, },
201 { .name
= "nSBSCfg", .offset
= 118, .bytes
= 2, },
202 { .name
= "nROMID0", .offset
= 120, .bytes
= 2, },
203 { .name
= "nROMID1", .offset
= 122, .bytes
= 2, },
204 { .name
= "nROMID2", .offset
= 124, .bytes
= 2, },
205 { .name
= "nROMID3", .offset
= 126, .bytes
= 2, },
207 { .name
= "nVAlrtTh", .offset
= 128, .bytes
= 2, },
208 { .name
= "nTAlrtTh", .offset
= 130, .bytes
= 2, },
209 { .name
= "nSAlrtTh", .offset
= 132, .bytes
= 2, },
210 { .name
= "nIAlrtTh", .offset
= 134, .bytes
= 2, },
211 { .name
= "nUser1C4", .offset
= 136, .bytes
= 2, },
212 { .name
= "nUser1C5", .offset
= 138, .bytes
= 2, },
213 { .name
= "nFullSOCThr", .offset
= 140, .bytes
= 2, },
214 { .name
= "nTTFCfg", .offset
= 142, .bytes
= 2, },
215 { .name
= "nCGain", .offset
= 144, .bytes
= 2, },
216 { .name
= "nTCurve", .offset
= 146, .bytes
= 2, },
217 { .name
= "nTGain", .offset
= 148, .bytes
= 2, },
218 { .name
= "nTOff", .offset
= 150, .bytes
= 2, },
219 { .name
= "nManfctrName0", .offset
= 152, .bytes
= 2, },
220 { .name
= "nManfctrName1", .offset
= 154, .bytes
= 2, },
221 { .name
= "nManfctrName2", .offset
= 156, .bytes
= 2, },
222 { .name
= "nRSense", .offset
= 158, .bytes
= 2, },
224 { .name
= "nUser1D0", .offset
= 160, .bytes
= 2, },
225 { .name
= "nUser1D1", .offset
= 162, .bytes
= 2, },
226 { .name
= "nAgeFcCfg", .offset
= 164, .bytes
= 2, },
227 { .name
= "nDesignVoltage", .offset
= 166, .bytes
= 2, },
228 { .name
= "nUser1D4", .offset
= 168, .bytes
= 2, },
229 { .name
= "nRFastVShdn", .offset
= 170, .bytes
= 2, },
230 { .name
= "nManfctrDate", .offset
= 172, .bytes
= 2, },
231 { .name
= "nFirstUsed", .offset
= 174, .bytes
= 2, },
232 { .name
= "nSerialNumber0", .offset
= 176, .bytes
= 2, },
233 { .name
= "nSerialNumber1", .offset
= 178, .bytes
= 2, },
234 { .name
= "nSerialNumber2", .offset
= 180, .bytes
= 2, },
235 { .name
= "nDeviceName0", .offset
= 182, .bytes
= 2, },
236 { .name
= "nDeviceName1", .offset
= 184, .bytes
= 2, },
237 { .name
= "nDeviceName2", .offset
= 186, .bytes
= 2, },
238 { .name
= "nDeviceName3", .offset
= 188, .bytes
= 2, },
239 { .name
= "nDeviceName4", .offset
= 190, .bytes
= 2, },
242 static const enum power_supply_property max1720x_battery_props
[] = {
243 POWER_SUPPLY_PROP_PRESENT
,
244 POWER_SUPPLY_PROP_CAPACITY
,
245 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
246 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN
,
247 POWER_SUPPLY_PROP_CHARGE_AVG
,
248 POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG
,
249 POWER_SUPPLY_PROP_TIME_TO_FULL_AVG
,
250 POWER_SUPPLY_PROP_TEMP
,
251 POWER_SUPPLY_PROP_CURRENT_NOW
,
252 POWER_SUPPLY_PROP_CURRENT_AVG
,
253 POWER_SUPPLY_PROP_MODEL_NAME
,
254 POWER_SUPPLY_PROP_MANUFACTURER
,
257 /* Convert regs value to power_supply units */
259 static int max172xx_time_to_ps(unsigned int reg
)
261 return reg
* 5625 / 1000; /* in sec. */
264 static int max172xx_percent_to_ps(unsigned int reg
)
266 return reg
/ 256; /* in percent from 0 to 100 */
269 static int max172xx_voltage_to_ps(unsigned int reg
)
271 return reg
* 1250; /* in uV */
274 static int max172xx_capacity_to_ps(unsigned int reg
)
276 return reg
* 500; /* in uAh */
280 * Current and temperature is signed values, so unsigned regs
281 * value must be converted to signed type
284 static int max172xx_temperature_to_ps(unsigned int reg
)
286 int val
= (int16_t)reg
;
288 return val
* 10 / 256; /* in tenths of deg. C */
292 * Calculating current registers resolution:
294 * RSense stored in 10^-5 Ohm, so mesaurment voltage must be
295 * in 10^-11 Volts for get current in uA.
296 * 16 bit current reg fullscale +/-51.2mV is 102400 uV.
297 * So: 102400 / 65535 * 10^5 = 156252
299 static int max172xx_current_to_voltage(unsigned int reg
)
301 int val
= (int16_t)reg
;
306 static int max1720x_battery_get_property(struct power_supply
*psy
,
307 enum power_supply_property psp
,
308 union power_supply_propval
*val
)
310 struct max1720x_device_info
*info
= power_supply_get_drvdata(psy
);
311 unsigned int reg_val
;
315 case POWER_SUPPLY_PROP_PRESENT
:
317 * POWER_SUPPLY_PROP_PRESENT will always readable via
318 * sysfs interface. Value return 0 if battery not
319 * present or unaccesable via I2c.
321 ret
= regmap_read(info
->regmap
, MAX172XX_STATUS
, ®_val
);
327 val
->intval
= !FIELD_GET(MAX172XX_STATUS_BAT_ABSENT
, reg_val
);
329 case POWER_SUPPLY_PROP_CAPACITY
:
330 ret
= regmap_read(info
->regmap
, MAX172XX_REPSOC
, ®_val
);
331 val
->intval
= max172xx_percent_to_ps(reg_val
);
333 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
334 ret
= regmap_read(info
->regmap
, MAX172XX_BATT
, ®_val
);
335 val
->intval
= max172xx_voltage_to_ps(reg_val
);
337 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN
:
338 ret
= regmap_read(info
->regmap
, MAX172XX_DESIGN_CAP
, ®_val
);
339 val
->intval
= max172xx_capacity_to_ps(reg_val
);
341 case POWER_SUPPLY_PROP_CHARGE_AVG
:
342 ret
= regmap_read(info
->regmap
, MAX172XX_REPCAP
, ®_val
);
343 val
->intval
= max172xx_capacity_to_ps(reg_val
);
345 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG
:
346 ret
= regmap_read(info
->regmap
, MAX172XX_TTE
, ®_val
);
347 val
->intval
= max172xx_time_to_ps(reg_val
);
349 case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG
:
350 ret
= regmap_read(info
->regmap
, MAX172XX_TTF
, ®_val
);
351 val
->intval
= max172xx_time_to_ps(reg_val
);
353 case POWER_SUPPLY_PROP_TEMP
:
354 ret
= regmap_read(info
->regmap
, MAX172XX_TEMP
, ®_val
);
355 val
->intval
= max172xx_temperature_to_ps(reg_val
);
357 case POWER_SUPPLY_PROP_CURRENT_NOW
:
358 ret
= regmap_read(info
->regmap
, MAX172XX_CURRENT
, ®_val
);
359 val
->intval
= max172xx_current_to_voltage(reg_val
) / info
->rsense
;
361 case POWER_SUPPLY_PROP_CURRENT_AVG
:
362 ret
= regmap_read(info
->regmap
, MAX172XX_AVG_CURRENT
, ®_val
);
363 val
->intval
= max172xx_current_to_voltage(reg_val
) / info
->rsense
;
365 case POWER_SUPPLY_PROP_MODEL_NAME
:
366 ret
= regmap_read(info
->regmap
, MAX172XX_DEV_NAME
, ®_val
);
367 reg_val
= FIELD_GET(MAX172XX_DEV_NAME_TYPE_MASK
, reg_val
);
368 if (reg_val
== MAX172XX_DEV_NAME_TYPE_MAX17201
)
369 val
->strval
= max17201_model
;
370 else if (reg_val
== MAX172XX_DEV_NAME_TYPE_MAX17205
)
371 val
->strval
= max17205_model
;
375 case POWER_SUPPLY_PROP_MANUFACTURER
:
376 val
->strval
= max1720x_manufacturer
;
386 int max1720x_nvmem_reg_read(void *priv
, unsigned int off
, void *val
, size_t len
)
388 struct max1720x_device_info
*info
= priv
;
389 unsigned int reg
= MAX1720X_NXTABLE0
+ (off
/ 2);
391 return regmap_bulk_read(info
->regmap_nv
, reg
, val
, len
/ 2);
394 static void max1720x_unregister_ancillary(void *data
)
396 struct max1720x_device_info
*info
= data
;
398 i2c_unregister_device(info
->ancillary
);
401 static int max1720x_probe_nvmem(struct i2c_client
*client
,
402 struct max1720x_device_info
*info
)
404 struct device
*dev
= &client
->dev
;
405 struct nvmem_config nvmem_config
= {
407 .name
= "max1720x_nvmem",
408 .cells
= max1720x_nvmem_cells
,
409 .ncells
= ARRAY_SIZE(max1720x_nvmem_cells
),
412 .reg_read
= max1720x_nvmem_reg_read
,
413 .size
= ARRAY_SIZE(max1720x_nvmem_cells
) * 2,
418 struct nvmem_device
*nvmem
;
422 info
->ancillary
= i2c_new_ancillary_device(client
, "nvmem", 0xb);
423 if (IS_ERR(info
->ancillary
)) {
424 dev_err(dev
, "Failed to initialize ancillary i2c device\n");
425 return PTR_ERR(info
->ancillary
);
428 ret
= devm_add_action_or_reset(dev
, max1720x_unregister_ancillary
, info
);
430 dev_err(dev
, "Failed to add unregister callback\n");
434 info
->regmap_nv
= devm_regmap_init_i2c(info
->ancillary
,
435 &max1720x_nvmem_regmap_cfg
);
436 if (IS_ERR(info
->regmap_nv
)) {
437 dev_err(dev
, "regmap initialization of nvmem failed\n");
438 return PTR_ERR(info
->regmap_nv
);
441 ret
= regmap_read(info
->regmap_nv
, MAX1720X_NRSENSE
, &val
);
443 dev_err(dev
, "Failed to read sense resistor value\n");
449 dev_warn(dev
, "RSense not calibrated, set 10 mOhms!\n");
450 info
->rsense
= 1000; /* in regs in 10^-5 */
453 nvmem
= devm_nvmem_register(dev
, &nvmem_config
);
455 dev_err(dev
, "Could not register nvmem!");
456 return PTR_ERR(nvmem
);
462 static const struct power_supply_desc max1720x_bat_desc
= {
465 .type
= POWER_SUPPLY_TYPE_BATTERY
,
466 .properties
= max1720x_battery_props
,
467 .num_properties
= ARRAY_SIZE(max1720x_battery_props
),
468 .get_property
= max1720x_battery_get_property
,
471 static int max1720x_probe(struct i2c_client
*client
)
473 struct power_supply_config psy_cfg
= {};
474 struct device
*dev
= &client
->dev
;
475 struct max1720x_device_info
*info
;
476 struct power_supply
*bat
;
479 info
= devm_kzalloc(dev
, sizeof(*info
), GFP_KERNEL
);
483 psy_cfg
.drv_data
= info
;
484 psy_cfg
.fwnode
= dev_fwnode(dev
);
485 i2c_set_clientdata(client
, info
);
486 info
->regmap
= devm_regmap_init_i2c(client
, &max1720x_regmap_cfg
);
487 if (IS_ERR(info
->regmap
))
488 return dev_err_probe(dev
, PTR_ERR(info
->regmap
),
489 "regmap initialization failed\n");
491 ret
= max1720x_probe_nvmem(client
, info
);
493 return dev_err_probe(dev
, ret
, "Failed to probe nvmem\n");
495 bat
= devm_power_supply_register(dev
, &max1720x_bat_desc
, &psy_cfg
);
497 return dev_err_probe(dev
, PTR_ERR(bat
),
498 "Failed to register power supply\n");
503 static const struct of_device_id max1720x_of_match
[] = {
504 { .compatible
= "maxim,max17201" },
507 MODULE_DEVICE_TABLE(of
, max1720x_of_match
);
509 static struct i2c_driver max1720x_i2c_driver
= {
512 .of_match_table
= max1720x_of_match
,
514 .probe
= max1720x_probe
,
516 module_i2c_driver(max1720x_i2c_driver
);
518 MODULE_LICENSE("GPL");
519 MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>");
520 MODULE_DESCRIPTION("Maxim MAX17201/MAX17205 Fuel Gauge IC driver");