diff options
author | Todd Brandt <todd.e.brandt@linux.intel.com> | 2015-02-04 19:24:38 -0500 |
---|---|---|
committer | Sebastian Reichel <sre@kernel.org> | 2015-03-07 14:08:58 -0500 |
commit | 5a5bf49088f4c92f36786a2e4c20e17f715f0827 (patch) | |
tree | cbc9958cbf61c584532b88eeb3863436d0b1309a /drivers/power | |
parent | b571a77a0dec969728e102971dd126cf374c2cef (diff) |
X-Power AXP288 PMIC Fuel Gauge Driver
New power_supply driver at driver/power which interfaces with the
axp20x mfd driver as a cell. Provides battery info, monitors for
changes, and generates alerts on temperature and capacity issues
Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>
Acked-by: Jacob Pan <jacob.jun.pan@linux.intel.com>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 9 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/axp288_fuel_gauge.c | 1151 |
3 files changed, 1161 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 640eb36f49c0..4091fb092d06 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig | |||
@@ -204,6 +204,15 @@ config CHARGER_DA9150 | |||
204 | This driver can also be built as a module. If so, the module will be | 204 | This driver can also be built as a module. If so, the module will be |
205 | called da9150-charger. | 205 | called da9150-charger. |
206 | 206 | ||
207 | config AXP288_FUEL_GAUGE | ||
208 | tristate "X-Powers AXP288 Fuel Gauge" | ||
209 | depends on MFD_AXP20X && IIO | ||
210 | help | ||
211 | Say yes here to have support for X-Power power management IC (PMIC) | ||
212 | Fuel Gauge. The device provides battery statistics and status | ||
213 | monitoring as well as alerts for battery over/under voltage and | ||
214 | over/under temperature. | ||
215 | |||
207 | config BATTERY_MAX17040 | 216 | config BATTERY_MAX17040 |
208 | tristate "Maxim MAX17040 Fuel Gauge" | 217 | tristate "Maxim MAX17040 Fuel Gauge" |
209 | depends on I2C | 218 | depends on I2C |
diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 7b604f4926fc..b7b0181c95e5 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile | |||
@@ -63,3 +63,4 @@ obj-$(CONFIG_POWER_AVS) += avs/ | |||
63 | obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o | 63 | obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o |
64 | obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o | 64 | obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o |
65 | obj-$(CONFIG_POWER_RESET) += reset/ | 65 | obj-$(CONFIG_POWER_RESET) += reset/ |
66 | obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o | ||
diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c new file mode 100644 index 000000000000..c86e709c1880 --- /dev/null +++ b/drivers/power/axp288_fuel_gauge.c | |||
@@ -0,0 +1,1151 @@ | |||
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/device.h> | ||
26 | #include <linux/workqueue.h> | ||
27 | #include <linux/mfd/axp20x.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/power_supply.h> | ||
30 | #include <linux/iio/consumer.h> | ||
31 | #include <linux/debugfs.h> | ||
32 | #include <linux/seq_file.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 CV_4100 4100 /* 4100mV */ | ||
54 | #define CV_4150 4150 /* 4150mV */ | ||
55 | #define CV_4200 4200 /* 4200mV */ | ||
56 | #define CV_4350 4350 /* 4350mV */ | ||
57 | |||
58 | #define TEMP_IRQ_CFG_QWBTU (1 << 0) | ||
59 | #define TEMP_IRQ_CFG_WBTU (1 << 1) | ||
60 | #define TEMP_IRQ_CFG_QWBTO (1 << 2) | ||
61 | #define TEMP_IRQ_CFG_WBTO (1 << 3) | ||
62 | #define TEMP_IRQ_CFG_MASK 0xf | ||
63 | |||
64 | #define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0) | ||
65 | #define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1) | ||
66 | #define FG_IRQ_CFG_LOWBATT_MASK 0x3 | ||
67 | #define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0) | ||
68 | #define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1) | ||
69 | |||
70 | #define FG_CNTL_OCV_ADJ_STAT (1 << 2) | ||
71 | #define FG_CNTL_OCV_ADJ_EN (1 << 3) | ||
72 | #define FG_CNTL_CAP_ADJ_STAT (1 << 4) | ||
73 | #define FG_CNTL_CAP_ADJ_EN (1 << 5) | ||
74 | #define FG_CNTL_CC_EN (1 << 6) | ||
75 | #define FG_CNTL_GAUGE_EN (1 << 7) | ||
76 | |||
77 | #define FG_REP_CAP_VALID (1 << 7) | ||
78 | #define FG_REP_CAP_VAL_MASK 0x7F | ||
79 | |||
80 | #define FG_DES_CAP1_VALID (1 << 7) | ||
81 | #define FG_DES_CAP1_VAL_MASK 0x7F | ||
82 | #define FG_DES_CAP0_VAL_MASK 0xFF | ||
83 | #define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */ | ||
84 | |||
85 | #define FG_CC_MTR1_VALID (1 << 7) | ||
86 | #define FG_CC_MTR1_VAL_MASK 0x7F | ||
87 | #define FG_CC_MTR0_VAL_MASK 0xFF | ||
88 | #define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */ | ||
89 | |||
90 | #define FG_OCV_CAP_VALID (1 << 7) | ||
91 | #define FG_OCV_CAP_VAL_MASK 0x7F | ||
92 | #define FG_CC_CAP_VALID (1 << 7) | ||
93 | #define FG_CC_CAP_VAL_MASK 0x7F | ||
94 | |||
95 | #define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ | ||
96 | #define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ | ||
97 | #define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ | ||
98 | #define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ | ||
99 | #define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ | ||
100 | #define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ | ||
101 | |||
102 | #define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */ | ||
103 | #define NR_RETRY_CNT 3 | ||
104 | #define DEV_NAME "axp288_fuel_gauge" | ||
105 | |||
106 | /* 1.1mV per LSB expressed in uV */ | ||
107 | #define VOLTAGE_FROM_ADC(a) ((a * 11) / 10) | ||
108 | /* properties converted to tenths of degrees, uV, uA, uW */ | ||
109 | #define PROP_TEMP(a) ((a) * 10) | ||
110 | #define UNPROP_TEMP(a) ((a) / 10) | ||
111 | #define PROP_VOLT(a) ((a) * 1000) | ||
112 | #define PROP_CURR(a) ((a) * 1000) | ||
113 | |||
114 | #define AXP288_FG_INTR_NUM 6 | ||
115 | enum { | ||
116 | QWBTU_IRQ = 0, | ||
117 | WBTU_IRQ, | ||
118 | QWBTO_IRQ, | ||
119 | WBTO_IRQ, | ||
120 | WL2_IRQ, | ||
121 | WL1_IRQ, | ||
122 | }; | ||
123 | |||
124 | struct axp288_fg_info { | ||
125 | struct platform_device *pdev; | ||
126 | struct axp20x_fg_pdata *pdata; | ||
127 | struct regmap *regmap; | ||
128 | struct regmap_irq_chip_data *regmap_irqc; | ||
129 | int irq[AXP288_FG_INTR_NUM]; | ||
130 | struct power_supply bat; | ||
131 | struct mutex lock; | ||
132 | int status; | ||
133 | struct delayed_work status_monitor; | ||
134 | struct dentry *debug_file; | ||
135 | }; | ||
136 | |||
137 | static enum power_supply_property fuel_gauge_props[] = { | ||
138 | POWER_SUPPLY_PROP_STATUS, | ||
139 | POWER_SUPPLY_PROP_PRESENT, | ||
140 | POWER_SUPPLY_PROP_HEALTH, | ||
141 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, | ||
142 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, | ||
143 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | ||
144 | POWER_SUPPLY_PROP_VOLTAGE_OCV, | ||
145 | POWER_SUPPLY_PROP_CURRENT_NOW, | ||
146 | POWER_SUPPLY_PROP_CAPACITY, | ||
147 | POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, | ||
148 | POWER_SUPPLY_PROP_TEMP, | ||
149 | POWER_SUPPLY_PROP_TEMP_MAX, | ||
150 | POWER_SUPPLY_PROP_TEMP_MIN, | ||
151 | POWER_SUPPLY_PROP_TEMP_ALERT_MIN, | ||
152 | POWER_SUPPLY_PROP_TEMP_ALERT_MAX, | ||
153 | POWER_SUPPLY_PROP_TECHNOLOGY, | ||
154 | POWER_SUPPLY_PROP_CHARGE_FULL, | ||
155 | POWER_SUPPLY_PROP_CHARGE_NOW, | ||
156 | POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, | ||
157 | POWER_SUPPLY_PROP_MODEL_NAME, | ||
158 | }; | ||
159 | |||
160 | static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) | ||
161 | { | ||
162 | int ret, i; | ||
163 | unsigned int val; | ||
164 | |||
165 | for (i = 0; i < NR_RETRY_CNT; i++) { | ||
166 | ret = regmap_read(info->regmap, reg, &val); | ||
167 | if (ret == -EBUSY) | ||
168 | continue; | ||
169 | else | ||
170 | break; | ||
171 | } | ||
172 | |||
173 | if (ret < 0) | ||
174 | dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret); | ||
175 | |||
176 | return val; | ||
177 | } | ||
178 | |||
179 | static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) | ||
180 | { | ||
181 | int ret; | ||
182 | |||
183 | ret = regmap_write(info->regmap, reg, (unsigned int)val); | ||
184 | |||
185 | if (ret < 0) | ||
186 | dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret); | ||
187 | |||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | static int pmic_read_adc_val(const char *name, int *raw_val, | ||
192 | struct axp288_fg_info *info) | ||
193 | { | ||
194 | int ret, val = 0; | ||
195 | struct iio_channel *indio_chan; | ||
196 | |||
197 | indio_chan = iio_channel_get(NULL, name); | ||
198 | if (IS_ERR_OR_NULL(indio_chan)) { | ||
199 | ret = PTR_ERR(indio_chan); | ||
200 | goto exit; | ||
201 | } | ||
202 | ret = iio_read_channel_raw(indio_chan, &val); | ||
203 | if (ret < 0) { | ||
204 | dev_err(&info->pdev->dev, | ||
205 | "IIO channel read error: %x, %x\n", ret, val); | ||
206 | goto err_exit; | ||
207 | } | ||
208 | |||
209 | dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val); | ||
210 | *raw_val = val; | ||
211 | |||
212 | err_exit: | ||
213 | iio_channel_release(indio_chan); | ||
214 | exit: | ||
215 | return ret; | ||
216 | } | ||
217 | |||
218 | #ifdef CONFIG_DEBUG_FS | ||
219 | static int fuel_gauge_debug_show(struct seq_file *s, void *data) | ||
220 | { | ||
221 | struct axp288_fg_info *info = s->private; | ||
222 | int raw_val, ret; | ||
223 | |||
224 | seq_printf(s, " PWR_STATUS[%02x] : %02x\n", | ||
225 | AXP20X_PWR_INPUT_STATUS, | ||
226 | fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS)); | ||
227 | seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n", | ||
228 | AXP20X_PWR_OP_MODE, | ||
229 | fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE)); | ||
230 | seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n", | ||
231 | AXP20X_CHRG_CTRL1, | ||
232 | fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1)); | ||
233 | seq_printf(s, " VLTF[%02x] : %02x\n", | ||
234 | AXP20X_V_LTF_DISCHRG, | ||
235 | fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG)); | ||
236 | seq_printf(s, " VHTF[%02x] : %02x\n", | ||
237 | AXP20X_V_HTF_DISCHRG, | ||
238 | fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG)); | ||
239 | seq_printf(s, " CC_CTRL[%02x] : %02x\n", | ||
240 | AXP20X_CC_CTRL, | ||
241 | fuel_gauge_reg_readb(info, AXP20X_CC_CTRL)); | ||
242 | seq_printf(s, "BATTERY CAP[%02x] : %02x\n", | ||
243 | AXP20X_FG_RES, | ||
244 | fuel_gauge_reg_readb(info, AXP20X_FG_RES)); | ||
245 | seq_printf(s, " FG_RDC1[%02x] : %02x\n", | ||
246 | AXP288_FG_RDC1_REG, | ||
247 | fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG)); | ||
248 | seq_printf(s, " FG_RDC0[%02x] : %02x\n", | ||
249 | AXP288_FG_RDC0_REG, | ||
250 | fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG)); | ||
251 | seq_printf(s, " FG_OCVH[%02x] : %02x\n", | ||
252 | AXP288_FG_OCVH_REG, | ||
253 | fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG)); | ||
254 | seq_printf(s, " FG_OCVL[%02x] : %02x\n", | ||
255 | AXP288_FG_OCVL_REG, | ||
256 | fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG)); | ||
257 | seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n", | ||
258 | AXP288_FG_DES_CAP1_REG, | ||
259 | fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG)); | ||
260 | seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n", | ||
261 | AXP288_FG_DES_CAP0_REG, | ||
262 | fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG)); | ||
263 | seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n", | ||
264 | AXP288_FG_CC_MTR1_REG, | ||
265 | fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG)); | ||
266 | seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n", | ||
267 | AXP288_FG_CC_MTR0_REG, | ||
268 | fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG)); | ||
269 | seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n", | ||
270 | AXP288_FG_OCV_CAP_REG, | ||
271 | fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG)); | ||
272 | seq_printf(s, " FG_CC_CAP[%02x] : %02x\n", | ||
273 | AXP288_FG_CC_CAP_REG, | ||
274 | fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG)); | ||
275 | seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n", | ||
276 | AXP288_FG_LOW_CAP_REG, | ||
277 | fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG)); | ||
278 | seq_printf(s, "TUNING_CTL0[%02x] : %02x\n", | ||
279 | AXP288_FG_TUNE0, | ||
280 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE0)); | ||
281 | seq_printf(s, "TUNING_CTL1[%02x] : %02x\n", | ||
282 | AXP288_FG_TUNE1, | ||
283 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE1)); | ||
284 | seq_printf(s, "TUNING_CTL2[%02x] : %02x\n", | ||
285 | AXP288_FG_TUNE2, | ||
286 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE2)); | ||
287 | seq_printf(s, "TUNING_CTL3[%02x] : %02x\n", | ||
288 | AXP288_FG_TUNE3, | ||
289 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE3)); | ||
290 | seq_printf(s, "TUNING_CTL4[%02x] : %02x\n", | ||
291 | AXP288_FG_TUNE4, | ||
292 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE4)); | ||
293 | seq_printf(s, "TUNING_CTL5[%02x] : %02x\n", | ||
294 | AXP288_FG_TUNE5, | ||
295 | fuel_gauge_reg_readb(info, AXP288_FG_TUNE5)); | ||
296 | |||
297 | ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); | ||
298 | if (ret >= 0) | ||
299 | seq_printf(s, "axp288-batttemp : %d\n", raw_val); | ||
300 | ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info); | ||
301 | if (ret >= 0) | ||
302 | seq_printf(s, "axp288-pmictemp : %d\n", raw_val); | ||
303 | ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info); | ||
304 | if (ret >= 0) | ||
305 | seq_printf(s, "axp288-systtemp : %d\n", raw_val); | ||
306 | ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info); | ||
307 | if (ret >= 0) | ||
308 | seq_printf(s, "axp288-chrgcurr : %d\n", raw_val); | ||
309 | ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info); | ||
310 | if (ret >= 0) | ||
311 | seq_printf(s, "axp288-dchrgcur : %d\n", raw_val); | ||
312 | ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); | ||
313 | if (ret >= 0) | ||
314 | seq_printf(s, "axp288-battvolt : %d\n", raw_val); | ||
315 | |||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static int debug_open(struct inode *inode, struct file *file) | ||
320 | { | ||
321 | return single_open(file, fuel_gauge_debug_show, inode->i_private); | ||
322 | } | ||
323 | |||
324 | static const struct file_operations fg_debug_fops = { | ||
325 | .open = debug_open, | ||
326 | .read = seq_read, | ||
327 | .llseek = seq_lseek, | ||
328 | .release = single_release, | ||
329 | }; | ||
330 | |||
331 | static void fuel_gauge_create_debugfs(struct axp288_fg_info *info) | ||
332 | { | ||
333 | info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL, | ||
334 | info, &fg_debug_fops); | ||
335 | } | ||
336 | |||
337 | static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) | ||
338 | { | ||
339 | debugfs_remove(info->debug_file); | ||
340 | } | ||
341 | #else | ||
342 | static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info) | ||
343 | { | ||
344 | } | ||
345 | static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) | ||
346 | { | ||
347 | } | ||
348 | #endif | ||
349 | |||
350 | static void fuel_gauge_get_status(struct axp288_fg_info *info) | ||
351 | { | ||
352 | int pwr_stat, ret; | ||
353 | int charge, discharge; | ||
354 | |||
355 | pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); | ||
356 | if (pwr_stat < 0) { | ||
357 | dev_err(&info->pdev->dev, | ||
358 | "PWR STAT read failed:%d\n", pwr_stat); | ||
359 | return; | ||
360 | } | ||
361 | ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); | ||
362 | if (ret < 0) { | ||
363 | dev_err(&info->pdev->dev, | ||
364 | "ADC charge current read failed:%d\n", ret); | ||
365 | return; | ||
366 | } | ||
367 | ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); | ||
368 | if (ret < 0) { | ||
369 | dev_err(&info->pdev->dev, | ||
370 | "ADC discharge current read failed:%d\n", ret); | ||
371 | return; | ||
372 | } | ||
373 | |||
374 | if (charge > 0) | ||
375 | info->status = POWER_SUPPLY_STATUS_CHARGING; | ||
376 | else if (discharge > 0) | ||
377 | info->status = POWER_SUPPLY_STATUS_DISCHARGING; | ||
378 | else { | ||
379 | if (pwr_stat & CHRG_STAT_BAT_PRESENT) | ||
380 | info->status = POWER_SUPPLY_STATUS_FULL; | ||
381 | else | ||
382 | info->status = POWER_SUPPLY_STATUS_NOT_CHARGING; | ||
383 | } | ||
384 | } | ||
385 | |||
386 | static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) | ||
387 | { | ||
388 | int ret = 0, raw_val; | ||
389 | |||
390 | ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); | ||
391 | if (ret < 0) | ||
392 | goto vbatt_read_fail; | ||
393 | |||
394 | *vbatt = VOLTAGE_FROM_ADC(raw_val); | ||
395 | vbatt_read_fail: | ||
396 | return ret; | ||
397 | } | ||
398 | |||
399 | static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) | ||
400 | { | ||
401 | int ret, value = 0; | ||
402 | int charge, discharge; | ||
403 | |||
404 | ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); | ||
405 | if (ret < 0) | ||
406 | goto current_read_fail; | ||
407 | ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); | ||
408 | if (ret < 0) | ||
409 | goto current_read_fail; | ||
410 | |||
411 | if (charge > 0) | ||
412 | value = charge; | ||
413 | else if (discharge > 0) | ||
414 | value = -1 * discharge; | ||
415 | |||
416 | *cur = value; | ||
417 | current_read_fail: | ||
418 | return ret; | ||
419 | } | ||
420 | |||
421 | static int temp_to_adc(struct axp288_fg_info *info, int tval) | ||
422 | { | ||
423 | int rntc = 0, i, ret, adc_val; | ||
424 | int rmin, rmax, tmin, tmax; | ||
425 | int tcsz = info->pdata->tcsz; | ||
426 | |||
427 | /* get the Rntc resitance value for this temp */ | ||
428 | if (tval > info->pdata->thermistor_curve[0][1]) { | ||
429 | rntc = info->pdata->thermistor_curve[0][0]; | ||
430 | } else if (tval <= info->pdata->thermistor_curve[tcsz-1][1]) { | ||
431 | rntc = info->pdata->thermistor_curve[tcsz-1][0]; | ||
432 | } else { | ||
433 | for (i = 1; i < tcsz; i++) { | ||
434 | if (tval > info->pdata->thermistor_curve[i][1]) { | ||
435 | rmin = info->pdata->thermistor_curve[i-1][0]; | ||
436 | rmax = info->pdata->thermistor_curve[i][0]; | ||
437 | tmin = info->pdata->thermistor_curve[i-1][1]; | ||
438 | tmax = info->pdata->thermistor_curve[i][1]; | ||
439 | rntc = rmin + ((rmax - rmin) * | ||
440 | (tval - tmin) / (tmax - tmin)); | ||
441 | break; | ||
442 | } | ||
443 | } | ||
444 | } | ||
445 | |||
446 | /* we need the current to calculate the proper adc voltage */ | ||
447 | ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE); | ||
448 | if (ret < 0) { | ||
449 | dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); | ||
450 | ret = 0x30; | ||
451 | } | ||
452 | |||
453 | /* | ||
454 | * temperature is proportional to NTS thermistor resistance | ||
455 | * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA | ||
456 | * [12-bit ADC VAL] = R_NTC(Ω) * current / 800 | ||
457 | */ | ||
458 | adc_val = rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800; | ||
459 | |||
460 | return adc_val; | ||
461 | } | ||
462 | |||
463 | static int adc_to_temp(struct axp288_fg_info *info, int adc_val) | ||
464 | { | ||
465 | int ret, r, i, tval = 0; | ||
466 | int rmin, rmax, tmin, tmax; | ||
467 | int tcsz = info->pdata->tcsz; | ||
468 | |||
469 | ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE); | ||
470 | if (ret < 0) { | ||
471 | dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); | ||
472 | ret = 0x30; | ||
473 | } | ||
474 | |||
475 | /* | ||
476 | * temperature is proportional to NTS thermistor resistance | ||
477 | * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA | ||
478 | * R_NTC(Ω) = [12-bit ADC VAL] * 800 / current | ||
479 | */ | ||
480 | r = adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3))); | ||
481 | |||
482 | if (r < info->pdata->thermistor_curve[0][0]) { | ||
483 | tval = info->pdata->thermistor_curve[0][1]; | ||
484 | } else if (r >= info->pdata->thermistor_curve[tcsz-1][0]) { | ||
485 | tval = info->pdata->thermistor_curve[tcsz-1][1]; | ||
486 | } else { | ||
487 | for (i = 1; i < tcsz; i++) { | ||
488 | if (r < info->pdata->thermistor_curve[i][0]) { | ||
489 | rmin = info->pdata->thermistor_curve[i-1][0]; | ||
490 | rmax = info->pdata->thermistor_curve[i][0]; | ||
491 | tmin = info->pdata->thermistor_curve[i-1][1]; | ||
492 | tmax = info->pdata->thermistor_curve[i][1]; | ||
493 | tval = tmin + ((tmax - tmin) * | ||
494 | (r - rmin) / (rmax - rmin)); | ||
495 | break; | ||
496 | } | ||
497 | } | ||
498 | } | ||
499 | |||
500 | return tval; | ||
501 | } | ||
502 | |||
503 | static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp) | ||
504 | { | ||
505 | int ret, raw_val = 0; | ||
506 | |||
507 | ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); | ||
508 | if (ret < 0) | ||
509 | goto temp_read_fail; | ||
510 | |||
511 | *btemp = adc_to_temp(info, raw_val); | ||
512 | |||
513 | temp_read_fail: | ||
514 | return ret; | ||
515 | } | ||
516 | |||
517 | static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv) | ||
518 | { | ||
519 | int ret, value; | ||
520 | |||
521 | /* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */ | ||
522 | ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG); | ||
523 | if (ret < 0) | ||
524 | goto vocv_read_fail; | ||
525 | value = ret << 4; | ||
526 | |||
527 | ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG); | ||
528 | if (ret < 0) | ||
529 | goto vocv_read_fail; | ||
530 | value |= (ret & 0xf); | ||
531 | |||
532 | *vocv = VOLTAGE_FROM_ADC(value); | ||
533 | vocv_read_fail: | ||
534 | return ret; | ||
535 | } | ||
536 | |||
537 | static int fuel_gauge_battery_health(struct axp288_fg_info *info) | ||
538 | { | ||
539 | int temp, vocv; | ||
540 | int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN; | ||
541 | |||
542 | ret = fuel_gauge_get_btemp(info, &temp); | ||
543 | if (ret < 0) | ||
544 | goto health_read_fail; | ||
545 | |||
546 | ret = fuel_gauge_get_vocv(info, &vocv); | ||
547 | if (ret < 0) | ||
548 | goto health_read_fail; | ||
549 | |||
550 | if (vocv > info->pdata->max_volt) | ||
551 | health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; | ||
552 | else if (temp > info->pdata->max_temp) | ||
553 | health = POWER_SUPPLY_HEALTH_OVERHEAT; | ||
554 | else if (temp < info->pdata->min_temp) | ||
555 | health = POWER_SUPPLY_HEALTH_COLD; | ||
556 | else if (vocv < info->pdata->min_volt) | ||
557 | health = POWER_SUPPLY_HEALTH_DEAD; | ||
558 | else | ||
559 | health = POWER_SUPPLY_HEALTH_GOOD; | ||
560 | |||
561 | health_read_fail: | ||
562 | return health; | ||
563 | } | ||
564 | |||
565 | static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info) | ||
566 | { | ||
567 | int ret, adc_val; | ||
568 | |||
569 | /* program temperature threshold as 1/16 ADC value */ | ||
570 | adc_val = temp_to_adc(info, info->pdata->max_temp); | ||
571 | ret = fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >> 4); | ||
572 | |||
573 | return ret; | ||
574 | } | ||
575 | |||
576 | static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info) | ||
577 | { | ||
578 | int ret, adc_val; | ||
579 | |||
580 | /* program temperature threshold as 1/16 ADC value */ | ||
581 | adc_val = temp_to_adc(info, info->pdata->min_temp); | ||
582 | ret = fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >> 4); | ||
583 | |||
584 | return ret; | ||
585 | } | ||
586 | |||
587 | static int fuel_gauge_get_property(struct power_supply *ps, | ||
588 | enum power_supply_property prop, | ||
589 | union power_supply_propval *val) | ||
590 | { | ||
591 | struct axp288_fg_info *info = container_of(ps, | ||
592 | struct axp288_fg_info, bat); | ||
593 | int ret = 0, value; | ||
594 | |||
595 | mutex_lock(&info->lock); | ||
596 | switch (prop) { | ||
597 | case POWER_SUPPLY_PROP_STATUS: | ||
598 | fuel_gauge_get_status(info); | ||
599 | val->intval = info->status; | ||
600 | break; | ||
601 | case POWER_SUPPLY_PROP_HEALTH: | ||
602 | val->intval = fuel_gauge_battery_health(info); | ||
603 | break; | ||
604 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
605 | ret = fuel_gauge_get_vbatt(info, &value); | ||
606 | if (ret < 0) | ||
607 | goto fuel_gauge_read_err; | ||
608 | val->intval = PROP_VOLT(value); | ||
609 | break; | ||
610 | case POWER_SUPPLY_PROP_VOLTAGE_OCV: | ||
611 | ret = fuel_gauge_get_vocv(info, &value); | ||
612 | if (ret < 0) | ||
613 | goto fuel_gauge_read_err; | ||
614 | val->intval = PROP_VOLT(value); | ||
615 | break; | ||
616 | case POWER_SUPPLY_PROP_CURRENT_NOW: | ||
617 | ret = fuel_gauge_get_current(info, &value); | ||
618 | if (ret < 0) | ||
619 | goto fuel_gauge_read_err; | ||
620 | val->intval = PROP_CURR(value); | ||
621 | break; | ||
622 | case POWER_SUPPLY_PROP_PRESENT: | ||
623 | ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); | ||
624 | if (ret < 0) | ||
625 | goto fuel_gauge_read_err; | ||
626 | |||
627 | if (ret & CHRG_STAT_BAT_PRESENT) | ||
628 | val->intval = 1; | ||
629 | else | ||
630 | val->intval = 0; | ||
631 | break; | ||
632 | case POWER_SUPPLY_PROP_CAPACITY: | ||
633 | ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); | ||
634 | if (ret < 0) | ||
635 | goto fuel_gauge_read_err; | ||
636 | |||
637 | if (!(ret & FG_REP_CAP_VALID)) | ||
638 | dev_err(&info->pdev->dev, | ||
639 | "capacity measurement not valid\n"); | ||
640 | val->intval = (ret & FG_REP_CAP_VAL_MASK); | ||
641 | break; | ||
642 | case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: | ||
643 | ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); | ||
644 | if (ret < 0) | ||
645 | goto fuel_gauge_read_err; | ||
646 | val->intval = (ret & 0x0f); | ||
647 | break; | ||
648 | case POWER_SUPPLY_PROP_TEMP: | ||
649 | ret = fuel_gauge_get_btemp(info, &value); | ||
650 | if (ret < 0) | ||
651 | goto fuel_gauge_read_err; | ||
652 | val->intval = PROP_TEMP(value); | ||
653 | break; | ||
654 | case POWER_SUPPLY_PROP_TEMP_MAX: | ||
655 | case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: | ||
656 | val->intval = PROP_TEMP(info->pdata->max_temp); | ||
657 | break; | ||
658 | case POWER_SUPPLY_PROP_TEMP_MIN: | ||
659 | case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: | ||
660 | val->intval = PROP_TEMP(info->pdata->min_temp); | ||
661 | break; | ||
662 | case POWER_SUPPLY_PROP_TECHNOLOGY: | ||
663 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | ||
664 | break; | ||
665 | case POWER_SUPPLY_PROP_CHARGE_NOW: | ||
666 | ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG); | ||
667 | if (ret < 0) | ||
668 | goto fuel_gauge_read_err; | ||
669 | |||
670 | value = (ret & FG_CC_MTR1_VAL_MASK) << 8; | ||
671 | ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG); | ||
672 | if (ret < 0) | ||
673 | goto fuel_gauge_read_err; | ||
674 | value |= (ret & FG_CC_MTR0_VAL_MASK); | ||
675 | val->intval = value * FG_DES_CAP_RES_LSB; | ||
676 | break; | ||
677 | case POWER_SUPPLY_PROP_CHARGE_FULL: | ||
678 | ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); | ||
679 | if (ret < 0) | ||
680 | goto fuel_gauge_read_err; | ||
681 | |||
682 | value = (ret & FG_DES_CAP1_VAL_MASK) << 8; | ||
683 | ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG); | ||
684 | if (ret < 0) | ||
685 | goto fuel_gauge_read_err; | ||
686 | value |= (ret & FG_DES_CAP0_VAL_MASK); | ||
687 | val->intval = value * FG_DES_CAP_RES_LSB; | ||
688 | break; | ||
689 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: | ||
690 | val->intval = PROP_CURR(info->pdata->design_cap); | ||
691 | break; | ||
692 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: | ||
693 | val->intval = PROP_VOLT(info->pdata->max_volt); | ||
694 | break; | ||
695 | case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: | ||
696 | val->intval = PROP_VOLT(info->pdata->min_volt); | ||
697 | break; | ||
698 | case POWER_SUPPLY_PROP_MODEL_NAME: | ||
699 | val->strval = info->pdata->battid; | ||
700 | break; | ||
701 | default: | ||
702 | mutex_unlock(&info->lock); | ||
703 | return -EINVAL; | ||
704 | } | ||
705 | |||
706 | mutex_unlock(&info->lock); | ||
707 | return 0; | ||
708 | |||
709 | fuel_gauge_read_err: | ||
710 | mutex_unlock(&info->lock); | ||
711 | return ret; | ||
712 | } | ||
713 | |||
714 | static int fuel_gauge_set_property(struct power_supply *ps, | ||
715 | enum power_supply_property prop, | ||
716 | const union power_supply_propval *val) | ||
717 | { | ||
718 | struct axp288_fg_info *info = container_of(ps, | ||
719 | struct axp288_fg_info, bat); | ||
720 | int ret = 0; | ||
721 | |||
722 | mutex_lock(&info->lock); | ||
723 | switch (prop) { | ||
724 | case POWER_SUPPLY_PROP_STATUS: | ||
725 | info->status = val->intval; | ||
726 | break; | ||
727 | case POWER_SUPPLY_PROP_TEMP_MIN: | ||
728 | case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: | ||
729 | if ((val->intval < PD_DEF_MIN_TEMP) || | ||
730 | (val->intval > PD_DEF_MAX_TEMP)) { | ||
731 | ret = -EINVAL; | ||
732 | break; | ||
733 | } | ||
734 | info->pdata->min_temp = UNPROP_TEMP(val->intval); | ||
735 | ret = fuel_gauge_set_low_btemp_alert(info); | ||
736 | if (ret < 0) | ||
737 | dev_err(&info->pdev->dev, | ||
738 | "temp alert min set fail:%d\n", ret); | ||
739 | break; | ||
740 | case POWER_SUPPLY_PROP_TEMP_MAX: | ||
741 | case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: | ||
742 | if ((val->intval < PD_DEF_MIN_TEMP) || | ||
743 | (val->intval > PD_DEF_MAX_TEMP)) { | ||
744 | ret = -EINVAL; | ||
745 | break; | ||
746 | } | ||
747 | info->pdata->max_temp = UNPROP_TEMP(val->intval); | ||
748 | ret = fuel_gauge_set_high_btemp_alert(info); | ||
749 | if (ret < 0) | ||
750 | dev_err(&info->pdev->dev, | ||
751 | "temp alert max set fail:%d\n", ret); | ||
752 | break; | ||
753 | case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: | ||
754 | if ((val->intval < 0) || (val->intval > 15)) { | ||
755 | ret = -EINVAL; | ||
756 | break; | ||
757 | } | ||
758 | ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); | ||
759 | if (ret < 0) | ||
760 | break; | ||
761 | ret &= 0xf0; | ||
762 | ret |= (val->intval & 0xf); | ||
763 | ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret); | ||
764 | break; | ||
765 | default: | ||
766 | ret = -EINVAL; | ||
767 | break; | ||
768 | } | ||
769 | |||
770 | mutex_unlock(&info->lock); | ||
771 | return ret; | ||
772 | } | ||
773 | |||
774 | static int fuel_gauge_property_is_writeable(struct power_supply *psy, | ||
775 | enum power_supply_property psp) | ||
776 | { | ||
777 | int ret; | ||
778 | |||
779 | switch (psp) { | ||
780 | case POWER_SUPPLY_PROP_STATUS: | ||
781 | case POWER_SUPPLY_PROP_TEMP_MIN: | ||
782 | case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: | ||
783 | case POWER_SUPPLY_PROP_TEMP_MAX: | ||
784 | case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: | ||
785 | case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: | ||
786 | ret = 1; | ||
787 | break; | ||
788 | default: | ||
789 | ret = 0; | ||
790 | } | ||
791 | |||
792 | return ret; | ||
793 | } | ||
794 | |||
795 | static void fuel_gauge_status_monitor(struct work_struct *work) | ||
796 | { | ||
797 | struct axp288_fg_info *info = container_of(work, | ||
798 | struct axp288_fg_info, status_monitor.work); | ||
799 | |||
800 | fuel_gauge_get_status(info); | ||
801 | power_supply_changed(&info->bat); | ||
802 | schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); | ||
803 | } | ||
804 | |||
805 | static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) | ||
806 | { | ||
807 | struct axp288_fg_info *info = dev; | ||
808 | int i; | ||
809 | |||
810 | for (i = 0; i < AXP288_FG_INTR_NUM; i++) { | ||
811 | if (info->irq[i] == irq) | ||
812 | break; | ||
813 | } | ||
814 | |||
815 | if (i >= AXP288_FG_INTR_NUM) { | ||
816 | dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); | ||
817 | return IRQ_NONE; | ||
818 | } | ||
819 | |||
820 | switch (i) { | ||
821 | case QWBTU_IRQ: | ||
822 | dev_info(&info->pdev->dev, | ||
823 | "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); | ||
824 | break; | ||
825 | case WBTU_IRQ: | ||
826 | dev_info(&info->pdev->dev, | ||
827 | "Battery under temperature in work mode IRQ (WBTU)\n"); | ||
828 | break; | ||
829 | case QWBTO_IRQ: | ||
830 | dev_info(&info->pdev->dev, | ||
831 | "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); | ||
832 | break; | ||
833 | case WBTO_IRQ: | ||
834 | dev_info(&info->pdev->dev, | ||
835 | "Battery over temperature in work mode IRQ (WBTO)\n"); | ||
836 | break; | ||
837 | case WL2_IRQ: | ||
838 | dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n"); | ||
839 | break; | ||
840 | case WL1_IRQ: | ||
841 | dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n"); | ||
842 | break; | ||
843 | default: | ||
844 | dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); | ||
845 | } | ||
846 | |||
847 | power_supply_changed(&info->bat); | ||
848 | return IRQ_HANDLED; | ||
849 | } | ||
850 | |||
851 | static void fuel_gauge_external_power_changed(struct power_supply *psy) | ||
852 | { | ||
853 | struct axp288_fg_info *info = container_of(psy, | ||
854 | struct axp288_fg_info, bat); | ||
855 | |||
856 | power_supply_changed(&info->bat); | ||
857 | } | ||
858 | |||
859 | static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info) | ||
860 | { | ||
861 | int ret; | ||
862 | u8 reg_val; | ||
863 | |||
864 | ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); | ||
865 | if (ret < 0) { | ||
866 | dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); | ||
867 | return ret; | ||
868 | } | ||
869 | ret = (ret & FG_REP_CAP_VAL_MASK); | ||
870 | |||
871 | if (ret > FG_LOW_CAP_WARN_THR) | ||
872 | reg_val = FG_LOW_CAP_WARN_THR; | ||
873 | else if (ret > FG_LOW_CAP_CRIT_THR) | ||
874 | reg_val = FG_LOW_CAP_CRIT_THR; | ||
875 | else | ||
876 | reg_val = FG_LOW_CAP_SHDN_THR; | ||
877 | |||
878 | reg_val |= FG_LOW_CAP_THR1_VAL; | ||
879 | ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val); | ||
880 | if (ret < 0) | ||
881 | dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret); | ||
882 | |||
883 | return ret; | ||
884 | } | ||
885 | |||
886 | static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info) | ||
887 | { | ||
888 | int ret; | ||
889 | u8 val; | ||
890 | |||
891 | ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); | ||
892 | if (ret < 0) | ||
893 | goto fg_prog_ocv_fail; | ||
894 | else | ||
895 | val = (ret & ~CHRG_CCCV_CV_MASK); | ||
896 | |||
897 | switch (info->pdata->max_volt) { | ||
898 | case CV_4100: | ||
899 | val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS); | ||
900 | break; | ||
901 | case CV_4150: | ||
902 | val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS); | ||
903 | break; | ||
904 | case CV_4200: | ||
905 | val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS); | ||
906 | break; | ||
907 | case CV_4350: | ||
908 | val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS); | ||
909 | break; | ||
910 | default: | ||
911 | val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS); | ||
912 | break; | ||
913 | } | ||
914 | |||
915 | ret = fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val); | ||
916 | fg_prog_ocv_fail: | ||
917 | return ret; | ||
918 | } | ||
919 | |||
920 | static int fuel_gauge_program_design_cap(struct axp288_fg_info *info) | ||
921 | { | ||
922 | int ret; | ||
923 | |||
924 | ret = fuel_gauge_reg_writeb(info, | ||
925 | AXP288_FG_DES_CAP1_REG, info->pdata->cap1); | ||
926 | if (ret < 0) | ||
927 | goto fg_prog_descap_fail; | ||
928 | |||
929 | ret = fuel_gauge_reg_writeb(info, | ||
930 | AXP288_FG_DES_CAP0_REG, info->pdata->cap0); | ||
931 | |||
932 | fg_prog_descap_fail: | ||
933 | return ret; | ||
934 | } | ||
935 | |||
936 | static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info) | ||
937 | { | ||
938 | int ret = 0, i; | ||
939 | |||
940 | for (i = 0; i < OCV_CURVE_SIZE; i++) { | ||
941 | ret = fuel_gauge_reg_writeb(info, | ||
942 | AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve[i]); | ||
943 | if (ret < 0) | ||
944 | goto fg_prog_ocv_fail; | ||
945 | } | ||
946 | |||
947 | fg_prog_ocv_fail: | ||
948 | return ret; | ||
949 | } | ||
950 | |||
951 | static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info) | ||
952 | { | ||
953 | int ret; | ||
954 | |||
955 | ret = fuel_gauge_reg_writeb(info, | ||
956 | AXP288_FG_RDC1_REG, info->pdata->rdc1); | ||
957 | if (ret < 0) | ||
958 | goto fg_prog_ocv_fail; | ||
959 | |||
960 | ret = fuel_gauge_reg_writeb(info, | ||
961 | AXP288_FG_RDC0_REG, info->pdata->rdc0); | ||
962 | |||
963 | fg_prog_ocv_fail: | ||
964 | return ret; | ||
965 | } | ||
966 | |||
967 | static void fuel_gauge_init_config_regs(struct axp288_fg_info *info) | ||
968 | { | ||
969 | int ret; | ||
970 | |||
971 | /* | ||
972 | * check if the config data is already | ||
973 | * programmed and if so just return. | ||
974 | */ | ||
975 | |||
976 | ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); | ||
977 | if (ret < 0) { | ||
978 | dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n"); | ||
979 | } else if (!(ret & FG_DES_CAP1_VALID)) { | ||
980 | dev_info(&info->pdev->dev, "FG data needs to be initialized\n"); | ||
981 | } else { | ||
982 | dev_info(&info->pdev->dev, "FG data is already initialized\n"); | ||
983 | return; | ||
984 | } | ||
985 | |||
986 | ret = fuel_gauge_program_vbatt_full(info); | ||
987 | if (ret < 0) | ||
988 | dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret); | ||
989 | |||
990 | ret = fuel_gauge_program_design_cap(info); | ||
991 | if (ret < 0) | ||
992 | dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret); | ||
993 | |||
994 | ret = fuel_gauge_program_rdc_vals(info); | ||
995 | if (ret < 0) | ||
996 | dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret); | ||
997 | |||
998 | ret = fuel_gauge_program_ocv_curve(info); | ||
999 | if (ret < 0) | ||
1000 | dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret); | ||
1001 | |||
1002 | ret = fuel_gauge_set_lowbatt_thresholds(info); | ||
1003 | if (ret < 0) | ||
1004 | dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret); | ||
1005 | |||
1006 | ret = fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef); | ||
1007 | if (ret < 0) | ||
1008 | dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret); | ||
1009 | } | ||
1010 | |||
1011 | static void fuel_gauge_init_irq(struct axp288_fg_info *info) | ||
1012 | { | ||
1013 | int ret, i, pirq; | ||
1014 | |||
1015 | for (i = 0; i < AXP288_FG_INTR_NUM; i++) { | ||
1016 | pirq = platform_get_irq(info->pdev, i); | ||
1017 | info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); | ||
1018 | if (info->irq[i] < 0) { | ||
1019 | dev_warn(&info->pdev->dev, | ||
1020 | "regmap_irq get virq failed for IRQ %d: %d\n", | ||
1021 | pirq, info->irq[i]); | ||
1022 | info->irq[i] = -1; | ||
1023 | goto intr_failed; | ||
1024 | } | ||
1025 | ret = request_threaded_irq(info->irq[i], | ||
1026 | NULL, fuel_gauge_thread_handler, | ||
1027 | IRQF_ONESHOT, DEV_NAME, info); | ||
1028 | if (ret) { | ||
1029 | dev_warn(&info->pdev->dev, | ||
1030 | "request irq failed for IRQ %d: %d\n", | ||
1031 | pirq, info->irq[i]); | ||
1032 | info->irq[i] = -1; | ||
1033 | goto intr_failed; | ||
1034 | } else { | ||
1035 | dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n", | ||
1036 | pirq, info->irq[i]); | ||
1037 | } | ||
1038 | } | ||
1039 | return; | ||
1040 | |||
1041 | intr_failed: | ||
1042 | for (; i > 0; i--) { | ||
1043 | free_irq(info->irq[i - 1], info); | ||
1044 | info->irq[i - 1] = -1; | ||
1045 | } | ||
1046 | } | ||
1047 | |||
1048 | static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info) | ||
1049 | { | ||
1050 | int ret; | ||
1051 | unsigned int val; | ||
1052 | |||
1053 | ret = fuel_gauge_set_high_btemp_alert(info); | ||
1054 | if (ret < 0) | ||
1055 | dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", ret); | ||
1056 | |||
1057 | ret = fuel_gauge_set_low_btemp_alert(info); | ||
1058 | if (ret < 0) | ||
1059 | dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", ret); | ||
1060 | |||
1061 | /* enable interrupts */ | ||
1062 | val = fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN); | ||
1063 | val |= TEMP_IRQ_CFG_MASK; | ||
1064 | fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val); | ||
1065 | |||
1066 | val = fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN); | ||
1067 | val |= FG_IRQ_CFG_LOWBATT_MASK; | ||
1068 | val = fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val); | ||
1069 | } | ||
1070 | |||
1071 | static int axp288_fuel_gauge_probe(struct platform_device *pdev) | ||
1072 | { | ||
1073 | int ret; | ||
1074 | struct axp288_fg_info *info; | ||
1075 | struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); | ||
1076 | |||
1077 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | ||
1078 | if (!info) | ||
1079 | return -ENOMEM; | ||
1080 | |||
1081 | info->pdev = pdev; | ||
1082 | info->regmap = axp20x->regmap; | ||
1083 | info->regmap_irqc = axp20x->regmap_irqc; | ||
1084 | info->status = POWER_SUPPLY_STATUS_UNKNOWN; | ||
1085 | info->pdata = pdev->dev.platform_data; | ||
1086 | if (!info->pdata) | ||
1087 | return -ENODEV; | ||
1088 | |||
1089 | platform_set_drvdata(pdev, info); | ||
1090 | |||
1091 | mutex_init(&info->lock); | ||
1092 | INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor); | ||
1093 | |||
1094 | info->bat.name = DEV_NAME; | ||
1095 | info->bat.type = POWER_SUPPLY_TYPE_BATTERY; | ||
1096 | info->bat.properties = fuel_gauge_props; | ||
1097 | info->bat.num_properties = ARRAY_SIZE(fuel_gauge_props); | ||
1098 | info->bat.get_property = fuel_gauge_get_property; | ||
1099 | info->bat.set_property = fuel_gauge_set_property; | ||
1100 | info->bat.property_is_writeable = fuel_gauge_property_is_writeable; | ||
1101 | info->bat.external_power_changed = fuel_gauge_external_power_changed; | ||
1102 | ret = power_supply_register(&pdev->dev, &info->bat); | ||
1103 | if (ret) { | ||
1104 | dev_err(&pdev->dev, "failed to register battery: %d\n", ret); | ||
1105 | return ret; | ||
1106 | } | ||
1107 | |||
1108 | fuel_gauge_create_debugfs(info); | ||
1109 | fuel_gauge_init_config_regs(info); | ||
1110 | fuel_gauge_init_irq(info); | ||
1111 | fuel_gauge_init_hw_regs(info); | ||
1112 | schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); | ||
1113 | |||
1114 | return ret; | ||
1115 | } | ||
1116 | |||
1117 | static struct platform_device_id axp288_fg_id_table[] = { | ||
1118 | { .name = DEV_NAME }, | ||
1119 | {}, | ||
1120 | }; | ||
1121 | |||
1122 | static int axp288_fuel_gauge_remove(struct platform_device *pdev) | ||
1123 | { | ||
1124 | struct axp288_fg_info *info = platform_get_drvdata(pdev); | ||
1125 | int i; | ||
1126 | |||
1127 | cancel_delayed_work_sync(&info->status_monitor); | ||
1128 | power_supply_unregister(&info->bat); | ||
1129 | fuel_gauge_remove_debugfs(info); | ||
1130 | |||
1131 | for (i = 0; i < AXP288_FG_INTR_NUM; i++) | ||
1132 | if (info->irq[i] >= 0) | ||
1133 | free_irq(info->irq[i], info); | ||
1134 | |||
1135 | return 0; | ||
1136 | } | ||
1137 | |||
1138 | static struct platform_driver axp288_fuel_gauge_driver = { | ||
1139 | .probe = axp288_fuel_gauge_probe, | ||
1140 | .remove = axp288_fuel_gauge_remove, | ||
1141 | .id_table = axp288_fg_id_table, | ||
1142 | .driver = { | ||
1143 | .name = DEV_NAME, | ||
1144 | }, | ||
1145 | }; | ||
1146 | |||
1147 | module_platform_driver(axp288_fuel_gauge_driver); | ||
1148 | |||
1149 | MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>"); | ||
1150 | MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); | ||
1151 | MODULE_LICENSE("GPL"); | ||