1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Analog Devices (Linear Technology) LTC4162-L charger IC. |
4 | * Copyright (C) 2020, Topic Embedded Products |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/of.h> |
10 | #include <linux/pm_runtime.h> |
11 | #include <linux/power_supply.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/regmap.h> |
14 | |
15 | /* Registers (names based on what datasheet uses) */ |
16 | #define LTC4162L_EN_LIMIT_ALERTS_REG 0x0D |
17 | #define LTC4162L_EN_CHARGER_STATE_ALERTS_REG 0x0E |
18 | #define LTC4162L_EN_CHARGE_STATUS_ALERTS_REG 0x0F |
19 | #define LTC4162L_CONFIG_BITS_REG 0x14 |
20 | #define LTC4162L_IIN_LIMIT_TARGET 0x15 |
21 | #define LTC4162L_ARM_SHIP_MODE 0x19 |
22 | #define LTC4162L_CHARGE_CURRENT_SETTING 0X1A |
23 | #define LTC4162L_VCHARGE_SETTING 0X1B |
24 | #define LTC4162L_C_OVER_X_THRESHOLD 0x1C |
25 | #define LTC4162L_MAX_CV_TIME 0X1D |
26 | #define LTC4162L_MAX_CHARGE_TIME 0X1E |
27 | #define LTC4162L_CHARGER_CONFIG_BITS 0x29 |
28 | #define LTC4162L_CHARGER_STATE 0x34 |
29 | #define LTC4162L_CHARGE_STATUS 0x35 |
30 | #define LTC4162L_LIMIT_ALERTS_REG 0x36 |
31 | #define LTC4162L_CHARGER_STATE_ALERTS_REG 0x37 |
32 | #define LTC4162L_CHARGE_STATUS_ALERTS_REG 0x38 |
33 | #define LTC4162L_SYSTEM_STATUS_REG 0x39 |
34 | #define LTC4162L_VBAT 0x3A |
35 | #define LTC4162L_VIN 0x3B |
36 | #define LTC4162L_VOUT 0x3C |
37 | #define LTC4162L_IBAT 0x3D |
38 | #define LTC4162L_IIN 0x3E |
39 | #define LTC4162L_DIE_TEMPERATURE 0x3F |
40 | #define LTC4162L_THERMISTOR_VOLTAGE 0x40 |
41 | #define LTC4162L_BSR 0x41 |
42 | #define LTC4162L_JEITA_REGION 0x42 |
43 | #define LTC4162L_CHEM_CELLS_REG 0x43 |
44 | #define LTC4162L_ICHARGE_DAC 0x44 |
45 | #define LTC4162L_VCHARGE_DAC 0x45 |
46 | #define LTC4162L_IIN_LIMIT_DAC 0x46 |
47 | #define LTC4162L_VBAT_FILT 0x47 |
48 | #define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B |
49 | |
50 | /* Enumeration as in datasheet. Individual bits are mutually exclusive. */ |
51 | enum ltc4162l_state { |
52 | battery_detection = 2048, |
53 | charger_suspended = 256, |
54 | precharge = 128, /* trickle on low bat voltage */ |
55 | cc_cv_charge = 64, /* normal charge */ |
56 | ntc_pause = 32, |
57 | timer_term = 16, |
58 | c_over_x_term = 8, /* battery is full */ |
59 | max_charge_time_fault = 4, |
60 | bat_missing_fault = 2, |
61 | bat_short_fault = 1 |
62 | }; |
63 | |
64 | /* Individual bits are mutually exclusive. Only active in charging states.*/ |
65 | enum ltc4162l_charge_status { |
66 | ilim_reg_active = 32, |
67 | thermal_reg_active = 16, |
68 | vin_uvcl_active = 8, |
69 | iin_limit_active = 4, |
70 | constant_current = 2, |
71 | constant_voltage = 1, |
72 | charger_off = 0 |
73 | }; |
74 | |
75 | /* Magic number to write to ARM_SHIP_MODE register */ |
76 | #define LTC4162L_ARM_SHIP_MODE_MAGIC 21325 |
77 | |
78 | struct ltc4162l_info { |
79 | struct i2c_client *client; |
80 | struct regmap *regmap; |
81 | struct power_supply *charger; |
82 | u32 rsnsb; /* Series resistor that sets charge current, microOhm */ |
83 | u32 rsnsi; /* Series resistor to measure input current, microOhm */ |
84 | u8 cell_count; /* Number of connected cells, 0 while unknown */ |
85 | }; |
86 | |
87 | static u8 ltc4162l_get_cell_count(struct ltc4162l_info *info) |
88 | { |
89 | int ret; |
90 | unsigned int val; |
91 | |
92 | /* Once read successfully */ |
93 | if (info->cell_count) |
94 | return info->cell_count; |
95 | |
96 | ret = regmap_read(map: info->regmap, LTC4162L_CHEM_CELLS_REG, val: &val); |
97 | if (ret) |
98 | return 0; |
99 | |
100 | /* Lower 4 bits is the cell count, or 0 if the chip doesn't know yet */ |
101 | val &= 0x0f; |
102 | if (!val) |
103 | return 0; |
104 | |
105 | /* Once determined, keep the value */ |
106 | info->cell_count = val; |
107 | |
108 | return val; |
109 | }; |
110 | |
111 | /* Convert enum value to POWER_SUPPLY_STATUS value */ |
112 | static int ltc4162l_state_decode(enum ltc4162l_state value) |
113 | { |
114 | switch (value) { |
115 | case precharge: |
116 | case cc_cv_charge: |
117 | return POWER_SUPPLY_STATUS_CHARGING; |
118 | case c_over_x_term: |
119 | return POWER_SUPPLY_STATUS_FULL; |
120 | case bat_missing_fault: |
121 | case bat_short_fault: |
122 | return POWER_SUPPLY_STATUS_UNKNOWN; |
123 | default: |
124 | return POWER_SUPPLY_STATUS_NOT_CHARGING; |
125 | } |
126 | }; |
127 | |
128 | static int ltc4162l_get_status(struct ltc4162l_info *info, |
129 | union power_supply_propval *val) |
130 | { |
131 | unsigned int regval; |
132 | int ret; |
133 | |
134 | ret = regmap_read(map: info->regmap, LTC4162L_CHARGER_STATE, val: ®val); |
135 | if (ret) { |
136 | dev_err(&info->client->dev, "Failed to read CHARGER_STATE\n" ); |
137 | return ret; |
138 | } |
139 | |
140 | val->intval = ltc4162l_state_decode(value: regval); |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int ltc4162l_charge_status_decode(enum ltc4162l_charge_status value) |
146 | { |
147 | if (!value) |
148 | return POWER_SUPPLY_CHARGE_TYPE_NONE; |
149 | |
150 | /* constant voltage/current and input_current limit are "fast" modes */ |
151 | if (value <= iin_limit_active) |
152 | return POWER_SUPPLY_CHARGE_TYPE_FAST; |
153 | |
154 | /* Anything that's not fast we'll return as trickle */ |
155 | return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
156 | } |
157 | |
158 | static int ltc4162l_get_charge_type(struct ltc4162l_info *info, |
159 | union power_supply_propval *val) |
160 | { |
161 | unsigned int regval; |
162 | int ret; |
163 | |
164 | ret = regmap_read(map: info->regmap, LTC4162L_CHARGE_STATUS, val: ®val); |
165 | if (ret) |
166 | return ret; |
167 | |
168 | val->intval = ltc4162l_charge_status_decode(value: regval); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int ltc4162l_state_to_health(enum ltc4162l_state value) |
174 | { |
175 | switch (value) { |
176 | case ntc_pause: |
177 | return POWER_SUPPLY_HEALTH_OVERHEAT; |
178 | case timer_term: |
179 | return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; |
180 | case max_charge_time_fault: |
181 | return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; |
182 | case bat_missing_fault: |
183 | return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
184 | case bat_short_fault: |
185 | return POWER_SUPPLY_HEALTH_DEAD; |
186 | default: |
187 | return POWER_SUPPLY_HEALTH_GOOD; |
188 | } |
189 | } |
190 | |
191 | static int ltc4162l_get_health(struct ltc4162l_info *info, |
192 | union power_supply_propval *val) |
193 | { |
194 | unsigned int regval; |
195 | int ret; |
196 | |
197 | ret = regmap_read(map: info->regmap, LTC4162L_CHARGER_STATE, val: ®val); |
198 | if (ret) |
199 | return ret; |
200 | |
201 | val->intval = ltc4162l_state_to_health(value: regval); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static int ltc4162l_get_online(struct ltc4162l_info *info, |
207 | union power_supply_propval *val) |
208 | { |
209 | unsigned int regval; |
210 | int ret; |
211 | |
212 | ret = regmap_read(map: info->regmap, LTC4162L_SYSTEM_STATUS_REG, val: ®val); |
213 | if (ret) |
214 | return ret; |
215 | |
216 | /* BIT(2) indicates if input voltage is sufficient to charge */ |
217 | val->intval = !!(regval & BIT(2)); |
218 | |
219 | return 0; |
220 | } |
221 | |
222 | static int ltc4162l_get_vbat(struct ltc4162l_info *info, |
223 | unsigned int reg, |
224 | union power_supply_propval *val) |
225 | { |
226 | unsigned int regval; |
227 | int ret; |
228 | |
229 | ret = regmap_read(map: info->regmap, reg, val: ®val); |
230 | if (ret) |
231 | return ret; |
232 | |
233 | /* cell_count × 192.4μV/LSB */ |
234 | regval *= 1924; |
235 | regval *= ltc4162l_get_cell_count(info); |
236 | regval /= 10; |
237 | val->intval = regval; |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int ltc4162l_get_ibat(struct ltc4162l_info *info, |
243 | union power_supply_propval *val) |
244 | { |
245 | unsigned int regval; |
246 | int ret; |
247 | |
248 | ret = regmap_read(map: info->regmap, LTC4162L_IBAT, val: ®val); |
249 | if (ret) |
250 | return ret; |
251 | |
252 | /* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */ |
253 | ret = (s16)(regval & 0xFFFF); |
254 | val->intval = 100 * mult_frac(ret, 14660, (int)info->rsnsb); |
255 | |
256 | return 0; |
257 | } |
258 | |
259 | |
260 | static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, |
261 | union power_supply_propval *val) |
262 | { |
263 | unsigned int regval; |
264 | int ret; |
265 | |
266 | ret = regmap_read(map: info->regmap, LTC4162L_VIN, val: ®val); |
267 | if (ret) |
268 | return ret; |
269 | |
270 | /* 1.649mV/LSB */ |
271 | val->intval = regval * 1694; |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static int ltc4162l_get_input_current(struct ltc4162l_info *info, |
277 | union power_supply_propval *val) |
278 | { |
279 | unsigned int regval; |
280 | int ret; |
281 | |
282 | ret = regmap_read(map: info->regmap, LTC4162L_IIN, val: ®val); |
283 | if (ret) |
284 | return ret; |
285 | |
286 | /* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */ |
287 | ret = (s16)(regval & 0xFFFF); |
288 | ret *= 14660; |
289 | ret /= info->rsnsi; |
290 | ret *= 100; |
291 | |
292 | val->intval = ret; |
293 | |
294 | return 0; |
295 | } |
296 | |
297 | static int ltc4162l_get_icharge(struct ltc4162l_info *info, |
298 | unsigned int reg, |
299 | union power_supply_propval *val) |
300 | { |
301 | unsigned int regval; |
302 | int ret; |
303 | |
304 | ret = regmap_read(map: info->regmap, reg, val: ®val); |
305 | if (ret) |
306 | return ret; |
307 | |
308 | regval &= BIT(6) - 1; /* Only the lower 5 bits */ |
309 | |
310 | /* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */ |
311 | ++regval; |
312 | val->intval = 10000u * mult_frac(regval, 100000u, info->rsnsb); |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | static int ltc4162l_set_icharge(struct ltc4162l_info *info, |
318 | unsigned int reg, |
319 | unsigned int value) |
320 | { |
321 | value = mult_frac(value, info->rsnsb, 100000u); |
322 | value /= 10000u; |
323 | |
324 | /* Round to lowest possible */ |
325 | if (value) |
326 | --value; |
327 | |
328 | if (value > 31) |
329 | return -EINVAL; |
330 | |
331 | return regmap_write(map: info->regmap, reg, val: value); |
332 | } |
333 | |
334 | |
335 | static int ltc4162l_get_vcharge(struct ltc4162l_info *info, |
336 | unsigned int reg, |
337 | union power_supply_propval *val) |
338 | { |
339 | unsigned int regval; |
340 | int ret; |
341 | u32 voltage; |
342 | |
343 | ret = regmap_read(map: info->regmap, reg, val: ®val); |
344 | if (ret) |
345 | return ret; |
346 | |
347 | regval &= BIT(6) - 1; /* Only the lower 5 bits */ |
348 | |
349 | /* |
350 | * charge voltage setting can be computed from |
351 | * cell_count × (vcharge_setting × 12.5mV + 3.8125V) |
352 | * where vcharge_setting ranges from 0 to 31 (4.2V max). |
353 | */ |
354 | voltage = 3812500 + (regval * 12500); |
355 | voltage *= ltc4162l_get_cell_count(info); |
356 | val->intval = voltage; |
357 | |
358 | return 0; |
359 | } |
360 | |
361 | static int ltc4162l_set_vcharge(struct ltc4162l_info *info, |
362 | unsigned int reg, |
363 | unsigned int value) |
364 | { |
365 | u8 cell_count = ltc4162l_get_cell_count(info); |
366 | |
367 | if (!cell_count) |
368 | return -EBUSY; /* Not available yet, try again later */ |
369 | |
370 | value /= cell_count; |
371 | |
372 | if (value < 3812500) |
373 | return -EINVAL; |
374 | |
375 | value -= 3812500; |
376 | value /= 12500; |
377 | |
378 | if (value > 31) |
379 | return -EINVAL; |
380 | |
381 | return regmap_write(map: info->regmap, reg, val: value); |
382 | } |
383 | |
384 | static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info, |
385 | union power_supply_propval *val) |
386 | { |
387 | unsigned int regval; |
388 | int ret; |
389 | |
390 | ret = regmap_read(map: info->regmap, LTC4162L_IIN_LIMIT_DAC, val: ®val); |
391 | if (ret) |
392 | return ret; |
393 | |
394 | regval &= BIT(6) - 1; /* Only 6 bits */ |
395 | |
396 | /* (iin_limit_dac + 1) × 500μV / RSNSI */ |
397 | ++regval; |
398 | regval *= 5000000u; |
399 | regval /= info->rsnsi; |
400 | val->intval = 100u * regval; |
401 | |
402 | return 0; |
403 | } |
404 | |
405 | static int ltc4162l_set_iin_limit(struct ltc4162l_info *info, |
406 | unsigned int value) |
407 | { |
408 | unsigned int regval; |
409 | |
410 | regval = mult_frac(value, info->rsnsi, 50000u); |
411 | regval /= 10000u; |
412 | if (regval) |
413 | --regval; |
414 | if (regval > 63) |
415 | regval = 63; |
416 | |
417 | return regmap_write(map: info->regmap, LTC4162L_IIN_LIMIT_TARGET, val: regval); |
418 | } |
419 | |
420 | static int ltc4162l_get_die_temp(struct ltc4162l_info *info, |
421 | union power_supply_propval *val) |
422 | { |
423 | unsigned int regval; |
424 | int ret; |
425 | |
426 | ret = regmap_read(map: info->regmap, LTC4162L_DIE_TEMPERATURE, val: ®val); |
427 | if (ret) |
428 | return ret; |
429 | |
430 | /* die_temp × 0.0215°C/LSB - 264.4°C */ |
431 | ret = (s16)(regval & 0xFFFF); |
432 | ret *= 215; |
433 | ret /= 100; /* Centidegrees scale */ |
434 | ret -= 26440; |
435 | val->intval = ret; |
436 | |
437 | return 0; |
438 | } |
439 | |
440 | static int ltc4162l_get_term_current(struct ltc4162l_info *info, |
441 | union power_supply_propval *val) |
442 | { |
443 | unsigned int regval; |
444 | int ret; |
445 | |
446 | ret = regmap_read(map: info->regmap, LTC4162L_CHARGER_CONFIG_BITS, val: ®val); |
447 | if (ret) |
448 | return ret; |
449 | |
450 | /* Check if C_OVER_X_THRESHOLD is enabled */ |
451 | if (!(regval & BIT(2))) { |
452 | val->intval = 0; |
453 | return 0; |
454 | } |
455 | |
456 | ret = regmap_read(map: info->regmap, LTC4162L_C_OVER_X_THRESHOLD, val: ®val); |
457 | if (ret) |
458 | return ret; |
459 | |
460 | /* 1.466μV / RSNSB amperes/LSB */ |
461 | regval *= 14660u; |
462 | regval /= info->rsnsb; |
463 | val->intval = 100 * regval; |
464 | |
465 | return 0; |
466 | } |
467 | |
468 | static int ltc4162l_set_term_current(struct ltc4162l_info *info, |
469 | unsigned int value) |
470 | { |
471 | int ret; |
472 | unsigned int regval; |
473 | |
474 | if (!value) { |
475 | /* Disable en_c_over_x_term when set to zero */ |
476 | return regmap_update_bits(map: info->regmap, |
477 | LTC4162L_CHARGER_CONFIG_BITS, |
478 | BIT(2), val: 0); |
479 | } |
480 | |
481 | regval = mult_frac(value, info->rsnsb, 14660u); |
482 | regval /= 100u; |
483 | |
484 | ret = regmap_write(map: info->regmap, LTC4162L_C_OVER_X_THRESHOLD, val: regval); |
485 | if (ret) |
486 | return ret; |
487 | |
488 | /* Set en_c_over_x_term after changing the threshold value */ |
489 | return regmap_update_bits(map: info->regmap, LTC4162L_CHARGER_CONFIG_BITS, |
490 | BIT(2), BIT(2)); |
491 | } |
492 | |
493 | /* Custom properties */ |
494 | static const char * const ltc4162l_charge_status_name[] = { |
495 | "ilim_reg_active" , /* 32 */ |
496 | "thermal_reg_active" , |
497 | "vin_uvcl_active" , |
498 | "iin_limit_active" , |
499 | "constant_current" , |
500 | "constant_voltage" , |
501 | "charger_off" /* 0 */ |
502 | }; |
503 | |
504 | static ssize_t charge_status_show(struct device *dev, |
505 | struct device_attribute *attr, char *buf) |
506 | { |
507 | struct power_supply *psy = to_power_supply(dev); |
508 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
509 | const char *result = ltc4162l_charge_status_name[ |
510 | ARRAY_SIZE(ltc4162l_charge_status_name) - 1]; |
511 | unsigned int regval; |
512 | unsigned int mask; |
513 | unsigned int index; |
514 | int ret; |
515 | |
516 | ret = regmap_read(map: info->regmap, LTC4162L_CHARGE_STATUS, val: ®val); |
517 | if (ret) |
518 | return ret; |
519 | |
520 | /* Only one bit is set according to datasheet, let's be safe here */ |
521 | for (mask = 32, index = 0; mask != 0; mask >>= 1, ++index) { |
522 | if (regval & mask) { |
523 | result = ltc4162l_charge_status_name[index]; |
524 | break; |
525 | } |
526 | } |
527 | |
528 | return sysfs_emit(buf, fmt: "%s\n" , result); |
529 | } |
530 | static DEVICE_ATTR_RO(charge_status); |
531 | |
532 | static ssize_t vbat_show(struct device *dev, |
533 | struct device_attribute *attr, char *buf) |
534 | { |
535 | struct power_supply *psy = to_power_supply(dev); |
536 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
537 | union power_supply_propval val; |
538 | int ret; |
539 | |
540 | ret = ltc4162l_get_vbat(info, LTC4162L_VBAT, val: &val); |
541 | if (ret) |
542 | return ret; |
543 | |
544 | return sysfs_emit(buf, fmt: "%d\n" , val.intval); |
545 | } |
546 | static DEVICE_ATTR_RO(vbat); |
547 | |
548 | static ssize_t vbat_avg_show(struct device *dev, |
549 | struct device_attribute *attr, char *buf) |
550 | { |
551 | struct power_supply *psy = to_power_supply(dev); |
552 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
553 | union power_supply_propval val; |
554 | int ret; |
555 | |
556 | ret = ltc4162l_get_vbat(info, LTC4162L_VBAT_FILT, val: &val); |
557 | if (ret) |
558 | return ret; |
559 | |
560 | return sysfs_emit(buf, fmt: "%d\n" , val.intval); |
561 | } |
562 | static DEVICE_ATTR_RO(vbat_avg); |
563 | |
564 | static ssize_t ibat_show(struct device *dev, |
565 | struct device_attribute *attr, char *buf) |
566 | { |
567 | struct power_supply *psy = to_power_supply(dev); |
568 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
569 | union power_supply_propval val; |
570 | int ret; |
571 | |
572 | ret = ltc4162l_get_ibat(info, val: &val); |
573 | if (ret) |
574 | return ret; |
575 | |
576 | return sysfs_emit(buf, fmt: "%d\n" , val.intval); |
577 | } |
578 | static DEVICE_ATTR_RO(ibat); |
579 | |
580 | static ssize_t force_telemetry_show(struct device *dev, |
581 | struct device_attribute *attr, char *buf) |
582 | { |
583 | struct power_supply *psy = to_power_supply(dev); |
584 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
585 | unsigned int regval; |
586 | int ret; |
587 | |
588 | ret = regmap_read(map: info->regmap, LTC4162L_CONFIG_BITS_REG, val: ®val); |
589 | if (ret) |
590 | return ret; |
591 | |
592 | return sysfs_emit(buf, fmt: "%u\n" , regval & BIT(2) ? 1 : 0); |
593 | } |
594 | |
595 | static ssize_t force_telemetry_store(struct device *dev, |
596 | struct device_attribute *attr, |
597 | const char *buf, |
598 | size_t count) |
599 | { |
600 | struct power_supply *psy = to_power_supply(dev); |
601 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
602 | int ret; |
603 | unsigned int value; |
604 | |
605 | ret = kstrtouint(s: buf, base: 0, res: &value); |
606 | if (ret < 0) |
607 | return ret; |
608 | |
609 | ret = regmap_update_bits(map: info->regmap, LTC4162L_CONFIG_BITS_REG, |
610 | BIT(2), val: value ? BIT(2) : 0); |
611 | if (ret < 0) |
612 | return ret; |
613 | |
614 | return count; |
615 | } |
616 | |
617 | static DEVICE_ATTR_RW(force_telemetry); |
618 | |
619 | static ssize_t arm_ship_mode_show(struct device *dev, |
620 | struct device_attribute *attr, char *buf) |
621 | { |
622 | struct power_supply *psy = to_power_supply(dev); |
623 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
624 | unsigned int regval; |
625 | int ret; |
626 | |
627 | ret = regmap_read(map: info->regmap, LTC4162L_ARM_SHIP_MODE, val: ®val); |
628 | if (ret) |
629 | return ret; |
630 | |
631 | return sysfs_emit(buf, fmt: "%u\n" , |
632 | regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0); |
633 | } |
634 | |
635 | static ssize_t arm_ship_mode_store(struct device *dev, |
636 | struct device_attribute *attr, |
637 | const char *buf, |
638 | size_t count) |
639 | { |
640 | struct power_supply *psy = to_power_supply(dev); |
641 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
642 | int ret; |
643 | unsigned int value; |
644 | |
645 | ret = kstrtouint(s: buf, base: 0, res: &value); |
646 | if (ret < 0) |
647 | return ret; |
648 | |
649 | ret = regmap_write(map: info->regmap, LTC4162L_ARM_SHIP_MODE, |
650 | val: value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0); |
651 | if (ret < 0) |
652 | return ret; |
653 | |
654 | return count; |
655 | } |
656 | |
657 | static DEVICE_ATTR_RW(arm_ship_mode); |
658 | |
659 | static struct attribute *ltc4162l_sysfs_entries[] = { |
660 | &dev_attr_charge_status.attr, |
661 | &dev_attr_ibat.attr, |
662 | &dev_attr_vbat.attr, |
663 | &dev_attr_vbat_avg.attr, |
664 | &dev_attr_force_telemetry.attr, |
665 | &dev_attr_arm_ship_mode.attr, |
666 | NULL, |
667 | }; |
668 | |
669 | static const struct attribute_group ltc4162l_attr_group = { |
670 | .name = NULL, /* put in device directory */ |
671 | .attrs = ltc4162l_sysfs_entries, |
672 | }; |
673 | |
674 | static const struct attribute_group *ltc4162l_attr_groups[] = { |
675 | <c4162l_attr_group, |
676 | NULL, |
677 | }; |
678 | |
679 | static int ltc4162l_get_property(struct power_supply *psy, |
680 | enum power_supply_property psp, |
681 | union power_supply_propval *val) |
682 | { |
683 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
684 | |
685 | switch (psp) { |
686 | case POWER_SUPPLY_PROP_STATUS: |
687 | return ltc4162l_get_status(info, val); |
688 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
689 | return ltc4162l_get_charge_type(info, val); |
690 | case POWER_SUPPLY_PROP_HEALTH: |
691 | return ltc4162l_get_health(info, val); |
692 | case POWER_SUPPLY_PROP_ONLINE: |
693 | return ltc4162l_get_online(info, val); |
694 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
695 | return ltc4162l_get_input_voltage(info, val); |
696 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
697 | return ltc4162l_get_input_current(info, val); |
698 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
699 | return ltc4162l_get_icharge(info, |
700 | LTC4162L_ICHARGE_DAC, val); |
701 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
702 | return ltc4162l_get_icharge(info, |
703 | LTC4162L_CHARGE_CURRENT_SETTING, val); |
704 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
705 | return ltc4162l_get_vcharge(info, |
706 | LTC4162L_VCHARGE_DAC, val); |
707 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
708 | return ltc4162l_get_vcharge(info, |
709 | LTC4162L_VCHARGE_SETTING, val); |
710 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
711 | return ltc4162l_get_iin_limit_dac(info, val); |
712 | case POWER_SUPPLY_PROP_TEMP: |
713 | return ltc4162l_get_die_temp(info, val); |
714 | case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
715 | return ltc4162l_get_term_current(info, val); |
716 | default: |
717 | return -EINVAL; |
718 | } |
719 | } |
720 | |
721 | static int ltc4162l_set_property(struct power_supply *psy, |
722 | enum power_supply_property psp, |
723 | const union power_supply_propval *val) |
724 | { |
725 | struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
726 | |
727 | switch (psp) { |
728 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
729 | return ltc4162l_set_icharge(info, |
730 | LTC4162L_CHARGE_CURRENT_SETTING, value: val->intval); |
731 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
732 | return ltc4162l_set_vcharge(info, |
733 | LTC4162L_VCHARGE_SETTING, value: val->intval); |
734 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
735 | return ltc4162l_set_iin_limit(info, value: val->intval); |
736 | case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
737 | return ltc4162l_set_term_current(info, value: val->intval); |
738 | default: |
739 | return -EINVAL; |
740 | } |
741 | } |
742 | |
743 | static int ltc4162l_property_is_writeable(struct power_supply *psy, |
744 | enum power_supply_property psp) |
745 | { |
746 | switch (psp) { |
747 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
748 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
749 | case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
750 | case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
751 | return 1; |
752 | default: |
753 | return 0; |
754 | } |
755 | } |
756 | |
757 | /* Charger power supply property routines */ |
758 | static enum power_supply_property ltc4162l_properties[] = { |
759 | POWER_SUPPLY_PROP_STATUS, |
760 | POWER_SUPPLY_PROP_CHARGE_TYPE, |
761 | POWER_SUPPLY_PROP_HEALTH, |
762 | POWER_SUPPLY_PROP_ONLINE, |
763 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
764 | POWER_SUPPLY_PROP_CURRENT_NOW, |
765 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, |
766 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, |
767 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, |
768 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, |
769 | POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
770 | POWER_SUPPLY_PROP_TEMP, |
771 | POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, |
772 | }; |
773 | |
774 | static const struct power_supply_desc ltc4162l_desc = { |
775 | .name = "ltc4162-l" , |
776 | .type = POWER_SUPPLY_TYPE_MAINS, |
777 | .properties = ltc4162l_properties, |
778 | .num_properties = ARRAY_SIZE(ltc4162l_properties), |
779 | .get_property = ltc4162l_get_property, |
780 | .set_property = ltc4162l_set_property, |
781 | .property_is_writeable = ltc4162l_property_is_writeable, |
782 | }; |
783 | |
784 | static bool ltc4162l_is_writeable_reg(struct device *dev, unsigned int reg) |
785 | { |
786 | /* all registers up to this one are writeable */ |
787 | if (reg <= LTC4162L_CHARGER_CONFIG_BITS) |
788 | return true; |
789 | |
790 | /* The ALERTS registers can be written to clear alerts */ |
791 | if (reg >= LTC4162L_LIMIT_ALERTS_REG && |
792 | reg <= LTC4162L_CHARGE_STATUS_ALERTS_REG) |
793 | return true; |
794 | |
795 | return false; |
796 | } |
797 | |
798 | static bool ltc4162l_is_volatile_reg(struct device *dev, unsigned int reg) |
799 | { |
800 | /* all registers after this one are read-only status registers */ |
801 | return reg > LTC4162L_CHARGER_CONFIG_BITS; |
802 | } |
803 | |
804 | static const struct regmap_config ltc4162l_regmap_config = { |
805 | .reg_bits = 8, |
806 | .val_bits = 16, |
807 | .val_format_endian = REGMAP_ENDIAN_LITTLE, |
808 | .writeable_reg = ltc4162l_is_writeable_reg, |
809 | .volatile_reg = ltc4162l_is_volatile_reg, |
810 | .max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC, |
811 | .cache_type = REGCACHE_RBTREE, |
812 | }; |
813 | |
814 | static void ltc4162l_clear_interrupts(struct ltc4162l_info *info) |
815 | { |
816 | /* Acknowledge interrupt to chip by clearing all events */ |
817 | regmap_write(map: info->regmap, LTC4162L_LIMIT_ALERTS_REG, val: 0); |
818 | regmap_write(map: info->regmap, LTC4162L_CHARGER_STATE_ALERTS_REG, val: 0); |
819 | regmap_write(map: info->regmap, LTC4162L_CHARGE_STATUS_ALERTS_REG, val: 0); |
820 | } |
821 | |
822 | static int ltc4162l_probe(struct i2c_client *client) |
823 | { |
824 | struct i2c_adapter *adapter = client->adapter; |
825 | struct device *dev = &client->dev; |
826 | struct ltc4162l_info *info; |
827 | struct power_supply_config ltc4162l_config = {}; |
828 | u32 value; |
829 | int ret; |
830 | |
831 | if (!i2c_check_functionality(adap: adapter, I2C_FUNC_SMBUS_WORD_DATA)) { |
832 | dev_err(dev, "No support for SMBUS_WORD_DATA\n" ); |
833 | return -ENODEV; |
834 | } |
835 | info = devm_kzalloc(dev, size: sizeof(*info), GFP_KERNEL); |
836 | if (!info) |
837 | return -ENOMEM; |
838 | |
839 | info->client = client; |
840 | i2c_set_clientdata(client, data: info); |
841 | |
842 | info->regmap = devm_regmap_init_i2c(client, <c4162l_regmap_config); |
843 | if (IS_ERR(ptr: info->regmap)) { |
844 | dev_err(dev, "Failed to initialize register map\n" ); |
845 | return PTR_ERR(ptr: info->regmap); |
846 | } |
847 | |
848 | ret = device_property_read_u32(dev, propname: "lltc,rsnsb-micro-ohms" , |
849 | val: &info->rsnsb); |
850 | if (ret) { |
851 | dev_err(dev, "Missing lltc,rsnsb-micro-ohms property\n" ); |
852 | return ret; |
853 | } |
854 | if (!info->rsnsb) |
855 | return -EINVAL; |
856 | |
857 | ret = device_property_read_u32(dev, propname: "lltc,rsnsi-micro-ohms" , |
858 | val: &info->rsnsi); |
859 | if (ret) { |
860 | dev_err(dev, "Missing lltc,rsnsi-micro-ohms property\n" ); |
861 | return ret; |
862 | } |
863 | if (!info->rsnsi) |
864 | return -EINVAL; |
865 | |
866 | if (!device_property_read_u32(dev, propname: "lltc,cell-count" , val: &value)) |
867 | info->cell_count = value; |
868 | |
869 | ltc4162l_config.of_node = dev->of_node; |
870 | ltc4162l_config.drv_data = info; |
871 | ltc4162l_config.attr_grp = ltc4162l_attr_groups; |
872 | |
873 | info->charger = devm_power_supply_register(parent: dev, desc: <c4162l_desc, |
874 | cfg: <c4162l_config); |
875 | if (IS_ERR(ptr: info->charger)) { |
876 | dev_err(dev, "Failed to register charger\n" ); |
877 | return PTR_ERR(ptr: info->charger); |
878 | } |
879 | |
880 | /* Disable the threshold alerts, we're not using them */ |
881 | regmap_write(map: info->regmap, LTC4162L_EN_LIMIT_ALERTS_REG, val: 0); |
882 | |
883 | /* Enable interrupts on all status changes */ |
884 | regmap_write(map: info->regmap, LTC4162L_EN_CHARGER_STATE_ALERTS_REG, |
885 | val: 0x1fff); |
886 | regmap_write(map: info->regmap, LTC4162L_EN_CHARGE_STATUS_ALERTS_REG, val: 0x1f); |
887 | |
888 | ltc4162l_clear_interrupts(info); |
889 | |
890 | return 0; |
891 | } |
892 | |
893 | static void ltc4162l_alert(struct i2c_client *client, |
894 | enum i2c_alert_protocol type, unsigned int flag) |
895 | { |
896 | struct ltc4162l_info *info = i2c_get_clientdata(client); |
897 | |
898 | if (type != I2C_PROTOCOL_SMBUS_ALERT) |
899 | return; |
900 | |
901 | ltc4162l_clear_interrupts(info); |
902 | power_supply_changed(psy: info->charger); |
903 | } |
904 | |
905 | static const struct i2c_device_id ltc4162l_i2c_id_table[] = { |
906 | { "ltc4162-l" , 0 }, |
907 | { }, |
908 | }; |
909 | MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table); |
910 | |
911 | static const struct of_device_id ltc4162l_of_match[] __maybe_unused = { |
912 | { .compatible = "lltc,ltc4162-l" , }, |
913 | { }, |
914 | }; |
915 | MODULE_DEVICE_TABLE(of, ltc4162l_of_match); |
916 | |
917 | static struct i2c_driver ltc4162l_driver = { |
918 | .probe = ltc4162l_probe, |
919 | .alert = ltc4162l_alert, |
920 | .id_table = ltc4162l_i2c_id_table, |
921 | .driver = { |
922 | .name = "ltc4162-l-charger" , |
923 | .of_match_table = of_match_ptr(ltc4162l_of_match), |
924 | }, |
925 | }; |
926 | module_i2c_driver(ltc4162l_driver); |
927 | |
928 | MODULE_LICENSE("GPL" ); |
929 | MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>" ); |
930 | MODULE_DESCRIPTION("LTC4162-L charger driver" ); |
931 | |