diff options
Diffstat (limited to 'drivers/power/max17042_fuelgauge_u1.c')
-rw-r--r-- | drivers/power/max17042_fuelgauge_u1.c | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/drivers/power/max17042_fuelgauge_u1.c b/drivers/power/max17042_fuelgauge_u1.c new file mode 100644 index 00000000000..1434c72063a --- /dev/null +++ b/drivers/power/max17042_fuelgauge_u1.c @@ -0,0 +1,1022 @@ +/* + * max17042-fuelgauge.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2010 Samsung Electronics + * + * based on max17040_battery.c + * + * <ms925.kim@samsung.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 <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/power/max17042_fuelgauge_u1.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +struct max17042_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply battery; + struct max17042_platform_data *pdata; + + int vcell; /* battery voltage */ + int avgvcell; /* average battery voltage */ + int vfocv; /* calculated battery voltage */ + int soc; /* battery capacity */ + int raw_soc; /* fuel gauge raw data */ + int temperature; + int fuel_alert_soc; /* fuel alert threshold */ + bool is_fuel_alerted; /* fuel alerted */ + struct wake_lock fuel_alert_wake_lock; + bool is_enable; /* can be fuel guage enable */ + +#ifdef RECAL_SOC_FOR_MAXIM + int cnt; + int recalc_180s; + int boot_cnt; +#endif +}; + +static int max17042_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (val->intval) { + case 0: /*vcell */ + val->intval = chip->vcell; + break; + case 1: /*vfocv */ + val->intval = chip->vfocv; + break; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = chip->avgvcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + switch (val->intval) { + case 0: /*normal soc */ + val->intval = chip->soc; + break; + case 1: /*raw soc */ + val->intval = chip->raw_soc; + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = chip->temperature / 100; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17042_write_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static int max17042_read_reg(struct i2c_client *client, int reg, u8 * buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d (reg = 0x%x)\n", + __func__, ret, reg); + + return ret; +} + +static void max17042_write_reg_array(struct i2c_client *client, + struct max17042_reg_data *data, int size) +{ + int i; + + for (i = 0; i < size; i += 3) + max17042_write_reg(client, (data + i)->reg_addr, + ((u8 *) (data + i)) + 1); +} + +static void max17042_init_regs(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + /* struct max17042_platform_data *pdata = client->dev.platform_data; */ + + dev_info(&client->dev, "%s\n", __func__); + + if (chip->is_enable) { + /* max17042_write_reg_array(client, pdata->init, + pdata->init_size); */ + + if (max17042_read_reg(client, MAX17042_REG_FILTERCFG, data) < 0) + return; + + /* Clear average vcell (12 sec) */ + data[0] &= 0x8f; + + max17042_write_reg(client, MAX17042_REG_FILTERCFG, data); + } +} + +static void max17042_alert_init(struct i2c_client *client) +{ + struct max17042_platform_data *pdata = client->dev.platform_data; + + dev_info(&client->dev, "%s\n", __func__); + + max17042_write_reg_array(client, pdata->alert_init, + pdata->alert_init_size); +} + +static int max17042_read_vfocv(struct i2c_client *client) +{ + u8 data[2]; + u32 vfocv = 0; + struct max17042_chip *chip = i2c_get_clientdata(client); + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VFOCV, data) < 0) + return -1; + + vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + chip->vfocv = vfocv; + + return vfocv; + } else + return 4000; + +} + +#if 0 +static int max17042_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + u32 vfsoc = 0; + +#ifndef NO_READ_I2C_FOR_MAXIM + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return -1; + + vfsoc = data[1]; + + return vfsoc; +#else + return 100; +#endif +} +#else +static void max17042_get_soc(struct i2c_client *client); +static int max17042_read_vfsoc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + max17042_get_soc(client); + + return chip->soc; +} +#endif + +static void max17042_reset_soc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + dev_info(&client->dev, "%s : Before quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + + if (max17042_read_reg(client, MAX17042_REG_MISCCFG, data) < 0) + return; + + /* Set bit10 makes quick start */ + data[1] |= (0x1 << 2); + max17042_write_reg(client, MAX17042_REG_MISCCFG, data); + + msleep(500); + + dev_info(&client->dev, "%s : After quick-start - " + "VfOCV(%d), VfSOC(%d)\n", + __func__, max17042_read_vfocv(client), + max17042_read_vfsoc(client)); + } + + return; +} + +static void max17042_get_vcell(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VCELL, data) < 0) + return; + + chip->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + if (max17042_read_reg(client, MAX17042_REG_AVGVCELL, data) < 0) + return; + + chip->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + } else { + chip->vcell = 4000000; + chip->avgvcell = 4000000; + } +} + +static int max17042_recalc_soc(struct i2c_client *client, int boot_cnt) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + struct power_supply *psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + u8 data[2]; + u16 temp_vfocv = 0; + int soc; + + if (chip->is_enable) { + /* AverageVcell : 175.8ms * 256 = 45s sampling */ + if (boot_cnt < 4) /* 30s using VCELL */ + max17042_read_reg(client, MAX17042_REG_VCELL, data); + else + max17042_read_reg(client, MAX17042_REG_AVGVCELL, data); + temp_vfocv = (data[1] << 8); + temp_vfocv |= data[0]; + + if (psy != NULL) { + psy->get_property(psy, + POWER_SUPPLY_PROP_ONLINE, &value); + + if (value.intval == 0) + temp_vfocv = temp_vfocv + 0x0380; /* +70mV */ + else + temp_vfocv = temp_vfocv - 0x0380; /* -70mV */ + + dev_info(&client->dev, "cable = %d, ", value.intval); + } else + temp_vfocv = temp_vfocv + 0x0380; /* +70mV */ + + data[1] = temp_vfocv >> 8; + data[0] = 0x00FF & temp_vfocv; + dev_info(&client->dev, "forced write to vfocv %d mV\n", + (temp_vfocv >> 4) * 125 / 100); + max17042_write_reg(client, MAX17042_REG_VFOCV, data); + + msleep(200); + + max17042_read_reg(client, MAX17042_REG_SOC_VF, data); + soc = min((int)data[1], 100); + + max17042_read_reg(client, MAX17042_REG_VCELL, data); + dev_info(&client->dev, "new vcell = %d, vfocv = %d, soc = %d\n", + ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000, + max17042_read_vfocv(client), soc); + } else + soc = 70; + + return soc; +} + +static void max17042_get_soc(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + int soc; + int diff = 0; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_SOC_VF, data) < 0) + return; + dev_info(&chip->client->dev, "%s : soc(%02x%02x)\n", + __func__, data[1], data[0]); + + soc = (data[1] * 100) + (data[0] * 100 / 256); + + chip->raw_soc = min(soc / 100, 100); + +#ifdef RECAL_SOC_FOR_MAXIM + if (chip->pdata->need_soc_recal()) { + dev_info(&client->dev, + "%s : recalculate soc\n", __func__); + + /*modified 3.6V cut-off */ + /*raw 3% ~ 95% */ + soc = (soc < 300) ? 0 : ((soc - 300) * 100 / 9200) + 1; + + if (chip->boot_cnt < 4) + chip->boot_cnt++; + + dev_info(&client->dev, + "vcell = %d, vfocv = %d, soc = %d\n", + chip->vcell, max17042_read_vfocv(client), soc); + + if (soc < 5) { + dev_info(&client->dev, + "recalc force soc = %d, diff = %d!\n", + soc, diff); + chip->recalc_180s = 1; + } + + /*when using fuelgauge level, diff is calculated */ + if (chip->recalc_180s == 0 && chip->boot_cnt != 1) { + if (chip->soc > soc) + diff = chip->soc - soc; + else + diff = soc - chip->soc; + } else /*during recalc, diff is not valid */ + diff = 0; + + if (diff > 10) { + dev_info(&client->dev, + "recalc 180s soc = %d, diff = %d!\n", + soc, diff); + chip->recalc_180s = 1; + } + + if (chip->recalc_180s == 1) { + if (chip->cnt < 18) { + chip->cnt++; + soc = max17042_recalc_soc(client, + chip->boot_cnt); + } else { + chip->recalc_180s = 0; + chip->cnt = 0; + } + } + } else { + /*modified 3.4V cut-off */ + /*raw 1.6% ~ 97.6% */ + soc = (soc > 100) ? ((soc - 60) * 100 / 9700) : 0; + /*raw 1.5% ~ 95% */ + /*soc = (soc < 150) ? 0 : ((soc - 150) * 100 / 9350) + 1; */ + } +#else + /* adjusted soc by adding 0.45 */ + soc += 45; + soc /= 100; +#endif + } else + soc = 70; + + soc = min(soc, 100); + + chip->soc = soc; + + dev_info(&client->dev, "%s : use raw (%d), soc (%d)\n", + __func__, chip->raw_soc, soc); +} + +static void max17042_get_temperature(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + s32 temper = 0; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_TEMPERATURE, data) < 0) + return; + + /* data[] store 2's compliment format number */ + if (data[1] & (0x1 << 7)) { + /* Negative */ + temper = ((~(data[1])) & 0xFF) + 1; + temper *= (-1000); + } else { + temper = data[1] & 0x7F; + temper *= 1000; + temper += data[0] * 39 / 10; + } + + dev_dbg(&client->dev, "%s: MAX17042 Temperature = %d\n", + __func__, chip->temperature); + } else + temper = 40; + + chip->temperature = temper; +} + +static void max17042_get_version(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + + if (chip->is_enable) { + if (max17042_read_reg(client, MAX17042_REG_VERSION, data) < 0) + return; + + dev_info(&client->dev, "MAX17042 Fuel-Gauge Ver %d%d\n", + data[0], data[1]); + } +} + +static int max17042_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + u8 data[2]; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP: + if (chip->pdata->enable_gauging_temperature && + chip->is_enable) { + data[0] = 0; + data[1] = val->intval; + max17042_write_reg(chip->client, + MAX17042_REG_TEMPERATURE, data); + } else + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void max17042_work(struct work_struct *work) +{ + struct max17042_chip *chip; + + chip = container_of(work, struct max17042_chip, work.work); + + max17042_get_vcell(chip->client); + max17042_read_vfocv(chip->client); + max17042_get_soc(chip->client); + + if (chip->pdata->enable_gauging_temperature) + max17042_get_temperature(chip->client); + + /* polling check for fuel alert for booting in low battery*/ + if (chip->raw_soc < chip->fuel_alert_soc) { + if (!(chip->is_fuel_alerted) && + chip->pdata->low_batt_cb) { + wake_lock(&chip->fuel_alert_wake_lock); + chip->pdata->low_batt_cb(); + chip->is_fuel_alerted = true; + + dev_info(&chip->client->dev, + "fuel alert activated by polling check (raw:%d)\n", + chip->raw_soc); + } else + dev_info(&chip->client->dev, + "fuel alert already activated (raw:%d)\n", + chip->raw_soc); + } else if (chip->raw_soc == chip->fuel_alert_soc) { + if (chip->is_fuel_alerted) { + wake_unlock(&chip->fuel_alert_wake_lock); + chip->is_fuel_alerted = false; + + dev_info(&chip->client->dev, + "fuel alert deactivated by polling check (raw:%d)\n", + chip->raw_soc); + } + } + +#ifdef LOG_REG_FOR_MAXIM + { + int reg; + int i; + u8 data[2]; + u8 buf[1024]; + + i = 0; + for (reg = 0; reg < 0x50; reg++) { + max17042_read_reg(chip->client, reg, data); + i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]); + } + printk(KERN_INFO "%s", buf); + + i = 0; + for (reg = 0xe0; reg < 0x100; reg++) { + max17042_read_reg(chip->client, reg, data); + i += sprintf(buf + i, "0x%02x%02xh,", data[1], data[0]); + } + printk(KERN_INFO "%s", buf); + } +#endif + + schedule_delayed_work(&chip->work, MAX17042_LONG_DELAY); +} + +static enum power_supply_property max17042_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +#define SEC_FG_ATTR(_name) \ +{ \ + .attr = { .name = #_name, \ + .mode = 0664 }, \ + .show = sec_fg_show_property, \ + .store = sec_fg_store, \ +} + +static struct device_attribute sec_fg_attrs[] = { + SEC_FG_ATTR(fg_reset_soc), + SEC_FG_ATTR(fg_read_soc), +}; + +enum { + FG_RESET_SOC = 0, + FG_READ_SOC, +}; + +static ssize_t sec_fg_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int i = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_READ_SOC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + max17042_read_vfsoc(chip->client)); + break; + default: + i = -EINVAL; + } + + return i; +} + +static ssize_t sec_fg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max17042_chip *chip = container_of(psy, + struct max17042_chip, + battery); + + int x = 0; + int ret = 0; + const ptrdiff_t off = attr - sec_fg_attrs; + + switch (off) { + case FG_RESET_SOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x == 1) + max17042_reset_soc(chip->client); + ret = count; + } +#if 1 +/* tester requested to sustain unity with parental models but + keep source code for later */ + { + struct power_supply *psy = + power_supply_get_by_name("battery"); + union power_supply_propval value; + + if (!psy) { + pr_err("%s: fail to get battery ps\n", __func__); + return -ENODEV; + } + + value.intval = 0; /* dummy value */ + psy->set_property(psy, + POWER_SUPPLY_PROP_CAPACITY, &value); + } +#endif + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int fuelgauge_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 fg_attrs_failed; + } + goto succeed; + +fg_attrs_failed: + while (i--) + device_remove_file(dev, &sec_fg_attrs[i]); +succeed: + return rc; +} + +static bool max17042_check_status(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + u8 data[2]; + bool ret = false; + + if (chip->is_enable) { + /* check if Smn was generated */ + if (max17042_read_reg(client, MAX17042_REG_STATUS, data) < 0) + return ret; + + dev_info(&client->dev, "%s : status_reg(%02x%02x)\n", + __func__, data[1], data[0]); + + /* minimum SOC threshold exceeded. */ + if (data[1] & (0x1 << 2)) + ret = true; + + /* clear status reg */ + if (!ret) { + data[1] = 0; + max17042_write_reg(client, MAX17042_REG_STATUS, data); + msleep(200); + } + } else + ret = true; + + return ret; +} + +static irqreturn_t max17042_irq_thread(int irq, void *irq_data) +{ + u8 data[2]; + bool max17042_alert_status = false; + struct max17042_chip *chip = irq_data; + + /* update SOC */ + max17042_get_soc(chip->client); + + max17042_alert_status = max17042_check_status(chip->client); + + if (max17042_alert_status /* && !(chip->is_fuel_alerted)*/) { + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return IRQ_HANDLED; + + data[1] |= (0x1 << 3); + + if (chip->pdata->low_batt_cb && !(chip->is_fuel_alerted)) { + wake_lock(&chip->fuel_alert_wake_lock); + chip->pdata->low_batt_cb(); + chip->is_fuel_alerted = true; + } else + dev_err(&chip->client->dev, + "failed to call low_batt_cb()\n"); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, + "%s : low batt alerted!! config_reg(%02x%02x)\n", + __func__, data[1], data[0]); + } else if (!max17042_alert_status /* && (chip->is_fuel_alerted)*/) { + if (chip->is_fuel_alerted) + wake_unlock(&chip->fuel_alert_wake_lock); + chip->is_fuel_alerted = false; + + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return IRQ_HANDLED; + + data[1] &= (~(0x1 << 3)); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, + "%s : low batt released!! config_reg(%02x%02x)\n", + __func__, data[1], data[0]); + } + + max17042_read_reg(chip->client, MAX17042_REG_VCELL, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_TEMPERATURE, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_TEMPERATURE(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_CONFIG(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_VFOCV, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VFOCV(%02x%02x)\n", + __func__, data[1], data[0]); + + max17042_read_reg(chip->client, MAX17042_REG_SOC_VF, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_SOC_VF(%02x%02x)\n", + __func__, data[1], data[0]); + + dev_err(&chip->client->dev, + "%s : PMIC IRQ (%d), FUEL GAUGE IRQ (%d)\n", + __func__, gpio_get_value(GPIO_PMIC_IRQ), + gpio_get_value(chip->pdata->alert_gpio)); + +#if 0 + **max17042_read_reg(chip->client, MAX17042_REG_STATUS, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_STATUS(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_VALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_TALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_TALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_SALRT_TH, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_SALRT_TH(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_AVGVCELL, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_AVGVCELL(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_VERSION, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_VERSION(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_LEARNCFG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_LEARNCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_MISCCFG, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_MISCCFG(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_CGAIN, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_CGAIN(%02x%02x)\n", + __func__, data[1], data[0]); + + **max17042_read_reg(chip->client, MAX17042_REG_RCOMP, data); + dev_info(&chip->client->dev, "%s : MAX17042_REG_RCOMP(%02x%02x)\n", + __func__, data[1], data[0]); +#endif + + return IRQ_HANDLED; +} + +static int max17042_irq_init(struct max17042_chip *chip) +{ + int ret; + u8 data[2]; + + /* 1. Set max17042 alert configuration. */ + max17042_alert_init(chip->client); + + chip->is_fuel_alerted = false; + + /* 2. Request irq */ + if (chip->pdata->alert_irq) { + ret = request_threaded_irq(chip->pdata->alert_irq, NULL, + max17042_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max17042 fuel alert", chip); + if (ret) { + dev_err(&chip->client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(chip->pdata->alert_irq); + if (ret < 0) + dev_err(&chip->client->dev, + "failed to enable wakeup src %d\n", ret); + } + + if (max17042_read_reg(chip->client, MAX17042_REG_CONFIG, data) + < 0) + return -1; + + /*Enable Alert (Aen = 1) */ + data[0] |= (0x1 << 2); + + max17042_write_reg(chip->client, MAX17042_REG_CONFIG, data); + + dev_info(&chip->client->dev, "%s : config_reg(%02x%02x) irq(%d)\n", + __func__, data[1], data[0], chip->pdata->alert_irq); + + return 0; +} + +static int __devinit max17042_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17042_chip *chip; + int i; + struct max17042_reg_data *data; + int ret; + u8 i2c_data[2]; + + dev_info(&client->dev, "%s: MAX17042 Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "fuelgauge"; + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; + chip->battery.get_property = max17042_get_property; + chip->battery.set_property = max17042_set_property; + chip->battery.properties = max17042_battery_props; + chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props); + +#ifdef RECAL_SOC_FOR_MAXIM + chip->cnt = 0; + chip->recalc_180s = 0; + chip->boot_cnt = 0; +#endif + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + kfree(chip); + return ret; + } + + if (max17042_read_reg(client, MAX17042_REG_VERSION, i2c_data) < 0) + chip->is_enable = false; + else + chip->is_enable = true; + + dev_info(&client->dev, "%s : is enable (%d)\n", + __func__, chip->is_enable); + + /* initialize fuel gauge registers */ + max17042_init_regs(client); + + if (chip->is_enable) { + /* register low batt intr */ + chip->pdata->alert_irq = gpio_to_irq(chip->pdata->alert_gpio); + + wake_lock_init(&chip->fuel_alert_wake_lock, WAKE_LOCK_SUSPEND, + "fuel_alerted"); + + data = chip->pdata->alert_init; + for (i = 0; i < chip->pdata->alert_init_size; i += 3) + if ((data + i)->reg_addr == + MAX17042_REG_SALRT_TH) + chip->fuel_alert_soc = + (data + i)->reg_data1; + + dev_info(&client->dev, "fuel alert soc (%d)\n", + chip->fuel_alert_soc); + + ret = max17042_irq_init(chip); + if (ret) + goto err_kfree; + } + + max17042_get_version(client); + + /* create fuelgauge attributes */ + fuelgauge_create_attrs(chip->battery.dev); + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17042_work); + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + + dev_info(&client->dev, "%s: MAX17042 Driver Loaded\n", __func__); + + return 0; + +err_kfree: + wake_lock_destroy(&chip->fuel_alert_wake_lock); + kfree(chip); + return ret; +} + +static int __devexit max17042_remove(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + cancel_delayed_work(&chip->work); + wake_lock_destroy(&chip->fuel_alert_wake_lock); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM + +static int max17042_suspend(struct i2c_client *client, pm_message_t state) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17042_resume(struct i2c_client *client) +{ + struct max17042_chip *chip = i2c_get_clientdata(client); + + schedule_delayed_work(&chip->work, MAX17042_SHORT_DELAY); + return 0; +} + +#else + +#define max17042_suspend NULL +#define max17042_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17042_id[] = { + {"max17042", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max17042_id); + +static struct i2c_driver max17042_i2c_driver = { + .driver = { + .name = "max17042", + }, + .probe = max17042_probe, + .remove = __devexit_p(max17042_remove), + .suspend = max17042_suspend, + .resume = max17042_resume, + .id_table = max17042_id, +}; + +static int __init max17042_init(void) +{ + return i2c_add_driver(&max17042_i2c_driver); +} + +module_init(max17042_init); + +static void __exit max17042_exit(void) +{ + i2c_del_driver(&max17042_i2c_driver); +} + +module_exit(max17042_exit); + +MODULE_AUTHOR("<ms925.kim@samsung.com>"); +MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); +MODULE_LICENSE("GPL"); |