blob: a457330ebf6693a9b75fe0b2b8fe748fe22b1874 [file] [log] [blame]
/*
* max17048_battery.c
* fuel-gauge systems for lithium-ion (Li+) batteries
*
* Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
* Chandler Zhang <chazhang@nvidia.com>
* Syed Rafiuddin <srafiuddin@nvidia.com>
*
* 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.
*/
#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/max17048_battery.h>
#include <linux/power/battery-charger-gauge-comm.h>
#include <linux/pm.h>
#include <linux/jiffies.h>
#define MAX17048_VCELL 0x02
#define MAX17048_SOC 0x04
#define MAX17048_VER 0x08
#define MAX17048_HIBRT 0x0A
#define MAX17048_CONFIG 0x0C
#define MAX17048_OCV 0x0E
#define MAX17048_VALRT 0x14
#define MAX17048_VRESET 0x18
#define MAX17048_STATUS 0x1A
#define MAX17048_UNLOCK 0x3E
#define MAX17048_TABLE 0x40
#define MAX17048_RCOMPSEG1 0x80
#define MAX17048_RCOMPSEG2 0x90
#define MAX17048_CMD 0xFF
#define MAX17048_UNLOCK_VALUE 0x4a57
#define MAX17048_RESET_VALUE 0x5400
#define MAX17048_DELAY (30*HZ)
#define MAX17048_BATTERY_FULL 100
#define MAX17048_BATTERY_LOW 15
#define MAX17048_VERSION_NO 0x11
struct max17048_chip {
struct i2c_client *client;
struct delayed_work work;
struct power_supply battery;
struct max17048_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 lasttime_soc;
int lasttime_status;
int shutdown_complete;
int charge_complete;
struct mutex mutex;
};
struct max17048_chip *max17048_data;
static int max17048_write_word(struct i2c_client *client, int reg, u16 value)
{
struct max17048_chip *chip = i2c_get_clientdata(client);
int ret;
mutex_lock(&chip->mutex);
if (chip && chip->shutdown_complete) {
mutex_unlock(&chip->mutex);
return -ENODEV;
}
ret = i2c_smbus_write_word_data(client, reg, swab16(value));
if (ret < 0)
dev_err(&client->dev, "%s(): Failed in writing register"
"0x%02x err %d\n", __func__, reg, ret);
mutex_unlock(&chip->mutex);
return ret;
}
static int max17048_write_block(const struct i2c_client *client,
uint8_t command, uint8_t length, const uint8_t *values)
{
struct max17048_chip *chip = i2c_get_clientdata(client);
int ret;
mutex_lock(&chip->mutex);
if (chip && chip->shutdown_complete) {
mutex_unlock(&chip->mutex);
return -ENODEV;
}
ret = i2c_smbus_write_i2c_block_data(client, command, length, values);
if (ret < 0)
dev_err(&client->dev, "%s(): Failed in writing block data to"
"0x%02x err %d\n", __func__, command, ret);
mutex_unlock(&chip->mutex);
return ret;
}
static int max17048_read_word(struct i2c_client *client, int reg)
{
struct max17048_chip *chip = i2c_get_clientdata(client);
int ret;
mutex_lock(&chip->mutex);
if (chip && chip->shutdown_complete) {
mutex_unlock(&chip->mutex);
return -ENODEV;
}
ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in reading register"
"0x%02x err %d\n", __func__, reg, ret);
mutex_unlock(&chip->mutex);
return ret;
} else {
ret = (int)swab16((uint16_t)(ret & 0x0000ffff));
mutex_unlock(&chip->mutex);
return ret;
}
}
/* Return value in uV */
static int max17048_get_ocv(struct max17048_chip *chip)
{
int r;
int reg;
int ocv;
r = max17048_write_word(chip->client, MAX17048_UNLOCK,
MAX17048_UNLOCK_VALUE);
if (r)
return r;
reg = max17048_read_word(chip->client, MAX17048_OCV);
ocv = (reg >> 4) * 1250;
r = max17048_write_word(chip->client, MAX17048_UNLOCK, 0);
WARN_ON(r);
return ocv;
}
static int max17048_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max17048_chip *chip = container_of(psy,
struct max17048_chip, battery);
int temp;
int ret;
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;
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_VOLTAGE_OCV:
val->intval = max17048_get_ocv(chip);
break;
case POWER_SUPPLY_PROP_TEMP:
ret = battery_gauge_get_battery_temperature(chip->bg_dev,
&temp);
if (ret < 0)
return -EINVAL;
val->intval = temp;
break;
default:
return -EINVAL;
}
return 0;
}
static void max17048_get_vcell(struct i2c_client *client)
{
struct max17048_chip *chip = i2c_get_clientdata(client);
int vcell;
vcell = max17048_read_word(client, MAX17048_VCELL);
if (vcell < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, vcell);
else
chip->vcell = (uint16_t)(((vcell >> 4) * 125) / 100);
}
static void max17048_get_soc(struct i2c_client *client)
{
struct max17048_chip *chip = i2c_get_clientdata(client);
int soc;
soc = max17048_read_word(client, MAX17048_SOC);
if (soc < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, soc);
else
chip->soc = (uint16_t)soc >> 9;
if (chip->soc >= MAX17048_BATTERY_FULL && chip->charge_complete != 1)
chip->soc = MAX17048_BATTERY_FULL-1;
if (chip->status == POWER_SUPPLY_STATUS_FULL && chip->charge_complete) {
chip->soc = MAX17048_BATTERY_FULL;
chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
chip->health = POWER_SUPPLY_HEALTH_GOOD;
} else if (chip->soc < MAX17048_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;
}
}
static uint16_t max17048_get_version(struct i2c_client *client)
{
return swab16(max17048_read_word(client, MAX17048_VER));
}
static void max17048_work(struct work_struct *work)
{
struct max17048_chip *chip;
chip = container_of(work, struct max17048_chip, work.work);
max17048_get_vcell(chip->client);
max17048_get_soc(chip->client);
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, MAX17048_DELAY);
}
void max17048_battery_status(int status)
{
if (!max17048_data)
return;
if (status == progress)
max17048_data->status = POWER_SUPPLY_STATUS_CHARGING;
else
max17048_data->status = POWER_SUPPLY_STATUS_DISCHARGING;
max17048_data->lasttime_status = max17048_data->status;
power_supply_changed(&max17048_data->battery);
}
EXPORT_SYMBOL_GPL(max17048_battery_status);
static enum power_supply_property max17048_battery_props[] = {
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_TEMP,
};
static int max17048_write_rcomp_seg(struct i2c_client *client,
uint16_t rcomp_seg)
{
uint8_t rs1, rs2;
int ret;
uint8_t rcomp_seg_table[16];
rs1 = (rcomp_seg >> 8) & 0xff;
rs2 = rcomp_seg & 0xff;
rcomp_seg_table[0] = rcomp_seg_table[2] = rcomp_seg_table[4] =
rcomp_seg_table[6] = rcomp_seg_table[8] = rcomp_seg_table[10] =
rcomp_seg_table[12] = rcomp_seg_table[14] = rs1;
rcomp_seg_table[1] = rcomp_seg_table[3] = rcomp_seg_table[5] =
rcomp_seg_table[7] = rcomp_seg_table[9] = rcomp_seg_table[11] =
rcomp_seg_table[13] = rcomp_seg_table[15] = rs2;
ret = max17048_write_block(client, MAX17048_RCOMPSEG1,
16, (uint8_t *)rcomp_seg_table);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
ret = max17048_write_block(client, MAX17048_RCOMPSEG2,
16, (uint8_t *)rcomp_seg_table);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
return 0;
}
static int max17048_load_model_data(struct max17048_chip *chip)
{
struct i2c_client *client = chip->client;
struct max17048_battery_model *mdata = chip->pdata->model_data;
uint16_t soc_tst, ocv;
int i, ret = 0;
/* read OCV */
ret = max17048_read_word(client, MAX17048_OCV);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
ocv = (uint16_t)ret;
if (ocv == 0xffff) {
dev_err(&client->dev, "%s: Failed in unlocking"
"max17048 err: %d\n", __func__, ocv);
return -1;
}
/* write custom model data */
for (i = 0; i < 4; i += 1) {
if (max17048_write_block(client,
(MAX17048_TABLE+i*16), 16,
&mdata->data_tbl[i*0x10]) < 0) {
dev_err(&client->dev, "%s: error writing model data:\n",
__func__);
return -1;
}
}
/* Write OCV Test value */
ret = max17048_write_word(client, MAX17048_OCV, mdata->ocvtest);
if (ret < 0)
return ret;
ret = max17048_write_rcomp_seg(client, mdata->rcomp_seg);
if (ret < 0)
return ret;
/* Disable hibernate */
ret = max17048_write_word(client, MAX17048_HIBRT, 0x0000);
if (ret < 0)
return ret;
/* Lock model access */
ret = max17048_write_word(client, MAX17048_UNLOCK, 0x0000);
if (ret < 0)
return ret;
/* Delay between 150ms to 600ms */
mdelay(200);
/* Read SOC Register and compare to expected result */
ret = max17048_read_word(client, MAX17048_SOC);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
soc_tst = (uint16_t)ret;
if (!((soc_tst >> 8) >= mdata->soccheck_A &&
(soc_tst >> 8) <= mdata->soccheck_B)) {
dev_err(&client->dev, "%s: soc comparison failed %d\n",
__func__, ret);
return ret;
} else {
dev_info(&client->dev, "MAX17048 Custom data"
" loading successfull\n");
}
/* unlock model access */
ret = max17048_write_word(client, MAX17048_UNLOCK,
MAX17048_UNLOCK_VALUE);
if (ret < 0)
return ret;
/* Restore OCV */
ret = max17048_write_word(client, MAX17048_OCV, ocv);
if (ret < 0)
return ret;
return ret;
}
static int max17048_initialize(struct max17048_chip *chip)
{
uint8_t ret, config = 0;
struct i2c_client *client = chip->client;
struct max17048_battery_model *mdata = chip->pdata->model_data;
/* unlock model access */
ret = max17048_write_word(client, MAX17048_UNLOCK,
MAX17048_UNLOCK_VALUE);
if (ret < 0)
return ret;
/* load model data */
ret = max17048_load_model_data(chip);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
if (mdata->bits == 19)
config = 32 - (mdata->alert_threshold * 2);
else if (mdata->bits == 18)
config = 32 - mdata->alert_threshold;
else
WARN_ON("Unknown mdata->bits");
config = mdata->one_percent_alerts | config;
ret = max17048_write_word(client, MAX17048_CONFIG,
((mdata->rcomp << 8) | config));
if (ret < 0)
return ret;
/* Voltage Alert configuration */
ret = max17048_write_word(client, MAX17048_VALRT, mdata->valert);
if (ret < 0)
return ret;
ret = max17048_write_word(client, MAX17048_VRESET, mdata->vreset);
if (ret < 0)
return ret;
/* Lock model access */
ret = max17048_write_word(client, MAX17048_UNLOCK, 0x0000);
if (ret < 0)
return ret;
/* Add delay */
mdelay(200);
return 0;
}
int max17048_check_battery()
{
uint16_t version;
if (!max17048_data)
return -ENODEV;
version = max17048_get_version(max17048_data->client);
if (version != MAX17048_VERSION_NO)
return -ENODEV;
return 0;
}
EXPORT_SYMBOL_GPL(max17048_check_battery);
#ifdef CONFIG_OF
static struct max17048_platform_data *max17048_parse_dt(struct device *dev)
{
struct max17048_platform_data *pdata;
struct max17048_battery_model *model_data;
struct device_node *np = dev->of_node;
u32 val, val_array[MAX17048_DATA_SIZE];
int i, ret;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
model_data = devm_kzalloc(dev, sizeof(*model_data), GFP_KERNEL);
if (!model_data)
return ERR_PTR(-ENOMEM);
pdata->model_data = model_data;
ret = of_property_read_u32(np, "bits", &val);
if (ret < 0)
return ERR_PTR(ret);
if ((val == 18) || (val == 19))
model_data->bits = val;
ret = of_property_read_u32(np, "alert-threshold", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->alert_threshold = val;
if (model_data->bits == 19) /* LSB is 0.5%, if 19-bit model. */
model_data->alert_threshold /= 2;
ret = of_property_read_u32(np, "one-percent-alerts", &val);
if (ret < 0)
return ERR_PTR(ret);
if (val)
model_data->one_percent_alerts = 0x40;
ret = of_property_read_u32(np, "valert-max", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->valert = ((val / 20) & 0xFF) << 8; /* LSB is 20mV. */
ret = of_property_read_u32(np, "valert-min", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->valert |= (val / 20) & 0xFF; /* LSB is 20mV. */
ret = of_property_read_u32(np, "vreset-threshold", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->vreset = ((val / 40) & 0xFE) << 8; /* LSB is 40mV. */
ret = of_property_read_u32(np, "vreset-disable", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->vreset |= (val & 0x01) << 8;
ret = of_property_read_u32(np, "hib-threshold", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->hibernate = (val & 0xFF) << 8;
ret = of_property_read_u32(np, "hib-active-threshold", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->hibernate |= val & 0xFF;
ret = of_property_read_u32(np, "rcomp", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->rcomp = val;
ret = of_property_read_u32(np, "rcomp-seg", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->rcomp_seg = val;
ret = of_property_read_u32(np, "soccheck-a", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->soccheck_A = val;
ret = of_property_read_u32(np, "soccheck-b", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->soccheck_B = val;
ret = of_property_read_u32(np, "ocvtest", &val);
if (ret < 0)
return ERR_PTR(ret);
model_data->ocvtest = val;
ret = of_property_read_u32_array(np, "data-tbl", val_array,
MAX17048_DATA_SIZE);
if (ret < 0)
return ERR_PTR(ret);
for (i = 0; i < MAX17048_DATA_SIZE; i++)
model_data->data_tbl[i] = val_array[i];
return pdata;
}
#else
static struct max17048_platform_data *max17048_parse_dt(struct device *dev)
{
return NULL;
}
#endif /* CONFIG_OF */
static int max17048_update_battery_status(struct battery_gauge_dev *bg_dev,
enum battery_charger_status status)
{
struct max17048_chip *chip = battery_gauge_get_drvdata(bg_dev);
if (status == BATTERY_CHARGING)
chip->status = POWER_SUPPLY_STATUS_CHARGING;
else if (status == BATTERY_CHARGING_DONE) {
chip->charge_complete = 1;
chip->soc = MAX17048_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 max17048_bg_ops = {
.update_battery_status = max17048_update_battery_status,
};
static struct battery_gauge_info max17048_bgi = {
.cell_id = 0,
.bg_ops = &max17048_bg_ops,
};
static int max17048_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct max17048_chip *chip;
int ret;
uint16_t version;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->client = client;
if (client->dev.of_node) {
chip->pdata = max17048_parse_dt(&client->dev);
if (IS_ERR(chip->pdata))
return PTR_ERR(chip->pdata);
} else {
chip->pdata = client->dev.platform_data;
if (!chip->pdata)
return -ENODATA;
}
max17048_data = chip;
mutex_init(&chip->mutex);
chip->shutdown_complete = 0;
i2c_set_clientdata(client, chip);
version = max17048_check_battery();
if (version < 0) {
ret = -ENODEV;
goto error;
}
dev_info(&client->dev, "MAX17048 Fuel-Gauge Ver 0x%x\n", version);
ret = max17048_initialize(chip);
if (ret < 0) {
dev_err(&client->dev, "Error: Initializing fuel-gauge\n");
goto error;
}
chip->battery.name = "battery";
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = max17048_get_property;
chip->battery.properties = max17048_battery_props;
chip->battery.num_properties = ARRAY_SIZE(max17048_battery_props);
chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
chip->lasttime_status = POWER_SUPPLY_STATUS_DISCHARGING;
chip->charge_complete = 0;
ret = power_supply_register(&client->dev, &chip->battery);
if (ret) {
dev_err(&client->dev, "failed: power supply register\n");
goto error;
}
max17048_bgi.tz_name = chip->pdata->tz_name;
chip->bg_dev = battery_gauge_register(&client->dev, &max17048_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, max17048_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 max17048_remove(struct i2c_client *client)
{
struct max17048_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 max17048_shutdown(struct i2c_client *client)
{
struct max17048_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 max17048_suspend(struct device *dev)
{
struct max17048_chip *chip = dev_get_drvdata(dev);
int ret;
cancel_delayed_work_sync(&chip->work);
ret = max17048_write_word(chip->client, MAX17048_HIBRT, 0xffff);
if (ret < 0) {
dev_err(dev, "failed in entering hibernate mode\n");
return ret;
}
return 0;
}
static int max17048_resume(struct device *dev)
{
struct max17048_chip *chip = dev_get_drvdata(dev);
int ret;
struct max17048_battery_model *mdata = chip->pdata->model_data;
ret = max17048_write_word(chip->client, MAX17048_HIBRT, mdata->hibernate);
if (ret < 0) {
dev_err(dev, "failed in exiting hibernate mode\n");
return ret;
}
schedule_delayed_work(&chip->work, MAX17048_DELAY);
return 0;
}
#endif /* CONFIG_PM */
static SIMPLE_DEV_PM_OPS(max17048_pm_ops, max17048_suspend, max17048_resume);
#ifdef CONFIG_OF
static const struct of_device_id max17048_dt_match[] = {
{ .compatible = "maxim,max17048" },
{ },
};
MODULE_DEVICE_TABLE(of, max17048_dt_match);
#endif
static const struct i2c_device_id max17048_id[] = {
{ "max17048", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17048_id);
static struct i2c_driver max17048_i2c_driver = {
.driver = {
.name = "max17048",
.of_match_table = of_match_ptr(max17048_dt_match),
.pm = &max17048_pm_ops,
},
.probe = max17048_probe,
.remove = max17048_remove,
.id_table = max17048_id,
.shutdown = max17048_shutdown,
};
static int __init max17048_init(void)
{
return i2c_add_driver(&max17048_i2c_driver);
}
fs_initcall_sync(max17048_init);
static void __exit max17048_exit(void)
{
i2c_del_driver(&max17048_i2c_driver);
}
module_exit(max17048_exit);
MODULE_AUTHOR("Chandler Zhang <chazhang@nvidia.com>");
MODULE_DESCRIPTION("MAX17048 Fuel Gauge");
MODULE_LICENSE("GPL");