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