Add Maxim ModelGauge ICs gauge driver for MAX17040/41/43/44/48/49/58/59 chips Signed-off-by: Vladimir Barinov <vladimir.barinov@xxxxxxxxxxxxxxxxxx> --- drivers/power/Kconfig | 9 drivers/power/Makefile | 1 drivers/power/modelgauge_battery.c | 838 +++++++++++++++++++++++ include/linux/platform_data/battery-modelgauge.h | 31 4 files changed, 879 insertions(+) Index: battery-2.6/drivers/power/Kconfig =================================================================== --- battery-2.6.orig/drivers/power/Kconfig 2014-02-02 01:29:07.434614235 +0400 +++ battery-2.6/drivers/power/Kconfig 2014-02-02 01:29:29.070614750 +0400 @@ -205,6 +205,15 @@ with MAX17042. This driver also supports max17047/50 chips which are improved version of max17042. +config BATTERY_MODELGAUGE + tristate "Maxim ModelGauge ICs fuel gauge" + depends on I2C + select REGMAP_I2C + help + ModelGauge(TM) is a Maxim algorithm incorporated in + MAX17040/41/43/44/48/49/58/59 fuel-gauges for lithium-ion (Li+) + batteries. + config BATTERY_Z2 tristate "Z2 battery driver" depends on I2C && MACH_ZIPIT2 Index: battery-2.6/drivers/power/Makefile =================================================================== --- battery-2.6.orig/drivers/power/Makefile 2014-02-02 01:29:07.462614237 +0400 +++ battery-2.6/drivers/power/Makefile 2014-02-02 01:29:12.978614368 +0400 @@ -32,6 +32,7 @@ obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o +obj-$(CONFIG_BATTERY_MODELGAUGE) += modelgauge_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o Index: battery-2.6/drivers/power/modelgauge_battery.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ battery-2.6/drivers/power/modelgauge_battery.c 2014-02-02 01:29:34.314614874 +0400 @@ -0,0 +1,838 @@ +/* + * Maxim ModelGauge ICs fuel gauge driver + * + * Author: Vladimir Barinov <source@xxxxxxxxxxxxxxxxxx> + * Copyright (C) 2013 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/platform_data/battery-modelgauge.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define DRV_NAME "modelgauge" + +/* Register offsets for ModelGauge ICs */ +#define MODELGAUGE_VCELL_REG 0x02 +#define MODELGAUGE_SOC_REG 0x04 +#define MODELGAUGE_MODE_REG 0x06 +#define MODELGAUGE_VERSION_REG 0x08 +#define MODELGAUGE_HIBRT_REG 0x0A +#define MODELGAUGE_CONFIG_REG 0x0C +#define MODELGAUGE_OCV_REG 0x0E +#define MODELGAUGE_VALRT_REG 0x14 +#define MODELGAUGE_CRATE_REG 0x16 +#define MODELGAUGE_VRESETID_REG 0x18 +#define MODELGAUGE_STATUS_REG 0x1A +#define MODELGAUGE_UNLOCK_REG 0x3E +#define MODELGAUGE_TABLE_REG 0x40 +#define MODELGAUGE_RCOMPSEG_REG 0x80 +#define MODELGAUGE_CMD_REG 0xFE + +/* MODE register bits */ +#define MODELGAUGE_MODE_QUICKSTART (1 << 14) +#define MODELGAUGE_MODE_ENSLEEP (1 << 13) +#define MODELGAUGE_MODE_HIBSTAT (1 << 12) + +/* CONFIG register bits */ +#define MODELGAUGE_CONFIG_SLEEP (1 << 7) +#define MODELGAUGE_CONFIG_ALSC (1 << 6) +#define MODELGAUGE_CONFIG_ALRT (1 << 5) +#define MODELGAUGE_CONFIG_ATHD_MASK 0x1f + +/* STATUS register bits */ +#define MODELGAUGE_STATUS_ENVR (1 << 14) +#define MODELGAUGE_STATUS_SC (1 << 13) +#define MODELGAUGE_STATUS_HD (1 << 12) +#define MODELGAUGE_STATUS_VR (1 << 11) +#define MODELGAUGE_STATUS_VL (1 << 10) +#define MODELGAUGE_STATUS_VH (1 << 9) +#define MODELGAUGE_STATUS_RI (1 << 8) + +/* VRESETID register bits */ +#define MODELGAUGE_VRESETID_DIS (1 << 8) + +#define MODELGAUGE_UNLOCK_VALUE 0x4a57 +#define MODELGAUGE_RESET_VALUE 0x5400 + +#define MODELGAUGE_RCOMP_UPDATE_DELAY 60000 + +/* Capacity threshold where an interrupt is generated on the ALRT pin */ +#define MODELGAUGE_EMPTY_ATHD 15 +/* Enable alert for 1% soc change */ +#define MODELGAUGE_SOC_CHANGE_ALERT 1 +/* Hibernate threshold (crate), where IC enters hibernate mode */ +#define MODELGAUGE_HIBRT_THD 20 +/* Active threshold (mV), where IC exits hibernate mode */ +#define MODELGAUGE_ACTIVE_THD 60 +/* Voltage (mV), when IC alerts if battery voltage less then undervoltage */ +#define MODELGAUGE_UV 0 +/* Voltage (mV), when IC alerts if battery voltage greater then overvoltage */ +#define MODELGAUGE_OV 5120 +/* + * Voltage threshold (mV) below which the IC resets itself. + * Used to detect battery removal and reinsertion + */ +#define MODELGAUGE_RV 0 + +enum chip_id { + ID_MAX17040, + ID_MAX17041, + ID_MAX17043, + ID_MAX17044, + ID_MAX17048, + ID_MAX17049, + ID_MAX17058, + ID_MAX17059, +}; + +struct modelgauge_priv { + struct device *dev; + struct regmap *regmap; + struct power_supply battery; + struct modelgauge_platform_data *pdata; + enum chip_id chip; + struct work_struct load_work; + struct delayed_work rcomp_work; + int soc_shift; +}; + +static void modelgauge_write_block(struct regmap *regmap, u8 adr, u8 size, + u16 *data) +{ + int k; + + /* RAM has different endianness then registers */ + for (k = 0; k < size; k += 2, adr += 2, data++) + regmap_write(regmap, adr, cpu_to_be16(*data)); +} + +static int modelgauge_lsb_to_uvolts(struct modelgauge_priv *priv, int lsb) +{ + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17043: + return (lsb >> 4) * 1250; /* 1.25mV per bit */ + case ID_MAX17041: + case ID_MAX17044: + return (lsb >> 4) * 2500; /* 2.5mV per bit */ + case ID_MAX17048: + case ID_MAX17058: + return lsb * 625 / 8; /* 78.125uV per bit */ + case ID_MAX17049: + case ID_MAX17059: + return lsb * 625 / 4; /* 156.25uV per bit */ + default: + return -EINVAL; + } +} + +static enum power_supply_property modelgauge_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static int modelgauge_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct modelgauge_priv *priv = container_of(psy, + struct modelgauge_priv, + battery); + struct regmap *regmap = priv->regmap; + struct modelgauge_platform_data *pdata = priv->pdata; + int reg; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (pdata && pdata->get_charging_status) + val->intval = pdata->get_charging_status(); + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(regmap, MODELGAUGE_VCELL_REG, ®); + if (ret < 0) + return ret; + + val->intval = modelgauge_lsb_to_uvolts(priv, reg); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + /* Unlock model access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, + MODELGAUGE_UNLOCK_VALUE); + ret = regmap_read(regmap, MODELGAUGE_OCV_REG, ®); + /* Lock model access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, 0); + if (ret < 0) + return ret; + + val->intval = modelgauge_lsb_to_uvolts(priv, reg); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(regmap, MODELGAUGE_SOC_REG, ®); + if (ret < 0) + return ret; + + val->intval = reg / (1 << priv->soc_shift); + break; + case POWER_SUPPLY_PROP_TEMP: + if (pdata && pdata->get_temperature) + val->intval = pdata->get_temperature(); + else + val->intval = 25; + break; + default: + return -EINVAL; + } + return 0; +} + +static void modelgauge_update_rcomp(struct modelgauge_priv *priv) +{ + struct regmap *regmap = priv->regmap; + struct modelgauge_platform_data *pdata = priv->pdata; + u16 rcomp; + int temp; + + if (pdata->get_temperature) + temp = pdata->get_temperature(); + else + temp = 25; + + if (!pdata->temp_co_up) + pdata->temp_co_up = -500; + if (!pdata->temp_co_down) + pdata->temp_co_down = -5000; + + rcomp = pdata->rcomp0; + if (temp > 20) + rcomp += (temp - 20) * pdata->temp_co_up / 1000; + else + rcomp += (temp - 20) * pdata->temp_co_down / 1000; + + /* Update RCOMP */ + regmap_update_bits(regmap, MODELGAUGE_CONFIG_REG, 0xff, rcomp << 8); +} + +static void modelgauge_update_rcomp_work(struct work_struct *work) +{ + struct modelgauge_priv *priv = container_of(work, + struct modelgauge_priv, + rcomp_work.work); + + modelgauge_update_rcomp(priv); + schedule_delayed_work(&priv->rcomp_work, + msecs_to_jiffies(MODELGAUGE_RCOMP_UPDATE_DELAY)); +} + +static int modelgauge_load_model(struct modelgauge_priv *priv) +{ + struct regmap *regmap = priv->regmap; + struct modelgauge_platform_data *pdata = priv->pdata; + int ret = -EINVAL; + int timeout, k; + int ocv, config, soc; + + /* Save CONFIG */ + regmap_read(regmap, MODELGAUGE_CONFIG_REG, &config); + + for (timeout = 0; timeout < 100; timeout++) { + /* Unlock model access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, + MODELGAUGE_UNLOCK_VALUE); + + /* Read OCV */ + regmap_read(regmap, MODELGAUGE_OCV_REG, &ocv); + if (ocv != 0xffff) + break; + } + + if (timeout >= 100) { + dev_err(priv->dev, "timeout to unlock model access\n"); + ret = -EIO; + goto exit; + } + + switch (priv->chip) { + case ID_MAX17058: + case ID_MAX17059: + /* Reset chip transaction does not provide ACK */ + regmap_write(regmap, MODELGAUGE_CMD_REG, + MODELGAUGE_RESET_VALUE); + msleep(150); + + for (timeout = 0; timeout < 100; timeout++) { + int reg; + + /* Unlock Model Access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, + MODELGAUGE_UNLOCK_VALUE); + + /* Read OCV */ + regmap_read(regmap, MODELGAUGE_OCV_REG, ®); + if (reg != 0xffff) + break; + } + + if (timeout >= 100) { + dev_err(priv->dev, "timeout to unlock model access\n"); + ret = -EIO; + goto exit; + } + break; + default: + break; + } + + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17041: + case ID_MAX17043: + case ID_MAX17044: + /* Write OCV */ + regmap_write(regmap, MODELGAUGE_OCV_REG, pdata->ocvtest); + /* Write RCOMP to its maximum value */ + regmap_write(regmap, MODELGAUGE_CONFIG_REG, 0xff00); + break; + default: + break; + } + + /* Write the model */ + modelgauge_write_block(regmap, MODELGAUGE_TABLE_REG, + MODELGAUGE_TABLE_SIZE, + (u16 *)pdata->model_data); + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: { + u16 buf[16]; + + if (!pdata->rcomp_seg) + pdata->rcomp_seg = 0x80; + + for (k = 0; k < 16; k++) + *buf = pdata->rcomp_seg; + + /* Write RCOMPSeg */ + modelgauge_write_block(regmap, MODELGAUGE_RCOMPSEG_REG, + 32, buf); + } + break; + default: + break; + } + + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17041: + case ID_MAX17043: + case ID_MAX17044: + /* Delay at least 150ms */ + msleep(150); + break; + default: + break; + } + + /* Write OCV */ + regmap_write(regmap, MODELGAUGE_OCV_REG, pdata->ocvtest); + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + /* Disable Hibernate */ + regmap_write(regmap, MODELGAUGE_HIBRT_REG, 0); + /* fall-through */ + case ID_MAX17058: + case ID_MAX17059: + /* Lock Model Access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, 0); + break; + default: + break; + } + + /* Delay between 150ms and 600ms */ + msleep(200); + + /* Read SOC Register and compare to expected result */ + regmap_read(regmap, MODELGAUGE_SOC_REG, &soc); + soc >>= 8; + if (soc >= pdata->soc_check_a && soc <= pdata->soc_check_b) + ret = 0; + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + case ID_MAX17058: + case ID_MAX17059: + /* Unlock model access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, + MODELGAUGE_UNLOCK_VALUE); + break; + default: + break; + } + + /* Restore CONFIG and OCV */ + regmap_write(regmap, MODELGAUGE_CONFIG_REG, config); + regmap_write(regmap, MODELGAUGE_OCV_REG, ocv); + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + /* Restore Hibernate */ + regmap_write(regmap, MODELGAUGE_HIBRT_REG, + (MODELGAUGE_HIBRT_THD << 8) | + MODELGAUGE_ACTIVE_THD); + break; + default: + break; + } + +exit: + /* Lock model access */ + regmap_write(regmap, MODELGAUGE_UNLOCK_REG, 0); + + /* Wait 150ms minimum */ + msleep(150); + + return ret; +} + +static void modelgauge_load_model_work(struct work_struct *work) +{ + struct modelgauge_priv *priv = container_of(work, + struct modelgauge_priv, + load_work); + struct regmap *regmap = priv->regmap; + int ret; + int timeout; + + for (timeout = 0; timeout < 10; timeout++) { + /* Load custom model data */ + ret = modelgauge_load_model(priv); + if (!ret) + break; + } + + if (timeout >= 10) { + dev_info(priv->dev, "failed to load custom model\n"); + return; + } + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + case ID_MAX17058: + case ID_MAX17059: + /* Clear reset indicator bit */ + regmap_update_bits(regmap, MODELGAUGE_STATUS_REG, + MODELGAUGE_STATUS_RI, 0); + break; + default: + break; + } +} + +static irqreturn_t modelgauge_irq_handler(int id, void *dev) +{ + struct modelgauge_priv *priv = dev; + + /* clear alert status bit */ + regmap_update_bits(priv->regmap, MODELGAUGE_CONFIG_REG, + MODELGAUGE_CONFIG_ALRT, 0); + + power_supply_changed(&priv->battery); + return IRQ_HANDLED; +} + +static int modelgauge_init(struct modelgauge_priv *priv) +{ + struct regmap *regmap = priv->regmap; + struct modelgauge_platform_data *pdata = priv->pdata; + int ret; + int reg; + + ret = regmap_read(regmap, MODELGAUGE_VERSION_REG, ®); + if (ret < 0) + return -ENODEV; + + dev_info(priv->dev, "IC production version 0x%04x\n", reg); + + /* SOC=0 means unrecoverable IC fault, reset is a workaround */ + regmap_read(regmap, MODELGAUGE_SOC_REG, ®); + if (!reg) { + dev_info(priv->dev, "Reset chip, SOC measurement stall\n"); + /* Reset chip transaction does not provide ACK */ + regmap_write(regmap, MODELGAUGE_CMD_REG, + MODELGAUGE_RESET_VALUE); + msleep(150); + } + + /* Default model uses 8 bits per percent */ + priv->soc_shift = 8; + + if (!priv->pdata) { + dev_info(priv->dev, "no platform data provided\n"); + return 0; + } + + switch (pdata->bits) { + case 19: + priv->soc_shift = 9; + break; + case 18: + default: + priv->soc_shift = 8; + break; + } + + /* Set RCOMP */ + modelgauge_update_rcomp(priv); + if (pdata->get_temperature) { + /* Schedule update RCOMP */ + schedule_delayed_work(&priv->rcomp_work, + msecs_to_jiffies(MODELGAUGE_RCOMP_UPDATE_DELAY)); + } + + /* Clear alert status bit, wake-up, set alert threshold */ + reg = 0; + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + reg |= MODELGAUGE_SOC_CHANGE_ALERT ? MODELGAUGE_CONFIG_ALSC : 0; + /* fall-through */ + case ID_MAX17043: + case ID_MAX17044: + case ID_MAX17058: + case ID_MAX17059: + reg |= 32 - (MODELGAUGE_EMPTY_ATHD << (priv->soc_shift - 8)); + break; + default: + break; + } + regmap_update_bits(regmap, MODELGAUGE_CONFIG_REG, + MODELGAUGE_CONFIG_ALRT | MODELGAUGE_CONFIG_SLEEP | + MODELGAUGE_CONFIG_ALSC | MODELGAUGE_CONFIG_ATHD_MASK, + reg); + + switch (priv->chip) { + case ID_MAX17048: + case ID_MAX17049: + /* Set Hibernate thresholds */ + reg = (MODELGAUGE_HIBRT_THD * 125 / 26) & 0xff; + reg <<= 8; + reg |= (MODELGAUGE_ACTIVE_THD * 4 / 5) & 0xff; + regmap_write(regmap, MODELGAUGE_HIBRT_REG, reg); + + /* Set undervoltage/overvoltage alerts */ + reg = (MODELGAUGE_UV / 20) & 0xff; + reg <<= 8; + reg |= (MODELGAUGE_OV / 20) & 0xff; + regmap_write(regmap, MODELGAUGE_VALRT_REG, reg); + /* fall-through */ + case ID_MAX17058: + case ID_MAX17059: + /* Disable sleep mode and quick start */ + regmap_write(regmap, MODELGAUGE_MODE_REG, 0); + + /* Setup reset voltage threshold */ + if (MODELGAUGE_RV) + reg = ((MODELGAUGE_RV / 40) & 0x7f) << 9; + else + reg = MODELGAUGE_VRESETID_DIS; + regmap_write(regmap, MODELGAUGE_VRESETID_REG, reg); + + /* Skip load model if reset indicator cleared */ + regmap_read(regmap, MODELGAUGE_STATUS_REG, ®); + /* Skip load custom model */ + if (!(reg & MODELGAUGE_STATUS_RI)) + return 0; + break; + default: + break; + } + + /* Schedule load custom model work */ + if (pdata->model_data) + schedule_work(&priv->load_work); + + return 0; +} + +static struct modelgauge_platform_data *modelgauge_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct modelgauge_platform_data *pdata; + struct property *prop; + int ret; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + ret = of_property_read_u8(np, "maxim,rcomp0", &pdata->rcomp0); + if (ret) + pdata->rcomp0 = 25; + + ret = of_property_read_u32(np, "maxim,temp-co-up", &pdata->temp_co_up); + if (ret) + pdata->temp_co_up = -500; + + ret = of_property_read_u32(np, "maxim,temp-co-down", + &pdata->temp_co_down); + if (ret) + pdata->temp_co_down = -5000; + + ret = of_property_read_u16(np, "maxim,ocvtest", &pdata->ocvtest); + if (ret) + pdata->ocvtest = 0; + + ret = of_property_read_u8(np, "maxim,soc-check-a", &pdata->soc_check_a); + if (ret) + pdata->soc_check_a = 0; + + ret = of_property_read_u8(np, "maxim,soc-check-b", &pdata->soc_check_b); + if (ret) + pdata->soc_check_b = 0; + + ret = of_property_read_u8(np, "maxim,bits", &pdata->bits); + if (ret) + pdata->bits = 18; + + ret = of_property_read_u16(np, "maxim,rcomp-seg", &pdata->rcomp_seg); + if (ret) + pdata->rcomp_seg = 0; + + prop = of_find_property(np, "maxim,model-data", NULL); + if (prop && prop->length == MODELGAUGE_TABLE_SIZE) { + pdata->model_data = devm_kzalloc(dev, MODELGAUGE_TABLE_SIZE, + GFP_KERNEL); + if (!pdata->model_data) + goto out; + + ret = of_property_read_u8_array(np, "maxim,model-data", + pdata->model_data, + MODELGAUGE_TABLE_SIZE); + if (ret) { + dev_warn(dev, "failed to get model_data %d\n", ret); + devm_kfree(dev, pdata->model_data); + pdata->model_data = NULL; + } + } + +out: + return pdata; +} + +static const struct regmap_config modelgauge_regmap = { + .reg_bits = 8, + .val_bits = 16, +}; + +static int modelgauge_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct modelgauge_priv *priv; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (client->dev.of_node) + priv->pdata = modelgauge_parse_dt(&client->dev); + else + priv->pdata = client->dev.platform_data; + + priv->dev = &client->dev; + priv->chip = id->driver_data; + + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &modelgauge_regmap); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->battery.name = "modelgauge_battery"; + priv->battery.type = POWER_SUPPLY_TYPE_BATTERY; + priv->battery.get_property = modelgauge_get_property; + priv->battery.properties = modelgauge_battery_props; + priv->battery.num_properties = ARRAY_SIZE(modelgauge_battery_props); + + INIT_WORK(&priv->load_work, modelgauge_load_model_work); + INIT_DELAYED_WORK(&priv->rcomp_work, modelgauge_update_rcomp_work); + + ret = modelgauge_init(priv); + if (ret) + return ret; + + ret = power_supply_register(&client->dev, &priv->battery); + if (ret) { + dev_err(priv->dev, "failed: power supply register\n"); + goto err_supply; + } + + if (client->irq) { + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17041: + dev_err(priv->dev, "alert line is not supported\n"); + ret = -EINVAL; + goto err_irq; + default: + ret = devm_request_threaded_irq(priv->dev, client->irq, + NULL, + modelgauge_irq_handler, + IRQF_TRIGGER_FALLING, + priv->battery.name, + priv); + if (ret) { + dev_err(priv->dev, "failed to request irq %d\n", + client->irq); + goto err_irq; + } + } + } + + return 0; + +err_irq: + power_supply_unregister(&priv->battery); +err_supply: + cancel_work_sync(&priv->load_work); + cancel_delayed_work_sync(&priv->rcomp_work); + return ret; +} + +static int modelgauge_remove(struct i2c_client *client) +{ + struct modelgauge_priv *priv = i2c_get_clientdata(client); + + cancel_work_sync(&priv->load_work); + cancel_delayed_work_sync(&priv->rcomp_work); + + power_supply_unregister(&priv->battery); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int modelgauge_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct modelgauge_priv *priv = i2c_get_clientdata(client); + struct modelgauge_platform_data *pdata = priv->pdata; + + if (pdata && pdata->get_temperature) + cancel_delayed_work_sync(&priv->rcomp_work); + + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17041: + return 0; + default: + if (client->irq) { + disable_irq(client->irq); + enable_irq_wake(client->irq); + } + } + + return 0; +} + +static int modelgauge_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct modelgauge_priv *priv = i2c_get_clientdata(client); + struct modelgauge_platform_data *pdata = priv->pdata; + + if (pdata && pdata->get_temperature) + schedule_delayed_work(&priv->rcomp_work, + msecs_to_jiffies(MODELGAUGE_RCOMP_UPDATE_DELAY)); + + switch (priv->chip) { + case ID_MAX17040: + case ID_MAX17041: + return 0; + default: + if (client->irq) { + disable_irq_wake(client->irq); + enable_irq(client->irq); + } + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(modelgauge_pm_ops, + modelgauge_suspend, modelgauge_resume); +#define MODELGAUGE_PM_OPS (&modelgauge_pm_ops) +#else +#define MODELGAUGE_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id modelgauge_match[] = { + { .compatible = "maxim,max17040" }, + { .compatible = "maxim,max17041" }, + { .compatible = "maxim,max17043" }, + { .compatible = "maxim,max17044" }, + { .compatible = "maxim,max17048" }, + { .compatible = "maxim,max17049" }, + { .compatible = "maxim,max17058" }, + { .compatible = "maxim,max17059" }, + { }, +}; +MODULE_DEVICE_TABLE(of, modelgauge_match); + +static const struct i2c_device_id modelgauge_id[] = { + { "max17040", ID_MAX17040 }, + { "max17041", ID_MAX17041 }, + { "max17043", ID_MAX17043 }, + { "max17044", ID_MAX17044 }, + { "max17048", ID_MAX17048 }, + { "max17049", ID_MAX17049 }, + { "max17058", ID_MAX17058 }, + { "max17059", ID_MAX17059 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, modelgauge_id); + +static struct i2c_driver modelgauge_i2c_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(modelgauge_match), + .pm = MODELGAUGE_PM_OPS, + }, + .probe = modelgauge_probe, + .remove = modelgauge_remove, + .id_table = modelgauge_id, +}; +module_i2c_driver(modelgauge_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("Maxim ModelGauge fuel gauge"); Index: battery-2.6/include/linux/platform_data/battery-modelgauge.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ battery-2.6/include/linux/platform_data/battery-modelgauge.h 2014-02-02 01:29:34.314614874 +0400 @@ -0,0 +1,31 @@ +/* + * Maxim ModelGauge ICs fuel gauge driver header file + * + * Author: Vladimir Barinov <source@xxxxxxxxxxxxxxxxxx> + * Copyright (C) 2013 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __BATTERY_MODELGAUGE_H_ +#define __BATTERY_MODELGAUGE_H_ + +#define MODELGAUGE_TABLE_SIZE 64 + +struct modelgauge_platform_data { + u8 rcomp0; + int temp_co_up; + int temp_co_down; + u16 ocvtest; + u8 soc_check_a; + u8 soc_check_b; + u8 bits; + u16 rcomp_seg; + u8 *model_data; + int (*get_temperature)(void); + int (*get_charging_status)(void); +}; +#endif -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html