diff options
author | Ramakrishna Pallala <ramakrishna.pallala@intel.com> | 2015-05-04 12:46:07 -0400 |
---|---|---|
committer | Sebastian Reichel <sre@kernel.org> | 2015-05-24 04:32:24 -0400 |
commit | 843735b788a4e49c453f4aefdae80e6dfbe9ee85 (patch) | |
tree | 07f78ff4e44bf8f6ffdca5ab18e2a89f5a890d79 | |
parent | edd4ab0559316a1efe0881a4e2ccaeb4fec73142 (diff) |
power: axp288_charger: axp288 charger driver
This patch adds new power supply charger driver support
for X-Power AXP288 PMIC integrated charger.
This driver interfaces with the axp20x mfd driver as a cell
and listens to extcon cable events for setting up charging.
Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
Acked-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
-rw-r--r-- | drivers/power/Kconfig | 7 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/axp288_charger.c | 941 | ||||
-rw-r--r-- | include/linux/mfd/axp20x.h | 7 |
4 files changed, 956 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0fe5b08055ba..a848b1a5273e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig | |||
@@ -204,6 +204,13 @@ 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_CHARGER | ||
208 | tristate "X-Powers AXP288 Charger" | ||
209 | depends on MFD_AXP20X && EXTCON_AXP288 | ||
210 | help | ||
211 | Say yes here to have support X-Power AXP288 power management IC (PMIC) | ||
212 | integrated charger. | ||
213 | |||
207 | config AXP288_FUEL_GAUGE | 214 | config AXP288_FUEL_GAUGE |
208 | tristate "X-Powers AXP288 Fuel Gauge" | 215 | tristate "X-Powers AXP288 Fuel Gauge" |
209 | depends on MFD_AXP20X && IIO | 216 | depends on MFD_AXP20X && IIO |
diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 03942e99776b..3572a72432d0 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile | |||
@@ -66,3 +66,4 @@ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o | |||
66 | obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o | 66 | obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o |
67 | obj-$(CONFIG_POWER_RESET) += reset/ | 67 | obj-$(CONFIG_POWER_RESET) += reset/ |
68 | obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o | 68 | obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o |
69 | obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o | ||
diff --git a/drivers/power/axp288_charger.c b/drivers/power/axp288_charger.c new file mode 100644 index 000000000000..5680317f4823 --- /dev/null +++ b/drivers/power/axp288_charger.c | |||
@@ -0,0 +1,941 @@ | |||
1 | /* | ||
2 | * axp288_charger.c - X-power AXP288 PMIC Charger driver | ||
3 | * | ||
4 | * Copyright (C) 2014 Intel Corporation | ||
5 | * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/device.h> | ||
19 | #include <linux/regmap.h> | ||
20 | #include <linux/workqueue.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/usb/otg.h> | ||
24 | #include <linux/notifier.h> | ||
25 | #include <linux/power_supply.h> | ||
26 | #include <linux/notifier.h> | ||
27 | #include <linux/property.h> | ||
28 | #include <linux/mfd/axp20x.h> | ||
29 | #include <linux/extcon.h> | ||
30 | |||
31 | #define PS_STAT_VBUS_TRIGGER (1 << 0) | ||
32 | #define PS_STAT_BAT_CHRG_DIR (1 << 2) | ||
33 | #define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) | ||
34 | #define PS_STAT_VBUS_VALID (1 << 4) | ||
35 | #define PS_STAT_VBUS_PRESENT (1 << 5) | ||
36 | |||
37 | #define CHRG_STAT_BAT_SAFE_MODE (1 << 3) | ||
38 | #define CHRG_STAT_BAT_VALID (1 << 4) | ||
39 | #define CHRG_STAT_BAT_PRESENT (1 << 5) | ||
40 | #define CHRG_STAT_CHARGING (1 << 6) | ||
41 | #define CHRG_STAT_PMIC_OTP (1 << 7) | ||
42 | |||
43 | #define VBUS_ISPOUT_CUR_LIM_MASK 0x03 | ||
44 | #define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 | ||
45 | #define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ | ||
46 | #define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ | ||
47 | #define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ | ||
48 | #define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ | ||
49 | #define VBUS_ISPOUT_VHOLD_SET_MASK 0x31 | ||
50 | #define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 | ||
51 | #define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ | ||
52 | #define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ | ||
53 | #define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ | ||
54 | #define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7) | ||
55 | |||
56 | #define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ | ||
57 | #define CHRG_CCCV_CC_BIT_POS 0 | ||
58 | #define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ | ||
59 | #define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ | ||
60 | #define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ | ||
61 | #define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ | ||
62 | #define CHRG_CCCV_CV_BIT_POS 5 | ||
63 | #define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ | ||
64 | #define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ | ||
65 | #define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ | ||
66 | #define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ | ||
67 | #define CHRG_CCCV_CHG_EN (1 << 7) | ||
68 | |||
69 | #define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ | ||
70 | #define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ | ||
71 | #define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ | ||
72 | #define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ | ||
73 | #define CNTL2_CHGLED_TYPEB (1 << 4) | ||
74 | #define CNTL2_CHG_OUT_TURNON (1 << 5) | ||
75 | #define CNTL2_PC_TIMEOUT_MASK 0xC0 | ||
76 | #define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ | ||
77 | #define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ | ||
78 | #define CNTL2_PC_TIMEOUT_70MINS 0x3 | ||
79 | |||
80 | #define CHRG_ILIM_TEMP_LOOP_EN (1 << 3) | ||
81 | #define CHRG_VBUS_ILIM_MASK 0xf0 | ||
82 | #define CHRG_VBUS_ILIM_BIT_POS 4 | ||
83 | #define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ | ||
84 | #define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ | ||
85 | #define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ | ||
86 | #define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ | ||
87 | #define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ | ||
88 | #define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ | ||
89 | #define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ | ||
90 | |||
91 | #define CHRG_VLTFC_0C 0xA5 /* 0 DegC */ | ||
92 | #define CHRG_VHTFC_45C 0x1F /* 45 DegC */ | ||
93 | |||
94 | #define BAT_IRQ_CFG_CHRG_DONE (1 << 2) | ||
95 | #define BAT_IRQ_CFG_CHRG_START (1 << 3) | ||
96 | #define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4) | ||
97 | #define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5) | ||
98 | #define BAT_IRQ_CFG_BAT_DISCON (1 << 6) | ||
99 | #define BAT_IRQ_CFG_BAT_CONN (1 << 7) | ||
100 | #define BAT_IRQ_CFG_BAT_MASK 0xFC | ||
101 | |||
102 | #define TEMP_IRQ_CFG_QCBTU (1 << 4) | ||
103 | #define TEMP_IRQ_CFG_CBTU (1 << 5) | ||
104 | #define TEMP_IRQ_CFG_QCBTO (1 << 6) | ||
105 | #define TEMP_IRQ_CFG_CBTO (1 << 7) | ||
106 | #define TEMP_IRQ_CFG_MASK 0xF0 | ||
107 | |||
108 | #define FG_CNTL_OCV_ADJ_EN (1 << 3) | ||
109 | |||
110 | #define CV_4100MV 4100 /* 4100mV */ | ||
111 | #define CV_4150MV 4150 /* 4150mV */ | ||
112 | #define CV_4200MV 4200 /* 4200mV */ | ||
113 | #define CV_4350MV 4350 /* 4350mV */ | ||
114 | |||
115 | #define CC_200MA 200 /* 200mA */ | ||
116 | #define CC_600MA 600 /* 600mA */ | ||
117 | #define CC_800MA 800 /* 800mA */ | ||
118 | #define CC_1000MA 1000 /* 1000mA */ | ||
119 | #define CC_1600MA 1600 /* 1600mA */ | ||
120 | #define CC_2000MA 2000 /* 2000mA */ | ||
121 | |||
122 | #define ILIM_100MA 100 /* 100mA */ | ||
123 | #define ILIM_500MA 500 /* 500mA */ | ||
124 | #define ILIM_900MA 900 /* 900mA */ | ||
125 | #define ILIM_1500MA 1500 /* 1500mA */ | ||
126 | #define ILIM_2000MA 2000 /* 2000mA */ | ||
127 | #define ILIM_2500MA 2500 /* 2500mA */ | ||
128 | #define ILIM_3000MA 3000 /* 3000mA */ | ||
129 | |||
130 | #define AXP288_EXTCON_DEV_NAME "axp288_extcon" | ||
131 | |||
132 | #define AXP288_EXTCON_SLOW_CHARGER "SLOW-CHARGER" | ||
133 | #define AXP288_EXTCON_DOWNSTREAM_CHARGER "CHARGE-DOWNSTREAM" | ||
134 | #define AXP288_EXTCON_FAST_CHARGER "FAST-CHARGER" | ||
135 | |||
136 | enum { | ||
137 | VBUS_OV_IRQ = 0, | ||
138 | CHARGE_DONE_IRQ, | ||
139 | CHARGE_CHARGING_IRQ, | ||
140 | BAT_SAFE_QUIT_IRQ, | ||
141 | BAT_SAFE_ENTER_IRQ, | ||
142 | QCBTU_IRQ, | ||
143 | CBTU_IRQ, | ||
144 | QCBTO_IRQ, | ||
145 | CBTO_IRQ, | ||
146 | CHRG_INTR_END, | ||
147 | }; | ||
148 | |||
149 | struct axp288_chrg_info { | ||
150 | struct platform_device *pdev; | ||
151 | struct axp20x_chrg_pdata *pdata; | ||
152 | struct regmap *regmap; | ||
153 | struct regmap_irq_chip_data *regmap_irqc; | ||
154 | int irq[CHRG_INTR_END]; | ||
155 | struct power_supply *psy_usb; | ||
156 | struct mutex lock; | ||
157 | |||
158 | /* OTG/Host mode */ | ||
159 | struct { | ||
160 | struct work_struct work; | ||
161 | struct extcon_specific_cable_nb cable; | ||
162 | struct notifier_block id_nb; | ||
163 | bool id_short; | ||
164 | } otg; | ||
165 | |||
166 | /* SDP/CDP/DCP USB charging cable notifications */ | ||
167 | struct { | ||
168 | struct extcon_dev *edev; | ||
169 | bool connected; | ||
170 | enum power_supply_type chg_type; | ||
171 | struct notifier_block nb; | ||
172 | struct work_struct work; | ||
173 | } cable; | ||
174 | |||
175 | int health; | ||
176 | int inlmt; | ||
177 | int cc; | ||
178 | int cv; | ||
179 | int max_cc; | ||
180 | int max_cv; | ||
181 | bool online; | ||
182 | bool present; | ||
183 | bool enable_charger; | ||
184 | bool is_charger_enabled; | ||
185 | }; | ||
186 | |||
187 | static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) | ||
188 | { | ||
189 | u8 reg_val; | ||
190 | int ret; | ||
191 | |||
192 | if (cc < CHRG_CCCV_CC_OFFSET) | ||
193 | cc = CHRG_CCCV_CC_OFFSET; | ||
194 | else if (cc > info->max_cc) | ||
195 | cc = info->max_cc; | ||
196 | |||
197 | reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; | ||
198 | cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; | ||
199 | reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; | ||
200 | |||
201 | ret = regmap_update_bits(info->regmap, | ||
202 | AXP20X_CHRG_CTRL1, | ||
203 | CHRG_CCCV_CC_MASK, reg_val); | ||
204 | if (ret >= 0) | ||
205 | info->cc = cc; | ||
206 | |||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) | ||
211 | { | ||
212 | u8 reg_val; | ||
213 | int ret; | ||
214 | |||
215 | if (cv <= CV_4100MV) { | ||
216 | reg_val = CHRG_CCCV_CV_4100MV; | ||
217 | cv = CV_4100MV; | ||
218 | } else if (cv <= CV_4150MV) { | ||
219 | reg_val = CHRG_CCCV_CV_4150MV; | ||
220 | cv = CV_4150MV; | ||
221 | } else if (cv <= CV_4200MV) { | ||
222 | reg_val = CHRG_CCCV_CV_4200MV; | ||
223 | cv = CV_4200MV; | ||
224 | } else { | ||
225 | reg_val = CHRG_CCCV_CV_4350MV; | ||
226 | cv = CV_4350MV; | ||
227 | } | ||
228 | |||
229 | reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; | ||
230 | |||
231 | ret = regmap_update_bits(info->regmap, | ||
232 | AXP20X_CHRG_CTRL1, | ||
233 | CHRG_CCCV_CV_MASK, reg_val); | ||
234 | |||
235 | if (ret >= 0) | ||
236 | info->cv = cv; | ||
237 | |||
238 | return ret; | ||
239 | } | ||
240 | |||
241 | static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, | ||
242 | int inlmt) | ||
243 | { | ||
244 | int ret; | ||
245 | unsigned int val; | ||
246 | u8 reg_val; | ||
247 | |||
248 | /* Read in limit register */ | ||
249 | ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val); | ||
250 | if (ret < 0) | ||
251 | goto set_inlmt_fail; | ||
252 | |||
253 | if (inlmt <= ILIM_100MA) { | ||
254 | reg_val = CHRG_VBUS_ILIM_100MA; | ||
255 | inlmt = ILIM_100MA; | ||
256 | } else if (inlmt <= ILIM_500MA) { | ||
257 | reg_val = CHRG_VBUS_ILIM_500MA; | ||
258 | inlmt = ILIM_500MA; | ||
259 | } else if (inlmt <= ILIM_900MA) { | ||
260 | reg_val = CHRG_VBUS_ILIM_900MA; | ||
261 | inlmt = ILIM_900MA; | ||
262 | } else if (inlmt <= ILIM_1500MA) { | ||
263 | reg_val = CHRG_VBUS_ILIM_1500MA; | ||
264 | inlmt = ILIM_1500MA; | ||
265 | } else if (inlmt <= ILIM_2000MA) { | ||
266 | reg_val = CHRG_VBUS_ILIM_2000MA; | ||
267 | inlmt = ILIM_2000MA; | ||
268 | } else if (inlmt <= ILIM_2500MA) { | ||
269 | reg_val = CHRG_VBUS_ILIM_2500MA; | ||
270 | inlmt = ILIM_2500MA; | ||
271 | } else { | ||
272 | reg_val = CHRG_VBUS_ILIM_3000MA; | ||
273 | inlmt = ILIM_3000MA; | ||
274 | } | ||
275 | |||
276 | reg_val = (val & ~CHRG_VBUS_ILIM_MASK) | ||
277 | | (reg_val << CHRG_VBUS_ILIM_BIT_POS); | ||
278 | ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val); | ||
279 | if (ret >= 0) | ||
280 | info->inlmt = inlmt; | ||
281 | else | ||
282 | dev_err(&info->pdev->dev, "charger BAK control %d\n", ret); | ||
283 | |||
284 | |||
285 | set_inlmt_fail: | ||
286 | return ret; | ||
287 | } | ||
288 | |||
289 | static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, | ||
290 | bool enable) | ||
291 | { | ||
292 | int ret; | ||
293 | |||
294 | if (enable) | ||
295 | ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, | ||
296 | VBUS_ISPOUT_VBUS_PATH_DIS, 0); | ||
297 | else | ||
298 | ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, | ||
299 | VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); | ||
300 | |||
301 | if (ret < 0) | ||
302 | dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret); | ||
303 | |||
304 | |||
305 | return ret; | ||
306 | } | ||
307 | |||
308 | static int axp288_charger_enable_charger(struct axp288_chrg_info *info, | ||
309 | bool enable) | ||
310 | { | ||
311 | int ret; | ||
312 | |||
313 | if (enable) | ||
314 | ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, | ||
315 | CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); | ||
316 | else | ||
317 | ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, | ||
318 | CHRG_CCCV_CHG_EN, 0); | ||
319 | if (ret < 0) | ||
320 | dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret); | ||
321 | else | ||
322 | info->is_charger_enabled = enable; | ||
323 | |||
324 | return ret; | ||
325 | } | ||
326 | |||
327 | static int axp288_charger_is_present(struct axp288_chrg_info *info) | ||
328 | { | ||
329 | int ret, present = 0; | ||
330 | unsigned int val; | ||
331 | |||
332 | ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); | ||
333 | if (ret < 0) | ||
334 | return ret; | ||
335 | |||
336 | if (val & PS_STAT_VBUS_PRESENT) | ||
337 | present = 1; | ||
338 | return present; | ||
339 | } | ||
340 | |||
341 | static int axp288_charger_is_online(struct axp288_chrg_info *info) | ||
342 | { | ||
343 | int ret, online = 0; | ||
344 | unsigned int val; | ||
345 | |||
346 | ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); | ||
347 | if (ret < 0) | ||
348 | return ret; | ||
349 | |||
350 | if (val & PS_STAT_VBUS_VALID) | ||
351 | online = 1; | ||
352 | return online; | ||
353 | } | ||
354 | |||
355 | static int axp288_get_charger_health(struct axp288_chrg_info *info) | ||
356 | { | ||
357 | int ret, pwr_stat, chrg_stat; | ||
358 | int health = POWER_SUPPLY_HEALTH_UNKNOWN; | ||
359 | unsigned int val; | ||
360 | |||
361 | ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); | ||
362 | if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT)) | ||
363 | goto health_read_fail; | ||
364 | else | ||
365 | pwr_stat = val; | ||
366 | |||
367 | ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val); | ||
368 | if (ret < 0) | ||
369 | goto health_read_fail; | ||
370 | else | ||
371 | chrg_stat = val; | ||
372 | |||
373 | if (!(pwr_stat & PS_STAT_VBUS_VALID)) | ||
374 | health = POWER_SUPPLY_HEALTH_DEAD; | ||
375 | else if (chrg_stat & CHRG_STAT_PMIC_OTP) | ||
376 | health = POWER_SUPPLY_HEALTH_OVERHEAT; | ||
377 | else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE) | ||
378 | health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; | ||
379 | else | ||
380 | health = POWER_SUPPLY_HEALTH_GOOD; | ||
381 | |||
382 | health_read_fail: | ||
383 | return health; | ||
384 | } | ||
385 | |||
386 | static int axp288_charger_usb_set_property(struct power_supply *psy, | ||
387 | enum power_supply_property psp, | ||
388 | const union power_supply_propval *val) | ||
389 | { | ||
390 | struct axp288_chrg_info *info = power_supply_get_drvdata(psy); | ||
391 | int ret = 0; | ||
392 | int scaled_val; | ||
393 | |||
394 | mutex_lock(&info->lock); | ||
395 | |||
396 | switch (psp) { | ||
397 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | ||
398 | scaled_val = min(val->intval, info->max_cc); | ||
399 | scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); | ||
400 | ret = axp288_charger_set_cc(info, scaled_val); | ||
401 | if (ret < 0) | ||
402 | dev_warn(&info->pdev->dev, "set charge current failed\n"); | ||
403 | break; | ||
404 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: | ||
405 | scaled_val = min(val->intval, info->max_cv); | ||
406 | scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); | ||
407 | ret = axp288_charger_set_cv(info, scaled_val); | ||
408 | if (ret < 0) | ||
409 | dev_warn(&info->pdev->dev, "set charge voltage failed\n"); | ||
410 | break; | ||
411 | default: | ||
412 | ret = -EINVAL; | ||
413 | } | ||
414 | |||
415 | mutex_unlock(&info->lock); | ||
416 | return ret; | ||
417 | } | ||
418 | |||
419 | static int axp288_charger_usb_get_property(struct power_supply *psy, | ||
420 | enum power_supply_property psp, | ||
421 | union power_supply_propval *val) | ||
422 | { | ||
423 | struct axp288_chrg_info *info = power_supply_get_drvdata(psy); | ||
424 | int ret = 0; | ||
425 | |||
426 | mutex_lock(&info->lock); | ||
427 | |||
428 | switch (psp) { | ||
429 | case POWER_SUPPLY_PROP_PRESENT: | ||
430 | /* Check for OTG case first */ | ||
431 | if (info->otg.id_short) { | ||
432 | val->intval = 0; | ||
433 | break; | ||
434 | } | ||
435 | ret = axp288_charger_is_present(info); | ||
436 | if (ret < 0) | ||
437 | goto psy_get_prop_fail; | ||
438 | info->present = ret; | ||
439 | val->intval = info->present; | ||
440 | break; | ||
441 | case POWER_SUPPLY_PROP_ONLINE: | ||
442 | /* Check for OTG case first */ | ||
443 | if (info->otg.id_short) { | ||
444 | val->intval = 0; | ||
445 | break; | ||
446 | } | ||
447 | ret = axp288_charger_is_online(info); | ||
448 | if (ret < 0) | ||
449 | goto psy_get_prop_fail; | ||
450 | info->online = ret; | ||
451 | val->intval = info->online; | ||
452 | break; | ||
453 | case POWER_SUPPLY_PROP_HEALTH: | ||
454 | val->intval = axp288_get_charger_health(info); | ||
455 | break; | ||
456 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | ||
457 | val->intval = info->cc * 1000; | ||
458 | break; | ||
459 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: | ||
460 | val->intval = info->max_cc * 1000; | ||
461 | break; | ||
462 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: | ||
463 | val->intval = info->cv * 1000; | ||
464 | break; | ||
465 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: | ||
466 | val->intval = info->max_cv * 1000; | ||
467 | break; | ||
468 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: | ||
469 | val->intval = info->inlmt * 1000; | ||
470 | break; | ||
471 | default: | ||
472 | ret = -EINVAL; | ||
473 | goto psy_get_prop_fail; | ||
474 | } | ||
475 | |||
476 | psy_get_prop_fail: | ||
477 | mutex_unlock(&info->lock); | ||
478 | return ret; | ||
479 | } | ||
480 | |||
481 | static int axp288_charger_property_is_writeable(struct power_supply *psy, | ||
482 | enum power_supply_property psp) | ||
483 | { | ||
484 | int ret; | ||
485 | |||
486 | switch (psp) { | ||
487 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | ||
488 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: | ||
489 | ret = 1; | ||
490 | break; | ||
491 | default: | ||
492 | ret = 0; | ||
493 | } | ||
494 | |||
495 | return ret; | ||
496 | } | ||
497 | |||
498 | static enum power_supply_property axp288_usb_props[] = { | ||
499 | POWER_SUPPLY_PROP_PRESENT, | ||
500 | POWER_SUPPLY_PROP_ONLINE, | ||
501 | POWER_SUPPLY_PROP_TYPE, | ||
502 | POWER_SUPPLY_PROP_HEALTH, | ||
503 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, | ||
504 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, | ||
505 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, | ||
506 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, | ||
507 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, | ||
508 | }; | ||
509 | |||
510 | static const struct power_supply_desc axp288_charger_desc = { | ||
511 | .name = "axp288_charger", | ||
512 | .type = POWER_SUPPLY_TYPE_USB, | ||
513 | .properties = axp288_usb_props, | ||
514 | .num_properties = ARRAY_SIZE(axp288_usb_props), | ||
515 | .get_property = axp288_charger_usb_get_property, | ||
516 | .set_property = axp288_charger_usb_set_property, | ||
517 | .property_is_writeable = axp288_charger_property_is_writeable, | ||
518 | }; | ||
519 | |||
520 | static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) | ||
521 | { | ||
522 | struct axp288_chrg_info *info = dev; | ||
523 | int i; | ||
524 | |||
525 | for (i = 0; i < CHRG_INTR_END; i++) { | ||
526 | if (info->irq[i] == irq) | ||
527 | break; | ||
528 | } | ||
529 | |||
530 | if (i >= CHRG_INTR_END) { | ||
531 | dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); | ||
532 | return IRQ_NONE; | ||
533 | } | ||
534 | |||
535 | switch (i) { | ||
536 | case VBUS_OV_IRQ: | ||
537 | dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); | ||
538 | break; | ||
539 | case CHARGE_DONE_IRQ: | ||
540 | dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); | ||
541 | break; | ||
542 | case CHARGE_CHARGING_IRQ: | ||
543 | dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); | ||
544 | break; | ||
545 | case BAT_SAFE_QUIT_IRQ: | ||
546 | dev_dbg(&info->pdev->dev, | ||
547 | "Quit Safe Mode(restart timer) Charging IRQ\n"); | ||
548 | break; | ||
549 | case BAT_SAFE_ENTER_IRQ: | ||
550 | dev_dbg(&info->pdev->dev, | ||
551 | "Enter Safe Mode(timer expire) Charging IRQ\n"); | ||
552 | break; | ||
553 | case QCBTU_IRQ: | ||
554 | dev_dbg(&info->pdev->dev, | ||
555 | "Quit Battery Under Temperature(CHRG) INTR\n"); | ||
556 | break; | ||
557 | case CBTU_IRQ: | ||
558 | dev_dbg(&info->pdev->dev, | ||
559 | "Hit Battery Under Temperature(CHRG) INTR\n"); | ||
560 | break; | ||
561 | case QCBTO_IRQ: | ||
562 | dev_dbg(&info->pdev->dev, | ||
563 | "Quit Battery Over Temperature(CHRG) INTR\n"); | ||
564 | break; | ||
565 | case CBTO_IRQ: | ||
566 | dev_dbg(&info->pdev->dev, | ||
567 | "Hit Battery Over Temperature(CHRG) INTR\n"); | ||
568 | break; | ||
569 | default: | ||
570 | dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); | ||
571 | goto out; | ||
572 | } | ||
573 | |||
574 | power_supply_changed(info->psy_usb); | ||
575 | out: | ||
576 | return IRQ_HANDLED; | ||
577 | } | ||
578 | |||
579 | static void axp288_charger_extcon_evt_worker(struct work_struct *work) | ||
580 | { | ||
581 | struct axp288_chrg_info *info = | ||
582 | container_of(work, struct axp288_chrg_info, cable.work); | ||
583 | int ret, current_limit; | ||
584 | bool changed = false; | ||
585 | struct extcon_dev *edev = info->cable.edev; | ||
586 | bool old_connected = info->cable.connected; | ||
587 | |||
588 | /* Determine cable/charger type */ | ||
589 | if (extcon_get_cable_state(edev, AXP288_EXTCON_SLOW_CHARGER) > 0) { | ||
590 | dev_dbg(&info->pdev->dev, "USB SDP charger is connected"); | ||
591 | info->cable.connected = true; | ||
592 | info->cable.chg_type = POWER_SUPPLY_TYPE_USB; | ||
593 | } else if (extcon_get_cable_state(edev, | ||
594 | AXP288_EXTCON_DOWNSTREAM_CHARGER) > 0) { | ||
595 | dev_dbg(&info->pdev->dev, "USB CDP charger is connected"); | ||
596 | info->cable.connected = true; | ||
597 | info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP; | ||
598 | } else if (extcon_get_cable_state(edev, | ||
599 | AXP288_EXTCON_FAST_CHARGER) > 0) { | ||
600 | dev_dbg(&info->pdev->dev, "USB DCP charger is connected"); | ||
601 | info->cable.connected = true; | ||
602 | info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP; | ||
603 | } else { | ||
604 | if (old_connected) | ||
605 | dev_dbg(&info->pdev->dev, "USB charger disconnected"); | ||
606 | info->cable.connected = false; | ||
607 | info->cable.chg_type = POWER_SUPPLY_TYPE_USB; | ||
608 | } | ||
609 | |||
610 | /* Cable status changed */ | ||
611 | if (old_connected != info->cable.connected) | ||
612 | changed = true; | ||
613 | |||
614 | if (!changed) | ||
615 | return; | ||
616 | |||
617 | mutex_lock(&info->lock); | ||
618 | |||
619 | if (info->is_charger_enabled && !info->cable.connected) { | ||
620 | info->enable_charger = false; | ||
621 | ret = axp288_charger_enable_charger(info, info->enable_charger); | ||
622 | if (ret < 0) | ||
623 | dev_err(&info->pdev->dev, | ||
624 | "cannot disable charger (%d)", ret); | ||
625 | |||
626 | } else if (!info->is_charger_enabled && info->cable.connected) { | ||
627 | switch (info->cable.chg_type) { | ||
628 | case POWER_SUPPLY_TYPE_USB: | ||
629 | current_limit = ILIM_500MA; | ||
630 | break; | ||
631 | case POWER_SUPPLY_TYPE_USB_CDP: | ||
632 | current_limit = ILIM_1500MA; | ||
633 | break; | ||
634 | case POWER_SUPPLY_TYPE_USB_DCP: | ||
635 | current_limit = ILIM_2000MA; | ||
636 | break; | ||
637 | default: | ||
638 | /* Unknown */ | ||
639 | current_limit = 0; | ||
640 | break; | ||
641 | } | ||
642 | |||
643 | /* Set vbus current limit first, then enable charger */ | ||
644 | ret = axp288_charger_set_vbus_inlmt(info, current_limit); | ||
645 | if (ret < 0) { | ||
646 | dev_err(&info->pdev->dev, | ||
647 | "error setting current limit (%d)", ret); | ||
648 | } else { | ||
649 | info->enable_charger = (current_limit > 0); | ||
650 | ret = axp288_charger_enable_charger(info, | ||
651 | info->enable_charger); | ||
652 | if (ret < 0) | ||
653 | dev_err(&info->pdev->dev, | ||
654 | "cannot enable charger (%d)", ret); | ||
655 | } | ||
656 | } | ||
657 | |||
658 | if (changed) | ||
659 | info->health = axp288_get_charger_health(info); | ||
660 | |||
661 | mutex_unlock(&info->lock); | ||
662 | |||
663 | if (changed) | ||
664 | power_supply_changed(info->psy_usb); | ||
665 | } | ||
666 | |||
667 | static int axp288_charger_handle_cable_evt(struct notifier_block *nb, | ||
668 | unsigned long event, void *param) | ||
669 | { | ||
670 | struct axp288_chrg_info *info = | ||
671 | container_of(nb, struct axp288_chrg_info, cable.nb); | ||
672 | |||
673 | schedule_work(&info->cable.work); | ||
674 | |||
675 | return NOTIFY_OK; | ||
676 | } | ||
677 | |||
678 | static void axp288_charger_otg_evt_worker(struct work_struct *work) | ||
679 | { | ||
680 | struct axp288_chrg_info *info = | ||
681 | container_of(work, struct axp288_chrg_info, otg.work); | ||
682 | int ret; | ||
683 | |||
684 | /* Disable VBUS path before enabling the 5V boost */ | ||
685 | ret = axp288_charger_vbus_path_select(info, !info->otg.id_short); | ||
686 | if (ret < 0) | ||
687 | dev_warn(&info->pdev->dev, "vbus path disable failed\n"); | ||
688 | } | ||
689 | |||
690 | static int axp288_charger_handle_otg_evt(struct notifier_block *nb, | ||
691 | unsigned long event, void *param) | ||
692 | { | ||
693 | struct axp288_chrg_info *info = | ||
694 | container_of(nb, struct axp288_chrg_info, otg.id_nb); | ||
695 | struct extcon_dev *edev = param; | ||
696 | int usb_host = extcon_get_cable_state(edev, "USB-Host"); | ||
697 | |||
698 | dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n", | ||
699 | usb_host ? "attached" : "detached"); | ||
700 | |||
701 | /* | ||
702 | * Set usb_id_short flag to avoid running charger detection logic | ||
703 | * in case usb host. | ||
704 | */ | ||
705 | info->otg.id_short = usb_host; | ||
706 | schedule_work(&info->otg.work); | ||
707 | |||
708 | return NOTIFY_OK; | ||
709 | } | ||
710 | |||
711 | static void charger_init_hw_regs(struct axp288_chrg_info *info) | ||
712 | { | ||
713 | int ret, cc, cv; | ||
714 | unsigned int val; | ||
715 | |||
716 | /* Program temperature thresholds */ | ||
717 | ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C); | ||
718 | if (ret < 0) | ||
719 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
720 | AXP20X_V_LTF_CHRG, ret); | ||
721 | |||
722 | ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C); | ||
723 | if (ret < 0) | ||
724 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
725 | AXP20X_V_HTF_CHRG, ret); | ||
726 | |||
727 | /* Do not turn-off charger o/p after charge cycle ends */ | ||
728 | ret = regmap_update_bits(info->regmap, | ||
729 | AXP20X_CHRG_CTRL2, | ||
730 | CNTL2_CHG_OUT_TURNON, 1); | ||
731 | if (ret < 0) | ||
732 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
733 | AXP20X_CHRG_CTRL2, ret); | ||
734 | |||
735 | /* Enable interrupts */ | ||
736 | ret = regmap_update_bits(info->regmap, | ||
737 | AXP20X_IRQ2_EN, | ||
738 | BAT_IRQ_CFG_BAT_MASK, 1); | ||
739 | if (ret < 0) | ||
740 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
741 | AXP20X_IRQ2_EN, ret); | ||
742 | |||
743 | ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN, | ||
744 | TEMP_IRQ_CFG_MASK, 1); | ||
745 | if (ret < 0) | ||
746 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
747 | AXP20X_IRQ3_EN, ret); | ||
748 | |||
749 | /* Setup ending condition for charging to be 10% of I(chrg) */ | ||
750 | ret = regmap_update_bits(info->regmap, | ||
751 | AXP20X_CHRG_CTRL1, | ||
752 | CHRG_CCCV_ITERM_20P, 0); | ||
753 | if (ret < 0) | ||
754 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
755 | AXP20X_CHRG_CTRL1, ret); | ||
756 | |||
757 | /* Disable OCV-SOC curve calibration */ | ||
758 | ret = regmap_update_bits(info->regmap, | ||
759 | AXP20X_CC_CTRL, | ||
760 | FG_CNTL_OCV_ADJ_EN, 0); | ||
761 | if (ret < 0) | ||
762 | dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", | ||
763 | AXP20X_CC_CTRL, ret); | ||
764 | |||
765 | /* Init charging current and voltage */ | ||
766 | info->max_cc = info->pdata->max_cc; | ||
767 | info->max_cv = info->pdata->max_cv; | ||
768 | |||
769 | /* Read current charge voltage and current limit */ | ||
770 | ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val); | ||
771 | if (ret < 0) { | ||
772 | /* Assume default if cannot read */ | ||
773 | info->cc = info->pdata->def_cc; | ||
774 | info->cv = info->pdata->def_cv; | ||
775 | } else { | ||
776 | /* Determine charge voltage */ | ||
777 | cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; | ||
778 | switch (cv) { | ||
779 | case CHRG_CCCV_CV_4100MV: | ||
780 | info->cv = CV_4100MV; | ||
781 | break; | ||
782 | case CHRG_CCCV_CV_4150MV: | ||
783 | info->cv = CV_4150MV; | ||
784 | break; | ||
785 | case CHRG_CCCV_CV_4200MV: | ||
786 | info->cv = CV_4200MV; | ||
787 | break; | ||
788 | case CHRG_CCCV_CV_4350MV: | ||
789 | info->cv = CV_4350MV; | ||
790 | break; | ||
791 | default: | ||
792 | info->cv = INT_MAX; | ||
793 | break; | ||
794 | } | ||
795 | |||
796 | /* Determine charge current limit */ | ||
797 | cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; | ||
798 | cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; | ||
799 | info->cc = cc; | ||
800 | |||
801 | /* Program default charging voltage and current */ | ||
802 | cc = min(info->pdata->def_cc, info->max_cc); | ||
803 | cv = min(info->pdata->def_cv, info->max_cv); | ||
804 | |||
805 | ret = axp288_charger_set_cc(info, cc); | ||
806 | if (ret < 0) | ||
807 | dev_warn(&info->pdev->dev, | ||
808 | "error(%d) in setting CC\n", ret); | ||
809 | |||
810 | ret = axp288_charger_set_cv(info, cv); | ||
811 | if (ret < 0) | ||
812 | dev_warn(&info->pdev->dev, | ||
813 | "error(%d) in setting CV\n", ret); | ||
814 | } | ||
815 | } | ||
816 | |||
817 | static int axp288_charger_probe(struct platform_device *pdev) | ||
818 | { | ||
819 | int ret, i, pirq; | ||
820 | struct axp288_chrg_info *info; | ||
821 | struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); | ||
822 | struct power_supply_config charger_cfg = {}; | ||
823 | |||
824 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | ||
825 | if (!info) | ||
826 | return -ENOMEM; | ||
827 | |||
828 | info->pdev = pdev; | ||
829 | info->regmap = axp20x->regmap; | ||
830 | info->regmap_irqc = axp20x->regmap_irqc; | ||
831 | info->pdata = pdev->dev.platform_data; | ||
832 | |||
833 | if (!info->pdata) { | ||
834 | /* Try ACPI provided pdata via device properties */ | ||
835 | if (!device_property_present(&pdev->dev, | ||
836 | "axp288_charger_data\n")) | ||
837 | dev_err(&pdev->dev, "failed to get platform data\n"); | ||
838 | return -ENODEV; | ||
839 | } | ||
840 | |||
841 | info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); | ||
842 | if (info->cable.edev == NULL) { | ||
843 | dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n", | ||
844 | AXP288_EXTCON_DEV_NAME); | ||
845 | return -EPROBE_DEFER; | ||
846 | } | ||
847 | |||
848 | /* Register for extcon notification */ | ||
849 | INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker); | ||
850 | info->cable.nb.notifier_call = axp288_charger_handle_cable_evt; | ||
851 | ret = extcon_register_notifier(info->cable.edev, &info->cable.nb); | ||
852 | if (ret) { | ||
853 | dev_err(&info->pdev->dev, | ||
854 | "failed to register extcon notifier %d\n", ret); | ||
855 | return ret; | ||
856 | } | ||
857 | |||
858 | platform_set_drvdata(pdev, info); | ||
859 | mutex_init(&info->lock); | ||
860 | |||
861 | /* Register with power supply class */ | ||
862 | charger_cfg.drv_data = info; | ||
863 | info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc, | ||
864 | &charger_cfg); | ||
865 | if (IS_ERR(info->psy_usb)) { | ||
866 | dev_err(&pdev->dev, "failed to register power supply charger\n"); | ||
867 | ret = PTR_ERR(info->psy_usb); | ||
868 | goto psy_reg_failed; | ||
869 | } | ||
870 | |||
871 | /* Register for OTG notification */ | ||
872 | INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker); | ||
873 | info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; | ||
874 | ret = extcon_register_interest(&info->otg.cable, NULL, "USB-Host", | ||
875 | &info->otg.id_nb); | ||
876 | if (ret) | ||
877 | dev_warn(&pdev->dev, "failed to register otg notifier\n"); | ||
878 | |||
879 | if (info->otg.cable.edev) | ||
880 | info->otg.id_short = extcon_get_cable_state( | ||
881 | info->otg.cable.edev, "USB-Host"); | ||
882 | |||
883 | /* Register charger interrupts */ | ||
884 | for (i = 0; i < CHRG_INTR_END; i++) { | ||
885 | pirq = platform_get_irq(info->pdev, i); | ||
886 | info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); | ||
887 | if (info->irq[i] < 0) { | ||
888 | dev_warn(&info->pdev->dev, | ||
889 | "failed to get virtual interrupt=%d\n", pirq); | ||
890 | ret = info->irq[i]; | ||
891 | goto intr_reg_failed; | ||
892 | } | ||
893 | ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], | ||
894 | NULL, axp288_charger_irq_thread_handler, | ||
895 | IRQF_ONESHOT, info->pdev->name, info); | ||
896 | if (ret) { | ||
897 | dev_err(&pdev->dev, "failed to request interrupt=%d\n", | ||
898 | info->irq[i]); | ||
899 | goto intr_reg_failed; | ||
900 | } | ||
901 | } | ||
902 | |||
903 | charger_init_hw_regs(info); | ||
904 | |||
905 | return 0; | ||
906 | |||
907 | intr_reg_failed: | ||
908 | if (info->otg.cable.edev) | ||
909 | extcon_unregister_interest(&info->otg.cable); | ||
910 | power_supply_unregister(info->psy_usb); | ||
911 | psy_reg_failed: | ||
912 | extcon_unregister_notifier(info->cable.edev, &info->cable.nb); | ||
913 | return ret; | ||
914 | } | ||
915 | |||
916 | static int axp288_charger_remove(struct platform_device *pdev) | ||
917 | { | ||
918 | struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev); | ||
919 | |||
920 | if (info->otg.cable.edev) | ||
921 | extcon_unregister_interest(&info->otg.cable); | ||
922 | |||
923 | extcon_unregister_notifier(info->cable.edev, &info->cable.nb); | ||
924 | power_supply_unregister(info->psy_usb); | ||
925 | |||
926 | return 0; | ||
927 | } | ||
928 | |||
929 | static struct platform_driver axp288_charger_driver = { | ||
930 | .probe = axp288_charger_probe, | ||
931 | .remove = axp288_charger_remove, | ||
932 | .driver = { | ||
933 | .name = "axp288_charger", | ||
934 | }, | ||
935 | }; | ||
936 | |||
937 | module_platform_driver(axp288_charger_driver); | ||
938 | |||
939 | MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); | ||
940 | MODULE_DESCRIPTION("X-power AXP288 Charger Driver"); | ||
941 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index dfabd6db7ddf..f9030df5acd1 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h | |||
@@ -275,4 +275,11 @@ struct axp20x_fg_pdata { | |||
275 | int thermistor_curve[MAX_THERM_CURVE_SIZE][2]; | 275 | int thermistor_curve[MAX_THERM_CURVE_SIZE][2]; |
276 | }; | 276 | }; |
277 | 277 | ||
278 | struct axp20x_chrg_pdata { | ||
279 | int max_cc; | ||
280 | int max_cv; | ||
281 | int def_cc; | ||
282 | int def_cv; | ||
283 | }; | ||
284 | |||
278 | #endif /* __LINUX_MFD_AXP20X_H */ | 285 | #endif /* __LINUX_MFD_AXP20X_H */ |