xref: /linux/drivers/power/supply/s2mu005-battery.c (revision 59bd5ae0db22566e2b961742126269c151d587c7)
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, &current_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, &current_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