/* * Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "sysedp_internal.h" #define UPDATE_INTERVAL 60000 static struct sysedp_batmon_calc_platform_data *pdata; static struct delayed_work work; static struct power_supply *psy; int (*get_ocv)(unsigned int capacity); static struct kobject batmon_kobj; /* ratio between user-space ESR setting and look-up-table based ESR value */ static int user_esr_ratio = 100; static int psy_get_property(enum power_supply_property psp, int *val) { union power_supply_propval pv; if (psy->get_property(psy, psp, &pv)) return -EFAULT; if (val) *val = pv.intval; return 0; } static int psy_ocv_from_chip(unsigned int capacity) { int val; if (psy_get_property(POWER_SUPPLY_PROP_VOLTAGE_OCV, &val)) return pdata->vsys_min; return val; } static int psy_capacity(void) { int val; if (psy_get_property(POWER_SUPPLY_PROP_CAPACITY, &val)) return 0; return val; } static int psy_temp(void) { int val; if (psy_get_property(POWER_SUPPLY_PROP_TEMP, &val)) return 25; return val; } /* Given two points (x1, y1) and (x2, y2), find the y coord of x */ static int interpolate(int x, int x1, int y1, int x2, int y2) { if (x1 == x2) return y1; return (y2 * (x - x1) - y1 * (x - x2)) / (x2 - x1); } /* bi-linearly interpolate from table */ static int bilinear_interpolate(int *array, int *xaxis, int *yaxis, int x_size, int y_size, int x, int y) { s64 r; int d; int yi1, yi2; int xi1, xi2; int q11, q12, q21, q22; int x1, x2, y1, y2; if (x_size <= 0 || y_size <= 0) return 0; if (x_size == 1 && y_size == 1) return array[0]; /* Given that x is within xaxis range, find x1 and x2 that * satisfy x1 >= x >= x2 */ for (xi2 = 1; xi2 < x_size - 1; xi2++) if (x > xaxis[xi2]) break; xi1 = xi2 - 1; xi2 = x_size > 1 ? xi2 : 0; x1 = xaxis[xi1]; x2 = xaxis[xi2]; for (yi2 = 1; yi2 < y_size - 1; yi2++) if (y > yaxis[yi2]) break; yi1 = yi2 - 1; yi2 = y_size > 1 ? yi2 : 0; y1 = yaxis[yi1]; y2 = yaxis[yi2]; if (x_size == 1) return interpolate(y, y1, array[yi1], y2, array[yi2]); if (y_size == 1) return interpolate(x, x1, array[xi1], x2, array[xi2]); q11 = array[xi1 + yi1 * x_size]; q12 = array[xi1 + yi2 * x_size]; q21 = array[xi2 + yi1 * x_size]; q22 = array[xi2 + yi2 * x_size]; r = (s64)q11 * (x2 - x) * (y2 - y); r += (s64)q21 * (x - x1) * (y2 - y); r += (s64)q12 * (x2 - x) * (y - y1); r += (s64)q22 * (x - x1) * (y - y1); d = ((x2-x1)*(y2-y1)); r = d ? div64_s64(r, d) : 0; return r; } static int psy_ocv_from_lut(unsigned int capacity) { struct sysedp_batmon_ocv_lut *p; struct sysedp_batmon_ocv_lut *q; p = pdata->ocv_lut; while (p->capacity > capacity) p++; if (p == pdata->ocv_lut) return p->ocv; q = p - 1; return interpolate(capacity, p->capacity, p->ocv, q->capacity, q->ocv); } static int lookup_esr(int capacity, int temp) { struct sysedp_batmon_rbat_lut *lut = pdata->rbat_lut; int ret = pdata->r_const; ret += bilinear_interpolate(lut->data, lut->temp_axis, lut->capacity_axis, lut->temp_size, lut->capacity_size, temp, capacity); return ret; } static int calc_esr(int capacity, int temp) { int esr; esr = lookup_esr(capacity, temp); esr = esr * user_esr_ratio / 100; return esr; } /* calculate maximum allowed current (in mA) limited by equivalent * series resistance (esr) */ static s64 calc_ibat_esr(s64 ocv, s64 esr) { if (ocv <= pdata->vsys_min) return 0; else if (esr <= 0) return 0; else return div64_s64(1000 * (ocv - pdata->vsys_min), esr); } /* Calc IBAT for a given temperature */ static int calc_ibat(int temp) { struct sysedp_batmon_ibat_lut *p; struct sysedp_batmon_ibat_lut *q; int ibat; p = pdata->ibat_lut; while (p->ibat && p->temp > temp) p++; if (p == pdata->ibat_lut) return p->ibat; q = p - 1; ibat = interpolate(temp, p->temp, p->ibat, q->temp, q->ibat); return ibat > 0 ? ibat : 0; } static s64 calc_pbat(s64 ocv, s64 ibat, s64 esr) { s64 vsys; vsys = ocv - div64_s64(ibat * esr, 1000); return div64_s64(vsys * ibat, 1000000); } static unsigned int calc_avail_budget(void) { int esr, capacity, temp; s64 ocv; s64 ibat_esr; s64 ibat; s64 ibat_max; s64 pbat; capacity = psy_capacity(); temp = psy_temp(); ocv = get_ocv(capacity); esr = calc_esr(capacity, temp); ibat_esr = calc_ibat_esr(ocv, esr); ibat = calc_ibat(temp); ibat_max = min(ibat_esr, ibat); pbat = calc_pbat(ocv, ibat_max, esr); pr_debug("capacity : %u\n", capacity); pr_debug("ocv : %lld\n", ocv); pr_debug("esr : %d\n", esr); pr_debug("ibat_esr : %lld\n", ibat_esr); pr_debug("ibat : %lld\n", ibat); pr_debug("ibat_max : %lld\n", ibat_max); pr_debug("pbat : %lld\n", pbat); return pbat; } static void batmon_update(struct work_struct *work) { unsigned int budget; unsigned int update_interval; budget = calc_avail_budget(); sysedp_set_avail_budget(budget); update_interval = pdata->update_interval ?: UPDATE_INTERVAL; schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(update_interval)); } static void batmon_shutdown(struct platform_device *pdev) { cancel_delayed_work_sync(&work); } static int batmon_suspend(struct platform_device *pdev, pm_message_t state) { batmon_shutdown(pdev); return 0; } static int batmon_resume(struct platform_device *pdev) { schedule_delayed_work(&work, 0); return 0; } static int init_ocv_reader(void) { if (pdata->ocv_lut) get_ocv = psy_ocv_from_lut; else if (!psy_get_property(POWER_SUPPLY_PROP_VOLTAGE_OCV, NULL)) get_ocv = psy_ocv_from_chip; else return -ENODEV; return 0; } #ifdef CONFIG_DEBUG_FS static int rbat_show(struct seq_file *file, void *data) { int t, c, i = 0; struct sysedp_batmon_rbat_lut *lut = pdata->rbat_lut; seq_printf(file, " %8s", "capacity"); for (t = 0; t < lut->temp_size; t++) seq_printf(file, "%8d", lut->temp_axis[t]); seq_puts(file, "\n"); for (c = 0; c < lut->capacity_size; c++) { seq_printf(file, "%8d%%", lut->capacity_axis[c]); for (t = 0; t < lut->temp_size; t++) seq_printf(file, "%8d", lut->data[i++]); seq_puts(file, "\n"); } return 0; } static int ibat_show(struct seq_file *file, void *data) { struct sysedp_batmon_ibat_lut *lut = pdata->ibat_lut; if (lut) { do { seq_printf(file, "%7d %7dmA\n", lut->temp, lut->ibat); } while ((lut++)->ibat); } return 0; } static int ocv_show(struct seq_file *file, void *data) { struct sysedp_batmon_ocv_lut *lut = pdata->ocv_lut; if (lut) { do { seq_printf(file, "%7d%% %7duV\n", lut->capacity, lut->ocv); } while ((lut++)->capacity); } return 0; } static int debug_open(struct inode *inode, struct file *file) { return single_open(file, inode->i_private, NULL); } static const struct file_operations debug_fops = { .open = debug_open, .read = seq_read, }; static void init_debug(void) { struct dentry *dd, *df; if (!sysedp_debugfs_dir) return; dd = debugfs_create_dir("batmon", sysedp_debugfs_dir); WARN_ON(IS_ERR_OR_NULL(dd)); df = debugfs_create_file("rbat", S_IRUGO, dd, rbat_show, &debug_fops); WARN_ON(IS_ERR_OR_NULL(df)); df = debugfs_create_file("ibat", S_IRUGO, dd, ibat_show, &debug_fops); WARN_ON(IS_ERR_OR_NULL(df)); df = debugfs_create_file("ocv", S_IRUGO, dd, ocv_show, &debug_fops); WARN_ON(IS_ERR_OR_NULL(df)); df = debugfs_create_u32("r_const", S_IRUGO, dd, &pdata->r_const); WARN_ON(IS_ERR_OR_NULL(df)); df = debugfs_create_u32("vsys_min", S_IRUGO, dd, &pdata->vsys_min); WARN_ON(IS_ERR_OR_NULL(df)); } #else static inline void init_debug(void) {} #endif static void of_batmon_calc_get_pdata(struct platform_device *pdev, struct sysedp_batmon_calc_platform_data **pdata) { struct device_node *np = pdev->dev.of_node; struct sysedp_batmon_calc_platform_data *obj_ptr; u32 *u32_ptr; const char *c_ptr; const void *ptr; u32 lenp, val; int n; int ret; int i; obj_ptr = devm_kzalloc(&pdev->dev, sizeof(struct sysedp_batmon_calc_platform_data), GFP_KERNEL); if (!obj_ptr) return; ptr = of_get_property(np, "power_supply", &lenp); if (!ptr) { dev_err(&pdev->dev, "Fail to get power_supply\n"); return; } else { obj_ptr->power_supply = devm_kzalloc(&pdev->dev, sizeof(char) * lenp, GFP_KERNEL); if (!obj_ptr->power_supply) return; ret = of_property_read_string(np, "power_supply", &c_ptr); if (ret) { dev_err(&pdev->dev, "Fail to read power_supply\n"); return; } strncpy(obj_ptr->power_supply, c_ptr, lenp); } ret = of_property_read_u32(np, "r_const", &val); if (ret) dev_info(&pdev->dev, "Fail to read r_const\n"); else obj_ptr->r_const = val; ret = of_property_read_u32(np, "vsys_min", &val); if (ret) dev_info(&pdev->dev, "Fail to read vsys_min\n"); else obj_ptr->vsys_min = val; ret = of_property_read_u32(np, "update_interval", &val); if (!ret) obj_ptr->update_interval = val; ptr = of_get_property(np, "ocv_lut", &lenp); if (ptr) { n = lenp / sizeof(u32); if (!n || (n % 2) != 0) return; obj_ptr->ocv_lut = devm_kzalloc(&pdev->dev, sizeof(struct sysedp_batmon_ocv_lut) * n / 2, GFP_KERNEL); if (!obj_ptr->ocv_lut) return; u32_ptr = kzalloc(sizeof(u32) * n, GFP_KERNEL); if (!u32_ptr) return; ret = of_property_read_u32_array(np, "ocv_lut", u32_ptr, n); if (ret) { dev_err(&pdev->dev, "Fail to read ocv_lut\n"); kfree(u32_ptr); return; } for (i = 0; i < n / 2; ++i) { obj_ptr->ocv_lut[i].capacity = u32_ptr[2 * i]; obj_ptr->ocv_lut[i].ocv = u32_ptr[2 * i + 1]; } kfree(u32_ptr); } ptr = of_get_property(np, "ibat_lut", &lenp); if (!ptr) { dev_err(&pdev->dev, "Fail to get ibat_lut\n"); return; } n = lenp / sizeof(u32); if (!n || (n % 2) != 0) return; obj_ptr->ibat_lut = devm_kzalloc(&pdev->dev, sizeof(struct sysedp_batmon_ibat_lut) * n / 2, GFP_KERNEL); if (!obj_ptr->ibat_lut) return; u32_ptr = kzalloc(sizeof(u32) * n, GFP_KERNEL); if (!u32_ptr) return; ret = of_property_read_u32_array(np, "ibat_lut", u32_ptr, n); if (ret) { dev_err(&pdev->dev, "Fail to read ibat_lut\n"); kfree(u32_ptr); return; } for (i = 0; i < n / 2; ++i) { obj_ptr->ibat_lut[i].temp = (s32)u32_ptr[2 * i]; obj_ptr->ibat_lut[i].ibat = u32_ptr[2 * i + 1]; } kfree(u32_ptr); obj_ptr->rbat_lut = devm_kzalloc(&pdev->dev, sizeof(struct sysedp_batmon_rbat_lut), GFP_KERNEL); if (!obj_ptr->rbat_lut) return; ptr = of_get_property(np, "rbat_data", &lenp); if (!ptr) { dev_err(&pdev->dev, "Fail to get rbat_data\n"); return; } n = lenp / sizeof(u32); if (!n) return; obj_ptr->rbat_lut->data = devm_kzalloc(&pdev->dev, sizeof(int) * n, GFP_KERNEL); if (!obj_ptr->rbat_lut->data) return; u32_ptr = kzalloc(sizeof(u32) * n, GFP_KERNEL); if (!u32_ptr) return; ret = of_property_read_u32_array(np, "rbat_data", u32_ptr, n); if (ret) { dev_err(&pdev->dev, "Fail to read rbat_data\n"); kfree(u32_ptr); return; } for (i = 0; i < n; ++i) obj_ptr->rbat_lut->data[i] = u32_ptr[i]; kfree(u32_ptr); obj_ptr->rbat_lut->data_size = n; ptr = of_get_property(np, "temp_axis", &lenp); if (!ptr) { dev_err(&pdev->dev, "Fail to get temp_axis\n"); return; } n = lenp / sizeof(u32); if (!n) return; obj_ptr->rbat_lut->temp_axis = devm_kzalloc(&pdev->dev, sizeof(int) * n, GFP_KERNEL); if (!obj_ptr->rbat_lut->temp_axis) return; u32_ptr = kzalloc(sizeof(u32) * n, GFP_KERNEL); if (!u32_ptr) return; ret = of_property_read_u32_array(np, "temp_axis", u32_ptr, n); if (ret) { dev_err(&pdev->dev, "Fail to read temp_axis\n"); kfree(u32_ptr); return; } for (i = 0; i < n; ++i) obj_ptr->rbat_lut->temp_axis[i] = (s32)u32_ptr[i]; kfree(u32_ptr); obj_ptr->rbat_lut->temp_size = n; ptr = of_get_property(np, "capacity_axis", &lenp); if (!ptr) { dev_err(&pdev->dev, "Fail to get capacity_axis\n"); return; } n = lenp / sizeof(u32); if (!n) return; obj_ptr->rbat_lut->capacity_axis = devm_kzalloc(&pdev->dev, sizeof(int) * n, GFP_KERNEL); if (!obj_ptr->rbat_lut->capacity_axis) return; u32_ptr = kzalloc(sizeof(u32) * n, GFP_KERNEL); if (!u32_ptr) return; ret = of_property_read_u32_array(np, "capacity_axis", u32_ptr, n); if (ret) { dev_err(&pdev->dev, "Fail to read capacity_axis\n"); kfree(u32_ptr); return; } for (i = 0; i < n; ++i) obj_ptr->rbat_lut->capacity_axis[i] = u32_ptr[i]; kfree(u32_ptr); obj_ptr->rbat_lut->capacity_size = n; *pdata = obj_ptr; return; } struct batmon_attribute { struct attribute attr; ssize_t (*show)(char *buf); ssize_t (*store)(const char *buf, size_t count); }; static ssize_t esr_show(char *s) { int capacity, temp, esr; capacity = psy_capacity(); temp = psy_temp(); esr = calc_esr(capacity, temp); esr /= 1000; /* to mOhm */ return sprintf(s, "%d\n", esr); } static ssize_t esr_store(const char *s, size_t count) { int mohm, capacity, temp; int lut_esr; int n; n = sscanf(s, "%d %d %d", &mohm, &capacity, &temp); if (n != 1 && n != 3) return -EINVAL; if (mohm <= 0) return -EINVAL; if (n != 3) { capacity = psy_capacity(); temp = psy_temp(); } else { if (capacity < 0 || capacity > 100) return -EINVAL; } lut_esr = lookup_esr(capacity, temp); if (!lut_esr) return -EINVAL; user_esr_ratio = DIV_ROUND_CLOSEST(100 * 1000 * mohm, lut_esr); cancel_delayed_work_sync(&work); schedule_delayed_work(&work, 0); return count; } static struct batmon_attribute attr_esr = __ATTR(esr, 0660, esr_show, esr_store); static struct attribute *batmon_attrs[] = { &attr_esr.attr, NULL }; static ssize_t batmon_attr_show(struct kobject *kobj, struct attribute *_attr, char *buf) { ssize_t r = -EINVAL; struct batmon_attribute *attr; attr = container_of(_attr, struct batmon_attribute, attr); if (attr && attr->show) r = attr->show(buf); return r; } static ssize_t batmon_attr_store(struct kobject *kobj, struct attribute *_attr, const char *buf, size_t count) { ssize_t r = -EINVAL; struct batmon_attribute *attr; attr = container_of(_attr, struct batmon_attribute, attr); if (attr && attr->store) r = attr->store(buf, count); return r; } static const struct sysfs_ops batmon_sysfs_ops = { .show = batmon_attr_show, .store = batmon_attr_store, }; static struct kobj_type ktype_batmon = { .sysfs_ops = &batmon_sysfs_ops, .default_attrs = batmon_attrs, }; static int init_sysfs(void) { return kobject_init_and_add(&batmon_kobj, &ktype_batmon, &sysedp_kobj, "batmon"); } static int batmon_probe(struct platform_device *pdev) { int i; struct sysedp_batmon_rbat_lut *rbat; if (pdev->dev.of_node) of_batmon_calc_get_pdata(pdev, &pdata); else pdata = pdev->dev.platform_data; if (!pdata) return -EINVAL; /* validate pdata->rbat_lut table */ rbat = pdata->rbat_lut; if (!rbat) return -EINVAL; for (i = 1; i < rbat->temp_size; i++) if (rbat->temp_axis[i] >= rbat->temp_axis[i-1]) return -EINVAL; for (i = 1; i < rbat->capacity_size; i++) if (rbat->capacity_axis[i] >= rbat->capacity_axis[i-1]) return -EINVAL; if (rbat->capacity_size * rbat->temp_size != rbat->data_size) return -EINVAL; psy = power_supply_get_by_name(pdata->power_supply); if (!psy) return -EFAULT; if (init_ocv_reader()) return -EFAULT; init_sysfs(); INIT_DEFERRABLE_WORK(&work, batmon_update); schedule_delayed_work(&work, 0); init_debug(); return 0; } static const struct of_device_id batmon_calc_of_match[] = { { .compatible = "nvidia,tegra124-sysedp_batmon_calc", }, { }, }; MODULE_DEVICE_TABLE(of, batmon_calc_of_match); static struct platform_driver batmon_driver = { .probe = batmon_probe, .shutdown = batmon_shutdown, .suspend = batmon_suspend, .resume = batmon_resume, .driver = { .name = "sysedp_batmon_calc", .owner = THIS_MODULE, .of_match_table = batmon_calc_of_match, } }; static __init int batmon_init(void) { return platform_driver_register(&batmon_driver); } late_initcall(batmon_init);