1*aa213279SYassine Oudjana // SPDX-License-Identifier: GPL-2.0 2*aa213279SYassine Oudjana /* 3*aa213279SYassine Oudjana * Battery Fuel Gauge Driver for Samsung S2MU005 PMIC. 4*aa213279SYassine Oudjana * 5*aa213279SYassine Oudjana * Copyright (C) 2015 Samsung Electronics 6*aa213279SYassine Oudjana * Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com> 7*aa213279SYassine Oudjana * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> 8*aa213279SYassine Oudjana */ 9*aa213279SYassine Oudjana 10*aa213279SYassine Oudjana #include <linux/delay.h> 11*aa213279SYassine Oudjana #include <linux/i2c.h> 12*aa213279SYassine Oudjana #include <linux/interrupt.h> 13*aa213279SYassine Oudjana #include <linux/mod_devicetable.h> 14*aa213279SYassine Oudjana #include <linux/mutex.h> 15*aa213279SYassine Oudjana #include <linux/power_supply.h> 16*aa213279SYassine Oudjana #include <linux/property.h> 17*aa213279SYassine Oudjana #include <linux/regmap.h> 18*aa213279SYassine Oudjana #include <linux/units.h> 19*aa213279SYassine Oudjana 20*aa213279SYassine Oudjana #define S2MU005_FG_REG_STATUS 0x00 21*aa213279SYassine Oudjana #define S2MU005_FG_REG_IRQ 0x02 22*aa213279SYassine Oudjana #define S2MU005_FG_REG_RVBAT 0x04 23*aa213279SYassine Oudjana #define S2MU005_FG_REG_RCURCC 0x06 24*aa213279SYassine Oudjana #define S2MU005_FG_REG_RSOC 0x08 25*aa213279SYassine Oudjana #define S2MU005_FG_REG_MONOUT 0x0a 26*aa213279SYassine Oudjana #define S2MU005_FG_REG_MONOUTSEL 0x0c 27*aa213279SYassine Oudjana #define S2MU005_FG_REG_RBATCAP 0x0e 28*aa213279SYassine Oudjana #define S2MU005_FG_REG_RZADJ 0x12 29*aa213279SYassine Oudjana #define S2MU005_FG_REG_RBATZ0 0x16 30*aa213279SYassine Oudjana #define S2MU005_FG_REG_RBATZ1 0x18 31*aa213279SYassine Oudjana #define S2MU005_FG_REG_IRQLVL 0x1a 32*aa213279SYassine Oudjana #define S2MU005_FG_REG_START 0x1e 33*aa213279SYassine Oudjana 34*aa213279SYassine Oudjana #define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26 35*aa213279SYassine Oudjana #define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27 36*aa213279SYassine Oudjana 37*aa213279SYassine Oudjana struct s2mu005_fg { 38*aa213279SYassine Oudjana struct device *dev; 39*aa213279SYassine Oudjana struct regmap *regmap; 40*aa213279SYassine Oudjana struct power_supply *psy; 41*aa213279SYassine Oudjana struct mutex monout_mutex; 42*aa213279SYassine Oudjana }; 43*aa213279SYassine Oudjana 44*aa213279SYassine Oudjana static const struct regmap_config s2mu005_fg_regmap_config = { 45*aa213279SYassine Oudjana .reg_bits = 8, 46*aa213279SYassine Oudjana .val_bits = 16, 47*aa213279SYassine Oudjana .val_format_endian = REGMAP_ENDIAN_LITTLE, 48*aa213279SYassine Oudjana }; 49*aa213279SYassine Oudjana 50*aa213279SYassine Oudjana static irqreturn_t s2mu005_handle_irq(int irq, void *data) 51*aa213279SYassine Oudjana { 52*aa213279SYassine Oudjana struct s2mu005_fg *priv = data; 53*aa213279SYassine Oudjana 54*aa213279SYassine Oudjana msleep(100); 55*aa213279SYassine Oudjana power_supply_changed(priv->psy); 56*aa213279SYassine Oudjana 57*aa213279SYassine Oudjana return IRQ_HANDLED; 58*aa213279SYassine Oudjana } 59*aa213279SYassine Oudjana 60*aa213279SYassine Oudjana static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value) 61*aa213279SYassine Oudjana { 62*aa213279SYassine Oudjana struct regmap *regmap = priv->regmap; 63*aa213279SYassine Oudjana u32 val; 64*aa213279SYassine Oudjana int ret; 65*aa213279SYassine Oudjana 66*aa213279SYassine Oudjana ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val); 67*aa213279SYassine Oudjana if (ret < 0) { 68*aa213279SYassine Oudjana dev_err(priv->dev, "failed to read voltage register (%d)\n", ret); 69*aa213279SYassine Oudjana return ret; 70*aa213279SYassine Oudjana } 71*aa213279SYassine Oudjana 72*aa213279SYassine Oudjana *value = (val * MICRO) >> 13; 73*aa213279SYassine Oudjana 74*aa213279SYassine Oudjana return 0; 75*aa213279SYassine Oudjana } 76*aa213279SYassine Oudjana 77*aa213279SYassine Oudjana static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value) 78*aa213279SYassine Oudjana { 79*aa213279SYassine Oudjana struct regmap *regmap = priv->regmap; 80*aa213279SYassine Oudjana u32 val; 81*aa213279SYassine Oudjana int ret; 82*aa213279SYassine Oudjana 83*aa213279SYassine Oudjana mutex_lock(&priv->monout_mutex); 84*aa213279SYassine Oudjana 85*aa213279SYassine Oudjana ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL, 86*aa213279SYassine Oudjana S2MU005_FG_MONOUTSEL_AVGVOLTAGE); 87*aa213279SYassine Oudjana if (ret < 0) { 88*aa213279SYassine Oudjana dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n", 89*aa213279SYassine Oudjana ret); 90*aa213279SYassine Oudjana goto unlock; 91*aa213279SYassine Oudjana } 92*aa213279SYassine Oudjana 93*aa213279SYassine Oudjana ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val); 94*aa213279SYassine Oudjana if (ret < 0) { 95*aa213279SYassine Oudjana dev_err(priv->dev, "failed to read current register (%d)\n", ret); 96*aa213279SYassine Oudjana goto unlock; 97*aa213279SYassine Oudjana } 98*aa213279SYassine Oudjana 99*aa213279SYassine Oudjana *value = (val * MICRO) >> 12; 100*aa213279SYassine Oudjana 101*aa213279SYassine Oudjana unlock: 102*aa213279SYassine Oudjana mutex_unlock(&priv->monout_mutex); 103*aa213279SYassine Oudjana 104*aa213279SYassine Oudjana return ret; 105*aa213279SYassine Oudjana } 106*aa213279SYassine Oudjana static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value) 107*aa213279SYassine Oudjana { 108*aa213279SYassine Oudjana struct regmap *regmap = priv->regmap; 109*aa213279SYassine Oudjana u32 val; 110*aa213279SYassine Oudjana int ret; 111*aa213279SYassine Oudjana 112*aa213279SYassine Oudjana ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val); 113*aa213279SYassine Oudjana if (ret < 0) { 114*aa213279SYassine Oudjana dev_err(priv->dev, "failed to read current register (%d)\n", ret); 115*aa213279SYassine Oudjana return ret; 116*aa213279SYassine Oudjana } 117*aa213279SYassine Oudjana 118*aa213279SYassine Oudjana *value = -((s16)val * MICRO) >> 12; 119*aa213279SYassine Oudjana 120*aa213279SYassine Oudjana return 0; 121*aa213279SYassine Oudjana } 122*aa213279SYassine Oudjana 123*aa213279SYassine Oudjana static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value) 124*aa213279SYassine Oudjana { 125*aa213279SYassine Oudjana struct regmap *regmap = priv->regmap; 126*aa213279SYassine Oudjana u32 val; 127*aa213279SYassine Oudjana int ret; 128*aa213279SYassine Oudjana 129*aa213279SYassine Oudjana mutex_lock(&priv->monout_mutex); 130*aa213279SYassine Oudjana 131*aa213279SYassine Oudjana ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL, 132*aa213279SYassine Oudjana S2MU005_FG_MONOUTSEL_AVGCURRENT); 133*aa213279SYassine Oudjana if (ret < 0) { 134*aa213279SYassine Oudjana dev_err(priv->dev, "failed to enable average current monitoring (%d)\n", 135*aa213279SYassine Oudjana ret); 136*aa213279SYassine Oudjana goto unlock; 137*aa213279SYassine Oudjana } 138*aa213279SYassine Oudjana 139*aa213279SYassine Oudjana ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val); 140*aa213279SYassine Oudjana if (ret < 0) { 141*aa213279SYassine Oudjana dev_err(priv->dev, "failed to read current register (%d)\n", ret); 142*aa213279SYassine Oudjana goto unlock; 143*aa213279SYassine Oudjana } 144*aa213279SYassine Oudjana 145*aa213279SYassine Oudjana *value = -((s16)val * MICRO) >> 12; 146*aa213279SYassine Oudjana 147*aa213279SYassine Oudjana unlock: 148*aa213279SYassine Oudjana mutex_unlock(&priv->monout_mutex); 149*aa213279SYassine Oudjana 150*aa213279SYassine Oudjana return ret; 151*aa213279SYassine Oudjana } 152*aa213279SYassine Oudjana 153*aa213279SYassine Oudjana static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value) 154*aa213279SYassine Oudjana { 155*aa213279SYassine Oudjana struct regmap *regmap = priv->regmap; 156*aa213279SYassine Oudjana u32 val; 157*aa213279SYassine Oudjana int ret; 158*aa213279SYassine Oudjana 159*aa213279SYassine Oudjana ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val); 160*aa213279SYassine Oudjana if (ret < 0) { 161*aa213279SYassine Oudjana dev_err(priv->dev, "failed to read capacity register (%d)\n", ret); 162*aa213279SYassine Oudjana return ret; 163*aa213279SYassine Oudjana } 164*aa213279SYassine Oudjana 165*aa213279SYassine Oudjana *value = (val * CENTI) >> 14; 166*aa213279SYassine Oudjana 167*aa213279SYassine Oudjana return 0; 168*aa213279SYassine Oudjana } 169*aa213279SYassine Oudjana 170*aa213279SYassine Oudjana static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value) 171*aa213279SYassine Oudjana { 172*aa213279SYassine Oudjana int current_now, current_avg, capacity; 173*aa213279SYassine Oudjana int ret; 174*aa213279SYassine Oudjana 175*aa213279SYassine Oudjana ret = s2mu005_fg_get_current_now(priv, ¤t_now); 176*aa213279SYassine Oudjana if (ret < 0) 177*aa213279SYassine Oudjana return ret; 178*aa213279SYassine Oudjana 179*aa213279SYassine Oudjana ret = s2mu005_fg_get_current_avg(priv, ¤t_avg); 180*aa213279SYassine Oudjana if (ret < 0) 181*aa213279SYassine Oudjana return ret; 182*aa213279SYassine Oudjana 183*aa213279SYassine Oudjana /* 184*aa213279SYassine Oudjana * Verify both current values reported to reduce inaccuracies due to 185*aa213279SYassine Oudjana * internal hysteresis. 186*aa213279SYassine Oudjana */ 187*aa213279SYassine Oudjana if (current_now < 0 && current_avg < 0) { 188*aa213279SYassine Oudjana *value = POWER_SUPPLY_STATUS_DISCHARGING; 189*aa213279SYassine Oudjana } else if (current_now == 0) { 190*aa213279SYassine Oudjana *value = POWER_SUPPLY_STATUS_NOT_CHARGING; 191*aa213279SYassine Oudjana } else { 192*aa213279SYassine Oudjana *value = POWER_SUPPLY_STATUS_CHARGING; 193*aa213279SYassine Oudjana 194*aa213279SYassine Oudjana ret = s2mu005_fg_get_capacity(priv, &capacity); 195*aa213279SYassine Oudjana if (!ret && capacity > 98) 196*aa213279SYassine Oudjana *value = POWER_SUPPLY_STATUS_FULL; 197*aa213279SYassine Oudjana return ret; 198*aa213279SYassine Oudjana } 199*aa213279SYassine Oudjana 200*aa213279SYassine Oudjana return 0; 201*aa213279SYassine Oudjana } 202*aa213279SYassine Oudjana 203*aa213279SYassine Oudjana static const enum power_supply_property s2mu005_fg_properties[] = { 204*aa213279SYassine Oudjana POWER_SUPPLY_PROP_VOLTAGE_NOW, 205*aa213279SYassine Oudjana POWER_SUPPLY_PROP_VOLTAGE_AVG, 206*aa213279SYassine Oudjana POWER_SUPPLY_PROP_CURRENT_NOW, 207*aa213279SYassine Oudjana POWER_SUPPLY_PROP_CURRENT_AVG, 208*aa213279SYassine Oudjana POWER_SUPPLY_PROP_CAPACITY, 209*aa213279SYassine Oudjana POWER_SUPPLY_PROP_STATUS, 210*aa213279SYassine Oudjana }; 211*aa213279SYassine Oudjana 212*aa213279SYassine Oudjana static int s2mu005_fg_get_property(struct power_supply *psy, 213*aa213279SYassine Oudjana enum power_supply_property psp, 214*aa213279SYassine Oudjana union power_supply_propval *val) 215*aa213279SYassine Oudjana { 216*aa213279SYassine Oudjana struct s2mu005_fg *priv = power_supply_get_drvdata(psy); 217*aa213279SYassine Oudjana 218*aa213279SYassine Oudjana switch (psp) { 219*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_VOLTAGE_NOW: 220*aa213279SYassine Oudjana return s2mu005_fg_get_voltage_now(priv, &val->intval); 221*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_VOLTAGE_AVG: 222*aa213279SYassine Oudjana return s2mu005_fg_get_voltage_avg(priv, &val->intval); 223*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_CURRENT_NOW: 224*aa213279SYassine Oudjana return s2mu005_fg_get_current_now(priv, &val->intval); 225*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_CURRENT_AVG: 226*aa213279SYassine Oudjana return s2mu005_fg_get_current_avg(priv, &val->intval); 227*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_CAPACITY: 228*aa213279SYassine Oudjana return s2mu005_fg_get_capacity(priv, &val->intval); 229*aa213279SYassine Oudjana case POWER_SUPPLY_PROP_STATUS: 230*aa213279SYassine Oudjana return s2mu005_fg_get_status(priv, &val->intval); 231*aa213279SYassine Oudjana default: 232*aa213279SYassine Oudjana return -EINVAL; 233*aa213279SYassine Oudjana } 234*aa213279SYassine Oudjana } 235*aa213279SYassine Oudjana 236*aa213279SYassine Oudjana static const struct power_supply_desc s2mu005_fg_desc = { 237*aa213279SYassine Oudjana .name = "s2mu005-fuel-gauge", 238*aa213279SYassine Oudjana .type = POWER_SUPPLY_TYPE_BATTERY, 239*aa213279SYassine Oudjana .properties = s2mu005_fg_properties, 240*aa213279SYassine Oudjana .num_properties = ARRAY_SIZE(s2mu005_fg_properties), 241*aa213279SYassine Oudjana .get_property = s2mu005_fg_get_property, 242*aa213279SYassine Oudjana }; 243*aa213279SYassine Oudjana 244*aa213279SYassine Oudjana static int s2mu005_fg_i2c_probe(struct i2c_client *client) 245*aa213279SYassine Oudjana { 246*aa213279SYassine Oudjana struct device *dev = &client->dev; 247*aa213279SYassine Oudjana struct s2mu005_fg *priv; 248*aa213279SYassine Oudjana struct power_supply_config psy_cfg = {}; 249*aa213279SYassine Oudjana const struct power_supply_desc *psy_desc; 250*aa213279SYassine Oudjana int ret; 251*aa213279SYassine Oudjana 252*aa213279SYassine Oudjana priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 253*aa213279SYassine Oudjana if (!priv) 254*aa213279SYassine Oudjana return -ENOMEM; 255*aa213279SYassine Oudjana 256*aa213279SYassine Oudjana dev_set_drvdata(dev, priv); 257*aa213279SYassine Oudjana priv->dev = dev; 258*aa213279SYassine Oudjana 259*aa213279SYassine Oudjana priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config); 260*aa213279SYassine Oudjana if (IS_ERR(priv->regmap)) 261*aa213279SYassine Oudjana return dev_err_probe(dev, PTR_ERR(priv->regmap), 262*aa213279SYassine Oudjana "failed to initialize regmap\n"); 263*aa213279SYassine Oudjana 264*aa213279SYassine Oudjana ret = devm_mutex_init(dev, &priv->monout_mutex); 265*aa213279SYassine Oudjana if (ret) 266*aa213279SYassine Oudjana dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n"); 267*aa213279SYassine Oudjana 268*aa213279SYassine Oudjana psy_desc = device_get_match_data(dev); 269*aa213279SYassine Oudjana 270*aa213279SYassine Oudjana psy_cfg.drv_data = priv; 271*aa213279SYassine Oudjana psy_cfg.fwnode = dev_fwnode(dev); 272*aa213279SYassine Oudjana priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg); 273*aa213279SYassine Oudjana if (IS_ERR(priv->psy)) 274*aa213279SYassine Oudjana return dev_err_probe(dev, PTR_ERR(priv->psy), 275*aa213279SYassine Oudjana "failed to register power supply subsystem\n"); 276*aa213279SYassine Oudjana 277*aa213279SYassine Oudjana ret = devm_request_threaded_irq(priv->dev, client->irq, NULL, 278*aa213279SYassine Oudjana s2mu005_handle_irq, IRQF_ONESHOT, 279*aa213279SYassine Oudjana psy_desc->name, priv); 280*aa213279SYassine Oudjana if (ret) 281*aa213279SYassine Oudjana dev_err_probe(dev, ret, "failed to request IRQ\n"); 282*aa213279SYassine Oudjana 283*aa213279SYassine Oudjana return 0; 284*aa213279SYassine Oudjana } 285*aa213279SYassine Oudjana 286*aa213279SYassine Oudjana static const struct of_device_id s2mu005_fg_of_match_table[] = { 287*aa213279SYassine Oudjana { 288*aa213279SYassine Oudjana .compatible = "samsung,s2mu005-fuel-gauge", 289*aa213279SYassine Oudjana .data = &s2mu005_fg_desc, 290*aa213279SYassine Oudjana }, 291*aa213279SYassine Oudjana { } 292*aa213279SYassine Oudjana }; 293*aa213279SYassine Oudjana MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table); 294*aa213279SYassine Oudjana 295*aa213279SYassine Oudjana static struct i2c_driver s2mu005_fg_i2c_driver = { 296*aa213279SYassine Oudjana .probe = s2mu005_fg_i2c_probe, 297*aa213279SYassine Oudjana .driver = { 298*aa213279SYassine Oudjana .name = "s2mu005-fuel-gauge", 299*aa213279SYassine Oudjana .of_match_table = s2mu005_fg_of_match_table, 300*aa213279SYassine Oudjana }, 301*aa213279SYassine Oudjana }; 302*aa213279SYassine Oudjana module_i2c_driver(s2mu005_fg_i2c_driver); 303*aa213279SYassine Oudjana 304*aa213279SYassine Oudjana MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver"); 305*aa213279SYassine Oudjana MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>"); 306*aa213279SYassine Oudjana MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); 307*aa213279SYassine Oudjana MODULE_LICENSE("GPL"); 308