blob: 43417b2a771d3239d70bd5e841c3dc810158e5ff [file] [log] [blame]
Subbaraman Narayanamurthy92264bd2017-03-23 00:52:12 -07001/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
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
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/err.h>
16#include <linux/of.h>
17#include <linux/slab.h>
18#include <linux/module.h>
19#include <linux/types.h>
20#include <linux/batterydata-lib.h>
21#include <linux/power_supply.h>
22
23static int of_batterydata_read_lut(const struct device_node *np,
24 int max_cols, int max_rows, int *ncols, int *nrows,
25 int *col_legend_data, int *row_legend_data,
26 int *lut_data)
27{
28 struct property *prop;
29 const __be32 *data;
30 int cols, rows, size, i, j, *out_values;
31
32 prop = of_find_property(np, "qcom,lut-col-legend", NULL);
33 if (!prop) {
34 pr_err("%s: No col legend found\n", np->name);
35 return -EINVAL;
36 } else if (!prop->value) {
37 pr_err("%s: No col legend value found, np->name\n", np->name);
38 return -ENODATA;
39 } else if (prop->length > max_cols * sizeof(int)) {
40 pr_err("%s: Too many columns\n", np->name);
41 return -EINVAL;
42 }
43
44 cols = prop->length/sizeof(int);
45 *ncols = cols;
46 data = prop->value;
47 for (i = 0; i < cols; i++)
48 *col_legend_data++ = be32_to_cpup(data++);
49
50 rows = 0;
51
52 prop = of_find_property(np, "qcom,lut-row-legend", NULL);
53 if (!prop || row_legend_data == NULL) {
54 /* single row lut */
55 rows = 1;
56 } else if (!prop->value) {
57 pr_err("%s: No row legend value found\n", np->name);
58 return -ENODATA;
59 } else if (prop->length > max_rows * sizeof(int)) {
60 pr_err("%s: Too many rows\n", np->name);
61 return -EINVAL;
62 }
63
64 if (rows != 1) {
65 rows = prop->length/sizeof(int);
66 *nrows = rows;
67 data = prop->value;
68 for (i = 0; i < rows; i++)
69 *row_legend_data++ = be32_to_cpup(data++);
70 }
71
72 prop = of_find_property(np, "qcom,lut-data", NULL);
73 if (!prop) {
74 pr_err("prop 'qcom,lut-data' not found\n");
75 return -EINVAL;
76 }
77 data = prop->value;
78 size = prop->length/sizeof(int);
79 if (size != cols * rows) {
80 pr_err("%s: data size mismatch, %dx%d != %d\n",
81 np->name, cols, rows, size);
82 return -EINVAL;
83 }
84 for (i = 0; i < rows; i++) {
85 out_values = lut_data + (max_cols * i);
86 for (j = 0; j < cols; j++) {
87 *out_values++ = be32_to_cpup(data++);
88 pr_debug("Value = %d\n", *(out_values-1));
89 }
90 }
91
92 return 0;
93}
94
95static int of_batterydata_read_sf_lut(struct device_node *data_node,
96 const char *name, struct sf_lut *lut)
97{
98 struct device_node *node = of_find_node_by_name(data_node, name);
99 int rc;
100
101 if (!lut) {
102 pr_debug("No lut provided, skipping\n");
103 return 0;
104 } else if (!node) {
105 pr_err("Couldn't find %s node.\n", name);
106 return -EINVAL;
107 }
108
109 rc = of_batterydata_read_lut(node, PC_CC_COLS, PC_CC_ROWS,
110 &lut->cols, &lut->rows, lut->row_entries,
111 lut->percent, *lut->sf);
112 if (rc) {
113 pr_err("Failed to read %s node.\n", name);
114 return rc;
115 }
116
117 return 0;
118}
119
120static int of_batterydata_read_pc_temp_ocv_lut(struct device_node *data_node,
121 const char *name, struct pc_temp_ocv_lut *lut)
122{
123 struct device_node *node = of_find_node_by_name(data_node, name);
124 int rc;
125
126 if (!lut) {
127 pr_debug("No lut provided, skipping\n");
128 return 0;
129 } else if (!node) {
130 pr_err("Couldn't find %s node.\n", name);
131 return -EINVAL;
132 }
133 rc = of_batterydata_read_lut(node, PC_TEMP_COLS, PC_TEMP_ROWS,
134 &lut->cols, &lut->rows, lut->temp, lut->percent,
135 *lut->ocv);
136 if (rc) {
137 pr_err("Failed to read %s node.\n", name);
138 return rc;
139 }
140
141 return 0;
142}
143
144static int of_batterydata_read_ibat_temp_acc_lut(struct device_node *data_node,
145 const char *name, struct ibat_temp_acc_lut *lut)
146{
147 struct device_node *node = of_find_node_by_name(data_node, name);
148 int rc;
149
150 if (!lut) {
151 pr_debug("No lut provided, skipping\n");
152 return 0;
153 } else if (!node) {
154 pr_debug("Couldn't find %s node.\n", name);
155 return 0;
156 }
157 rc = of_batterydata_read_lut(node, ACC_TEMP_COLS, ACC_IBAT_ROWS,
158 &lut->cols, &lut->rows, lut->temp, lut->ibat,
159 *lut->acc);
160 if (rc) {
161 pr_err("Failed to read %s node.\n", name);
162 return rc;
163 }
164
165 return 0;
166}
167
168static int of_batterydata_read_single_row_lut(struct device_node *data_node,
169 const char *name, struct single_row_lut *lut)
170{
171 struct device_node *node = of_find_node_by_name(data_node, name);
172 int rc;
173
174 if (!lut) {
175 pr_debug("No lut provided, skipping\n");
176 return 0;
177 } else if (!node) {
178 pr_err("Couldn't find %s node.\n", name);
179 return -EINVAL;
180 }
181
182 rc = of_batterydata_read_lut(node, MAX_SINGLE_LUT_COLS, 1,
183 &lut->cols, NULL, lut->x, NULL, lut->y);
184 if (rc) {
185 pr_err("Failed to read %s node.\n", name);
186 return rc;
187 }
188
189 return 0;
190}
191
192static int of_batterydata_read_batt_id_kohm(const struct device_node *np,
193 const char *propname, struct batt_ids *batt_ids)
194{
195 struct property *prop;
196 const __be32 *data;
197 int num, i, *id_kohm = batt_ids->kohm;
198
199 prop = of_find_property(np, "qcom,batt-id-kohm", NULL);
200 if (!prop) {
201 pr_err("%s: No battery id resistor found\n", np->name);
202 return -EINVAL;
203 } else if (!prop->value) {
204 pr_err("%s: No battery id resistor value found, np->name\n",
205 np->name);
206 return -ENODATA;
207 } else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) {
208 pr_err("%s: Too many battery id resistors\n", np->name);
209 return -EINVAL;
210 }
211
212 num = prop->length/sizeof(__be32);
213 batt_ids->num = num;
214 data = prop->value;
215 for (i = 0; i < num; i++)
216 *id_kohm++ = be32_to_cpup(data++);
217
218 return 0;
219}
220
221#define OF_PROP_READ(property, qpnp_dt_property, node, rc, optional) \
222do { \
223 if (rc) \
224 break; \
225 rc = of_property_read_u32(node, "qcom," qpnp_dt_property, \
226 &property); \
227 \
228 if ((rc == -EINVAL) && optional) { \
229 property = -EINVAL; \
230 rc = 0; \
231 } else if (rc) { \
232 pr_err("Error reading " #qpnp_dt_property \
233 " property rc = %d\n", rc); \
234 } \
235} while (0)
236
237static int of_batterydata_load_battery_data(struct device_node *node,
238 int best_id_kohm,
239 struct bms_battery_data *batt_data)
240{
241 int rc;
242
243 rc = of_batterydata_read_single_row_lut(node, "qcom,fcc-temp-lut",
244 batt_data->fcc_temp_lut);
245 if (rc)
246 return rc;
247
248 rc = of_batterydata_read_pc_temp_ocv_lut(node,
249 "qcom,pc-temp-ocv-lut",
250 batt_data->pc_temp_ocv_lut);
251 if (rc)
252 return rc;
253
254 rc = of_batterydata_read_sf_lut(node, "qcom,rbatt-sf-lut",
255 batt_data->rbatt_sf_lut);
256 if (rc)
257 return rc;
258
259 rc = of_batterydata_read_ibat_temp_acc_lut(node, "qcom,ibat-acc-lut",
260 batt_data->ibat_acc_lut);
261 if (rc)
262 return rc;
263
264 rc = of_property_read_string(node, "qcom,battery-type",
265 &batt_data->battery_type);
266 if (rc) {
267 pr_err("Error reading qcom,battery-type property rc=%d\n", rc);
268 batt_data->battery_type = NULL;
269 return rc;
270 }
271
272 OF_PROP_READ(batt_data->fcc, "fcc-mah", node, rc, false);
273 OF_PROP_READ(batt_data->default_rbatt_mohm,
274 "default-rbatt-mohm", node, rc, false);
275 OF_PROP_READ(batt_data->rbatt_capacitive_mohm,
276 "rbatt-capacitive-mohm", node, rc, false);
277 OF_PROP_READ(batt_data->flat_ocv_threshold_uv,
278 "flat-ocv-threshold-uv", node, rc, true);
279 OF_PROP_READ(batt_data->max_voltage_uv,
280 "max-voltage-uv", node, rc, true);
281 OF_PROP_READ(batt_data->cutoff_uv, "v-cutoff-uv", node, rc, true);
282 OF_PROP_READ(batt_data->iterm_ua, "chg-term-ua", node, rc, true);
283 OF_PROP_READ(batt_data->fastchg_current_ma,
284 "fastchg-current-ma", node, rc, true);
285 OF_PROP_READ(batt_data->fg_cc_cv_threshold_mv,
286 "fg-cc-cv-threshold-mv", node, rc, true);
287
288 batt_data->batt_id_kohm = best_id_kohm;
289
290 return rc;
291}
292
293static int64_t of_batterydata_convert_battery_id_kohm(int batt_id_uv,
294 int rpull_up, int vadc_vdd)
295{
296 int64_t resistor_value_kohm, denom;
297
298 if (batt_id_uv == 0) {
299 /* vadc not correct or batt id line grounded, report 0 kohms */
300 return 0;
301 }
302 /* calculate the battery id resistance reported via ADC */
303 denom = div64_s64(vadc_vdd * 1000000LL, batt_id_uv) - 1000000LL;
304
305 if (denom == 0) {
306 /* batt id connector might be open, return 0 kohms */
307 return 0;
308 }
309 resistor_value_kohm = div64_s64(rpull_up * 1000000LL + denom/2, denom);
310
311 pr_debug("batt id voltage = %d, resistor value = %lld\n",
312 batt_id_uv, resistor_value_kohm);
313
314 return resistor_value_kohm;
315}
316
317struct device_node *of_batterydata_get_best_profile(
318 const struct device_node *batterydata_container_node,
319 int batt_id_kohm, const char *batt_type)
320{
321 struct batt_ids batt_ids;
322 struct device_node *node, *best_node = NULL;
323 const char *battery_type = NULL;
324 int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct,
325 i = 0, rc = 0, limit = 0;
326 bool in_range = false;
327
328 /* read battery id range percentage for best profile */
329 rc = of_property_read_u32(batterydata_container_node,
330 "qcom,batt-id-range-pct", &id_range_pct);
331
332 if (rc) {
333 if (rc == -EINVAL) {
334 id_range_pct = 0;
335 } else {
336 pr_err("failed to read battery id range\n");
337 return ERR_PTR(-ENXIO);
338 }
339 }
340
341 /*
342 * Find the battery data with a battery id resistor closest to this one
343 */
344 for_each_child_of_node(batterydata_container_node, node) {
345 if (batt_type != NULL) {
346 rc = of_property_read_string(node, "qcom,battery-type",
347 &battery_type);
348 if (!rc && strcmp(battery_type, batt_type) == 0) {
349 best_node = node;
350 best_id_kohm = batt_id_kohm;
351 break;
352 }
353 } else {
354 rc = of_batterydata_read_batt_id_kohm(node,
355 "qcom,batt-id-kohm",
356 &batt_ids);
357 if (rc)
358 continue;
359 for (i = 0; i < batt_ids.num; i++) {
360 delta = abs(batt_ids.kohm[i] - batt_id_kohm);
361 limit = (batt_ids.kohm[i] * id_range_pct) / 100;
362 in_range = (delta <= limit);
363 /*
364 * Check if the delta is the lowest one
365 * and also if the limits are in range
366 * before selecting the best node.
367 */
368 if ((delta < best_delta || !best_node)
369 && in_range) {
370 best_node = node;
371 best_delta = delta;
372 best_id_kohm = batt_ids.kohm[i];
373 }
374 }
375 }
376 }
377
378 if (best_node == NULL) {
379 pr_err("No battery data found\n");
380 return best_node;
381 }
382
383 /* check that profile id is in range of the measured batt_id */
384 if (abs(best_id_kohm - batt_id_kohm) >
385 ((best_id_kohm * id_range_pct) / 100)) {
386 pr_err("out of range: profile id %d batt id %d pct %d",
387 best_id_kohm, batt_id_kohm, id_range_pct);
388 return NULL;
389 }
390
391 rc = of_property_read_string(best_node, "qcom,battery-type",
392 &battery_type);
393 if (!rc)
394 pr_info("%s found\n", battery_type);
395 else
396 pr_info("%s found\n", best_node->name);
397
398 return best_node;
399}
400
401int of_batterydata_read_data(struct device_node *batterydata_container_node,
402 struct bms_battery_data *batt_data,
403 int batt_id_uv)
404{
405 struct device_node *node, *best_node;
406 struct batt_ids batt_ids;
407 const char *battery_type = NULL;
408 int delta, best_delta, batt_id_kohm, rpull_up_kohm,
409 vadc_vdd_uv, best_id_kohm, i, rc = 0;
410
411 node = batterydata_container_node;
412 OF_PROP_READ(rpull_up_kohm, "rpull-up-kohm", node, rc, false);
413 OF_PROP_READ(vadc_vdd_uv, "vref-batt-therm", node, rc, false);
414 if (rc)
415 return rc;
416
417 batt_id_kohm = of_batterydata_convert_battery_id_kohm(batt_id_uv,
418 rpull_up_kohm, vadc_vdd_uv);
419 best_node = NULL;
420 best_delta = 0;
421 best_id_kohm = 0;
422
423 /*
424 * Find the battery data with a battery id resistor closest to this one
425 */
426 for_each_child_of_node(batterydata_container_node, node) {
427 rc = of_batterydata_read_batt_id_kohm(node,
428 "qcom,batt-id-kohm",
429 &batt_ids);
430 if (rc)
431 continue;
432 for (i = 0; i < batt_ids.num; i++) {
433 delta = abs(batt_ids.kohm[i] - batt_id_kohm);
434 if (delta < best_delta || !best_node) {
435 best_node = node;
436 best_delta = delta;
437 best_id_kohm = batt_ids.kohm[i];
438 }
439 }
440 }
441
442 if (best_node == NULL) {
443 pr_err("No battery data found\n");
444 return -ENODATA;
445 }
446 rc = of_property_read_string(best_node, "qcom,battery-type",
447 &battery_type);
448 if (!rc)
449 pr_info("%s loaded\n", battery_type);
450 else
451 pr_info("%s loaded\n", best_node->name);
452
453 return of_batterydata_load_battery_data(best_node,
454 best_id_kohm, batt_data);
455}
456
457MODULE_LICENSE("GPL v2");