/*
* Copyright (c) 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
#include
#include
#include
#define LC709203F_THERMISTOR_B 0x06
#define LC709203F_INITIAL_RSOC 0x07
#define LC709203F_TEMPERATURE 0x08
#define LC709203F_VOLTAGE 0x09
#define LC709203F_ADJUSTMENT_PACK_APPLI 0x0B
#define LC709203F_ADJUSTMENT_PACK_THERM 0x0C
#define LC709203F_RSOC 0x0D
#define LC709203F_INDICATOR_TO_EMPTY 0x0F
#define LC709203F_IC_VERSION 0x11
#define LC709203F_CHANGE_OF_THE_PARAM 0x12
#define LC709203F_ALARM_LOW_CELL_RSOC 0x13
#define LC709203F_ALARM_LOW_CELL_VOLT 0x14
#define LC709203F_IC_POWER_MODE 0x15
#define LC709203F_STATUS_BIT 0x16
#define LC709203F_NUM_OF_THE_PARAM 0x1A
#define LC709203F_DELAY (30*HZ)
#define LC709203F_MAX_REGS 0x1A
#define LC709203F_BATTERY_LOW 15
#define LC709203F_BATTERY_FULL 100
struct lc709203f_platform_data {
const char *tz_name;
u32 initial_rsoc;
u32 appli_adjustment;
u32 thermistor_beta;
u32 therm_adjustment;
u32 threshold_soc;
u32 maximum_soc;
u32 alert_low_rsoc;
u32 alert_low_voltage;
bool support_battery_current;
};
struct lc709203f_chip {
struct i2c_client *client;
struct delayed_work work;
struct power_supply battery;
struct lc709203f_platform_data *pdata;
struct battery_gauge_dev *bg_dev;
/* battery voltage */
int vcell;
/* battery capacity */
int soc;
/* State Of Charge */
int status;
/* battery health */
int health;
/* battery capacity */
int capacity_level;
int temperature;
int lasttime_soc;
int lasttime_status;
int shutdown_complete;
int charge_complete;
struct mutex mutex;
int read_failed;
};
static int lc709203f_read_word(struct i2c_client *client, u8 reg)
{
int ret;
ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "err reading reg: 0x%x, %d\n", reg, ret);
return ret;
}
static int lc709203f_write_word(struct i2c_client *client, u8 reg, u16 value)
{
int ret;
ret = i2c_smbus_write_word_data(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "err writing 0x%0x, %d\n" , reg, ret);
return ret;
}
static int lc709203f_update_soc_voltage(struct lc709203f_chip *chip)
{
int val;
val = lc709203f_read_word(chip->client, LC709203F_VOLTAGE);
if (val < 0)
dev_err(&chip->client->dev, "%s: err %d\n", __func__, val);
else
chip->vcell = val;
val = lc709203f_read_word(chip->client, LC709203F_RSOC);
if (val < 0)
dev_err(&chip->client->dev, "%s: err %d\n", __func__, val);
else
chip->soc = battery_gauge_get_adjusted_soc(chip->bg_dev,
chip->pdata->threshold_soc,
chip->pdata->maximum_soc, val * 100);
if (chip->soc >= LC709203F_BATTERY_FULL && chip->charge_complete != 1)
chip->soc = LC709203F_BATTERY_FULL - 1;
if (chip->status == POWER_SUPPLY_STATUS_FULL && chip->charge_complete) {
chip->soc = LC709203F_BATTERY_FULL;
chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
chip->health = POWER_SUPPLY_HEALTH_GOOD;
} else if (chip->soc < LC709203F_BATTERY_LOW) {
chip->status = chip->lasttime_status;
chip->health = POWER_SUPPLY_HEALTH_DEAD;
chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
} else {
chip->status = chip->lasttime_status;
chip->health = POWER_SUPPLY_HEALTH_GOOD;
chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
}
return 0;
}
static void lc709203f_work(struct work_struct *work)
{
struct lc709203f_chip *chip;
int val;
int temperature;
chip = container_of(work, struct lc709203f_chip, work.work);
mutex_lock(&chip->mutex);
if (chip->shutdown_complete) {
mutex_unlock(&chip->mutex);
return;
}
lc709203f_update_soc_voltage(chip);
if (chip->soc != chip->lasttime_soc ||
chip->status != chip->lasttime_status) {
chip->lasttime_soc = chip->soc;
power_supply_changed(&chip->battery);
}
if (chip->pdata->tz_name) {
val = battery_gauge_get_battery_temperature(chip->bg_dev,
&temperature);
if (val < 0) {
dev_err(&chip->client->dev, "temp invalid\n");
} else {
lc709203f_write_word(chip->client, LC709203F_TEMPERATURE
, temperature * 10 + 2732);
chip->temperature = temperature;
}
}
mutex_unlock(&chip->mutex);
schedule_delayed_work(&chip->work, LC709203F_DELAY);
}
static int lc709203f_get_temperature(struct lc709203f_chip *chip)
{
int val;
if (chip->shutdown_complete)
return chip->temperature;
val = lc709203f_read_word(chip->client, LC709203F_TEMPERATURE);
if (val < 0) {
chip->read_failed++;
dev_err(&chip->client->dev, "%s: err %d\n", __func__, val);
if (chip->read_failed > 50)
return val;
return chip->temperature;
}
chip->read_failed = 0;;
chip->temperature = val;
return val;
}
static enum power_supply_property lc709203f_battery_props[] = {
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static int lc709203f_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct lc709203f_chip *chip = container_of(psy,
struct lc709203f_chip, battery);
int temperature;
int curr_ma;
int ret = 0;
mutex_lock(&chip->mutex);
switch (psp) {
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = chip->status;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = 1000 * chip->vcell;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = chip->soc;
if (chip->soc == 15)
dev_warn(&chip->client->dev,
"\nSystem Running low on battery - 15 percent\n");
if (chip->soc == 10)
dev_warn(&chip->client->dev,
"\nSystem Running low on battery - 10 percent\n");
if (chip->soc == 5)
dev_warn(&chip->client->dev,
"\nSystem Running low on battery - 5 percent\n");
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = chip->health;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = chip->capacity_level;
break;
case POWER_SUPPLY_PROP_TEMP:
temperature = lc709203f_get_temperature(chip);
/*
Temp ready by device is deci-kelvin
C = K -273.2
Report temp in dec-celcius.
*/
val->intval = temperature - 2732;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = 0;
ret = battery_gauge_get_battery_current(chip->bg_dev, &curr_ma);
if (!ret)
val->intval = 1000 * curr_ma;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&chip->mutex);
return ret;
}
static int lc709203f_update_battery_status(struct battery_gauge_dev *bg_dev,
enum battery_charger_status status)
{
struct lc709203f_chip *chip = battery_gauge_get_drvdata(bg_dev);
mutex_lock(&chip->mutex);
if (chip->shutdown_complete) {
mutex_unlock(&chip->mutex);
return 0;
}
if (status == BATTERY_CHARGING) {
chip->charge_complete = 0;
chip->status = POWER_SUPPLY_STATUS_CHARGING;
} else if (status == BATTERY_CHARGING_DONE) {
chip->charge_complete = 1;
chip->soc = LC709203F_BATTERY_FULL;
chip->status = POWER_SUPPLY_STATUS_FULL;
goto done;
} else {
chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
chip->charge_complete = 0;
}
chip->lasttime_status = chip->status;
done:
mutex_unlock(&chip->mutex);
power_supply_changed(&chip->battery);
return 0;
}
static struct battery_gauge_ops lc709203f_bg_ops = {
.update_battery_status = lc709203f_update_battery_status,
};
static struct battery_gauge_info lc709203f_bgi = {
.cell_id = 0,
.bg_ops = &lc709203f_bg_ops,
.current_channel_name = "battery-current",
};
static irqreturn_t lc709203f_irq(int id, void *dev)
{
struct lc709203f_chip *chip = dev;
struct i2c_client *client = chip->client;
dev_info(&client->dev, "%s(): STATUS_VL\n", __func__);
/* Forced set SOC 0 to power off */
chip->soc = 0;
chip->lasttime_soc = chip->soc;
chip->status = chip->lasttime_status;
chip->health = POWER_SUPPLY_HEALTH_DEAD;
chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
power_supply_changed(&chip->battery);
return IRQ_HANDLED;
}
static void of_lc709203f_parse_platform_data(struct i2c_client *client,
struct lc709203f_platform_data *pdata)
{
char const *pstr;
struct device_node *np = client->dev.of_node;
u32 pval;
int ret;
ret = of_property_read_u32(np, "onsemi,initial-rsoc", &pval);
if (!ret)
pdata->initial_rsoc = pval;
ret = of_property_read_u32(np, "onsemi,appli-adjustment", &pval);
if (!ret)
pdata->appli_adjustment = pval;
pdata->tz_name = NULL;
ret = of_property_read_string(np, "onsemi,tz-name", &pstr);
if (!ret)
pdata->tz_name = pstr;
ret = of_property_read_u32(np, "onsemi,thermistor-beta", &pval);
if (!ret) {
pdata->thermistor_beta = pval;
} else {
if (!pdata->tz_name)
dev_warn(&client->dev,
"Thermistor beta not provided\n");
}
ret = of_property_read_u32(np, "onsemi,thermistor-adjustment", &pval);
if (!ret)
pdata->therm_adjustment = pval;
ret = of_property_read_u32(np, "onsemi,kernel-threshold-soc", &pval);
if (!ret)
pdata->threshold_soc = pval;
ret = of_property_read_u32(np, "onsemi,kernel-maximum-soc", &pval);
if (!ret)
pdata->maximum_soc = pval;
else
pdata->maximum_soc = 100;
ret = of_property_read_u32(np, "onsemi,alert-low-rsoc", &pval);
if (!ret)
pdata->alert_low_rsoc = pval;
ret = of_property_read_u32(np, "onsemi,alert-low-voltage", &pval);
if (!ret)
pdata->alert_low_voltage = pval;
pdata->support_battery_current = of_property_read_bool(np,
"io-channel-names");
}
#ifdef CONFIG_DEBUG_FS
#include
#include
static struct dentry *debugfs_root;
static u8 valid_command[] = {0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xD, 0xF,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x1A};
static int dbg_lc709203f_show(struct seq_file *s, void *data)
{
struct i2c_client *client = s->private;
int ret;
int i;
seq_puts(s, "Register-->Value(16bit)\n");
for (i = 0; i < ARRAY_SIZE(valid_command); ++i) {
ret = lc709203f_read_word(client, valid_command[i]);
if (ret < 0)
seq_printf(s, "0x%02x: ERROR\n", valid_command[i]);
else
seq_printf(s, "0x%02x: 0x%04x\n",
valid_command[i], ret);
}
return 0;
}
static int dbg_lc709203f_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_lc709203f_show, inode->i_private);
}
static const struct file_operations lc709203f_debug_fops = {
.open = dbg_lc709203f_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int lc709203f_debugfs_init(struct i2c_client *client)
{
debugfs_root = debugfs_create_dir("lc709203f", NULL);
if (!debugfs_root)
pr_warn("lc709203f: Failed to create debugfs directory\n");
(void) debugfs_create_file("registers", S_IRUGO,
debugfs_root, (void *)client, &lc709203f_debug_fops);
return 0;
}
#else
static int lc709203f_debugfs_init(struct i2c_client *client)
{
return 0;
}
#endif
static int lc709203f_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lc709203f_chip *chip;
int ret;
/* Required PEC functionality */
client->flags = client->flags | I2C_CLIENT_PEC;
/* Check if device exist or not */
ret = i2c_smbus_read_word_data(client, LC709203F_NUM_OF_THE_PARAM);
if (ret < 0) {
dev_err(&client->dev, "device is not responding, %d\n", ret);
return ret;
}
dev_info(&client->dev, "Device Params 0x%04x\n", ret);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->client = client;
if (client->dev.of_node) {
chip->pdata = devm_kzalloc(&client->dev,
sizeof(*chip->pdata), GFP_KERNEL);
if (!chip->pdata)
return -ENOMEM;
of_lc709203f_parse_platform_data(client, chip->pdata);
} else {
chip->pdata = client->dev.platform_data;
}
if (!chip->pdata)
return -ENODATA;
mutex_init(&chip->mutex);
chip->shutdown_complete = 0;
i2c_set_clientdata(client, chip);
if (chip->pdata->initial_rsoc) {
ret = lc709203f_write_word(chip->client,
LC709203F_INITIAL_RSOC, chip->pdata->initial_rsoc);
if (ret < 0) {
dev_err(&client->dev,
"INITIAL_RSOC write failed: %d\n", ret);
return ret;
}
dev_info(&client->dev, "initial-rsoc: 0x%04x\n",
chip->pdata->initial_rsoc);
}
ret = lc709203f_write_word(chip->client,
LC709203F_ALARM_LOW_CELL_RSOC, chip->pdata->alert_low_rsoc);
if (ret < 0) {
dev_err(&client->dev, "LOW_RSOC write failed: %d\n", ret);
return ret;
}
ret = lc709203f_write_word(chip->client,
LC709203F_ALARM_LOW_CELL_VOLT, chip->pdata->alert_low_voltage);
if (ret < 0) {
dev_err(&client->dev, "LOW_VOLT write failed: %d\n", ret);
return ret;
}
if (chip->pdata->appli_adjustment) {
ret = lc709203f_write_word(chip->client,
LC709203F_ADJUSTMENT_PACK_APPLI,
chip->pdata->appli_adjustment);
if (ret < 0) {
dev_err(&client->dev,
"ADJUSTMENT_APPLI write failed: %d\n", ret);
return ret;
}
}
if (chip->pdata->tz_name || !chip->pdata->thermistor_beta)
goto skip_thermistor_config;
if (chip->pdata->therm_adjustment) {
ret = lc709203f_write_word(chip->client,
LC709203F_ADJUSTMENT_PACK_THERM,
chip->pdata->therm_adjustment);
if (ret < 0) {
dev_err(&client->dev,
"ADJUSTMENT_THERM write failed: %d\n", ret);
return ret;
}
}
ret = lc709203f_write_word(chip->client,
LC709203F_THERMISTOR_B, chip->pdata->thermistor_beta);
if (ret < 0) {
dev_err(&client->dev, "THERMISTOR_B write failed: %d\n", ret);
return ret;
}
ret = lc709203f_write_word(chip->client, LC709203F_STATUS_BIT, 0x1);
if (ret < 0) {
dev_err(&client->dev, "STATUS_BIT write failed: %d\n", ret);
return ret;
}
skip_thermistor_config:
lc709203f_update_soc_voltage(chip);
chip->battery.name = "battery";
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = lc709203f_get_property;
chip->battery.properties = lc709203f_battery_props;
chip->battery.num_properties = ARRAY_SIZE(lc709203f_battery_props);
chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
chip->lasttime_status = POWER_SUPPLY_STATUS_DISCHARGING;
chip->charge_complete = 0;
/* Remove current property if it is not supported */
if (!chip->pdata->support_battery_current)
chip->battery.num_properties--;
ret = power_supply_register(&client->dev, &chip->battery);
if (ret) {
dev_err(&client->dev, "failed: power supply register\n");
goto error;
}
lc709203f_bgi.tz_name = chip->pdata->tz_name;
chip->bg_dev = battery_gauge_register(&client->dev, &lc709203f_bgi,
chip);
if (IS_ERR(chip->bg_dev)) {
ret = PTR_ERR(chip->bg_dev);
dev_err(&client->dev, "battery gauge register failed: %d\n",
ret);
goto bg_err;
}
INIT_DEFERRABLE_WORK(&chip->work, lc709203f_work);
schedule_delayed_work(&chip->work, 0);
lc709203f_debugfs_init(client);
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, lc709203f_irq,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
dev_name(&client->dev), chip);
if (ret < 0) {
dev_err(&client->dev,
"%s: request IRQ %d fail, err = %d\n",
__func__, client->irq, ret);
client->irq = 0;
goto irq_reg_error;
}
}
device_set_wakeup_capable(&client->dev, 1);
dev_info(&client->dev, "Battery Voltage %dmV and SoC %d%%\n",
chip->vcell, chip->soc);
return 0;
irq_reg_error:
cancel_delayed_work_sync(&chip->work);
bg_err:
power_supply_unregister(&chip->battery);
error:
mutex_destroy(&chip->mutex);
return ret;
}
static int lc709203f_remove(struct i2c_client *client)
{
struct lc709203f_chip *chip = i2c_get_clientdata(client);
battery_gauge_unregister(chip->bg_dev);
power_supply_unregister(&chip->battery);
cancel_delayed_work_sync(&chip->work);
mutex_destroy(&chip->mutex);
return 0;
}
static void lc709203f_shutdown(struct i2c_client *client)
{
struct lc709203f_chip *chip = i2c_get_clientdata(client);
mutex_lock(&chip->mutex);
chip->shutdown_complete = 1;
mutex_unlock(&chip->mutex);
cancel_delayed_work_sync(&chip->work);
dev_info(&chip->client->dev, "At shutdown Voltage %dmV and SoC %d%%\n",
chip->vcell, chip->soc);
}
#ifdef CONFIG_PM_SLEEP
static int lc709203f_suspend(struct device *dev)
{
struct lc709203f_chip *chip = dev_get_drvdata(dev);
cancel_delayed_work_sync(&chip->work);
if (device_may_wakeup(&chip->client->dev))
enable_irq_wake(chip->client->irq);
return 0;
}
static int lc709203f_resume(struct device *dev)
{
struct lc709203f_chip *chip = dev_get_drvdata(dev);
if (device_may_wakeup(&chip->client->dev))
disable_irq_wake(chip->client->irq);
schedule_delayed_work(&chip->work, LC709203F_DELAY);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(lc709203f_pm_ops, lc709203f_suspend, lc709203f_resume);
static const struct i2c_device_id lc709203f_id[] = {
{ "lc709203f", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lc709203f_id);
static struct i2c_driver lc709203f_i2c_driver = {
.driver = {
.name = "lc709203f",
.pm = &lc709203f_pm_ops,
},
.probe = lc709203f_probe,
.remove = lc709203f_remove,
.id_table = lc709203f_id,
.shutdown = lc709203f_shutdown,
};
static int __init lc709203f_init(void)
{
return i2c_add_driver(&lc709203f_i2c_driver);
}
fs_initcall_sync(lc709203f_init);
static void __exit lc709203f_exit(void)
{
i2c_del_driver(&lc709203f_i2c_driver);
}
module_exit(lc709203f_exit);
MODULE_AUTHOR("Chaitanya Bandi ");
MODULE_DESCRIPTION("OnSemi LC709203F Fuel Gauge");
MODULE_LICENSE("GPL v2");