From d6f2f4ae4e593067dc59e2ac63f022434199f673 Mon Sep 17 00:00:00 2001 From: sbrissen Date: Wed, 23 Oct 2013 13:19:08 -0400 Subject: Add support for Note 8 (N5100 and N5110) Change-Id: I6c9798682f9f6349b37cb452353bd0c0e6958401 --- drivers/battery/max17047_fuelgauge_c.c | 2217 ++++++++++++++++++++++++++++++++ 1 file changed, 2217 insertions(+) create mode 100755 drivers/battery/max17047_fuelgauge_c.c (limited to 'drivers/battery/max17047_fuelgauge_c.c') diff --git a/drivers/battery/max17047_fuelgauge_c.c b/drivers/battery/max17047_fuelgauge_c.c new file mode 100755 index 0000000..4b26cb0 --- /dev/null +++ b/drivers/battery/max17047_fuelgauge_c.c @@ -0,0 +1,2217 @@ +/* + * max17047_fuelgauge.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son + * + * based on max17040_battery.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_TARGET_LOCALE_KOR) || defined(CONFIG_MACH_M0_CTC)\ + || defined(CONFIG_MACH_T0_CHN_CTC) +#ifdef CONFIG_DEBUG_FS +#include +#endif +#endif + +/* TRIM ERROR DETECTION */ +#define USE_TRIM_ERROR_DETECTION + +#define CONFIG_FUELGAUGE_MAX17047_COULOMB_COUNTING + +/* MAX17047 Registers. */ +#define MAX17047_REG_STATUS 0x00 +#define MAX17047_REG_VALRT_TH 0x01 +#define MAX17047_REG_TALRT_TH 0x02 +#define MAX17047_REG_SALRT_TH 0x03 +#define MAX17047_REG_REMCAP_REP 0x05 +#define MAX17047_SOCREP 0x06 +#define MAX17047_REG_VCELL 0x09 +#define MAX17047_REG_FULLCAP 0x10 +#define MAX17047_REG_TEMPERATURE 0x08 +#define MAX17047_REG_CYCLES 0x17 +#define MAX17047_REG_DESIGNCAP_REG 0x18 +#define MAX17047_REG_AVGVCELL 0x19 +#define MAX17047_REG_CONFIG 0x1D +#define MAX17047_REG_VERSION 0x21 +#define MAX17047_REG_FULLCAP_NOM 0x23 +#define MAX17047_REG_LEARNCFG 0x28 +#define MAX17047_REG_FILTERCFG 0x29 +#define MAX17047_REG_MISCCFG 0x2B +#define MAX17047_REG_CGAIN 0x2E +#define MAX17047_REG_RCOMP 0x38 +#define MAX17047_REG_VFOCV 0xFB +#define MAX17047_REG_SOC_VF 0xFF +#define MAX17047_REG_FULLCAP 0x10 +#define MAX17047_REG_FULLCAPNOM 0x23 +#define MAX17047_REG_CURRENT 0x0A +#define MAX17047_REG_AVG_CURRENT 0x0B +#define MAX17047_REG_DQACC 0x45 +#define MAX17047_REG_DPACC 0x46 +#define MAX17047_REG_VFSOC 0xFF + + + + +/* Polling work */ +#undef DEBUG_FUELGAUGE_POLLING +#define MAX17047_POLLING_INTERVAL 10000 + +/* rcomp update */ +#if defined(CONFIG_MACH_C1_KOR_SKT) || \ + defined(CONFIG_MACH_C1_KOR_KT) || \ + defined(CONFIG_MACH_C1_KOR_LGT) +#define CHECK_RCOMP_UPDATE +#define MAX17047_NEW_RCOMP 0x0070 +#endif + +/* adjust full soc */ +#if defined(CONFIG_MACH_T0) +#if defined(CONFIG_TARGET_LOCALE_KOR) +#define FULL_SOC_DEFAULT 9700 +#define FULL_SOC_LOW 9600 +#define FULL_SOC_HIGH 10050 +#define KEEP_FULL_SOC 100 /* 1.0% */ +#else +#define FULL_SOC_DEFAULT 9650 +#define FULL_SOC_LOW 9500 +#define FULL_SOC_HIGH 10050 +#define KEEP_FULL_SOC 100 /* 1.0% */ +#endif +#elif defined(CONFIG_MACH_GC1) +#define FULL_SOC_DEFAULT 9700 +#define FULL_SOC_LOW 9650 +#define FULL_SOC_HIGH 10000 +#define KEEP_FULL_SOC 110 /* 1.1% */ +#elif defined(CONFIG_MACH_KONA) +#define FULL_SOC_DEFAULT 9900 +#define FULL_SOC_LOW 9700 +#define FULL_SOC_HIGH 10000 +#define KEEP_FULL_SOC 100 // /* 1.0% */ +#else /* M0, C1,,, */ +#define FULL_SOC_DEFAULT 9850 +#define FULL_SOC_LOW 9700 +#define FULL_SOC_HIGH 10000 +#define KEEP_FULL_SOC 100 /* 1.0% */ +#endif +#define KEEP_SOC_DEFAULT 50 /* 0.5% */ + +struct max17047_fuelgauge_data { + struct i2c_client *client; + struct max17047_platform_data *pdata; + + struct power_supply fuelgauge; + + /* workqueue */ + struct delayed_work update_work; +#ifdef DEBUG_FUELGAUGE_POLLING + struct delayed_work polling_work; +#endif + + /* mutex */ + struct mutex irq_lock; + + /* wakelock */ + struct wake_lock update_wake_lock; + + unsigned int irq; + + unsigned int vcell; + unsigned int avgvcell; + unsigned int vfocv; + unsigned int soc; + unsigned int rawsoc; + unsigned int temperature; + +/*#if defined(CONFIG_FUELGAUGE_MAX17047_COULOMB_COUNTING)*/ + u32 previous_fullcap; + u32 previous_vffullcap; + + /* low battery comp */ + int low_batt_comp_cnt[LOW_BATT_COMP_RANGE_NUM][LOW_BATT_COMP_LEVEL_NUM]; + + /* low battery boot */ + int low_batt_boot_flag; + bool is_low_batt_alarm; + + /* miscellaneous */ + unsigned long fullcap_check_interval; + int full_check_flag; + bool is_first_check; +/*#endif*/ + + /* adjust full soc */ + int full_soc; + +#if defined(CONFIG_MACH_GC1) + int prev_status; +#endif + +#ifdef USE_TRIM_ERROR_DETECTION + /* trim error state */ + bool trim_err; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *fg_debugfs_dir; +#endif + +#ifdef CONFIG_HIBERNATION + u8 *reg_dump; +#endif + +}; + +static struct battery_data_t fg_battery_data[] = { + /* SDI battery data */ + { + .Capacity = 0x2530, + .low_battery_comp_voltage = 3500, + .low_battery_table = { + /* range, slope, offset */ + {-5000, 0, 0}, /* dummy for top limit */ + {-1250, 0, 3320}, + {-750, 97, 3451}, + {-100, 96, 3461}, + {0, 0, 3456}, + }, + .temp_adjust_table = { + /* range, slope, offset */ + {47000, 122, 8950}, + {60000, 200, 51000}, + {100000, 0, 0}, /* dummy for top limit */ + }, + .type_str = "SDI", + } +}; + + +#define MAX17047_CAPACITY 0x2530 + +static int max17047_i2c_read(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf); + if (ret < 0) + pr_err("%s: err %d, reg: 0x%02x\n", __func__, ret, reg); + + return ret; +} + +static int max17047_i2c_write(struct i2c_client *client, int reg, u8 *buf) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, 2, buf); + if (ret < 0) + pr_err("%s: err %d, reg: 0x%02x, data: 0x%x%x\n", __func__, + ret, reg, buf[0], buf[1]); + + return ret; +} + +static int fg_read_register(struct i2c_client *client, + u8 addr) +{ + u8 data[2]; + + if (max17047_i2c_read(client, addr, data) < 0) { + dev_err(&client->dev, "%s: Failed to read addr(0x%x)\n", + __func__, addr); + return -1; + } + + return (data[1] << 8) | data[0]; +} + +static int fg_write_register(struct i2c_client *client, + u8 addr, u16 w_data) +{ + u8 data[2]; + + data[0] = w_data & 0xFF; + data[1] = w_data >> 8; + + if (max17047_i2c_write(client, addr, data) < 0) { + dev_err(&client->dev, "%s: Failed to write addr(0x%x)\n", + __func__, addr); + return -1; + } + + return 0; +} + +static void max17047_test_read(struct max17047_fuelgauge_data *fg_data) +{ + int reg; + u8 data[2]; + int i; + u8 buf[673]; + + struct timespec ts; + struct rtc_time tm; + pr_info("%s\n", __func__); + + getnstimeofday(&ts); + rtc_time_to_tm(ts.tv_sec, &tm); + + pr_info("%s: %d/%d/%d %02d:%02d\n", __func__, + tm.tm_mday, + tm.tm_mon + 1, + tm.tm_year + 1900, + tm.tm_hour, + tm.tm_min); + + i = 0; + for (reg = 0; reg < 0x50; reg++) { + if (!(reg & 0xf)) + i += sprintf(buf + i, "\n%02x| ", reg); + max17047_i2c_read(fg_data->client, reg, data); + i += sprintf(buf + i, "%02x%02x ", data[1], data[0]); + } + for (reg = 0xe0; reg < 0x100; reg++) { + if (!(reg & 0xf)) + i += sprintf(buf + i, "\n%02x| ", reg); + max17047_i2c_read(fg_data->client, reg, data); + i += sprintf(buf + i, "%02x%02x ", data[1], data[0]); + } + + pr_info(" 0 1 2 3 4 5 6 7"); + pr_cont(" 8 9 a b c d e f"); + pr_cont("%s\n", buf); +} + +static int fg_check_battery_present(struct i2c_client *client) +{ + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (max17047_i2c_read(client, MAX17047_REG_STATUS, status_data) < 0) { + dev_err(&client->dev, + "%s: Failed to read STATUS_REG\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + dev_info(&client->dev, + "%s: addr(0x01), data(0x%04x)\n", __func__, + (status_data[1]<<8) | status_data[0]); + dev_info(&client->dev, "%s: battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + +static int max17047_get_temperature(struct i2c_client *client) +{ +#if defined(CONFIG_MACH_KONA) + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2] = {0x00, 0x00}; + int temper = 0; + + if (fg_check_battery_present(client)) { + if (max17047_i2c_read(client, MAX17047_REG_TEMPERATURE, data) < 0) { + dev_err(&client->dev, + "%s: Failed to read TEMPERATURE_REG\n", + __func__); + return -1; + } + + if (data[1]&(0x1 << 7)) { + temper = ((~(data[1]))&0xFF)+1; + temper *= (-1000); + temper -= ((~((int)data[0]))+1) * 39 / 10; + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + } + } else + temper = 20000; + + dev_info(&client->dev, "%s: TEMPERATURE(%d), data(0x%04x)\n", + __func__, temper/100, (data[1]<<8) | data[0]); + + return temper/100; +#else + return 300; +#endif +} + +/* max17047_get_XXX(); Return current value and update data value */ +static int max17047_get_vfocv(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 vfocv; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_VFOCV, data); + if (ret < 0) + return ret; + + vfocv = fg_data->vfocv = ((data[0] >> 3) + (data[1] << 5)) * 625 / 1000; + + pr_debug("%s: VFOCV(0x%02x%02x, %d)\n", __func__, + data[1], data[0], vfocv); + return vfocv * 1000; +} + +static int fg_read_vcell(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (max17047_i2c_read(client, MAX17047_REG_VCELL, data) < 0) { + dev_err(&client->dev, "%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1]<<8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + dev_info(&client->dev, "%s: VCELL(%d), data(0x%04x)\n", + __func__, vcell, (data[1]<<8) | data[0]); + + return vcell; +} + + +static int max17047_get_vcell(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 vcell; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_VCELL, data); + if (ret < 0) + return ret; + + vcell = fg_data->vcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + pr_debug("%s: VCELL(0x%02x%02x, %d)\n", __func__, + data[1], data[0], vcell); + return vcell; +} + +static int max17047_get_avgvcell(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int ret; + u32 avgvcell; + pr_debug("%s\n", __func__); + + ret = max17047_i2c_read(client, MAX17047_REG_AVGVCELL, data); + if (ret < 0) + return ret; + + avgvcell = fg_data->avgvcell = ((data[0] >> 3) + (data[1] << 5)) * 625; + + pr_debug("%s: AVGVCELL(0x%02x%02x, %d)\n", __func__, + data[1], data[0], avgvcell); + return avgvcell; +} + +static int max17047_get_rawsoc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int soc; + + if (max17047_i2c_read(client, MAX17047_SOCREP, data) < 0) { + dev_err(&client->dev, "%s: Failed to read SOCREP\n", __func__); + return -1; + } + + soc = (data[1] * 100) + (data[0] * 100 / 256); + + dev_dbg(&client->dev, "%s: raw capacity (0.01%%) (%d)\n", + __func__, soc); + + dev_dbg(&client->dev, "%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1]<<8) | data[0]); + + return min(soc, 10000); +} + +#if 0 +static int max17047_get_soc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + int rawsoc, soc, fullsoc, empty; + pr_debug("%s\n", __func__); + + rawsoc = max17047_get_rawsoc(fg_data->client); + +#if defined(CONFIG_MACH_C1) + empty = 0; +#else /* M0, T0,,, */ + empty = 29; +#endif + + if (fg_data->full_soc <= 0) + fg_data->full_soc = FULL_SOC_DEFAULT; + fullsoc = fg_data->full_soc - empty; + rawsoc -= empty; + + soc = fg_data->soc = + ((rawsoc < empty) ? 0 : (min((rawsoc * 100 / fullsoc), 100))); + + pr_info("%s: SOC(%d, %d / %d)\n", __func__, soc, rawsoc, fullsoc); + return soc; +} + +#else +/* soc should be 0.1% unit */ +static int max17047_get_soc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (max17047_i2c_read(client, MAX17047_SOCREP, data) < 0) { + pr_err("%s: Failed to read SOCREP\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + pr_info("%s: raw capacity (%d), data(0x%04x)\n", __func__, soc, (data[1]<<8) | data[0]); + + return min(soc, 1000); +} +#endif + +static int fg_reset_capacity_by_jig_connection(struct i2c_client *client) +{ + pr_info("%s: DesignCap = Capacity - 1 (Jig Connection)\n", __func__); + + return fg_write_register(client, MAX17047_REG_DESIGNCAP_REG, + fg_battery_data[SDI].Capacity - 1); +} + +#if defined(CONFIG_MACH_KONA) +/* For using JIG detach */ +struct i2c_client *fg_client; + +void fg_reset_capacity_by_jig_connection_ex(void) +{ + + pr_info("%s: DesignCap = Capacity - 1 (Jig Connection)\n", __func__); + printk("[BAT] %s call!!\n", __func__); + + + fg_write_register(fg_client, MAX17047_REG_DESIGNCAP_REG, + fg_battery_data[SDI].Capacity - 1); +} +EXPORT_SYMBOL(fg_reset_capacity_by_jig_connection_ex); +#endif + +static void fg_periodic_read(struct i2c_client *client) +{ + u8 reg; + int i; + int data[0x10]; + char *str = NULL; + + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return; + + for (i = 0; i < 16; i++) { + for (reg = 0; reg < 0x10; reg++) + data[reg] = fg_read_register(client, reg + i * 0x10); + + sprintf(str+strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + sprintf(str+strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + if (i == 4) + i = 13; + } + + dev_info(&client->dev, "maxim check %s", str); + + kfree(str); +} + +static int fg_read_current(struct i2c_client *client) +{ + u8 data1[2], data2[2]; + u32 temp, sign; + s32 i_current; + s32 avg_current; + + if (max17047_i2c_read(client, MAX17047_REG_CURRENT, data1) < 0) { + pr_err("%s: Failed to read CURRENT\n", + __func__); + return -1; + } + + if (max17047_i2c_read(client, MAX17047_REG_AVG_CURRENT, data2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", + __func__); + return -1; + } + + temp = ((data1[1]<<8) | data1[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + i_current = temp * 15625 / 100000; + if (sign) + i_current *= -1; + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + if (sign) + avg_current *= -1; + + pr_info("%s: CURRENT(%dmA), AVG_CURRENT(%dmA)\n", + __func__, i_current, avg_current); + + fg_periodic_read(client); + + return i_current; +} + +static int fg_read_avg_current(struct i2c_client *client) +{ + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + + if (max17047_i2c_read(client, MAX17047_REG_AVG_CURRENT, data2) < 0) { + pr_err("%s: Failed to read AVERAGE CURRENT\n", + __func__); + return -1; + } + + temp = ((data2[1]<<8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else + sign = POSITIVE; + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + avg_current = temp * 15625 / 100000; + + if (sign) + avg_current *= -1; + + return avg_current; +} + +/* soc should be 0.1% unit */ +static int fg_read_vfsoc(struct i2c_client *client) +{ + u8 data[2]; + int soc; + + if (max17047_i2c_read(client, MAX17047_REG_VFSOC, data) < 0) { + pr_err("%s: Failed to read VFSOC\n", __func__); + return -1; + } + + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + + +int get_fuelgauge_value(struct i2c_client *client, int data) +{ + int ret = 0; + + switch (data) { + case FG_LEVEL: + /*ret = fg_read_soc(client);*/ + ret = max17047_get_soc(client); + + break; + + case FG_TEMPERATURE: + /*ret = fg_read_temp(client);*/ + break; + + case FG_VOLTAGE: + ret = fg_read_vcell(client); + break; + + case FG_CURRENT: + ret = fg_read_current(client); + break; + + case FG_CURRENT_AVG: + ret = fg_read_avg_current(client); + break; + + case FG_CHECK_STATUS: + /*ret = fg_check_status_reg(client);*/ + break; + + case FG_RAW_SOC: + /*ret = fg_read_rawsoc(client);*/ + break; + + case FG_VF_SOC: + ret = fg_read_vfsoc(client); + break; + + case FG_AV_SOC: + /*ret = fg_read_avsoc(client);*/ + break; + + case FG_FULLCAP: + /*ret = fg_read_fullcap(client);*/ + break; + + case FG_MIXCAP: + /*ret = fg_read_mixcap(client);*/ + break; + + case FG_AVCAP: + /*ret = fg_read_avcap(client);*/ + break; + + case FG_REPCAP: + /*ret = fg_read_repcap(client);*/ + break; + + default: + ret = -1; + break; + } + + return ret; +} + + +void fg_check_vf_fullcap_range(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + + static int new_vffullcap; + bool is_vffullcap_changed = true; + + if (is_jig_attached == JIG_ON) + fg_reset_capacity_by_jig_connection(client); + + new_vffullcap = fg_read_register(client, MAX17047_REG_FULLCAP_NOM); + if (new_vffullcap < 0) + new_vffullcap = fg_battery_data[SDI].Capacity; + + /* compare with initial capacity */ + if (new_vffullcap > + (fg_battery_data[SDI].Capacity * 110 / 100)) { + pr_info("%s: [Case 1] capacity = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fg_battery_data[SDI].Capacity, + new_vffullcap); + + new_vffullcap = + (fg_battery_data[SDI].Capacity * 110) / 100; + + fg_write_register(client, MAX17047_REG_DQACC, + (u16)(new_vffullcap / 4)); + fg_write_register(client, MAX17047_REG_DPACC, (u16)0x3200); + } else if (new_vffullcap < + (fg_battery_data[SDI].Capacity * 50 / 100)) { + pr_info("%s: [Case 5] capacity = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fg_battery_data[SDI].Capacity, new_vffullcap); + + new_vffullcap = + (fg_battery_data[SDI].Capacity * 50) / 100; + + fg_write_register(client, MAX17047_REG_DQACC, + (u16)(new_vffullcap / 4)); + fg_write_register(client, MAX17047_REG_DPACC, (u16)0x3200); + } else { + /* compare with previous capacity */ + if (new_vffullcap > + (fg_data->previous_vffullcap * 110 / 100)) { + pr_info("%s: [Case 2] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fg_data->previous_vffullcap, + new_vffullcap); + + new_vffullcap = + (fg_data->previous_vffullcap * 110) / + 100; + + fg_write_register(client, MAX17047_REG_DQACC, + (u16)(new_vffullcap / 4)); + fg_write_register(client, MAX17047_REG_DPACC, (u16)0x3200); + } else if (new_vffullcap < + (fg_data->previous_vffullcap * 90 / 100)) { + pr_info("%s: [Case 3] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fg_data->previous_vffullcap, new_vffullcap); + + new_vffullcap = + (fg_data->previous_vffullcap * 90) / 100; + + fg_write_register(client, MAX17047_REG_DQACC, + (u16)(new_vffullcap / 4)); + fg_write_register(client, MAX17047_REG_DPACC, (u16)0x3200); + } else { + pr_info("%s: [Case 4] previous_vffullcap = 0x%04x, NewVfFullCap = 0x%04x\n", + __func__, fg_data->previous_vffullcap, + new_vffullcap); + is_vffullcap_changed = false; + } + } + + /* delay for register setting (dQacc, dPacc) */ + if (is_vffullcap_changed) + msleep(300); + + fg_data->previous_vffullcap = + fg_read_register(client, MAX17047_REG_FULLCAP_NOM); + + if (is_vffullcap_changed) + pr_info("%s : VfFullCap(0x%04x), dQacc(0x%04x), dPacc(0x%04x)\n", + __func__, + fg_read_register(client, MAX17047_REG_FULLCAP_NOM), + fg_read_register(client, MAX17047_REG_DQACC), + fg_read_register(client, MAX17047_REG_DPACC)); + +} + +void fg_set_full_charged(struct i2c_client *client) +{ + pr_info("[FG_Set_Full] (B) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, MAX17047_REG_FULLCAP)/2), + (fg_read_register(client, MAX17047_REG_REMCAP_REP)/2)); + + fg_write_register(client, MAX17047_REG_FULLCAP, + (u16)fg_read_register(client, MAX17047_REG_REMCAP_REP)); + + pr_info("[FG_Set_Full] (A) FullCAP(%d), RemCAP(%d)\n", + (fg_read_register(client, MAX17047_REG_FULLCAP)/2), + (fg_read_register(client, MAX17047_REG_REMCAP_REP)/2)); +} + +static void add_low_batt_comp_cnt(struct i2c_client *client, + int range, int level) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int i; + int j; + + /* Increase the requested count value, and reset others. */ + fg_data->low_batt_comp_cnt[range-1][level/2]++; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (i == range-1 && j == level/2) + continue; + else + fg_data->low_batt_comp_cnt[i][j] = 0; + } + } +} + + +static int get_low_batt_threshold(struct i2c_client *client, + int range, int nCurrent, int level) +{ + int ret = 0; + + ret = fg_battery_data[SDI].low_battery_table[range][OFFSET] + + ((nCurrent * + fg_battery_data[SDI].low_battery_table[range][SLOPE]) / + 1000); + + return ret; +} + +void reset_low_batt_comp_cnt(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + + memset(fg_data->low_batt_comp_cnt, 0, + sizeof(fg_data->low_batt_comp_cnt)); +} + +static void display_low_batt_comp_cnt(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + + pr_info("Check Array(%s): [%d, %d], [%d, %d], ", + fg_battery_data[SDI].type_str, + fg_data->low_batt_comp_cnt[0][0], + fg_data->low_batt_comp_cnt[0][1], + fg_data->low_batt_comp_cnt[1][0], + fg_data->low_batt_comp_cnt[1][1]); + pr_info("[%d, %d], [%d, %d], [%d, %d]\n", + fg_data->low_batt_comp_cnt[2][0], + fg_data->low_batt_comp_cnt[2][1], + fg_data->low_batt_comp_cnt[3][0], + fg_data->low_batt_comp_cnt[3][1], + fg_data->low_batt_comp_cnt[4][0], + fg_data->low_batt_comp_cnt[4][1]); +} + + +static int check_low_batt_comp_condition( + struct i2c_client *client, int *nLevel) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int i; + int j; + int ret = 0; + + for (i = 0; i < LOW_BATT_COMP_RANGE_NUM; i++) { + for (j = 0; j < LOW_BATT_COMP_LEVEL_NUM; j++) { + if (fg_data->low_batt_comp_cnt[i][j] >= + MAX_LOW_BATT_CHECK_CNT) { + display_low_batt_comp_cnt(client); + ret = 1; + *nLevel = j*2 + 1; + break; + } + } + } + + return ret; +} + +void fg_low_batt_compensation(struct i2c_client *client, u32 level) +{ +#if defined(CONFIG_MACH_KONA) + struct power_supply *battery_psy = power_supply_get_by_name("battery"); + union power_supply_propval value; +#endif + int read_val; + u32 temp; + + pr_info("%s: Adjust SOCrep to %d!!\n", + __func__, level); + + read_val = fg_read_register(client, MAX17047_REG_FULLCAP); + if (read_val < 0) + return; + +#if defined(CONFIG_MACH_KONA) + if (read_val > 2) { /* 3% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0301 */ + temp = read_val * (level*100 + 1) / 10000; + + /* Display conpensation 3% value for debug screen */ + value.intval = 1; + battery_psy->set_property(battery_psy, + POWER_SUPPLY_PROP_COMPENSATION_3, + &value); + } else { /* 1% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0090 */ + temp = read_val * (level*90) / 10000; + + /* Display conpensation 1% value for debug screen */ + value.intval = 1; + battery_psy->set_property(battery_psy, + POWER_SUPPLY_PROP_COMPENSATION_1, + &value); + } +#else + if (read_val > 2) /* 3% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0301 */ + temp = read_val * (level*100 + 1) / 10000; + else /* 1% compensation */ + /* RemCapREP (05h) = FullCap(10h) x 0.0090 */ + temp = read_val * (level*90) / 10000; +#endif + + fg_write_register(client, MAX17047_REG_REMCAP_REP, (u16)temp); +} + +void prevent_early_poweroff(struct i2c_client *client, + int vcell, int *fg_soc) +{ + int soc = 0; + int read_val; + + soc = get_fuelgauge_value(client, FG_LEVEL); + + if (soc > POWER_OFF_SOC_HIGH_MARGIN) + return; + + pr_info("%s: soc=%d%%, vcell=%d\n", __func__, + soc, vcell); + + if (vcell > POWER_OFF_VOLTAGE_HIGH_MARGIN) { + read_val = fg_read_register(client, MAX17047_REG_FULLCAP); + /* FullCAP * 0.013 */ + fg_write_register(client, MAX17047_REG_REMCAP_REP, + (u16)(read_val * 13 / 1000)); + msleep(200); + *fg_soc = max17047_get_soc(client); + dev_info(&client->dev, "%s : new soc=%d, vcell=%d\n", + __func__, *fg_soc, vcell); + } +} + +int low_batt_compensation(struct i2c_client *client, + int fg_soc, int fg_vcell, int fg_current) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int fg_avg_current = 0; + int fg_min_current = 0; + int new_level = 0; + int i, table_size; + + /* Not charging, Under low battery comp voltage */ + if (fg_vcell <= fg_battery_data[SDI].low_battery_comp_voltage) { + fg_avg_current = fg_read_avg_current(client); + fg_min_current = min(fg_avg_current, fg_current); + + table_size = + sizeof(fg_battery_data[SDI].low_battery_table) / + (sizeof(s16)*TABLE_MAX); + + for (i = 1; i < CURRENT_RANGE_MAX_NUM; i++) { + if ((fg_min_current >= fg_battery_data[SDI].low_battery_table[i-1][RANGE]) && + (fg_min_current < fg_battery_data[SDI].low_battery_table[i][RANGE])) { + if (fg_soc >= 2 && fg_vcell < + get_low_batt_threshold(client, + i, fg_min_current, 1)) { + add_low_batt_comp_cnt( + client, i, 1); + } else { + reset_low_batt_comp_cnt(client); + } + } + } + + if (check_low_batt_comp_condition(client, &new_level)) { + fg_low_batt_compensation(client, new_level); + reset_low_batt_comp_cnt(client); + + /* Do not update soc right after + * low battery compensation + * to prevent from powering-off suddenly + */ + pr_info("%s: SOC is set to %d by low compensation!!\n", + __func__, max17047_get_soc(client)); + } + } + + /* Prevent power off over 3500mV */ + prevent_early_poweroff(client, fg_vcell, &fg_soc); + + return fg_soc; +} + +int fg_adjust_capacity(struct i2c_client *client) +{ + u8 data[2]; + + data[0] = 0; + data[1] = 0; + + /* 1. Write RemCapREP(05h)=0; */ + if (max17047_i2c_write(client, MAX17047_REG_REMCAP_REP, data) < 0) { + pr_err("%s: Failed to write RemCap_REP\n", __func__); + return -1; + } + msleep(200); + + pr_info("%s: After adjust - RepSOC(%d)\n", __func__, max17047_get_soc(client)); + + return 0; +} + +static bool is_booted_in_low_battery(struct i2c_client *client) +{ + int fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + int fg_current = get_fuelgauge_value(client, FG_CURRENT); + int threshold = 0; + + threshold = 3300 + ((fg_current * 17) / 100); + + if (fg_vcell <= threshold) + return true; + else + return false; +} + +static bool fuelgauge_recovery_handler(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int current_soc; + int avsoc; + int temperature; + + if (fg_data->soc > LOW_BATTERY_SOC_REDUCE_UNIT) { + pr_err("%s: Reduce the Reported SOC by 1%%\n", + __func__); + current_soc = + get_fuelgauge_value(client, FG_LEVEL); + + if (current_soc) { + pr_info("%s: Returning to Normal discharge path\n", + __func__); + pr_info("%s: Actual SOC(%d) non-zero\n", + __func__, current_soc); + fg_data->is_low_batt_alarm = false; + } else { + temperature = + get_fuelgauge_value(client, FG_TEMPERATURE); + avsoc = + get_fuelgauge_value(client, FG_AV_SOC); + + if ((fg_data->soc > avsoc) || + (temperature < 0)) { + fg_data->soc -= + LOW_BATTERY_SOC_REDUCE_UNIT; + pr_err("%s: New Reduced RepSOC (%d)\n", + __func__, fg_data->soc); + } else + pr_info("%s: Waiting for recovery (AvSOC:%d)\n", + __func__, avsoc); + } + } + + return fg_data->is_low_batt_alarm; +} + + +static int get_fuelgauge_soc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + struct power_supply *battery_psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + int fg_soc; + int fg_vfsoc; + int fg_vcell; + int fg_current; + int avg_current; + ktime_t current_time; + struct timespec ts; + int fullcap_check_interval; + int cable_type; + + if (fg_data->is_low_batt_alarm) { + if (fuelgauge_recovery_handler(client)) { + fg_soc = fg_data->soc; + goto return_soc; + } + } + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + /* check fullcap range */ + fullcap_check_interval = + (ts.tv_sec - fg_data->fullcap_check_interval); + if (fullcap_check_interval > + VFFULLCAP_CHECK_INTERVAL) { + dev_info(&client->dev, + "%s: check fullcap range (interval:%d)\n", + __func__, fullcap_check_interval); + fg_check_vf_fullcap_range(client); + fg_data->fullcap_check_interval = ts.tv_sec; + } + + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + if (fg_soc < 0) { + pr_info("Can't read soc!!!"); + fg_soc = fg_data->soc; + } + + if (!battery_psy) { + pr_info("%s : battery driver didn't load yet.\n", __func__); + return fg_soc; + } + + battery_psy->get_property(battery_psy, + POWER_SUPPLY_PROP_ONLINE, + &value); + + cable_type = value.intval; + + if (fg_data->low_batt_boot_flag) { + fg_soc = 0; + + if (cable_type != POWER_SUPPLY_TYPE_BATTERY && + !is_booted_in_low_battery(client)) { + fg_adjust_capacity(client); + fg_data->low_batt_boot_flag = 0; + } + + if (cable_type == POWER_SUPPLY_TYPE_BATTERY) + fg_data->low_batt_boot_flag = 0; + } + + fg_vcell = get_fuelgauge_value(client, FG_VOLTAGE); + fg_current = get_fuelgauge_value(client, FG_CURRENT); + avg_current = get_fuelgauge_value(client, FG_CURRENT_AVG); + fg_vfsoc = get_fuelgauge_value(client, FG_VF_SOC); + + + battery_psy->get_property(battery_psy, + POWER_SUPPLY_PROP_STATUS, + &value); + + /* Algorithm for reducing time to fully charged (from MAXIM) */ + if (value.intval != POWER_SUPPLY_STATUS_DISCHARGING && + value.intval != POWER_SUPPLY_STATUS_FULL && + cable_type != POWER_SUPPLY_TYPE_USB && + /* Skip when first check after boot up */ + !fg_data->is_first_check && + (fg_vfsoc > VFSOC_FOR_FULLCAP_LEARNING && + (fg_current > LOW_CURRENT_FOR_FULLCAP_LEARNING && + fg_current < HIGH_CURRENT_FOR_FULLCAP_LEARNING) && + (avg_current > LOW_AVGCURRENT_FOR_FULLCAP_LEARNING && + avg_current < HIGH_AVGCURRENT_FOR_FULLCAP_LEARNING))) { + + if (fg_data->full_check_flag == 2) { + pr_info("%s: force fully charged SOC !! (%d)", + __func__, fg_data->full_check_flag); + fg_set_full_charged(client); + fg_soc = get_fuelgauge_value(client, FG_LEVEL); + } else if (fg_data->full_check_flag < 2) + pr_info("%s: full_check_flag (%d)", + __func__, fg_data->full_check_flag); + + /* prevent overflow */ + if (fg_data->full_check_flag++ > 10000) + fg_data->full_check_flag = 3; + } else + fg_data->full_check_flag = 0; + + /* Checks vcell level and tries to compensate SOC if needed.*/ + /* If jig cable is connected, then skip low batt compensation check. */ + if (is_jig_attached != JIG_ON && + value.intval == POWER_SUPPLY_STATUS_DISCHARGING) + fg_soc = low_batt_compensation( + client, fg_soc, fg_vcell, fg_current); + + if (fg_data->is_first_check) + fg_data->is_first_check = false; + fg_data->soc = fg_soc; + +return_soc: + +#if defined(CONFIG_MACH_KONA) + if (fg_data->full_soc <= 0) + fg_data->full_soc = FULL_SOC_DEFAULT; + + fg_soc =(min((fg_soc * 10000 / (fg_data->full_soc)), 1000)); +#endif + + pr_info("%s: soc(%d), low_batt_alarm(%d)\n", + __func__, fg_data->soc, + fg_data->is_low_batt_alarm); + + return fg_soc; +} + +static void max17047_adjust_fullsoc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = + i2c_get_clientdata(client); + int prev_full_soc = fg_data->full_soc; + int raw_soc = max17047_get_rawsoc(fg_data->client); + int keep_soc = 0; + + if (raw_soc < 0) { + pr_err("%s : fg data error!(%d)\n", __func__, raw_soc); + fg_data->full_soc = FULL_SOC_DEFAULT; + return; + } + + if (raw_soc < FULL_SOC_LOW) + fg_data->full_soc = FULL_SOC_LOW; + else if (raw_soc > FULL_SOC_HIGH) { + keep_soc = FULL_SOC_HIGH / 100; + fg_data->full_soc = (FULL_SOC_HIGH - keep_soc); + } else { + keep_soc = ((raw_soc * KEEP_FULL_SOC) / 10000); + if (raw_soc > (FULL_SOC_LOW + keep_soc)) + fg_data->full_soc = raw_soc - keep_soc; + else + fg_data->full_soc = FULL_SOC_LOW; + } + + if (prev_full_soc != fg_data->full_soc) + pr_info("%s : full_soc(%d->%d), rsoc(%d), keep(%d)\n", __func__, + prev_full_soc, fg_data->full_soc, raw_soc, keep_soc); +} + +/* SOC% alert, disabled(0xFF00) */ +static void max17047_set_salrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(%d%%), max(%d%%)\n", __func__, min, max); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_SALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_SALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: SALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +/* Temperature alert, disabled(0x7F80) */ +static void max17047_set_talrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(0x%02x), max(0x%02x)\n", __func__, min, max); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_TALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_TALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: TALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +/* Voltage alert, disabled(0xFF00) */ +static void max17047_set_valrt(struct max17047_fuelgauge_data *fg_data, + u8 min, u8 max) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: min(%dmV), max(%dmV)\n", __func__, min * 20, max * 20); + + i2c_data[1] = max; + i2c_data[0] = min; + max17047_i2c_write(client, MAX17047_REG_VALRT_TH, i2c_data); + + max17047_i2c_read(client, MAX17047_REG_VALRT_TH, i2c_data); + if ((i2c_data[0] != min) || (i2c_data[1] != max)) + pr_err("%s: VALRT_TH is not valid (0x%02d%02d ? 0x%02d%02d)\n", + __func__, i2c_data[1], i2c_data[0], max, min); +} + +static void max17047_alert_init(struct max17047_fuelgauge_data *fg_data) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_debug("%s\n", __func__); + + /* SALRT Threshold setting */ + /* min 1%, max disable */ + max17047_set_salrt(fg_data, 0x01, 0xFF); + + /* TALRT Threshold setting */ + /* min disable, max disable */ + max17047_set_talrt(fg_data, 0x80, 0x7F); + + /* VALRT Threshold setting */ + /* min disable, max disable */ + max17047_set_valrt(fg_data, 0x00, 0xFF); + + /* Enable SOC alerts */ + max17047_i2c_read(client, MAX17047_REG_CONFIG, i2c_data); + i2c_data[0] |= (0x1 << 2); + max17047_i2c_write(client, MAX17047_REG_CONFIG, i2c_data); +} + +static void max17047_reg_init(struct max17047_fuelgauge_data *fg_data) +{ + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_debug("%s\n", __func__); + + if (max17047_i2c_read(client, MAX17047_REG_FILTERCFG, i2c_data) < 0) + return; + + /* Clear average vcell (12 sec) */ + i2c_data[0] &= 0x8f; + + max17047_i2c_write(client, MAX17047_REG_FILTERCFG, i2c_data); + + i2c_data[0] = 0xd9; + i2c_data[1] = 0x35; + max17047_i2c_write(client, MAX17047_REG_CGAIN, i2c_data); +} + +static void max17047_update_work(struct work_struct *work) +{ + struct max17047_fuelgauge_data *fg_data = container_of(work, + struct max17047_fuelgauge_data, + update_work.work); + struct power_supply *battery_psy; + struct i2c_client *client = fg_data->client; + union power_supply_propval value; + pr_debug("%s\n", __func__); + +#ifdef CONFIG_SLP + battery_psy = &fg_data->fuelgauge; +#else + battery_psy = power_supply_get_by_name("battery"); +#endif + + max17047_get_vcell(client); + max17047_get_vfocv(client); + max17047_get_avgvcell(client); + max17047_get_rawsoc(client); + max17047_get_soc(client); + + pr_info("%s: VCELL(%d), VFOCV(%d), AVGVCELL(%d), RAWSOC(%d), SOC(%d)\n", + __func__, fg_data->vcell, + fg_data->vfocv, fg_data->avgvcell, + fg_data->rawsoc, fg_data->soc); + + max17047_test_read(fg_data); + + if (!battery_psy || !battery_psy->set_property) { + pr_err("%s: fail to get battery power supply\n", __func__); + return; + } + + battery_psy->set_property(battery_psy, + POWER_SUPPLY_PROP_STATUS, + &value); + + wake_lock_timeout(&fg_data->update_wake_lock, HZ); +} + +#ifdef DEBUG_FUELGAUGE_POLLING +static void max17047_polling_work(struct work_struct *work) +{ + struct max17047_fuelgauge_data *fg_data = container_of(work, + struct max17047_fuelgauge_data, + polling_work.work); + int reg; + int i; + u8 data[2]; + u8 buf[512]; + + max17047_get_vcell(fg_data->client); + max17047_get_vfocv(fg_data->client); + max17047_get_avgvcell(fg_data->client); + max17047_get_rawsoc(fg_data->client); + max17047_get_soc(fg_data->client); + + pr_info("%s: VCELL(%d), VFOCV(%d), AVGVCELL(%d), RAWSOC(%d), SOC(%d)\n", + __func__, fg_data->vcell, + fg_data->vfocv, fg_data->avgvcell, + fg_data->rawsoc, fg_data->soc); + + max17047_test_read(fg_data); + + schedule_delayed_work(&fg_data->polling_work, + msecs_to_jiffies(MAX17047_POLLING_INTERVAL)); +} +#endif + +static enum power_supply_property max17047_fuelgauge_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + +static int max17047_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17047_fuelgauge_data *fg_data = container_of(psy, + struct max17047_fuelgauge_data, + fuelgauge); + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (val->intval) { + case VOLTAGE_TYPE_VCELL: + val->intval = max17047_get_vcell(fg_data->client); + break; + case VOLTAGE_TYPE_VFOCV: + val->intval = max17047_get_vfocv(fg_data->client); + break; + default: + val->intval = max17047_get_vcell(fg_data->client); + break; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = max17047_get_avgvcell(fg_data->client); + break; + /* Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_fuelgauge_value(fg_data->client, FG_CURRENT); + break; + /* Average Current (mA) */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = get_fuelgauge_value(fg_data->client, FG_CURRENT_AVG); + break; + case POWER_SUPPLY_PROP_CAPACITY: + switch (val->intval) { + case SOC_TYPE_ADJUSTED: + /*val->intval = max17047_get_soc(fg_data->client);*/ + val->intval = get_fuelgauge_soc(fg_data->client) / 10; + break; + case SOC_TYPE_RAW: + val->intval = max17047_get_rawsoc(fg_data->client); + break; + case SOC_TYPE_FULL: + val->intval = fg_data->full_soc; + break; + default: + val->intval = get_fuelgauge_soc(fg_data->client) / 10; + break; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = max17047_get_temperature(fg_data->client); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int max17047_reset_soc(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + u8 data[2]; + int vfocv, fullcap; + + /* delay for current stablization */ + msleep(500); + + dev_info(&client->dev, + "%s: Before quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max17047_get_vcell(client), max17047_get_vfocv(client), + fg_read_vfsoc(client), max17047_get_soc(client)); + dev_info(&client->dev, + "%s: Before quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + + if (is_jig_attached == JIG_OFF) { + dev_info(&client->dev, + "%s : Return by No JIG_ON signal\n", __func__); + return 0; + } + + fg_write_register(client, MAX17047_REG_CYCLES, 0); + + if (max17047_i2c_read(client, MAX17047_REG_MISCCFG, data) < 0) { + dev_err(&client->dev, "%s: Failed to read MiscCFG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); + if (max17047_i2c_write(client, MAX17047_REG_MISCCFG, data) < 0) { + dev_err(&client->dev, + "%s: Failed to write MiscCFG\n", __func__); + return -1; + } + + msleep(250); + fg_write_register(client, MAX17047_REG_FULLCAP, + fg_battery_data[SDI].Capacity); + msleep(500); + + dev_info(&client->dev, + "%s: After quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max17047_get_vcell(client), max17047_get_vfocv(client), + fg_read_vfsoc(client), max17047_get_soc(client)); + dev_info(&client->dev, + "%s: After quick-start - current(%d), avg current(%d)\n", + __func__, fg_read_current(client), + fg_read_avg_current(client)); + fg_write_register(client, MAX17047_REG_CYCLES, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V + * (It's not a problem, depend on mode data) + * Power off for factory test(File system, etc..) */ + vfocv = max17047_get_vfocv(client); + if (vfocv < POWER_OFF_VOLTAGE_LOW_MARGIN) { + dev_info(&client->dev, "%s: Power off condition(%d)\n", + __func__, vfocv); + + fullcap = fg_read_register(client, MAX17047_REG_FULLCAP); + /* FullCAP * 0.009 */ + fg_write_register(client, MAX17047_REG_REMCAP_REP, + (u16)(fullcap * 9 / 1000)); + msleep(200); + dev_info(&client->dev, "%s: new soc=%d, vfocv=%d\n", __func__, + max17047_get_soc(client), vfocv); + } + + dev_info(&client->dev, + "%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max17047_get_vfocv(client), + fg_read_vfsoc(client), max17047_get_soc(client)); + + return 0; +} + + +static int max17047_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17047_fuelgauge_data *fg_data = container_of(psy, + struct max17047_fuelgauge_data, + fuelgauge); + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + max17047_reset_soc(fg_data->client); + break; + case POWER_SUPPLY_PROP_STATUS: + if (val->intval != POWER_SUPPLY_STATUS_FULL) + return -EINVAL; + pr_info("%s: charger full state!\n", __func__); + /* adjust full soc */ + max17047_adjust_fullsoc(fg_data->client); + break; +#if defined(CONFIG_MACH_GC1) + case POWER_SUPPLY_PROP_RCOMP: + if (fg_data->prev_status == val->intval) { + pr_debug("%s: No rcomp change, prev(%d) = cur(%d)\n", + __func__, fg_data->prev_status, val->intval); + } else { + if (val->intval == POWER_SUPPLY_STATUS_CHARGING) + max17047_set_rcomp(fg_data->client, 1); + else + max17047_set_rcomp(fg_data->client, 0); + max17047_get_rcomp(fg_data->client, val->intval); + fg_data->prev_status = val->intval; + } + break; +#endif + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t max17047_fuelgauge_isr(int irq, void *data) +{ + struct max17047_fuelgauge_data *fg_data = data; + struct i2c_client *client = fg_data->client; + u8 i2c_data[2]; + pr_info("%s: irq(%d)\n", __func__, irq); + mutex_lock(&fg_data->irq_lock); + + max17047_i2c_read(client, MAX17047_REG_STATUS, i2c_data); + pr_info("%s: MAX17047_REG_STATUS(0x%02x%02x)\n", __func__, + i2c_data[1], i2c_data[0]); + + cancel_delayed_work(&fg_data->update_work); + wake_lock(&fg_data->update_wake_lock); + schedule_delayed_work(&fg_data->update_work, msecs_to_jiffies(1000)); + + mutex_unlock(&fg_data->irq_lock); + return IRQ_HANDLED; +} + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS +static int max17047_debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t max17047_debugfs_read_registers(struct file *filp, + char __user *buffer, size_t count, loff_t *ppos) +{ + struct max17047_fuelgauge_data *fg_data = filp->private_data; + struct i2c_client *client = NULL; + u8 i2c_data[2]; + int reg = 0; + char *buf; + size_t len = 0; + ssize_t ret; + + if (!fg_data) { + pr_err("%s : fg_data is null\n", __func__); + return 0; + } + + client = fg_data->client; + + if (*ppos != 0) + return 0; + + if (count < sizeof(buf)) + return -ENOSPC; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = MAX17047_REG_STATUS; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "status(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_CONFIG; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "config(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_RCOMP; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "rcomp(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_CGAIN; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "cgain(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_SALRT_TH; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "salrt(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = MAX17047_REG_MISCCFG; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "misc(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x39; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "tempc0(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x0F; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "remCap(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + reg = 0x10; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "fullCap(0x%x)=%02x%02x ", reg, i2c_data[1], i2c_data[0]); + + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + + ret = simple_read_from_buffer(buffer, len, ppos, buf, PAGE_SIZE); + kfree(buf); + + return ret; +} + +static const struct file_operations max17047_debugfs_fops = { + .owner = THIS_MODULE, + .open = max17047_debugfs_open, + .read = max17047_debugfs_read_registers, +}; + +static ssize_t max17047_debugfs_read_defaultdata(struct file *filp, + char __user *buffer, size_t count, loff_t *ppos) +{ + struct max17047_fuelgauge_data *fg_data = filp->private_data; + struct i2c_client *client = NULL; + u8 i2c_data[2]; + int reg = 0; + char *buf; + size_t len = 0; + ssize_t ret; + + if (!fg_data) { + pr_err("%s : fg_data is null\n", __func__); + return 0; + } + + client = fg_data->client; + + if (*ppos != 0) + return 0; + + if (count < sizeof(buf)) + return -ENOSPC; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = MAX17047_REG_RCOMP; + max17047_i2c_read(client, reg, i2c_data); + len += snprintf(buf + len, PAGE_SIZE - len, + "rcomp=%02x%02x ", i2c_data[1], i2c_data[0]); + + len += snprintf(buf + len, PAGE_SIZE - len, + "fsoc=%d", fg_data->full_soc); + + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + + ret = simple_read_from_buffer(buffer, len, ppos, buf, PAGE_SIZE); + kfree(buf); + + return ret; +} + +static const struct file_operations max17047_debugfs_fops2 = { + .owner = THIS_MODULE, + .open = max17047_debugfs_open, + .read = max17047_debugfs_read_defaultdata, +}; +#endif +#endif + +#ifdef CHECK_RCOMP_UPDATE +static void max17047_check_rcomp_update(struct i2c_client *client) +{ + u8 data[2]; + int ret, rcomp; + + /* read rcomp */ + ret = max17047_i2c_read(client, MAX17047_REG_RCOMP, data); + if (ret < 0) + return; + + rcomp = (data[1] << 8) | data[0]; + pr_info("%s: rcomp = 0x%04x\n", __func__, rcomp); + + /* check rcomp update */ + if (rcomp != MAX17047_NEW_RCOMP) { + data[0] = MAX17047_NEW_RCOMP & 0xff; + data[1] = MAX17047_NEW_RCOMP >> 8; + max17047_i2c_write(client, MAX17047_REG_RCOMP, data); + pr_info("%s: set new rcomp = 0x%04x\n", + __func__, MAX17047_NEW_RCOMP); + max17047_i2c_read(client, MAX17047_REG_RCOMP, data); + rcomp = (data[1] << 8) | data[0]; + pr_info("%s: verify rcomp = 0x%04x\n", __func__, rcomp); + } +} +#endif + +static int __devinit max17047_fuelgauge_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17047_fuelgauge_data *fg_data; + struct max17047_platform_data *pdata = client->dev.platform_data; + int ret = -ENODEV; + int rawsoc, firstsoc; + ktime_t current_time; + struct timespec ts; + + pr_info("%s: fuelgauge init\n", __func__); + + if (!pdata) { + pr_err("%s: no platform data\n", __func__); + return -ENODEV; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + fg_data = kzalloc(sizeof(struct max17047_fuelgauge_data), GFP_KERNEL); + if (!fg_data) + return -ENOMEM; + + fg_data->client = client; + fg_data->pdata = pdata; + fg_client = client; + + i2c_set_clientdata(client, fg_data); + + mutex_init(&fg_data->irq_lock); + + wake_lock_init(&fg_data->update_wake_lock, WAKE_LOCK_SUSPEND, + "fuel-update"); + + /* Initialize full_soc, set this before fisrt SOC reading */ + fg_data->full_soc = FULL_SOC_DEFAULT; + /* first full_soc update */ + rawsoc = max17047_get_rawsoc(fg_data->client); + if (rawsoc > FULL_SOC_DEFAULT) + max17047_adjust_fullsoc(client); + firstsoc = max17047_get_soc(client); + pr_info("%s: rsoc=%d, fsoc=%d, soc=%d\n", __func__, + rawsoc, fg_data->full_soc, firstsoc); + + if (fg_data->pdata->psy_name) + fg_data->fuelgauge.name = + fg_data->pdata->psy_name; + else + fg_data->fuelgauge.name = "max17047-fuelgauge"; + + fg_data->fuelgauge.type = POWER_SUPPLY_TYPE_BATTERY; + fg_data->fuelgauge.properties = max17047_fuelgauge_props; + fg_data->fuelgauge.num_properties = + ARRAY_SIZE(max17047_fuelgauge_props); + fg_data->fuelgauge.get_property = max17047_get_property; + fg_data->fuelgauge.set_property = max17047_set_property; + + ret = power_supply_register(&client->dev, &fg_data->fuelgauge); + if (ret) { + pr_err("%s: failed power supply register\n", __func__); + goto err_psy_reg_fg; + } + + current_time = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(current_time); + + fg_data->fullcap_check_interval = ts.tv_sec; + + /* Init parameters to prevent wrong compensation */ + fg_data->previous_fullcap = + fg_read_register(fg_data->client, MAX17047_REG_FULLCAP); + + fg_data->previous_vffullcap = + fg_read_register(fg_data->client, MAX17047_REG_FULLCAPNOM); + + max17047_test_read(fg_data); + + if (is_jig_attached == JIG_ON) + fg_reset_capacity_by_jig_connection(fg_data->client); + + /* Initialize fuelgauge alert */ + max17047_alert_init(fg_data); + + INIT_DELAYED_WORK_DEFERRABLE(&fg_data->update_work, + max17047_update_work); + + /* Request IRQ */ + fg_data->irq = gpio_to_irq(fg_data->pdata->irq_gpio); + ret = gpio_request(fg_data->pdata->irq_gpio, "fuelgauge-irq"); + if (ret) { + pr_err("%s: failed requesting gpio %d\n", __func__, + fg_data->pdata->irq_gpio); + goto err_irq; + } + gpio_direction_input(fg_data->pdata->irq_gpio); + gpio_free(fg_data->pdata->irq_gpio); + + ret = request_threaded_irq(fg_data->irq, NULL, + max17047_fuelgauge_isr, IRQF_TRIGGER_FALLING, + "max17047-alert", fg_data); + if (ret < 0) { + pr_err("%s: fail to request max17047 irq: %d: %d\n", + __func__, fg_data->irq, ret); + goto err_irq; + } + + ret = enable_irq_wake(fg_data->irq); + if (ret < 0) { + pr_err("%s: failed enable irq wake %d\n", __func__, + fg_data->irq); + goto err_enable_irq; + } + +#ifdef DEBUG_FUELGAUGE_POLLING + INIT_DELAYED_WORK_DEFERRABLE(&fg_data->polling_work, + max17047_polling_work); + schedule_delayed_work(&fg_data->polling_work, 0); +#else + max17047_test_read(fg_data); +#endif + + pr_info("%s: probe complete\n", __func__); + +#if defined(CONFIG_TARGET_LOCALE_KOR) +#ifdef CONFIG_DEBUG_FS + fg_data->fg_debugfs_dir = + debugfs_create_dir("fg_debug", NULL); + if (fg_data->fg_debugfs_dir) { + if (!debugfs_create_file("max17047_regs", 0644, + fg_data->fg_debugfs_dir, + fg_data, &max17047_debugfs_fops)) + pr_err("%s : debugfs_create_file, error\n", __func__); + if (!debugfs_create_file("default_data", 0644, + fg_data->fg_debugfs_dir, + fg_data, &max17047_debugfs_fops2)) + pr_err("%s : debugfs_create_file2, error\n", __func__); + } else + pr_err("%s : debugfs_create_dir, error\n", __func__); +#endif +#endif + + return 0; + +err_enable_irq: + free_irq(fg_data->irq, fg_data); +err_irq: + power_supply_unregister(&fg_data->fuelgauge); +err_psy_reg_fg: + wake_lock_destroy(&fg_data->update_wake_lock); + mutex_destroy(&fg_data->irq_lock); + kfree(fg_data); + return ret; +} + +static int __devexit max17047_fuelgauge_remove(struct i2c_client *client) +{ + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + + wake_lock_destroy(&fg_data->update_wake_lock); + free_irq(fg_data->irq, fg_data); + power_supply_unregister(&fg_data->fuelgauge); +#ifdef DEBUG_FUELGAUGE_POLLING + cancel_delayed_work(&fg_data->polling_work); +#endif + cancel_delayed_work(&fg_data->update_work); + mutex_destroy(&fg_data->irq_lock); + kfree(fg_data); + + return 0; +} + +#ifdef CONFIG_PM +static int max17047_fuelgauge_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + struct power_supply *psy = power_supply_get_by_name("battery"); + union power_supply_propval value; + int charge_state, voltage_max, voltage_min; + int valrt_vol; + pr_info("%s\n", __func__); + +#ifdef DEBUG_FUELGAUGE_POLLING + cancel_delayed_work(&fg_data->polling_work); +#endif + +#if !defined(CONFIG_SLP) + /* default disable */ + valrt_vol = 0; + + /* voltage alert recharge voltage */ + if (!psy) { + pr_err("%s: fail to get battery psy\n", __func__); + return 0; + } + psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &value); + charge_state = value.intval; + + if (charge_state == POWER_SUPPLY_STATUS_FULL) { + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + &value); + voltage_max = value.intval; + + /* valrt voltage set as recharge voltage */ + valrt_vol = voltage_max - RECHG_DROP_VALUE; + } else { + psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + &value); + voltage_min = value.intval; + + /* valrt voltage set as min voltage - 50mV */ + valrt_vol = voltage_min - 50000; + } + + pr_info("%s: charge state(%d), vcell(%d), valrt(%d)\n", __func__, + charge_state, fg_data->vcell, valrt_vol); + + /* set voltage alert */ + max17047_set_valrt(fg_data, (valrt_vol / 1000 / 20), 0xFF); +#endif + + return 0; +} + +static int max17047_fuelgauge_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data = i2c_get_clientdata(client); + pr_info("%s\n", __func__); + +#if !defined(CONFIG_SLP) + /* min disable, max disable */ + max17047_set_valrt(fg_data, 0x00, 0xFF); +#endif + +#ifdef DEBUG_FUELGAUGE_POLLING + schedule_delayed_work(&fg_data->polling_work, 0); +#endif + + return 0; +} +#else +#define max17047_fuelgauge_suspend NULL +#define max17047_fuelgauge_resume NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17047_fuelgauge_id[] = { + {"max17047-fuelgauge", 0}, + {} +}; + +#ifdef CONFIG_HIBERNATION +static const u16 save_addr[] = { + MAX17047_REG_VALRT_TH, + MAX17047_REG_TALRT_TH, + MAX17047_REG_SALRT_TH, + + MAX17047_REG_TEMPERATURE, + MAX17047_REG_CONFIG, + + MAX17047_REG_LEARNCFG, + MAX17047_REG_FILTERCFG, + MAX17047_REG_MISCCFG, + MAX17047_REG_CGAIN, + MAX17047_REG_RCOMP, + MAX17047_REG_SOC_VF, +}; + + +static int max17047_freeze(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data + = i2c_get_clientdata(client); + int i, j; + + if (fg_data->reg_dump) { + dev_err(dev, "Register dump is not clean.\n"); + return -EINVAL; + } + + fg_data->reg_dump = kzalloc(sizeof(u16) * ARRAY_SIZE(save_addr), + GFP_KERNEL); + if (!fg_data->reg_dump) { + dev_err(dev, "Cannot allocate memory for hibernation dump.\n"); + return -ENOMEM; + } + + for (i = 0, j = 0; i < ARRAY_SIZE(save_addr); i++, j += 2) + max17047_i2c_read(client, save_addr[i] + , &(fg_data->reg_dump[j])); + + return 0; +} + +static int max17047_restore(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max17047_fuelgauge_data *fg_data + = i2c_get_clientdata(client); + int i, j; + + if (!fg_data->reg_dump) { + dev_err(dev, "Cannot allocate memory for hibernation dump.\n"); + return -ENOMEM; + } + + for (i = 0, j = 0; i < ARRAY_SIZE(save_addr); i++, j += 2) + max17047_i2c_write(client, save_addr[i] + , &(fg_data->reg_dump[j])); + + kfree(fg_data->reg_dump); + fg_data->reg_dump = NULL; + + return 0; +} +#endif + + + +#ifdef CONFIG_PM +const struct dev_pm_ops max17047_pm = { + .suspend = max17047_fuelgauge_suspend, + .resume = max17047_fuelgauge_resume, +#ifdef CONFIG_HIBERNATION + .freeze = max17047_freeze, + .thaw = max17047_restore, + .restore = max17047_restore, +#endif +}; +#endif + + +MODULE_DEVICE_TABLE(i2c, max17047_fuelgauge_id); + +static struct i2c_driver max17047_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max17047-fuelgauge", + .pm = &max17047_pm, + + }, + .probe = max17047_fuelgauge_i2c_probe, + .remove = __devexit_p(max17047_fuelgauge_remove), + .id_table = max17047_fuelgauge_id, +}; + +static int __init max17047_fuelgauge_init(void) +{ + return i2c_add_driver(&max17047_i2c_driver); +} + +static void __exit max17047_fuelgauge_exit(void) +{ + i2c_del_driver(&max17047_i2c_driver); +} + +module_init(max17047_fuelgauge_init); +module_exit(max17047_fuelgauge_exit); + +MODULE_AUTHOR("SangYoung Son "); +MODULE_DESCRIPTION("max17047 Fuel gauge driver"); +MODULE_LICENSE("GPL"); -- cgit v1.1