xref: /linux/drivers/power/supply/da9150-fg.c (revision bbfd5594756011167b8f8de9a00e0c946afda1e6)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2a419b4fdSAdam Thomson /*
3a419b4fdSAdam Thomson  * DA9150 Fuel-Gauge Driver
4a419b4fdSAdam Thomson  *
5a419b4fdSAdam Thomson  * Copyright (c) 2015 Dialog Semiconductor
6a419b4fdSAdam Thomson  *
7a419b4fdSAdam Thomson  * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
8a419b4fdSAdam Thomson  */
9a419b4fdSAdam Thomson 
10a419b4fdSAdam Thomson #include <linux/kernel.h>
11a419b4fdSAdam Thomson #include <linux/module.h>
12a419b4fdSAdam Thomson #include <linux/platform_device.h>
13a419b4fdSAdam Thomson #include <linux/of.h>
14a419b4fdSAdam Thomson #include <linux/slab.h>
15a419b4fdSAdam Thomson #include <linux/interrupt.h>
16a419b4fdSAdam Thomson #include <linux/delay.h>
17a419b4fdSAdam Thomson #include <linux/power_supply.h>
18a419b4fdSAdam Thomson #include <linux/list.h>
19a419b4fdSAdam Thomson #include <asm/div64.h>
20a419b4fdSAdam Thomson #include <linux/mfd/da9150/core.h>
21a419b4fdSAdam Thomson #include <linux/mfd/da9150/registers.h>
22419c0e9dSChristophe JAILLET #include <linux/devm-helpers.h>
23a419b4fdSAdam Thomson 
24a419b4fdSAdam Thomson /* Core2Wire */
25a419b4fdSAdam Thomson #define DA9150_QIF_READ		(0x0 << 7)
26a419b4fdSAdam Thomson #define DA9150_QIF_WRITE	(0x1 << 7)
27a419b4fdSAdam Thomson #define DA9150_QIF_CODE_MASK	0x7F
28a419b4fdSAdam Thomson 
29a419b4fdSAdam Thomson #define DA9150_QIF_BYTE_SIZE	8
30a419b4fdSAdam Thomson #define DA9150_QIF_BYTE_MASK	0xFF
31a419b4fdSAdam Thomson #define DA9150_QIF_SHORT_SIZE	2
32a419b4fdSAdam Thomson #define DA9150_QIF_LONG_SIZE	4
33a419b4fdSAdam Thomson 
34a419b4fdSAdam Thomson /* QIF Codes */
35a419b4fdSAdam Thomson #define DA9150_QIF_UAVG			6
36a419b4fdSAdam Thomson #define DA9150_QIF_UAVG_SIZE		DA9150_QIF_LONG_SIZE
37a419b4fdSAdam Thomson #define DA9150_QIF_IAVG			8
38a419b4fdSAdam Thomson #define DA9150_QIF_IAVG_SIZE		DA9150_QIF_LONG_SIZE
39a419b4fdSAdam Thomson #define DA9150_QIF_NTCAVG		12
40a419b4fdSAdam Thomson #define DA9150_QIF_NTCAVG_SIZE		DA9150_QIF_LONG_SIZE
41a419b4fdSAdam Thomson #define DA9150_QIF_SHUNT_VAL		36
42a419b4fdSAdam Thomson #define DA9150_QIF_SHUNT_VAL_SIZE	DA9150_QIF_SHORT_SIZE
43a419b4fdSAdam Thomson #define DA9150_QIF_SD_GAIN		38
44a419b4fdSAdam Thomson #define DA9150_QIF_SD_GAIN_SIZE		DA9150_QIF_LONG_SIZE
45a419b4fdSAdam Thomson #define DA9150_QIF_FCC_MAH		40
46a419b4fdSAdam Thomson #define DA9150_QIF_FCC_MAH_SIZE		DA9150_QIF_SHORT_SIZE
47a419b4fdSAdam Thomson #define DA9150_QIF_SOC_PCT		43
48a419b4fdSAdam Thomson #define DA9150_QIF_SOC_PCT_SIZE		DA9150_QIF_SHORT_SIZE
49a419b4fdSAdam Thomson #define DA9150_QIF_CHARGE_LIMIT		44
50a419b4fdSAdam Thomson #define DA9150_QIF_CHARGE_LIMIT_SIZE	DA9150_QIF_SHORT_SIZE
51a419b4fdSAdam Thomson #define DA9150_QIF_DISCHARGE_LIMIT	45
52a419b4fdSAdam Thomson #define DA9150_QIF_DISCHARGE_LIMIT_SIZE	DA9150_QIF_SHORT_SIZE
53a419b4fdSAdam Thomson #define DA9150_QIF_FW_MAIN_VER		118
54a419b4fdSAdam Thomson #define DA9150_QIF_FW_MAIN_VER_SIZE	DA9150_QIF_SHORT_SIZE
55a419b4fdSAdam Thomson #define DA9150_QIF_E_FG_STATUS		126
56a419b4fdSAdam Thomson #define DA9150_QIF_E_FG_STATUS_SIZE	DA9150_QIF_SHORT_SIZE
57a419b4fdSAdam Thomson #define DA9150_QIF_SYNC			127
58a419b4fdSAdam Thomson #define DA9150_QIF_SYNC_SIZE		DA9150_QIF_SHORT_SIZE
59a419b4fdSAdam Thomson #define DA9150_QIF_MAX_CODES		128
60a419b4fdSAdam Thomson 
61a419b4fdSAdam Thomson /* QIF Sync Timeout */
62a419b4fdSAdam Thomson #define DA9150_QIF_SYNC_TIMEOUT		1000
63a419b4fdSAdam Thomson #define DA9150_QIF_SYNC_RETRIES		10
64a419b4fdSAdam Thomson 
65a419b4fdSAdam Thomson /* QIF E_FG_STATUS */
66a419b4fdSAdam Thomson #define DA9150_FG_IRQ_LOW_SOC_MASK	(1 << 0)
67a419b4fdSAdam Thomson #define DA9150_FG_IRQ_HIGH_SOC_MASK	(1 << 1)
68a419b4fdSAdam Thomson #define DA9150_FG_IRQ_SOC_MASK	\
69a419b4fdSAdam Thomson 	(DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK)
70a419b4fdSAdam Thomson 
71a419b4fdSAdam Thomson /* Private data */
72a419b4fdSAdam Thomson struct da9150_fg {
73a419b4fdSAdam Thomson 	struct da9150 *da9150;
74a419b4fdSAdam Thomson 	struct device *dev;
75a419b4fdSAdam Thomson 
76a419b4fdSAdam Thomson 	struct mutex io_lock;
77a419b4fdSAdam Thomson 
78a419b4fdSAdam Thomson 	struct power_supply *battery;
79a419b4fdSAdam Thomson 	struct delayed_work work;
80a419b4fdSAdam Thomson 	u32 interval;
81a419b4fdSAdam Thomson 
82a419b4fdSAdam Thomson 	int warn_soc;
83a419b4fdSAdam Thomson 	int crit_soc;
84a419b4fdSAdam Thomson 	int soc;
85a419b4fdSAdam Thomson };
86a419b4fdSAdam Thomson 
87a419b4fdSAdam Thomson /* Battery Properties */
da9150_fg_read_attr(struct da9150_fg * fg,u8 code,u8 size)88a419b4fdSAdam Thomson static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size)
89a419b4fdSAdam Thomson 
90a419b4fdSAdam Thomson {
91fc5a7f03SGustavo A. R. Silva 	u8 buf[DA9150_QIF_LONG_SIZE];
92a419b4fdSAdam Thomson 	u8 read_addr;
93a419b4fdSAdam Thomson 	u32 res = 0;
94a419b4fdSAdam Thomson 	int i;
95a419b4fdSAdam Thomson 
96a419b4fdSAdam Thomson 	/* Set QIF code (READ mode) */
97a419b4fdSAdam Thomson 	read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ;
98a419b4fdSAdam Thomson 
99a419b4fdSAdam Thomson 	da9150_read_qif(fg->da9150, read_addr, size, buf);
100a419b4fdSAdam Thomson 	for (i = 0; i < size; ++i)
101a419b4fdSAdam Thomson 		res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE));
102a419b4fdSAdam Thomson 
103a419b4fdSAdam Thomson 	return res;
104a419b4fdSAdam Thomson }
105a419b4fdSAdam Thomson 
da9150_fg_write_attr(struct da9150_fg * fg,u8 code,u8 size,u32 val)106a419b4fdSAdam Thomson static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size,
107a419b4fdSAdam Thomson 				 u32 val)
108a419b4fdSAdam Thomson 
109a419b4fdSAdam Thomson {
110fc5a7f03SGustavo A. R. Silva 	u8 buf[DA9150_QIF_LONG_SIZE];
111a419b4fdSAdam Thomson 	u8 write_addr;
112a419b4fdSAdam Thomson 	int i;
113a419b4fdSAdam Thomson 
114a419b4fdSAdam Thomson 	/* Set QIF code (WRITE mode) */
115a419b4fdSAdam Thomson 	write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE;
116a419b4fdSAdam Thomson 
117a419b4fdSAdam Thomson 	for (i = 0; i < size; ++i) {
118a419b4fdSAdam Thomson 		buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) &
119a419b4fdSAdam Thomson 			 DA9150_QIF_BYTE_MASK;
120a419b4fdSAdam Thomson 	}
121a419b4fdSAdam Thomson 	da9150_write_qif(fg->da9150, write_addr, size, buf);
122a419b4fdSAdam Thomson }
123a419b4fdSAdam Thomson 
124a419b4fdSAdam Thomson /* Trigger QIF Sync to update QIF readable data */
da9150_fg_read_sync_start(struct da9150_fg * fg)125a419b4fdSAdam Thomson static void da9150_fg_read_sync_start(struct da9150_fg *fg)
126a419b4fdSAdam Thomson {
127a419b4fdSAdam Thomson 	int i = 0;
128a419b4fdSAdam Thomson 	u32 res = 0;
129a419b4fdSAdam Thomson 
130a419b4fdSAdam Thomson 	mutex_lock(&fg->io_lock);
131a419b4fdSAdam Thomson 
132a419b4fdSAdam Thomson 	/* Check if QIF sync already requested, and write to sync if not */
133a419b4fdSAdam Thomson 	res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
134a419b4fdSAdam Thomson 				  DA9150_QIF_SYNC_SIZE);
135a419b4fdSAdam Thomson 	if (res > 0)
136a419b4fdSAdam Thomson 		da9150_fg_write_attr(fg, DA9150_QIF_SYNC,
137a419b4fdSAdam Thomson 				     DA9150_QIF_SYNC_SIZE, 0);
138a419b4fdSAdam Thomson 
139a419b4fdSAdam Thomson 	/* Wait for sync to complete */
140a419b4fdSAdam Thomson 	res = 0;
141a419b4fdSAdam Thomson 	while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
142a419b4fdSAdam Thomson 		usleep_range(DA9150_QIF_SYNC_TIMEOUT,
143a419b4fdSAdam Thomson 			     DA9150_QIF_SYNC_TIMEOUT * 2);
144a419b4fdSAdam Thomson 		res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
145a419b4fdSAdam Thomson 					  DA9150_QIF_SYNC_SIZE);
146a419b4fdSAdam Thomson 	}
147a419b4fdSAdam Thomson 
148a419b4fdSAdam Thomson 	/* Check if sync completed */
149a419b4fdSAdam Thomson 	if (res == 0)
150a419b4fdSAdam Thomson 		dev_err(fg->dev, "Failed to perform QIF read sync!\n");
151a419b4fdSAdam Thomson }
152a419b4fdSAdam Thomson 
153a419b4fdSAdam Thomson /*
154a419b4fdSAdam Thomson  * Should always be called after QIF sync read has been performed, and all
155a419b4fdSAdam Thomson  * attributes required have been accessed.
156a419b4fdSAdam Thomson  */
da9150_fg_read_sync_end(struct da9150_fg * fg)157a419b4fdSAdam Thomson static inline void da9150_fg_read_sync_end(struct da9150_fg *fg)
158a419b4fdSAdam Thomson {
159a419b4fdSAdam Thomson 	mutex_unlock(&fg->io_lock);
160a419b4fdSAdam Thomson }
161a419b4fdSAdam Thomson 
162a419b4fdSAdam Thomson /* Sync read of single QIF attribute */
da9150_fg_read_attr_sync(struct da9150_fg * fg,u8 code,u8 size)163a419b4fdSAdam Thomson static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size)
164a419b4fdSAdam Thomson {
165a419b4fdSAdam Thomson 	u32 val;
166a419b4fdSAdam Thomson 
167a419b4fdSAdam Thomson 	da9150_fg_read_sync_start(fg);
168a419b4fdSAdam Thomson 	val = da9150_fg_read_attr(fg, code, size);
169a419b4fdSAdam Thomson 	da9150_fg_read_sync_end(fg);
170a419b4fdSAdam Thomson 
171a419b4fdSAdam Thomson 	return val;
172a419b4fdSAdam Thomson }
173a419b4fdSAdam Thomson 
174a419b4fdSAdam Thomson /* Wait for QIF Sync, write QIF data and wait for ack */
da9150_fg_write_attr_sync(struct da9150_fg * fg,u8 code,u8 size,u32 val)175a419b4fdSAdam Thomson static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size,
176a419b4fdSAdam Thomson 				      u32 val)
177a419b4fdSAdam Thomson {
178a419b4fdSAdam Thomson 	int i = 0;
179a419b4fdSAdam Thomson 	u32 res = 0, sync_val;
180a419b4fdSAdam Thomson 
181a419b4fdSAdam Thomson 	mutex_lock(&fg->io_lock);
182a419b4fdSAdam Thomson 
183a419b4fdSAdam Thomson 	/* Check if QIF sync already requested */
184a419b4fdSAdam Thomson 	res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
185a419b4fdSAdam Thomson 				  DA9150_QIF_SYNC_SIZE);
186a419b4fdSAdam Thomson 
187a419b4fdSAdam Thomson 	/* Wait for an existing sync to complete */
188a419b4fdSAdam Thomson 	while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
189a419b4fdSAdam Thomson 		usleep_range(DA9150_QIF_SYNC_TIMEOUT,
190a419b4fdSAdam Thomson 			     DA9150_QIF_SYNC_TIMEOUT * 2);
191a419b4fdSAdam Thomson 		res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
192a419b4fdSAdam Thomson 					  DA9150_QIF_SYNC_SIZE);
193a419b4fdSAdam Thomson 	}
194a419b4fdSAdam Thomson 
195a419b4fdSAdam Thomson 	if (res == 0) {
196a419b4fdSAdam Thomson 		dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n");
197a419b4fdSAdam Thomson 		mutex_unlock(&fg->io_lock);
198a419b4fdSAdam Thomson 		return;
199a419b4fdSAdam Thomson 	}
200a419b4fdSAdam Thomson 
201a419b4fdSAdam Thomson 	/* Write value for QIF code */
202a419b4fdSAdam Thomson 	da9150_fg_write_attr(fg, code, size, val);
203a419b4fdSAdam Thomson 
204a419b4fdSAdam Thomson 	/* Wait for write acknowledgment */
205a419b4fdSAdam Thomson 	i = 0;
206a419b4fdSAdam Thomson 	sync_val = res;
207a419b4fdSAdam Thomson 	while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
208a419b4fdSAdam Thomson 		usleep_range(DA9150_QIF_SYNC_TIMEOUT,
209a419b4fdSAdam Thomson 			     DA9150_QIF_SYNC_TIMEOUT * 2);
210a419b4fdSAdam Thomson 		res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
211a419b4fdSAdam Thomson 					  DA9150_QIF_SYNC_SIZE);
212a419b4fdSAdam Thomson 	}
213a419b4fdSAdam Thomson 
214a419b4fdSAdam Thomson 	mutex_unlock(&fg->io_lock);
215a419b4fdSAdam Thomson 
216a419b4fdSAdam Thomson 	/* Check write was actually successful */
217a419b4fdSAdam Thomson 	if (res != (sync_val + 1))
218a419b4fdSAdam Thomson 		dev_err(fg->dev, "Error performing QIF sync write for code %d\n",
219a419b4fdSAdam Thomson 			code);
220a419b4fdSAdam Thomson }
221a419b4fdSAdam Thomson 
222a419b4fdSAdam Thomson /* Power Supply attributes */
da9150_fg_capacity(struct da9150_fg * fg,union power_supply_propval * val)223a419b4fdSAdam Thomson static int da9150_fg_capacity(struct da9150_fg *fg,
224a419b4fdSAdam Thomson 			      union power_supply_propval *val)
225a419b4fdSAdam Thomson {
226a419b4fdSAdam Thomson 	val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT,
227a419b4fdSAdam Thomson 					       DA9150_QIF_SOC_PCT_SIZE);
228a419b4fdSAdam Thomson 
229a419b4fdSAdam Thomson 	if (val->intval > 100)
230a419b4fdSAdam Thomson 		val->intval = 100;
231a419b4fdSAdam Thomson 
232a419b4fdSAdam Thomson 	return 0;
233a419b4fdSAdam Thomson }
234a419b4fdSAdam Thomson 
da9150_fg_current_avg(struct da9150_fg * fg,union power_supply_propval * val)235a419b4fdSAdam Thomson static int da9150_fg_current_avg(struct da9150_fg *fg,
236a419b4fdSAdam Thomson 				 union power_supply_propval *val)
237a419b4fdSAdam Thomson {
238a419b4fdSAdam Thomson 	u32 iavg, sd_gain, shunt_val;
239a419b4fdSAdam Thomson 	u64 div, res;
240a419b4fdSAdam Thomson 
241a419b4fdSAdam Thomson 	da9150_fg_read_sync_start(fg);
242a419b4fdSAdam Thomson 	iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG,
243a419b4fdSAdam Thomson 				   DA9150_QIF_IAVG_SIZE);
244a419b4fdSAdam Thomson 	shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL,
245a419b4fdSAdam Thomson 					DA9150_QIF_SHUNT_VAL_SIZE);
246a419b4fdSAdam Thomson 	sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN,
247a419b4fdSAdam Thomson 				      DA9150_QIF_SD_GAIN_SIZE);
248a419b4fdSAdam Thomson 	da9150_fg_read_sync_end(fg);
249a419b4fdSAdam Thomson 
250*3fb3cb43SAndrey Vatoropin 	div = 65536ULL * sd_gain * shunt_val;
251a419b4fdSAdam Thomson 	do_div(div, 1000000);
252*3fb3cb43SAndrey Vatoropin 	res = 1000000ULL * iavg;
253a419b4fdSAdam Thomson 	do_div(res, div);
254a419b4fdSAdam Thomson 
255a419b4fdSAdam Thomson 	val->intval = (int) res;
256a419b4fdSAdam Thomson 
257a419b4fdSAdam Thomson 	return 0;
258a419b4fdSAdam Thomson }
259a419b4fdSAdam Thomson 
da9150_fg_voltage_avg(struct da9150_fg * fg,union power_supply_propval * val)260a419b4fdSAdam Thomson static int da9150_fg_voltage_avg(struct da9150_fg *fg,
261a419b4fdSAdam Thomson 				 union power_supply_propval *val)
262a419b4fdSAdam Thomson {
263a419b4fdSAdam Thomson 	u64 res;
264a419b4fdSAdam Thomson 
265a419b4fdSAdam Thomson 	val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG,
266a419b4fdSAdam Thomson 					       DA9150_QIF_UAVG_SIZE);
267a419b4fdSAdam Thomson 
268a419b4fdSAdam Thomson 	res = (u64) (val->intval * 186ULL);
269a419b4fdSAdam Thomson 	do_div(res, 10000);
270a419b4fdSAdam Thomson 	val->intval = (int) res;
271a419b4fdSAdam Thomson 
272a419b4fdSAdam Thomson 	return 0;
273a419b4fdSAdam Thomson }
274a419b4fdSAdam Thomson 
da9150_fg_charge_full(struct da9150_fg * fg,union power_supply_propval * val)275a419b4fdSAdam Thomson static int da9150_fg_charge_full(struct da9150_fg *fg,
276a419b4fdSAdam Thomson 				 union power_supply_propval *val)
277a419b4fdSAdam Thomson {
278a419b4fdSAdam Thomson 	val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH,
279a419b4fdSAdam Thomson 					       DA9150_QIF_FCC_MAH_SIZE);
280a419b4fdSAdam Thomson 
281a419b4fdSAdam Thomson 	val->intval = val->intval * 1000;
282a419b4fdSAdam Thomson 
283a419b4fdSAdam Thomson 	return 0;
284a419b4fdSAdam Thomson }
285a419b4fdSAdam Thomson 
286a419b4fdSAdam Thomson /*
287a419b4fdSAdam Thomson  * Temperature reading from device is only valid if battery/system provides
288a419b4fdSAdam Thomson  * valid NTC to associated pin of DA9150 chip.
289a419b4fdSAdam Thomson  */
da9150_fg_temp(struct da9150_fg * fg,union power_supply_propval * val)290a419b4fdSAdam Thomson static int da9150_fg_temp(struct da9150_fg *fg,
291a419b4fdSAdam Thomson 			  union power_supply_propval *val)
292a419b4fdSAdam Thomson {
293a419b4fdSAdam Thomson 	val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG,
294a419b4fdSAdam Thomson 					       DA9150_QIF_NTCAVG_SIZE);
295a419b4fdSAdam Thomson 
296a419b4fdSAdam Thomson 	val->intval = (val->intval * 10) / 1048576;
297a419b4fdSAdam Thomson 
298a419b4fdSAdam Thomson 	return 0;
299a419b4fdSAdam Thomson }
300a419b4fdSAdam Thomson 
301a419b4fdSAdam Thomson static enum power_supply_property da9150_fg_props[] = {
302a419b4fdSAdam Thomson 	POWER_SUPPLY_PROP_CAPACITY,
303a419b4fdSAdam Thomson 	POWER_SUPPLY_PROP_CURRENT_AVG,
304a419b4fdSAdam Thomson 	POWER_SUPPLY_PROP_VOLTAGE_AVG,
305a419b4fdSAdam Thomson 	POWER_SUPPLY_PROP_CHARGE_FULL,
306a419b4fdSAdam Thomson 	POWER_SUPPLY_PROP_TEMP,
307a419b4fdSAdam Thomson };
308a419b4fdSAdam Thomson 
da9150_fg_get_prop(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)309a419b4fdSAdam Thomson static int da9150_fg_get_prop(struct power_supply *psy,
310a419b4fdSAdam Thomson 			      enum power_supply_property psp,
311a419b4fdSAdam Thomson 			      union power_supply_propval *val)
312a419b4fdSAdam Thomson {
313a419b4fdSAdam Thomson 	struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent);
314a419b4fdSAdam Thomson 	int ret;
315a419b4fdSAdam Thomson 
316a419b4fdSAdam Thomson 	switch (psp) {
317a419b4fdSAdam Thomson 	case POWER_SUPPLY_PROP_CAPACITY:
318a419b4fdSAdam Thomson 		ret = da9150_fg_capacity(fg, val);
319a419b4fdSAdam Thomson 		break;
320a419b4fdSAdam Thomson 	case POWER_SUPPLY_PROP_CURRENT_AVG:
321a419b4fdSAdam Thomson 		ret = da9150_fg_current_avg(fg, val);
322a419b4fdSAdam Thomson 		break;
323a419b4fdSAdam Thomson 	case POWER_SUPPLY_PROP_VOLTAGE_AVG:
324a419b4fdSAdam Thomson 		ret = da9150_fg_voltage_avg(fg, val);
325a419b4fdSAdam Thomson 		break;
326a419b4fdSAdam Thomson 	case POWER_SUPPLY_PROP_CHARGE_FULL:
327a419b4fdSAdam Thomson 		ret = da9150_fg_charge_full(fg, val);
328a419b4fdSAdam Thomson 		break;
329a419b4fdSAdam Thomson 	case POWER_SUPPLY_PROP_TEMP:
330a419b4fdSAdam Thomson 		ret = da9150_fg_temp(fg, val);
331a419b4fdSAdam Thomson 		break;
332a419b4fdSAdam Thomson 	default:
333a419b4fdSAdam Thomson 		ret = -EINVAL;
334a419b4fdSAdam Thomson 		break;
335a419b4fdSAdam Thomson 	}
336a419b4fdSAdam Thomson 
337a419b4fdSAdam Thomson 	return ret;
338a419b4fdSAdam Thomson }
339a419b4fdSAdam Thomson 
340a419b4fdSAdam Thomson /* Repeated SOC check */
da9150_fg_soc_changed(struct da9150_fg * fg)341a419b4fdSAdam Thomson static bool da9150_fg_soc_changed(struct da9150_fg *fg)
342a419b4fdSAdam Thomson {
343a419b4fdSAdam Thomson 	union power_supply_propval val;
344a419b4fdSAdam Thomson 
345a419b4fdSAdam Thomson 	da9150_fg_capacity(fg, &val);
346a419b4fdSAdam Thomson 	if (val.intval != fg->soc) {
347a419b4fdSAdam Thomson 		fg->soc = val.intval;
348a419b4fdSAdam Thomson 		return true;
349a419b4fdSAdam Thomson 	}
350a419b4fdSAdam Thomson 
351a419b4fdSAdam Thomson 	return false;
352a419b4fdSAdam Thomson }
353a419b4fdSAdam Thomson 
da9150_fg_work(struct work_struct * work)354a419b4fdSAdam Thomson static void da9150_fg_work(struct work_struct *work)
355a419b4fdSAdam Thomson {
356a419b4fdSAdam Thomson 	struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work);
357a419b4fdSAdam Thomson 
358a419b4fdSAdam Thomson 	/* Report if SOC has changed */
359a419b4fdSAdam Thomson 	if (da9150_fg_soc_changed(fg))
360a419b4fdSAdam Thomson 		power_supply_changed(fg->battery);
361a419b4fdSAdam Thomson 
362a419b4fdSAdam Thomson 	schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval));
363a419b4fdSAdam Thomson }
364a419b4fdSAdam Thomson 
365a419b4fdSAdam Thomson /* SOC level event configuration */
da9150_fg_soc_event_config(struct da9150_fg * fg)366a419b4fdSAdam Thomson static void da9150_fg_soc_event_config(struct da9150_fg *fg)
367a419b4fdSAdam Thomson {
368a419b4fdSAdam Thomson 	int soc;
369a419b4fdSAdam Thomson 
370a419b4fdSAdam Thomson 	soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT,
371a419b4fdSAdam Thomson 				       DA9150_QIF_SOC_PCT_SIZE);
372a419b4fdSAdam Thomson 
373a419b4fdSAdam Thomson 	if (soc > fg->warn_soc) {
374a419b4fdSAdam Thomson 		/* If SOC > warn level, set discharge warn level event */
375a419b4fdSAdam Thomson 		da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT,
376a419b4fdSAdam Thomson 					  DA9150_QIF_DISCHARGE_LIMIT_SIZE,
377a419b4fdSAdam Thomson 					  fg->warn_soc + 1);
378a419b4fdSAdam Thomson 	} else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) {
379a419b4fdSAdam Thomson 		/*
380a419b4fdSAdam Thomson 		 * If SOC <= warn level, set discharge crit level event,
381a419b4fdSAdam Thomson 		 * and set charge warn level event.
382a419b4fdSAdam Thomson 		 */
383a419b4fdSAdam Thomson 		da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT,
384a419b4fdSAdam Thomson 					  DA9150_QIF_DISCHARGE_LIMIT_SIZE,
385a419b4fdSAdam Thomson 					  fg->crit_soc + 1);
386a419b4fdSAdam Thomson 
387a419b4fdSAdam Thomson 		da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT,
388a419b4fdSAdam Thomson 					  DA9150_QIF_CHARGE_LIMIT_SIZE,
389a419b4fdSAdam Thomson 					  fg->warn_soc);
390a419b4fdSAdam Thomson 	} else if (soc <= fg->crit_soc) {
391a419b4fdSAdam Thomson 		/* If SOC <= crit level, set charge crit level event */
392a419b4fdSAdam Thomson 		da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT,
393a419b4fdSAdam Thomson 					  DA9150_QIF_CHARGE_LIMIT_SIZE,
394a419b4fdSAdam Thomson 					  fg->crit_soc);
395a419b4fdSAdam Thomson 	}
396a419b4fdSAdam Thomson }
397a419b4fdSAdam Thomson 
da9150_fg_irq(int irq,void * data)398a419b4fdSAdam Thomson static irqreturn_t da9150_fg_irq(int irq, void *data)
399a419b4fdSAdam Thomson {
400a419b4fdSAdam Thomson 	struct da9150_fg *fg = data;
401a419b4fdSAdam Thomson 	u32 e_fg_status;
402a419b4fdSAdam Thomson 
403a419b4fdSAdam Thomson 	/* Read FG IRQ status info */
404a419b4fdSAdam Thomson 	e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS,
405a419b4fdSAdam Thomson 					  DA9150_QIF_E_FG_STATUS_SIZE);
406a419b4fdSAdam Thomson 
407a419b4fdSAdam Thomson 	/* Handle warning/critical threhold events */
408a419b4fdSAdam Thomson 	if (e_fg_status & DA9150_FG_IRQ_SOC_MASK)
409a419b4fdSAdam Thomson 		da9150_fg_soc_event_config(fg);
410a419b4fdSAdam Thomson 
411a419b4fdSAdam Thomson 	/* Clear any FG IRQs */
412a419b4fdSAdam Thomson 	da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS,
413a419b4fdSAdam Thomson 			     DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status);
414a419b4fdSAdam Thomson 
415a419b4fdSAdam Thomson 	return IRQ_HANDLED;
416a419b4fdSAdam Thomson }
417a419b4fdSAdam Thomson 
da9150_fg_dt_pdata(struct device * dev)418a419b4fdSAdam Thomson static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev)
419a419b4fdSAdam Thomson {
420a419b4fdSAdam Thomson 	struct device_node *fg_node = dev->of_node;
421a419b4fdSAdam Thomson 	struct da9150_fg_pdata *pdata;
422a419b4fdSAdam Thomson 
423a419b4fdSAdam Thomson 	pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL);
424a419b4fdSAdam Thomson 	if (!pdata)
425a419b4fdSAdam Thomson 		return NULL;
426a419b4fdSAdam Thomson 
427a419b4fdSAdam Thomson 	of_property_read_u32(fg_node, "dlg,update-interval",
428a419b4fdSAdam Thomson 			     &pdata->update_interval);
429a419b4fdSAdam Thomson 	of_property_read_u8(fg_node, "dlg,warn-soc-level",
430a419b4fdSAdam Thomson 			    &pdata->warn_soc_lvl);
431a419b4fdSAdam Thomson 	of_property_read_u8(fg_node, "dlg,crit-soc-level",
432a419b4fdSAdam Thomson 			    &pdata->crit_soc_lvl);
433a419b4fdSAdam Thomson 
434a419b4fdSAdam Thomson 	return pdata;
435a419b4fdSAdam Thomson }
436a419b4fdSAdam Thomson 
437a419b4fdSAdam Thomson static const struct power_supply_desc fg_desc = {
438a419b4fdSAdam Thomson 	.name		= "da9150-fg",
439a419b4fdSAdam Thomson 	.type		= POWER_SUPPLY_TYPE_BATTERY,
440a419b4fdSAdam Thomson 	.properties	= da9150_fg_props,
441a419b4fdSAdam Thomson 	.num_properties	= ARRAY_SIZE(da9150_fg_props),
442a419b4fdSAdam Thomson 	.get_property	= da9150_fg_get_prop,
443a419b4fdSAdam Thomson };
444a419b4fdSAdam Thomson 
da9150_fg_probe(struct platform_device * pdev)445a419b4fdSAdam Thomson static int da9150_fg_probe(struct platform_device *pdev)
446a419b4fdSAdam Thomson {
447a419b4fdSAdam Thomson 	struct device *dev = &pdev->dev;
448a419b4fdSAdam Thomson 	struct da9150 *da9150 = dev_get_drvdata(dev->parent);
449a419b4fdSAdam Thomson 	struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev);
450a419b4fdSAdam Thomson 	struct da9150_fg *fg;
451a419b4fdSAdam Thomson 	int ver, irq, ret = 0;
452a419b4fdSAdam Thomson 
453a419b4fdSAdam Thomson 	fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
454a419b4fdSAdam Thomson 	if (fg == NULL)
455a419b4fdSAdam Thomson 		return -ENOMEM;
456a419b4fdSAdam Thomson 
457a419b4fdSAdam Thomson 	platform_set_drvdata(pdev, fg);
458a419b4fdSAdam Thomson 	fg->da9150 = da9150;
459a419b4fdSAdam Thomson 	fg->dev = dev;
460a419b4fdSAdam Thomson 
461a419b4fdSAdam Thomson 	mutex_init(&fg->io_lock);
462a419b4fdSAdam Thomson 
463a419b4fdSAdam Thomson 	/* Enable QIF */
464a419b4fdSAdam Thomson 	da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK,
465a419b4fdSAdam Thomson 			DA9150_FG_QIF_EN_MASK);
466a419b4fdSAdam Thomson 
467a419b4fdSAdam Thomson 	fg->battery = devm_power_supply_register(dev, &fg_desc, NULL);
468a419b4fdSAdam Thomson 	if (IS_ERR(fg->battery)) {
469a419b4fdSAdam Thomson 		ret = PTR_ERR(fg->battery);
470a419b4fdSAdam Thomson 		return ret;
471a419b4fdSAdam Thomson 	}
472a419b4fdSAdam Thomson 
473a419b4fdSAdam Thomson 	ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER,
474a419b4fdSAdam Thomson 				  DA9150_QIF_FW_MAIN_VER_SIZE);
475a419b4fdSAdam Thomson 	dev_info(dev, "Version: 0x%x\n", ver);
476a419b4fdSAdam Thomson 
477a419b4fdSAdam Thomson 	/* Handle DT data if provided */
478a419b4fdSAdam Thomson 	if (dev->of_node) {
479a419b4fdSAdam Thomson 		fg_pdata = da9150_fg_dt_pdata(dev);
480a419b4fdSAdam Thomson 		dev->platform_data = fg_pdata;
481a419b4fdSAdam Thomson 	}
482a419b4fdSAdam Thomson 
483a419b4fdSAdam Thomson 	/* Handle any pdata provided */
484a419b4fdSAdam Thomson 	if (fg_pdata) {
485a419b4fdSAdam Thomson 		fg->interval = fg_pdata->update_interval;
486a419b4fdSAdam Thomson 
487a419b4fdSAdam Thomson 		if (fg_pdata->warn_soc_lvl > 100)
488a419b4fdSAdam Thomson 			dev_warn(dev, "Invalid SOC warning level provided, Ignoring");
489a419b4fdSAdam Thomson 		else
490a419b4fdSAdam Thomson 			fg->warn_soc = fg_pdata->warn_soc_lvl;
491a419b4fdSAdam Thomson 
492a419b4fdSAdam Thomson 		if ((fg_pdata->crit_soc_lvl > 100) ||
493a419b4fdSAdam Thomson 		    (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl))
494a419b4fdSAdam Thomson 			dev_warn(dev, "Invalid SOC critical level provided, Ignoring");
495a419b4fdSAdam Thomson 		else
496a419b4fdSAdam Thomson 			fg->crit_soc = fg_pdata->crit_soc_lvl;
497a419b4fdSAdam Thomson 
498a419b4fdSAdam Thomson 
499a419b4fdSAdam Thomson 	}
500a419b4fdSAdam Thomson 
501a419b4fdSAdam Thomson 	/* Configure initial SOC level events */
502a419b4fdSAdam Thomson 	da9150_fg_soc_event_config(fg);
503a419b4fdSAdam Thomson 
504a419b4fdSAdam Thomson 	/*
505a419b4fdSAdam Thomson 	 * If an interval period has been provided then setup repeating
506a419b4fdSAdam Thomson 	 * work for reporting data updates.
507a419b4fdSAdam Thomson 	 */
508a419b4fdSAdam Thomson 	if (fg->interval) {
509419c0e9dSChristophe JAILLET 		ret = devm_delayed_work_autocancel(dev, &fg->work,
510419c0e9dSChristophe JAILLET 						   da9150_fg_work);
511419c0e9dSChristophe JAILLET 		if (ret) {
512419c0e9dSChristophe JAILLET 			dev_err(dev, "Failed to init work\n");
513419c0e9dSChristophe JAILLET 			return ret;
514419c0e9dSChristophe JAILLET 		}
515419c0e9dSChristophe JAILLET 
516a419b4fdSAdam Thomson 		schedule_delayed_work(&fg->work,
517a419b4fdSAdam Thomson 				      msecs_to_jiffies(fg->interval));
518a419b4fdSAdam Thomson 	}
519a419b4fdSAdam Thomson 
520a419b4fdSAdam Thomson 	/* Register IRQ */
521a419b4fdSAdam Thomson 	irq = platform_get_irq_byname(pdev, "FG");
522e6824196SYang Li 	if (irq < 0)
523419c0e9dSChristophe JAILLET 		return irq;
524a419b4fdSAdam Thomson 
525a419b4fdSAdam Thomson 	ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq,
526a419b4fdSAdam Thomson 					IRQF_ONESHOT, "FG", fg);
527a419b4fdSAdam Thomson 	if (ret) {
528a419b4fdSAdam Thomson 		dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret);
529a419b4fdSAdam Thomson 		return ret;
530a419b4fdSAdam Thomson 	}
531a419b4fdSAdam Thomson 
532a419b4fdSAdam Thomson 	return 0;
533a419b4fdSAdam Thomson }
534a419b4fdSAdam Thomson 
da9150_fg_resume(struct platform_device * pdev)535a419b4fdSAdam Thomson static int da9150_fg_resume(struct platform_device *pdev)
536a419b4fdSAdam Thomson {
537a419b4fdSAdam Thomson 	struct da9150_fg *fg = platform_get_drvdata(pdev);
538a419b4fdSAdam Thomson 
539a419b4fdSAdam Thomson 	/*
540a419b4fdSAdam Thomson 	 * Trigger SOC check to happen now so as to indicate any value change
541a419b4fdSAdam Thomson 	 * since last check before suspend.
542a419b4fdSAdam Thomson 	 */
543a419b4fdSAdam Thomson 	if (fg->interval)
544a419b4fdSAdam Thomson 		flush_delayed_work(&fg->work);
545a419b4fdSAdam Thomson 
546a419b4fdSAdam Thomson 	return 0;
547a419b4fdSAdam Thomson }
548a419b4fdSAdam Thomson 
549a419b4fdSAdam Thomson static struct platform_driver da9150_fg_driver = {
550a419b4fdSAdam Thomson 	.driver = {
551a419b4fdSAdam Thomson 		.name = "da9150-fuel-gauge",
552a419b4fdSAdam Thomson 	},
553a419b4fdSAdam Thomson 	.probe = da9150_fg_probe,
554a419b4fdSAdam Thomson 	.resume = da9150_fg_resume,
555a419b4fdSAdam Thomson };
556a419b4fdSAdam Thomson 
557a419b4fdSAdam Thomson module_platform_driver(da9150_fg_driver);
558a419b4fdSAdam Thomson 
559a419b4fdSAdam Thomson MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150");
560a419b4fdSAdam Thomson MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
561a419b4fdSAdam Thomson MODULE_LICENSE("GPL");
562