]>
Commit | Line | Data |
---|---|---|
56d7df87 CC |
1 | /* Copyright (c) 2014, Sony Mobile Communications Inc. |
2 | * | |
3 | * This program is free software; you can redistribute it and/or modify | |
4 | * it under the terms of the GNU General Public License version 2 and | |
5 | * only version 2 as published by the Free Software Foundation. | |
6 | * | |
7 | * This program is distributed in the hope that it will be useful, | |
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | * GNU General Public License for more details. | |
11 | * | |
12 | * This driver is for the multi-block Switch-Mode Battery Charger and Boost | |
13 | * (SMBB) hardware, found in Qualcomm PM8941 PMICs. The charger is an | |
14 | * integrated, single-cell lithium-ion battery charger. | |
15 | * | |
16 | * Sub-components: | |
17 | * - Charger core | |
18 | * - Buck | |
19 | * - DC charge-path | |
20 | * - USB charge-path | |
21 | * - Battery interface | |
22 | * - Boost (not implemented) | |
23 | * - Misc | |
24 | * - HF-Buck | |
25 | */ | |
26 | ||
27 | #include <linux/errno.h> | |
28 | #include <linux/interrupt.h> | |
29 | #include <linux/kernel.h> | |
30 | #include <linux/module.h> | |
31 | #include <linux/mutex.h> | |
32 | #include <linux/of.h> | |
33 | #include <linux/platform_device.h> | |
34 | #include <linux/power_supply.h> | |
35 | #include <linux/regmap.h> | |
36 | #include <linux/slab.h> | |
eee1d077 | 37 | #include <linux/extcon.h> |
56d7df87 CC |
38 | |
39 | #define SMBB_CHG_VMAX 0x040 | |
40 | #define SMBB_CHG_VSAFE 0x041 | |
41 | #define SMBB_CHG_CFG 0x043 | |
42 | #define SMBB_CHG_IMAX 0x044 | |
43 | #define SMBB_CHG_ISAFE 0x045 | |
44 | #define SMBB_CHG_VIN_MIN 0x047 | |
45 | #define SMBB_CHG_CTRL 0x049 | |
46 | #define CTRL_EN BIT(7) | |
47 | #define SMBB_CHG_VBAT_WEAK 0x052 | |
48 | #define SMBB_CHG_IBAT_TERM_CHG 0x05b | |
49 | #define IBAT_TERM_CHG_IEOC BIT(7) | |
50 | #define IBAT_TERM_CHG_IEOC_BMS BIT(7) | |
51 | #define IBAT_TERM_CHG_IEOC_CHG 0 | |
52 | #define SMBB_CHG_VBAT_DET 0x05d | |
53 | #define SMBB_CHG_TCHG_MAX_EN 0x060 | |
54 | #define TCHG_MAX_EN BIT(7) | |
55 | #define SMBB_CHG_WDOG_TIME 0x062 | |
56 | #define SMBB_CHG_WDOG_EN 0x065 | |
57 | #define WDOG_EN BIT(7) | |
58 | ||
59 | #define SMBB_BUCK_REG_MODE 0x174 | |
60 | #define BUCK_REG_MODE BIT(0) | |
61 | #define BUCK_REG_MODE_VBAT BIT(0) | |
62 | #define BUCK_REG_MODE_VSYS 0 | |
63 | ||
64 | #define SMBB_BAT_PRES_STATUS 0x208 | |
65 | #define PRES_STATUS_BAT_PRES BIT(7) | |
66 | #define SMBB_BAT_TEMP_STATUS 0x209 | |
67 | #define TEMP_STATUS_OK BIT(7) | |
68 | #define TEMP_STATUS_HOT BIT(6) | |
69 | #define SMBB_BAT_BTC_CTRL 0x249 | |
70 | #define BTC_CTRL_COMP_EN BIT(7) | |
71 | #define BTC_CTRL_COLD_EXT BIT(1) | |
72 | #define BTC_CTRL_HOT_EXT_N BIT(0) | |
73 | ||
74 | #define SMBB_USB_IMAX 0x344 | |
75 | #define SMBB_USB_ENUM_TIMER_STOP 0x34e | |
76 | #define ENUM_TIMER_STOP BIT(0) | |
77 | #define SMBB_USB_SEC_ACCESS 0x3d0 | |
78 | #define SEC_ACCESS_MAGIC 0xa5 | |
79 | #define SMBB_USB_REV_BST 0x3ed | |
80 | #define REV_BST_CHG_GONE BIT(7) | |
81 | ||
82 | #define SMBB_DC_IMAX 0x444 | |
83 | ||
84 | #define SMBB_MISC_REV2 0x601 | |
85 | #define SMBB_MISC_BOOT_DONE 0x642 | |
86 | #define BOOT_DONE BIT(7) | |
87 | ||
88 | #define STATUS_USBIN_VALID BIT(0) /* USB connection is valid */ | |
89 | #define STATUS_DCIN_VALID BIT(1) /* DC connection is valid */ | |
90 | #define STATUS_BAT_HOT BIT(2) /* Battery temp 1=Hot, 0=Cold */ | |
91 | #define STATUS_BAT_OK BIT(3) /* Battery temp OK */ | |
92 | #define STATUS_BAT_PRESENT BIT(4) /* Battery is present */ | |
93 | #define STATUS_CHG_DONE BIT(5) /* Charge cycle is complete */ | |
94 | #define STATUS_CHG_TRKL BIT(6) /* Trickle charging */ | |
95 | #define STATUS_CHG_FAST BIT(7) /* Fast charging */ | |
96 | #define STATUS_CHG_GONE BIT(8) /* No charger is connected */ | |
97 | ||
98 | enum smbb_attr { | |
99 | ATTR_BAT_ISAFE, | |
100 | ATTR_BAT_IMAX, | |
101 | ATTR_USBIN_IMAX, | |
102 | ATTR_DCIN_IMAX, | |
103 | ATTR_BAT_VSAFE, | |
104 | ATTR_BAT_VMAX, | |
105 | ATTR_BAT_VMIN, | |
106 | ATTR_CHG_VDET, | |
107 | ATTR_VIN_MIN, | |
108 | _ATTR_CNT, | |
109 | }; | |
110 | ||
111 | struct smbb_charger { | |
112 | unsigned int revision; | |
113 | unsigned int addr; | |
114 | struct device *dev; | |
eee1d077 | 115 | struct extcon_dev *edev; |
56d7df87 CC |
116 | |
117 | bool dc_disabled; | |
118 | bool jeita_ext_temp; | |
119 | unsigned long status; | |
120 | struct mutex statlock; | |
121 | ||
122 | unsigned int attr[_ATTR_CNT]; | |
123 | ||
124 | struct power_supply *usb_psy; | |
125 | struct power_supply *dc_psy; | |
126 | struct power_supply *bat_psy; | |
127 | struct regmap *regmap; | |
128 | }; | |
129 | ||
eee1d077 SB |
130 | static const unsigned int smbb_usb_extcon_cable[] = { |
131 | EXTCON_USB, | |
132 | EXTCON_NONE, | |
133 | }; | |
134 | ||
56d7df87 CC |
135 | static int smbb_vbat_weak_fn(unsigned int index) |
136 | { | |
137 | return 2100000 + index * 100000; | |
138 | } | |
139 | ||
140 | static int smbb_vin_fn(unsigned int index) | |
141 | { | |
142 | if (index > 42) | |
143 | return 5600000 + (index - 43) * 200000; | |
144 | return 3400000 + index * 50000; | |
145 | } | |
146 | ||
147 | static int smbb_vmax_fn(unsigned int index) | |
148 | { | |
149 | return 3240000 + index * 10000; | |
150 | } | |
151 | ||
152 | static int smbb_vbat_det_fn(unsigned int index) | |
153 | { | |
154 | return 3240000 + index * 20000; | |
155 | } | |
156 | ||
157 | static int smbb_imax_fn(unsigned int index) | |
158 | { | |
159 | if (index < 2) | |
160 | return 100000 + index * 50000; | |
161 | return index * 100000; | |
162 | } | |
163 | ||
164 | static int smbb_bat_imax_fn(unsigned int index) | |
165 | { | |
166 | return index * 50000; | |
167 | } | |
168 | ||
169 | static unsigned int smbb_hw_lookup(unsigned int val, int (*fn)(unsigned int)) | |
170 | { | |
171 | unsigned int widx; | |
172 | unsigned int sel; | |
173 | ||
174 | for (widx = sel = 0; (*fn)(widx) <= val; ++widx) | |
175 | sel = widx; | |
176 | ||
177 | return sel; | |
178 | } | |
179 | ||
180 | static const struct smbb_charger_attr { | |
181 | const char *name; | |
182 | unsigned int reg; | |
183 | unsigned int safe_reg; | |
184 | unsigned int max; | |
185 | unsigned int min; | |
186 | unsigned int fail_ok; | |
187 | int (*hw_fn)(unsigned int); | |
188 | } smbb_charger_attrs[] = { | |
189 | [ATTR_BAT_ISAFE] = { | |
190 | .name = "qcom,fast-charge-safe-current", | |
191 | .reg = SMBB_CHG_ISAFE, | |
192 | .max = 3000000, | |
193 | .min = 200000, | |
194 | .hw_fn = smbb_bat_imax_fn, | |
195 | .fail_ok = 1, | |
196 | }, | |
197 | [ATTR_BAT_IMAX] = { | |
198 | .name = "qcom,fast-charge-current-limit", | |
199 | .reg = SMBB_CHG_IMAX, | |
200 | .safe_reg = SMBB_CHG_ISAFE, | |
201 | .max = 3000000, | |
202 | .min = 200000, | |
203 | .hw_fn = smbb_bat_imax_fn, | |
204 | }, | |
205 | [ATTR_DCIN_IMAX] = { | |
206 | .name = "qcom,dc-current-limit", | |
207 | .reg = SMBB_DC_IMAX, | |
208 | .max = 2500000, | |
209 | .min = 100000, | |
210 | .hw_fn = smbb_imax_fn, | |
211 | }, | |
212 | [ATTR_BAT_VSAFE] = { | |
213 | .name = "qcom,fast-charge-safe-voltage", | |
214 | .reg = SMBB_CHG_VSAFE, | |
215 | .max = 5000000, | |
216 | .min = 3240000, | |
217 | .hw_fn = smbb_vmax_fn, | |
218 | .fail_ok = 1, | |
219 | }, | |
220 | [ATTR_BAT_VMAX] = { | |
221 | .name = "qcom,fast-charge-high-threshold-voltage", | |
222 | .reg = SMBB_CHG_VMAX, | |
223 | .safe_reg = SMBB_CHG_VSAFE, | |
224 | .max = 5000000, | |
225 | .min = 3240000, | |
226 | .hw_fn = smbb_vmax_fn, | |
227 | }, | |
228 | [ATTR_BAT_VMIN] = { | |
229 | .name = "qcom,fast-charge-low-threshold-voltage", | |
230 | .reg = SMBB_CHG_VBAT_WEAK, | |
231 | .max = 3600000, | |
232 | .min = 2100000, | |
233 | .hw_fn = smbb_vbat_weak_fn, | |
234 | }, | |
235 | [ATTR_CHG_VDET] = { | |
236 | .name = "qcom,auto-recharge-threshold-voltage", | |
237 | .reg = SMBB_CHG_VBAT_DET, | |
238 | .max = 5000000, | |
239 | .min = 3240000, | |
240 | .hw_fn = smbb_vbat_det_fn, | |
241 | }, | |
242 | [ATTR_VIN_MIN] = { | |
243 | .name = "qcom,minimum-input-voltage", | |
244 | .reg = SMBB_CHG_VIN_MIN, | |
245 | .max = 9600000, | |
246 | .min = 4200000, | |
247 | .hw_fn = smbb_vin_fn, | |
248 | }, | |
249 | [ATTR_USBIN_IMAX] = { | |
250 | .name = "usb-charge-current-limit", | |
251 | .reg = SMBB_USB_IMAX, | |
252 | .max = 2500000, | |
253 | .min = 100000, | |
254 | .hw_fn = smbb_imax_fn, | |
255 | }, | |
256 | }; | |
257 | ||
258 | static int smbb_charger_attr_write(struct smbb_charger *chg, | |
259 | enum smbb_attr which, unsigned int val) | |
260 | { | |
261 | const struct smbb_charger_attr *prop; | |
262 | unsigned int wval; | |
263 | unsigned int out; | |
264 | int rc; | |
265 | ||
266 | prop = &smbb_charger_attrs[which]; | |
267 | ||
268 | if (val > prop->max || val < prop->min) { | |
269 | dev_err(chg->dev, "value out of range for %s [%u:%u]\n", | |
270 | prop->name, prop->min, prop->max); | |
271 | return -EINVAL; | |
272 | } | |
273 | ||
274 | if (prop->safe_reg) { | |
275 | rc = regmap_read(chg->regmap, | |
276 | chg->addr + prop->safe_reg, &wval); | |
277 | if (rc) { | |
278 | dev_err(chg->dev, | |
279 | "unable to read safe value for '%s'\n", | |
280 | prop->name); | |
281 | return rc; | |
282 | } | |
283 | ||
284 | wval = prop->hw_fn(wval); | |
285 | ||
286 | if (val > wval) { | |
287 | dev_warn(chg->dev, | |
288 | "%s above safe value, clamping at %u\n", | |
289 | prop->name, wval); | |
290 | val = wval; | |
291 | } | |
292 | } | |
293 | ||
294 | wval = smbb_hw_lookup(val, prop->hw_fn); | |
295 | ||
296 | rc = regmap_write(chg->regmap, chg->addr + prop->reg, wval); | |
297 | if (rc) { | |
298 | dev_err(chg->dev, "unable to update %s", prop->name); | |
299 | return rc; | |
300 | } | |
301 | out = prop->hw_fn(wval); | |
302 | if (out != val) { | |
303 | dev_warn(chg->dev, | |
304 | "%s inaccurate, rounded to %u\n", | |
305 | prop->name, out); | |
306 | } | |
307 | ||
308 | dev_dbg(chg->dev, "%s <= %d\n", prop->name, out); | |
309 | ||
310 | chg->attr[which] = out; | |
311 | ||
312 | return 0; | |
313 | } | |
314 | ||
315 | static int smbb_charger_attr_read(struct smbb_charger *chg, | |
316 | enum smbb_attr which) | |
317 | { | |
318 | const struct smbb_charger_attr *prop; | |
319 | unsigned int val; | |
320 | int rc; | |
321 | ||
322 | prop = &smbb_charger_attrs[which]; | |
323 | ||
324 | rc = regmap_read(chg->regmap, chg->addr + prop->reg, &val); | |
325 | if (rc) { | |
326 | dev_err(chg->dev, "failed to read %s\n", prop->name); | |
327 | return rc; | |
328 | } | |
329 | val = prop->hw_fn(val); | |
330 | dev_dbg(chg->dev, "%s => %d\n", prop->name, val); | |
331 | ||
332 | chg->attr[which] = val; | |
333 | ||
334 | return 0; | |
335 | } | |
336 | ||
337 | static int smbb_charger_attr_parse(struct smbb_charger *chg, | |
338 | enum smbb_attr which) | |
339 | { | |
340 | const struct smbb_charger_attr *prop; | |
341 | unsigned int val; | |
342 | int rc; | |
343 | ||
344 | prop = &smbb_charger_attrs[which]; | |
345 | ||
346 | rc = of_property_read_u32(chg->dev->of_node, prop->name, &val); | |
347 | if (rc == 0) { | |
348 | rc = smbb_charger_attr_write(chg, which, val); | |
349 | if (!rc || !prop->fail_ok) | |
350 | return rc; | |
351 | } | |
352 | return smbb_charger_attr_read(chg, which); | |
353 | } | |
354 | ||
355 | static void smbb_set_line_flag(struct smbb_charger *chg, int irq, int flag) | |
356 | { | |
357 | bool state; | |
358 | int ret; | |
359 | ||
360 | ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &state); | |
0bc58e93 | 361 | if (ret < 0) { |
56d7df87 CC |
362 | dev_err(chg->dev, "failed to read irq line\n"); |
363 | return; | |
364 | } | |
365 | ||
366 | mutex_lock(&chg->statlock); | |
367 | if (state) | |
368 | chg->status |= flag; | |
369 | else | |
370 | chg->status &= ~flag; | |
371 | mutex_unlock(&chg->statlock); | |
372 | ||
373 | dev_dbg(chg->dev, "status = %03lx\n", chg->status); | |
374 | } | |
375 | ||
376 | static irqreturn_t smbb_usb_valid_handler(int irq, void *_data) | |
377 | { | |
378 | struct smbb_charger *chg = _data; | |
379 | ||
380 | smbb_set_line_flag(chg, irq, STATUS_USBIN_VALID); | |
eee1d077 SB |
381 | extcon_set_cable_state_(chg->edev, EXTCON_USB, |
382 | chg->status & STATUS_USBIN_VALID); | |
56d7df87 CC |
383 | power_supply_changed(chg->usb_psy); |
384 | ||
385 | return IRQ_HANDLED; | |
386 | } | |
387 | ||
388 | static irqreturn_t smbb_dc_valid_handler(int irq, void *_data) | |
389 | { | |
390 | struct smbb_charger *chg = _data; | |
391 | ||
392 | smbb_set_line_flag(chg, irq, STATUS_DCIN_VALID); | |
393 | if (!chg->dc_disabled) | |
394 | power_supply_changed(chg->dc_psy); | |
395 | ||
396 | return IRQ_HANDLED; | |
397 | } | |
398 | ||
399 | static irqreturn_t smbb_bat_temp_handler(int irq, void *_data) | |
400 | { | |
401 | struct smbb_charger *chg = _data; | |
402 | unsigned int val; | |
403 | int rc; | |
404 | ||
405 | rc = regmap_read(chg->regmap, chg->addr + SMBB_BAT_TEMP_STATUS, &val); | |
406 | if (rc) | |
407 | return IRQ_HANDLED; | |
408 | ||
409 | mutex_lock(&chg->statlock); | |
410 | if (val & TEMP_STATUS_OK) { | |
411 | chg->status |= STATUS_BAT_OK; | |
412 | } else { | |
413 | chg->status &= ~STATUS_BAT_OK; | |
414 | if (val & TEMP_STATUS_HOT) | |
415 | chg->status |= STATUS_BAT_HOT; | |
416 | } | |
417 | mutex_unlock(&chg->statlock); | |
418 | ||
419 | power_supply_changed(chg->bat_psy); | |
420 | return IRQ_HANDLED; | |
421 | } | |
422 | ||
423 | static irqreturn_t smbb_bat_present_handler(int irq, void *_data) | |
424 | { | |
425 | struct smbb_charger *chg = _data; | |
426 | ||
427 | smbb_set_line_flag(chg, irq, STATUS_BAT_PRESENT); | |
428 | power_supply_changed(chg->bat_psy); | |
429 | ||
430 | return IRQ_HANDLED; | |
431 | } | |
432 | ||
433 | static irqreturn_t smbb_chg_done_handler(int irq, void *_data) | |
434 | { | |
435 | struct smbb_charger *chg = _data; | |
436 | ||
437 | smbb_set_line_flag(chg, irq, STATUS_CHG_DONE); | |
438 | power_supply_changed(chg->bat_psy); | |
439 | ||
440 | return IRQ_HANDLED; | |
441 | } | |
442 | ||
443 | static irqreturn_t smbb_chg_gone_handler(int irq, void *_data) | |
444 | { | |
445 | struct smbb_charger *chg = _data; | |
446 | ||
447 | smbb_set_line_flag(chg, irq, STATUS_CHG_GONE); | |
448 | power_supply_changed(chg->bat_psy); | |
449 | power_supply_changed(chg->usb_psy); | |
450 | if (!chg->dc_disabled) | |
451 | power_supply_changed(chg->dc_psy); | |
452 | ||
453 | return IRQ_HANDLED; | |
454 | } | |
455 | ||
456 | static irqreturn_t smbb_chg_fast_handler(int irq, void *_data) | |
457 | { | |
458 | struct smbb_charger *chg = _data; | |
459 | ||
460 | smbb_set_line_flag(chg, irq, STATUS_CHG_FAST); | |
461 | power_supply_changed(chg->bat_psy); | |
462 | ||
463 | return IRQ_HANDLED; | |
464 | } | |
465 | ||
466 | static irqreturn_t smbb_chg_trkl_handler(int irq, void *_data) | |
467 | { | |
468 | struct smbb_charger *chg = _data; | |
469 | ||
470 | smbb_set_line_flag(chg, irq, STATUS_CHG_TRKL); | |
471 | power_supply_changed(chg->bat_psy); | |
472 | ||
473 | return IRQ_HANDLED; | |
474 | } | |
475 | ||
476 | static const struct smbb_irq { | |
477 | const char *name; | |
478 | irqreturn_t (*handler)(int, void *); | |
479 | } smbb_charger_irqs[] = { | |
480 | { "chg-done", smbb_chg_done_handler }, | |
481 | { "chg-fast", smbb_chg_fast_handler }, | |
482 | { "chg-trkl", smbb_chg_trkl_handler }, | |
483 | { "bat-temp-ok", smbb_bat_temp_handler }, | |
484 | { "bat-present", smbb_bat_present_handler }, | |
485 | { "chg-gone", smbb_chg_gone_handler }, | |
486 | { "usb-valid", smbb_usb_valid_handler }, | |
487 | { "dc-valid", smbb_dc_valid_handler }, | |
488 | }; | |
489 | ||
490 | static int smbb_usbin_get_property(struct power_supply *psy, | |
491 | enum power_supply_property psp, | |
492 | union power_supply_propval *val) | |
493 | { | |
494 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
495 | int rc = 0; | |
496 | ||
497 | switch (psp) { | |
498 | case POWER_SUPPLY_PROP_ONLINE: | |
499 | mutex_lock(&chg->statlock); | |
500 | val->intval = !(chg->status & STATUS_CHG_GONE) && | |
501 | (chg->status & STATUS_USBIN_VALID); | |
502 | mutex_unlock(&chg->statlock); | |
503 | break; | |
504 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: | |
505 | val->intval = chg->attr[ATTR_USBIN_IMAX]; | |
506 | break; | |
507 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: | |
508 | val->intval = 2500000; | |
509 | break; | |
510 | default: | |
511 | rc = -EINVAL; | |
512 | break; | |
513 | } | |
514 | ||
515 | return rc; | |
516 | } | |
517 | ||
518 | static int smbb_usbin_set_property(struct power_supply *psy, | |
519 | enum power_supply_property psp, | |
520 | const union power_supply_propval *val) | |
521 | { | |
522 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
523 | int rc; | |
524 | ||
525 | switch (psp) { | |
526 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: | |
527 | rc = smbb_charger_attr_write(chg, ATTR_USBIN_IMAX, | |
528 | val->intval); | |
529 | break; | |
530 | default: | |
531 | rc = -EINVAL; | |
532 | break; | |
533 | } | |
534 | ||
535 | return rc; | |
536 | } | |
537 | ||
538 | static int smbb_dcin_get_property(struct power_supply *psy, | |
539 | enum power_supply_property psp, | |
540 | union power_supply_propval *val) | |
541 | { | |
542 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
543 | int rc = 0; | |
544 | ||
545 | switch (psp) { | |
546 | case POWER_SUPPLY_PROP_ONLINE: | |
547 | mutex_lock(&chg->statlock); | |
548 | val->intval = !(chg->status & STATUS_CHG_GONE) && | |
549 | (chg->status & STATUS_DCIN_VALID); | |
550 | mutex_unlock(&chg->statlock); | |
551 | break; | |
552 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: | |
553 | val->intval = chg->attr[ATTR_DCIN_IMAX]; | |
554 | break; | |
555 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: | |
556 | val->intval = 2500000; | |
557 | break; | |
558 | default: | |
559 | rc = -EINVAL; | |
560 | break; | |
561 | } | |
562 | ||
563 | return rc; | |
564 | } | |
565 | ||
566 | static int smbb_dcin_set_property(struct power_supply *psy, | |
567 | enum power_supply_property psp, | |
568 | const union power_supply_propval *val) | |
569 | { | |
570 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
571 | int rc; | |
572 | ||
573 | switch (psp) { | |
574 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: | |
575 | rc = smbb_charger_attr_write(chg, ATTR_DCIN_IMAX, | |
576 | val->intval); | |
577 | break; | |
578 | default: | |
579 | rc = -EINVAL; | |
580 | break; | |
581 | } | |
582 | ||
583 | return rc; | |
584 | } | |
585 | ||
586 | static int smbb_charger_writable_property(struct power_supply *psy, | |
587 | enum power_supply_property psp) | |
588 | { | |
589 | return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT; | |
590 | } | |
591 | ||
592 | static int smbb_battery_get_property(struct power_supply *psy, | |
593 | enum power_supply_property psp, | |
594 | union power_supply_propval *val) | |
595 | { | |
596 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
597 | unsigned long status; | |
598 | int rc = 0; | |
599 | ||
600 | mutex_lock(&chg->statlock); | |
601 | status = chg->status; | |
602 | mutex_unlock(&chg->statlock); | |
603 | ||
604 | switch (psp) { | |
605 | case POWER_SUPPLY_PROP_STATUS: | |
606 | if (status & STATUS_CHG_GONE) | |
607 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
608 | else if (!(status & (STATUS_DCIN_VALID | STATUS_USBIN_VALID))) | |
609 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
610 | else if (status & STATUS_CHG_DONE) | |
611 | val->intval = POWER_SUPPLY_STATUS_FULL; | |
612 | else if (!(status & STATUS_BAT_OK)) | |
613 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
614 | else if (status & (STATUS_CHG_FAST | STATUS_CHG_TRKL)) | |
615 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | |
616 | else /* everything is ok for charging, but we are not... */ | |
617 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
618 | break; | |
619 | case POWER_SUPPLY_PROP_HEALTH: | |
620 | if (status & STATUS_BAT_OK) | |
621 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
622 | else if (status & STATUS_BAT_HOT) | |
623 | val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; | |
624 | else | |
625 | val->intval = POWER_SUPPLY_HEALTH_COLD; | |
626 | break; | |
627 | case POWER_SUPPLY_PROP_CHARGE_TYPE: | |
628 | if (status & STATUS_CHG_FAST) | |
629 | val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; | |
630 | else if (status & STATUS_CHG_TRKL) | |
631 | val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; | |
632 | else | |
633 | val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; | |
634 | break; | |
635 | case POWER_SUPPLY_PROP_PRESENT: | |
636 | val->intval = !!(status & STATUS_BAT_PRESENT); | |
637 | break; | |
638 | case POWER_SUPPLY_PROP_CURRENT_MAX: | |
639 | val->intval = chg->attr[ATTR_BAT_IMAX]; | |
640 | break; | |
641 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: | |
642 | val->intval = chg->attr[ATTR_BAT_VMAX]; | |
643 | break; | |
644 | case POWER_SUPPLY_PROP_TECHNOLOGY: | |
645 | /* this charger is a single-cell lithium-ion battery charger | |
646 | * only. If you hook up some other technology, there will be | |
647 | * fireworks. | |
648 | */ | |
649 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | |
650 | break; | |
651 | case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: | |
652 | val->intval = 3000000; /* single-cell li-ion low end */ | |
653 | break; | |
654 | default: | |
655 | rc = -EINVAL; | |
656 | break; | |
657 | } | |
658 | ||
659 | return rc; | |
660 | } | |
661 | ||
662 | static int smbb_battery_set_property(struct power_supply *psy, | |
663 | enum power_supply_property psp, | |
664 | const union power_supply_propval *val) | |
665 | { | |
666 | struct smbb_charger *chg = power_supply_get_drvdata(psy); | |
667 | int rc; | |
668 | ||
669 | switch (psp) { | |
670 | case POWER_SUPPLY_PROP_CURRENT_MAX: | |
671 | rc = smbb_charger_attr_write(chg, ATTR_BAT_IMAX, val->intval); | |
672 | break; | |
673 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: | |
674 | rc = smbb_charger_attr_write(chg, ATTR_BAT_VMAX, val->intval); | |
675 | break; | |
676 | default: | |
677 | rc = -EINVAL; | |
678 | break; | |
679 | } | |
680 | ||
681 | return rc; | |
682 | } | |
683 | ||
684 | static int smbb_battery_writable_property(struct power_supply *psy, | |
685 | enum power_supply_property psp) | |
686 | { | |
687 | switch (psp) { | |
688 | case POWER_SUPPLY_PROP_CURRENT_MAX: | |
689 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: | |
690 | return 1; | |
691 | default: | |
692 | return 0; | |
693 | } | |
694 | } | |
695 | ||
696 | static enum power_supply_property smbb_charger_properties[] = { | |
697 | POWER_SUPPLY_PROP_ONLINE, | |
698 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, | |
699 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, | |
700 | }; | |
701 | ||
702 | static enum power_supply_property smbb_battery_properties[] = { | |
703 | POWER_SUPPLY_PROP_STATUS, | |
704 | POWER_SUPPLY_PROP_HEALTH, | |
705 | POWER_SUPPLY_PROP_PRESENT, | |
706 | POWER_SUPPLY_PROP_CHARGE_TYPE, | |
707 | POWER_SUPPLY_PROP_CURRENT_MAX, | |
708 | POWER_SUPPLY_PROP_VOLTAGE_MAX, | |
709 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, | |
710 | POWER_SUPPLY_PROP_TECHNOLOGY, | |
711 | }; | |
712 | ||
713 | static const struct reg_off_mask_default { | |
714 | unsigned int offset; | |
715 | unsigned int mask; | |
716 | unsigned int value; | |
717 | unsigned int rev_mask; | |
718 | } smbb_charger_setup[] = { | |
719 | /* The bootloader is supposed to set this... make sure anyway. */ | |
720 | { SMBB_MISC_BOOT_DONE, BOOT_DONE, BOOT_DONE }, | |
721 | ||
722 | /* Disable software timer */ | |
723 | { SMBB_CHG_TCHG_MAX_EN, TCHG_MAX_EN, 0 }, | |
724 | ||
725 | /* Clear and disable watchdog */ | |
726 | { SMBB_CHG_WDOG_TIME, 0xff, 160 }, | |
727 | { SMBB_CHG_WDOG_EN, WDOG_EN, 0 }, | |
728 | ||
729 | /* Use charger based EoC detection */ | |
730 | { SMBB_CHG_IBAT_TERM_CHG, IBAT_TERM_CHG_IEOC, IBAT_TERM_CHG_IEOC_CHG }, | |
731 | ||
732 | /* Disable GSM PA load adjustment. | |
733 | * The PA signal is incorrectly connected on v2. | |
734 | */ | |
735 | { SMBB_CHG_CFG, 0xff, 0x00, BIT(3) }, | |
736 | ||
737 | /* Use VBAT (not VSYS) to compensate for IR drop during fast charging */ | |
738 | { SMBB_BUCK_REG_MODE, BUCK_REG_MODE, BUCK_REG_MODE_VBAT }, | |
739 | ||
740 | /* Enable battery temperature comparators */ | |
741 | { SMBB_BAT_BTC_CTRL, BTC_CTRL_COMP_EN, BTC_CTRL_COMP_EN }, | |
742 | ||
743 | /* Stop USB enumeration timer */ | |
744 | { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP }, | |
745 | ||
746 | #if 0 /* FIXME supposedly only to disable hardware ARB termination */ | |
747 | { SMBB_USB_SEC_ACCESS, SEC_ACCESS_MAGIC }, | |
748 | { SMBB_USB_REV_BST, 0xff, REV_BST_CHG_GONE }, | |
749 | #endif | |
750 | ||
751 | /* Stop USB enumeration timer, again */ | |
752 | { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP }, | |
753 | ||
754 | /* Enable charging */ | |
755 | { SMBB_CHG_CTRL, CTRL_EN, CTRL_EN }, | |
756 | }; | |
757 | ||
758 | static char *smbb_bif[] = { "smbb-bif" }; | |
759 | ||
760 | static const struct power_supply_desc bat_psy_desc = { | |
761 | .name = "smbb-bif", | |
762 | .type = POWER_SUPPLY_TYPE_BATTERY, | |
763 | .properties = smbb_battery_properties, | |
764 | .num_properties = ARRAY_SIZE(smbb_battery_properties), | |
765 | .get_property = smbb_battery_get_property, | |
766 | .set_property = smbb_battery_set_property, | |
767 | .property_is_writeable = smbb_battery_writable_property, | |
768 | }; | |
769 | ||
770 | static const struct power_supply_desc usb_psy_desc = { | |
771 | .name = "smbb-usbin", | |
772 | .type = POWER_SUPPLY_TYPE_USB, | |
773 | .properties = smbb_charger_properties, | |
774 | .num_properties = ARRAY_SIZE(smbb_charger_properties), | |
775 | .get_property = smbb_usbin_get_property, | |
776 | .set_property = smbb_usbin_set_property, | |
777 | .property_is_writeable = smbb_charger_writable_property, | |
778 | }; | |
779 | ||
780 | static const struct power_supply_desc dc_psy_desc = { | |
781 | .name = "smbb-dcin", | |
782 | .type = POWER_SUPPLY_TYPE_MAINS, | |
783 | .properties = smbb_charger_properties, | |
784 | .num_properties = ARRAY_SIZE(smbb_charger_properties), | |
785 | .get_property = smbb_dcin_get_property, | |
786 | .set_property = smbb_dcin_set_property, | |
787 | .property_is_writeable = smbb_charger_writable_property, | |
788 | }; | |
789 | ||
790 | static int smbb_charger_probe(struct platform_device *pdev) | |
791 | { | |
792 | struct power_supply_config bat_cfg = {}; | |
793 | struct power_supply_config usb_cfg = {}; | |
794 | struct power_supply_config dc_cfg = {}; | |
795 | struct smbb_charger *chg; | |
796 | int rc, i; | |
797 | ||
798 | chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); | |
799 | if (!chg) | |
800 | return -ENOMEM; | |
801 | ||
802 | chg->dev = &pdev->dev; | |
803 | mutex_init(&chg->statlock); | |
804 | ||
805 | chg->regmap = dev_get_regmap(pdev->dev.parent, NULL); | |
806 | if (!chg->regmap) { | |
807 | dev_err(&pdev->dev, "failed to locate regmap\n"); | |
808 | return -ENODEV; | |
809 | } | |
810 | ||
811 | rc = of_property_read_u32(pdev->dev.of_node, "reg", &chg->addr); | |
812 | if (rc) { | |
813 | dev_err(&pdev->dev, "missing or invalid 'reg' property\n"); | |
814 | return rc; | |
815 | } | |
816 | ||
817 | rc = regmap_read(chg->regmap, chg->addr + SMBB_MISC_REV2, &chg->revision); | |
818 | if (rc) { | |
819 | dev_err(&pdev->dev, "unable to read revision\n"); | |
820 | return rc; | |
821 | } | |
822 | ||
823 | chg->revision += 1; | |
824 | if (chg->revision != 2 && chg->revision != 3) { | |
825 | dev_err(&pdev->dev, "v1 hardware not supported\n"); | |
826 | return -ENODEV; | |
827 | } | |
828 | dev_info(&pdev->dev, "Initializing SMBB rev %u", chg->revision); | |
829 | ||
830 | chg->dc_disabled = of_property_read_bool(pdev->dev.of_node, "qcom,disable-dc"); | |
831 | ||
832 | for (i = 0; i < _ATTR_CNT; ++i) { | |
833 | rc = smbb_charger_attr_parse(chg, i); | |
834 | if (rc) { | |
835 | dev_err(&pdev->dev, "failed to parse/apply settings\n"); | |
836 | return rc; | |
837 | } | |
838 | } | |
839 | ||
840 | bat_cfg.drv_data = chg; | |
841 | bat_cfg.of_node = pdev->dev.of_node; | |
842 | chg->bat_psy = devm_power_supply_register(&pdev->dev, | |
843 | &bat_psy_desc, | |
844 | &bat_cfg); | |
845 | if (IS_ERR(chg->bat_psy)) { | |
846 | dev_err(&pdev->dev, "failed to register battery\n"); | |
847 | return PTR_ERR(chg->bat_psy); | |
848 | } | |
849 | ||
850 | usb_cfg.drv_data = chg; | |
851 | usb_cfg.supplied_to = smbb_bif; | |
852 | usb_cfg.num_supplicants = ARRAY_SIZE(smbb_bif); | |
853 | chg->usb_psy = devm_power_supply_register(&pdev->dev, | |
854 | &usb_psy_desc, | |
855 | &usb_cfg); | |
856 | if (IS_ERR(chg->usb_psy)) { | |
857 | dev_err(&pdev->dev, "failed to register USB power supply\n"); | |
858 | return PTR_ERR(chg->usb_psy); | |
859 | } | |
860 | ||
eee1d077 SB |
861 | chg->edev = devm_extcon_dev_allocate(&pdev->dev, smbb_usb_extcon_cable); |
862 | if (IS_ERR(chg->edev)) { | |
863 | dev_err(&pdev->dev, "failed to allocate extcon device\n"); | |
864 | return -ENOMEM; | |
865 | } | |
866 | ||
867 | rc = devm_extcon_dev_register(&pdev->dev, chg->edev); | |
868 | if (rc < 0) { | |
869 | dev_err(&pdev->dev, "failed to register extcon device\n"); | |
870 | return rc; | |
871 | } | |
872 | ||
56d7df87 CC |
873 | if (!chg->dc_disabled) { |
874 | dc_cfg.drv_data = chg; | |
875 | dc_cfg.supplied_to = smbb_bif; | |
876 | dc_cfg.num_supplicants = ARRAY_SIZE(smbb_bif); | |
877 | chg->dc_psy = devm_power_supply_register(&pdev->dev, | |
878 | &dc_psy_desc, | |
879 | &dc_cfg); | |
880 | if (IS_ERR(chg->dc_psy)) { | |
881 | dev_err(&pdev->dev, "failed to register DC power supply\n"); | |
882 | return PTR_ERR(chg->dc_psy); | |
883 | } | |
884 | } | |
885 | ||
886 | for (i = 0; i < ARRAY_SIZE(smbb_charger_irqs); ++i) { | |
887 | int irq; | |
888 | ||
889 | irq = platform_get_irq_byname(pdev, smbb_charger_irqs[i].name); | |
890 | if (irq < 0) { | |
891 | dev_err(&pdev->dev, "failed to get irq '%s'\n", | |
892 | smbb_charger_irqs[i].name); | |
893 | return irq; | |
894 | } | |
895 | ||
896 | smbb_charger_irqs[i].handler(irq, chg); | |
897 | ||
898 | rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, | |
899 | smbb_charger_irqs[i].handler, IRQF_ONESHOT, | |
900 | smbb_charger_irqs[i].name, chg); | |
901 | if (rc) { | |
902 | dev_err(&pdev->dev, "failed to request irq '%s'\n", | |
903 | smbb_charger_irqs[i].name); | |
904 | return rc; | |
905 | } | |
906 | } | |
907 | ||
908 | chg->jeita_ext_temp = of_property_read_bool(pdev->dev.of_node, | |
909 | "qcom,jeita-extended-temp-range"); | |
910 | ||
911 | /* Set temperature range to [35%:70%] or [25%:80%] accordingly */ | |
912 | rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_BAT_BTC_CTRL, | |
913 | BTC_CTRL_COLD_EXT | BTC_CTRL_HOT_EXT_N, | |
914 | chg->jeita_ext_temp ? | |
915 | BTC_CTRL_COLD_EXT : | |
916 | BTC_CTRL_HOT_EXT_N); | |
917 | if (rc) { | |
918 | dev_err(&pdev->dev, | |
919 | "unable to set %s temperature range\n", | |
920 | chg->jeita_ext_temp ? "JEITA extended" : "normal"); | |
921 | return rc; | |
922 | } | |
923 | ||
924 | for (i = 0; i < ARRAY_SIZE(smbb_charger_setup); ++i) { | |
925 | const struct reg_off_mask_default *r = &smbb_charger_setup[i]; | |
926 | ||
927 | if (r->rev_mask & BIT(chg->revision)) | |
928 | continue; | |
929 | ||
930 | rc = regmap_update_bits(chg->regmap, chg->addr + r->offset, | |
931 | r->mask, r->value); | |
932 | if (rc) { | |
933 | dev_err(&pdev->dev, | |
934 | "unable to initializing charging, bailing\n"); | |
935 | return rc; | |
936 | } | |
937 | } | |
938 | ||
939 | platform_set_drvdata(pdev, chg); | |
940 | ||
941 | return 0; | |
942 | } | |
943 | ||
944 | static int smbb_charger_remove(struct platform_device *pdev) | |
945 | { | |
946 | struct smbb_charger *chg; | |
947 | ||
948 | chg = platform_get_drvdata(pdev); | |
949 | ||
950 | regmap_update_bits(chg->regmap, chg->addr + SMBB_CHG_CTRL, CTRL_EN, 0); | |
951 | ||
952 | return 0; | |
953 | } | |
954 | ||
955 | static const struct of_device_id smbb_charger_id_table[] = { | |
956 | { .compatible = "qcom,pm8941-charger" }, | |
957 | { } | |
958 | }; | |
959 | MODULE_DEVICE_TABLE(of, smbb_charger_id_table); | |
960 | ||
961 | static struct platform_driver smbb_charger_driver = { | |
962 | .probe = smbb_charger_probe, | |
963 | .remove = smbb_charger_remove, | |
964 | .driver = { | |
965 | .name = "qcom-smbb", | |
966 | .of_match_table = smbb_charger_id_table, | |
967 | }, | |
968 | }; | |
969 | module_platform_driver(smbb_charger_driver); | |
970 | ||
971 | MODULE_DESCRIPTION("Qualcomm Switch-Mode Battery Charger and Boost driver"); | |
972 | MODULE_LICENSE("GPL v2"); |