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