summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChaitanya Bandi <bandik@nvidia.com>2013-12-30 17:23:51 +0530
committerLaxman Dewangan <ldewangan@nvidia.com>2014-01-07 03:10:41 -0800
commitc60b8066e3faeab7e64acf6ef5e4677226cecad4 (patch)
tree2230add07142a0d59c1c2a634df1ba6ecbbaa60a
parent7fe3295e746a0f0e744c87b8c504435493809119 (diff)
power: lc709203f: Add lc709203f fuel gauge driver
Added On-semi's LC709203F fuel gauge driver. Bug 1388336 Change-Id: Id9f4c0f752e9f452c0c5f61db0d1032c52e07fb5 Signed-off-by: Chaitanya Bandi <bandik@nvidia.com> Reviewed-on: http://git-master/r/350572 Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com> Tested-by: Laxman Dewangan <ldewangan@nvidia.com>
-rw-r--r--drivers/power/Kconfig9
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/lc709203f_battery.c458
-rw-r--r--include/linux/power/lc709203f_battery.h24
4 files changed, 492 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index eda5b6af54e6..4ed79f363beb 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -320,6 +320,15 @@ config BATTERY_BQ27441
BQ27441 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment.
+config BATTERY_LC709203F
+ tristate "On semi's LC709203F Fuel Gauge"
+ depends on I2C
+ select BATTERY_CHARGER_GAUGE_COMM
+ help
+ LC709203F is fuel-gauge system for from On-semi for
+ Lithium-ion batteries. Say Y here to enable the driver for
+ the same.
+
config BATTERY_ANDROID
tristate "Battery driver for Android"
help
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 2a40f6edd719..e9fca369037c 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
+obj-$(CONFIG_BATTERY_LC709203F) += lc709203f_battery.o
obj-$(CONFIG_CHARGER_BQ2419X) += bq2419x-charger.o
obj-$(CONFIG_CHARGER_BQ2471X) += bq2471x-charger.o
obj-$(CONFIG_CHARGER_BQ2477X) += bq2477x-charger.o
diff --git a/drivers/power/lc709203f_battery.c b/drivers/power/lc709203f_battery.c
new file mode 100644
index 000000000000..fc63dddf1fad
--- /dev/null
+++ b/drivers/power/lc709203f_battery.c
@@ -0,0 +1,458 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/power/battery-charger-gauge-comm.h>
+#include <linux/pm.h>
+#include <linux/jiffies.h>
+#include <linux/regmap.h>
+#include <linux/power/lc709203f_battery.h>
+
+#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_chip {
+ struct i2c_client *client;
+ struct delayed_work work;
+ struct power_supply battery;
+ struct lc709203f_platform_data *pdata;
+ struct battery_gauge_dev *bg_dev;
+ struct regmap *regmap;
+
+ /* battery voltage */
+ int vcell;
+ /* battery capacity */
+ int soc;
+ /* State Of Charge */
+ int status;
+ /* battery health */
+ int health;
+ /* battery capacity */
+ int capacity_level;
+
+ int lasttime_soc;
+ int lasttime_status;
+ int shutdown_complete;
+ int charge_complete;
+ struct mutex mutex;
+};
+
+struct lc709203f_chip *lc709203f_data;
+
+static const struct regmap_config lc709203f_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = LC709203F_MAX_REGS,
+};
+
+static int lc709203f_read_word(struct i2c_client *client, u8 reg)
+{
+ int ret;
+ u16 val;
+
+ struct lc709203f_chip *chip = i2c_get_clientdata(client);
+
+ mutex_lock(&chip->mutex);
+ if (chip && chip->shutdown_complete) {
+ mutex_unlock(&chip->mutex);
+ return -ENODEV;
+ }
+
+ ret = regmap_raw_read(chip->regmap, reg, (u8 *) &val, sizeof(val));
+ if (ret < 0) {
+ dev_err(&client->dev, "err reading reg: 0x%x, %d\n", reg, ret);
+ mutex_unlock(&chip->mutex);
+ return ret;
+ }
+
+ mutex_unlock(&chip->mutex);
+ return val;
+}
+
+static int lc709203f_read_byte(struct i2c_client *client, u8 reg)
+{
+ int ret;
+ u8 val;
+
+ struct lc709203f_chip *chip = i2c_get_clientdata(client);
+
+ mutex_lock(&chip->mutex);
+ if (chip && chip->shutdown_complete) {
+ mutex_unlock(&chip->mutex);
+ return -ENODEV;
+ }
+
+ ret = regmap_raw_read(chip->regmap, reg, (u8 *) &val, sizeof(val));
+ if (ret < 0) {
+ dev_err(&client->dev, "err reading reg: 0x%x\n, %d", reg, ret);
+ mutex_unlock(&chip->mutex);
+ return ret;
+ }
+
+ mutex_unlock(&chip->mutex);
+ return val;
+}
+
+
+static int lc709203f_write_byte(struct i2c_client *client, u8 reg, u8 value)
+{
+ struct lc709203f_chip *chip = i2c_get_clientdata(client);
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ if (chip && chip->shutdown_complete) {
+ mutex_unlock(&chip->mutex);
+ return -ENODEV;
+ }
+
+ ret = regmap_write(chip->regmap, reg, value);
+ if (ret < 0)
+ dev_err(&client->dev, "err writing 0x%0x, %d\n" , reg, ret);
+
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static void lc709203f_work(struct work_struct *work)
+{
+ struct lc709203f_chip *chip;
+ int val;
+
+ chip = container_of(work, struct lc709203f_chip, work.work);
+
+ 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 = val;
+ 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;
+ }
+
+ if (chip->soc != chip->lasttime_soc ||
+ chip->status != chip->lasttime_status) {
+ chip->lasttime_soc = chip->soc;
+ power_supply_changed(&chip->battery);
+ }
+
+ schedule_delayed_work(&chip->work, LC709203F_DELAY);
+}
+
+static int lc709203f_get_temperature(struct lc709203f_chip *chip)
+{
+ int val;
+
+ val = lc709203f_read_word(chip->client, LC709203F_TEMPERATURE);
+ if (val < 0) {
+ dev_err(&chip->client->dev, "%s: err %d\n", __func__, val);
+ return -EINVAL;
+ }
+ 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_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+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;
+
+ 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 = 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_CAPACITY_LEVEL:
+ val->intval = chip->capacity_level;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ temperature = lc709203f_get_temperature(chip);
+ val->intval = (temperature / 10) - 273;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+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);
+
+ 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;
+ power_supply_changed(&chip->battery);
+ return 0;
+ } else {
+ chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ chip->charge_complete = 0;
+ }
+ chip->lasttime_status = chip->status;
+ 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,
+};
+
+static int lc709203f_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lc709203f_chip *chip;
+ int ret;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+
+ chip->pdata = client->dev.platform_data;
+ if (!chip->pdata)
+ return -ENODATA;
+
+ lc709203f_data = chip;
+ mutex_init(&chip->mutex);
+ chip->shutdown_complete = 0;
+ i2c_set_clientdata(client, 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;
+
+ chip->regmap = devm_regmap_init_i2c(client, &lc709203f_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "regmap init failed with err %d\n", ret);
+ goto error;
+ }
+
+ /* Dummy read to check if the slave is present*/
+ ret = lc709203f_read_word(chip->client, LC709203F_VOLTAGE);
+ if (ret < 0) {
+ dev_err(&client->dev, "Exiting driver as xfer failed\n");
+ /* Exit driver if not present */
+ goto error;
+ }
+
+ 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);
+
+ return 0;
+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);
+
+ cancel_delayed_work_sync(&chip->work);
+ mutex_lock(&chip->mutex);
+ chip->shutdown_complete = 1;
+ mutex_unlock(&chip->mutex);
+
+}
+
+#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);
+ return 0;
+}
+
+static int lc709203f_resume(struct device *dev)
+{
+ struct lc709203f_chip *chip = dev_get_drvdata(dev);
+ schedule_delayed_work(&chip->work, LC709203F_DELAY);
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static SIMPLE_DEV_PM_OPS(lc709203f_pm_ops, lc709203f_suspend, lc709203f_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id lc709203f_dt_match[] = {
+ { .compatible = "onsemi,lc709203f" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, lc709203f_dt_match);
+#endif
+
+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",
+ .of_match_table = of_match_ptr(lc709203f_dt_match),
+ .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 <bandik@nvidia.com>");
+MODULE_DESCRIPTION("LC709203F Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/lc709203f_battery.h b/include/linux/power/lc709203f_battery.h
new file mode 100644
index 000000000000..048500103adc
--- /dev/null
+++ b/include/linux/power/lc709203f_battery.h
@@ -0,0 +1,24 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LC709203F_BATTERY_H_
+#define __LC709203F_BATTERY_H_
+
+struct lc709203f_platform_data {
+ const char *tz_name;
+};
+
+#endif