1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * axp288_charger.c - X-power AXP288 PMIC Charger driver |
4 | * |
5 | * Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com> |
6 | * Copyright (C) 2014 Intel Corporation |
7 | * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> |
8 | */ |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/bitops.h> |
12 | #include <linux/module.h> |
13 | #include <linux/device.h> |
14 | #include <linux/regmap.h> |
15 | #include <linux/workqueue.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/usb/otg.h> |
19 | #include <linux/notifier.h> |
20 | #include <linux/power_supply.h> |
21 | #include <linux/property.h> |
22 | #include <linux/mfd/axp20x.h> |
23 | #include <linux/extcon.h> |
24 | #include <linux/dmi.h> |
25 | #include <asm/iosf_mbi.h> |
26 | |
27 | #define PS_STAT_VBUS_TRIGGER BIT(0) |
28 | #define PS_STAT_BAT_CHRG_DIR BIT(2) |
29 | #define PS_STAT_VBAT_ABOVE_VHOLD BIT(3) |
30 | #define PS_STAT_VBUS_VALID BIT(4) |
31 | #define PS_STAT_VBUS_PRESENT BIT(5) |
32 | |
33 | #define CHRG_STAT_BAT_SAFE_MODE BIT(3) |
34 | #define CHRG_STAT_BAT_VALID BIT(4) |
35 | #define CHRG_STAT_BAT_PRESENT BIT(5) |
36 | #define CHRG_STAT_CHARGING BIT(6) |
37 | #define CHRG_STAT_PMIC_OTP BIT(7) |
38 | |
39 | #define VBUS_ISPOUT_CUR_LIM_MASK 0x03 |
40 | #define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 |
41 | #define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ |
42 | #define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ |
43 | #define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ |
44 | #define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ |
45 | #define VBUS_ISPOUT_VHOLD_SET_MASK 0x38 |
46 | #define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 |
47 | #define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ |
48 | #define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ |
49 | #define VBUS_ISPOUT_VHOLD_SET_4400MV 0x4 /* 4400mV */ |
50 | #define VBUS_ISPOUT_VBUS_PATH_DIS BIT(7) |
51 | |
52 | #define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ |
53 | #define CHRG_CCCV_CC_BIT_POS 0 |
54 | #define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ |
55 | #define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ |
56 | #define CHRG_CCCV_ITERM_20P BIT(4) /* 20% of CC */ |
57 | #define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ |
58 | #define CHRG_CCCV_CV_BIT_POS 5 |
59 | #define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ |
60 | #define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ |
61 | #define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ |
62 | #define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ |
63 | #define CHRG_CCCV_CHG_EN BIT(7) |
64 | |
65 | #define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ |
66 | #define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ |
67 | #define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ |
68 | #define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ |
69 | #define CNTL2_CHGLED_TYPEB BIT(4) |
70 | #define CNTL2_CHG_OUT_TURNON BIT(5) |
71 | #define CNTL2_PC_TIMEOUT_MASK 0xC0 |
72 | #define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ |
73 | #define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ |
74 | #define CNTL2_PC_TIMEOUT_70MINS 0x3 |
75 | |
76 | #define CHRG_ILIM_TEMP_LOOP_EN BIT(3) |
77 | #define CHRG_VBUS_ILIM_MASK 0xf0 |
78 | #define CHRG_VBUS_ILIM_BIT_POS 4 |
79 | #define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ |
80 | #define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ |
81 | #define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ |
82 | #define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ |
83 | #define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ |
84 | #define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ |
85 | #define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ |
86 | #define CHRG_VBUS_ILIM_3500MA 0x7 /* 3500mA */ |
87 | #define CHRG_VBUS_ILIM_4000MA 0x8 /* 4000mA */ |
88 | |
89 | #define CHRG_VLTFC_0C 0xA5 /* 0 DegC */ |
90 | #define CHRG_VHTFC_45C 0x1F /* 45 DegC */ |
91 | |
92 | #define FG_CNTL_OCV_ADJ_EN BIT(3) |
93 | |
94 | #define CV_4100MV 4100 /* 4100mV */ |
95 | #define CV_4150MV 4150 /* 4150mV */ |
96 | #define CV_4200MV 4200 /* 4200mV */ |
97 | #define CV_4350MV 4350 /* 4350mV */ |
98 | |
99 | #define AXP288_REG_UPDATE_INTERVAL (60 * HZ) |
100 | |
101 | #define AXP288_EXTCON_DEV_NAME "axp288_extcon" |
102 | #define USB_HOST_EXTCON_HID "INT3496" |
103 | #define USB_HOST_EXTCON_NAME "INT3496:00" |
104 | |
105 | enum { |
106 | VBUS_OV_IRQ = 0, |
107 | CHARGE_DONE_IRQ, |
108 | CHARGE_CHARGING_IRQ, |
109 | BAT_SAFE_QUIT_IRQ, |
110 | BAT_SAFE_ENTER_IRQ, |
111 | QCBTU_IRQ, |
112 | CBTU_IRQ, |
113 | QCBTO_IRQ, |
114 | CBTO_IRQ, |
115 | CHRG_INTR_END, |
116 | }; |
117 | |
118 | struct axp288_chrg_info { |
119 | struct platform_device *pdev; |
120 | struct regmap *regmap; |
121 | struct regmap_irq_chip_data *regmap_irqc; |
122 | int irq[CHRG_INTR_END]; |
123 | struct power_supply *psy_usb; |
124 | struct mutex lock; |
125 | |
126 | /* OTG/Host mode */ |
127 | struct { |
128 | struct work_struct work; |
129 | struct extcon_dev *cable; |
130 | struct notifier_block id_nb; |
131 | bool id_short; |
132 | } otg; |
133 | |
134 | /* SDP/CDP/DCP USB charging cable notifications */ |
135 | struct { |
136 | struct extcon_dev *edev; |
137 | struct notifier_block nb; |
138 | struct work_struct work; |
139 | } cable; |
140 | |
141 | int cc; |
142 | int cv; |
143 | int max_cc; |
144 | int max_cv; |
145 | |
146 | unsigned long last_updated; |
147 | unsigned int input_status; |
148 | unsigned int op_mode; |
149 | unsigned int backend_control; |
150 | bool valid; |
151 | }; |
152 | |
153 | static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) |
154 | { |
155 | u8 reg_val; |
156 | int ret; |
157 | |
158 | if (cc < CHRG_CCCV_CC_OFFSET) |
159 | cc = CHRG_CCCV_CC_OFFSET; |
160 | else if (cc > info->max_cc) |
161 | cc = info->max_cc; |
162 | |
163 | reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; |
164 | cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; |
165 | reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; |
166 | |
167 | ret = regmap_update_bits(map: info->regmap, |
168 | AXP20X_CHRG_CTRL1, |
169 | CHRG_CCCV_CC_MASK, val: reg_val); |
170 | if (ret >= 0) |
171 | info->cc = cc; |
172 | |
173 | return ret; |
174 | } |
175 | |
176 | static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) |
177 | { |
178 | u8 reg_val; |
179 | int ret; |
180 | |
181 | if (cv <= CV_4100MV) { |
182 | reg_val = CHRG_CCCV_CV_4100MV; |
183 | cv = CV_4100MV; |
184 | } else if (cv <= CV_4150MV) { |
185 | reg_val = CHRG_CCCV_CV_4150MV; |
186 | cv = CV_4150MV; |
187 | } else if (cv <= CV_4200MV) { |
188 | reg_val = CHRG_CCCV_CV_4200MV; |
189 | cv = CV_4200MV; |
190 | } else { |
191 | reg_val = CHRG_CCCV_CV_4350MV; |
192 | cv = CV_4350MV; |
193 | } |
194 | |
195 | reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; |
196 | |
197 | ret = regmap_update_bits(map: info->regmap, |
198 | AXP20X_CHRG_CTRL1, |
199 | CHRG_CCCV_CV_MASK, val: reg_val); |
200 | |
201 | if (ret >= 0) |
202 | info->cv = cv; |
203 | |
204 | return ret; |
205 | } |
206 | |
207 | static int axp288_charger_get_vbus_inlmt(struct axp288_chrg_info *info) |
208 | { |
209 | unsigned int val; |
210 | |
211 | val = info->backend_control; |
212 | |
213 | val >>= CHRG_VBUS_ILIM_BIT_POS; |
214 | switch (val) { |
215 | case CHRG_VBUS_ILIM_100MA: |
216 | return 100000; |
217 | case CHRG_VBUS_ILIM_500MA: |
218 | return 500000; |
219 | case CHRG_VBUS_ILIM_900MA: |
220 | return 900000; |
221 | case CHRG_VBUS_ILIM_1500MA: |
222 | return 1500000; |
223 | case CHRG_VBUS_ILIM_2000MA: |
224 | return 2000000; |
225 | case CHRG_VBUS_ILIM_2500MA: |
226 | return 2500000; |
227 | case CHRG_VBUS_ILIM_3000MA: |
228 | return 3000000; |
229 | case CHRG_VBUS_ILIM_3500MA: |
230 | return 3500000; |
231 | default: |
232 | /* All b1xxx values map to 4000 mA */ |
233 | return 4000000; |
234 | } |
235 | } |
236 | |
237 | static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, |
238 | int inlmt) |
239 | { |
240 | int ret; |
241 | u8 reg_val; |
242 | |
243 | if (inlmt >= 4000000) |
244 | reg_val = CHRG_VBUS_ILIM_4000MA << CHRG_VBUS_ILIM_BIT_POS; |
245 | else if (inlmt >= 3500000) |
246 | reg_val = CHRG_VBUS_ILIM_3500MA << CHRG_VBUS_ILIM_BIT_POS; |
247 | else if (inlmt >= 3000000) |
248 | reg_val = CHRG_VBUS_ILIM_3000MA << CHRG_VBUS_ILIM_BIT_POS; |
249 | else if (inlmt >= 2500000) |
250 | reg_val = CHRG_VBUS_ILIM_2500MA << CHRG_VBUS_ILIM_BIT_POS; |
251 | else if (inlmt >= 2000000) |
252 | reg_val = CHRG_VBUS_ILIM_2000MA << CHRG_VBUS_ILIM_BIT_POS; |
253 | else if (inlmt >= 1500000) |
254 | reg_val = CHRG_VBUS_ILIM_1500MA << CHRG_VBUS_ILIM_BIT_POS; |
255 | else if (inlmt >= 900000) |
256 | reg_val = CHRG_VBUS_ILIM_900MA << CHRG_VBUS_ILIM_BIT_POS; |
257 | else if (inlmt >= 500000) |
258 | reg_val = CHRG_VBUS_ILIM_500MA << CHRG_VBUS_ILIM_BIT_POS; |
259 | else |
260 | reg_val = CHRG_VBUS_ILIM_100MA << CHRG_VBUS_ILIM_BIT_POS; |
261 | |
262 | ret = regmap_update_bits(map: info->regmap, AXP20X_CHRG_BAK_CTRL, |
263 | CHRG_VBUS_ILIM_MASK, val: reg_val); |
264 | if (ret < 0) |
265 | dev_err(&info->pdev->dev, "charger BAK control %d\n" , ret); |
266 | |
267 | return ret; |
268 | } |
269 | |
270 | static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, |
271 | bool enable) |
272 | { |
273 | int ret; |
274 | |
275 | if (enable) |
276 | ret = regmap_update_bits(map: info->regmap, AXP20X_VBUS_IPSOUT_MGMT, |
277 | VBUS_ISPOUT_VBUS_PATH_DIS, val: 0); |
278 | else |
279 | ret = regmap_update_bits(map: info->regmap, AXP20X_VBUS_IPSOUT_MGMT, |
280 | VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); |
281 | |
282 | if (ret < 0) |
283 | dev_err(&info->pdev->dev, "axp288 vbus path select %d\n" , ret); |
284 | |
285 | return ret; |
286 | } |
287 | |
288 | static int axp288_charger_enable_charger(struct axp288_chrg_info *info, |
289 | bool enable) |
290 | { |
291 | int ret; |
292 | |
293 | if (enable) |
294 | ret = regmap_update_bits(map: info->regmap, AXP20X_CHRG_CTRL1, |
295 | CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); |
296 | else |
297 | ret = regmap_update_bits(map: info->regmap, AXP20X_CHRG_CTRL1, |
298 | CHRG_CCCV_CHG_EN, val: 0); |
299 | if (ret < 0) |
300 | dev_err(&info->pdev->dev, "axp288 enable charger %d\n" , ret); |
301 | |
302 | return ret; |
303 | } |
304 | |
305 | static int axp288_get_charger_health(struct axp288_chrg_info *info) |
306 | { |
307 | if (!(info->input_status & PS_STAT_VBUS_PRESENT)) |
308 | return POWER_SUPPLY_HEALTH_UNKNOWN; |
309 | |
310 | if (!(info->input_status & PS_STAT_VBUS_VALID)) |
311 | return POWER_SUPPLY_HEALTH_DEAD; |
312 | else if (info->op_mode & CHRG_STAT_PMIC_OTP) |
313 | return POWER_SUPPLY_HEALTH_OVERHEAT; |
314 | else if (info->op_mode & CHRG_STAT_BAT_SAFE_MODE) |
315 | return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; |
316 | else |
317 | return POWER_SUPPLY_HEALTH_GOOD; |
318 | } |
319 | |
320 | static int axp288_charger_usb_set_property(struct power_supply *psy, |
321 | enum power_supply_property psp, |
322 | const union power_supply_propval *val) |
323 | { |
324 | struct axp288_chrg_info *info = power_supply_get_drvdata(psy); |
325 | int ret = 0; |
326 | int scaled_val; |
327 | |
328 | mutex_lock(&info->lock); |
329 | switch (psp) { |
330 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
331 | scaled_val = min(val->intval, info->max_cc); |
332 | scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); |
333 | ret = axp288_charger_set_cc(info, cc: scaled_val); |
334 | if (ret < 0) { |
335 | dev_warn(&info->pdev->dev, "set charge current failed\n" ); |
336 | goto out; |
337 | } |
338 | break; |
339 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
340 | scaled_val = min(val->intval, info->max_cv); |
341 | scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); |
342 | ret = axp288_charger_set_cv(info, cv: scaled_val); |
343 | if (ret < 0) { |
344 | dev_warn(&info->pdev->dev, "set charge voltage failed\n" ); |
345 | goto out; |
346 | } |
347 | break; |
348 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
349 | ret = axp288_charger_set_vbus_inlmt(info, inlmt: val->intval); |
350 | if (ret < 0) { |
351 | dev_warn(&info->pdev->dev, "set input current limit failed\n" ); |
352 | goto out; |
353 | } |
354 | info->valid = false; |
355 | break; |
356 | default: |
357 | ret = -EINVAL; |
358 | } |
359 | |
360 | out: |
361 | mutex_unlock(lock: &info->lock); |
362 | return ret; |
363 | } |
364 | |
365 | static int axp288_charger_reg_readb(struct axp288_chrg_info *info, int reg, unsigned int *ret_val) |
366 | { |
367 | int ret; |
368 | |
369 | ret = regmap_read(map: info->regmap, reg, val: ret_val); |
370 | if (ret < 0) { |
371 | dev_err(&info->pdev->dev, "Error %d on reading value from register 0x%04x\n" , |
372 | ret, |
373 | reg); |
374 | return ret; |
375 | } |
376 | return 0; |
377 | } |
378 | |
379 | static int axp288_charger_usb_update_property(struct axp288_chrg_info *info) |
380 | { |
381 | int ret = 0; |
382 | |
383 | if (info->valid && time_before(jiffies, info->last_updated + AXP288_REG_UPDATE_INTERVAL)) |
384 | return 0; |
385 | |
386 | dev_dbg(&info->pdev->dev, "Charger updating register values...\n" ); |
387 | |
388 | ret = iosf_mbi_block_punit_i2c_access(); |
389 | if (ret < 0) |
390 | return ret; |
391 | |
392 | ret = axp288_charger_reg_readb(info, AXP20X_PWR_INPUT_STATUS, ret_val: &info->input_status); |
393 | if (ret < 0) |
394 | goto out; |
395 | |
396 | ret = axp288_charger_reg_readb(info, AXP20X_PWR_OP_MODE, ret_val: &info->op_mode); |
397 | if (ret < 0) |
398 | goto out; |
399 | |
400 | ret = axp288_charger_reg_readb(info, AXP20X_CHRG_BAK_CTRL, ret_val: &info->backend_control); |
401 | if (ret < 0) |
402 | goto out; |
403 | |
404 | info->last_updated = jiffies; |
405 | info->valid = true; |
406 | out: |
407 | iosf_mbi_unblock_punit_i2c_access(); |
408 | return ret; |
409 | } |
410 | |
411 | static int axp288_charger_usb_get_property(struct power_supply *psy, |
412 | enum power_supply_property psp, |
413 | union power_supply_propval *val) |
414 | { |
415 | struct axp288_chrg_info *info = power_supply_get_drvdata(psy); |
416 | int ret; |
417 | |
418 | mutex_lock(&info->lock); |
419 | ret = axp288_charger_usb_update_property(info); |
420 | if (ret < 0) |
421 | goto out; |
422 | |
423 | switch (psp) { |
424 | case POWER_SUPPLY_PROP_PRESENT: |
425 | /* Check for OTG case first */ |
426 | if (info->otg.id_short) { |
427 | val->intval = 0; |
428 | break; |
429 | } |
430 | val->intval = (info->input_status & PS_STAT_VBUS_PRESENT) ? 1 : 0; |
431 | break; |
432 | case POWER_SUPPLY_PROP_ONLINE: |
433 | /* Check for OTG case first */ |
434 | if (info->otg.id_short) { |
435 | val->intval = 0; |
436 | break; |
437 | } |
438 | val->intval = (info->input_status & PS_STAT_VBUS_VALID) ? 1 : 0; |
439 | break; |
440 | case POWER_SUPPLY_PROP_HEALTH: |
441 | val->intval = axp288_get_charger_health(info); |
442 | break; |
443 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
444 | val->intval = info->cc * 1000; |
445 | break; |
446 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
447 | val->intval = info->max_cc * 1000; |
448 | break; |
449 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
450 | val->intval = info->cv * 1000; |
451 | break; |
452 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
453 | val->intval = info->max_cv * 1000; |
454 | break; |
455 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
456 | val->intval = axp288_charger_get_vbus_inlmt(info); |
457 | break; |
458 | default: |
459 | ret = -EINVAL; |
460 | } |
461 | |
462 | out: |
463 | mutex_unlock(lock: &info->lock); |
464 | return ret; |
465 | } |
466 | |
467 | static int axp288_charger_property_is_writeable(struct power_supply *psy, |
468 | enum power_supply_property psp) |
469 | { |
470 | int ret; |
471 | |
472 | switch (psp) { |
473 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
474 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
475 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
476 | ret = 1; |
477 | break; |
478 | default: |
479 | ret = 0; |
480 | } |
481 | |
482 | return ret; |
483 | } |
484 | |
485 | static enum power_supply_property axp288_usb_props[] = { |
486 | POWER_SUPPLY_PROP_PRESENT, |
487 | POWER_SUPPLY_PROP_ONLINE, |
488 | POWER_SUPPLY_PROP_TYPE, |
489 | POWER_SUPPLY_PROP_HEALTH, |
490 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, |
491 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, |
492 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, |
493 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, |
494 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
495 | }; |
496 | |
497 | static const struct power_supply_desc axp288_charger_desc = { |
498 | .name = "axp288_charger" , |
499 | .type = POWER_SUPPLY_TYPE_USB, |
500 | .properties = axp288_usb_props, |
501 | .num_properties = ARRAY_SIZE(axp288_usb_props), |
502 | .get_property = axp288_charger_usb_get_property, |
503 | .set_property = axp288_charger_usb_set_property, |
504 | .property_is_writeable = axp288_charger_property_is_writeable, |
505 | }; |
506 | |
507 | static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) |
508 | { |
509 | struct axp288_chrg_info *info = dev; |
510 | int i; |
511 | |
512 | for (i = 0; i < CHRG_INTR_END; i++) { |
513 | if (info->irq[i] == irq) |
514 | break; |
515 | } |
516 | |
517 | if (i >= CHRG_INTR_END) { |
518 | dev_warn(&info->pdev->dev, "spurious interrupt!!\n" ); |
519 | return IRQ_NONE; |
520 | } |
521 | |
522 | switch (i) { |
523 | case VBUS_OV_IRQ: |
524 | dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n" ); |
525 | break; |
526 | case CHARGE_DONE_IRQ: |
527 | dev_dbg(&info->pdev->dev, "Charging Done INTR\n" ); |
528 | break; |
529 | case CHARGE_CHARGING_IRQ: |
530 | dev_dbg(&info->pdev->dev, "Start Charging IRQ\n" ); |
531 | break; |
532 | case BAT_SAFE_QUIT_IRQ: |
533 | dev_dbg(&info->pdev->dev, |
534 | "Quit Safe Mode(restart timer) Charging IRQ\n" ); |
535 | break; |
536 | case BAT_SAFE_ENTER_IRQ: |
537 | dev_dbg(&info->pdev->dev, |
538 | "Enter Safe Mode(timer expire) Charging IRQ\n" ); |
539 | break; |
540 | case QCBTU_IRQ: |
541 | dev_dbg(&info->pdev->dev, |
542 | "Quit Battery Under Temperature(CHRG) INTR\n" ); |
543 | break; |
544 | case CBTU_IRQ: |
545 | dev_dbg(&info->pdev->dev, |
546 | "Hit Battery Under Temperature(CHRG) INTR\n" ); |
547 | break; |
548 | case QCBTO_IRQ: |
549 | dev_dbg(&info->pdev->dev, |
550 | "Quit Battery Over Temperature(CHRG) INTR\n" ); |
551 | break; |
552 | case CBTO_IRQ: |
553 | dev_dbg(&info->pdev->dev, |
554 | "Hit Battery Over Temperature(CHRG) INTR\n" ); |
555 | break; |
556 | default: |
557 | dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n" ); |
558 | goto out; |
559 | } |
560 | mutex_lock(&info->lock); |
561 | info->valid = false; |
562 | mutex_unlock(lock: &info->lock); |
563 | power_supply_changed(psy: info->psy_usb); |
564 | out: |
565 | return IRQ_HANDLED; |
566 | } |
567 | |
568 | /* |
569 | * The HP Pavilion x2 10 series comes in a number of variants: |
570 | * Bay Trail SoC + AXP288 PMIC, Micro-USB, DMI_BOARD_NAME: "8021" |
571 | * Bay Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "815D" |
572 | * Cherry Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "813E" |
573 | * Cherry Trail SoC + TI PMIC, Type-C, DMI_BOARD_NAME: "827C" or "82F4" |
574 | * |
575 | * The variants with the AXP288 + Type-C connector are all kinds of special: |
576 | * |
577 | * 1. They use a Type-C connector which the AXP288 does not support, so when |
578 | * using a Type-C charger it is not recognized. Unlike most AXP288 devices, |
579 | * this model actually has mostly working ACPI AC / Battery code, the ACPI code |
580 | * "solves" this by simply setting the input_current_limit to 3A. |
581 | * There are still some issues with the ACPI code, so we use this native driver, |
582 | * and to solve the charging not working (500mA is not enough) issue we hardcode |
583 | * the 3A input_current_limit like the ACPI code does. |
584 | * |
585 | * 2. If no charger is connected the machine boots with the vbus-path disabled. |
586 | * Normally this is done when a 5V boost converter is active to avoid the PMIC |
587 | * trying to charge from the 5V boost converter's output. This is done when |
588 | * an OTG host cable is inserted and the ID pin on the micro-B receptacle is |
589 | * pulled low and the ID pin has an ACPI event handler associated with it |
590 | * which re-enables the vbus-path when the ID pin is pulled high when the |
591 | * OTG host cable is removed. The Type-C connector has no ID pin, there is |
592 | * no ID pin handler and there appears to be no 5V boost converter, so we |
593 | * end up not charging because the vbus-path is disabled, until we unplug |
594 | * the charger which automatically clears the vbus-path disable bit and then |
595 | * on the second plug-in of the adapter we start charging. To solve the not |
596 | * charging on first charger plugin we unconditionally enable the vbus-path at |
597 | * probe on this model, which is safe since there is no 5V boost converter. |
598 | */ |
599 | static const struct dmi_system_id axp288_hp_x2_dmi_ids[] = { |
600 | { |
601 | .matches = { |
602 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard" ), |
603 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable" ), |
604 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "815D" ), |
605 | }, |
606 | }, |
607 | { |
608 | .matches = { |
609 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "HP" ), |
610 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable" ), |
611 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "813E" ), |
612 | }, |
613 | }, |
614 | {} /* Terminating entry */ |
615 | }; |
616 | |
617 | static void axp288_charger_extcon_evt_worker(struct work_struct *work) |
618 | { |
619 | struct axp288_chrg_info *info = |
620 | container_of(work, struct axp288_chrg_info, cable.work); |
621 | int ret, current_limit; |
622 | struct extcon_dev *edev = info->cable.edev; |
623 | unsigned int val; |
624 | |
625 | ret = regmap_read(map: info->regmap, AXP20X_PWR_INPUT_STATUS, val: &val); |
626 | if (ret < 0) { |
627 | dev_err(&info->pdev->dev, "Error reading status (%d)\n" , ret); |
628 | return; |
629 | } |
630 | |
631 | /* Offline? Disable charging and bail */ |
632 | if (!(val & PS_STAT_VBUS_VALID)) { |
633 | dev_dbg(&info->pdev->dev, "USB charger disconnected\n" ); |
634 | axp288_charger_enable_charger(info, enable: false); |
635 | mutex_lock(&info->lock); |
636 | info->valid = false; |
637 | mutex_unlock(lock: &info->lock); |
638 | power_supply_changed(psy: info->psy_usb); |
639 | return; |
640 | } |
641 | |
642 | /* Determine cable/charger type */ |
643 | if (dmi_check_system(list: axp288_hp_x2_dmi_ids)) { |
644 | /* See comment above axp288_hp_x2_dmi_ids declaration */ |
645 | dev_dbg(&info->pdev->dev, "HP X2 with Type-C, setting inlmt to 3A\n" ); |
646 | current_limit = 3000000; |
647 | } else if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) { |
648 | dev_dbg(&info->pdev->dev, "USB SDP charger is connected\n" ); |
649 | current_limit = 500000; |
650 | } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) { |
651 | dev_dbg(&info->pdev->dev, "USB CDP charger is connected\n" ); |
652 | current_limit = 1500000; |
653 | } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) { |
654 | dev_dbg(&info->pdev->dev, "USB DCP charger is connected\n" ); |
655 | current_limit = 2000000; |
656 | } else { |
657 | /* Charger type detection still in progress, bail. */ |
658 | return; |
659 | } |
660 | |
661 | /* Set vbus current limit first, then enable charger */ |
662 | ret = axp288_charger_set_vbus_inlmt(info, inlmt: current_limit); |
663 | if (ret == 0) |
664 | axp288_charger_enable_charger(info, enable: true); |
665 | else |
666 | dev_err(&info->pdev->dev, |
667 | "error setting current limit (%d)\n" , ret); |
668 | |
669 | mutex_lock(&info->lock); |
670 | info->valid = false; |
671 | mutex_unlock(lock: &info->lock); |
672 | power_supply_changed(psy: info->psy_usb); |
673 | } |
674 | |
675 | static int axp288_charger_handle_cable_evt(struct notifier_block *nb, |
676 | unsigned long event, void *param) |
677 | { |
678 | struct axp288_chrg_info *info = |
679 | container_of(nb, struct axp288_chrg_info, cable.nb); |
680 | schedule_work(work: &info->cable.work); |
681 | return NOTIFY_OK; |
682 | } |
683 | |
684 | static void axp288_charger_otg_evt_worker(struct work_struct *work) |
685 | { |
686 | struct axp288_chrg_info *info = |
687 | container_of(work, struct axp288_chrg_info, otg.work); |
688 | struct extcon_dev *edev = info->otg.cable; |
689 | int ret, usb_host = extcon_get_state(edev, EXTCON_USB_HOST); |
690 | |
691 | dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n" , |
692 | usb_host ? "attached" : "detached" ); |
693 | |
694 | /* |
695 | * Set usb_id_short flag to avoid running charger detection logic |
696 | * in case usb host. |
697 | */ |
698 | info->otg.id_short = usb_host; |
699 | |
700 | /* Disable VBUS path before enabling the 5V boost */ |
701 | ret = axp288_charger_vbus_path_select(info, enable: !info->otg.id_short); |
702 | if (ret < 0) |
703 | dev_warn(&info->pdev->dev, "vbus path disable failed\n" ); |
704 | } |
705 | |
706 | static int axp288_charger_handle_otg_evt(struct notifier_block *nb, |
707 | unsigned long event, void *param) |
708 | { |
709 | struct axp288_chrg_info *info = |
710 | container_of(nb, struct axp288_chrg_info, otg.id_nb); |
711 | |
712 | schedule_work(work: &info->otg.work); |
713 | |
714 | return NOTIFY_OK; |
715 | } |
716 | |
717 | static int charger_init_hw_regs(struct axp288_chrg_info *info) |
718 | { |
719 | int ret, cc, cv; |
720 | unsigned int val; |
721 | |
722 | /* Program temperature thresholds */ |
723 | ret = regmap_write(map: info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C); |
724 | if (ret < 0) { |
725 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
726 | AXP20X_V_LTF_CHRG, ret); |
727 | return ret; |
728 | } |
729 | |
730 | ret = regmap_write(map: info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C); |
731 | if (ret < 0) { |
732 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
733 | AXP20X_V_HTF_CHRG, ret); |
734 | return ret; |
735 | } |
736 | |
737 | /* Do not turn-off charger o/p after charge cycle ends */ |
738 | ret = regmap_update_bits(map: info->regmap, |
739 | AXP20X_CHRG_CTRL2, |
740 | CNTL2_CHG_OUT_TURNON, CNTL2_CHG_OUT_TURNON); |
741 | if (ret < 0) { |
742 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
743 | AXP20X_CHRG_CTRL2, ret); |
744 | return ret; |
745 | } |
746 | |
747 | /* Setup ending condition for charging to be 10% of I(chrg) */ |
748 | ret = regmap_update_bits(map: info->regmap, |
749 | AXP20X_CHRG_CTRL1, |
750 | CHRG_CCCV_ITERM_20P, val: 0); |
751 | if (ret < 0) { |
752 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
753 | AXP20X_CHRG_CTRL1, ret); |
754 | return ret; |
755 | } |
756 | |
757 | /* Disable OCV-SOC curve calibration */ |
758 | ret = regmap_update_bits(map: info->regmap, |
759 | AXP20X_CC_CTRL, |
760 | FG_CNTL_OCV_ADJ_EN, val: 0); |
761 | if (ret < 0) { |
762 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
763 | AXP20X_CC_CTRL, ret); |
764 | return ret; |
765 | } |
766 | |
767 | if (dmi_check_system(list: axp288_hp_x2_dmi_ids)) { |
768 | /* See comment above axp288_hp_x2_dmi_ids declaration */ |
769 | ret = axp288_charger_vbus_path_select(info, enable: true); |
770 | if (ret < 0) |
771 | return ret; |
772 | } else { |
773 | /* Set Vhold to the factory default / recommended 4.4V */ |
774 | val = VBUS_ISPOUT_VHOLD_SET_4400MV << VBUS_ISPOUT_VHOLD_SET_BIT_POS; |
775 | ret = regmap_update_bits(map: info->regmap, AXP20X_VBUS_IPSOUT_MGMT, |
776 | VBUS_ISPOUT_VHOLD_SET_MASK, val); |
777 | if (ret < 0) { |
778 | dev_err(&info->pdev->dev, "register(%x) write error(%d)\n" , |
779 | AXP20X_VBUS_IPSOUT_MGMT, ret); |
780 | return ret; |
781 | } |
782 | } |
783 | |
784 | /* Read current charge voltage and current limit */ |
785 | ret = regmap_read(map: info->regmap, AXP20X_CHRG_CTRL1, val: &val); |
786 | if (ret < 0) { |
787 | dev_err(&info->pdev->dev, "register(%x) read error(%d)\n" , |
788 | AXP20X_CHRG_CTRL1, ret); |
789 | return ret; |
790 | } |
791 | |
792 | /* Determine charge voltage */ |
793 | cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; |
794 | switch (cv) { |
795 | case CHRG_CCCV_CV_4100MV: |
796 | info->cv = CV_4100MV; |
797 | break; |
798 | case CHRG_CCCV_CV_4150MV: |
799 | info->cv = CV_4150MV; |
800 | break; |
801 | case CHRG_CCCV_CV_4200MV: |
802 | info->cv = CV_4200MV; |
803 | break; |
804 | case CHRG_CCCV_CV_4350MV: |
805 | info->cv = CV_4350MV; |
806 | break; |
807 | } |
808 | |
809 | /* Determine charge current limit */ |
810 | cc = (val & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; |
811 | cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; |
812 | info->cc = cc; |
813 | |
814 | /* |
815 | * Do not allow the user to configure higher settings then those |
816 | * set by the firmware |
817 | */ |
818 | info->max_cv = info->cv; |
819 | info->max_cc = info->cc; |
820 | |
821 | return 0; |
822 | } |
823 | |
824 | static void axp288_charger_cancel_work(void *data) |
825 | { |
826 | struct axp288_chrg_info *info = data; |
827 | |
828 | cancel_work_sync(work: &info->otg.work); |
829 | cancel_work_sync(work: &info->cable.work); |
830 | } |
831 | |
832 | static int axp288_charger_probe(struct platform_device *pdev) |
833 | { |
834 | int ret, i, pirq; |
835 | struct axp288_chrg_info *info; |
836 | struct device *dev = &pdev->dev; |
837 | struct axp20x_dev *axp20x = dev_get_drvdata(dev: pdev->dev.parent); |
838 | struct power_supply_config charger_cfg = {}; |
839 | const char *extcon_name = NULL; |
840 | unsigned int val; |
841 | |
842 | /* |
843 | * Normally the native AXP288 fg/charger drivers are preferred but |
844 | * on some devices the ACPI drivers should be used instead. |
845 | */ |
846 | if (!acpi_quirk_skip_acpi_ac_and_battery()) |
847 | return -ENODEV; |
848 | |
849 | /* |
850 | * On some devices the fuelgauge and charger parts of the axp288 are |
851 | * not used, check that the fuelgauge is enabled (CC_CTRL != 0). |
852 | */ |
853 | ret = regmap_read(map: axp20x->regmap, AXP20X_CC_CTRL, val: &val); |
854 | if (ret < 0) |
855 | return ret; |
856 | if (val == 0) |
857 | return -ENODEV; |
858 | |
859 | info = devm_kzalloc(dev, size: sizeof(*info), GFP_KERNEL); |
860 | if (!info) |
861 | return -ENOMEM; |
862 | |
863 | mutex_init(&info->lock); |
864 | info->pdev = pdev; |
865 | info->regmap = axp20x->regmap; |
866 | info->regmap_irqc = axp20x->regmap_irqc; |
867 | |
868 | info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); |
869 | if (IS_ERR(ptr: info->cable.edev)) { |
870 | dev_err_probe(dev, err: PTR_ERR(ptr: info->cable.edev), |
871 | fmt: "extcon_get_extcon_dev(%s) failed\n" , |
872 | AXP288_EXTCON_DEV_NAME); |
873 | return PTR_ERR(ptr: info->cable.edev); |
874 | } |
875 | |
876 | /* |
877 | * On devices with broken ACPI GPIO event handlers there also is no ACPI |
878 | * "INT3496" (USB_HOST_EXTCON_HID) device. x86-android-tablets.ko |
879 | * instantiates an "intel-int3496" extcon on these devs as a workaround. |
880 | */ |
881 | if (acpi_quirk_skip_gpio_event_handlers()) |
882 | extcon_name = "intel-int3496" ; |
883 | else if (acpi_dev_present(USB_HOST_EXTCON_HID, NULL, hrv: -1)) |
884 | extcon_name = USB_HOST_EXTCON_NAME; |
885 | |
886 | if (extcon_name) { |
887 | info->otg.cable = extcon_get_extcon_dev(extcon_name); |
888 | if (IS_ERR(ptr: info->otg.cable)) { |
889 | dev_err_probe(dev, err: PTR_ERR(ptr: info->otg.cable), |
890 | fmt: "extcon_get_extcon_dev(%s) failed\n" , |
891 | USB_HOST_EXTCON_NAME); |
892 | return PTR_ERR(ptr: info->otg.cable); |
893 | } |
894 | dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n" ); |
895 | } |
896 | |
897 | platform_set_drvdata(pdev, data: info); |
898 | |
899 | ret = charger_init_hw_regs(info); |
900 | if (ret) |
901 | return ret; |
902 | |
903 | /* Register with power supply class */ |
904 | charger_cfg.drv_data = info; |
905 | info->psy_usb = devm_power_supply_register(parent: dev, desc: &axp288_charger_desc, |
906 | cfg: &charger_cfg); |
907 | if (IS_ERR(ptr: info->psy_usb)) { |
908 | ret = PTR_ERR(ptr: info->psy_usb); |
909 | dev_err(dev, "failed to register power supply: %d\n" , ret); |
910 | return ret; |
911 | } |
912 | |
913 | /* Cancel our work on cleanup, register this before the notifiers */ |
914 | ret = devm_add_action(dev, axp288_charger_cancel_work, info); |
915 | if (ret) |
916 | return ret; |
917 | |
918 | /* Register for extcon notification */ |
919 | INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker); |
920 | info->cable.nb.notifier_call = axp288_charger_handle_cable_evt; |
921 | ret = devm_extcon_register_notifier_all(dev, edev: info->cable.edev, |
922 | nb: &info->cable.nb); |
923 | if (ret) { |
924 | dev_err(dev, "failed to register cable extcon notifier\n" ); |
925 | return ret; |
926 | } |
927 | schedule_work(work: &info->cable.work); |
928 | |
929 | /* Register for OTG notification */ |
930 | INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker); |
931 | info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; |
932 | if (info->otg.cable) { |
933 | ret = devm_extcon_register_notifier(dev, edev: info->otg.cable, |
934 | EXTCON_USB_HOST, nb: &info->otg.id_nb); |
935 | if (ret) { |
936 | dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n" ); |
937 | return ret; |
938 | } |
939 | schedule_work(work: &info->otg.work); |
940 | } |
941 | |
942 | /* Register charger interrupts */ |
943 | for (i = 0; i < CHRG_INTR_END; i++) { |
944 | pirq = platform_get_irq(info->pdev, i); |
945 | if (pirq < 0) |
946 | return pirq; |
947 | |
948 | info->irq[i] = regmap_irq_get_virq(data: info->regmap_irqc, irq: pirq); |
949 | if (info->irq[i] < 0) { |
950 | dev_warn(&info->pdev->dev, |
951 | "failed to get virtual interrupt=%d\n" , pirq); |
952 | return info->irq[i]; |
953 | } |
954 | ret = devm_request_threaded_irq(dev: &info->pdev->dev, irq: info->irq[i], |
955 | NULL, thread_fn: axp288_charger_irq_thread_handler, |
956 | IRQF_ONESHOT, devname: info->pdev->name, dev_id: info); |
957 | if (ret) { |
958 | dev_err(dev, "failed to request interrupt=%d\n" , |
959 | info->irq[i]); |
960 | return ret; |
961 | } |
962 | } |
963 | |
964 | return 0; |
965 | } |
966 | |
967 | static const struct platform_device_id axp288_charger_id_table[] = { |
968 | { .name = "axp288_charger" }, |
969 | {}, |
970 | }; |
971 | MODULE_DEVICE_TABLE(platform, axp288_charger_id_table); |
972 | |
973 | static struct platform_driver axp288_charger_driver = { |
974 | .probe = axp288_charger_probe, |
975 | .id_table = axp288_charger_id_table, |
976 | .driver = { |
977 | .name = "axp288_charger" , |
978 | }, |
979 | }; |
980 | |
981 | module_platform_driver(axp288_charger_driver); |
982 | |
983 | MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>" ); |
984 | MODULE_DESCRIPTION("X-power AXP288 Charger Driver" ); |
985 | MODULE_LICENSE("GPL v2" ); |
986 | |