blob: 6b6b1102fda9c4c26df80a617a547e11943c79d8 [file] [log] [blame]
/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
#include <linux/slab.h>
#define BCL_DEV_NAME "battery_current_limit"
#define BCL_NAME_LENGTH 20
/*
* Default BCL poll interval 1000 msec
*/
#define BCL_POLL_INTERVAL 1000
/*
* Mininum BCL poll interval 10 msec
*/
#define MIN_BCL_POLL_INTERVAL 10
#define BATTERY_VOLTAGE_MIN 3400
static const char bcl_type[] = "bcl";
/*
* Battery Current Limit Enable or Not
*/
enum bcl_device_mode {
BCL_DEVICE_DISABLED = 0,
BCL_DEVICE_ENABLED,
};
/*
* Battery Current Limit Iavail Threshold Mode set
*/
enum bcl_iavail_threshold_mode {
BCL_IAVAIL_THRESHOLD_DISABLED = 0,
BCL_IAVAIL_THRESHOLD_ENABLED,
};
/*
* Battery Current Limit Iavail Threshold Mode
*/
enum bcl_iavail_threshold_type {
BCL_IAVAIL_LOW_THRESHOLD_TYPE = 0,
BCL_IAVAIL_HIGH_THRESHOLD_TYPE,
BCL_IAVAIL_THRESHOLD_TYPE_MAX,
};
/**
* BCL control block
*
*/
struct bcl_context {
/* BCL device */
struct device *dev;
/* BCL related config parameter */
/* BCL mode enable or not */
enum bcl_device_mode bcl_mode;
/* BCL Iavail Threshold Activate or Not */
enum bcl_iavail_threshold_mode
bcl_threshold_mode[BCL_IAVAIL_THRESHOLD_TYPE_MAX];
/* BCL Iavail Threshold value in milli Amp */
int bcl_threshold_value_ma[BCL_IAVAIL_THRESHOLD_TYPE_MAX];
/* BCL Type */
char bcl_type[BCL_NAME_LENGTH];
/* BCL poll in msec */
int bcl_poll_interval_msec;
/* BCL realtime value based on poll */
/* BCL realtime vbat in mV*/
int bcl_vbat_mv;
/* BCL realtime rbat in mOhms*/
int bcl_rbat_mohm;
/*BCL realtime iavail in milli Amp*/
int bcl_iavail;
/*BCL vbatt min in mV*/
int bcl_vbat_min;
/* BCL period poll delay work structure */
struct delayed_work bcl_iavail_work;
};
static struct bcl_context *gbcl;
static int bcl_get_battery_voltage(int *vbatt_mv)
{
static struct power_supply *psy;
union power_supply_propval ret = {0,};
if (psy == NULL) {
psy = power_supply_get_by_name("battery");
if (psy == NULL) {
pr_err("failed to get ps battery\n");
return -EINVAL;
}
}
if (psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret))
return -EINVAL;
if (ret.intval <= 0)
return -EINVAL;
*vbatt_mv = ret.intval / 1000;
return 0;
}
static int bcl_get_resistance(int *rbatt_mohm)
{
static struct power_supply *psy;
union power_supply_propval ret = {0,};
if (psy == NULL) {
psy = power_supply_get_by_name("bms");
if (psy == NULL) {
pr_err("failed to get ps bms\n");
return -EINVAL;
}
}
if (psy->get_property(psy, POWER_SUPPLY_PROP_RESISTANCE, &ret))
return -EINVAL;
if (ret.intval <= 0)
return -EINVAL;
*rbatt_mohm = ret.intval / 1000;
return 0;
}
/*
* BCL iavail calculation and trigger notification to user space
* if iavail cross threshold
*/
static void bcl_calculate_iavail_trigger(void)
{
int iavail_ma = 0;
int vbatt_mv;
int rbatt_mohm;
bool threshold_cross = false;
if (!gbcl) {
pr_err("called before initialization\n");
return;
}
if (bcl_get_battery_voltage(&vbatt_mv))
return;
if (bcl_get_resistance(&rbatt_mohm))
return;
iavail_ma = (vbatt_mv - gbcl->bcl_vbat_min) * 1000 / rbatt_mohm;
gbcl->bcl_rbat_mohm = rbatt_mohm;
gbcl->bcl_vbat_mv = vbatt_mv;
gbcl->bcl_iavail = iavail_ma;
pr_debug("iavail %d, vbatt %d rbatt %d\n", iavail_ma, vbatt_mv,
rbatt_mohm);
if ((gbcl->bcl_threshold_mode[BCL_IAVAIL_HIGH_THRESHOLD_TYPE] ==
BCL_IAVAIL_THRESHOLD_ENABLED)
&& (iavail_ma >=
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_HIGH_THRESHOLD_TYPE]))
threshold_cross = true;
else if ((gbcl->bcl_threshold_mode[BCL_IAVAIL_LOW_THRESHOLD_TYPE]
== BCL_IAVAIL_THRESHOLD_ENABLED)
&& (iavail_ma <=
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_LOW_THRESHOLD_TYPE]))
threshold_cross = true;
if (threshold_cross)
sysfs_notify(&gbcl->dev->kobj, NULL, "type");
}
/*
* BCL iavail work
*/
static void bcl_iavail_work(struct work_struct *work)
{
struct bcl_context *bcl = container_of(work,
struct bcl_context, bcl_iavail_work.work);
if (gbcl->bcl_mode == BCL_DEVICE_ENABLED) {
bcl_calculate_iavail_trigger();
/* restart the delay work for caculating imax */
schedule_delayed_work(&bcl->bcl_iavail_work,
msecs_to_jiffies(bcl->bcl_poll_interval_msec));
}
}
/*
* Set BCL mode
*/
static void bcl_mode_set(enum bcl_device_mode mode)
{
if (!gbcl)
return;
if (gbcl->bcl_mode == mode)
return;
if (gbcl->bcl_mode == BCL_DEVICE_DISABLED
&& mode == BCL_DEVICE_ENABLED) {
gbcl->bcl_mode = mode;
bcl_iavail_work(&(gbcl->bcl_iavail_work.work));
return;
} else if (gbcl->bcl_mode == BCL_DEVICE_ENABLED
&& mode == BCL_DEVICE_DISABLED) {
gbcl->bcl_mode = mode;
cancel_delayed_work_sync(&(gbcl->bcl_iavail_work));
return;
}
return;
}
#define show_bcl(name, variable, format) \
static ssize_t \
name##_show(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
if (gbcl) \
return snprintf(buf, PAGE_SIZE, format, gbcl->variable); \
else \
return -EPERM; \
}
show_bcl(type, bcl_type, "%s\n")
show_bcl(vbat, bcl_vbat_mv, "%d\n")
show_bcl(rbat, bcl_rbat_mohm, "%d\n")
show_bcl(iavail, bcl_iavail, "%d\n")
show_bcl(vbat_min, bcl_vbat_min, "%d\n");
show_bcl(poll_interval, bcl_poll_interval_msec, "%d\n")
static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
if (!gbcl)
return -EPERM;
return snprintf(buf, PAGE_SIZE, "%s\n",
gbcl->bcl_mode == BCL_DEVICE_ENABLED ? "enabled"
: "disabled");
}
static ssize_t
mode_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
if (!gbcl)
return -EPERM;
if (!strncmp(buf, "enabled", 7))
bcl_mode_set(BCL_DEVICE_ENABLED);
else if (!strncmp(buf, "disabled", 8))
bcl_mode_set(BCL_DEVICE_DISABLED);
else
return -EINVAL;
return count;
}
static ssize_t
poll_interval_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int value;
if (!gbcl)
return -EPERM;
if (!sscanf(buf, "%d", &value))
return -EINVAL;
if (value < MIN_BCL_POLL_INTERVAL)
return -EINVAL;
gbcl->bcl_poll_interval_msec = value;
return count;
}
static ssize_t vbat_min_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int value;
int ret;
if (!gbcl)
return -EPERM;
ret = kstrtoint(buf, 10, &value);
if (ret || (value < 0)) {
pr_err("Incorrect vbatt min value\n");
return -EINVAL;
}
gbcl->bcl_vbat_min = value;
return count;
}
static ssize_t iavail_low_threshold_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (!gbcl)
return -EPERM;
return snprintf(buf, PAGE_SIZE, "%s\n",
gbcl->bcl_threshold_mode[BCL_IAVAIL_LOW_THRESHOLD_TYPE]
== BCL_IAVAIL_THRESHOLD_ENABLED ? "enabled" : "disabled");
}
static ssize_t iavail_low_threshold_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
if (!gbcl)
return -EPERM;
if (!strncmp(buf, "enabled", 7))
gbcl->bcl_threshold_mode[BCL_IAVAIL_LOW_THRESHOLD_TYPE]
= BCL_IAVAIL_THRESHOLD_ENABLED;
else if (!strncmp(buf, "disabled", 7))
gbcl->bcl_threshold_mode[BCL_IAVAIL_LOW_THRESHOLD_TYPE]
= BCL_IAVAIL_THRESHOLD_DISABLED;
else
return -EINVAL;
return count;
}
static ssize_t iavail_high_threshold_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (!gbcl)
return -EPERM;
return snprintf(buf, PAGE_SIZE, "%s\n",
gbcl->bcl_threshold_mode[BCL_IAVAIL_HIGH_THRESHOLD_TYPE]
== BCL_IAVAIL_THRESHOLD_ENABLED ? "enabled" : "disabled");
}
static ssize_t iavail_high_threshold_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
if (!gbcl)
return -EPERM;
if (!strncmp(buf, "enabled", 7))
gbcl->bcl_threshold_mode[BCL_IAVAIL_HIGH_THRESHOLD_TYPE]
= BCL_IAVAIL_THRESHOLD_ENABLED;
else if (!strncmp(buf, "disabled", 7))
gbcl->bcl_threshold_mode[BCL_IAVAIL_HIGH_THRESHOLD_TYPE]
= BCL_IAVAIL_THRESHOLD_DISABLED;
else
return -EINVAL;
return count;
}
static ssize_t iavail_low_threshold_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (!gbcl)
return -EPERM;
return snprintf(buf, PAGE_SIZE, "%d\n",
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_LOW_THRESHOLD_TYPE]);
}
static ssize_t iavail_low_threshold_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int val;
int ret;
ret = kstrtoint(buf, 10, &val);
if (ret || (val < 0)) {
pr_err("Incorrect available current threshold value\n");
return -EINVAL;
}
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_LOW_THRESHOLD_TYPE] = val;
return count;
}
static ssize_t iavail_high_threshold_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (!gbcl)
return -EPERM;
return snprintf(buf, PAGE_SIZE, "%d\n",
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_HIGH_THRESHOLD_TYPE]);
}
static ssize_t iavail_high_threshold_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int val;
int ret;
ret = kstrtoint(buf, 10, &val);
if (ret || (val < 0)) {
pr_err("Incorrect available current threshold value\n");
return -EINVAL;
}
gbcl->bcl_threshold_value_ma[BCL_IAVAIL_HIGH_THRESHOLD_TYPE] = val;
return count;
}
/*
* BCL device attributes
*/
static struct device_attribute bcl_dev_attr[] = {
__ATTR(type, 0444, type_show, NULL),
__ATTR(iavail, 0444, iavail_show, NULL),
__ATTR(vbat_min, 0644, vbat_min_show, vbat_min_store),
__ATTR(vbat, 0444, vbat_show, NULL),
__ATTR(rbat, 0444, rbat_show, NULL),
__ATTR(mode, 0644, mode_show, mode_store),
__ATTR(poll_interval, 0644,
poll_interval_show, poll_interval_store),
__ATTR(iavail_low_threshold_mode, 0644,
iavail_low_threshold_mode_show,
iavail_low_threshold_mode_store),
__ATTR(iavail_high_threshold_mode, 0644,
iavail_high_threshold_mode_show,
iavail_high_threshold_mode_store),
__ATTR(iavail_low_threshold_value, 0644,
iavail_low_threshold_value_show,
iavail_low_threshold_value_store),
__ATTR(iavail_high_threshold_value, 0644,
iavail_high_threshold_value_show,
iavail_high_threshold_value_store),
};
static int create_bcl_sysfs(struct bcl_context *bcl)
{
int result = 0;
int num_attr = sizeof(bcl_dev_attr)/sizeof(struct device_attribute);
int i;
for (i = 0; i < num_attr; i++) {
result = device_create_file(bcl->dev, &bcl_dev_attr[i]);
if (result < 0)
return result;
}
return 0;
}
static void remove_bcl_sysfs(struct bcl_context *bcl)
{
int num_attr = sizeof(bcl_dev_attr)/sizeof(struct device_attribute);
int i;
for (i = 0; i < num_attr; i++)
device_remove_file(bcl->dev, &bcl_dev_attr[i]);
return;
}
static int __devinit bcl_probe(struct platform_device *pdev)
{
struct bcl_context *bcl;
int ret = 0;
bcl = kzalloc(sizeof(struct bcl_context), GFP_KERNEL);
if (!bcl) {
pr_err("Cannot allocate bcl_context\n");
return -ENOMEM;
}
gbcl = bcl;
/* For BCL */
/* Init default BCL params */
bcl->dev = &pdev->dev;
bcl->bcl_mode = BCL_DEVICE_DISABLED;
bcl->bcl_threshold_mode[BCL_IAVAIL_LOW_THRESHOLD_TYPE] =
BCL_IAVAIL_THRESHOLD_DISABLED;
bcl->bcl_threshold_mode[BCL_IAVAIL_HIGH_THRESHOLD_TYPE] =
BCL_IAVAIL_THRESHOLD_DISABLED;
bcl->bcl_threshold_value_ma[BCL_IAVAIL_LOW_THRESHOLD_TYPE] = 0;
bcl->bcl_threshold_value_ma[BCL_IAVAIL_HIGH_THRESHOLD_TYPE] = 0;
bcl->bcl_vbat_min = BATTERY_VOLTAGE_MIN;
snprintf(bcl->bcl_type, BCL_NAME_LENGTH, "%s", bcl_type);
bcl->bcl_poll_interval_msec = BCL_POLL_INTERVAL;
ret = create_bcl_sysfs(bcl);
if (ret < 0) {
pr_err("Cannot create bcl sysfs\n");
kfree(bcl);
return ret;
}
platform_set_drvdata(pdev, bcl);
INIT_DELAYED_WORK_DEFERRABLE(&bcl->bcl_iavail_work, bcl_iavail_work);
return 0;
}
static int __devexit bcl_remove(struct platform_device *pdev)
{
remove_bcl_sysfs(gbcl);
kfree(gbcl);
gbcl = NULL;
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct of_device_id bcl_match_table[] = {
{.compatible = "qcom,bcl"},
{},
};
static struct platform_driver bcl_driver = {
.probe = bcl_probe,
.remove = __devexit_p(bcl_remove),
.driver = {
.name = BCL_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = bcl_match_table,
},
};
static int __init bcl_init(void)
{
return platform_driver_register(&bcl_driver);
}
static void __exit bcl_exit(void)
{
platform_driver_unregister(&bcl_driver);
}
late_initcall(bcl_init);
module_exit(bcl_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("battery current limit driver");
MODULE_ALIAS("platform:" BCL_DEV_NAME);