/* * 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");