1715ecbc1SLuca Ceresoli // SPDX-License-Identifier: GPL-2.0+ 2715ecbc1SLuca Ceresoli /* 3715ecbc1SLuca Ceresoli * max77976_charger.c - Driver for the Maxim MAX77976 battery charger 4715ecbc1SLuca Ceresoli * 5715ecbc1SLuca Ceresoli * Copyright (C) 2021 Luca Ceresoli 6a6c487cdSLuca Ceresoli * Author: Luca Ceresoli <luca.ceresoli@bootlin.com> 7715ecbc1SLuca Ceresoli */ 8715ecbc1SLuca Ceresoli 9715ecbc1SLuca Ceresoli #include <linux/i2c.h> 10715ecbc1SLuca Ceresoli #include <linux/module.h> 11715ecbc1SLuca Ceresoli #include <linux/power_supply.h> 12715ecbc1SLuca Ceresoli #include <linux/regmap.h> 13715ecbc1SLuca Ceresoli 14715ecbc1SLuca Ceresoli #define MAX77976_DRIVER_NAME "max77976-charger" 15715ecbc1SLuca Ceresoli #define MAX77976_CHIP_ID 0x76 16715ecbc1SLuca Ceresoli 17715ecbc1SLuca Ceresoli static const char *max77976_manufacturer = "Maxim Integrated"; 18715ecbc1SLuca Ceresoli static const char *max77976_model = "MAX77976"; 19715ecbc1SLuca Ceresoli 20715ecbc1SLuca Ceresoli /* -------------------------------------------------------------------------- 21715ecbc1SLuca Ceresoli * Register map 22715ecbc1SLuca Ceresoli */ 23715ecbc1SLuca Ceresoli 24715ecbc1SLuca Ceresoli #define MAX77976_REG_CHIP_ID 0x00 25715ecbc1SLuca Ceresoli #define MAX77976_REG_CHIP_REVISION 0x01 26715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_INT_OK 0x12 27715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_DETAILS_01 0x14 28715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_00 0x16 29715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_02 0x18 30715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_06 0x1c 31715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_09 0x1f 32715ecbc1SLuca Ceresoli 33715ecbc1SLuca Ceresoli /* CHG_DETAILS_01.CHG_DTLS values */ 34715ecbc1SLuca Ceresoli enum max77976_charging_state { 35715ecbc1SLuca Ceresoli MAX77976_CHARGING_PREQUALIFICATION = 0x0, 36715ecbc1SLuca Ceresoli MAX77976_CHARGING_FAST_CONST_CURRENT, 37715ecbc1SLuca Ceresoli MAX77976_CHARGING_FAST_CONST_VOLTAGE, 38715ecbc1SLuca Ceresoli MAX77976_CHARGING_TOP_OFF, 39715ecbc1SLuca Ceresoli MAX77976_CHARGING_DONE, 40715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_05, 41715ecbc1SLuca Ceresoli MAX77976_CHARGING_TIMER_FAULT, 42715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_QBATT_OFF, 43715ecbc1SLuca Ceresoli MAX77976_CHARGING_OFF, 44715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_09, 45715ecbc1SLuca Ceresoli MAX77976_CHARGING_THERMAL_SHUTDOWN, 46715ecbc1SLuca Ceresoli MAX77976_CHARGING_WATCHDOG_EXPIRED, 47715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_JEITA, 48715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_THM_REMOVAL, 49715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_PIN, 50715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_0F, 51715ecbc1SLuca Ceresoli }; 52715ecbc1SLuca Ceresoli 53715ecbc1SLuca Ceresoli /* CHG_DETAILS_01.BAT_DTLS values */ 54715ecbc1SLuca Ceresoli enum max77976_battery_state { 55715ecbc1SLuca Ceresoli MAX77976_BATTERY_BATTERY_REMOVAL = 0x0, 56715ecbc1SLuca Ceresoli MAX77976_BATTERY_PREQUALIFICATION, 57715ecbc1SLuca Ceresoli MAX77976_BATTERY_TIMER_FAULT, 58715ecbc1SLuca Ceresoli MAX77976_BATTERY_REGULAR_VOLTAGE, 59715ecbc1SLuca Ceresoli MAX77976_BATTERY_LOW_VOLTAGE, 60715ecbc1SLuca Ceresoli MAX77976_BATTERY_OVERVOLTAGE, 61715ecbc1SLuca Ceresoli MAX77976_BATTERY_RESERVED, 62715ecbc1SLuca Ceresoli MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present 63715ecbc1SLuca Ceresoli }; 64715ecbc1SLuca Ceresoli 65715ecbc1SLuca Ceresoli /* CHG_CNFG_00.MODE values */ 66715ecbc1SLuca Ceresoli enum max77976_mode { 67715ecbc1SLuca Ceresoli MAX77976_MODE_CHARGER_BUCK = 0x5, 68715ecbc1SLuca Ceresoli MAX77976_MODE_BOOST = 0x9, 69715ecbc1SLuca Ceresoli }; 70715ecbc1SLuca Ceresoli 71715ecbc1SLuca Ceresoli /* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */ 72715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_STEP 50000U 73715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_MIN 100000U 74715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_MAX 5500000U 75715ecbc1SLuca Ceresoli 76715ecbc1SLuca Ceresoli /* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */ 77715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_STEP 100000U 78715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_MIN 100000U 79715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_MAX 3200000U 80715ecbc1SLuca Ceresoli 81715ecbc1SLuca Ceresoli enum max77976_field_idx { 82715ecbc1SLuca Ceresoli VERSION, REVISION, /* CHIP_REVISION */ 83715ecbc1SLuca Ceresoli CHGIN_OK, /* CHG_INT_OK */ 84715ecbc1SLuca Ceresoli BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */ 85715ecbc1SLuca Ceresoli MODE, /* CHG_CNFG_00 */ 86715ecbc1SLuca Ceresoli CHG_CC, /* CHG_CNFG_02 */ 87715ecbc1SLuca Ceresoli CHGPROT, /* CHG_CNFG_06 */ 88715ecbc1SLuca Ceresoli CHGIN_ILIM, /* CHG_CNFG_09 */ 89715ecbc1SLuca Ceresoli MAX77976_N_REGMAP_FIELDS 90715ecbc1SLuca Ceresoli }; 91715ecbc1SLuca Ceresoli 92715ecbc1SLuca Ceresoli static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = { 93715ecbc1SLuca Ceresoli [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7), 94715ecbc1SLuca Ceresoli [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3), 95715ecbc1SLuca Ceresoli [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6), 96715ecbc1SLuca Ceresoli [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3), 97715ecbc1SLuca Ceresoli [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6), 98715ecbc1SLuca Ceresoli [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3), 99715ecbc1SLuca Ceresoli [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6), 100715ecbc1SLuca Ceresoli [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3), 101715ecbc1SLuca Ceresoli [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5), 102715ecbc1SLuca Ceresoli }; 103715ecbc1SLuca Ceresoli 104715ecbc1SLuca Ceresoli static const struct regmap_config max77976_regmap_config = { 105715ecbc1SLuca Ceresoli .reg_bits = 8, 106715ecbc1SLuca Ceresoli .val_bits = 8, 107715ecbc1SLuca Ceresoli .max_register = 0x24, 108715ecbc1SLuca Ceresoli }; 109715ecbc1SLuca Ceresoli 110715ecbc1SLuca Ceresoli /* -------------------------------------------------------------------------- 111715ecbc1SLuca Ceresoli * Data structures 112715ecbc1SLuca Ceresoli */ 113715ecbc1SLuca Ceresoli 114715ecbc1SLuca Ceresoli struct max77976 { 115715ecbc1SLuca Ceresoli struct i2c_client *client; 116715ecbc1SLuca Ceresoli struct regmap *regmap; 117715ecbc1SLuca Ceresoli struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS]; 118715ecbc1SLuca Ceresoli }; 119715ecbc1SLuca Ceresoli 120715ecbc1SLuca Ceresoli /* -------------------------------------------------------------------------- 121715ecbc1SLuca Ceresoli * power_supply properties 122715ecbc1SLuca Ceresoli */ 123715ecbc1SLuca Ceresoli 124715ecbc1SLuca Ceresoli static int max77976_get_status(struct max77976 *chg, int *val) 125715ecbc1SLuca Ceresoli { 126715ecbc1SLuca Ceresoli unsigned int regval; 127715ecbc1SLuca Ceresoli int err; 128715ecbc1SLuca Ceresoli 129715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); 130715ecbc1SLuca Ceresoli if (err < 0) 131715ecbc1SLuca Ceresoli return err; 132715ecbc1SLuca Ceresoli 133715ecbc1SLuca Ceresoli switch (regval) { 134715ecbc1SLuca Ceresoli case MAX77976_CHARGING_PREQUALIFICATION: 135715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_CURRENT: 136715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_VOLTAGE: 137715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TOP_OFF: 138715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_CHARGING; 139715ecbc1SLuca Ceresoli break; 140715ecbc1SLuca Ceresoli case MAX77976_CHARGING_DONE: 141715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_FULL; 142715ecbc1SLuca Ceresoli break; 143715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TIMER_FAULT: 144715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: 145715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_JEITA: 146715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: 147715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_PIN: 148715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_NOT_CHARGING; 149715ecbc1SLuca Ceresoli break; 150715ecbc1SLuca Ceresoli case MAX77976_CHARGING_OFF: 151715ecbc1SLuca Ceresoli case MAX77976_CHARGING_THERMAL_SHUTDOWN: 152715ecbc1SLuca Ceresoli case MAX77976_CHARGING_WATCHDOG_EXPIRED: 153715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_DISCHARGING; 154715ecbc1SLuca Ceresoli break; 155715ecbc1SLuca Ceresoli default: 156715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_UNKNOWN; 157715ecbc1SLuca Ceresoli } 158715ecbc1SLuca Ceresoli 159715ecbc1SLuca Ceresoli return 0; 160715ecbc1SLuca Ceresoli } 161715ecbc1SLuca Ceresoli 162715ecbc1SLuca Ceresoli static int max77976_get_charge_type(struct max77976 *chg, int *val) 163715ecbc1SLuca Ceresoli { 164715ecbc1SLuca Ceresoli unsigned int regval; 165715ecbc1SLuca Ceresoli int err; 166715ecbc1SLuca Ceresoli 167715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHG_DTLS], ®val); 168715ecbc1SLuca Ceresoli if (err < 0) 169715ecbc1SLuca Ceresoli return err; 170715ecbc1SLuca Ceresoli 171715ecbc1SLuca Ceresoli switch (regval) { 172715ecbc1SLuca Ceresoli case MAX77976_CHARGING_PREQUALIFICATION: 173715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 174715ecbc1SLuca Ceresoli break; 175715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_CURRENT: 176715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_VOLTAGE: 177715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_FAST; 178715ecbc1SLuca Ceresoli break; 179715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TOP_OFF: 180715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; 181715ecbc1SLuca Ceresoli break; 182715ecbc1SLuca Ceresoli case MAX77976_CHARGING_DONE: 183715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TIMER_FAULT: 184715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_QBATT_OFF: 185715ecbc1SLuca Ceresoli case MAX77976_CHARGING_OFF: 186715ecbc1SLuca Ceresoli case MAX77976_CHARGING_THERMAL_SHUTDOWN: 187715ecbc1SLuca Ceresoli case MAX77976_CHARGING_WATCHDOG_EXPIRED: 188715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_JEITA: 189715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL: 190715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_PIN: 191715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_NONE; 192715ecbc1SLuca Ceresoli break; 193715ecbc1SLuca Ceresoli default: 194715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; 195715ecbc1SLuca Ceresoli } 196715ecbc1SLuca Ceresoli 197715ecbc1SLuca Ceresoli return 0; 198715ecbc1SLuca Ceresoli } 199715ecbc1SLuca Ceresoli 200715ecbc1SLuca Ceresoli static int max77976_get_health(struct max77976 *chg, int *val) 201715ecbc1SLuca Ceresoli { 202715ecbc1SLuca Ceresoli unsigned int regval; 203715ecbc1SLuca Ceresoli int err; 204715ecbc1SLuca Ceresoli 205715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[BAT_DTLS], ®val); 206715ecbc1SLuca Ceresoli if (err < 0) 207715ecbc1SLuca Ceresoli return err; 208715ecbc1SLuca Ceresoli 209715ecbc1SLuca Ceresoli switch (regval) { 210715ecbc1SLuca Ceresoli case MAX77976_BATTERY_BATTERY_REMOVAL: 211715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_NO_BATTERY; 212715ecbc1SLuca Ceresoli break; 213715ecbc1SLuca Ceresoli case MAX77976_BATTERY_LOW_VOLTAGE: 214715ecbc1SLuca Ceresoli case MAX77976_BATTERY_REGULAR_VOLTAGE: 215715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_GOOD; 216715ecbc1SLuca Ceresoli break; 217715ecbc1SLuca Ceresoli case MAX77976_BATTERY_TIMER_FAULT: 218715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; 219715ecbc1SLuca Ceresoli break; 220715ecbc1SLuca Ceresoli case MAX77976_BATTERY_OVERVOLTAGE: 221715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 222715ecbc1SLuca Ceresoli break; 223715ecbc1SLuca Ceresoli case MAX77976_BATTERY_PREQUALIFICATION: 224715ecbc1SLuca Ceresoli case MAX77976_BATTERY_BATTERY_ONLY: 225715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_UNKNOWN; 226715ecbc1SLuca Ceresoli break; 227715ecbc1SLuca Ceresoli default: 228715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_UNKNOWN; 229715ecbc1SLuca Ceresoli } 230715ecbc1SLuca Ceresoli 231715ecbc1SLuca Ceresoli return 0; 232715ecbc1SLuca Ceresoli } 233715ecbc1SLuca Ceresoli 234715ecbc1SLuca Ceresoli static int max77976_get_online(struct max77976 *chg, int *val) 235715ecbc1SLuca Ceresoli { 236715ecbc1SLuca Ceresoli unsigned int regval; 237715ecbc1SLuca Ceresoli int err; 238715ecbc1SLuca Ceresoli 239715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHGIN_OK], ®val); 240715ecbc1SLuca Ceresoli if (err < 0) 241715ecbc1SLuca Ceresoli return err; 242715ecbc1SLuca Ceresoli 243715ecbc1SLuca Ceresoli *val = (regval ? 1 : 0); 244715ecbc1SLuca Ceresoli 245715ecbc1SLuca Ceresoli return 0; 246715ecbc1SLuca Ceresoli } 247715ecbc1SLuca Ceresoli 248715ecbc1SLuca Ceresoli static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx, 249715ecbc1SLuca Ceresoli unsigned int clamp_min, unsigned int clamp_max, 250715ecbc1SLuca Ceresoli unsigned int mult, int *val) 251715ecbc1SLuca Ceresoli { 252715ecbc1SLuca Ceresoli unsigned int regval; 253715ecbc1SLuca Ceresoli int err; 254715ecbc1SLuca Ceresoli 255715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[fidx], ®val); 256715ecbc1SLuca Ceresoli if (err < 0) 257715ecbc1SLuca Ceresoli return err; 258715ecbc1SLuca Ceresoli 259715ecbc1SLuca Ceresoli *val = clamp_val(regval * mult, clamp_min, clamp_max); 260715ecbc1SLuca Ceresoli 261715ecbc1SLuca Ceresoli return 0; 262715ecbc1SLuca Ceresoli } 263715ecbc1SLuca Ceresoli 264715ecbc1SLuca Ceresoli static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx, 265715ecbc1SLuca Ceresoli unsigned int clamp_min, unsigned int clamp_max, 266715ecbc1SLuca Ceresoli unsigned int div, int val) 267715ecbc1SLuca Ceresoli { 268715ecbc1SLuca Ceresoli unsigned int regval; 269715ecbc1SLuca Ceresoli 270715ecbc1SLuca Ceresoli regval = clamp_val(val, clamp_min, clamp_max) / div; 271715ecbc1SLuca Ceresoli 272715ecbc1SLuca Ceresoli return regmap_field_write(chg->rfield[fidx], regval); 273715ecbc1SLuca Ceresoli } 274715ecbc1SLuca Ceresoli 275715ecbc1SLuca Ceresoli static int max77976_get_property(struct power_supply *psy, 276715ecbc1SLuca Ceresoli enum power_supply_property psp, 277715ecbc1SLuca Ceresoli union power_supply_propval *val) 278715ecbc1SLuca Ceresoli { 279715ecbc1SLuca Ceresoli struct max77976 *chg = power_supply_get_drvdata(psy); 280715ecbc1SLuca Ceresoli int err = 0; 281715ecbc1SLuca Ceresoli 282715ecbc1SLuca Ceresoli switch (psp) { 283715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_STATUS: 284715ecbc1SLuca Ceresoli err = max77976_get_status(chg, &val->intval); 285715ecbc1SLuca Ceresoli break; 286715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_TYPE: 287715ecbc1SLuca Ceresoli err = max77976_get_charge_type(chg, &val->intval); 288715ecbc1SLuca Ceresoli break; 289715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_HEALTH: 290715ecbc1SLuca Ceresoli err = max77976_get_health(chg, &val->intval); 291715ecbc1SLuca Ceresoli break; 292715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_ONLINE: 293715ecbc1SLuca Ceresoli err = max77976_get_online(chg, &val->intval); 294715ecbc1SLuca Ceresoli break; 295715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: 296715ecbc1SLuca Ceresoli val->intval = MAX77976_CHG_CC_MAX; 297715ecbc1SLuca Ceresoli break; 298715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: 299715ecbc1SLuca Ceresoli err = max77976_get_integer(chg, CHG_CC, 300715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MIN, 301715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MAX, 302715ecbc1SLuca Ceresoli MAX77976_CHG_CC_STEP, 303715ecbc1SLuca Ceresoli &val->intval); 304715ecbc1SLuca Ceresoli break; 305715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 306715ecbc1SLuca Ceresoli err = max77976_get_integer(chg, CHGIN_ILIM, 307715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MIN, 308715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MAX, 309715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_STEP, 310715ecbc1SLuca Ceresoli &val->intval); 311715ecbc1SLuca Ceresoli break; 312715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_MODEL_NAME: 313715ecbc1SLuca Ceresoli val->strval = max77976_model; 314715ecbc1SLuca Ceresoli break; 315715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_MANUFACTURER: 316715ecbc1SLuca Ceresoli val->strval = max77976_manufacturer; 317715ecbc1SLuca Ceresoli break; 318715ecbc1SLuca Ceresoli default: 319715ecbc1SLuca Ceresoli err = -EINVAL; 320715ecbc1SLuca Ceresoli } 321715ecbc1SLuca Ceresoli 322715ecbc1SLuca Ceresoli return err; 323715ecbc1SLuca Ceresoli } 324715ecbc1SLuca Ceresoli 325715ecbc1SLuca Ceresoli static int max77976_set_property(struct power_supply *psy, 326715ecbc1SLuca Ceresoli enum power_supply_property psp, 327715ecbc1SLuca Ceresoli const union power_supply_propval *val) 328715ecbc1SLuca Ceresoli { 329715ecbc1SLuca Ceresoli struct max77976 *chg = power_supply_get_drvdata(psy); 330715ecbc1SLuca Ceresoli int err = 0; 331715ecbc1SLuca Ceresoli 332715ecbc1SLuca Ceresoli switch (psp) { 333715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: 334715ecbc1SLuca Ceresoli err = max77976_set_integer(chg, CHG_CC, 335715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MIN, 336715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MAX, 337715ecbc1SLuca Ceresoli MAX77976_CHG_CC_STEP, 338715ecbc1SLuca Ceresoli val->intval); 339715ecbc1SLuca Ceresoli break; 340715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 341715ecbc1SLuca Ceresoli err = max77976_set_integer(chg, CHGIN_ILIM, 342715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MIN, 343715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MAX, 344715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_STEP, 345715ecbc1SLuca Ceresoli val->intval); 346715ecbc1SLuca Ceresoli break; 347715ecbc1SLuca Ceresoli default: 348715ecbc1SLuca Ceresoli err = -EINVAL; 349715ecbc1SLuca Ceresoli } 350715ecbc1SLuca Ceresoli 351715ecbc1SLuca Ceresoli return err; 352715ecbc1SLuca Ceresoli }; 353715ecbc1SLuca Ceresoli 354715ecbc1SLuca Ceresoli static int max77976_property_is_writeable(struct power_supply *psy, 355715ecbc1SLuca Ceresoli enum power_supply_property psp) 356715ecbc1SLuca Ceresoli { 357715ecbc1SLuca Ceresoli switch (psp) { 358715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: 359715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 360715ecbc1SLuca Ceresoli return true; 361715ecbc1SLuca Ceresoli default: 362715ecbc1SLuca Ceresoli return false; 363715ecbc1SLuca Ceresoli } 364715ecbc1SLuca Ceresoli } 365715ecbc1SLuca Ceresoli 366715ecbc1SLuca Ceresoli static enum power_supply_property max77976_psy_props[] = { 367715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_STATUS, 368715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_TYPE, 369715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_HEALTH, 370715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_ONLINE, 371715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, 372715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, 373715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, 374715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_MODEL_NAME, 375715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_MANUFACTURER, 376715ecbc1SLuca Ceresoli }; 377715ecbc1SLuca Ceresoli 378715ecbc1SLuca Ceresoli static const struct power_supply_desc max77976_psy_desc = { 379715ecbc1SLuca Ceresoli .name = MAX77976_DRIVER_NAME, 380715ecbc1SLuca Ceresoli .type = POWER_SUPPLY_TYPE_USB, 381715ecbc1SLuca Ceresoli .properties = max77976_psy_props, 382715ecbc1SLuca Ceresoli .num_properties = ARRAY_SIZE(max77976_psy_props), 383715ecbc1SLuca Ceresoli .get_property = max77976_get_property, 384715ecbc1SLuca Ceresoli .set_property = max77976_set_property, 385715ecbc1SLuca Ceresoli .property_is_writeable = max77976_property_is_writeable, 386715ecbc1SLuca Ceresoli }; 387715ecbc1SLuca Ceresoli 388715ecbc1SLuca Ceresoli /* -------------------------------------------------------------------------- 389715ecbc1SLuca Ceresoli * Entry point 390715ecbc1SLuca Ceresoli */ 391715ecbc1SLuca Ceresoli 392715ecbc1SLuca Ceresoli static int max77976_detect(struct max77976 *chg) 393715ecbc1SLuca Ceresoli { 394715ecbc1SLuca Ceresoli struct device *dev = &chg->client->dev; 395715ecbc1SLuca Ceresoli unsigned int id, ver, rev; 396715ecbc1SLuca Ceresoli int err; 397715ecbc1SLuca Ceresoli 398715ecbc1SLuca Ceresoli err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id); 399715ecbc1SLuca Ceresoli if (err) 400715ecbc1SLuca Ceresoli return dev_err_probe(dev, err, "cannot read chip ID\n"); 401715ecbc1SLuca Ceresoli 402715ecbc1SLuca Ceresoli if (id != MAX77976_CHIP_ID) 403715ecbc1SLuca Ceresoli return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id); 404715ecbc1SLuca Ceresoli 405715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[VERSION], &ver); 406715ecbc1SLuca Ceresoli if (!err) 407715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[REVISION], &rev); 408715ecbc1SLuca Ceresoli if (err) 409715ecbc1SLuca Ceresoli return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n"); 410715ecbc1SLuca Ceresoli 411715ecbc1SLuca Ceresoli dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev); 412715ecbc1SLuca Ceresoli 413715ecbc1SLuca Ceresoli return 0; 414715ecbc1SLuca Ceresoli } 415715ecbc1SLuca Ceresoli 416715ecbc1SLuca Ceresoli static int max77976_configure(struct max77976 *chg) 417715ecbc1SLuca Ceresoli { 418715ecbc1SLuca Ceresoli struct device *dev = &chg->client->dev; 419715ecbc1SLuca Ceresoli int err; 420715ecbc1SLuca Ceresoli 421715ecbc1SLuca Ceresoli /* Magic value to unlock writing to some registers */ 422715ecbc1SLuca Ceresoli err = regmap_field_write(chg->rfield[CHGPROT], 0x3); 423715ecbc1SLuca Ceresoli if (err) 424715ecbc1SLuca Ceresoli goto err; 425715ecbc1SLuca Ceresoli 426715ecbc1SLuca Ceresoli /* 427715ecbc1SLuca Ceresoli * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF. 428715ecbc1SLuca Ceresoli * Other modes are not implemented by this driver. 429715ecbc1SLuca Ceresoli */ 430715ecbc1SLuca Ceresoli err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK); 431715ecbc1SLuca Ceresoli if (err) 432715ecbc1SLuca Ceresoli goto err; 433715ecbc1SLuca Ceresoli 434715ecbc1SLuca Ceresoli return 0; 435715ecbc1SLuca Ceresoli 436715ecbc1SLuca Ceresoli err: 437715ecbc1SLuca Ceresoli return dev_err_probe(dev, err, "error while configuring"); 438715ecbc1SLuca Ceresoli } 439715ecbc1SLuca Ceresoli 440715ecbc1SLuca Ceresoli static int max77976_probe(struct i2c_client *client) 441715ecbc1SLuca Ceresoli { 442715ecbc1SLuca Ceresoli struct device *dev = &client->dev; 443715ecbc1SLuca Ceresoli struct power_supply_config psy_cfg = {}; 444715ecbc1SLuca Ceresoli struct power_supply *psy; 445715ecbc1SLuca Ceresoli struct max77976 *chg; 446715ecbc1SLuca Ceresoli int err; 447715ecbc1SLuca Ceresoli int i; 448715ecbc1SLuca Ceresoli 449715ecbc1SLuca Ceresoli chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); 450715ecbc1SLuca Ceresoli if (!chg) 451715ecbc1SLuca Ceresoli return -ENOMEM; 452715ecbc1SLuca Ceresoli 453715ecbc1SLuca Ceresoli i2c_set_clientdata(client, chg); 454715ecbc1SLuca Ceresoli psy_cfg.drv_data = chg; 455715ecbc1SLuca Ceresoli chg->client = client; 456715ecbc1SLuca Ceresoli 457715ecbc1SLuca Ceresoli chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config); 458715ecbc1SLuca Ceresoli if (IS_ERR(chg->regmap)) 459715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(chg->regmap), 460715ecbc1SLuca Ceresoli "cannot allocate regmap\n"); 461715ecbc1SLuca Ceresoli 462715ecbc1SLuca Ceresoli for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) { 463715ecbc1SLuca Ceresoli chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap, 464715ecbc1SLuca Ceresoli max77976_reg_field[i]); 465715ecbc1SLuca Ceresoli if (IS_ERR(chg->rfield[i])) 466715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(chg->rfield[i]), 467715ecbc1SLuca Ceresoli "cannot allocate regmap field\n"); 468715ecbc1SLuca Ceresoli } 469715ecbc1SLuca Ceresoli 470715ecbc1SLuca Ceresoli err = max77976_detect(chg); 471715ecbc1SLuca Ceresoli if (err) 472715ecbc1SLuca Ceresoli return err; 473715ecbc1SLuca Ceresoli 474715ecbc1SLuca Ceresoli err = max77976_configure(chg); 475715ecbc1SLuca Ceresoli if (err) 476715ecbc1SLuca Ceresoli return err; 477715ecbc1SLuca Ceresoli 478715ecbc1SLuca Ceresoli psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg); 479715ecbc1SLuca Ceresoli if (IS_ERR(psy)) 480715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n"); 481715ecbc1SLuca Ceresoli 482715ecbc1SLuca Ceresoli return 0; 483715ecbc1SLuca Ceresoli } 484715ecbc1SLuca Ceresoli 485715ecbc1SLuca Ceresoli static const struct i2c_device_id max77976_i2c_id[] = { 486715ecbc1SLuca Ceresoli { MAX77976_DRIVER_NAME, 0 }, 487715ecbc1SLuca Ceresoli { }, 488715ecbc1SLuca Ceresoli }; 489715ecbc1SLuca Ceresoli MODULE_DEVICE_TABLE(i2c, max77976_i2c_id); 490715ecbc1SLuca Ceresoli 491715ecbc1SLuca Ceresoli static const struct of_device_id max77976_of_id[] = { 492715ecbc1SLuca Ceresoli { .compatible = "maxim,max77976" }, 493715ecbc1SLuca Ceresoli { }, 494715ecbc1SLuca Ceresoli }; 495715ecbc1SLuca Ceresoli MODULE_DEVICE_TABLE(of, max77976_of_id); 496715ecbc1SLuca Ceresoli 497715ecbc1SLuca Ceresoli static struct i2c_driver max77976_driver = { 498715ecbc1SLuca Ceresoli .driver = { 499715ecbc1SLuca Ceresoli .name = MAX77976_DRIVER_NAME, 500715ecbc1SLuca Ceresoli .of_match_table = max77976_of_id, 501715ecbc1SLuca Ceresoli }, 502fe20b1dcSUwe Kleine-König .probe = max77976_probe, 503715ecbc1SLuca Ceresoli .id_table = max77976_i2c_id, 504715ecbc1SLuca Ceresoli }; 505715ecbc1SLuca Ceresoli module_i2c_driver(max77976_driver); 506715ecbc1SLuca Ceresoli 507a6c487cdSLuca Ceresoli MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>"); 508715ecbc1SLuca Ceresoli MODULE_DESCRIPTION("Maxim MAX77976 charger driver"); 509715ecbc1SLuca Ceresoli MODULE_LICENSE("GPL v2"); 510