blob: 1057aaf4f550be11a36f0a7da3b2e3c91eacb989 [file] [log] [blame]
/*
* sec_fuelgauge.c
* Samsung Mobile Fuel Gauge Driver
*
* Copyright (C) 2012 Samsung Electronics
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define DEBUG
#include <linux/battery/sec_fuelgauge.h>
#include <linux/of_gpio.h>
static struct device_attribute sec_fg_attrs[] = {
SEC_FG_ATTR(reg),
SEC_FG_ATTR(data),
SEC_FG_ATTR(regs),
};
static enum power_supply_property sec_fuelgauge_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
};
/* capacity is 0.1% unit */
static void sec_fg_get_scaled_capacity(
struct sec_fuelgauge_info *fuelgauge,
union power_supply_propval *val)
{
val->intval = (val->intval < fuelgauge->pdata->capacity_min) ?
0 : ((val->intval - fuelgauge->pdata->capacity_min) * 1000 /
(fuelgauge->capacity_max - fuelgauge->pdata->capacity_min));
dev_dbg(&fuelgauge->client->dev,
"%s: scaled capacity (%d.%d)\n",
__func__, val->intval/10, val->intval%10);
}
/* capacity is integer */
static void sec_fg_get_atomic_capacity(
struct sec_fuelgauge_info *fuelgauge,
union power_supply_propval *val)
{
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) {
if (fuelgauge->capacity_old < val->intval)
val->intval = fuelgauge->capacity_old + 1;
else if (fuelgauge->capacity_old > val->intval)
val->intval = fuelgauge->capacity_old - 1;
}
/* keep SOC stable in abnormal status */
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) {
if (!fuelgauge->is_charging &&
fuelgauge->capacity_old < val->intval) {
dev_err(&fuelgauge->client->dev,
"%s: capacity (old %d : new %d)\n",
__func__, fuelgauge->capacity_old, val->intval);
val->intval = fuelgauge->capacity_old;
}
}
/* updated old capacity */
fuelgauge->capacity_old = val->intval;
}
static int sec_fg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
int soc_type = val->intval;
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_ENERGY_NOW:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
if (!sec_hal_fg_get_property(fuelgauge->client, psp, val))
return -EINVAL;
if (psp == POWER_SUPPLY_PROP_CAPACITY) {
if (soc_type == SEC_FUELGAUGE_CAPACITY_TYPE_RAW)
break;
if (fuelgauge->pdata->capacity_calculation_type &
(SEC_FUELGAUGE_CAPACITY_TYPE_SCALE |
SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE))
sec_fg_get_scaled_capacity(fuelgauge, val);
/* capacity should be between 0% and 100%
* (0.1% degree)
*/
if (val->intval > 1000)
val->intval = 1000;
if (val->intval < 0)
val->intval = 0;
/* get only integer part */
val->intval /= 10;
/* check whether doing the wake_unlock */
if ((val->intval > fuelgauge->pdata->fuel_alert_soc) &&
fuelgauge->is_fuel_alerted) {
wake_unlock(&fuelgauge->fuel_alert_wake_lock);
sec_hal_fg_fuelalert_init(fuelgauge->client,
fuelgauge->pdata->fuel_alert_soc);
}
/* (Only for atomic capacity)
* In initial time, capacity_old is 0.
* and in resume from sleep,
* capacity_old is too different from actual soc.
* should update capacity_old
* by val->intval in booting or resume.
*/
if (fuelgauge->initial_update_of_soc) {
/* updated old capacity */
fuelgauge->capacity_old = val->intval;
fuelgauge->initial_update_of_soc = false;
break;
}
if (fuelgauge->pdata->capacity_calculation_type &
(SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC |
SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL))
sec_fg_get_atomic_capacity(fuelgauge, val);
}
break;
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CHARGE_FULL:
return -ENODATA;
default:
return -EINVAL;
}
return 0;
}
static int sec_fg_calculate_dynamic_scale(
struct sec_fuelgauge_info *fuelgauge)
{
union power_supply_propval raw_soc_val;
raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW;
if (!sec_hal_fg_get_property(fuelgauge->client,
POWER_SUPPLY_PROP_CAPACITY,
&raw_soc_val))
return -EINVAL;
raw_soc_val.intval /= 10;
if (raw_soc_val.intval <
fuelgauge->pdata->capacity_max -
fuelgauge->pdata->capacity_max_margin) {
fuelgauge->capacity_max =
fuelgauge->pdata->capacity_max -
fuelgauge->pdata->capacity_max_margin;
dev_dbg(&fuelgauge->client->dev, "%s: capacity_max (%d)",
__func__, fuelgauge->capacity_max);
} else {
fuelgauge->capacity_max =
(raw_soc_val.intval >
fuelgauge->pdata->capacity_max +
fuelgauge->pdata->capacity_max_margin) ?
(fuelgauge->pdata->capacity_max +
fuelgauge->pdata->capacity_max_margin) :
raw_soc_val.intval;
dev_dbg(&fuelgauge->client->dev, "%s: raw soc (%d)",
__func__, fuelgauge->capacity_max);
}
fuelgauge->capacity_max =
(fuelgauge->capacity_max * 99 / 100);
/* update capacity_old for sec_fg_get_atomic_capacity algorithm */
fuelgauge->capacity_old = 100;
dev_info(&fuelgauge->client->dev, "%s: %d is used for capacity_max\n",
__func__, fuelgauge->capacity_max);
return fuelgauge->capacity_max;
}
static int sec_fg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (val->intval == POWER_SUPPLY_STATUS_FULL)
sec_hal_fg_full_charged(fuelgauge->client);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (val->intval == POWER_SUPPLY_TYPE_BATTERY) {
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)
sec_fg_calculate_dynamic_scale(fuelgauge);
}
break;
case POWER_SUPPLY_PROP_ONLINE:
fuelgauge->cable_type = val->intval;
if (val->intval == POWER_SUPPLY_TYPE_BATTERY)
fuelgauge->is_charging = false;
else
fuelgauge->is_charging = true;
case POWER_SUPPLY_PROP_CAPACITY:
if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) {
fuelgauge->initial_update_of_soc = true;
if (!sec_hal_fg_reset(fuelgauge->client))
return -EINVAL;
else
break;
}
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
if (!sec_hal_fg_set_property(fuelgauge->client, psp, val))
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static void sec_fg_isr_work(struct work_struct *work)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(work, struct sec_fuelgauge_info, isr_work.work);
/* process for fuel gauge chip */
sec_hal_fg_fuelalert_process(fuelgauge, fuelgauge->is_fuel_alerted);
/* process for others */
if (fuelgauge->pdata->fuelalert_process != NULL)
fuelgauge->pdata->fuelalert_process(fuelgauge->is_fuel_alerted);
}
static irqreturn_t sec_fg_irq_thread(int irq, void *irq_data)
{
struct sec_fuelgauge_info *fuelgauge = irq_data;
bool fuel_alerted;
if (fuelgauge->pdata->fuel_alert_soc >= 0) {
fuel_alerted =
sec_hal_fg_is_fuelalerted(fuelgauge->client);
dev_info(&fuelgauge->client->dev,
"%s: Fuel-alert %salerted!\n",
__func__, fuel_alerted ? "" : "NOT ");
if (fuel_alerted == fuelgauge->is_fuel_alerted) {
if (!fuelgauge->pdata->repeated_fuelalert) {
dev_dbg(&fuelgauge->client->dev,
"%s: Fuel-alert Repeated (%d)\n",
__func__, fuelgauge->is_fuel_alerted);
return IRQ_HANDLED;
}
}
if (fuel_alerted)
wake_lock(&fuelgauge->fuel_alert_wake_lock);
else
wake_unlock(&fuelgauge->fuel_alert_wake_lock);
schedule_delayed_work(&fuelgauge->isr_work, 0);
fuelgauge->is_fuel_alerted = fuel_alerted;
}
return IRQ_HANDLED;
}
static int sec_fg_create_attrs(struct device *dev)
{
int i, rc;
for (i = 0; i < ARRAY_SIZE(sec_fg_attrs); i++) {
rc = device_create_file(dev, &sec_fg_attrs[i]);
if (rc)
goto create_attrs_failed;
}
goto create_attrs_succeed;
create_attrs_failed:
dev_err(dev, "%s: failed (%d)\n", __func__, rc);
while (i--)
device_remove_file(dev, &sec_fg_attrs[i]);
create_attrs_succeed:
return rc;
}
ssize_t sec_fg_show_attrs(struct device *dev,
struct device_attribute *attr, char *buf)
{
const ptrdiff_t offset = attr - sec_fg_attrs;
int i = 0;
switch (offset) {
case FG_REG:
case FG_DATA:
case FG_REGS:
i = sec_hal_fg_show_attrs(dev, offset, buf);
break;
default:
i = -EINVAL;
break;
}
return i;
}
ssize_t sec_fg_store_attrs(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
const ptrdiff_t offset = attr - sec_fg_attrs;
int ret = 0;
switch (offset) {
case FG_REG:
case FG_DATA:
ret = sec_hal_fg_store_attrs(dev, offset, buf, count);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
#ifdef CONFIG_OF
static int fuelgauge_parse_dt(struct device *dev,
struct sec_fuelgauge_info *fuelgauge)
{
struct device_node *np = dev->of_node;
sec_battery_platform_data_t *pdata = fuelgauge->pdata;
int ret;
#if 0
int ta_int_gpio;
sec_battery_platform_data_t sec_battery_pdata;
#endif
/* reset, irq gpio info */
if (np == NULL) {
pr_err("%s np NULL\n", __func__);
} else {
pdata->fg_irq = of_get_named_gpio(np, "fuelgauge,fuel_int", 0);
if (pdata->fg_irq < 0) {
pr_err("%s error reading fg_irq = %d\n", __func__, pdata->fg_irq);
pdata->fg_irq = 0;
}
ret = of_property_read_u32(np, "fuelgauge,capacity_max",
&pdata->capacity_max);
if (ret < 0)
pr_err("%s error reading capacity_max %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin",
&pdata->capacity_max_margin);
if (ret < 0)
pr_err("%s error reading capacity_max_margin %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_min",
&pdata->capacity_min);
if (ret < 0)
pr_err("%s error reading capacity_min %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_calculation_type",
&pdata->capacity_calculation_type);
if (ret < 0)
pr_err("%s error reading capacity_calculation_type %d\n",
__func__, ret);
ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc",
&pdata->fuel_alert_soc);
if (ret < 0)
pr_err("%s error reading pdata->fuel_alert_soc %d\n",
__func__, ret);
pdata->repeated_fuelalert = of_property_read_bool(np,
"fuelgaguge,repeated_fuelalert");
pr_info("%s fg_irq: %d, capacity_max: %d\n"
"cpacity_max_margin: %d, capacity_min: %d\n"
"calculation_type: 0x%x, fuel_alert_soc: %d,\n"
"repeated_fuelalert: %d\n",
__func__, pdata->fg_irq,
pdata->capacity_max, pdata->capacity_max_margin,
pdata->capacity_min, pdata->capacity_calculation_type,
pdata->fuel_alert_soc, pdata->repeated_fuelalert);
}
return 0;
}
#endif
static int __devinit sec_fuelgauge_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct sec_fuelgauge_info *fuelgauge;
sec_battery_platform_data_t *pdata = NULL;
struct battery_data_t *battery_data = NULL;
int ret = 0;
union power_supply_propval raw_soc_val;
dev_info(&client->dev,
"%s: SEC Fuelgauge Driver Loading\n", __func__);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL);
if (!fuelgauge)
return -ENOMEM;
mutex_init(&fuelgauge->fg_lock);
fuelgauge->client = client;
if (client->dev.of_node) {
int error;
pdata = devm_kzalloc(&client->dev,
sizeof(sec_battery_platform_data_t),
GFP_KERNEL);
if (!pdata) {
dev_err(&client->dev, "Failed to allocate memory\n");
ret = -ENOMEM;
goto err_free;
}
battery_data = devm_kzalloc(&client->dev,
sizeof(struct battery_data_t),
GFP_KERNEL);
if (!battery_data) {
dev_err(&client->dev, "Failed to allocate memory\n");
devm_kfree(&client->dev, pdata);
ret = -ENOMEM;
goto err_free;
}
pdata->battery_data = (void *)battery_data;
fuelgauge->pdata = pdata;
error = fuelgauge_parse_dt(&client->dev, fuelgauge);
if (error) {
dev_err(&client->dev,
"%s: Failed to get fuel_int\n", __func__);
}
} else {
dev_err(&client->dev,
"%s: Failed to get of_node\n", __func__);
fuelgauge->pdata = client->dev.platform_data;
}
i2c_set_clientdata(client, fuelgauge);
if (!sec_hal_fg_init(fuelgauge->client)) {
dev_err(&client->dev,
"%s: Failed to Initialize Fuelgauge\n", __func__);
goto err_free;
}
fuelgauge->psy_fg.name = "sec-fuelgauge";
fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_UNKNOWN;
fuelgauge->psy_fg.get_property = sec_fg_get_property;
fuelgauge->psy_fg.set_property = sec_fg_set_property;
fuelgauge->psy_fg.properties = sec_fuelgauge_props;
fuelgauge->psy_fg.num_properties =
ARRAY_SIZE(sec_fuelgauge_props);
fuelgauge->capacity_max = fuelgauge->pdata->capacity_max;
raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW;
sec_hal_fg_get_property(fuelgauge->client,
POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val);
raw_soc_val.intval /= 10;
if(raw_soc_val.intval > fuelgauge->pdata->capacity_max)
sec_fg_calculate_dynamic_scale(fuelgauge);
/*
if (!fuelgauge->pdata->fg_gpio_init()) {
dev_err(&client->dev,
"%s: Failed to Initialize GPIO\n", __func__);
goto err_free;
}
*/
ret = power_supply_register(&client->dev, &fuelgauge->psy_fg);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Register psy_fg\n", __func__);
goto err_free;
}
if (fuelgauge->pdata->fg_irq) {
fuelgauge->fg_irq = gpio_to_irq(fuelgauge->pdata->fg_irq);
INIT_DELAYED_WORK(
&fuelgauge->isr_work, sec_fg_isr_work);
ret = request_threaded_irq(fuelgauge->fg_irq,
NULL, sec_fg_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"fuelgauge-irq", fuelgauge);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Reqeust IRQ\n", __func__);
goto err_supply_unreg;
}
ret = enable_irq_wake(fuelgauge->fg_irq);
if (ret < 0)
dev_err(&client->dev,
"%s: Failed to Enable Wakeup Source(%d)\n",
__func__, ret);
}
fuelgauge->is_fuel_alerted = false;
if (fuelgauge->pdata->fuel_alert_soc >= 0) {
if (sec_hal_fg_fuelalert_init(fuelgauge->client,
fuelgauge->pdata->fuel_alert_soc))
wake_lock_init(&fuelgauge->fuel_alert_wake_lock,
WAKE_LOCK_SUSPEND, "fuel_alerted");
else {
dev_err(&client->dev,
"%s: Failed to Initialize Fuel-alert\n",
__func__);
goto err_irq;
}
}
fuelgauge->initial_update_of_soc = true;
ret = sec_fg_create_attrs(fuelgauge->psy_fg.dev);
if (ret) {
dev_err(&client->dev,
"%s : Failed to create_attrs\n", __func__);
goto err_irq;
}
dev_info(&client->dev,
"%s: SEC Fuelgauge Driver Loaded\n", __func__);
return 0;
err_irq:
if (fuelgauge->fg_irq)
free_irq(fuelgauge->fg_irq, fuelgauge);
wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock);
err_supply_unreg:
power_supply_unregister(&fuelgauge->psy_fg);
err_free:
mutex_destroy(&fuelgauge->fg_lock);
kfree(fuelgauge);
return ret;
}
static int __devexit sec_fuelgauge_remove(
struct i2c_client *client)
{
struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client);
if (fuelgauge->pdata->fuel_alert_soc >= 0)
wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock);
return 0;
}
static int sec_fuelgauge_suspend(struct device *dev)
{
struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev);
if (!sec_hal_fg_suspend(fuelgauge->client))
dev_err(&fuelgauge->client->dev,
"%s: Failed to Suspend Fuelgauge\n", __func__);
return 0;
}
static int sec_fuelgauge_resume(struct device *dev)
{
struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev);
if (!sec_hal_fg_resume(fuelgauge->client))
dev_err(&fuelgauge->client->dev,
"%s: Failed to Resume Fuelgauge\n", __func__);
fuelgauge->initial_update_of_soc = true;
return 0;
}
static void sec_fuelgauge_shutdown(struct i2c_client *client)
{
}
static const struct i2c_device_id sec_fuelgauge_id[] = {
{"sec-fuelgauge", 0},
{}
};
static const struct dev_pm_ops sec_fuelgauge_pm_ops = {
.suspend = sec_fuelgauge_suspend,
.resume = sec_fuelgauge_resume,
};
MODULE_DEVICE_TABLE(i2c, sec_fuelgauge_id);
#if defined(CONFIG_OF)
static struct of_device_id sec_fuelgauge_i2c_dt_ids[] = {
{ .compatible = "sec-fuelgauge,i2c" },
{ }
};
MODULE_DEVICE_TABLE(of, sec_fuelgauge_i2c_dt_ids);
#endif /* CONFIG_OF */
static struct i2c_driver sec_fuelgauge_driver = {
.driver = {
.name = "sec-fuelgauge",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &sec_fuelgauge_pm_ops,
#endif
#if defined(CONFIG_OF)
.of_match_table = sec_fuelgauge_i2c_dt_ids,
#endif /* CONFIG_OF */
},
.probe = sec_fuelgauge_probe,
.remove = __devexit_p(sec_fuelgauge_remove),
.shutdown = sec_fuelgauge_shutdown,
.id_table = sec_fuelgauge_id,
};
static int __init sec_fuelgauge_init(void)
{
dev_info(0, "%s: \n", __func__);
return i2c_add_driver(&sec_fuelgauge_driver);
}
static void __exit sec_fuelgauge_exit(void)
{
i2c_del_driver(&sec_fuelgauge_driver);
}
module_init(sec_fuelgauge_init);
module_exit(sec_fuelgauge_exit);
MODULE_DESCRIPTION("Samsung Fuel Gauge Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");