Commit | Line | Data |
---|---|---|
f8c7f7dd MV |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | // | |
3 | // Copyright (C) 2018 ROHM Semiconductors | |
4 | // | |
5 | // power-supply driver for ROHM BD70528 PMIC | |
6 | ||
7 | /* | |
8 | * BD70528 charger HW state machine. | |
9 | * | |
10 | * The thermal shutdown state is not drawn. From any other state but | |
11 | * battery error and suspend it is possible to go to TSD/TMP states | |
12 | * if temperature is out of bounds. | |
13 | * | |
14 | * CHG_RST = H | |
15 | * or CHG_EN=L | |
16 | * or (DCIN2_UVLO=L && DCIN1_UVLO=L) | |
17 | * or (DCIN2_OVLO=H & DCIN1_UVKLO=L) | |
18 | * | |
19 | * +--------------+ +--------------+ | |
20 | * | | | | | |
21 | * | Any state +-------> | Suspend | | |
22 | * | | | | | |
23 | * +--------------+ +------+-------+ | |
24 | * | | |
25 | * CHG_EN = H && BAT_DET = H && | | |
26 | * No errors (temp, bat_ov, UVLO, | | |
27 | * OVLO...) | | |
28 | * | | |
29 | * BAT_OV or +---------v----------+ | |
30 | * (DBAT && TTRI) | | | |
31 | * +-----------------+ Trickle Charge | <---------------+ | |
32 | * | | | | | |
33 | * | +-------+------------+ | | |
34 | * | | | | |
35 | * | | ^ | | |
36 | * | V_BAT > VTRI_TH | | VBAT < VTRI_TH - 50mV | | |
37 | * | | | | | |
38 | * | v | | | |
39 | * | | | | |
40 | * | BAT_OV or +----------+----+ | | |
41 | * | (DBAT && TFST) | | | | |
42 | * | +----------------+ Fast Charge | | | |
43 | * | | | | | | |
44 | * v v +----+----------+ | | |
45 | * | | | |
46 | *+----------------+ ILIM_DET=L | ^ ILIM_DET | | |
47 | *| | & CV_DET=H | | or CV_DET=L | | |
48 | *| Battery Error | & VBAT > | | or VBAT < VRECHG_TH | | |
49 | *| | VRECHG_TH | | or IBAT > IFST/x | | |
50 | *+----------------+ & IBAT < | | | | |
51 | * IFST/x v | | | |
52 | * ^ | | | |
53 | * | +---------+-+ | | |
54 | * | | | | | |
55 | * +-------------------+ Top OFF | | | |
56 | * BAT_OV = H or | | | | |
57 | * (DBAT && TFST) +-----+-----+ | | |
58 | * | | | |
59 | * Stay top-off for 15s | | | |
60 | * v | | |
61 | * | | |
62 | * +--------+ | | |
63 | * | | | | |
64 | * | Done +-------------------------+ | |
65 | * | | | |
66 | * +--------+ VBAT < VRECHG_TH | |
67 | */ | |
68 | ||
69 | #include <linux/kernel.h> | |
70 | #include <linux/interrupt.h> | |
71 | #include <linux/mfd/rohm-bd70528.h> | |
72 | #include <linux/module.h> | |
73 | #include <linux/platform_device.h> | |
74 | #include <linux/power_supply.h> | |
92f7d909 | 75 | #include <linux/linear_range.h> |
f8c7f7dd MV |
76 | |
77 | #define CHG_STAT_SUSPEND 0x0 | |
78 | #define CHG_STAT_TRICKLE 0x1 | |
79 | #define CHG_STAT_FAST 0x3 | |
80 | #define CHG_STAT_TOPOFF 0xe | |
81 | #define CHG_STAT_DONE 0xf | |
82 | #define CHG_STAT_OTP_TRICKLE 0x10 | |
83 | #define CHG_STAT_OTP_FAST 0x11 | |
84 | #define CHG_STAT_OTP_DONE 0x12 | |
85 | #define CHG_STAT_TSD_TRICKLE 0x20 | |
86 | #define CHG_STAT_TSD_FAST 0x21 | |
87 | #define CHG_STAT_TSD_TOPOFF 0x22 | |
88 | #define CHG_STAT_BAT_ERR 0x7f | |
89 | ||
90 | static const char *bd70528_charger_model = "BD70528"; | |
91 | static const char *bd70528_charger_manufacturer = "ROHM Semiconductors"; | |
92 | ||
93 | #define BD_ERR_IRQ_HND(_name_, _wrn_) \ | |
94 | static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \ | |
95 | { \ | |
96 | struct power_supply *psy = (struct power_supply *)arg; \ | |
97 | \ | |
98 | power_supply_changed(psy); \ | |
99 | dev_err(&psy->dev, (_wrn_)); \ | |
100 | \ | |
101 | return IRQ_HANDLED; \ | |
102 | } | |
103 | ||
104 | #define BD_INFO_IRQ_HND(_name_, _wrn_) \ | |
105 | static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \ | |
106 | { \ | |
107 | struct power_supply *psy = (struct power_supply *)arg; \ | |
108 | \ | |
109 | power_supply_changed(psy); \ | |
110 | dev_dbg(&psy->dev, (_wrn_)); \ | |
111 | \ | |
112 | return IRQ_HANDLED; \ | |
113 | } | |
114 | ||
115 | #define BD_IRQ_HND(_name_) bd0528_##_name_##_interrupt | |
116 | ||
117 | struct bd70528_psy { | |
118 | struct regmap *regmap; | |
119 | struct device *dev; | |
120 | struct power_supply *psy; | |
121 | }; | |
122 | ||
123 | BD_ERR_IRQ_HND(BAT_OV_DET, "Battery overvoltage detected\n"); | |
124 | BD_ERR_IRQ_HND(DBAT_DET, "Dead battery detected\n"); | |
125 | BD_ERR_IRQ_HND(COLD_DET, "Battery cold\n"); | |
126 | BD_ERR_IRQ_HND(HOT_DET, "Battery hot\n"); | |
127 | BD_ERR_IRQ_HND(CHG_TSD, "Charger thermal shutdown\n"); | |
128 | BD_ERR_IRQ_HND(DCIN2_OV_DET, "DCIN2 overvoltage detected\n"); | |
129 | ||
130 | BD_INFO_IRQ_HND(BAT_OV_RES, "Battery voltage back to normal\n"); | |
131 | BD_INFO_IRQ_HND(COLD_RES, "Battery temperature back to normal\n"); | |
132 | BD_INFO_IRQ_HND(HOT_RES, "Battery temperature back to normal\n"); | |
133 | BD_INFO_IRQ_HND(BAT_RMV, "Battery removed\n"); | |
134 | BD_INFO_IRQ_HND(BAT_DET, "Battery detected\n"); | |
135 | BD_INFO_IRQ_HND(DCIN2_OV_RES, "DCIN2 voltage back to normal\n"); | |
136 | BD_INFO_IRQ_HND(DCIN2_RMV, "DCIN2 removed\n"); | |
137 | BD_INFO_IRQ_HND(DCIN2_DET, "DCIN2 detected\n"); | |
138 | BD_INFO_IRQ_HND(DCIN1_RMV, "DCIN1 removed\n"); | |
139 | BD_INFO_IRQ_HND(DCIN1_DET, "DCIN1 detected\n"); | |
140 | ||
141 | struct irq_name_pair { | |
142 | const char *n; | |
143 | irqreturn_t (*h)(int irq, void *arg); | |
144 | }; | |
145 | ||
146 | static int bd70528_get_irqs(struct platform_device *pdev, | |
147 | struct bd70528_psy *bdpsy) | |
148 | { | |
149 | int irq, i, ret; | |
150 | unsigned int mask; | |
151 | static const struct irq_name_pair bd70528_chg_irqs[] = { | |
152 | { .n = "bd70528-bat-ov-res", .h = BD_IRQ_HND(BAT_OV_RES) }, | |
153 | { .n = "bd70528-bat-ov-det", .h = BD_IRQ_HND(BAT_OV_DET) }, | |
154 | { .n = "bd70528-bat-dead", .h = BD_IRQ_HND(DBAT_DET) }, | |
155 | { .n = "bd70528-bat-warmed", .h = BD_IRQ_HND(COLD_RES) }, | |
156 | { .n = "bd70528-bat-cold", .h = BD_IRQ_HND(COLD_DET) }, | |
157 | { .n = "bd70528-bat-cooled", .h = BD_IRQ_HND(HOT_RES) }, | |
158 | { .n = "bd70528-bat-hot", .h = BD_IRQ_HND(HOT_DET) }, | |
159 | { .n = "bd70528-chg-tshd", .h = BD_IRQ_HND(CHG_TSD) }, | |
160 | { .n = "bd70528-bat-removed", .h = BD_IRQ_HND(BAT_RMV) }, | |
161 | { .n = "bd70528-bat-detected", .h = BD_IRQ_HND(BAT_DET) }, | |
162 | { .n = "bd70528-dcin2-ov-res", .h = BD_IRQ_HND(DCIN2_OV_RES) }, | |
163 | { .n = "bd70528-dcin2-ov-det", .h = BD_IRQ_HND(DCIN2_OV_DET) }, | |
164 | { .n = "bd70528-dcin2-removed", .h = BD_IRQ_HND(DCIN2_RMV) }, | |
165 | { .n = "bd70528-dcin2-detected", .h = BD_IRQ_HND(DCIN2_DET) }, | |
166 | { .n = "bd70528-dcin1-removed", .h = BD_IRQ_HND(DCIN1_RMV) }, | |
167 | { .n = "bd70528-dcin1-detected", .h = BD_IRQ_HND(DCIN1_DET) }, | |
168 | }; | |
169 | ||
170 | for (i = 0; i < ARRAY_SIZE(bd70528_chg_irqs); i++) { | |
171 | irq = platform_get_irq_byname(pdev, bd70528_chg_irqs[i].n); | |
172 | if (irq < 0) { | |
173 | dev_err(&pdev->dev, "Bad IRQ information for %s (%d)\n", | |
174 | bd70528_chg_irqs[i].n, irq); | |
175 | return irq; | |
176 | } | |
177 | ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, | |
178 | bd70528_chg_irqs[i].h, | |
179 | IRQF_ONESHOT, | |
180 | bd70528_chg_irqs[i].n, | |
181 | bdpsy->psy); | |
182 | ||
183 | if (ret) | |
184 | return ret; | |
185 | } | |
186 | /* | |
187 | * BD70528 irq controller is not touching the main mask register. | |
188 | * So enable the charger block interrupts at main level. We can just | |
189 | * leave them enabled as irq-controller should disable irqs | |
190 | * from sub-registers when IRQ is disabled or freed. | |
191 | */ | |
192 | mask = BD70528_REG_INT_BAT1_MASK | BD70528_REG_INT_BAT2_MASK; | |
193 | ret = regmap_update_bits(bdpsy->regmap, | |
194 | BD70528_REG_INT_MAIN_MASK, mask, 0); | |
195 | if (ret) | |
196 | dev_err(&pdev->dev, "Failed to enable charger IRQs\n"); | |
197 | ||
198 | return ret; | |
199 | } | |
200 | ||
201 | static int bd70528_get_charger_status(struct bd70528_psy *bdpsy, int *val) | |
202 | { | |
203 | int ret; | |
204 | unsigned int v; | |
205 | ||
206 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v); | |
207 | if (ret) { | |
208 | dev_err(bdpsy->dev, "Charger state read failure %d\n", | |
209 | ret); | |
210 | return ret; | |
211 | } | |
212 | ||
213 | switch (v & BD70528_MASK_CHG_STAT) { | |
214 | case CHG_STAT_SUSPEND: | |
215 | /* Maybe we should check the CHG_TTRI_EN? */ | |
216 | case CHG_STAT_OTP_TRICKLE: | |
217 | case CHG_STAT_OTP_FAST: | |
218 | case CHG_STAT_OTP_DONE: | |
219 | case CHG_STAT_TSD_TRICKLE: | |
220 | case CHG_STAT_TSD_FAST: | |
221 | case CHG_STAT_TSD_TOPOFF: | |
222 | case CHG_STAT_BAT_ERR: | |
223 | *val = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
224 | break; | |
225 | case CHG_STAT_DONE: | |
226 | *val = POWER_SUPPLY_STATUS_FULL; | |
227 | break; | |
228 | case CHG_STAT_TRICKLE: | |
229 | case CHG_STAT_FAST: | |
230 | case CHG_STAT_TOPOFF: | |
231 | *val = POWER_SUPPLY_STATUS_CHARGING; | |
232 | break; | |
233 | default: | |
234 | *val = POWER_SUPPLY_STATUS_UNKNOWN; | |
235 | break; | |
236 | } | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | static int bd70528_get_charge_type(struct bd70528_psy *bdpsy, int *val) | |
242 | { | |
243 | int ret; | |
244 | unsigned int v; | |
245 | ||
246 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v); | |
247 | if (ret) { | |
248 | dev_err(bdpsy->dev, "Charger state read failure %d\n", | |
249 | ret); | |
250 | return ret; | |
251 | } | |
252 | ||
253 | switch (v & BD70528_MASK_CHG_STAT) { | |
254 | case CHG_STAT_TRICKLE: | |
255 | *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; | |
256 | break; | |
257 | case CHG_STAT_FAST: | |
258 | case CHG_STAT_TOPOFF: | |
259 | *val = POWER_SUPPLY_CHARGE_TYPE_FAST; | |
260 | break; | |
261 | case CHG_STAT_DONE: | |
262 | case CHG_STAT_SUSPEND: | |
263 | /* Maybe we should check the CHG_TTRI_EN? */ | |
264 | case CHG_STAT_OTP_TRICKLE: | |
265 | case CHG_STAT_OTP_FAST: | |
266 | case CHG_STAT_OTP_DONE: | |
267 | case CHG_STAT_TSD_TRICKLE: | |
268 | case CHG_STAT_TSD_FAST: | |
269 | case CHG_STAT_TSD_TOPOFF: | |
270 | case CHG_STAT_BAT_ERR: | |
271 | *val = POWER_SUPPLY_CHARGE_TYPE_NONE; | |
272 | break; | |
273 | default: | |
274 | *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; | |
275 | break; | |
276 | } | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
281 | static int bd70528_get_battery_health(struct bd70528_psy *bdpsy, int *val) | |
282 | { | |
283 | int ret; | |
284 | unsigned int v; | |
285 | ||
286 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v); | |
287 | if (ret) { | |
288 | dev_err(bdpsy->dev, "Battery state read failure %d\n", | |
289 | ret); | |
290 | return ret; | |
291 | } | |
292 | /* No battery? */ | |
293 | if (!(v & BD70528_MASK_CHG_BAT_DETECT)) | |
294 | *val = POWER_SUPPLY_HEALTH_DEAD; | |
295 | else if (v & BD70528_MASK_CHG_BAT_OVERVOLT) | |
296 | *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; | |
297 | else if (v & BD70528_MASK_CHG_BAT_TIMER) | |
298 | *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; | |
299 | else | |
300 | *val = POWER_SUPPLY_HEALTH_GOOD; | |
301 | ||
302 | return 0; | |
303 | } | |
304 | ||
305 | static int bd70528_get_online(struct bd70528_psy *bdpsy, int *val) | |
306 | { | |
307 | int ret; | |
308 | unsigned int v; | |
309 | ||
310 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_IN_STAT, &v); | |
311 | if (ret) { | |
312 | dev_err(bdpsy->dev, "DC1 IN state read failure %d\n", | |
313 | ret); | |
314 | return ret; | |
315 | } | |
316 | ||
317 | *val = (v & BD70528_MASK_CHG_DCIN1_UVLO) ? 1 : 0; | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
322 | static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val) | |
323 | { | |
324 | int ret; | |
325 | unsigned int v; | |
326 | ||
327 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v); | |
328 | if (ret) { | |
329 | dev_err(bdpsy->dev, "Battery state read failure %d\n", | |
330 | ret); | |
331 | return ret; | |
332 | } | |
333 | ||
334 | *val = (v & BD70528_MASK_CHG_BAT_DETECT) ? 1 : 0; | |
335 | ||
336 | return 0; | |
337 | } | |
338 | ||
92f7d909 | 339 | static const struct linear_range current_limit_ranges[] = { |
f8c7f7dd MV |
340 | { |
341 | .min = 5, | |
342 | .step = 1, | |
92f7d909 MV |
343 | .min_sel = 0, |
344 | .max_sel = 0x22, | |
f8c7f7dd MV |
345 | }, |
346 | { | |
347 | .min = 40, | |
348 | .step = 5, | |
92f7d909 MV |
349 | .min_sel = 0x23, |
350 | .max_sel = 0x26, | |
f8c7f7dd MV |
351 | }, |
352 | { | |
353 | .min = 60, | |
354 | .step = 20, | |
92f7d909 MV |
355 | .min_sel = 0x27, |
356 | .max_sel = 0x2d, | |
f8c7f7dd MV |
357 | }, |
358 | { | |
359 | .min = 200, | |
360 | .step = 50, | |
92f7d909 MV |
361 | .min_sel = 0x2e, |
362 | .max_sel = 0x34, | |
363 | }, | |
364 | { | |
365 | .min = 500, | |
366 | .step = 0, | |
367 | .min_sel = 0x35, | |
368 | .max_sel = 0x3f, | |
369 | }, | |
f8c7f7dd MV |
370 | }; |
371 | ||
372 | /* | |
373 | * BD70528 would support setting and getting own charge current/ | |
374 | * voltage for low temperatures. The driver currently only reads | |
375 | * the charge current at room temperature. We do set both though. | |
376 | */ | |
92f7d909 | 377 | static const struct linear_range warm_charge_curr[] = { |
f8c7f7dd MV |
378 | { |
379 | .min = 10, | |
380 | .step = 10, | |
92f7d909 MV |
381 | .min_sel = 0, |
382 | .max_sel = 0x12, | |
f8c7f7dd MV |
383 | }, |
384 | { | |
385 | .min = 200, | |
386 | .step = 25, | |
92f7d909 MV |
387 | .min_sel = 0x13, |
388 | .max_sel = 0x1f, | |
f8c7f7dd MV |
389 | }, |
390 | }; | |
391 | ||
392 | /* | |
393 | * Cold charge current selectors are identical to warm charge current | |
394 | * selectors. The difference is that only smaller currents are available | |
395 | * at cold charge range. | |
396 | */ | |
397 | #define MAX_COLD_CHG_CURR_SEL 0x15 | |
398 | #define MAX_WARM_CHG_CURR_SEL 0x1f | |
399 | #define MIN_CHG_CURR_SEL 0x0 | |
400 | ||
f8c7f7dd MV |
401 | static int get_charge_current(struct bd70528_psy *bdpsy, int *ma) |
402 | { | |
403 | unsigned int sel; | |
404 | int ret; | |
405 | ||
406 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CHG_CURR_WARM, | |
407 | &sel); | |
408 | if (ret) { | |
409 | dev_err(bdpsy->dev, | |
410 | "Charge current reading failed (%d)\n", ret); | |
411 | return ret; | |
412 | } | |
413 | ||
414 | sel &= BD70528_MASK_CHG_CHG_CURR; | |
415 | ||
92f7d909 MV |
416 | ret = linear_range_get_value_array(&warm_charge_curr[0], |
417 | ARRAY_SIZE(warm_charge_curr), | |
418 | sel, ma); | |
f8c7f7dd MV |
419 | if (ret) { |
420 | dev_err(bdpsy->dev, | |
421 | "Unknown charge current value 0x%x\n", | |
422 | sel); | |
423 | } | |
424 | ||
425 | return ret; | |
426 | } | |
427 | ||
428 | static int get_current_limit(struct bd70528_psy *bdpsy, int *ma) | |
429 | { | |
430 | unsigned int sel; | |
431 | int ret; | |
432 | ||
433 | ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_DCIN_ILIM, | |
434 | &sel); | |
435 | ||
436 | if (ret) { | |
437 | dev_err(bdpsy->dev, | |
438 | "Input current limit reading failed (%d)\n", ret); | |
439 | return ret; | |
440 | } | |
441 | ||
442 | sel &= BD70528_MASK_CHG_DCIN_ILIM; | |
443 | ||
92f7d909 MV |
444 | ret = linear_range_get_value_array(¤t_limit_ranges[0], |
445 | ARRAY_SIZE(current_limit_ranges), | |
446 | sel, ma); | |
f8c7f7dd MV |
447 | if (ret) { |
448 | /* Unspecified values mean 500 mA */ | |
449 | *ma = 500; | |
450 | } | |
451 | return 0; | |
452 | } | |
453 | ||
454 | static enum power_supply_property bd70528_charger_props[] = { | |
455 | POWER_SUPPLY_PROP_STATUS, | |
456 | POWER_SUPPLY_PROP_CHARGE_TYPE, | |
457 | POWER_SUPPLY_PROP_HEALTH, | |
458 | POWER_SUPPLY_PROP_PRESENT, | |
459 | POWER_SUPPLY_PROP_ONLINE, | |
460 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, | |
461 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, | |
462 | POWER_SUPPLY_PROP_MODEL_NAME, | |
463 | POWER_SUPPLY_PROP_MANUFACTURER, | |
464 | }; | |
465 | ||
466 | static int bd70528_charger_get_property(struct power_supply *psy, | |
467 | enum power_supply_property psp, | |
468 | union power_supply_propval *val) | |
469 | { | |
470 | struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy); | |
471 | int ret = 0; | |
472 | ||
473 | switch (psp) { | |
474 | case POWER_SUPPLY_PROP_STATUS: | |
475 | return bd70528_get_charger_status(bdpsy, &val->intval); | |
476 | case POWER_SUPPLY_PROP_CHARGE_TYPE: | |
477 | return bd70528_get_charge_type(bdpsy, &val->intval); | |
478 | case POWER_SUPPLY_PROP_HEALTH: | |
479 | return bd70528_get_battery_health(bdpsy, &val->intval); | |
480 | case POWER_SUPPLY_PROP_PRESENT: | |
481 | return bd70528_get_present(bdpsy, &val->intval); | |
482 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: | |
483 | ret = get_current_limit(bdpsy, &val->intval); | |
484 | val->intval *= 1000; | |
485 | return ret; | |
486 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | |
487 | ret = get_charge_current(bdpsy, &val->intval); | |
488 | val->intval *= 1000; | |
489 | return ret; | |
490 | case POWER_SUPPLY_PROP_ONLINE: | |
491 | return bd70528_get_online(bdpsy, &val->intval); | |
492 | case POWER_SUPPLY_PROP_MODEL_NAME: | |
493 | val->strval = bd70528_charger_model; | |
494 | return 0; | |
495 | case POWER_SUPPLY_PROP_MANUFACTURER: | |
496 | val->strval = bd70528_charger_manufacturer; | |
497 | return 0; | |
498 | default: | |
499 | break; | |
500 | } | |
501 | ||
502 | return -EINVAL; | |
503 | } | |
504 | ||
505 | static int bd70528_prop_is_writable(struct power_supply *psy, | |
506 | enum power_supply_property psp) | |
507 | { | |
508 | switch (psp) { | |
509 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: | |
510 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | |
511 | return 1; | |
512 | default: | |
513 | break; | |
514 | } | |
515 | return 0; | |
516 | } | |
517 | ||
518 | static int set_charge_current(struct bd70528_psy *bdpsy, int ma) | |
519 | { | |
520 | unsigned int reg; | |
521 | int ret = 0, tmpret; | |
522 | bool found; | |
523 | ||
524 | if (ma > 500) { | |
525 | dev_warn(bdpsy->dev, | |
526 | "Requested charge current %u exceed maximum (500mA)\n", | |
527 | ma); | |
528 | reg = MAX_WARM_CHG_CURR_SEL; | |
529 | goto set; | |
530 | } | |
531 | if (ma < 10) { | |
532 | dev_err(bdpsy->dev, | |
533 | "Requested charge current %u smaller than min (10mA)\n", | |
534 | ma); | |
535 | reg = MIN_CHG_CURR_SEL; | |
536 | ret = -EINVAL; | |
537 | goto set; | |
538 | } | |
539 | ||
92f7d909 MV |
540 | /* |
541 | * For BD70528 voltage/current limits we happily accept any value which | |
542 | * belongs the range. We could check if value matching the selector is | |
543 | * desired by computing the range min + (sel - sel_low) * range step - but | |
544 | * I guess it is enough if we use voltage/current which is closest (below) | |
545 | * the requested? | |
546 | */ | |
547 | ||
548 | ret = linear_range_get_selector_low_array(warm_charge_curr, | |
549 | ARRAY_SIZE(warm_charge_curr), | |
550 | ma, ®, &found); | |
f8c7f7dd | 551 | if (ret) { |
92f7d909 MV |
552 | dev_err(bdpsy->dev, |
553 | "Unsupported charge current %u mA\n", ma); | |
f8c7f7dd MV |
554 | reg = MIN_CHG_CURR_SEL; |
555 | goto set; | |
556 | } | |
557 | if (!found) { | |
92f7d909 MV |
558 | /* |
559 | * There was a gap in supported values and we hit it. | |
560 | * Yet a smaller value was found so we use it. | |
561 | */ | |
f8c7f7dd MV |
562 | dev_warn(bdpsy->dev, |
563 | "Unsupported charge current %u mA\n", ma); | |
564 | } | |
565 | set: | |
566 | ||
567 | tmpret = regmap_update_bits(bdpsy->regmap, | |
568 | BD70528_REG_CHG_CHG_CURR_WARM, | |
569 | BD70528_MASK_CHG_CHG_CURR, reg); | |
570 | if (tmpret) | |
571 | dev_err(bdpsy->dev, | |
572 | "Charge current write failure (%d)\n", tmpret); | |
573 | ||
574 | if (reg > MAX_COLD_CHG_CURR_SEL) | |
575 | reg = MAX_COLD_CHG_CURR_SEL; | |
576 | ||
577 | if (!tmpret) | |
578 | tmpret = regmap_update_bits(bdpsy->regmap, | |
579 | BD70528_REG_CHG_CHG_CURR_COLD, | |
580 | BD70528_MASK_CHG_CHG_CURR, reg); | |
581 | ||
582 | if (!ret) | |
583 | ret = tmpret; | |
584 | ||
585 | return ret; | |
586 | } | |
587 | ||
588 | #define MAX_CURR_LIMIT_SEL 0x34 | |
589 | #define MIN_CURR_LIMIT_SEL 0x0 | |
590 | ||
591 | static int set_current_limit(struct bd70528_psy *bdpsy, int ma) | |
592 | { | |
593 | unsigned int reg; | |
594 | int ret = 0, tmpret; | |
595 | bool found; | |
596 | ||
597 | if (ma > 500) { | |
598 | dev_warn(bdpsy->dev, | |
599 | "Requested current limit %u exceed maximum (500mA)\n", | |
600 | ma); | |
601 | reg = MAX_CURR_LIMIT_SEL; | |
602 | goto set; | |
603 | } | |
604 | if (ma < 5) { | |
605 | dev_err(bdpsy->dev, | |
606 | "Requested current limit %u smaller than min (5mA)\n", | |
607 | ma); | |
608 | reg = MIN_CURR_LIMIT_SEL; | |
609 | ret = -EINVAL; | |
610 | goto set; | |
611 | } | |
612 | ||
92f7d909 MV |
613 | ret = linear_range_get_selector_low_array(current_limit_ranges, |
614 | ARRAY_SIZE(current_limit_ranges), | |
615 | ma, ®, &found); | |
f8c7f7dd | 616 | if (ret) { |
92f7d909 | 617 | dev_err(bdpsy->dev, "Unsupported current limit %umA\n", ma); |
f8c7f7dd MV |
618 | reg = MIN_CURR_LIMIT_SEL; |
619 | goto set; | |
620 | } | |
621 | if (!found) { | |
92f7d909 MV |
622 | /* |
623 | * There was a gap in supported values and we hit it. | |
624 | * We found a smaller value from ranges and use it. | |
625 | * Warn user though. | |
626 | */ | |
627 | dev_warn(bdpsy->dev, "Unsupported current limit %umA\n", ma); | |
f8c7f7dd MV |
628 | } |
629 | ||
630 | set: | |
631 | tmpret = regmap_update_bits(bdpsy->regmap, | |
632 | BD70528_REG_CHG_DCIN_ILIM, | |
633 | BD70528_MASK_CHG_DCIN_ILIM, reg); | |
634 | ||
635 | if (!ret) | |
636 | ret = tmpret; | |
637 | ||
638 | return ret; | |
639 | } | |
640 | ||
641 | static int bd70528_charger_set_property(struct power_supply *psy, | |
642 | enum power_supply_property psp, | |
643 | const union power_supply_propval *val) | |
644 | { | |
645 | struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy); | |
646 | ||
647 | switch (psp) { | |
648 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: | |
649 | return set_current_limit(bdpsy, val->intval / 1000); | |
650 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | |
651 | return set_charge_current(bdpsy, val->intval / 1000); | |
652 | default: | |
653 | break; | |
654 | } | |
655 | return -EINVAL; | |
656 | } | |
657 | ||
658 | static const struct power_supply_desc bd70528_charger_desc = { | |
659 | .name = "bd70528-charger", | |
660 | .type = POWER_SUPPLY_TYPE_MAINS, | |
661 | .properties = bd70528_charger_props, | |
662 | .num_properties = ARRAY_SIZE(bd70528_charger_props), | |
663 | .get_property = bd70528_charger_get_property, | |
664 | .set_property = bd70528_charger_set_property, | |
665 | .property_is_writeable = bd70528_prop_is_writable, | |
666 | }; | |
667 | ||
668 | static int bd70528_power_probe(struct platform_device *pdev) | |
669 | { | |
670 | struct bd70528_psy *bdpsy; | |
671 | struct power_supply_config cfg = {}; | |
672 | ||
673 | bdpsy = devm_kzalloc(&pdev->dev, sizeof(*bdpsy), GFP_KERNEL); | |
674 | if (!bdpsy) | |
675 | return -ENOMEM; | |
676 | ||
677 | bdpsy->regmap = dev_get_regmap(pdev->dev.parent, NULL); | |
678 | if (!bdpsy->regmap) { | |
679 | dev_err(&pdev->dev, "No regmap found for chip\n"); | |
680 | return -EINVAL; | |
681 | } | |
682 | bdpsy->dev = &pdev->dev; | |
683 | ||
684 | platform_set_drvdata(pdev, bdpsy); | |
685 | cfg.drv_data = bdpsy; | |
686 | cfg.of_node = pdev->dev.parent->of_node; | |
687 | ||
688 | bdpsy->psy = devm_power_supply_register(&pdev->dev, | |
689 | &bd70528_charger_desc, &cfg); | |
690 | if (IS_ERR(bdpsy->psy)) { | |
691 | dev_err(&pdev->dev, "failed: power supply register\n"); | |
692 | return PTR_ERR(bdpsy->psy); | |
693 | } | |
694 | ||
695 | return bd70528_get_irqs(pdev, bdpsy); | |
696 | } | |
697 | ||
698 | static struct platform_driver bd70528_power = { | |
699 | .driver = { | |
700 | .name = "bd70528-power" | |
701 | }, | |
702 | .probe = bd70528_power_probe, | |
703 | }; | |
704 | ||
705 | module_platform_driver(bd70528_power); | |
706 | ||
707 | MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); | |
708 | MODULE_DESCRIPTION("BD70528 power-supply driver"); | |
709 | MODULE_LICENSE("GPL"); | |
9480029f | 710 | MODULE_ALIAS("platform:bd70528-power"); |