1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Power supply driver for the RICOH RN5T618 power management chip family |
4 | * |
5 | * Copyright (C) 2020 Andreas Kemnade |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/device.h> |
10 | #include <linux/bitops.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/iio/consumer.h> |
13 | #include <linux/init.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/module.h> |
16 | #include <linux/mfd/rn5t618.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/power_supply.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/slab.h> |
21 | |
22 | #define CHG_STATE_ADP_INPUT 0x40 |
23 | #define CHG_STATE_USB_INPUT 0x80 |
24 | #define CHG_STATE_MASK 0x1f |
25 | #define CHG_STATE_CHG_OFF 0 |
26 | #define CHG_STATE_CHG_READY_VADP 1 |
27 | #define CHG_STATE_CHG_TRICKLE 2 |
28 | #define CHG_STATE_CHG_RAPID 3 |
29 | #define CHG_STATE_CHG_COMPLETE 4 |
30 | #define CHG_STATE_SUSPEND 5 |
31 | #define CHG_STATE_VCHG_OVER_VOL 6 |
32 | #define CHG_STATE_BAT_ERROR 7 |
33 | #define CHG_STATE_NO_BAT 8 |
34 | #define CHG_STATE_BAT_OVER_VOL 9 |
35 | #define CHG_STATE_BAT_TEMP_ERR 10 |
36 | #define CHG_STATE_DIE_ERR 11 |
37 | #define CHG_STATE_DIE_SHUTDOWN 12 |
38 | #define CHG_STATE_NO_BAT2 13 |
39 | #define CHG_STATE_CHG_READY_VUSB 14 |
40 | |
41 | #define GCHGDET_TYPE_MASK 0x30 |
42 | #define GCHGDET_TYPE_SDP 0x00 |
43 | #define GCHGDET_TYPE_CDP 0x10 |
44 | #define GCHGDET_TYPE_DCP 0x20 |
45 | |
46 | #define FG_ENABLE 1 |
47 | |
48 | /* |
49 | * Formula seems accurate for battery current, but for USB current around 70mA |
50 | * per step was seen on Kobo Clara HD but all sources show the same formula |
51 | * also fur USB current. To avoid accidentially unwanted high currents we stick |
52 | * to that formula |
53 | */ |
54 | #define TO_CUR_REG(x) ((x) / 100000 - 1) |
55 | #define FROM_CUR_REG(x) ((((x) & 0x1f) + 1) * 100000) |
56 | #define CHG_MIN_CUR 100000 |
57 | #define CHG_MAX_CUR 1800000 |
58 | #define ADP_MAX_CUR 2500000 |
59 | #define USB_MAX_CUR 1400000 |
60 | |
61 | |
62 | struct rn5t618_power_info { |
63 | struct rn5t618 *rn5t618; |
64 | struct platform_device *pdev; |
65 | struct power_supply *battery; |
66 | struct power_supply *usb; |
67 | struct power_supply *adp; |
68 | struct iio_channel *channel_vusb; |
69 | struct iio_channel *channel_vadp; |
70 | int irq; |
71 | }; |
72 | |
73 | static enum power_supply_usb_type rn5t618_usb_types[] = { |
74 | POWER_SUPPLY_USB_TYPE_SDP, |
75 | POWER_SUPPLY_USB_TYPE_DCP, |
76 | POWER_SUPPLY_USB_TYPE_CDP, |
77 | POWER_SUPPLY_USB_TYPE_UNKNOWN |
78 | }; |
79 | |
80 | static enum power_supply_property rn5t618_usb_props[] = { |
81 | /* input current limit is not very accurate */ |
82 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
83 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
84 | POWER_SUPPLY_PROP_STATUS, |
85 | POWER_SUPPLY_PROP_USB_TYPE, |
86 | POWER_SUPPLY_PROP_ONLINE, |
87 | }; |
88 | |
89 | static enum power_supply_property rn5t618_adp_props[] = { |
90 | /* input current limit is not very accurate */ |
91 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
92 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
93 | POWER_SUPPLY_PROP_STATUS, |
94 | POWER_SUPPLY_PROP_ONLINE, |
95 | }; |
96 | |
97 | |
98 | static enum power_supply_property rn5t618_battery_props[] = { |
99 | POWER_SUPPLY_PROP_STATUS, |
100 | POWER_SUPPLY_PROP_PRESENT, |
101 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
102 | POWER_SUPPLY_PROP_CURRENT_NOW, |
103 | POWER_SUPPLY_PROP_CAPACITY, |
104 | POWER_SUPPLY_PROP_TEMP, |
105 | POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, |
106 | POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, |
107 | POWER_SUPPLY_PROP_TECHNOLOGY, |
108 | POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, |
109 | POWER_SUPPLY_PROP_CHARGE_FULL, |
110 | POWER_SUPPLY_PROP_CHARGE_NOW, |
111 | }; |
112 | |
113 | static int rn5t618_battery_read_doublereg(struct rn5t618_power_info *info, |
114 | u8 reg, u16 *result) |
115 | { |
116 | int ret, i; |
117 | u8 data[2]; |
118 | u16 old, new; |
119 | |
120 | old = 0; |
121 | /* Prevent races when registers are changing. */ |
122 | for (i = 0; i < 3; i++) { |
123 | ret = regmap_bulk_read(map: info->rn5t618->regmap, |
124 | reg, val: data, val_count: sizeof(data)); |
125 | if (ret) |
126 | return ret; |
127 | |
128 | new = data[0] << 8; |
129 | new |= data[1]; |
130 | if (new == old) |
131 | break; |
132 | |
133 | old = new; |
134 | } |
135 | |
136 | *result = new; |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | static int rn5t618_decode_status(unsigned int status) |
142 | { |
143 | switch (status & CHG_STATE_MASK) { |
144 | case CHG_STATE_CHG_OFF: |
145 | case CHG_STATE_SUSPEND: |
146 | case CHG_STATE_VCHG_OVER_VOL: |
147 | case CHG_STATE_DIE_SHUTDOWN: |
148 | return POWER_SUPPLY_STATUS_DISCHARGING; |
149 | |
150 | case CHG_STATE_CHG_TRICKLE: |
151 | case CHG_STATE_CHG_RAPID: |
152 | return POWER_SUPPLY_STATUS_CHARGING; |
153 | |
154 | case CHG_STATE_CHG_COMPLETE: |
155 | return POWER_SUPPLY_STATUS_FULL; |
156 | |
157 | default: |
158 | return POWER_SUPPLY_STATUS_NOT_CHARGING; |
159 | } |
160 | } |
161 | |
162 | static int rn5t618_battery_status(struct rn5t618_power_info *info, |
163 | union power_supply_propval *val) |
164 | { |
165 | unsigned int v; |
166 | int ret; |
167 | |
168 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTATE, val: &v); |
169 | if (ret) |
170 | return ret; |
171 | |
172 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
173 | |
174 | if (v & 0xc0) { /* USB or ADP plugged */ |
175 | val->intval = rn5t618_decode_status(status: v); |
176 | } else |
177 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
178 | |
179 | return ret; |
180 | } |
181 | |
182 | static int rn5t618_battery_present(struct rn5t618_power_info *info, |
183 | union power_supply_propval *val) |
184 | { |
185 | unsigned int v; |
186 | int ret; |
187 | |
188 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTATE, val: &v); |
189 | if (ret) |
190 | return ret; |
191 | |
192 | v &= CHG_STATE_MASK; |
193 | if ((v == CHG_STATE_NO_BAT) || (v == CHG_STATE_NO_BAT2)) |
194 | val->intval = 0; |
195 | else |
196 | val->intval = 1; |
197 | |
198 | return ret; |
199 | } |
200 | |
201 | static int rn5t618_battery_voltage_now(struct rn5t618_power_info *info, |
202 | union power_supply_propval *val) |
203 | { |
204 | u16 res; |
205 | int ret; |
206 | |
207 | ret = rn5t618_battery_read_doublereg(info, RN5T618_VOLTAGE_1, result: &res); |
208 | if (ret) |
209 | return ret; |
210 | |
211 | val->intval = res * 2 * 2500 / 4095 * 1000; |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static int rn5t618_battery_current_now(struct rn5t618_power_info *info, |
217 | union power_supply_propval *val) |
218 | { |
219 | u16 res; |
220 | int ret; |
221 | |
222 | ret = rn5t618_battery_read_doublereg(info, RN5T618_CC_AVEREG1, result: &res); |
223 | if (ret) |
224 | return ret; |
225 | |
226 | /* current is negative when discharging */ |
227 | val->intval = sign_extend32(value: res, index: 13) * 1000; |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static int rn5t618_battery_capacity(struct rn5t618_power_info *info, |
233 | union power_supply_propval *val) |
234 | { |
235 | unsigned int v; |
236 | int ret; |
237 | |
238 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_SOC, val: &v); |
239 | if (ret) |
240 | return ret; |
241 | |
242 | val->intval = v; |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static int rn5t618_battery_temp(struct rn5t618_power_info *info, |
248 | union power_supply_propval *val) |
249 | { |
250 | u16 res; |
251 | int ret; |
252 | |
253 | ret = rn5t618_battery_read_doublereg(info, RN5T618_TEMP_1, result: &res); |
254 | if (ret) |
255 | return ret; |
256 | |
257 | val->intval = sign_extend32(value: res, index: 11) * 10 / 16; |
258 | |
259 | return 0; |
260 | } |
261 | |
262 | static int rn5t618_battery_tte(struct rn5t618_power_info *info, |
263 | union power_supply_propval *val) |
264 | { |
265 | u16 res; |
266 | int ret; |
267 | |
268 | ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_EMPTY_H, result: &res); |
269 | if (ret) |
270 | return ret; |
271 | |
272 | if (res == 65535) |
273 | return -ENODATA; |
274 | |
275 | val->intval = res * 60; |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static int rn5t618_battery_ttf(struct rn5t618_power_info *info, |
281 | union power_supply_propval *val) |
282 | { |
283 | u16 res; |
284 | int ret; |
285 | |
286 | ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_FULL_H, result: &res); |
287 | if (ret) |
288 | return ret; |
289 | |
290 | if (res == 65535) |
291 | return -ENODATA; |
292 | |
293 | val->intval = res * 60; |
294 | |
295 | return 0; |
296 | } |
297 | |
298 | static int rn5t618_battery_set_current_limit(struct rn5t618_power_info *info, |
299 | const union power_supply_propval *val) |
300 | { |
301 | if (val->intval < CHG_MIN_CUR) |
302 | return -EINVAL; |
303 | |
304 | if (val->intval >= CHG_MAX_CUR) |
305 | return -EINVAL; |
306 | |
307 | return regmap_update_bits(map: info->rn5t618->regmap, |
308 | RN5T618_CHGISET, |
309 | mask: 0x1F, TO_CUR_REG(val->intval)); |
310 | } |
311 | |
312 | static int rn5t618_battery_get_current_limit(struct rn5t618_power_info *info, |
313 | union power_supply_propval *val) |
314 | { |
315 | unsigned int regval; |
316 | int ret; |
317 | |
318 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGISET, |
319 | val: ®val); |
320 | if (ret < 0) |
321 | return ret; |
322 | |
323 | val->intval = FROM_CUR_REG(regval); |
324 | |
325 | return 0; |
326 | } |
327 | |
328 | static int rn5t618_battery_charge_full(struct rn5t618_power_info *info, |
329 | union power_supply_propval *val) |
330 | { |
331 | u16 res; |
332 | int ret; |
333 | |
334 | ret = rn5t618_battery_read_doublereg(info, RN5T618_FA_CAP_H, result: &res); |
335 | if (ret) |
336 | return ret; |
337 | |
338 | val->intval = res * 1000; |
339 | |
340 | return 0; |
341 | } |
342 | |
343 | static int rn5t618_battery_charge_now(struct rn5t618_power_info *info, |
344 | union power_supply_propval *val) |
345 | { |
346 | u16 res; |
347 | int ret; |
348 | |
349 | ret = rn5t618_battery_read_doublereg(info, RN5T618_RE_CAP_H, result: &res); |
350 | if (ret) |
351 | return ret; |
352 | |
353 | val->intval = res * 1000; |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | static int rn5t618_battery_get_property(struct power_supply *psy, |
359 | enum power_supply_property psp, |
360 | union power_supply_propval *val) |
361 | { |
362 | int ret = 0; |
363 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
364 | |
365 | switch (psp) { |
366 | case POWER_SUPPLY_PROP_STATUS: |
367 | ret = rn5t618_battery_status(info, val); |
368 | break; |
369 | case POWER_SUPPLY_PROP_PRESENT: |
370 | ret = rn5t618_battery_present(info, val); |
371 | break; |
372 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
373 | ret = rn5t618_battery_voltage_now(info, val); |
374 | break; |
375 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
376 | ret = rn5t618_battery_current_now(info, val); |
377 | break; |
378 | case POWER_SUPPLY_PROP_CAPACITY: |
379 | ret = rn5t618_battery_capacity(info, val); |
380 | break; |
381 | case POWER_SUPPLY_PROP_TEMP: |
382 | ret = rn5t618_battery_temp(info, val); |
383 | break; |
384 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: |
385 | ret = rn5t618_battery_tte(info, val); |
386 | break; |
387 | case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: |
388 | ret = rn5t618_battery_ttf(info, val); |
389 | break; |
390 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
391 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
392 | break; |
393 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
394 | ret = rn5t618_battery_get_current_limit(info, val); |
395 | break; |
396 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
397 | ret = rn5t618_battery_charge_full(info, val); |
398 | break; |
399 | case POWER_SUPPLY_PROP_CHARGE_NOW: |
400 | ret = rn5t618_battery_charge_now(info, val); |
401 | break; |
402 | default: |
403 | return -EINVAL; |
404 | } |
405 | |
406 | return ret; |
407 | } |
408 | |
409 | static int rn5t618_battery_set_property(struct power_supply *psy, |
410 | enum power_supply_property psp, |
411 | const union power_supply_propval *val) |
412 | { |
413 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
414 | |
415 | switch (psp) { |
416 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
417 | return rn5t618_battery_set_current_limit(info, val); |
418 | default: |
419 | return -EINVAL; |
420 | } |
421 | } |
422 | |
423 | static int rn5t618_battery_property_is_writeable(struct power_supply *psy, |
424 | enum power_supply_property psp) |
425 | { |
426 | switch (psp) { |
427 | case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
428 | return true; |
429 | default: |
430 | return false; |
431 | } |
432 | } |
433 | |
434 | static int rn5t618_adp_get_property(struct power_supply *psy, |
435 | enum power_supply_property psp, |
436 | union power_supply_propval *val) |
437 | { |
438 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
439 | unsigned int chgstate; |
440 | unsigned int regval; |
441 | bool online; |
442 | int ret; |
443 | |
444 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTATE, val: &chgstate); |
445 | if (ret) |
446 | return ret; |
447 | |
448 | online = !!(chgstate & CHG_STATE_ADP_INPUT); |
449 | |
450 | switch (psp) { |
451 | case POWER_SUPPLY_PROP_ONLINE: |
452 | val->intval = online; |
453 | break; |
454 | case POWER_SUPPLY_PROP_STATUS: |
455 | if (!online) { |
456 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
457 | break; |
458 | } |
459 | val->intval = rn5t618_decode_status(status: chgstate); |
460 | if (val->intval != POWER_SUPPLY_STATUS_CHARGING) |
461 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
462 | |
463 | break; |
464 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
465 | ret = regmap_read(map: info->rn5t618->regmap, |
466 | RN5T618_REGISET1, val: ®val); |
467 | if (ret < 0) |
468 | return ret; |
469 | |
470 | val->intval = FROM_CUR_REG(regval); |
471 | break; |
472 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
473 | if (!info->channel_vadp) |
474 | return -ENODATA; |
475 | |
476 | ret = iio_read_channel_processed_scale(chan: info->channel_vadp, val: &val->intval, scale: 1000); |
477 | if (ret < 0) |
478 | return ret; |
479 | |
480 | break; |
481 | default: |
482 | return -EINVAL; |
483 | } |
484 | |
485 | return 0; |
486 | } |
487 | |
488 | static int rn5t618_adp_set_property(struct power_supply *psy, |
489 | enum power_supply_property psp, |
490 | const union power_supply_propval *val) |
491 | { |
492 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
493 | int ret; |
494 | |
495 | switch (psp) { |
496 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
497 | if (val->intval > ADP_MAX_CUR) |
498 | return -EINVAL; |
499 | |
500 | if (val->intval < CHG_MIN_CUR) |
501 | return -EINVAL; |
502 | |
503 | ret = regmap_write(map: info->rn5t618->regmap, RN5T618_REGISET1, |
504 | TO_CUR_REG(val->intval)); |
505 | if (ret < 0) |
506 | return ret; |
507 | |
508 | break; |
509 | default: |
510 | return -EINVAL; |
511 | } |
512 | |
513 | return 0; |
514 | } |
515 | |
516 | static int rn5t618_adp_property_is_writeable(struct power_supply *psy, |
517 | enum power_supply_property psp) |
518 | { |
519 | switch (psp) { |
520 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
521 | return true; |
522 | default: |
523 | return false; |
524 | } |
525 | } |
526 | |
527 | static int rc5t619_usb_get_type(struct rn5t618_power_info *info, |
528 | union power_supply_propval *val) |
529 | { |
530 | unsigned int regval; |
531 | int ret; |
532 | |
533 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_GCHGDET, val: ®val); |
534 | if (ret < 0) |
535 | return ret; |
536 | |
537 | switch (regval & GCHGDET_TYPE_MASK) { |
538 | case GCHGDET_TYPE_SDP: |
539 | val->intval = POWER_SUPPLY_USB_TYPE_SDP; |
540 | break; |
541 | case GCHGDET_TYPE_CDP: |
542 | val->intval = POWER_SUPPLY_USB_TYPE_CDP; |
543 | break; |
544 | case GCHGDET_TYPE_DCP: |
545 | val->intval = POWER_SUPPLY_USB_TYPE_DCP; |
546 | break; |
547 | default: |
548 | val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
549 | } |
550 | |
551 | return 0; |
552 | } |
553 | |
554 | static int rn5t618_usb_get_property(struct power_supply *psy, |
555 | enum power_supply_property psp, |
556 | union power_supply_propval *val) |
557 | { |
558 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
559 | unsigned int chgstate; |
560 | unsigned int regval; |
561 | bool online; |
562 | int ret; |
563 | |
564 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTATE, val: &chgstate); |
565 | if (ret) |
566 | return ret; |
567 | |
568 | online = !!(chgstate & CHG_STATE_USB_INPUT); |
569 | |
570 | switch (psp) { |
571 | case POWER_SUPPLY_PROP_ONLINE: |
572 | val->intval = online; |
573 | break; |
574 | case POWER_SUPPLY_PROP_STATUS: |
575 | if (!online) { |
576 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
577 | break; |
578 | } |
579 | val->intval = rn5t618_decode_status(status: chgstate); |
580 | if (val->intval != POWER_SUPPLY_STATUS_CHARGING) |
581 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
582 | |
583 | break; |
584 | case POWER_SUPPLY_PROP_USB_TYPE: |
585 | if (!online || (info->rn5t618->variant != RC5T619)) |
586 | return -ENODATA; |
587 | |
588 | return rc5t619_usb_get_type(info, val); |
589 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
590 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CHGCTL1, |
591 | val: ®val); |
592 | if (ret < 0) |
593 | return ret; |
594 | |
595 | val->intval = 0; |
596 | if (regval & 2) { |
597 | ret = regmap_read(map: info->rn5t618->regmap, |
598 | RN5T618_REGISET2, |
599 | val: ®val); |
600 | if (ret < 0) |
601 | return ret; |
602 | |
603 | val->intval = FROM_CUR_REG(regval); |
604 | } |
605 | break; |
606 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
607 | if (!info->channel_vusb) |
608 | return -ENODATA; |
609 | |
610 | ret = iio_read_channel_processed_scale(chan: info->channel_vusb, val: &val->intval, scale: 1000); |
611 | if (ret < 0) |
612 | return ret; |
613 | |
614 | break; |
615 | default: |
616 | return -EINVAL; |
617 | } |
618 | |
619 | return 0; |
620 | } |
621 | |
622 | static int rn5t618_usb_set_property(struct power_supply *psy, |
623 | enum power_supply_property psp, |
624 | const union power_supply_propval *val) |
625 | { |
626 | struct rn5t618_power_info *info = power_supply_get_drvdata(psy); |
627 | int ret; |
628 | |
629 | switch (psp) { |
630 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
631 | if (val->intval > USB_MAX_CUR) |
632 | return -EINVAL; |
633 | |
634 | if (val->intval < CHG_MIN_CUR) |
635 | return -EINVAL; |
636 | |
637 | ret = regmap_write(map: info->rn5t618->regmap, RN5T618_REGISET2, |
638 | val: 0xE0 | TO_CUR_REG(val->intval)); |
639 | if (ret < 0) |
640 | return ret; |
641 | |
642 | break; |
643 | default: |
644 | return -EINVAL; |
645 | } |
646 | |
647 | return 0; |
648 | } |
649 | |
650 | static int rn5t618_usb_property_is_writeable(struct power_supply *psy, |
651 | enum power_supply_property psp) |
652 | { |
653 | switch (psp) { |
654 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
655 | return true; |
656 | default: |
657 | return false; |
658 | } |
659 | } |
660 | |
661 | static const struct power_supply_desc rn5t618_battery_desc = { |
662 | .name = "rn5t618-battery" , |
663 | .type = POWER_SUPPLY_TYPE_BATTERY, |
664 | .properties = rn5t618_battery_props, |
665 | .num_properties = ARRAY_SIZE(rn5t618_battery_props), |
666 | .get_property = rn5t618_battery_get_property, |
667 | .set_property = rn5t618_battery_set_property, |
668 | .property_is_writeable = rn5t618_battery_property_is_writeable, |
669 | }; |
670 | |
671 | static const struct power_supply_desc rn5t618_adp_desc = { |
672 | .name = "rn5t618-adp" , |
673 | .type = POWER_SUPPLY_TYPE_MAINS, |
674 | .properties = rn5t618_adp_props, |
675 | .num_properties = ARRAY_SIZE(rn5t618_adp_props), |
676 | .get_property = rn5t618_adp_get_property, |
677 | .set_property = rn5t618_adp_set_property, |
678 | .property_is_writeable = rn5t618_adp_property_is_writeable, |
679 | }; |
680 | |
681 | static const struct power_supply_desc rn5t618_usb_desc = { |
682 | .name = "rn5t618-usb" , |
683 | .type = POWER_SUPPLY_TYPE_USB, |
684 | .usb_types = rn5t618_usb_types, |
685 | .num_usb_types = ARRAY_SIZE(rn5t618_usb_types), |
686 | .properties = rn5t618_usb_props, |
687 | .num_properties = ARRAY_SIZE(rn5t618_usb_props), |
688 | .get_property = rn5t618_usb_get_property, |
689 | .set_property = rn5t618_usb_set_property, |
690 | .property_is_writeable = rn5t618_usb_property_is_writeable, |
691 | }; |
692 | |
693 | static irqreturn_t rn5t618_charger_irq(int irq, void *data) |
694 | { |
695 | struct device *dev = data; |
696 | struct rn5t618_power_info *info = dev_get_drvdata(dev); |
697 | |
698 | unsigned int ctrl, stat1, stat2, err; |
699 | |
700 | regmap_read(map: info->rn5t618->regmap, RN5T618_CHGERR_IRR, val: &err); |
701 | regmap_read(map: info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, val: &ctrl); |
702 | regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, val: &stat1); |
703 | regmap_read(map: info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, val: &stat2); |
704 | |
705 | regmap_write(map: info->rn5t618->regmap, RN5T618_CHGERR_IRR, val: 0); |
706 | regmap_write(map: info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, val: 0); |
707 | regmap_write(map: info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, val: 0); |
708 | regmap_write(map: info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, val: 0); |
709 | |
710 | dev_dbg(dev, "chgerr: %x chgctrl: %x chgstat: %x chgstat2: %x\n" , |
711 | err, ctrl, stat1, stat2); |
712 | |
713 | power_supply_changed(psy: info->usb); |
714 | power_supply_changed(psy: info->adp); |
715 | power_supply_changed(psy: info->battery); |
716 | |
717 | return IRQ_HANDLED; |
718 | } |
719 | |
720 | static int rn5t618_power_probe(struct platform_device *pdev) |
721 | { |
722 | int ret = 0; |
723 | unsigned int v; |
724 | struct power_supply_config psy_cfg = {}; |
725 | struct rn5t618_power_info *info; |
726 | |
727 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
728 | if (!info) |
729 | return -ENOMEM; |
730 | |
731 | info->pdev = pdev; |
732 | info->rn5t618 = dev_get_drvdata(dev: pdev->dev.parent); |
733 | info->irq = -1; |
734 | |
735 | platform_set_drvdata(pdev, data: info); |
736 | |
737 | info->channel_vusb = devm_iio_channel_get(dev: &pdev->dev, consumer_channel: "vusb" ); |
738 | if (IS_ERR(ptr: info->channel_vusb)) { |
739 | if (PTR_ERR(ptr: info->channel_vusb) == -ENODEV) |
740 | return -EPROBE_DEFER; |
741 | return PTR_ERR(ptr: info->channel_vusb); |
742 | } |
743 | |
744 | info->channel_vadp = devm_iio_channel_get(dev: &pdev->dev, consumer_channel: "vadp" ); |
745 | if (IS_ERR(ptr: info->channel_vadp)) { |
746 | if (PTR_ERR(ptr: info->channel_vadp) == -ENODEV) |
747 | return -EPROBE_DEFER; |
748 | return PTR_ERR(ptr: info->channel_vadp); |
749 | } |
750 | |
751 | ret = regmap_read(map: info->rn5t618->regmap, RN5T618_CONTROL, val: &v); |
752 | if (ret) |
753 | return ret; |
754 | |
755 | if (!(v & FG_ENABLE)) { |
756 | /* E.g. the vendor kernels of various Kobo and Tolino Ebook |
757 | * readers disable the fuel gauge on shutdown. If a kernel |
758 | * without fuel gauge support is booted after that, the fuel |
759 | * gauge will get decalibrated. |
760 | */ |
761 | dev_info(&pdev->dev, "Fuel gauge not enabled, enabling now\n" ); |
762 | dev_info(&pdev->dev, "Expect imprecise results\n" ); |
763 | regmap_update_bits(map: info->rn5t618->regmap, RN5T618_CONTROL, |
764 | FG_ENABLE, FG_ENABLE); |
765 | } |
766 | |
767 | psy_cfg.drv_data = info; |
768 | info->battery = devm_power_supply_register(parent: &pdev->dev, |
769 | desc: &rn5t618_battery_desc, |
770 | cfg: &psy_cfg); |
771 | if (IS_ERR(ptr: info->battery)) { |
772 | ret = PTR_ERR(ptr: info->battery); |
773 | dev_err(&pdev->dev, "failed to register battery: %d\n" , ret); |
774 | return ret; |
775 | } |
776 | |
777 | info->adp = devm_power_supply_register(parent: &pdev->dev, |
778 | desc: &rn5t618_adp_desc, |
779 | cfg: &psy_cfg); |
780 | if (IS_ERR(ptr: info->adp)) { |
781 | ret = PTR_ERR(ptr: info->adp); |
782 | dev_err(&pdev->dev, "failed to register adp: %d\n" , ret); |
783 | return ret; |
784 | } |
785 | |
786 | info->usb = devm_power_supply_register(parent: &pdev->dev, |
787 | desc: &rn5t618_usb_desc, |
788 | cfg: &psy_cfg); |
789 | if (IS_ERR(ptr: info->usb)) { |
790 | ret = PTR_ERR(ptr: info->usb); |
791 | dev_err(&pdev->dev, "failed to register usb: %d\n" , ret); |
792 | return ret; |
793 | } |
794 | |
795 | if (info->rn5t618->irq_data) |
796 | info->irq = regmap_irq_get_virq(data: info->rn5t618->irq_data, |
797 | irq: RN5T618_IRQ_CHG); |
798 | |
799 | if (info->irq < 0) |
800 | info->irq = -1; |
801 | else { |
802 | ret = devm_request_threaded_irq(dev: &pdev->dev, irq: info->irq, NULL, |
803 | thread_fn: rn5t618_charger_irq, |
804 | IRQF_ONESHOT, |
805 | devname: "rn5t618_power" , |
806 | dev_id: &pdev->dev); |
807 | |
808 | if (ret < 0) { |
809 | dev_err(&pdev->dev, "request IRQ:%d fail\n" , |
810 | info->irq); |
811 | info->irq = -1; |
812 | } |
813 | } |
814 | |
815 | return 0; |
816 | } |
817 | |
818 | static struct platform_driver rn5t618_power_driver = { |
819 | .driver = { |
820 | .name = "rn5t618-power" , |
821 | }, |
822 | .probe = rn5t618_power_probe, |
823 | }; |
824 | |
825 | module_platform_driver(rn5t618_power_driver); |
826 | MODULE_ALIAS("platform:rn5t618-power" ); |
827 | MODULE_DESCRIPTION("Power supply driver for RICOH RN5T618" ); |
828 | MODULE_LICENSE("GPL" ); |
829 | |