1*70d7dd27SAmit Sunil Dhamne // SPDX-License-Identifier: GPL-2.0-only 2*70d7dd27SAmit Sunil Dhamne /* 3*70d7dd27SAmit Sunil Dhamne * max77759_charger.c - Battery charger driver for MAX77759 charger device. 4*70d7dd27SAmit Sunil Dhamne * 5*70d7dd27SAmit Sunil Dhamne * Copyright 2025 Google LLC. 6*70d7dd27SAmit Sunil Dhamne */ 7*70d7dd27SAmit Sunil Dhamne 8*70d7dd27SAmit Sunil Dhamne #include <linux/bitfield.h> 9*70d7dd27SAmit Sunil Dhamne #include <linux/cleanup.h> 10*70d7dd27SAmit Sunil Dhamne #include <linux/device.h> 11*70d7dd27SAmit Sunil Dhamne #include <linux/devm-helpers.h> 12*70d7dd27SAmit Sunil Dhamne #include <linux/interrupt.h> 13*70d7dd27SAmit Sunil Dhamne #include <linux/irq.h> 14*70d7dd27SAmit Sunil Dhamne #include <linux/linear_range.h> 15*70d7dd27SAmit Sunil Dhamne #include <linux/mfd/max77759.h> 16*70d7dd27SAmit Sunil Dhamne #include <linux/module.h> 17*70d7dd27SAmit Sunil Dhamne #include <linux/mod_devicetable.h> 18*70d7dd27SAmit Sunil Dhamne #include <linux/mutex.h> 19*70d7dd27SAmit Sunil Dhamne #include <linux/of.h> 20*70d7dd27SAmit Sunil Dhamne #include <linux/platform_device.h> 21*70d7dd27SAmit Sunil Dhamne #include <linux/power_supply.h> 22*70d7dd27SAmit Sunil Dhamne #include <linux/regmap.h> 23*70d7dd27SAmit Sunil Dhamne #include <linux/regulator/driver.h> 24*70d7dd27SAmit Sunil Dhamne #include <linux/string_choices.h> 25*70d7dd27SAmit Sunil Dhamne #include <linux/workqueue.h> 26*70d7dd27SAmit Sunil Dhamne 27*70d7dd27SAmit Sunil Dhamne /* Default values for Fast Charge Current & Float Voltage */ 28*70d7dd27SAmit Sunil Dhamne #define CHG_CC_DEFAULT_UA 2266770 29*70d7dd27SAmit Sunil Dhamne #define CHG_FV_DEFAULT_MV 4300 30*70d7dd27SAmit Sunil Dhamne 31*70d7dd27SAmit Sunil Dhamne #define MAX_NUM_RETRIES 3 32*70d7dd27SAmit Sunil Dhamne #define PSY_WORK_RETRY_DELAY_MS 10 33*70d7dd27SAmit Sunil Dhamne 34*70d7dd27SAmit Sunil Dhamne #define FOREACH_IRQ(S) \ 35*70d7dd27SAmit Sunil Dhamne S(AICL), \ 36*70d7dd27SAmit Sunil Dhamne S(CHGIN), \ 37*70d7dd27SAmit Sunil Dhamne S(CHG), \ 38*70d7dd27SAmit Sunil Dhamne S(INLIM), \ 39*70d7dd27SAmit Sunil Dhamne S(BAT_OILO), \ 40*70d7dd27SAmit Sunil Dhamne S(CHG_STA_CC), \ 41*70d7dd27SAmit Sunil Dhamne S(CHG_STA_CV), \ 42*70d7dd27SAmit Sunil Dhamne S(CHG_STA_TO), \ 43*70d7dd27SAmit Sunil Dhamne S(CHG_STA_DONE) 44*70d7dd27SAmit Sunil Dhamne 45*70d7dd27SAmit Sunil Dhamne #define GENERATE_ENUM(e) e 46*70d7dd27SAmit Sunil Dhamne #define GENERATE_STRING(s) #s 47*70d7dd27SAmit Sunil Dhamne 48*70d7dd27SAmit Sunil Dhamne enum { 49*70d7dd27SAmit Sunil Dhamne FOREACH_IRQ(GENERATE_ENUM) 50*70d7dd27SAmit Sunil Dhamne }; 51*70d7dd27SAmit Sunil Dhamne 52*70d7dd27SAmit Sunil Dhamne static const char *const chgr_irqs_str[] = { 53*70d7dd27SAmit Sunil Dhamne FOREACH_IRQ(GENERATE_STRING) 54*70d7dd27SAmit Sunil Dhamne }; 55*70d7dd27SAmit Sunil Dhamne 56*70d7dd27SAmit Sunil Dhamne #define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) 57*70d7dd27SAmit Sunil Dhamne 58*70d7dd27SAmit Sunil Dhamne /* Fast charge current limits (in uA) */ 59*70d7dd27SAmit Sunil Dhamne static const struct linear_range chgcc_limit_ranges[] = { 60*70d7dd27SAmit Sunil Dhamne LINEAR_RANGE(133330, 0x0, 0x2, 0), 61*70d7dd27SAmit Sunil Dhamne LINEAR_RANGE(200000, 0x3, 0x3C, 66670), 62*70d7dd27SAmit Sunil Dhamne }; 63*70d7dd27SAmit Sunil Dhamne 64*70d7dd27SAmit Sunil Dhamne /* Charge Termination Voltage Limits (in mV) */ 65*70d7dd27SAmit Sunil Dhamne static const struct linear_range chg_cv_prm_ranges[] = { 66*70d7dd27SAmit Sunil Dhamne LINEAR_RANGE(3800, 0x38, 0x39, 100), 67*70d7dd27SAmit Sunil Dhamne LINEAR_RANGE(4000, 0x0, 0x32, 10), 68*70d7dd27SAmit Sunil Dhamne }; 69*70d7dd27SAmit Sunil Dhamne 70*70d7dd27SAmit Sunil Dhamne /* USB input current limits (in uA) */ 71*70d7dd27SAmit Sunil Dhamne static const struct linear_range chgin_ilim_ranges[] = { 72*70d7dd27SAmit Sunil Dhamne LINEAR_RANGE(100000, 0x3, 0x7F, 25000), 73*70d7dd27SAmit Sunil Dhamne }; 74*70d7dd27SAmit Sunil Dhamne 75*70d7dd27SAmit Sunil Dhamne struct max77759_charger { 76*70d7dd27SAmit Sunil Dhamne struct device *dev; 77*70d7dd27SAmit Sunil Dhamne struct regmap *regmap; 78*70d7dd27SAmit Sunil Dhamne struct power_supply *psy; 79*70d7dd27SAmit Sunil Dhamne struct regulator_dev *chgin_otg_rdev; 80*70d7dd27SAmit Sunil Dhamne struct notifier_block nb; 81*70d7dd27SAmit Sunil Dhamne struct power_supply *tcpm_psy; 82*70d7dd27SAmit Sunil Dhamne struct delayed_work psy_work; 83*70d7dd27SAmit Sunil Dhamne struct mutex retry_lock; /* Protects psy_work_retry_cnt */ 84*70d7dd27SAmit Sunil Dhamne u32 psy_work_retry_cnt; 85*70d7dd27SAmit Sunil Dhamne int irqs[NUM_IRQS]; 86*70d7dd27SAmit Sunil Dhamne struct mutex lock; /* protects the state below */ 87*70d7dd27SAmit Sunil Dhamne enum max77759_chgr_mode mode; 88*70d7dd27SAmit Sunil Dhamne }; 89*70d7dd27SAmit Sunil Dhamne 90*70d7dd27SAmit Sunil Dhamne static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) 91*70d7dd27SAmit Sunil Dhamne { 92*70d7dd27SAmit Sunil Dhamne return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, 93*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock 94*70d7dd27SAmit Sunil Dhamne ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); 95*70d7dd27SAmit Sunil Dhamne } 96*70d7dd27SAmit Sunil Dhamne 97*70d7dd27SAmit Sunil Dhamne static int charger_input_valid(struct max77759_charger *chg) 98*70d7dd27SAmit Sunil Dhamne { 99*70d7dd27SAmit Sunil Dhamne u32 val; 100*70d7dd27SAmit Sunil Dhamne int ret; 101*70d7dd27SAmit Sunil Dhamne 102*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); 103*70d7dd27SAmit Sunil Dhamne if (ret) 104*70d7dd27SAmit Sunil Dhamne return ret; 105*70d7dd27SAmit Sunil Dhamne 106*70d7dd27SAmit Sunil Dhamne return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && 107*70d7dd27SAmit Sunil Dhamne (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); 108*70d7dd27SAmit Sunil Dhamne } 109*70d7dd27SAmit Sunil Dhamne 110*70d7dd27SAmit Sunil Dhamne static int get_online(struct max77759_charger *chg) 111*70d7dd27SAmit Sunil Dhamne { 112*70d7dd27SAmit Sunil Dhamne u32 val; 113*70d7dd27SAmit Sunil Dhamne int ret; 114*70d7dd27SAmit Sunil Dhamne 115*70d7dd27SAmit Sunil Dhamne ret = charger_input_valid(chg); 116*70d7dd27SAmit Sunil Dhamne if (ret <= 0) 117*70d7dd27SAmit Sunil Dhamne return ret; 118*70d7dd27SAmit Sunil Dhamne 119*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); 120*70d7dd27SAmit Sunil Dhamne if (ret) 121*70d7dd27SAmit Sunil Dhamne return ret; 122*70d7dd27SAmit Sunil Dhamne 123*70d7dd27SAmit Sunil Dhamne guard(mutex)(&chg->lock); 124*70d7dd27SAmit Sunil Dhamne 125*70d7dd27SAmit Sunil Dhamne return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && 126*70d7dd27SAmit Sunil Dhamne (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); 127*70d7dd27SAmit Sunil Dhamne } 128*70d7dd27SAmit Sunil Dhamne 129*70d7dd27SAmit Sunil Dhamne static int get_status(struct max77759_charger *chg) 130*70d7dd27SAmit Sunil Dhamne { 131*70d7dd27SAmit Sunil Dhamne u32 val; 132*70d7dd27SAmit Sunil Dhamne int ret; 133*70d7dd27SAmit Sunil Dhamne 134*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); 135*70d7dd27SAmit Sunil Dhamne if (ret) 136*70d7dd27SAmit Sunil Dhamne return ret; 137*70d7dd27SAmit Sunil Dhamne 138*70d7dd27SAmit Sunil Dhamne switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { 139*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_PREQUAL: 140*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_CC: 141*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_CV: 142*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_TO: 143*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_STATUS_CHARGING; 144*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_DONE: 145*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_STATUS_FULL; 146*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: 147*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: 148*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: 149*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: 150*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_STATUS_NOT_CHARGING; 151*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_OFF: 152*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_STATUS_DISCHARGING; 153*70d7dd27SAmit Sunil Dhamne default: 154*70d7dd27SAmit Sunil Dhamne break; 155*70d7dd27SAmit Sunil Dhamne } 156*70d7dd27SAmit Sunil Dhamne 157*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_STATUS_UNKNOWN; 158*70d7dd27SAmit Sunil Dhamne } 159*70d7dd27SAmit Sunil Dhamne 160*70d7dd27SAmit Sunil Dhamne static int get_charge_type(struct max77759_charger *chg) 161*70d7dd27SAmit Sunil Dhamne { 162*70d7dd27SAmit Sunil Dhamne u32 val; 163*70d7dd27SAmit Sunil Dhamne int ret; 164*70d7dd27SAmit Sunil Dhamne 165*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); 166*70d7dd27SAmit Sunil Dhamne if (ret) 167*70d7dd27SAmit Sunil Dhamne return ret; 168*70d7dd27SAmit Sunil Dhamne 169*70d7dd27SAmit Sunil Dhamne switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { 170*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_PREQUAL: 171*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 172*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_CC: 173*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_CV: 174*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_CHARGE_TYPE_FAST; 175*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_TO: 176*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_CHARGE_TYPE_STANDARD; 177*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_DONE: 178*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: 179*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: 180*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: 181*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: 182*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHG_DTLS_OFF: 183*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_CHARGE_TYPE_NONE; 184*70d7dd27SAmit Sunil Dhamne default: 185*70d7dd27SAmit Sunil Dhamne break; 186*70d7dd27SAmit Sunil Dhamne } 187*70d7dd27SAmit Sunil Dhamne 188*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; 189*70d7dd27SAmit Sunil Dhamne } 190*70d7dd27SAmit Sunil Dhamne 191*70d7dd27SAmit Sunil Dhamne static int get_chg_health(struct max77759_charger *chg) 192*70d7dd27SAmit Sunil Dhamne { 193*70d7dd27SAmit Sunil Dhamne u32 val; 194*70d7dd27SAmit Sunil Dhamne int ret; 195*70d7dd27SAmit Sunil Dhamne 196*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); 197*70d7dd27SAmit Sunil Dhamne if (ret) 198*70d7dd27SAmit Sunil Dhamne return ret; 199*70d7dd27SAmit Sunil Dhamne 200*70d7dd27SAmit Sunil Dhamne switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_00_CHGIN_DTLS, val)) { 201*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: 202*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: 203*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; 204*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: 205*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_OVERVOLTAGE; 206*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: 207*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_GOOD; 208*70d7dd27SAmit Sunil Dhamne default: 209*70d7dd27SAmit Sunil Dhamne break; 210*70d7dd27SAmit Sunil Dhamne } 211*70d7dd27SAmit Sunil Dhamne 212*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_UNKNOWN; 213*70d7dd27SAmit Sunil Dhamne } 214*70d7dd27SAmit Sunil Dhamne 215*70d7dd27SAmit Sunil Dhamne static int get_batt_health(struct max77759_charger *chg) 216*70d7dd27SAmit Sunil Dhamne { 217*70d7dd27SAmit Sunil Dhamne u32 val; 218*70d7dd27SAmit Sunil Dhamne int ret; 219*70d7dd27SAmit Sunil Dhamne 220*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); 221*70d7dd27SAmit Sunil Dhamne if (ret) 222*70d7dd27SAmit Sunil Dhamne return ret; 223*70d7dd27SAmit Sunil Dhamne 224*70d7dd27SAmit Sunil Dhamne switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { 225*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: 226*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_NO_BATTERY; 227*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: 228*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_DEAD; 229*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: 230*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; 231*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: 232*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: 233*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_GOOD; 234*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: 235*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; 236*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: 237*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_OVERVOLTAGE; 238*70d7dd27SAmit Sunil Dhamne case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: 239*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_OVERCURRENT; 240*70d7dd27SAmit Sunil Dhamne default: 241*70d7dd27SAmit Sunil Dhamne break; 242*70d7dd27SAmit Sunil Dhamne } 243*70d7dd27SAmit Sunil Dhamne 244*70d7dd27SAmit Sunil Dhamne return POWER_SUPPLY_HEALTH_UNKNOWN; 245*70d7dd27SAmit Sunil Dhamne } 246*70d7dd27SAmit Sunil Dhamne 247*70d7dd27SAmit Sunil Dhamne static int get_health(struct max77759_charger *chg) 248*70d7dd27SAmit Sunil Dhamne { 249*70d7dd27SAmit Sunil Dhamne int ret; 250*70d7dd27SAmit Sunil Dhamne 251*70d7dd27SAmit Sunil Dhamne ret = get_online(chg); 252*70d7dd27SAmit Sunil Dhamne if (ret < 0) 253*70d7dd27SAmit Sunil Dhamne return ret; 254*70d7dd27SAmit Sunil Dhamne 255*70d7dd27SAmit Sunil Dhamne if (ret) { 256*70d7dd27SAmit Sunil Dhamne ret = get_chg_health(chg); 257*70d7dd27SAmit Sunil Dhamne if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) 258*70d7dd27SAmit Sunil Dhamne return ret; 259*70d7dd27SAmit Sunil Dhamne } 260*70d7dd27SAmit Sunil Dhamne 261*70d7dd27SAmit Sunil Dhamne return get_batt_health(chg); 262*70d7dd27SAmit Sunil Dhamne } 263*70d7dd27SAmit Sunil Dhamne 264*70d7dd27SAmit Sunil Dhamne static int get_fast_charge_current(struct max77759_charger *chg) 265*70d7dd27SAmit Sunil Dhamne { 266*70d7dd27SAmit Sunil Dhamne u32 regval, val; 267*70d7dd27SAmit Sunil Dhamne int ret; 268*70d7dd27SAmit Sunil Dhamne 269*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); 270*70d7dd27SAmit Sunil Dhamne if (ret) 271*70d7dd27SAmit Sunil Dhamne return ret; 272*70d7dd27SAmit Sunil Dhamne 273*70d7dd27SAmit Sunil Dhamne regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); 274*70d7dd27SAmit Sunil Dhamne ret = linear_range_get_value_array(chgcc_limit_ranges, 275*70d7dd27SAmit Sunil Dhamne ARRAY_SIZE(chgcc_limit_ranges), 276*70d7dd27SAmit Sunil Dhamne regval, &val); 277*70d7dd27SAmit Sunil Dhamne return ret ? ret : val; 278*70d7dd27SAmit Sunil Dhamne } 279*70d7dd27SAmit Sunil Dhamne 280*70d7dd27SAmit Sunil Dhamne static int set_fast_charge_current_limit(struct max77759_charger *chg, 281*70d7dd27SAmit Sunil Dhamne u32 cc_max_ua) 282*70d7dd27SAmit Sunil Dhamne { 283*70d7dd27SAmit Sunil Dhamne bool found; 284*70d7dd27SAmit Sunil Dhamne u32 regval; 285*70d7dd27SAmit Sunil Dhamne 286*70d7dd27SAmit Sunil Dhamne linear_range_get_selector_high_array(chgcc_limit_ranges, 287*70d7dd27SAmit Sunil Dhamne ARRAY_SIZE(chgcc_limit_ranges), 288*70d7dd27SAmit Sunil Dhamne cc_max_ua, ®val, &found); 289*70d7dd27SAmit Sunil Dhamne if (!found) 290*70d7dd27SAmit Sunil Dhamne return -EINVAL; 291*70d7dd27SAmit Sunil Dhamne 292*70d7dd27SAmit Sunil Dhamne return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, 293*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); 294*70d7dd27SAmit Sunil Dhamne } 295*70d7dd27SAmit Sunil Dhamne 296*70d7dd27SAmit Sunil Dhamne static int get_float_voltage(struct max77759_charger *chg) 297*70d7dd27SAmit Sunil Dhamne { 298*70d7dd27SAmit Sunil Dhamne u32 regval, val; 299*70d7dd27SAmit Sunil Dhamne int ret; 300*70d7dd27SAmit Sunil Dhamne 301*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); 302*70d7dd27SAmit Sunil Dhamne if (ret) 303*70d7dd27SAmit Sunil Dhamne return ret; 304*70d7dd27SAmit Sunil Dhamne 305*70d7dd27SAmit Sunil Dhamne regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); 306*70d7dd27SAmit Sunil Dhamne ret = linear_range_get_value_array(chg_cv_prm_ranges, 307*70d7dd27SAmit Sunil Dhamne ARRAY_SIZE(chg_cv_prm_ranges), 308*70d7dd27SAmit Sunil Dhamne regval, &val); 309*70d7dd27SAmit Sunil Dhamne 310*70d7dd27SAmit Sunil Dhamne return ret ? ret : val; 311*70d7dd27SAmit Sunil Dhamne } 312*70d7dd27SAmit Sunil Dhamne 313*70d7dd27SAmit Sunil Dhamne static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) 314*70d7dd27SAmit Sunil Dhamne { 315*70d7dd27SAmit Sunil Dhamne u32 regval; 316*70d7dd27SAmit Sunil Dhamne bool found; 317*70d7dd27SAmit Sunil Dhamne 318*70d7dd27SAmit Sunil Dhamne linear_range_get_selector_high_array(chg_cv_prm_ranges, 319*70d7dd27SAmit Sunil Dhamne ARRAY_SIZE(chg_cv_prm_ranges), 320*70d7dd27SAmit Sunil Dhamne fv_mv, ®val, &found); 321*70d7dd27SAmit Sunil Dhamne if (!found) 322*70d7dd27SAmit Sunil Dhamne return -EINVAL; 323*70d7dd27SAmit Sunil Dhamne 324*70d7dd27SAmit Sunil Dhamne return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, 325*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, 326*70d7dd27SAmit Sunil Dhamne regval); 327*70d7dd27SAmit Sunil Dhamne } 328*70d7dd27SAmit Sunil Dhamne 329*70d7dd27SAmit Sunil Dhamne static int get_input_current_limit(struct max77759_charger *chg) 330*70d7dd27SAmit Sunil Dhamne { 331*70d7dd27SAmit Sunil Dhamne u32 regval, val; 332*70d7dd27SAmit Sunil Dhamne int ret; 333*70d7dd27SAmit Sunil Dhamne 334*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); 335*70d7dd27SAmit Sunil Dhamne if (ret) 336*70d7dd27SAmit Sunil Dhamne return ret; 337*70d7dd27SAmit Sunil Dhamne 338*70d7dd27SAmit Sunil Dhamne regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); 339*70d7dd27SAmit Sunil Dhamne regval = umax(regval, chgin_ilim_ranges[0].min_sel); 340*70d7dd27SAmit Sunil Dhamne 341*70d7dd27SAmit Sunil Dhamne ret = linear_range_get_value_array(chgin_ilim_ranges, 342*70d7dd27SAmit Sunil Dhamne ARRAY_SIZE(chgin_ilim_ranges), 343*70d7dd27SAmit Sunil Dhamne regval, &val); 344*70d7dd27SAmit Sunil Dhamne 345*70d7dd27SAmit Sunil Dhamne return ret ? ret : val; 346*70d7dd27SAmit Sunil Dhamne } 347*70d7dd27SAmit Sunil Dhamne 348*70d7dd27SAmit Sunil Dhamne static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) 349*70d7dd27SAmit Sunil Dhamne { 350*70d7dd27SAmit Sunil Dhamne u32 regval; 351*70d7dd27SAmit Sunil Dhamne 352*70d7dd27SAmit Sunil Dhamne if (ilim_ua < 0) 353*70d7dd27SAmit Sunil Dhamne return -EINVAL; 354*70d7dd27SAmit Sunil Dhamne 355*70d7dd27SAmit Sunil Dhamne linear_range_get_selector_within(chgin_ilim_ranges, ilim_ua, ®val); 356*70d7dd27SAmit Sunil Dhamne 357*70d7dd27SAmit Sunil Dhamne return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, 358*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, 359*70d7dd27SAmit Sunil Dhamne regval); 360*70d7dd27SAmit Sunil Dhamne } 361*70d7dd27SAmit Sunil Dhamne 362*70d7dd27SAmit Sunil Dhamne static const enum power_supply_property max77759_charger_props[] = { 363*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_ONLINE, 364*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_PRESENT, 365*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_STATUS, 366*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_CHARGE_TYPE, 367*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_HEALTH, 368*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, 369*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, 370*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, 371*70d7dd27SAmit Sunil Dhamne }; 372*70d7dd27SAmit Sunil Dhamne 373*70d7dd27SAmit Sunil Dhamne static int max77759_charger_get_property(struct power_supply *psy, 374*70d7dd27SAmit Sunil Dhamne enum power_supply_property psp, 375*70d7dd27SAmit Sunil Dhamne union power_supply_propval *pval) 376*70d7dd27SAmit Sunil Dhamne { 377*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = power_supply_get_drvdata(psy); 378*70d7dd27SAmit Sunil Dhamne int ret; 379*70d7dd27SAmit Sunil Dhamne 380*70d7dd27SAmit Sunil Dhamne switch (psp) { 381*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_ONLINE: 382*70d7dd27SAmit Sunil Dhamne ret = get_online(chg); 383*70d7dd27SAmit Sunil Dhamne break; 384*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_PRESENT: 385*70d7dd27SAmit Sunil Dhamne ret = charger_input_valid(chg); 386*70d7dd27SAmit Sunil Dhamne break; 387*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_STATUS: 388*70d7dd27SAmit Sunil Dhamne ret = get_status(chg); 389*70d7dd27SAmit Sunil Dhamne break; 390*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_CHARGE_TYPE: 391*70d7dd27SAmit Sunil Dhamne ret = get_charge_type(chg); 392*70d7dd27SAmit Sunil Dhamne break; 393*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_HEALTH: 394*70d7dd27SAmit Sunil Dhamne ret = get_health(chg); 395*70d7dd27SAmit Sunil Dhamne break; 396*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: 397*70d7dd27SAmit Sunil Dhamne ret = get_fast_charge_current(chg); 398*70d7dd27SAmit Sunil Dhamne break; 399*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: 400*70d7dd27SAmit Sunil Dhamne ret = get_float_voltage(chg); 401*70d7dd27SAmit Sunil Dhamne break; 402*70d7dd27SAmit Sunil Dhamne case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 403*70d7dd27SAmit Sunil Dhamne ret = get_input_current_limit(chg); 404*70d7dd27SAmit Sunil Dhamne break; 405*70d7dd27SAmit Sunil Dhamne default: 406*70d7dd27SAmit Sunil Dhamne ret = -EINVAL; 407*70d7dd27SAmit Sunil Dhamne } 408*70d7dd27SAmit Sunil Dhamne 409*70d7dd27SAmit Sunil Dhamne pval->intval = ret; 410*70d7dd27SAmit Sunil Dhamne return ret < 0 ? ret : 0; 411*70d7dd27SAmit Sunil Dhamne } 412*70d7dd27SAmit Sunil Dhamne 413*70d7dd27SAmit Sunil Dhamne static const struct power_supply_desc max77759_charger_desc = { 414*70d7dd27SAmit Sunil Dhamne .name = "max77759-charger", 415*70d7dd27SAmit Sunil Dhamne .type = POWER_SUPPLY_TYPE_USB, 416*70d7dd27SAmit Sunil Dhamne .properties = max77759_charger_props, 417*70d7dd27SAmit Sunil Dhamne .num_properties = ARRAY_SIZE(max77759_charger_props), 418*70d7dd27SAmit Sunil Dhamne .get_property = max77759_charger_get_property, 419*70d7dd27SAmit Sunil Dhamne }; 420*70d7dd27SAmit Sunil Dhamne 421*70d7dd27SAmit Sunil Dhamne static int charger_set_mode(struct max77759_charger *chg, 422*70d7dd27SAmit Sunil Dhamne enum max77759_chgr_mode mode) 423*70d7dd27SAmit Sunil Dhamne { 424*70d7dd27SAmit Sunil Dhamne int ret; 425*70d7dd27SAmit Sunil Dhamne 426*70d7dd27SAmit Sunil Dhamne guard(mutex)(&chg->lock); 427*70d7dd27SAmit Sunil Dhamne 428*70d7dd27SAmit Sunil Dhamne if (chg->mode == mode) 429*70d7dd27SAmit Sunil Dhamne return 0; 430*70d7dd27SAmit Sunil Dhamne 431*70d7dd27SAmit Sunil Dhamne if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || 432*70d7dd27SAmit Sunil Dhamne mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && 433*70d7dd27SAmit Sunil Dhamne chg->mode != MAX77759_CHGR_MODE_OFF) { 434*70d7dd27SAmit Sunil Dhamne dev_err(chg->dev, "Invalid mode transition from %d to %d\n", 435*70d7dd27SAmit Sunil Dhamne chg->mode, mode); 436*70d7dd27SAmit Sunil Dhamne return -EINVAL; 437*70d7dd27SAmit Sunil Dhamne } 438*70d7dd27SAmit Sunil Dhamne 439*70d7dd27SAmit Sunil Dhamne ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, 440*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); 441*70d7dd27SAmit Sunil Dhamne if (ret) 442*70d7dd27SAmit Sunil Dhamne return ret; 443*70d7dd27SAmit Sunil Dhamne 444*70d7dd27SAmit Sunil Dhamne chg->mode = mode; 445*70d7dd27SAmit Sunil Dhamne return 0; 446*70d7dd27SAmit Sunil Dhamne } 447*70d7dd27SAmit Sunil Dhamne 448*70d7dd27SAmit Sunil Dhamne static int enable_chgin_otg(struct regulator_dev *rdev) 449*70d7dd27SAmit Sunil Dhamne { 450*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = rdev_get_drvdata(rdev); 451*70d7dd27SAmit Sunil Dhamne 452*70d7dd27SAmit Sunil Dhamne return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); 453*70d7dd27SAmit Sunil Dhamne } 454*70d7dd27SAmit Sunil Dhamne 455*70d7dd27SAmit Sunil Dhamne static int disable_chgin_otg(struct regulator_dev *rdev) 456*70d7dd27SAmit Sunil Dhamne { 457*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = rdev_get_drvdata(rdev); 458*70d7dd27SAmit Sunil Dhamne 459*70d7dd27SAmit Sunil Dhamne return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); 460*70d7dd27SAmit Sunil Dhamne } 461*70d7dd27SAmit Sunil Dhamne 462*70d7dd27SAmit Sunil Dhamne static int chgin_otg_status(struct regulator_dev *rdev) 463*70d7dd27SAmit Sunil Dhamne { 464*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = rdev_get_drvdata(rdev); 465*70d7dd27SAmit Sunil Dhamne 466*70d7dd27SAmit Sunil Dhamne guard(mutex)(&chg->lock); 467*70d7dd27SAmit Sunil Dhamne 468*70d7dd27SAmit Sunil Dhamne return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; 469*70d7dd27SAmit Sunil Dhamne } 470*70d7dd27SAmit Sunil Dhamne 471*70d7dd27SAmit Sunil Dhamne static const struct regulator_ops chgin_otg_reg_ops = { 472*70d7dd27SAmit Sunil Dhamne .enable = enable_chgin_otg, 473*70d7dd27SAmit Sunil Dhamne .disable = disable_chgin_otg, 474*70d7dd27SAmit Sunil Dhamne .is_enabled = chgin_otg_status, 475*70d7dd27SAmit Sunil Dhamne }; 476*70d7dd27SAmit Sunil Dhamne 477*70d7dd27SAmit Sunil Dhamne static const struct regulator_desc chgin_otg_reg_desc = { 478*70d7dd27SAmit Sunil Dhamne .name = "chgin-otg", 479*70d7dd27SAmit Sunil Dhamne .of_match = of_match_ptr("chgin-otg-regulator"), 480*70d7dd27SAmit Sunil Dhamne .owner = THIS_MODULE, 481*70d7dd27SAmit Sunil Dhamne .ops = &chgin_otg_reg_ops, 482*70d7dd27SAmit Sunil Dhamne .fixed_uV = 5000000, 483*70d7dd27SAmit Sunil Dhamne .n_voltages = 1, 484*70d7dd27SAmit Sunil Dhamne }; 485*70d7dd27SAmit Sunil Dhamne 486*70d7dd27SAmit Sunil Dhamne static irqreturn_t irq_handler(int irq, void *data) 487*70d7dd27SAmit Sunil Dhamne { 488*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = data; 489*70d7dd27SAmit Sunil Dhamne 490*70d7dd27SAmit Sunil Dhamne power_supply_changed(chg->psy); 491*70d7dd27SAmit Sunil Dhamne 492*70d7dd27SAmit Sunil Dhamne return IRQ_HANDLED; 493*70d7dd27SAmit Sunil Dhamne } 494*70d7dd27SAmit Sunil Dhamne 495*70d7dd27SAmit Sunil Dhamne static irqreturn_t bat_oilo_irq_handler(int irq, void *data) 496*70d7dd27SAmit Sunil Dhamne { 497*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = data; 498*70d7dd27SAmit Sunil Dhamne 499*70d7dd27SAmit Sunil Dhamne dev_warn_ratelimited(chg->dev, 500*70d7dd27SAmit Sunil Dhamne "Battery over-current threshold crossed\n"); 501*70d7dd27SAmit Sunil Dhamne 502*70d7dd27SAmit Sunil Dhamne return irq_handler(irq, data); 503*70d7dd27SAmit Sunil Dhamne } 504*70d7dd27SAmit Sunil Dhamne 505*70d7dd27SAmit Sunil Dhamne static int max77759_init_irqhandler(struct max77759_charger *chg) 506*70d7dd27SAmit Sunil Dhamne { 507*70d7dd27SAmit Sunil Dhamne struct device *dev = chg->dev; 508*70d7dd27SAmit Sunil Dhamne irq_handler_t thread_fn; 509*70d7dd27SAmit Sunil Dhamne char *name; 510*70d7dd27SAmit Sunil Dhamne int i, ret; 511*70d7dd27SAmit Sunil Dhamne 512*70d7dd27SAmit Sunil Dhamne for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { 513*70d7dd27SAmit Sunil Dhamne ret = platform_get_irq_byname(to_platform_device(dev), 514*70d7dd27SAmit Sunil Dhamne chgr_irqs_str[i]); 515*70d7dd27SAmit Sunil Dhamne if (ret < 0) 516*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 517*70d7dd27SAmit Sunil Dhamne "Failed to get irq resource for %s\n", 518*70d7dd27SAmit Sunil Dhamne chgr_irqs_str[i]); 519*70d7dd27SAmit Sunil Dhamne 520*70d7dd27SAmit Sunil Dhamne chg->irqs[i] = ret; 521*70d7dd27SAmit Sunil Dhamne name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev), 522*70d7dd27SAmit Sunil Dhamne chgr_irqs_str[i]); 523*70d7dd27SAmit Sunil Dhamne if (!name) 524*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, -ENOMEM, 525*70d7dd27SAmit Sunil Dhamne "Failed to allocate space for irqname: %s\n", 526*70d7dd27SAmit Sunil Dhamne chgr_irqs_str[i]); 527*70d7dd27SAmit Sunil Dhamne 528*70d7dd27SAmit Sunil Dhamne if (i == BAT_OILO) 529*70d7dd27SAmit Sunil Dhamne thread_fn = bat_oilo_irq_handler; 530*70d7dd27SAmit Sunil Dhamne else 531*70d7dd27SAmit Sunil Dhamne thread_fn = irq_handler; 532*70d7dd27SAmit Sunil Dhamne 533*70d7dd27SAmit Sunil Dhamne ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, 534*70d7dd27SAmit Sunil Dhamne thread_fn, 0, name, chg); 535*70d7dd27SAmit Sunil Dhamne if (ret) 536*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 537*70d7dd27SAmit Sunil Dhamne "Unable to register irq handler for %s\n", 538*70d7dd27SAmit Sunil Dhamne chgr_irqs_str[i]); 539*70d7dd27SAmit Sunil Dhamne } 540*70d7dd27SAmit Sunil Dhamne 541*70d7dd27SAmit Sunil Dhamne return 0; 542*70d7dd27SAmit Sunil Dhamne } 543*70d7dd27SAmit Sunil Dhamne 544*70d7dd27SAmit Sunil Dhamne static int max77759_charger_init(struct max77759_charger *chg) 545*70d7dd27SAmit Sunil Dhamne { 546*70d7dd27SAmit Sunil Dhamne struct power_supply_battery_info *info; 547*70d7dd27SAmit Sunil Dhamne u32 regval, fast_chg_curr, fv; 548*70d7dd27SAmit Sunil Dhamne int ret; 549*70d7dd27SAmit Sunil Dhamne 550*70d7dd27SAmit Sunil Dhamne ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); 551*70d7dd27SAmit Sunil Dhamne if (ret) 552*70d7dd27SAmit Sunil Dhamne return ret; 553*70d7dd27SAmit Sunil Dhamne 554*70d7dd27SAmit Sunil Dhamne chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); 555*70d7dd27SAmit Sunil Dhamne ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); 556*70d7dd27SAmit Sunil Dhamne if (ret) 557*70d7dd27SAmit Sunil Dhamne return ret; 558*70d7dd27SAmit Sunil Dhamne 559*70d7dd27SAmit Sunil Dhamne if (power_supply_get_battery_info(chg->psy, &info)) { 560*70d7dd27SAmit Sunil Dhamne fv = CHG_FV_DEFAULT_MV; 561*70d7dd27SAmit Sunil Dhamne fast_chg_curr = CHG_CC_DEFAULT_UA; 562*70d7dd27SAmit Sunil Dhamne } else { 563*70d7dd27SAmit Sunil Dhamne fv = info->constant_charge_voltage_max_uv / 1000; 564*70d7dd27SAmit Sunil Dhamne fast_chg_curr = info->constant_charge_current_max_ua; 565*70d7dd27SAmit Sunil Dhamne } 566*70d7dd27SAmit Sunil Dhamne 567*70d7dd27SAmit Sunil Dhamne ret = set_fast_charge_current_limit(chg, fast_chg_curr); 568*70d7dd27SAmit Sunil Dhamne if (ret) 569*70d7dd27SAmit Sunil Dhamne return ret; 570*70d7dd27SAmit Sunil Dhamne 571*70d7dd27SAmit Sunil Dhamne ret = set_float_voltage_limit(chg, fv); 572*70d7dd27SAmit Sunil Dhamne if (ret) 573*70d7dd27SAmit Sunil Dhamne return ret; 574*70d7dd27SAmit Sunil Dhamne 575*70d7dd27SAmit Sunil Dhamne ret = unlock_prot_regs(chg, true); 576*70d7dd27SAmit Sunil Dhamne if (ret) 577*70d7dd27SAmit Sunil Dhamne return ret; 578*70d7dd27SAmit Sunil Dhamne 579*70d7dd27SAmit Sunil Dhamne /* Disable wireless charging input */ 580*70d7dd27SAmit Sunil Dhamne ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, 581*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); 582*70d7dd27SAmit Sunil Dhamne if (ret) 583*70d7dd27SAmit Sunil Dhamne goto relock; 584*70d7dd27SAmit Sunil Dhamne 585*70d7dd27SAmit Sunil Dhamne ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, 586*70d7dd27SAmit Sunil Dhamne MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); 587*70d7dd27SAmit Sunil Dhamne if (ret) 588*70d7dd27SAmit Sunil Dhamne goto relock; 589*70d7dd27SAmit Sunil Dhamne 590*70d7dd27SAmit Sunil Dhamne return unlock_prot_regs(chg, false); 591*70d7dd27SAmit Sunil Dhamne 592*70d7dd27SAmit Sunil Dhamne relock: 593*70d7dd27SAmit Sunil Dhamne (void)unlock_prot_regs(chg, false); 594*70d7dd27SAmit Sunil Dhamne return ret; 595*70d7dd27SAmit Sunil Dhamne } 596*70d7dd27SAmit Sunil Dhamne 597*70d7dd27SAmit Sunil Dhamne static void psy_work_item(struct work_struct *work) 598*70d7dd27SAmit Sunil Dhamne { 599*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = 600*70d7dd27SAmit Sunil Dhamne container_of(work, struct max77759_charger, psy_work.work); 601*70d7dd27SAmit Sunil Dhamne union power_supply_propval current_limit, online; 602*70d7dd27SAmit Sunil Dhamne int ret; 603*70d7dd27SAmit Sunil Dhamne 604*70d7dd27SAmit Sunil Dhamne ret = power_supply_get_property(chg->tcpm_psy, 605*70d7dd27SAmit Sunil Dhamne POWER_SUPPLY_PROP_CURRENT_MAX, 606*70d7dd27SAmit Sunil Dhamne ¤t_limit); 607*70d7dd27SAmit Sunil Dhamne if (ret) { 608*70d7dd27SAmit Sunil Dhamne dev_err(chg->dev, 609*70d7dd27SAmit Sunil Dhamne "Failed to get CURRENT_MAX psy property, ret=%d\n", 610*70d7dd27SAmit Sunil Dhamne ret); 611*70d7dd27SAmit Sunil Dhamne goto err; 612*70d7dd27SAmit Sunil Dhamne } 613*70d7dd27SAmit Sunil Dhamne 614*70d7dd27SAmit Sunil Dhamne ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, 615*70d7dd27SAmit Sunil Dhamne &online); 616*70d7dd27SAmit Sunil Dhamne if (ret) { 617*70d7dd27SAmit Sunil Dhamne dev_err(chg->dev, 618*70d7dd27SAmit Sunil Dhamne "Failed to get ONLINE psy property, ret=%d\n", 619*70d7dd27SAmit Sunil Dhamne ret); 620*70d7dd27SAmit Sunil Dhamne goto err; 621*70d7dd27SAmit Sunil Dhamne } 622*70d7dd27SAmit Sunil Dhamne 623*70d7dd27SAmit Sunil Dhamne if (online.intval && current_limit.intval) { 624*70d7dd27SAmit Sunil Dhamne ret = set_input_current_limit(chg, current_limit.intval); 625*70d7dd27SAmit Sunil Dhamne if (ret) { 626*70d7dd27SAmit Sunil Dhamne dev_err(chg->dev, 627*70d7dd27SAmit Sunil Dhamne "Unable to set current limit, ret=%d\n", ret); 628*70d7dd27SAmit Sunil Dhamne goto err; 629*70d7dd27SAmit Sunil Dhamne } 630*70d7dd27SAmit Sunil Dhamne 631*70d7dd27SAmit Sunil Dhamne charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); 632*70d7dd27SAmit Sunil Dhamne } else { 633*70d7dd27SAmit Sunil Dhamne charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); 634*70d7dd27SAmit Sunil Dhamne } 635*70d7dd27SAmit Sunil Dhamne 636*70d7dd27SAmit Sunil Dhamne scoped_guard(mutex, &chg->retry_lock) { 637*70d7dd27SAmit Sunil Dhamne if (chg->psy_work_retry_cnt) 638*70d7dd27SAmit Sunil Dhamne dev_dbg(chg->dev, 639*70d7dd27SAmit Sunil Dhamne "chg psy_work succeeded after %u tries\n", 640*70d7dd27SAmit Sunil Dhamne chg->psy_work_retry_cnt); 641*70d7dd27SAmit Sunil Dhamne chg->psy_work_retry_cnt = 0; 642*70d7dd27SAmit Sunil Dhamne } 643*70d7dd27SAmit Sunil Dhamne 644*70d7dd27SAmit Sunil Dhamne return; 645*70d7dd27SAmit Sunil Dhamne 646*70d7dd27SAmit Sunil Dhamne err: 647*70d7dd27SAmit Sunil Dhamne charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); 648*70d7dd27SAmit Sunil Dhamne scoped_guard(mutex, &chg->retry_lock) { 649*70d7dd27SAmit Sunil Dhamne if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) { 650*70d7dd27SAmit Sunil Dhamne dev_err(chg->dev, "chg psy work failed, giving up\n"); 651*70d7dd27SAmit Sunil Dhamne return; 652*70d7dd27SAmit Sunil Dhamne } 653*70d7dd27SAmit Sunil Dhamne 654*70d7dd27SAmit Sunil Dhamne ++chg->psy_work_retry_cnt; 655*70d7dd27SAmit Sunil Dhamne dev_dbg(chg->dev, "Retrying %u/%u chg psy_work\n", 656*70d7dd27SAmit Sunil Dhamne chg->psy_work_retry_cnt, MAX_NUM_RETRIES); 657*70d7dd27SAmit Sunil Dhamne schedule_delayed_work(&chg->psy_work, 658*70d7dd27SAmit Sunil Dhamne msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); 659*70d7dd27SAmit Sunil Dhamne } 660*70d7dd27SAmit Sunil Dhamne } 661*70d7dd27SAmit Sunil Dhamne 662*70d7dd27SAmit Sunil Dhamne static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) 663*70d7dd27SAmit Sunil Dhamne { 664*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg = container_of(nb, struct max77759_charger, 665*70d7dd27SAmit Sunil Dhamne nb); 666*70d7dd27SAmit Sunil Dhamne static const char *psy_name = "tcpm-source"; 667*70d7dd27SAmit Sunil Dhamne struct power_supply *psy = data; 668*70d7dd27SAmit Sunil Dhamne 669*70d7dd27SAmit Sunil Dhamne if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || 670*70d7dd27SAmit Sunil Dhamne evt != PSY_EVENT_PROP_CHANGED) 671*70d7dd27SAmit Sunil Dhamne return NOTIFY_OK; 672*70d7dd27SAmit Sunil Dhamne 673*70d7dd27SAmit Sunil Dhamne chg->tcpm_psy = psy; 674*70d7dd27SAmit Sunil Dhamne scoped_guard(mutex, &chg->retry_lock) 675*70d7dd27SAmit Sunil Dhamne chg->psy_work_retry_cnt = 0; 676*70d7dd27SAmit Sunil Dhamne 677*70d7dd27SAmit Sunil Dhamne schedule_delayed_work(&chg->psy_work, 0); 678*70d7dd27SAmit Sunil Dhamne 679*70d7dd27SAmit Sunil Dhamne return NOTIFY_OK; 680*70d7dd27SAmit Sunil Dhamne } 681*70d7dd27SAmit Sunil Dhamne 682*70d7dd27SAmit Sunil Dhamne static void max_tcpci_unregister_psy_notifier(void *nb) 683*70d7dd27SAmit Sunil Dhamne { 684*70d7dd27SAmit Sunil Dhamne power_supply_unreg_notifier(nb); 685*70d7dd27SAmit Sunil Dhamne } 686*70d7dd27SAmit Sunil Dhamne 687*70d7dd27SAmit Sunil Dhamne static int max77759_charger_probe(struct platform_device *pdev) 688*70d7dd27SAmit Sunil Dhamne { 689*70d7dd27SAmit Sunil Dhamne struct regulator_config chgin_otg_reg_cfg; 690*70d7dd27SAmit Sunil Dhamne struct power_supply_config psy_cfg; 691*70d7dd27SAmit Sunil Dhamne struct device *dev = &pdev->dev; 692*70d7dd27SAmit Sunil Dhamne struct max77759_charger *chg; 693*70d7dd27SAmit Sunil Dhamne int ret; 694*70d7dd27SAmit Sunil Dhamne 695*70d7dd27SAmit Sunil Dhamne device_set_of_node_from_dev(dev, dev->parent); 696*70d7dd27SAmit Sunil Dhamne chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); 697*70d7dd27SAmit Sunil Dhamne if (!chg) 698*70d7dd27SAmit Sunil Dhamne return -ENOMEM; 699*70d7dd27SAmit Sunil Dhamne 700*70d7dd27SAmit Sunil Dhamne platform_set_drvdata(pdev, chg); 701*70d7dd27SAmit Sunil Dhamne chg->dev = dev; 702*70d7dd27SAmit Sunil Dhamne chg->regmap = dev_get_regmap(dev->parent, "charger"); 703*70d7dd27SAmit Sunil Dhamne if (!chg->regmap) 704*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, -ENODEV, "Missing regmap\n"); 705*70d7dd27SAmit Sunil Dhamne 706*70d7dd27SAmit Sunil Dhamne ret = devm_mutex_init(dev, &chg->lock); 707*70d7dd27SAmit Sunil Dhamne if (ret) 708*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, "Failed to initialize lock\n"); 709*70d7dd27SAmit Sunil Dhamne 710*70d7dd27SAmit Sunil Dhamne ret = devm_mutex_init(dev, &chg->retry_lock); 711*70d7dd27SAmit Sunil Dhamne if (ret) 712*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 713*70d7dd27SAmit Sunil Dhamne "Failed to initialize retry_lock\n"); 714*70d7dd27SAmit Sunil Dhamne 715*70d7dd27SAmit Sunil Dhamne psy_cfg.fwnode = dev_fwnode(dev); 716*70d7dd27SAmit Sunil Dhamne psy_cfg.drv_data = chg; 717*70d7dd27SAmit Sunil Dhamne chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, 718*70d7dd27SAmit Sunil Dhamne &psy_cfg); 719*70d7dd27SAmit Sunil Dhamne if (IS_ERR(chg->psy)) 720*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, PTR_ERR(chg->psy), 721*70d7dd27SAmit Sunil Dhamne "Failed to register psy\n"); 722*70d7dd27SAmit Sunil Dhamne 723*70d7dd27SAmit Sunil Dhamne ret = max77759_charger_init(chg); 724*70d7dd27SAmit Sunil Dhamne if (ret) 725*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 726*70d7dd27SAmit Sunil Dhamne "Failed to initialize max77759 charger\n"); 727*70d7dd27SAmit Sunil Dhamne 728*70d7dd27SAmit Sunil Dhamne chgin_otg_reg_cfg.dev = dev; 729*70d7dd27SAmit Sunil Dhamne chgin_otg_reg_cfg.driver_data = chg; 730*70d7dd27SAmit Sunil Dhamne chgin_otg_reg_cfg.of_node = dev_of_node(dev); 731*70d7dd27SAmit Sunil Dhamne chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, 732*70d7dd27SAmit Sunil Dhamne &chgin_otg_reg_cfg); 733*70d7dd27SAmit Sunil Dhamne if (IS_ERR(chg->chgin_otg_rdev)) 734*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), 735*70d7dd27SAmit Sunil Dhamne "Failed to register chgin otg regulator\n"); 736*70d7dd27SAmit Sunil Dhamne 737*70d7dd27SAmit Sunil Dhamne ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); 738*70d7dd27SAmit Sunil Dhamne if (ret) 739*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, "Failed to initialize psy work\n"); 740*70d7dd27SAmit Sunil Dhamne 741*70d7dd27SAmit Sunil Dhamne chg->nb.notifier_call = psy_changed; 742*70d7dd27SAmit Sunil Dhamne ret = power_supply_reg_notifier(&chg->nb); 743*70d7dd27SAmit Sunil Dhamne if (ret) 744*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 745*70d7dd27SAmit Sunil Dhamne "Unable to register psy notifier\n"); 746*70d7dd27SAmit Sunil Dhamne 747*70d7dd27SAmit Sunil Dhamne ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, 748*70d7dd27SAmit Sunil Dhamne &chg->nb); 749*70d7dd27SAmit Sunil Dhamne if (ret) 750*70d7dd27SAmit Sunil Dhamne return dev_err_probe(dev, ret, 751*70d7dd27SAmit Sunil Dhamne "Failed to add devm action to unregister psy notifier\n"); 752*70d7dd27SAmit Sunil Dhamne 753*70d7dd27SAmit Sunil Dhamne return max77759_init_irqhandler(chg); 754*70d7dd27SAmit Sunil Dhamne } 755*70d7dd27SAmit Sunil Dhamne 756*70d7dd27SAmit Sunil Dhamne static const struct platform_device_id max77759_charger_id[] = { 757*70d7dd27SAmit Sunil Dhamne { .name = "max77759-charger", }, 758*70d7dd27SAmit Sunil Dhamne { } 759*70d7dd27SAmit Sunil Dhamne }; 760*70d7dd27SAmit Sunil Dhamne MODULE_DEVICE_TABLE(platform, max77759_charger_id); 761*70d7dd27SAmit Sunil Dhamne 762*70d7dd27SAmit Sunil Dhamne static struct platform_driver max77759_charger_driver = { 763*70d7dd27SAmit Sunil Dhamne .driver = { 764*70d7dd27SAmit Sunil Dhamne .name = "max77759-charger", 765*70d7dd27SAmit Sunil Dhamne .probe_type = PROBE_PREFER_ASYNCHRONOUS, 766*70d7dd27SAmit Sunil Dhamne }, 767*70d7dd27SAmit Sunil Dhamne .probe = max77759_charger_probe, 768*70d7dd27SAmit Sunil Dhamne .id_table = max77759_charger_id, 769*70d7dd27SAmit Sunil Dhamne }; 770*70d7dd27SAmit Sunil Dhamne module_platform_driver(max77759_charger_driver); 771*70d7dd27SAmit Sunil Dhamne 772*70d7dd27SAmit Sunil Dhamne MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>"); 773*70d7dd27SAmit Sunil Dhamne MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); 774*70d7dd27SAmit Sunil Dhamne MODULE_LICENSE("GPL"); 775