blob: 5aa21836cef1608cfc54dc119f162772333d227e [file] [log] [blame]
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <linux/sysedp.h>
#include <linux/edpdev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/workqueue.h>
#include <linux/suspend.h>
#include <linux/debugfs.h>
#include <linux/of.h>
#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);