xref: /linux/drivers/power/supply/axp288_fuel_gauge.c (revision e5c86679d5e864947a52fb31e45a425dea3e7fa9)
1 /*
2  * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver
3  *
4  * Copyright (C) 2014 Intel Corporation
5  *
6  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; version 2 of the License.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
15  * General Public License for more details.
16  *
17  */
18 
19 #include <linux/module.h>
20 #include <linux/kernel.h>
21 #include <linux/device.h>
22 #include <linux/regmap.h>
23 #include <linux/jiffies.h>
24 #include <linux/interrupt.h>
25 #include <linux/workqueue.h>
26 #include <linux/mfd/axp20x.h>
27 #include <linux/platform_device.h>
28 #include <linux/power_supply.h>
29 #include <linux/iio/consumer.h>
30 #include <linux/debugfs.h>
31 #include <linux/seq_file.h>
32 #include <asm/unaligned.h>
33 
34 #define CHRG_STAT_BAT_SAFE_MODE		(1 << 3)
35 #define CHRG_STAT_BAT_VALID			(1 << 4)
36 #define CHRG_STAT_BAT_PRESENT		(1 << 5)
37 #define CHRG_STAT_CHARGING			(1 << 6)
38 #define CHRG_STAT_PMIC_OTP			(1 << 7)
39 
40 #define CHRG_CCCV_CC_MASK			0xf     /* 4 bits */
41 #define CHRG_CCCV_CC_BIT_POS		0
42 #define CHRG_CCCV_CC_OFFSET			200     /* 200mA */
43 #define CHRG_CCCV_CC_LSB_RES		200     /* 200mA */
44 #define CHRG_CCCV_ITERM_20P			(1 << 4)    /* 20% of CC */
45 #define CHRG_CCCV_CV_MASK			0x60        /* 2 bits */
46 #define CHRG_CCCV_CV_BIT_POS		5
47 #define CHRG_CCCV_CV_4100MV			0x0     /* 4.10V */
48 #define CHRG_CCCV_CV_4150MV			0x1     /* 4.15V */
49 #define CHRG_CCCV_CV_4200MV			0x2     /* 4.20V */
50 #define CHRG_CCCV_CV_4350MV			0x3     /* 4.35V */
51 #define CHRG_CCCV_CHG_EN			(1 << 7)
52 
53 #define FG_CNTL_OCV_ADJ_STAT		(1 << 2)
54 #define FG_CNTL_OCV_ADJ_EN			(1 << 3)
55 #define FG_CNTL_CAP_ADJ_STAT		(1 << 4)
56 #define FG_CNTL_CAP_ADJ_EN			(1 << 5)
57 #define FG_CNTL_CC_EN				(1 << 6)
58 #define FG_CNTL_GAUGE_EN			(1 << 7)
59 
60 #define FG_15BIT_WORD_VALID			(1 << 15)
61 #define FG_15BIT_VAL_MASK			0x7fff
62 
63 #define FG_REP_CAP_VALID			(1 << 7)
64 #define FG_REP_CAP_VAL_MASK			0x7F
65 
66 #define FG_DES_CAP1_VALID			(1 << 7)
67 #define FG_DES_CAP_RES_LSB			1456    /* 1.456mAhr */
68 
69 #define FG_DES_CC_RES_LSB			1456    /* 1.456mAhr */
70 
71 #define FG_OCV_CAP_VALID			(1 << 7)
72 #define FG_OCV_CAP_VAL_MASK			0x7F
73 #define FG_CC_CAP_VALID				(1 << 7)
74 #define FG_CC_CAP_VAL_MASK			0x7F
75 
76 #define FG_LOW_CAP_THR1_MASK		0xf0    /* 5% tp 20% */
77 #define FG_LOW_CAP_THR1_VAL			0xa0    /* 15 perc */
78 #define FG_LOW_CAP_THR2_MASK		0x0f    /* 0% to 15% */
79 #define FG_LOW_CAP_WARN_THR			14  /* 14 perc */
80 #define FG_LOW_CAP_CRIT_THR			4   /* 4 perc */
81 #define FG_LOW_CAP_SHDN_THR			0   /* 0 perc */
82 
83 #define STATUS_MON_DELAY_JIFFIES    (HZ * 60)   /*60 sec */
84 #define NR_RETRY_CNT    3
85 #define DEV_NAME	"axp288_fuel_gauge"
86 
87 /* 1.1mV per LSB expressed in uV */
88 #define VOLTAGE_FROM_ADC(a)			((a * 11) / 10)
89 /* properties converted to uV, uA */
90 #define PROP_VOLT(a)		((a) * 1000)
91 #define PROP_CURR(a)		((a) * 1000)
92 
93 #define AXP288_FG_INTR_NUM	6
94 enum {
95 	QWBTU_IRQ = 0,
96 	WBTU_IRQ,
97 	QWBTO_IRQ,
98 	WBTO_IRQ,
99 	WL2_IRQ,
100 	WL1_IRQ,
101 };
102 
103 struct axp288_fg_info {
104 	struct platform_device *pdev;
105 	struct regmap *regmap;
106 	struct regmap_irq_chip_data *regmap_irqc;
107 	int irq[AXP288_FG_INTR_NUM];
108 	struct power_supply *bat;
109 	struct mutex lock;
110 	int status;
111 	int max_volt;
112 	struct delayed_work status_monitor;
113 	struct dentry *debug_file;
114 };
115 
116 static enum power_supply_property fuel_gauge_props[] = {
117 	POWER_SUPPLY_PROP_STATUS,
118 	POWER_SUPPLY_PROP_PRESENT,
119 	POWER_SUPPLY_PROP_HEALTH,
120 	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
121 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
122 	POWER_SUPPLY_PROP_VOLTAGE_OCV,
123 	POWER_SUPPLY_PROP_CURRENT_NOW,
124 	POWER_SUPPLY_PROP_CAPACITY,
125 	POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
126 	POWER_SUPPLY_PROP_TECHNOLOGY,
127 	POWER_SUPPLY_PROP_CHARGE_FULL,
128 	POWER_SUPPLY_PROP_CHARGE_NOW,
129 };
130 
131 static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
132 {
133 	int ret, i;
134 	unsigned int val;
135 
136 	for (i = 0; i < NR_RETRY_CNT; i++) {
137 		ret = regmap_read(info->regmap, reg, &val);
138 		if (ret == -EBUSY)
139 			continue;
140 		else
141 			break;
142 	}
143 
144 	if (ret < 0) {
145 		dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
146 		return ret;
147 	}
148 
149 	return val;
150 }
151 
152 static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
153 {
154 	int ret;
155 
156 	ret = regmap_write(info->regmap, reg, (unsigned int)val);
157 
158 	if (ret < 0)
159 		dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret);
160 
161 	return ret;
162 }
163 
164 static int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg)
165 {
166 	unsigned char buf[2];
167 	int ret;
168 
169 	ret = regmap_bulk_read(info->regmap, reg, buf, 2);
170 	if (ret < 0) {
171 		dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
172 			reg, ret);
173 		return ret;
174 	}
175 
176 	ret = get_unaligned_be16(buf);
177 	if (!(ret & FG_15BIT_WORD_VALID)) {
178 		dev_err(&info->pdev->dev, "Error reg 0x%02x contents not valid\n",
179 			reg);
180 		return -ENXIO;
181 	}
182 
183 	return ret & FG_15BIT_VAL_MASK;
184 }
185 
186 static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg)
187 {
188 	unsigned char buf[2];
189 	int ret;
190 
191 	ret = regmap_bulk_read(info->regmap, reg, buf, 2);
192 	if (ret < 0) {
193 		dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
194 			reg, ret);
195 		return ret;
196 	}
197 
198 	/* 12-bit data values have upper 8 bits in buf[0], lower 4 in buf[1] */
199 	return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f);
200 }
201 
202 static int pmic_read_adc_val(const char *name, int *raw_val,
203 		struct axp288_fg_info *info)
204 {
205 	int ret, val = 0;
206 	struct iio_channel *indio_chan;
207 
208 	indio_chan = iio_channel_get(NULL, name);
209 	if (IS_ERR_OR_NULL(indio_chan)) {
210 		ret = PTR_ERR(indio_chan);
211 		goto exit;
212 	}
213 	ret = iio_read_channel_raw(indio_chan, &val);
214 	if (ret < 0) {
215 		dev_err(&info->pdev->dev,
216 			"IIO channel read error: %x, %x\n", ret, val);
217 		goto err_exit;
218 	}
219 
220 	dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val);
221 	*raw_val = val;
222 
223 err_exit:
224 	iio_channel_release(indio_chan);
225 exit:
226 	return ret;
227 }
228 
229 #ifdef CONFIG_DEBUG_FS
230 static int fuel_gauge_debug_show(struct seq_file *s, void *data)
231 {
232 	struct axp288_fg_info *info = s->private;
233 	int raw_val, ret;
234 
235 	seq_printf(s, " PWR_STATUS[%02x] : %02x\n",
236 		AXP20X_PWR_INPUT_STATUS,
237 		fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));
238 	seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",
239 		AXP20X_PWR_OP_MODE,
240 		fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));
241 	seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",
242 		AXP20X_CHRG_CTRL1,
243 		fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));
244 	seq_printf(s, "       VLTF[%02x] : %02x\n",
245 		AXP20X_V_LTF_DISCHRG,
246 		fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));
247 	seq_printf(s, "       VHTF[%02x] : %02x\n",
248 		AXP20X_V_HTF_DISCHRG,
249 		fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));
250 	seq_printf(s, "    CC_CTRL[%02x] : %02x\n",
251 		AXP20X_CC_CTRL,
252 		fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));
253 	seq_printf(s, "BATTERY CAP[%02x] : %02x\n",
254 		AXP20X_FG_RES,
255 		fuel_gauge_reg_readb(info, AXP20X_FG_RES));
256 	seq_printf(s, "    FG_RDC1[%02x] : %02x\n",
257 		AXP288_FG_RDC1_REG,
258 		fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));
259 	seq_printf(s, "    FG_RDC0[%02x] : %02x\n",
260 		AXP288_FG_RDC0_REG,
261 		fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
262 	seq_printf(s, "     FG_OCV[%02x] : %04x\n",
263 		AXP288_FG_OCVH_REG,
264 		fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG));
265 	seq_printf(s, " FG_DES_CAP[%02x] : %04x\n",
266 		AXP288_FG_DES_CAP1_REG,
267 		fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG));
268 	seq_printf(s, "  FG_CC_MTR[%02x] : %04x\n",
269 		AXP288_FG_CC_MTR1_REG,
270 		fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG));
271 	seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
272 		AXP288_FG_OCV_CAP_REG,
273 		fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
274 	seq_printf(s, "  FG_CC_CAP[%02x] : %02x\n",
275 		AXP288_FG_CC_CAP_REG,
276 		fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));
277 	seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",
278 		AXP288_FG_LOW_CAP_REG,
279 		fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));
280 	seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",
281 		AXP288_FG_TUNE0,
282 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));
283 	seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",
284 		AXP288_FG_TUNE1,
285 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));
286 	seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",
287 		AXP288_FG_TUNE2,
288 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));
289 	seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",
290 		AXP288_FG_TUNE3,
291 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));
292 	seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",
293 		AXP288_FG_TUNE4,
294 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));
295 	seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",
296 		AXP288_FG_TUNE5,
297 		fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));
298 
299 	ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
300 	if (ret >= 0)
301 		seq_printf(s, "axp288-batttemp : %d\n", raw_val);
302 	ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info);
303 	if (ret >= 0)
304 		seq_printf(s, "axp288-pmictemp : %d\n", raw_val);
305 	ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info);
306 	if (ret >= 0)
307 		seq_printf(s, "axp288-systtemp : %d\n", raw_val);
308 	ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info);
309 	if (ret >= 0)
310 		seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);
311 	ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info);
312 	if (ret >= 0)
313 		seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);
314 	ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
315 	if (ret >= 0)
316 		seq_printf(s, "axp288-battvolt : %d\n", raw_val);
317 
318 	return 0;
319 }
320 
321 static int debug_open(struct inode *inode, struct file *file)
322 {
323 	return single_open(file, fuel_gauge_debug_show, inode->i_private);
324 }
325 
326 static const struct file_operations fg_debug_fops = {
327 	.open       = debug_open,
328 	.read       = seq_read,
329 	.llseek     = seq_lseek,
330 	.release    = single_release,
331 };
332 
333 static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
334 {
335 	info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL,
336 		info, &fg_debug_fops);
337 }
338 
339 static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
340 {
341 	debugfs_remove(info->debug_file);
342 }
343 #else
344 static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
345 {
346 }
347 static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
348 {
349 }
350 #endif
351 
352 static void fuel_gauge_get_status(struct axp288_fg_info *info)
353 {
354 	int pwr_stat, ret;
355 	int charge, discharge;
356 
357 	pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
358 	if (pwr_stat < 0) {
359 		dev_err(&info->pdev->dev,
360 			"PWR STAT read failed:%d\n", pwr_stat);
361 		return;
362 	}
363 	ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
364 	if (ret < 0) {
365 		dev_err(&info->pdev->dev,
366 			"ADC charge current read failed:%d\n", ret);
367 		return;
368 	}
369 	ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
370 	if (ret < 0) {
371 		dev_err(&info->pdev->dev,
372 			"ADC discharge current read failed:%d\n", ret);
373 		return;
374 	}
375 
376 	if (charge > 0)
377 		info->status = POWER_SUPPLY_STATUS_CHARGING;
378 	else if (discharge > 0)
379 		info->status = POWER_SUPPLY_STATUS_DISCHARGING;
380 	else {
381 		if (pwr_stat & CHRG_STAT_BAT_PRESENT)
382 			info->status = POWER_SUPPLY_STATUS_FULL;
383 		else
384 			info->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
385 	}
386 }
387 
388 static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)
389 {
390 	int ret = 0, raw_val;
391 
392 	ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
393 	if (ret < 0)
394 		goto vbatt_read_fail;
395 
396 	*vbatt = VOLTAGE_FROM_ADC(raw_val);
397 vbatt_read_fail:
398 	return ret;
399 }
400 
401 static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
402 {
403 	int ret, value = 0;
404 	int charge, discharge;
405 
406 	ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
407 	if (ret < 0)
408 		goto current_read_fail;
409 	ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
410 	if (ret < 0)
411 		goto current_read_fail;
412 
413 	if (charge > 0)
414 		value = charge;
415 	else if (discharge > 0)
416 		value = -1 * discharge;
417 
418 	*cur = value;
419 current_read_fail:
420 	return ret;
421 }
422 
423 static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
424 {
425 	int ret;
426 
427 	ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
428 	if (ret >= 0)
429 		*vocv = VOLTAGE_FROM_ADC(ret);
430 
431 	return ret;
432 }
433 
434 static int fuel_gauge_battery_health(struct axp288_fg_info *info)
435 {
436 	int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN;
437 
438 	ret = fuel_gauge_get_vocv(info, &vocv);
439 	if (ret < 0)
440 		goto health_read_fail;
441 
442 	if (vocv > info->max_volt)
443 		health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
444 	else
445 		health = POWER_SUPPLY_HEALTH_GOOD;
446 
447 health_read_fail:
448 	return health;
449 }
450 
451 static int fuel_gauge_get_property(struct power_supply *ps,
452 		enum power_supply_property prop,
453 		union power_supply_propval *val)
454 {
455 	struct axp288_fg_info *info = power_supply_get_drvdata(ps);
456 	int ret = 0, value;
457 
458 	mutex_lock(&info->lock);
459 	switch (prop) {
460 	case POWER_SUPPLY_PROP_STATUS:
461 		fuel_gauge_get_status(info);
462 		val->intval = info->status;
463 		break;
464 	case POWER_SUPPLY_PROP_HEALTH:
465 		val->intval = fuel_gauge_battery_health(info);
466 		break;
467 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
468 		ret = fuel_gauge_get_vbatt(info, &value);
469 		if (ret < 0)
470 			goto fuel_gauge_read_err;
471 		val->intval = PROP_VOLT(value);
472 		break;
473 	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
474 		ret = fuel_gauge_get_vocv(info, &value);
475 		if (ret < 0)
476 			goto fuel_gauge_read_err;
477 		val->intval = PROP_VOLT(value);
478 		break;
479 	case POWER_SUPPLY_PROP_CURRENT_NOW:
480 		ret = fuel_gauge_get_current(info, &value);
481 		if (ret < 0)
482 			goto fuel_gauge_read_err;
483 		val->intval = PROP_CURR(value);
484 		break;
485 	case POWER_SUPPLY_PROP_PRESENT:
486 		ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
487 		if (ret < 0)
488 			goto fuel_gauge_read_err;
489 
490 		if (ret & CHRG_STAT_BAT_PRESENT)
491 			val->intval = 1;
492 		else
493 			val->intval = 0;
494 		break;
495 	case POWER_SUPPLY_PROP_CAPACITY:
496 		ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
497 		if (ret < 0)
498 			goto fuel_gauge_read_err;
499 
500 		if (!(ret & FG_REP_CAP_VALID))
501 			dev_err(&info->pdev->dev,
502 				"capacity measurement not valid\n");
503 		val->intval = (ret & FG_REP_CAP_VAL_MASK);
504 		break;
505 	case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
506 		ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
507 		if (ret < 0)
508 			goto fuel_gauge_read_err;
509 		val->intval = (ret & 0x0f);
510 		break;
511 	case POWER_SUPPLY_PROP_TECHNOLOGY:
512 		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
513 		break;
514 	case POWER_SUPPLY_PROP_CHARGE_NOW:
515 		ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
516 		if (ret < 0)
517 			goto fuel_gauge_read_err;
518 
519 		val->intval = ret * FG_DES_CAP_RES_LSB;
520 		break;
521 	case POWER_SUPPLY_PROP_CHARGE_FULL:
522 		ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG);
523 		if (ret < 0)
524 			goto fuel_gauge_read_err;
525 
526 		val->intval = ret * FG_DES_CAP_RES_LSB;
527 		break;
528 	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
529 		val->intval = PROP_VOLT(info->max_volt);
530 		break;
531 	default:
532 		mutex_unlock(&info->lock);
533 		return -EINVAL;
534 	}
535 
536 	mutex_unlock(&info->lock);
537 	return 0;
538 
539 fuel_gauge_read_err:
540 	mutex_unlock(&info->lock);
541 	return ret;
542 }
543 
544 static int fuel_gauge_set_property(struct power_supply *ps,
545 		enum power_supply_property prop,
546 		const union power_supply_propval *val)
547 {
548 	struct axp288_fg_info *info = power_supply_get_drvdata(ps);
549 	int ret = 0;
550 
551 	mutex_lock(&info->lock);
552 	switch (prop) {
553 	case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
554 		if ((val->intval < 0) || (val->intval > 15)) {
555 			ret = -EINVAL;
556 			break;
557 		}
558 		ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
559 		if (ret < 0)
560 			break;
561 		ret &= 0xf0;
562 		ret |= (val->intval & 0xf);
563 		ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret);
564 		break;
565 	default:
566 		ret = -EINVAL;
567 		break;
568 	}
569 
570 	mutex_unlock(&info->lock);
571 	return ret;
572 }
573 
574 static int fuel_gauge_property_is_writeable(struct power_supply *psy,
575 	enum power_supply_property psp)
576 {
577 	int ret;
578 
579 	switch (psp) {
580 	case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
581 		ret = 1;
582 		break;
583 	default:
584 		ret = 0;
585 	}
586 
587 	return ret;
588 }
589 
590 static void fuel_gauge_status_monitor(struct work_struct *work)
591 {
592 	struct axp288_fg_info *info = container_of(work,
593 		struct axp288_fg_info, status_monitor.work);
594 
595 	fuel_gauge_get_status(info);
596 	power_supply_changed(info->bat);
597 	schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
598 }
599 
600 static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
601 {
602 	struct axp288_fg_info *info = dev;
603 	int i;
604 
605 	for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
606 		if (info->irq[i] == irq)
607 			break;
608 	}
609 
610 	if (i >= AXP288_FG_INTR_NUM) {
611 		dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
612 		return IRQ_NONE;
613 	}
614 
615 	switch (i) {
616 	case QWBTU_IRQ:
617 		dev_info(&info->pdev->dev,
618 			"Quit Battery under temperature in work mode IRQ (QWBTU)\n");
619 		break;
620 	case WBTU_IRQ:
621 		dev_info(&info->pdev->dev,
622 			"Battery under temperature in work mode IRQ (WBTU)\n");
623 		break;
624 	case QWBTO_IRQ:
625 		dev_info(&info->pdev->dev,
626 			"Quit Battery over temperature in work mode IRQ (QWBTO)\n");
627 		break;
628 	case WBTO_IRQ:
629 		dev_info(&info->pdev->dev,
630 			"Battery over temperature in work mode IRQ (WBTO)\n");
631 		break;
632 	case WL2_IRQ:
633 		dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
634 		break;
635 	case WL1_IRQ:
636 		dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
637 		break;
638 	default:
639 		dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
640 	}
641 
642 	power_supply_changed(info->bat);
643 	return IRQ_HANDLED;
644 }
645 
646 static void fuel_gauge_external_power_changed(struct power_supply *psy)
647 {
648 	struct axp288_fg_info *info = power_supply_get_drvdata(psy);
649 
650 	power_supply_changed(info->bat);
651 }
652 
653 static const struct power_supply_desc fuel_gauge_desc = {
654 	.name			= DEV_NAME,
655 	.type			= POWER_SUPPLY_TYPE_BATTERY,
656 	.properties		= fuel_gauge_props,
657 	.num_properties		= ARRAY_SIZE(fuel_gauge_props),
658 	.get_property		= fuel_gauge_get_property,
659 	.set_property		= fuel_gauge_set_property,
660 	.property_is_writeable	= fuel_gauge_property_is_writeable,
661 	.external_power_changed	= fuel_gauge_external_power_changed,
662 };
663 
664 static void fuel_gauge_init_irq(struct axp288_fg_info *info)
665 {
666 	int ret, i, pirq;
667 
668 	for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
669 		pirq = platform_get_irq(info->pdev, i);
670 		info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
671 		if (info->irq[i] < 0) {
672 			dev_warn(&info->pdev->dev,
673 				"regmap_irq get virq failed for IRQ %d: %d\n",
674 				pirq, info->irq[i]);
675 			info->irq[i] = -1;
676 			goto intr_failed;
677 		}
678 		ret = request_threaded_irq(info->irq[i],
679 				NULL, fuel_gauge_thread_handler,
680 				IRQF_ONESHOT, DEV_NAME, info);
681 		if (ret) {
682 			dev_warn(&info->pdev->dev,
683 				"request irq failed for IRQ %d: %d\n",
684 				pirq, info->irq[i]);
685 			info->irq[i] = -1;
686 			goto intr_failed;
687 		} else {
688 			dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n",
689 				pirq, info->irq[i]);
690 		}
691 	}
692 	return;
693 
694 intr_failed:
695 	for (; i > 0; i--) {
696 		free_irq(info->irq[i - 1], info);
697 		info->irq[i - 1] = -1;
698 	}
699 }
700 
701 static int axp288_fuel_gauge_probe(struct platform_device *pdev)
702 {
703 	int ret = 0;
704 	struct axp288_fg_info *info;
705 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
706 	struct power_supply_config psy_cfg = {};
707 
708 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
709 	if (!info)
710 		return -ENOMEM;
711 
712 	info->pdev = pdev;
713 	info->regmap = axp20x->regmap;
714 	info->regmap_irqc = axp20x->regmap_irqc;
715 	info->status = POWER_SUPPLY_STATUS_UNKNOWN;
716 
717 	platform_set_drvdata(pdev, info);
718 
719 	mutex_init(&info->lock);
720 	INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
721 
722 	ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
723 	if (ret < 0)
724 		return ret;
725 
726 	if (!(ret & FG_DES_CAP1_VALID)) {
727 		dev_err(&pdev->dev, "axp288 not configured by firmware\n");
728 		return -ENODEV;
729 	}
730 
731 	ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
732 	if (ret < 0)
733 		return ret;
734 	switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
735 	case CHRG_CCCV_CV_4100MV:
736 		info->max_volt = 4100;
737 		break;
738 	case CHRG_CCCV_CV_4150MV:
739 		info->max_volt = 4150;
740 		break;
741 	case CHRG_CCCV_CV_4200MV:
742 		info->max_volt = 4200;
743 		break;
744 	case CHRG_CCCV_CV_4350MV:
745 		info->max_volt = 4350;
746 		break;
747 	}
748 
749 	psy_cfg.drv_data = info;
750 	info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
751 	if (IS_ERR(info->bat)) {
752 		ret = PTR_ERR(info->bat);
753 		dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
754 		return ret;
755 	}
756 
757 	fuel_gauge_create_debugfs(info);
758 	fuel_gauge_init_irq(info);
759 	schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
760 
761 	return 0;
762 }
763 
764 static const struct platform_device_id axp288_fg_id_table[] = {
765 	{ .name = DEV_NAME },
766 	{},
767 };
768 MODULE_DEVICE_TABLE(platform, axp288_fg_id_table);
769 
770 static int axp288_fuel_gauge_remove(struct platform_device *pdev)
771 {
772 	struct axp288_fg_info *info = platform_get_drvdata(pdev);
773 	int i;
774 
775 	cancel_delayed_work_sync(&info->status_monitor);
776 	power_supply_unregister(info->bat);
777 	fuel_gauge_remove_debugfs(info);
778 
779 	for (i = 0; i < AXP288_FG_INTR_NUM; i++)
780 		if (info->irq[i] >= 0)
781 			free_irq(info->irq[i], info);
782 
783 	return 0;
784 }
785 
786 static struct platform_driver axp288_fuel_gauge_driver = {
787 	.probe = axp288_fuel_gauge_probe,
788 	.remove = axp288_fuel_gauge_remove,
789 	.id_table = axp288_fg_id_table,
790 	.driver = {
791 		.name = DEV_NAME,
792 	},
793 };
794 
795 module_platform_driver(axp288_fuel_gauge_driver);
796 
797 MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
798 MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
799 MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
800 MODULE_LICENSE("GPL");
801