qg-battery-profile.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
  4. * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
  5. */
  6. #define pr_fmt(fmt) "QG-K: %s: " fmt, __func__
  7. #include <linux/module.h>
  8. #include <linux/device.h>
  9. #include <linux/cdev.h>
  10. #include <linux/fs.h>
  11. #include <linux/fcntl.h>
  12. #include <linux/kernel.h>
  13. #include <linux/of.h>
  14. #include <linux/slab.h>
  15. #include <linux/uaccess.h>
  16. #include <uapi/linux/qg-profile.h>
  17. #include "qg-battery-profile.h"
  18. #include "qg-profile-lib.h"
  19. #include "qg-defs.h"
  20. struct qg_battery_data {
  21. /* battery-data class node */
  22. dev_t dev_no;
  23. struct class *battery_class;
  24. struct device *battery_device;
  25. struct cdev battery_cdev;
  26. /* profile */
  27. struct device_node *profile_node;
  28. struct profile_table_data profile[TABLE_MAX];
  29. };
  30. struct tables {
  31. int table_index;
  32. char *table_name;
  33. };
  34. static struct tables table[] = {
  35. {TABLE_SOC_OCV1, "qcom,pc-temp-v1-lut"},
  36. {TABLE_SOC_OCV2, "qcom,pc-temp-v2-lut"},
  37. {TABLE_FCC1, "qcom,fcc1-temp-lut"},
  38. {TABLE_FCC2, "qcom,fcc2-temp-lut"},
  39. {TABLE_Z1, "qcom,pc-temp-z1-lut"},
  40. {TABLE_Z2, "qcom,pc-temp-z2-lut"},
  41. {TABLE_Z3, "qcom,pc-temp-z3-lut"},
  42. {TABLE_Z4, "qcom,pc-temp-z4-lut"},
  43. {TABLE_Z5, "qcom,pc-temp-z5-lut"},
  44. {TABLE_Z6, "qcom,pc-temp-z6-lut"},
  45. {TABLE_Y1, "qcom,pc-temp-y1-lut"},
  46. {TABLE_Y2, "qcom,pc-temp-y2-lut"},
  47. {TABLE_Y3, "qcom,pc-temp-y3-lut"},
  48. {TABLE_Y4, "qcom,pc-temp-y4-lut"},
  49. {TABLE_Y5, "qcom,pc-temp-y5-lut"},
  50. {TABLE_Y6, "qcom,pc-temp-y6-lut"},
  51. };
  52. static struct qg_battery_data *the_battery;
  53. static void qg_battery_profile_free(void);
  54. static int qg_battery_data_open(struct inode *inode, struct file *file)
  55. {
  56. struct qg_battery_data *battery = container_of(inode->i_cdev,
  57. struct qg_battery_data, battery_cdev);
  58. file->private_data = battery;
  59. return 0;
  60. }
  61. static long qg_battery_data_ioctl(struct file *file, unsigned int cmd,
  62. unsigned long arg)
  63. {
  64. struct qg_battery_data *battery = file->private_data;
  65. struct battery_params __user *bp_user =
  66. (struct battery_params __user *)arg;
  67. struct battery_params bp;
  68. int rc = 0, soc, ocv_uv, fcc_mah, var, slope;
  69. if (!battery->profile_node) {
  70. pr_err("Battery data not set!\n");
  71. return -EINVAL;
  72. }
  73. if (!bp_user) {
  74. pr_err("Invalid battery-params user pointer\n");
  75. return -EINVAL;
  76. }
  77. if (copy_from_user(&bp, bp_user, sizeof(bp))) {
  78. pr_err("Failed in copy_from_user\n");
  79. return -EFAULT;
  80. }
  81. switch (cmd) {
  82. case BPIOCXSOC:
  83. if (bp.table_index != TABLE_SOC_OCV1 &&
  84. bp.table_index != TABLE_SOC_OCV2) {
  85. pr_err("Invalid table index %d for SOC-OCV lookup\n",
  86. bp.table_index);
  87. rc = -EINVAL;
  88. } else {
  89. /* OCV is passed as deci-uV - 10^-4 V */
  90. soc = qg_interpolate_soc(
  91. &battery->profile[bp.table_index],
  92. bp.batt_temp, UV_TO_DECIUV(bp.ocv_uv));
  93. soc = CAP(QG_MIN_SOC, QG_MAX_SOC, soc);
  94. rc = put_user(soc, &bp_user->soc);
  95. if (rc < 0) {
  96. pr_err("BPIOCXSOC: Failed rc=%d\n", rc);
  97. goto ret_err;
  98. }
  99. pr_debug("BPIOCXSOC: lut=%s ocv=%d batt_temp=%d soc=%d\n",
  100. battery->profile[bp.table_index].name,
  101. bp.ocv_uv, bp.batt_temp, soc);
  102. }
  103. break;
  104. case BPIOCXOCV:
  105. if (bp.table_index != TABLE_SOC_OCV1 &&
  106. bp.table_index != TABLE_SOC_OCV2) {
  107. pr_err("Invalid table index %d for SOC-OCV lookup\n",
  108. bp.table_index);
  109. rc = -EINVAL;
  110. } else {
  111. ocv_uv = qg_interpolate_var(
  112. &battery->profile[bp.table_index],
  113. bp.batt_temp, bp.soc);
  114. ocv_uv = DECIUV_TO_UV(ocv_uv);
  115. ocv_uv = CAP(QG_MIN_OCV_UV, QG_MAX_OCV_UV, ocv_uv);
  116. rc = put_user(ocv_uv, &bp_user->ocv_uv);
  117. if (rc < 0) {
  118. pr_err("BPIOCXOCV: Failed rc=%d\n", rc);
  119. goto ret_err;
  120. }
  121. pr_debug("BPIOCXOCV: lut=%s ocv=%d batt_temp=%d soc=%d\n",
  122. battery->profile[bp.table_index].name,
  123. ocv_uv, bp.batt_temp, bp.soc);
  124. }
  125. break;
  126. case BPIOCXFCC:
  127. if (bp.table_index != TABLE_FCC1 &&
  128. bp.table_index != TABLE_FCC2) {
  129. pr_err("Invalid table index %d for FCC lookup\n",
  130. bp.table_index);
  131. rc = -EINVAL;
  132. } else {
  133. fcc_mah = qg_interpolate_single_row_lut(
  134. &battery->profile[bp.table_index],
  135. bp.batt_temp, DEGC_SCALE);
  136. fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah);
  137. rc = put_user(fcc_mah, &bp_user->fcc_mah);
  138. if (rc) {
  139. pr_err("BPIOCXFCC: Failed rc=%d\n", rc);
  140. goto ret_err;
  141. }
  142. pr_debug("BPIOCXFCC: lut=%s batt_temp=%d fcc_mah=%d\n",
  143. battery->profile[bp.table_index].name,
  144. bp.batt_temp, fcc_mah);
  145. }
  146. break;
  147. case BPIOCXVAR:
  148. if (bp.table_index < TABLE_Z1 || bp.table_index >= TABLE_MAX) {
  149. pr_err("Invalid table index %d for VAR lookup\n",
  150. bp.table_index);
  151. rc = -EINVAL;
  152. } else {
  153. var = qg_interpolate_var(
  154. &battery->profile[bp.table_index],
  155. bp.batt_temp, bp.soc);
  156. var = CAP(QG_MIN_VAR, QG_MAX_VAR, var);
  157. rc = put_user(var, &bp_user->var);
  158. if (rc < 0) {
  159. pr_err("BPIOCXVAR: Failed rc=%d\n", rc);
  160. goto ret_err;
  161. }
  162. pr_debug("BPIOCXVAR: lut=%s var=%d batt_temp=%d soc=%d\n",
  163. battery->profile[bp.table_index].name,
  164. var, bp.batt_temp, bp.soc);
  165. }
  166. break;
  167. case BPIOCXSLOPE:
  168. if (bp.table_index != TABLE_SOC_OCV1 &&
  169. bp.table_index != TABLE_SOC_OCV2) {
  170. pr_err("Invalid table index %d for Slope lookup\n",
  171. bp.table_index);
  172. rc = -EINVAL;
  173. } else {
  174. slope = qg_interpolate_slope(
  175. &battery->profile[bp.table_index],
  176. bp.batt_temp, bp.soc);
  177. slope = CAP(QG_MIN_SLOPE, QG_MAX_SLOPE, slope);
  178. rc = put_user(slope, &bp_user->slope);
  179. if (rc) {
  180. pr_err("BPIOCXSLOPE: Failed rc=%d\n", rc);
  181. goto ret_err;
  182. }
  183. pr_debug("BPIOCXSLOPE: lut=%s soc=%d batt_temp=%d slope=%d\n",
  184. battery->profile[bp.table_index].name,
  185. bp.soc, bp.batt_temp, slope);
  186. }
  187. break;
  188. default:
  189. pr_err("IOCTL %d not supported\n", cmd);
  190. rc = -EINVAL;
  191. }
  192. ret_err:
  193. return rc;
  194. }
  195. static int qg_battery_data_release(struct inode *inode, struct file *file)
  196. {
  197. pr_debug("battery_data device closed\n");
  198. return 0;
  199. }
  200. static const struct file_operations qg_battery_data_fops = {
  201. .owner = THIS_MODULE,
  202. .open = qg_battery_data_open,
  203. .unlocked_ioctl = qg_battery_data_ioctl,
  204. .compat_ioctl = qg_battery_data_ioctl,
  205. .release = qg_battery_data_release,
  206. };
  207. static int get_length(struct device_node *node,
  208. int *length, char *prop_name, bool ignore_null)
  209. {
  210. struct property *prop;
  211. prop = of_find_property(node, prop_name, NULL);
  212. if (!prop) {
  213. if (ignore_null) {
  214. *length = 1;
  215. return 0;
  216. }
  217. pr_err("Failed to find %s property\n", prop_name);
  218. return -ENODATA;
  219. } else if (!prop->value) {
  220. pr_err("Failed to find value for %s property\n", prop_name);
  221. return -ENODATA;
  222. }
  223. *length = prop->length / sizeof(u32);
  224. return 0;
  225. }
  226. static int qg_parse_battery_profile(struct qg_battery_data *battery)
  227. {
  228. int i, j, k, rows = 0, cols = 0, lut_length = 0, rc = 0;
  229. struct device_node *node;
  230. struct property *prop;
  231. const __be32 *data;
  232. for (i = 0; i < TABLE_MAX; i++) {
  233. node = of_find_node_by_name(battery->profile_node,
  234. table[i].table_name);
  235. if (!node) {
  236. pr_err("%s table not found\n", table[i].table_name);
  237. rc = -ENODEV;
  238. goto cleanup;
  239. }
  240. rc = get_length(node, &cols, "qcom,lut-col-legend", false);
  241. if (rc < 0) {
  242. pr_err("Failed to get col-length for %s table rc=%d\n",
  243. table[i].table_name, rc);
  244. goto cleanup;
  245. }
  246. rc = get_length(node, &rows, "qcom,lut-row-legend", true);
  247. if (rc < 0) {
  248. pr_err("Failed to get row-length for %s table rc=%d\n",
  249. table[i].table_name, rc);
  250. goto cleanup;
  251. }
  252. rc = get_length(node, &lut_length, "qcom,lut-data", false);
  253. if (rc < 0) {
  254. pr_err("Failed to get lut-length for %s table rc=%d\n",
  255. table[i].table_name, rc);
  256. goto cleanup;
  257. }
  258. if (lut_length != cols * rows) {
  259. pr_err("Invalid lut-length for %s table\n",
  260. table[i].table_name);
  261. rc = -EINVAL;
  262. goto cleanup;
  263. }
  264. battery->profile[i].name = kzalloc(strlen(table[i].table_name)
  265. + 1, GFP_KERNEL);
  266. if (!battery->profile[i].name) {
  267. rc = -ENOMEM;
  268. goto cleanup;
  269. }
  270. strscpy(battery->profile[i].name, table[i].table_name,
  271. strlen(table[i].table_name));
  272. battery->profile[i].rows = rows;
  273. battery->profile[i].cols = cols;
  274. if (rows != 1) {
  275. battery->profile[i].row_entries = kcalloc(rows,
  276. sizeof(*battery->profile[i].row_entries),
  277. GFP_KERNEL);
  278. if (!battery->profile[i].row_entries) {
  279. rc = -ENOMEM;
  280. goto cleanup;
  281. }
  282. }
  283. battery->profile[i].col_entries = kcalloc(cols,
  284. sizeof(*battery->profile[i].col_entries),
  285. GFP_KERNEL);
  286. if (!battery->profile[i].col_entries) {
  287. rc = -ENOMEM;
  288. goto cleanup;
  289. }
  290. battery->profile[i].data = kcalloc(rows,
  291. sizeof(*battery->profile[i].data), GFP_KERNEL);
  292. if (!battery->profile[i].data) {
  293. rc = -ENOMEM;
  294. goto cleanup;
  295. }
  296. for (j = 0; j < rows; j++) {
  297. battery->profile[i].data[j] = kcalloc(cols,
  298. sizeof(**battery->profile[i].data),
  299. GFP_KERNEL);
  300. if (!battery->profile[i].data[j]) {
  301. rc = -ENOMEM;
  302. goto cleanup;
  303. }
  304. }
  305. /* read profile data */
  306. rc = of_property_read_u32_array(node, "qcom,lut-col-legend",
  307. battery->profile[i].col_entries, cols);
  308. if (rc < 0) {
  309. pr_err("Failed to read cols values for table %s rc=%d\n",
  310. table[i].table_name, rc);
  311. goto cleanup;
  312. }
  313. if (rows != 1) {
  314. rc = of_property_read_u32_array(node,
  315. "qcom,lut-row-legend",
  316. battery->profile[i].row_entries, rows);
  317. if (rc < 0) {
  318. pr_err("Failed to read row values for table %s rc=%d\n",
  319. table[i].table_name, rc);
  320. goto cleanup;
  321. }
  322. }
  323. prop = of_find_property(node, "qcom,lut-data", NULL);
  324. if (!prop) {
  325. pr_err("Failed to find lut-data\n");
  326. rc = -EINVAL;
  327. goto cleanup;
  328. }
  329. data = prop->value;
  330. for (j = 0; j < rows; j++) {
  331. for (k = 0; k < cols; k++)
  332. battery->profile[i].data[j][k] =
  333. be32_to_cpup(data++);
  334. }
  335. pr_debug("Profile table %s parsed rows=%d cols=%d\n",
  336. battery->profile[i].name, battery->profile[i].rows,
  337. battery->profile[i].cols);
  338. }
  339. return 0;
  340. cleanup:
  341. for (; i >= 0; i++) {
  342. kfree(battery->profile[i].name);
  343. kfree(battery->profile[i].row_entries);
  344. kfree(battery->profile[i].col_entries);
  345. for (j = 0; j < battery->profile[i].rows; j++) {
  346. if (battery->profile[i].data)
  347. kfree(battery->profile[i].data[j]);
  348. }
  349. kfree(battery->profile[i].data);
  350. }
  351. return rc;
  352. }
  353. int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging)
  354. {
  355. u8 table_index = charging ? TABLE_SOC_OCV1 : TABLE_SOC_OCV2;
  356. if (!the_battery || !the_battery->profile_node)
  357. return -ENODEV;
  358. *soc = qg_interpolate_soc(&the_battery->profile[table_index],
  359. batt_temp, UV_TO_DECIUV(ocv_uv));
  360. *soc = CAP(0, 100, DIV_ROUND_CLOSEST(*soc, 100));
  361. return 0;
  362. }
  363. int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging)
  364. {
  365. u8 table_index = charging ? TABLE_FCC1 : TABLE_FCC2;
  366. u32 fcc_mah;
  367. if (!the_battery || !the_battery->profile_node)
  368. return -ENODEV;
  369. fcc_mah = qg_interpolate_single_row_lut(
  370. &the_battery->profile[table_index],
  371. batt_temp, DEGC_SCALE);
  372. fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah);
  373. *nom_cap_uah = fcc_mah * 1000;
  374. return 0;
  375. }
  376. int qg_batterydata_init(struct device_node *profile_node)
  377. {
  378. int rc = 0;
  379. struct qg_battery_data *battery;
  380. /*
  381. * If a battery profile is already initialized, free the existing
  382. * profile data and re-allocate and load the new profile. This is
  383. * required for multi-profile load support.
  384. */
  385. if (the_battery) {
  386. battery = the_battery;
  387. battery->profile_node = NULL;
  388. qg_battery_profile_free();
  389. } else {
  390. battery = kzalloc(sizeof(*battery), GFP_KERNEL);
  391. if (!battery)
  392. return -ENOMEM;
  393. /* char device to access battery-profile data */
  394. rc = alloc_chrdev_region(&battery->dev_no, 0, 1,
  395. "qg_battery");
  396. if (rc < 0) {
  397. pr_err("Failed to allocate chrdev rc=%d\n", rc);
  398. goto free_battery;
  399. }
  400. cdev_init(&battery->battery_cdev, &qg_battery_data_fops);
  401. rc = cdev_add(&battery->battery_cdev,
  402. battery->dev_no, 1);
  403. if (rc) {
  404. pr_err("Failed to add battery_cdev rc=%d\n", rc);
  405. goto unregister_chrdev;
  406. }
  407. battery->battery_class = class_create(THIS_MODULE,
  408. "qg_battery");
  409. if (IS_ERR_OR_NULL(battery->battery_class)) {
  410. pr_err("Failed to create qg-battery class\n");
  411. rc = -ENODEV;
  412. goto delete_cdev;
  413. }
  414. battery->battery_device = device_create(
  415. battery->battery_class,
  416. NULL, battery->dev_no,
  417. NULL, "qg_battery");
  418. if (IS_ERR_OR_NULL(battery->battery_device)) {
  419. pr_err("Failed to create battery_device device\n");
  420. rc = -ENODEV;
  421. goto destroy_class;
  422. }
  423. the_battery = battery;
  424. }
  425. battery->profile_node = profile_node;
  426. /* parse the battery profile */
  427. rc = qg_parse_battery_profile(battery);
  428. if (rc < 0) {
  429. pr_err("Failed to parse battery profile rc=%d\n", rc);
  430. goto destroy_device;
  431. }
  432. pr_info("QG Battery-profile loaded\n");
  433. return 0;
  434. destroy_device:
  435. device_destroy(battery->battery_class, battery->dev_no);
  436. destroy_class:
  437. class_destroy(battery->battery_class);
  438. delete_cdev:
  439. cdev_del(&battery->battery_cdev);
  440. unregister_chrdev:
  441. unregister_chrdev_region(battery->dev_no, 1);
  442. free_battery:
  443. kfree(battery);
  444. return rc;
  445. }
  446. static void qg_battery_profile_free(void)
  447. {
  448. int i, j;
  449. /* delete all the battery profile memory */
  450. for (i = 0; i < TABLE_MAX; i++) {
  451. kfree(the_battery->profile[i].name);
  452. kfree(the_battery->profile[i].row_entries);
  453. kfree(the_battery->profile[i].col_entries);
  454. for (j = 0; j < the_battery->profile[i].rows; j++) {
  455. if (the_battery->profile[i].data)
  456. kfree(the_battery->profile[i].data[j]);
  457. }
  458. kfree(the_battery->profile[i].data);
  459. }
  460. }
  461. void qg_batterydata_exit(void)
  462. {
  463. if (the_battery) {
  464. /* unregister the device node */
  465. device_destroy(the_battery->battery_class, the_battery->dev_no);
  466. class_destroy(the_battery->battery_class);
  467. cdev_del(&the_battery->battery_cdev);
  468. unregister_chrdev_region(the_battery->dev_no, 1);
  469. qg_battery_profile_free();
  470. }
  471. kfree(the_battery);
  472. the_battery = NULL;
  473. }