blob: 2061408fe0f17ea4b480633d9c6ede97c38123b8 [file] [log] [blame]
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -07001/* Copyright (c) 2013, 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/of.h>
16#include <linux/slab.h>
17#include <linux/module.h>
18#include <linux/types.h>
19#include <linux/batterydata-lib.h>
20
21static int of_batterydata_read_lut(const struct device_node *np,
22 int max_cols, int max_rows, int *ncols, int *nrows,
23 int *col_legend_data, int *row_legend_data,
24 int *lut_data)
25{
26 struct property *prop;
27 const __be32 *data;
28 int cols, rows, size, i, j, *out_values;
29
30 prop = of_find_property(np, "qcom,lut-col-legend", NULL);
31 if (!prop) {
32 pr_err("%s: No col legend found\n", np->name);
33 return -EINVAL;
34 } else if (!prop->value) {
35 pr_err("%s: No col legend value found, np->name\n", np->name);
36 return -ENODATA;
37 } else if (prop->length > max_cols * sizeof(int)) {
38 pr_err("%s: Too many columns\n", np->name);
39 return -EINVAL;
40 }
41
42 cols = prop->length/sizeof(int);
43 *ncols = cols;
44 data = prop->value;
45 for (i = 0; i < cols; i++)
46 *col_legend_data++ = be32_to_cpup(data++);
47
48 prop = of_find_property(np, "qcom,lut-row-legend", NULL);
49 if (!prop || row_legend_data == NULL) {
50 /* single row lut */
51 rows = 1;
52 } else if (!prop->value) {
53 pr_err("%s: No row legend value found\n", np->name);
54 return -ENODATA;
55 } else if (prop->length > max_rows * sizeof(int)) {
56 pr_err("%s: Too many rows\n", np->name);
57 return -EINVAL;
58 } else {
59 rows = prop->length/sizeof(int);
60 *nrows = rows;
61 data = prop->value;
62 for (i = 0; i < rows; i++)
63 *row_legend_data++ = be32_to_cpup(data++);
64 }
65
66 prop = of_find_property(np, "qcom,lut-data", NULL);
67 data = prop->value;
68 size = prop->length/sizeof(int);
69 if (!prop || size != cols * rows) {
70 pr_err("%s: data size mismatch, %dx%d != %d\n",
71 np->name, cols, rows, size);
72 return -EINVAL;
73 }
74 for (i = 0; i < rows; i++) {
75 out_values = lut_data + (max_cols * i);
76 for (j = 0; j < cols; j++) {
77 *out_values++ = be32_to_cpup(data++);
78 pr_debug("Value = %d\n", *(out_values-1));
79 }
80 }
81
82 return 0;
83}
84
85static int of_batterydata_read_sf_lut(struct device_node *data_node,
86 const char *name, struct sf_lut *lut)
87{
88 struct device_node *node = of_find_node_by_name(data_node, name);
89 int rc;
90
91 if (!lut) {
92 pr_debug("No lut provided, skipping\n");
93 return 0;
94 } else if (!node) {
95 pr_err("Couldn't find %s node.\n", name);
96 return -EINVAL;
97 }
98
99 rc = of_batterydata_read_lut(node, PC_CC_COLS, PC_CC_ROWS,
100 &lut->cols, &lut->rows, lut->row_entries,
101 lut->percent, *lut->sf);
102 if (rc) {
103 pr_err("Failed to read %s node.\n", name);
104 return rc;
105 }
106
107 return 0;
108}
109
110static int of_batterydata_read_pc_temp_ocv_lut(struct device_node *data_node,
111 const char *name, struct pc_temp_ocv_lut *lut)
112{
113 struct device_node *node = of_find_node_by_name(data_node, name);
114 int rc;
115
116 if (!lut) {
117 pr_debug("No lut provided, skipping\n");
118 return 0;
119 } else if (!node) {
120 pr_err("Couldn't find %s node.\n", name);
121 return -EINVAL;
122 }
123 rc = of_batterydata_read_lut(node, PC_TEMP_COLS, PC_TEMP_ROWS,
124 &lut->cols, &lut->rows, lut->temp, lut->percent,
125 *lut->ocv);
126 if (rc) {
127 pr_err("Failed to read %s node.\n", name);
128 return rc;
129 }
130
131 return 0;
132}
133
134static int of_batterydata_read_single_row_lut(struct device_node *data_node,
135 const char *name, struct single_row_lut *lut)
136{
137 struct device_node *node = of_find_node_by_name(data_node, name);
138 int rc;
139
140 if (!lut) {
141 pr_debug("No lut provided, skipping\n");
142 return 0;
143 } else if (!node) {
144 pr_err("Couldn't find %s node.\n", name);
145 return -EINVAL;
146 }
147
148 rc = of_batterydata_read_lut(node, MAX_SINGLE_LUT_COLS, 1,
149 &lut->cols, NULL, lut->x, NULL, lut->y);
150 if (rc) {
151 pr_err("Failed to read %s node.\n", name);
152 return rc;
153 }
154
155 return 0;
156}
157
Xu Kai32c6bd02013-08-20 18:03:46 +0800158static int of_batterydata_read_batt_id_kohm(const struct device_node *np,
159 const char *propname, struct batt_ids *batt_ids)
160{
161 struct property *prop;
162 const __be32 *data;
163 int num, i, *id_kohm = batt_ids->kohm;
164
165 prop = of_find_property(np, "qcom,batt-id-kohm", NULL);
166 if (!prop) {
167 pr_err("%s: No battery id resistor found\n", np->name);
168 return -EINVAL;
169 } else if (!prop->value) {
170 pr_err("%s: No battery id resistor value found, np->name\n",
171 np->name);
172 return -ENODATA;
173 } else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) {
174 pr_err("%s: Too many battery id resistors\n", np->name);
175 return -EINVAL;
176 }
177
178 num = prop->length/sizeof(__be32);
179 batt_ids->num = num;
180 data = prop->value;
181 for (i = 0; i < num; i++)
182 *id_kohm++ = be32_to_cpup(data++);
183
184 return 0;
185}
186
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700187#define OF_PROP_READ(property, qpnp_dt_property, node, rc, optional) \
188do { \
Xiaozhe Shi4f3bc802013-07-22 14:58:26 -0700189 if (rc) \
190 break; \
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700191 rc = of_property_read_u32(node, "qcom," qpnp_dt_property, \
192 &property); \
193 \
194 if ((rc == -EINVAL) && optional) { \
195 property = -EINVAL; \
196 rc = 0; \
197 } else if (rc) { \
198 pr_err("Error reading " #qpnp_dt_property \
199 " property rc = %d\n", rc); \
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700200 } \
201} while (0)
202
203static int of_batterydata_load_battery_data(struct device_node *node,
Xu Kai32c6bd02013-08-20 18:03:46 +0800204 int best_id_kohm,
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700205 struct bms_battery_data *batt_data)
206{
207 int rc;
208
209 rc = of_batterydata_read_single_row_lut(node, "qcom,fcc-temp-lut",
210 batt_data->fcc_temp_lut);
211 if (rc)
212 return rc;
213
214 rc = of_batterydata_read_pc_temp_ocv_lut(node,
215 "qcom,pc-temp-ocv-lut",
216 batt_data->pc_temp_ocv_lut);
217 if (rc)
218 return rc;
219
220 rc = of_batterydata_read_sf_lut(node, "qcom,rbatt-sf-lut",
221 batt_data->rbatt_sf_lut);
222 if (rc)
223 return rc;
224
225 OF_PROP_READ(batt_data->fcc, "fcc-mah", node, rc, false);
226 OF_PROP_READ(batt_data->default_rbatt_mohm,
227 "default-rbatt-mohm", node, rc, false);
228 OF_PROP_READ(batt_data->rbatt_capacitive_mohm,
229 "rbatt-capacitive-mohm", node, rc, false);
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700230 OF_PROP_READ(batt_data->flat_ocv_threshold_uv,
231 "flat-ocv-threshold", node, rc, true);
232 OF_PROP_READ(batt_data->max_voltage_uv,
233 "max-voltage-uv", node, rc, true);
234 OF_PROP_READ(batt_data->cutoff_uv, "v-cutoff-uv", node, rc, true);
235 OF_PROP_READ(batt_data->iterm_ua, "chg-term-ua", node, rc, true);
236
Xu Kai32c6bd02013-08-20 18:03:46 +0800237 batt_data->batt_id_kohm = best_id_kohm;
238
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700239 return rc;
240}
241
242static int64_t of_batterydata_convert_battery_id_kohm(int batt_id_uv,
243 int rpull_up, int vadc_vdd)
244{
245 int64_t resistor_value_kohm, denom;
246
247 /* calculate the battery id resistance reported via ADC */
248 denom = div64_s64(vadc_vdd * 1000000LL, batt_id_uv) - 1000000LL;
249
250 resistor_value_kohm = div64_s64(rpull_up * 1000000LL + denom/2, denom);
251
252 pr_debug("batt id voltage = %d, resistor value = %lld\n",
253 batt_id_uv, resistor_value_kohm);
254
255 return resistor_value_kohm;
256}
257
258int of_batterydata_read_data(struct device_node *batterydata_container_node,
259 struct bms_battery_data *batt_data,
260 int batt_id_uv)
261{
262 struct device_node *node, *best_node;
Xu Kai32c6bd02013-08-20 18:03:46 +0800263 struct batt_ids batt_ids;
264 int delta, best_delta, batt_id_kohm, rpull_up_kohm,
265 vadc_vdd_uv, best_id_kohm, i, rc = 0;
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700266
267 node = batterydata_container_node;
268 OF_PROP_READ(rpull_up_kohm, "rpull-up-kohm", node, rc, false);
269 OF_PROP_READ(vadc_vdd_uv, "vref-batt-therm", node, rc, false);
Xiaozhe Shi4f3bc802013-07-22 14:58:26 -0700270 if (rc)
271 return rc;
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700272
273 batt_id_kohm = of_batterydata_convert_battery_id_kohm(batt_id_uv,
274 rpull_up_kohm, vadc_vdd_uv);
275 best_node = NULL;
276 best_delta = 0;
Xu Kai32c6bd02013-08-20 18:03:46 +0800277 best_id_kohm = 0;
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700278
279 /*
280 * Find the battery data with a battery id resistor closest to this one
281 */
282 for_each_child_of_node(batterydata_container_node, node) {
Xu Kai32c6bd02013-08-20 18:03:46 +0800283 rc = of_batterydata_read_batt_id_kohm(node,
284 "qcom,batt-id-kohm",
285 &batt_ids);
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700286 if (rc)
287 continue;
Xu Kai32c6bd02013-08-20 18:03:46 +0800288 for (i = 0; i < batt_ids.num; i++) {
289 delta = abs(batt_ids.kohm[i] - batt_id_kohm);
290 if (delta < best_delta || !best_node) {
291 best_node = node;
292 best_delta = delta;
293 best_id_kohm = batt_ids.kohm[i];
294 }
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700295 }
296 }
297
298 if (best_node == NULL) {
299 pr_err("No battery data found\n");
300 return -ENODATA;
301 }
302
Xu Kai32c6bd02013-08-20 18:03:46 +0800303 return of_batterydata_load_battery_data(best_node,
304 best_id_kohm, batt_data);
Xiaozhe Shi7ef86d92013-06-13 10:58:21 -0700305}
306
307MODULE_LICENSE("GPL v2");