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