| 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Fuel gauge driver for Maxim 17201/17205 |
| 4 | * |
| 5 | * based on max1721x_battery.c |
| 6 | * |
| 7 | * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH |
| 8 | */ |
| 9 | |
| 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> |
| 16 | |
| 17 | #include <linux/unaligned.h> |
| 18 | |
| 19 | /* SBS compliant registers */ |
| 20 | #define MAX172XX_TEMP1 0x34 |
| 21 | #define MAX172XX_INT_TEMP 0x35 |
| 22 | #define MAX172XX_TEMP2 0x3B |
| 23 | |
| 24 | /* Nonvolatile registers */ |
| 25 | #define MAX1720X_NXTABLE0 0x80 |
| 26 | #define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */ |
| 27 | #define MAX1720X_NDEVICE_NAME4 0xDF |
| 28 | |
| 29 | /* ModelGauge m5 */ |
| 30 | #define MAX172XX_STATUS 0x00 /* Status */ |
| 31 | #define MAX172XX_STATUS_BAT_ABSENT BIT(3) /* Battery absent */ |
| 32 | #define MAX172XX_STATUS_IMX BIT(6) /* Maximum Current Alert Threshold Exceeded */ |
| 33 | #define MAX172XX_STATUS_VMN BIT(8) /* Minimum Voltage Alert Threshold Exceeded */ |
| 34 | #define MAX172XX_STATUS_TMN BIT(9) /* Minimum Temperature Alert Threshold Exceeded */ |
| 35 | #define MAX172XX_STATUS_VMX BIT(12) /* Maximum Voltage Alert Threshold Exceeded */ |
| 36 | #define MAX172XX_STATUS_TMX BIT(13) /* Maximum Temperature Alert Threshold Exceeded */ |
| 37 | #define MAX172XX_REPCAP 0x05 /* Average capacity */ |
| 38 | #define MAX172XX_REPSOC 0x06 /* Percentage of charge */ |
| 39 | #define MAX172XX_TEMP 0x08 /* Temperature */ |
| 40 | #define MAX172XX_CURRENT 0x0A /* Actual current */ |
| 41 | #define MAX172XX_AVG_CURRENT 0x0B /* Average current */ |
| 42 | #define MAX172XX_FULL_CAP 0x10 /* Calculated full capacity */ |
| 43 | #define MAX172XX_TTE 0x11 /* Time to empty */ |
| 44 | #define MAX172XX_AVG_TA 0x16 /* Average temperature */ |
| 45 | #define MAX172XX_CYCLES 0x17 |
| 46 | #define MAX172XX_DESIGN_CAP 0x18 /* Design capacity */ |
| 47 | #define MAX172XX_AVG_VCELL 0x19 |
| 48 | #define MAX172XX_TTF 0x20 /* Time to full */ |
| 49 | #define MAX172XX_DEV_NAME 0x21 /* Device name */ |
| 50 | #define MAX172XX_DEV_NAME_TYPE_MASK GENMASK(3, 0) |
| 51 | #define MAX172XX_DEV_NAME_TYPE_MAX17201 BIT(0) |
| 52 | #define MAX172XX_DEV_NAME_TYPE_MAX17205 (BIT(0) | BIT(2)) |
| 53 | #define MAX172XX_QR_TABLE10 0x22 |
| 54 | #define MAX172XX_BATT 0xDA /* Battery voltage */ |
| 55 | #define MAX172XX_ATAVCAP 0xDF |
| 56 | |
| 57 | static const char *const max1720x_manufacturer = "Maxim Integrated" ; |
| 58 | static const char *const max17201_model = "MAX17201" ; |
| 59 | static const char *const max17205_model = "MAX17205" ; |
| 60 | |
| 61 | struct max1720x_device_info { |
| 62 | struct regmap *regmap; |
| 63 | struct regmap *regmap_nv; |
| 64 | struct i2c_client *ancillary; |
| 65 | int rsense; |
| 66 | }; |
| 67 | |
| 68 | /* |
| 69 | * Model Gauge M5 Algorithm output register |
| 70 | * Volatile data (must not be cached) |
| 71 | */ |
| 72 | static const struct regmap_range max1720x_volatile_allow[] = { |
| 73 | regmap_reg_range(MAX172XX_STATUS, MAX172XX_CYCLES), |
| 74 | regmap_reg_range(MAX172XX_AVG_VCELL, MAX172XX_TTF), |
| 75 | regmap_reg_range(MAX172XX_QR_TABLE10, MAX172XX_ATAVCAP), |
| 76 | }; |
| 77 | |
| 78 | static const struct regmap_range max1720x_readable_allow[] = { |
| 79 | regmap_reg_range(MAX172XX_STATUS, MAX172XX_ATAVCAP), |
| 80 | }; |
| 81 | |
| 82 | static const struct regmap_range max1720x_readable_deny[] = { |
| 83 | /* unused registers */ |
| 84 | regmap_reg_range(0x24, 0x26), |
| 85 | regmap_reg_range(0x30, 0x31), |
| 86 | regmap_reg_range(0x33, 0x34), |
| 87 | regmap_reg_range(0x37, 0x37), |
| 88 | regmap_reg_range(0x3B, 0x3C), |
| 89 | regmap_reg_range(0x40, 0x41), |
| 90 | regmap_reg_range(0x43, 0x44), |
| 91 | regmap_reg_range(0x47, 0x49), |
| 92 | regmap_reg_range(0x4B, 0x4C), |
| 93 | regmap_reg_range(0x4E, 0xAF), |
| 94 | regmap_reg_range(0xB1, 0xB3), |
| 95 | regmap_reg_range(0xB5, 0xB7), |
| 96 | regmap_reg_range(0xBF, 0xD0), |
| 97 | regmap_reg_range(0xDB, 0xDB), |
| 98 | regmap_reg_range(0xE0, 0xFF), |
| 99 | }; |
| 100 | |
| 101 | static const struct regmap_access_table max1720x_readable_regs = { |
| 102 | .yes_ranges = max1720x_readable_allow, |
| 103 | .n_yes_ranges = ARRAY_SIZE(max1720x_readable_allow), |
| 104 | .no_ranges = max1720x_readable_deny, |
| 105 | .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), |
| 106 | }; |
| 107 | |
| 108 | static const struct regmap_access_table max1720x_volatile_regs = { |
| 109 | .yes_ranges = max1720x_volatile_allow, |
| 110 | .n_yes_ranges = ARRAY_SIZE(max1720x_volatile_allow), |
| 111 | .no_ranges = max1720x_readable_deny, |
| 112 | .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), |
| 113 | }; |
| 114 | |
| 115 | static const struct regmap_config max1720x_regmap_cfg = { |
| 116 | .reg_bits = 8, |
| 117 | .val_bits = 16, |
| 118 | .max_register = MAX172XX_ATAVCAP, |
| 119 | .val_format_endian = REGMAP_ENDIAN_LITTLE, |
| 120 | .rd_table = &max1720x_readable_regs, |
| 121 | .volatile_table = &max1720x_volatile_regs, |
| 122 | .cache_type = REGCACHE_MAPLE, |
| 123 | }; |
| 124 | |
| 125 | static const struct regmap_range max1720x_nvmem_allow[] = { |
| 126 | regmap_reg_range(MAX172XX_TEMP1, MAX172XX_INT_TEMP), |
| 127 | regmap_reg_range(MAX172XX_TEMP2, MAX172XX_TEMP2), |
| 128 | regmap_reg_range(MAX1720X_NXTABLE0, MAX1720X_NDEVICE_NAME4), |
| 129 | }; |
| 130 | |
| 131 | static const struct regmap_range max1720x_nvmem_deny[] = { |
| 132 | regmap_reg_range(0x00, 0x33), |
| 133 | regmap_reg_range(0x36, 0x3A), |
| 134 | regmap_reg_range(0x3C, 0x7F), |
| 135 | regmap_reg_range(0xE0, 0xFF), |
| 136 | }; |
| 137 | |
| 138 | static const struct regmap_access_table max1720x_nvmem_regs = { |
| 139 | .yes_ranges = max1720x_nvmem_allow, |
| 140 | .n_yes_ranges = ARRAY_SIZE(max1720x_nvmem_allow), |
| 141 | .no_ranges = max1720x_nvmem_deny, |
| 142 | .n_no_ranges = ARRAY_SIZE(max1720x_nvmem_deny), |
| 143 | }; |
| 144 | |
| 145 | static const struct regmap_config max1720x_nvmem_regmap_cfg = { |
| 146 | .reg_bits = 8, |
| 147 | .val_bits = 16, |
| 148 | .max_register = MAX1720X_NDEVICE_NAME4, |
| 149 | .val_format_endian = REGMAP_ENDIAN_LITTLE, |
| 150 | .rd_table = &max1720x_nvmem_regs, |
| 151 | }; |
| 152 | |
| 153 | static const struct nvmem_cell_info max1720x_nvmem_cells[] = { |
| 154 | { .name = "nXTable0" , .offset = 0, .bytes = 2, }, |
| 155 | { .name = "nXTable1" , .offset = 2, .bytes = 2, }, |
| 156 | { .name = "nXTable2" , .offset = 4, .bytes = 2, }, |
| 157 | { .name = "nXTable3" , .offset = 6, .bytes = 2, }, |
| 158 | { .name = "nXTable4" , .offset = 8, .bytes = 2, }, |
| 159 | { .name = "nXTable5" , .offset = 10, .bytes = 2, }, |
| 160 | { .name = "nXTable6" , .offset = 12, .bytes = 2, }, |
| 161 | { .name = "nXTable7" , .offset = 14, .bytes = 2, }, |
| 162 | { .name = "nXTable8" , .offset = 16, .bytes = 2, }, |
| 163 | { .name = "nXTable9" , .offset = 18, .bytes = 2, }, |
| 164 | { .name = "nXTable10" , .offset = 20, .bytes = 2, }, |
| 165 | { .name = "nXTable11" , .offset = 22, .bytes = 2, }, |
| 166 | { .name = "nUser18C" , .offset = 24, .bytes = 2, }, |
| 167 | { .name = "nUser18D" , .offset = 26, .bytes = 2, }, |
| 168 | { .name = "nODSCTh" , .offset = 28, .bytes = 2, }, |
| 169 | { .name = "nODSCCfg" , .offset = 30, .bytes = 2, }, |
| 170 | |
| 171 | { .name = "nOCVTable0" , .offset = 32, .bytes = 2, }, |
| 172 | { .name = "nOCVTable1" , .offset = 34, .bytes = 2, }, |
| 173 | { .name = "nOCVTable2" , .offset = 36, .bytes = 2, }, |
| 174 | { .name = "nOCVTable3" , .offset = 38, .bytes = 2, }, |
| 175 | { .name = "nOCVTable4" , .offset = 40, .bytes = 2, }, |
| 176 | { .name = "nOCVTable5" , .offset = 42, .bytes = 2, }, |
| 177 | { .name = "nOCVTable6" , .offset = 44, .bytes = 2, }, |
| 178 | { .name = "nOCVTable7" , .offset = 46, .bytes = 2, }, |
| 179 | { .name = "nOCVTable8" , .offset = 48, .bytes = 2, }, |
| 180 | { .name = "nOCVTable9" , .offset = 50, .bytes = 2, }, |
| 181 | { .name = "nOCVTable10" , .offset = 52, .bytes = 2, }, |
| 182 | { .name = "nOCVTable11" , .offset = 54, .bytes = 2, }, |
| 183 | { .name = "nIChgTerm" , .offset = 56, .bytes = 2, }, |
| 184 | { .name = "nFilterCfg" , .offset = 58, .bytes = 2, }, |
| 185 | { .name = "nVEmpty" , .offset = 60, .bytes = 2, }, |
| 186 | { .name = "nLearnCfg" , .offset = 62, .bytes = 2, }, |
| 187 | |
| 188 | { .name = "nQRTable00" , .offset = 64, .bytes = 2, }, |
| 189 | { .name = "nQRTable10" , .offset = 66, .bytes = 2, }, |
| 190 | { .name = "nQRTable20" , .offset = 68, .bytes = 2, }, |
| 191 | { .name = "nQRTable30" , .offset = 70, .bytes = 2, }, |
| 192 | { .name = "nCycles" , .offset = 72, .bytes = 2, }, |
| 193 | { .name = "nFullCapNom" , .offset = 74, .bytes = 2, }, |
| 194 | { .name = "nRComp0" , .offset = 76, .bytes = 2, }, |
| 195 | { .name = "nTempCo" , .offset = 78, .bytes = 2, }, |
| 196 | { .name = "nIAvgEmpty" , .offset = 80, .bytes = 2, }, |
| 197 | { .name = "nFullCapRep" , .offset = 82, .bytes = 2, }, |
| 198 | { .name = "nVoltTemp" , .offset = 84, .bytes = 2, }, |
| 199 | { .name = "nMaxMinCurr" , .offset = 86, .bytes = 2, }, |
| 200 | { .name = "nMaxMinVolt" , .offset = 88, .bytes = 2, }, |
| 201 | { .name = "nMaxMinTemp" , .offset = 90, .bytes = 2, }, |
| 202 | { .name = "nSOC" , .offset = 92, .bytes = 2, }, |
| 203 | { .name = "nTimerH" , .offset = 94, .bytes = 2, }, |
| 204 | |
| 205 | { .name = "nConfig" , .offset = 96, .bytes = 2, }, |
| 206 | { .name = "nRippleCfg" , .offset = 98, .bytes = 2, }, |
| 207 | { .name = "nMiscCfg" , .offset = 100, .bytes = 2, }, |
| 208 | { .name = "nDesignCap" , .offset = 102, .bytes = 2, }, |
| 209 | { .name = "nHibCfg" , .offset = 104, .bytes = 2, }, |
| 210 | { .name = "nPackCfg" , .offset = 106, .bytes = 2, }, |
| 211 | { .name = "nRelaxCfg" , .offset = 108, .bytes = 2, }, |
| 212 | { .name = "nConvgCfg" , .offset = 110, .bytes = 2, }, |
| 213 | { .name = "nNVCfg0" , .offset = 112, .bytes = 2, }, |
| 214 | { .name = "nNVCfg1" , .offset = 114, .bytes = 2, }, |
| 215 | { .name = "nNVCfg2" , .offset = 116, .bytes = 2, }, |
| 216 | { .name = "nSBSCfg" , .offset = 118, .bytes = 2, }, |
| 217 | { .name = "nROMID0" , .offset = 120, .bytes = 2, }, |
| 218 | { .name = "nROMID1" , .offset = 122, .bytes = 2, }, |
| 219 | { .name = "nROMID2" , .offset = 124, .bytes = 2, }, |
| 220 | { .name = "nROMID3" , .offset = 126, .bytes = 2, }, |
| 221 | |
| 222 | { .name = "nVAlrtTh" , .offset = 128, .bytes = 2, }, |
| 223 | { .name = "nTAlrtTh" , .offset = 130, .bytes = 2, }, |
| 224 | { .name = "nSAlrtTh" , .offset = 132, .bytes = 2, }, |
| 225 | { .name = "nIAlrtTh" , .offset = 134, .bytes = 2, }, |
| 226 | { .name = "nUser1C4" , .offset = 136, .bytes = 2, }, |
| 227 | { .name = "nUser1C5" , .offset = 138, .bytes = 2, }, |
| 228 | { .name = "nFullSOCThr" , .offset = 140, .bytes = 2, }, |
| 229 | { .name = "nTTFCfg" , .offset = 142, .bytes = 2, }, |
| 230 | { .name = "nCGain" , .offset = 144, .bytes = 2, }, |
| 231 | { .name = "nTCurve" , .offset = 146, .bytes = 2, }, |
| 232 | { .name = "nTGain" , .offset = 148, .bytes = 2, }, |
| 233 | { .name = "nTOff" , .offset = 150, .bytes = 2, }, |
| 234 | { .name = "nManfctrName0" , .offset = 152, .bytes = 2, }, |
| 235 | { .name = "nManfctrName1" , .offset = 154, .bytes = 2, }, |
| 236 | { .name = "nManfctrName2" , .offset = 156, .bytes = 2, }, |
| 237 | { .name = "nRSense" , .offset = 158, .bytes = 2, }, |
| 238 | |
| 239 | { .name = "nUser1D0" , .offset = 160, .bytes = 2, }, |
| 240 | { .name = "nUser1D1" , .offset = 162, .bytes = 2, }, |
| 241 | { .name = "nAgeFcCfg" , .offset = 164, .bytes = 2, }, |
| 242 | { .name = "nDesignVoltage" , .offset = 166, .bytes = 2, }, |
| 243 | { .name = "nUser1D4" , .offset = 168, .bytes = 2, }, |
| 244 | { .name = "nRFastVShdn" , .offset = 170, .bytes = 2, }, |
| 245 | { .name = "nManfctrDate" , .offset = 172, .bytes = 2, }, |
| 246 | { .name = "nFirstUsed" , .offset = 174, .bytes = 2, }, |
| 247 | { .name = "nSerialNumber0" , .offset = 176, .bytes = 2, }, |
| 248 | { .name = "nSerialNumber1" , .offset = 178, .bytes = 2, }, |
| 249 | { .name = "nSerialNumber2" , .offset = 180, .bytes = 2, }, |
| 250 | { .name = "nDeviceName0" , .offset = 182, .bytes = 2, }, |
| 251 | { .name = "nDeviceName1" , .offset = 184, .bytes = 2, }, |
| 252 | { .name = "nDeviceName2" , .offset = 186, .bytes = 2, }, |
| 253 | { .name = "nDeviceName3" , .offset = 188, .bytes = 2, }, |
| 254 | { .name = "nDeviceName4" , .offset = 190, .bytes = 2, }, |
| 255 | }; |
| 256 | |
| 257 | static const enum power_supply_property max1720x_battery_props[] = { |
| 258 | POWER_SUPPLY_PROP_HEALTH, |
| 259 | POWER_SUPPLY_PROP_PRESENT, |
| 260 | POWER_SUPPLY_PROP_CAPACITY, |
| 261 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| 262 | POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
| 263 | POWER_SUPPLY_PROP_CHARGE_AVG, |
| 264 | POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, |
| 265 | POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, |
| 266 | POWER_SUPPLY_PROP_TEMP, |
| 267 | POWER_SUPPLY_PROP_CURRENT_NOW, |
| 268 | POWER_SUPPLY_PROP_CURRENT_AVG, |
| 269 | POWER_SUPPLY_PROP_CHARGE_FULL, |
| 270 | POWER_SUPPLY_PROP_MODEL_NAME, |
| 271 | POWER_SUPPLY_PROP_MANUFACTURER, |
| 272 | }; |
| 273 | |
| 274 | /* Convert regs value to power_supply units */ |
| 275 | |
| 276 | static int max172xx_time_to_ps(unsigned int reg) |
| 277 | { |
| 278 | return reg * 5625 / 1000; /* in sec. */ |
| 279 | } |
| 280 | |
| 281 | static int max172xx_percent_to_ps(unsigned int reg) |
| 282 | { |
| 283 | return reg / 256; /* in percent from 0 to 100 */ |
| 284 | } |
| 285 | |
| 286 | static int max172xx_voltage_to_ps(unsigned int reg) |
| 287 | { |
| 288 | return reg * 1250; /* in uV */ |
| 289 | } |
| 290 | |
| 291 | static int max172xx_capacity_to_ps(unsigned int reg, |
| 292 | struct max1720x_device_info *info) |
| 293 | { |
| 294 | return reg * (500000 / info->rsense); /* in uAh */ |
| 295 | } |
| 296 | |
| 297 | /* |
| 298 | * Current and temperature is signed values, so unsigned regs |
| 299 | * value must be converted to signed type |
| 300 | */ |
| 301 | |
| 302 | static int max172xx_temperature_to_ps(unsigned int reg) |
| 303 | { |
| 304 | int val = (int16_t)reg; |
| 305 | |
| 306 | return val * 10 / 256; /* in tenths of deg. C */ |
| 307 | } |
| 308 | |
| 309 | /* |
| 310 | * Calculating current registers resolution: |
| 311 | * |
| 312 | * RSense stored in 10^-5 Ohm, so measurement voltage must be |
| 313 | * in 10^-11 Volts for get current in uA. |
| 314 | * 16 bit current reg fullscale +/-51.2mV is 102400 uV. |
| 315 | * So: 102400 / 65535 * 10^5 = 156252 |
| 316 | */ |
| 317 | static int max172xx_current_to_voltage(unsigned int reg) |
| 318 | { |
| 319 | int val = (int16_t)reg; |
| 320 | |
| 321 | return val * 156252; |
| 322 | } |
| 323 | |
| 324 | static int max172xx_battery_health(struct max1720x_device_info *info, |
| 325 | unsigned int *health) |
| 326 | { |
| 327 | unsigned int status; |
| 328 | int ret; |
| 329 | |
| 330 | ret = regmap_read(map: info->regmap, MAX172XX_STATUS, val: &status); |
| 331 | if (ret < 0) |
| 332 | return ret; |
| 333 | |
| 334 | if (status & MAX172XX_STATUS_VMN) |
| 335 | *health = POWER_SUPPLY_HEALTH_DEAD; |
| 336 | else if (status & MAX172XX_STATUS_VMX) |
| 337 | *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| 338 | else if (status & MAX172XX_STATUS_TMN) |
| 339 | *health = POWER_SUPPLY_HEALTH_COLD; |
| 340 | else if (status & MAX172XX_STATUS_TMX) |
| 341 | *health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| 342 | else if (status & MAX172XX_STATUS_IMX) |
| 343 | *health = POWER_SUPPLY_HEALTH_OVERCURRENT; |
| 344 | else |
| 345 | *health = POWER_SUPPLY_HEALTH_GOOD; |
| 346 | |
| 347 | /* Clear events which are not self-clearing to detect next events */ |
| 348 | if (status > 0 && status != MAX172XX_STATUS_IMX) { |
| 349 | ret = regmap_set_bits(map: info->regmap, MAX172XX_STATUS, |
| 350 | MAX172XX_STATUS_VMN | |
| 351 | MAX172XX_STATUS_VMX | |
| 352 | MAX172XX_STATUS_TMN | |
| 353 | MAX172XX_STATUS_TMX); |
| 354 | if (ret < 0) |
| 355 | return ret; |
| 356 | } |
| 357 | |
| 358 | return 0; |
| 359 | } |
| 360 | |
| 361 | static int max1720x_battery_get_property(struct power_supply *psy, |
| 362 | enum power_supply_property psp, |
| 363 | union power_supply_propval *val) |
| 364 | { |
| 365 | struct max1720x_device_info *info = power_supply_get_drvdata(psy); |
| 366 | unsigned int reg_val; |
| 367 | int ret = 0; |
| 368 | |
| 369 | switch (psp) { |
| 370 | case POWER_SUPPLY_PROP_HEALTH: |
| 371 | ret = max172xx_battery_health(info, health: ®_val); |
| 372 | val->intval = reg_val; |
| 373 | break; |
| 374 | case POWER_SUPPLY_PROP_PRESENT: |
| 375 | /* |
| 376 | * POWER_SUPPLY_PROP_PRESENT will always readable via |
| 377 | * sysfs interface. Value return 0 if battery not |
| 378 | * present or unaccesable via I2c. |
| 379 | */ |
| 380 | ret = regmap_read(map: info->regmap, MAX172XX_STATUS, val: ®_val); |
| 381 | if (ret < 0) { |
| 382 | val->intval = 0; |
| 383 | return 0; |
| 384 | } |
| 385 | |
| 386 | val->intval = !FIELD_GET(MAX172XX_STATUS_BAT_ABSENT, reg_val); |
| 387 | break; |
| 388 | case POWER_SUPPLY_PROP_CAPACITY: |
| 389 | ret = regmap_read(map: info->regmap, MAX172XX_REPSOC, val: ®_val); |
| 390 | val->intval = max172xx_percent_to_ps(reg: reg_val); |
| 391 | break; |
| 392 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| 393 | ret = regmap_read(map: info->regmap, MAX172XX_BATT, val: ®_val); |
| 394 | val->intval = max172xx_voltage_to_ps(reg: reg_val); |
| 395 | break; |
| 396 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| 397 | ret = regmap_read(map: info->regmap, MAX172XX_DESIGN_CAP, val: ®_val); |
| 398 | val->intval = max172xx_capacity_to_ps(reg: reg_val, info); |
| 399 | break; |
| 400 | case POWER_SUPPLY_PROP_CHARGE_AVG: |
| 401 | ret = regmap_read(map: info->regmap, MAX172XX_REPCAP, val: ®_val); |
| 402 | val->intval = max172xx_capacity_to_ps(reg: reg_val, info); |
| 403 | break; |
| 404 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: |
| 405 | ret = regmap_read(map: info->regmap, MAX172XX_TTE, val: ®_val); |
| 406 | val->intval = max172xx_time_to_ps(reg: reg_val); |
| 407 | break; |
| 408 | case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: |
| 409 | ret = regmap_read(map: info->regmap, MAX172XX_TTF, val: ®_val); |
| 410 | val->intval = max172xx_time_to_ps(reg: reg_val); |
| 411 | break; |
| 412 | case POWER_SUPPLY_PROP_TEMP: |
| 413 | ret = regmap_read(map: info->regmap, MAX172XX_TEMP, val: ®_val); |
| 414 | val->intval = max172xx_temperature_to_ps(reg: reg_val); |
| 415 | break; |
| 416 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
| 417 | ret = regmap_read(map: info->regmap, MAX172XX_CURRENT, val: ®_val); |
| 418 | val->intval = max172xx_current_to_voltage(reg: reg_val) / info->rsense; |
| 419 | break; |
| 420 | case POWER_SUPPLY_PROP_CURRENT_AVG: |
| 421 | ret = regmap_read(map: info->regmap, MAX172XX_AVG_CURRENT, val: ®_val); |
| 422 | val->intval = max172xx_current_to_voltage(reg: reg_val) / info->rsense; |
| 423 | break; |
| 424 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
| 425 | ret = regmap_read(map: info->regmap, MAX172XX_FULL_CAP, val: ®_val); |
| 426 | val->intval = max172xx_capacity_to_ps(reg: reg_val, info); |
| 427 | break; |
| 428 | case POWER_SUPPLY_PROP_MODEL_NAME: |
| 429 | ret = regmap_read(map: info->regmap, MAX172XX_DEV_NAME, val: ®_val); |
| 430 | if (ret) |
| 431 | return ret; |
| 432 | reg_val = FIELD_GET(MAX172XX_DEV_NAME_TYPE_MASK, reg_val); |
| 433 | if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17201) |
| 434 | val->strval = max17201_model; |
| 435 | else if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17205) |
| 436 | val->strval = max17205_model; |
| 437 | else |
| 438 | return -ENODEV; |
| 439 | break; |
| 440 | case POWER_SUPPLY_PROP_MANUFACTURER: |
| 441 | val->strval = max1720x_manufacturer; |
| 442 | break; |
| 443 | default: |
| 444 | return -EINVAL; |
| 445 | } |
| 446 | |
| 447 | return ret; |
| 448 | } |
| 449 | |
| 450 | static int max1720x_read_temp(struct device *dev, u8 reg, char *buf) |
| 451 | { |
| 452 | struct power_supply *psy = dev_get_drvdata(dev); |
| 453 | struct max1720x_device_info *info = power_supply_get_drvdata(psy); |
| 454 | unsigned int val; |
| 455 | int ret; |
| 456 | |
| 457 | ret = regmap_read(map: info->regmap_nv, reg, val: &val); |
| 458 | if (ret < 0) |
| 459 | return ret; |
| 460 | |
| 461 | /* |
| 462 | * Temperature in degrees Celsius starting at absolute zero, -273C or |
| 463 | * 0K with an LSb of 0.1C |
| 464 | */ |
| 465 | return sysfs_emit(buf, fmt: "%d\n" , val - 2730); |
| 466 | } |
| 467 | |
| 468 | static ssize_t temp_ain1_show(struct device *dev, struct device_attribute *attr, |
| 469 | char *buf) |
| 470 | { |
| 471 | return max1720x_read_temp(dev, MAX172XX_TEMP1, buf); |
| 472 | } |
| 473 | |
| 474 | static ssize_t temp_ain2_show(struct device *dev, struct device_attribute *attr, |
| 475 | char *buf) |
| 476 | { |
| 477 | return max1720x_read_temp(dev, MAX172XX_TEMP2, buf); |
| 478 | } |
| 479 | |
| 480 | static ssize_t temp_int_show(struct device *dev, struct device_attribute *attr, |
| 481 | char *buf) |
| 482 | { |
| 483 | return max1720x_read_temp(dev, MAX172XX_INT_TEMP, buf); |
| 484 | } |
| 485 | |
| 486 | static DEVICE_ATTR_RO(temp_ain1); |
| 487 | static DEVICE_ATTR_RO(temp_ain2); |
| 488 | static DEVICE_ATTR_RO(temp_int); |
| 489 | |
| 490 | static struct attribute *max1720x_attrs[] = { |
| 491 | &dev_attr_temp_ain1.attr, |
| 492 | &dev_attr_temp_ain2.attr, |
| 493 | &dev_attr_temp_int.attr, |
| 494 | NULL |
| 495 | }; |
| 496 | ATTRIBUTE_GROUPS(max1720x); |
| 497 | |
| 498 | static |
| 499 | int max1720x_nvmem_reg_read(void *priv, unsigned int off, void *val, size_t len) |
| 500 | { |
| 501 | struct max1720x_device_info *info = priv; |
| 502 | unsigned int reg = MAX1720X_NXTABLE0 + (off / 2); |
| 503 | |
| 504 | return regmap_bulk_read(map: info->regmap_nv, reg, val, val_count: len / 2); |
| 505 | } |
| 506 | |
| 507 | static void max1720x_unregister_ancillary(void *data) |
| 508 | { |
| 509 | struct max1720x_device_info *info = data; |
| 510 | |
| 511 | i2c_unregister_device(client: info->ancillary); |
| 512 | } |
| 513 | |
| 514 | static int max1720x_probe_nvmem(struct i2c_client *client, |
| 515 | struct max1720x_device_info *info) |
| 516 | { |
| 517 | struct device *dev = &client->dev; |
| 518 | struct nvmem_config nvmem_config = { |
| 519 | .dev = dev, |
| 520 | .name = "max1720x_nvmem" , |
| 521 | .cells = max1720x_nvmem_cells, |
| 522 | .ncells = ARRAY_SIZE(max1720x_nvmem_cells), |
| 523 | .read_only = true, |
| 524 | .root_only = true, |
| 525 | .reg_read = max1720x_nvmem_reg_read, |
| 526 | .size = ARRAY_SIZE(max1720x_nvmem_cells) * 2, |
| 527 | .word_size = 2, |
| 528 | .stride = 2, |
| 529 | .priv = info, |
| 530 | }; |
| 531 | struct nvmem_device *nvmem; |
| 532 | unsigned int val; |
| 533 | int ret; |
| 534 | |
| 535 | info->ancillary = i2c_new_ancillary_device(client, name: "nvmem" , default_addr: 0xb); |
| 536 | if (IS_ERR(ptr: info->ancillary)) { |
| 537 | dev_err(dev, "Failed to initialize ancillary i2c device\n" ); |
| 538 | return PTR_ERR(ptr: info->ancillary); |
| 539 | } |
| 540 | |
| 541 | ret = devm_add_action_or_reset(dev, max1720x_unregister_ancillary, info); |
| 542 | if (ret) { |
| 543 | dev_err(dev, "Failed to add unregister callback\n" ); |
| 544 | return ret; |
| 545 | } |
| 546 | |
| 547 | info->regmap_nv = devm_regmap_init_i2c(info->ancillary, |
| 548 | &max1720x_nvmem_regmap_cfg); |
| 549 | if (IS_ERR(ptr: info->regmap_nv)) { |
| 550 | dev_err(dev, "regmap initialization of nvmem failed\n" ); |
| 551 | return PTR_ERR(ptr: info->regmap_nv); |
| 552 | } |
| 553 | |
| 554 | ret = regmap_read(map: info->regmap_nv, MAX1720X_NRSENSE, val: &val); |
| 555 | if (ret < 0) { |
| 556 | dev_err(dev, "Failed to read sense resistor value\n" ); |
| 557 | return ret; |
| 558 | } |
| 559 | |
| 560 | info->rsense = val; |
| 561 | if (!info->rsense) { |
| 562 | dev_warn(dev, "RSense not calibrated, set 10 mOhms!\n" ); |
| 563 | info->rsense = 1000; /* in regs in 10^-5 */ |
| 564 | } |
| 565 | |
| 566 | nvmem = devm_nvmem_register(dev, cfg: &nvmem_config); |
| 567 | if (IS_ERR(ptr: nvmem)) { |
| 568 | dev_err(dev, "Could not register nvmem!" ); |
| 569 | return PTR_ERR(ptr: nvmem); |
| 570 | } |
| 571 | |
| 572 | return 0; |
| 573 | } |
| 574 | |
| 575 | static const struct power_supply_desc max1720x_bat_desc = { |
| 576 | .name = "max1720x" , |
| 577 | .no_thermal = true, |
| 578 | .type = POWER_SUPPLY_TYPE_BATTERY, |
| 579 | .properties = max1720x_battery_props, |
| 580 | .num_properties = ARRAY_SIZE(max1720x_battery_props), |
| 581 | .get_property = max1720x_battery_get_property, |
| 582 | }; |
| 583 | |
| 584 | static int max1720x_probe(struct i2c_client *client) |
| 585 | { |
| 586 | struct power_supply_config psy_cfg = {}; |
| 587 | struct device *dev = &client->dev; |
| 588 | struct max1720x_device_info *info; |
| 589 | struct power_supply *bat; |
| 590 | int ret; |
| 591 | |
| 592 | info = devm_kzalloc(dev, size: sizeof(*info), GFP_KERNEL); |
| 593 | if (!info) |
| 594 | return -ENOMEM; |
| 595 | |
| 596 | psy_cfg.drv_data = info; |
| 597 | psy_cfg.fwnode = dev_fwnode(dev); |
| 598 | psy_cfg.attr_grp = max1720x_groups; |
| 599 | i2c_set_clientdata(client, data: info); |
| 600 | info->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg); |
| 601 | if (IS_ERR(ptr: info->regmap)) |
| 602 | return dev_err_probe(dev, err: PTR_ERR(ptr: info->regmap), |
| 603 | fmt: "regmap initialization failed\n" ); |
| 604 | |
| 605 | ret = max1720x_probe_nvmem(client, info); |
| 606 | if (ret) |
| 607 | return dev_err_probe(dev, err: ret, fmt: "Failed to probe nvmem\n" ); |
| 608 | |
| 609 | bat = devm_power_supply_register(parent: dev, desc: &max1720x_bat_desc, cfg: &psy_cfg); |
| 610 | if (IS_ERR(ptr: bat)) |
| 611 | return dev_err_probe(dev, err: PTR_ERR(ptr: bat), |
| 612 | fmt: "Failed to register power supply\n" ); |
| 613 | |
| 614 | return 0; |
| 615 | } |
| 616 | |
| 617 | static const struct of_device_id max1720x_of_match[] = { |
| 618 | { .compatible = "maxim,max17201" }, |
| 619 | {} |
| 620 | }; |
| 621 | MODULE_DEVICE_TABLE(of, max1720x_of_match); |
| 622 | |
| 623 | static struct i2c_driver max1720x_i2c_driver = { |
| 624 | .driver = { |
| 625 | .name = "max1720x" , |
| 626 | .of_match_table = max1720x_of_match, |
| 627 | }, |
| 628 | .probe = max1720x_probe, |
| 629 | }; |
| 630 | module_i2c_driver(max1720x_i2c_driver); |
| 631 | |
| 632 | MODULE_LICENSE("GPL" ); |
| 633 | MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>" ); |
| 634 | MODULE_DESCRIPTION("Maxim MAX17201/MAX17205 Fuel Gauge IC driver" ); |
| 635 | |