/* drivers/battery/sm5703_fuelgauge.c * SM5703 Voltage Tracking Fuelgauge Driver * * Copyright (C) 2016 * Author: Dongik Sin * Modified by SW Jung * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #if defined(CONFIG_MULTIBATT_SUPPORT) #include #include #endif #define SM5703_FG_DEVICE_NAME "sm5703-fg" #define ALIAS_NAME "sm5703-fuelgauge" #define FG_DET_BAT_PRESENT 1 #define MINVAL(a, b) ((a <= b) ? a : b) enum battery_table_type { DISCHARGE_TABLE = 0, CHARGE_TABLE, Q_TABLE, TABLE_MAX, }; static struct device_attribute sec_fg_attrs[] = { SEC_FG_ATTR(reg), SEC_FG_ATTR(data), SEC_FG_ATTR(regs), }; static enum power_supply_property sm5703_fuelgauge_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_ENERGY_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TEMP_AMBIENT, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, POWER_SUPPLY_PROP_CAPACITY_LEVEL, }; static inline int sm5703_fg_read_device(struct i2c_client *client, int reg, int bytes, void *dest) { int ret; if (bytes > 1) ret = i2c_smbus_read_i2c_block_data(client, reg, bytes, dest); else { ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) return ret; *(unsigned char *)dest = (unsigned char)ret; } return ret; } static int32_t sm5703_fg_i2c_read_word(struct i2c_client *client, uint8_t reg_addr) { uint16_t data = 0; int ret; ret = sm5703_fg_read_device(client, reg_addr, 2, &data); /* dev_info(&client->dev, "%s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); */ if (ret < 0) return ret; else return data; /* not use big endian */ /* return (int32_t)be16_to_cpu(data); */ } static int32_t sm5703_fg_i2c_write_word(struct i2c_client *client, uint8_t reg_addr,uint16_t data) { int ret; /* not use big endian */ /* data = cpu_to_be16(data); */ ret = i2c_smbus_write_i2c_block_data(client, reg_addr, 2, (uint8_t *)&data); /* dev_info(&client->dev, "%s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); */ return ret; } static int32_t sm5703_fg_i2c_verified_write_word(struct i2c_client *client, uint8_t reg_addr, uint16_t data) { int ret; /* not use big endian */ /* data = cpu_to_be16(data); */ ret = i2c_smbus_write_i2c_block_data(client, reg_addr, 2, (uint8_t *)&data); if (ret < 0) { msleep(50); dev_info(&client->dev, "1st fail i2c write %s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); ret = i2c_smbus_write_i2c_block_data(client, reg_addr, 2, (uint8_t *)&data); if (ret < 0) { msleep(50); dev_info(&client->dev, "2nd fail i2c write %s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); ret = i2c_smbus_write_i2c_block_data(client, reg_addr, 2, (uint8_t *)&data); if (ret < 0) { dev_info(&client->dev, "3rd fail i2c write %s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); } } } /* dev_info(&client->dev, "%s: ret = %d, addr = 0x%x, data = 0x%x\n", __func__, ret, reg_addr, data); */ return ret; } static unsigned int sm5703_get_vbat(struct i2c_client *client); static unsigned int sm5703_get_ocv(struct i2c_client *client); static int sm5703_get_curr(struct i2c_client *client); static int sm5703_get_temperature(struct i2c_client *client); static void sm5703_pr_ver_info(struct i2c_client *client) { dev_info(&client->dev, "SM5703 Fuel-Gauge Ver %s\n", FG_DRIVER_VER); } static unsigned int sm5703_get_ocv(struct i2c_client *client) { int ret; unsigned int ocv;// = 3500; /*3500 means 3500mV*/ struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); ret = sm5703_fg_i2c_read_word(client, SM5703_REG_OCV); if (ret < 0) { pr_err("%s: read ocv reg fail\n", __func__); ocv = 4000; } else { /* integer bit */ ocv = ((ret&0x0700)>>8) * 1000; /* integer + fractional bit */ ocv = ocv + (((ret&0x00ff) * 1000) / 256); } fuelgauge->info.batt_ocv = ocv; dev_info(&client->dev, "%s: read = 0x%x, ocv = %d\n", __func__, ret, ocv); return ocv; } static unsigned int sm5703_get_vbat(struct i2c_client *client) { int ret; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); unsigned int vbat;/* = 3500; 3500 means 3500mV*/ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_VOLTAGE); if (ret < 0) { pr_err("%s: read vbat reg fail", __func__); vbat = 4000; } else { /* integer bit */ vbat = ((ret&0x0700)>>8) * 1000; /* integer + fractional bit */ vbat = vbat + (((ret&0x00ff)*1000)/256); } fuelgauge->info.batt_voltage = vbat; /* cal avgvoltage */ fuelgauge->info.batt_avgvoltage = ((fuelgauge->info.batt_avgvoltage*2) + (fuelgauge->info.p_batt_voltage+vbat))/4; dev_info(&client->dev, "%s: read = 0x%x, vbat = %d\n", __func__, ret, vbat); dev_info(&client->dev, "%s: batt_avgvoltage = %d\n", __func__, fuelgauge->info.batt_avgvoltage); if (((fuelgauge->force_dec_mode == SM5703_COLD_MODE) || (fuelgauge->info.volt_alert_flag)) && vbat > 3400) { fuelgauge->force_dec_mode = SM5703_RECOVERY_MODE; wake_unlock(&fuelgauge->fuel_alert_wake_lock); fuelgauge->info.volt_alert_flag = false; sec_hal_fg_fuelalert_init(client, fuelgauge->pdata->fuel_alert_soc); dev_info(&client->dev, "%s : volt_alert_flag = %d \n", __func__, fuelgauge->info.volt_alert_flag); } return vbat; } static int sm5703_get_curr(struct i2c_client *client) { int ret; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); int curr;/* = 1000; 1000 means 1000mA*/ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CURRENT); if (ret<0) { pr_err("%s: read curr reg fail", __func__); curr = 0; } else { /* integer bit */ curr = ((ret & 0x0700) >> 8) * 1000; /* integer + fractional bit */ curr = curr + (((ret & 0x00ff) * 1000) / 256); if (ret & 0x8000) { curr *= -1; } } fuelgauge->info.batt_current = curr; /* cal avgcurr */ fuelgauge->info.batt_avgcurrent = ((fuelgauge->info.batt_avgcurrent*2) + (fuelgauge->info.p_batt_current+curr))/4; dev_info(&client->dev, "%s: read = 0x%x, curr = %d\n", __func__, ret, curr); dev_info(&client->dev, "%s: batt_avgcurrent = %d\n", __func__, fuelgauge->info.batt_avgcurrent); return curr; } static int sm5703_get_temperature(struct i2c_client *client) { int ret; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); int temp;/* = 250; 250 means 25.0oC*/ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_TEMPERATURE); if (ret<0) { pr_err("%s: read temp reg fail", __func__); temp = 0; } else { /* integer bit */ temp = ((ret & 0x7F00) >> 8) * 10; /* integer + fractional bit */ temp = temp + (((ret & 0x00ff) * 10) / 256); if (ret & 0x8000) { temp *= -1; } } fuelgauge->info.temp_fg = temp; dev_dbg(&client->dev, "%s: read = 0x%x, temp_fg = %d\n", __func__, ret, temp); return temp; } static unsigned int sm5703_get_device_id(struct i2c_client *client) { int ret; ret = sm5703_fg_i2c_read_word(client, SM5703_REG_DEVICE_ID); /* ret &= 0x00ff; */ dev_info(&client->dev, "%s: device_id = 0x%x\n", __func__, ret); return ret; } static bool sm5703_fg_check_reg_init_need(struct i2c_client *client) { int ret; ret = sm5703_fg_i2c_read_word(client, SM5703_REG_FG_OP_STATUS); if ((ret & INIT_CHECK_MASK) == DISABLE_RE_INIT) { dev_dbg(&client->dev, "%s: return 0\n", __func__); return 0; } else { dev_dbg(&client->dev, "%s: return 1\n", __func__); return 1; } } static bool sm5703_fg_get_batt_present(struct i2c_client *client) { /* SM5703 is not suport batt present */ dev_dbg(&client->dev, "%s: sm5703_fg_get_batt_present\n", __func__); return true; } int calculate_iocv(struct i2c_client *client) { int i; int max=0, min=0, sum=0, l_avg=0, s_avg=0, l_minmax_offset=0; int ret=0; for (i = SM5703_REG_IOCV_B_L_MIN; i <= SM5703_REG_IOCV_B_L_MAX; i++) { ret = sm5703_fg_i2c_read_word(client, i); if (i == SM5703_REG_IOCV_B_L_MIN) { max = ret; min = ret; sum = ret; } else { if (ret > max) max = ret; else if (ret < min) min = ret; sum = sum + ret; } } sum = sum - max - min; l_minmax_offset = max - min; l_avg = sum / (SM5703_REG_IOCV_B_L_MAX-SM5703_REG_IOCV_B_L_MIN-1); dev_info(&client->dev, "%s: iocv_l_max=0x%x, iocv_l_min=0x%x, iocv_l_sum=0x%x, iocv_l_avg=0x%x \n", __func__, max, min, sum, l_avg); ret = sm5703_fg_i2c_read_word(client, SM5703_REG_END_V_IDX); pr_info("%s: iocv_status_read = addr : 0x%x , data : 0x%x\n", __func__, SM5703_REG_END_V_IDX, ret); if ((ret & 0x0030) == 0x0030) { for (i = SM5703_REG_IOCV_B_S_MIN; i <= SM5703_REG_IOCV_B_S_MAX; i++) { ret = sm5703_fg_i2c_read_word(client, i); if (i == SM5703_REG_IOCV_B_S_MIN) { max = ret; min = ret; sum = ret; } else { if (ret > max) max = ret; else if (ret < min) min = ret; sum = sum + ret; } } sum = sum - max - min; s_avg = sum / (SM5703_REG_IOCV_B_S_MAX-SM5703_REG_IOCV_B_S_MIN-1); dev_info(&client->dev, "%s: iocv_s_max=0x%x, iocv_s_min=0x%x, iocv_s_sum=0x%x, iocv_s_avg=0x%x \n", __func__, max, min, sum, s_avg); } if (((abs(l_avg - s_avg) > 0x29) && (l_minmax_offset < 0xCC)) || (s_avg == 0)) { pr_info("%s: select L-AVG\n", __func__); ret = l_avg; } else { pr_info("%s: select S-AVG\n", __func__); ret = s_avg; } return ret; } #ifdef ENABLE_BATT_LONG_LIFE int get_v_max_index_by_cycle(struct i2c_client *client) { int cycle_index=0, len; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); for (len = fuelgauge->pdata->num_age_step-1; len >= 0; --len) { if(fuelgauge->chg_full_soc == fuelgauge->pdata->age_data[len].full_condition_soc) { cycle_index=len; break; } } pr_info("%s: chg_full_soc = %d, index = %d \n", __func__, fuelgauge->chg_full_soc, cycle_index); return cycle_index; } #endif static bool sm5703_fg_reg_init(struct i2c_client *client, int is_surge) { int i, j, value, ret; uint8_t table_reg; int write_table[3][16]; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); dev_info(&client->dev, "%s: sm5703_fg_reg_init START!!\n", __func__); /* start first param_ctrl unlock */ sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_CTRL, SM5703_FG_PARAM_UNLOCK_CODE); /* RCE write */ for (i = 0; i < 3; i++) { sm5703_fg_i2c_write_word(client, SM5703_REG_RCE0+i, fuelgauge->info.rce_value[i]); dev_dbg(&client->dev, "%s: RCE write RCE%d = 0x%x : 0x%x\n", __func__, i, SM5703_REG_RCE0+i, fuelgauge->info.rce_value[i]); } /* DTCD write */ sm5703_fg_i2c_write_word(client, SM5703_REG_DTCD, fuelgauge->info.dtcd_value); dev_dbg(&client->dev, "%s: DTCD write DTCD = 0x%x : 0x%x\n", __func__, SM5703_REG_DTCD, fuelgauge->info.dtcd_value); /* RS write */ sm5703_fg_i2c_write_word(client, SM5703_REG_RS, fuelgauge->info.rs_value[0]); dev_dbg(&client->dev, "%s: RS write RS = 0x%x : 0x%x\n", __func__, SM5703_REG_RS, fuelgauge->info.rs_value[0]); /* VIT_PERIOD write */ sm5703_fg_i2c_write_word(client, SM5703_REG_VIT_PERIOD, fuelgauge->info.vit_period); dev_dbg(&client->dev, "%s: VIT_PERIOD write VIT_PERIOD = 0x%x : 0x%x\n", __func__, SM5703_REG_VIT_PERIOD, fuelgauge->info.vit_period); /* TABLE_LEN write & pram unlock */ sm5703_fg_i2c_write_word( client, SM5703_REG_PARAM_CTRL, SM5703_FG_PARAM_UNLOCK_CODE | SM5703_FG_TABLE_LEN); #ifdef ENABLE_BATT_LONG_LIFE i = get_v_max_index_by_cycle(client); pr_info("%s: v_max_now is change %x -> %x \n", __func__, fuelgauge->info.v_max_now, fuelgauge->info.v_max_table[i]); pr_info("%s: q_max_now is change %x -> %x \n", __func__, fuelgauge->info.q_max_now, fuelgauge->info.q_max_table[i]); fuelgauge->info.v_max_now = fuelgauge->info.v_max_table[i]; fuelgauge->info.q_max_now = fuelgauge->info.q_max_table[i]; #endif for (i=TABLE_MAX-1; i >= 0; i--) { for (j=0; j <= SM5703_FG_TABLE_LEN; j++) { #ifdef ENABLE_BATT_LONG_LIFE if (i == Q_TABLE) { write_table[i][j] = fuelgauge->info.battery_table[i][j]; if (j == SM5703_FG_TABLE_LEN) { write_table[i][SM5703_FG_TABLE_LEN - 1] = fuelgauge->info.q_max_now; write_table[i][SM5703_FG_TABLE_LEN] = fuelgauge->info.q_max_now + (fuelgauge->info.q_max_now / 1000); } } else { write_table[i][j] = fuelgauge->info.battery_table[i][j]; if (j == SM5703_FG_TABLE_LEN - 1) { write_table[i][SM5703_FG_TABLE_LEN - 1] = fuelgauge->info.v_max_now; if (write_table[i][SM5703_FG_TABLE_LEN - 1] < write_table[i][SM5703_FG_TABLE_LEN - 2]) { write_table[i][SM5703_FG_TABLE_LEN - 2] = write_table[i][SM5703_FG_TABLE_LEN - 1] - 0x18; // ~11.7mV write_table[Q_TABLE][SM5703_FG_TABLE_LEN - 2] = (write_table[Q_TABLE][SM5703_FG_TABLE_LEN - 1] * 99) / 100; } } } #else write_table[i][j] = fuelgauge->info.battery_table[i][j]; #endif } } for (i = 0; i < 3; i++) { table_reg = SM5703_REG_TABLE_START + (i<<4); for (j = 0; j <= SM5703_FG_TABLE_LEN; j++) { sm5703_fg_i2c_write_word(client, (table_reg + j), write_table[i][j]); msleep(10); if (write_table[i][j] != sm5703_fg_i2c_read_word(client, (table_reg + j))) { pr_info("%s: TABLE write FAIL retry[%d][%d] = 0x%x : 0x%x\n", __func__, i, j, (table_reg + j), write_table[i][j]); sm5703_fg_i2c_write_word(client, (table_reg + j), write_table[i][j]); } pr_info("%s: TABLE write OK [%d][%d] = 0x%x : 0x%x\n", __func__, i, j, (table_reg + j), write_table[i][j]); } } /* MIX_MODE write */ sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MIX_FACTOR, fuelgauge->info.rs_value[1]); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAX, fuelgauge->info.rs_value[2]); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MIN, fuelgauge->info.rs_value[3]); sm5703_fg_i2c_write_word(client, SM5703_REG_MIX_RATE, fuelgauge->info.mix_value[0]); sm5703_fg_i2c_write_word(client, SM5703_REG_MIX_INIT_BLANK, fuelgauge->info.mix_value[1]); dev_dbg(&client->dev, "%s: RS_MIX_FACTOR = 0x%x, RS_MAX = 0x%x, RS_MIN = 0x%x, MIX_RATE = 0x%x, MIX_INIT_BLANK = 0x%x\n", __func__, fuelgauge->info.rs_value[1], fuelgauge->info.rs_value[2], fuelgauge->info.rs_value[3], fuelgauge->info.mix_value[0], fuelgauge->info.mix_value[1]); /* CAL write */ sm5703_fg_i2c_write_word(client, SM5703_REG_VOLT_CAL, fuelgauge->info.volt_cal); sm5703_fg_i2c_write_word(client, SM5703_REG_CURR_CAL, fuelgauge->info.curr_cal); dev_dbg(&client->dev, "%s: VOLT_CAL = 0x%x, CURR_CAL = 0x%x\n", __func__, fuelgauge->info.volt_cal, fuelgauge->info.curr_cal); /* TOPOFF SOC */ sm5703_fg_i2c_write_word(client, SM5703_REG_TOPOFFSOC, fuelgauge->info.topoff_soc); /* INIT_last - control register set */ value = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); value &= 0xDFFF; value |= ENABLE_MIX_MODE | ENABLE_TEMP_MEASURE | (fuelgauge->info.enable_topoff_soc << 13); value |= ENABLE_MANUAL_OCV; value |= (fuelgauge->info.data_ver << 5) & SM5703_BATTERY_VERSION; pr_info("%s: SM5703_REG_CNTL reg : 0x%x\n", __func__, value); ret = sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, value); if (ret < 0) dev_info(&client->dev, "%s: fail control register set(%d)\n", __func__, ret); dev_info(&client->dev, "%s: LAST SM5703_REG_CNTL = 0x%x : 0x%x\n", __func__, SM5703_REG_CNTL, value); /* LOCK */ value = SM5703_FG_PARAM_LOCK_CODE | SM5703_FG_TABLE_LEN; sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_CTRL, value); dev_info(&client->dev, "%s: LAST PARAM CTRL VALUE = 0x%x : 0x%x\n", __func__, SM5703_REG_PARAM_CTRL, value); /* surge reset defence */ if (is_surge) { value = ((fuelgauge->info.batt_ocv<<8)/125); } else { value = calculate_iocv(client); } sm5703_fg_i2c_write_word(client, SM5703_REG_IOCV_MAN, value); dev_info(&client->dev, "%s: IOCV_MAN_WRITE = %d : 0x%x\n", __func__, fuelgauge->info.batt_ocv, value); return 1; } static bool sm5703_fg_init(struct i2c_client *client, bool is_surge) { int ret; int ta_exist,reg_val; union power_supply_propval value; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); fuelgauge->info.is_FG_initialised = 0; fuelgauge->info.iocv_error_count = 0; board_fuelgauge_init(fuelgauge); /* SM5703 i2c read check */ ret = sm5703_get_device_id(client); if (ret < 0) { dev_dbg(&client->dev, "%s: fail to do i2c read(%d)\n", __func__, ret); return false; } /* enable_topoff set */ reg_val = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); reg_val &= 0xDFFF; reg_val |= (fuelgauge->info.enable_topoff_soc << 13); pr_info("%s: SM5703_REG_CNTL reg : 0x%x\n", __func__, reg_val); sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, reg_val); if (sm5703_fg_check_reg_init_need(client)) { sm5703_fg_reg_init(client, is_surge); } #ifdef ENABLE_BATT_LONG_LIFE fuelgauge->info.q_max_now = sm5703_fg_i2c_read_word(client, 0xCE); pr_info("%s: q_max_now = 0x%x\n", __func__, fuelgauge->info.q_max_now); fuelgauge->info.q_max_now = sm5703_fg_i2c_read_word(client, 0xCE); pr_info("%s: q_max_now = 0x%x\n", __func__, fuelgauge->info.q_max_now); #endif value.intval = POWER_SUPPLY_HEALTH_UNKNOWN; psy_do_property("sm5703-charger", get, POWER_SUPPLY_PROP_HEALTH, value); dev_dbg(&client->dev, "%s: get POWER_SUPPLY_PROP_HEALTH = 0x%x\n", __func__, value.intval); ta_exist = ((value.intval == POWER_SUPPLY_HEALTH_GOOD) | fuelgauge->is_charging) && (fuelgauge->info.batt_current > 30); dev_dbg(&client->dev, "%s: is_charging = %d, ta_exist = %d\n", __func__, fuelgauge->is_charging, ta_exist); /* get first voltage measure to avgvoltage */ fuelgauge->info.batt_avgvoltage = sm5703_get_vbat(client); /* get first temperature of fuelgauge */ fuelgauge->info.temp_fg= sm5703_get_temperature(client); /* get first OCV */ fuelgauge->info.batt_ocv = sm5703_get_ocv(client); ret = sm5703_fg_i2c_read_word(client, 0x30); dev_info(&client->dev, "%s: sm5703 FG 0x30 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x31); dev_info(&client->dev, "%s: sm5703 FG 0x31 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x32); dev_info(&client->dev, "%s: sm5703 FG 0x32 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x33); dev_info(&client->dev, "%s: sm5703 FG 0x33 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x34); dev_info(&client->dev, "%s: sm5703 FG 0x34 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x35); dev_info(&client->dev, "%s: sm5703 FG 0x35 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x36); dev_info(&client->dev, "%s: sm5703 FG 0x36 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x37); dev_info(&client->dev, "%s: sm5703 FG 0x37 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x40); dev_info(&client->dev, "%s: sm5703 FG 0x40 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x41); dev_info(&client->dev, "%s: sm5703 FG 0x41 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x42); dev_info(&client->dev, "%s: sm5703 FG 0x42 = 0x%x \n", __func__, ret); ret = sm5703_fg_i2c_read_word(client, 0x43); dev_info(&client->dev, "%s: sm5703 FG 0x43 = 0x%x \n", __func__, ret); fuelgauge->info.is_FG_initialised = 1; return true; } static void sm5703_fg_test_read(struct i2c_client *client) { int ret1, ret2, ret3, ret4; ret1 = sm5703_fg_i2c_read_word(client, 0xAC); ret2 = sm5703_fg_i2c_read_word(client, 0xAD); ret3 = sm5703_fg_i2c_read_word(client, 0xAE); ret4 = sm5703_fg_i2c_read_word(client, 0xAF); pr_info("0xAC=0x%04x, 0xAD=0x%04x, 0xAE=0x%04x, 0xAF=0x%04x \n", ret1, ret2, ret3, ret4); ret1 = sm5703_fg_i2c_read_word(client, 0xBC); ret2 = sm5703_fg_i2c_read_word(client, 0xBD); ret3 = sm5703_fg_i2c_read_word(client, 0xBE); ret4 = sm5703_fg_i2c_read_word(client, 0xBF); pr_info("0xBC=0x%04x, 0xBD=0x%04x, 0xBE=0x%04x, 0xBF=0x%04x \n", ret1, ret2, ret3, ret4); ret1 = sm5703_fg_i2c_read_word(client, 0xCC); ret2 = sm5703_fg_i2c_read_word(client, 0xCD); ret3 = sm5703_fg_i2c_read_word(client, 0xCE); ret4 = sm5703_fg_i2c_read_word(client, 0xCF); pr_info("0xCC=0x%04x, 0xCD=0x%04x, 0xCE=0x%04x, 0xCF=0x%04x \n", ret1, ret2, ret3, ret4); ret1 = sm5703_fg_i2c_read_word(client, 0x85); ret2 = sm5703_fg_i2c_read_word(client, 0x86); ret3 = sm5703_fg_i2c_read_word(client, 0x87); ret4 = sm5703_fg_i2c_read_word(client, 0x28); pr_info("0x85=0x%04x, 0x86=0x%04x, 0x87=0x%04x, 0x28=0x%04x \n", ret1, ret2, ret3, ret4); } void fg_abnormal_reset_check(struct i2c_client *client) { int ret; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); /* abnormal case.... SW reset */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_FG_OP_STATUS); if (((ret & INIT_CHECK_MASK) != DISABLE_RE_INIT) && (fuelgauge->info.is_FG_initialised == 1)) { ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); dev_info(&client->dev, "%s: SM5703 FG abnormal case!!!! SM5703_REG_CNTL : 0x%x\n", __func__, ret); if (ret == CNTL_REG_DEFAULT_VALUE) { /* SW reset code */ if (sm5703_fg_i2c_verified_write_word(client, SM5703_REG_MISC, SW_RESET_CODE) < 0) { dev_info(&client->dev, "%s: Warning!!!! SM5703 FG abnormal case.... SW reset FAIL \n", __func__); } else { dev_info(&client->dev, "%s: SM5703 FG abnormal case.... SW reset OK\n", __func__); } /* delay 200ms */ msleep(200); /* init code */ sm5703_fg_init(client, true); } } } void fg_vbatocv_check(struct i2c_client *client, int ta_exist) { int ret; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); /* iocv error case cover start */ if ((abs(fuelgauge->info.batt_current)<40) || ((ta_exist) && (abs(fuelgauge->info.batt_current)info.min_charge_curr))) { /* 30mV over */ if (abs(fuelgauge->info.batt_ocv-fuelgauge->info.batt_voltage) > 30) { fuelgauge->info.iocv_error_count ++; } if (fuelgauge->info.iocv_error_count > 5) /* prevent to overflow */ fuelgauge->info.iocv_error_count = 6; } else { fuelgauge->info.iocv_error_count = 0; } dev_info(&client->dev, "%s: iocv_error_count (%d)\n", __func__, fuelgauge->info.iocv_error_count); if (fuelgauge->info.iocv_error_count > 5) { dev_info(&client->dev, "%s: p_v - v = (%d)\n", __func__, fuelgauge->info.p_batt_voltage - fuelgauge->info.batt_voltage); /* 15mV over */ if (abs(fuelgauge->info.p_batt_voltage - fuelgauge->info.batt_voltage)>15) { fuelgauge->info.iocv_error_count = 0; } else { /* mode change to mix RS manual mode */ dev_info(&client->dev, "%s: mode change to mix RS manual mode\n", __func__); /* RS manual value write */ sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, fuelgauge->info.rs_value[0]); /* run update */ sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 0); sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 1); /* mode change */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = (ret | ENABLE_MIX_MODE) | ENABLE_RS_MAN_MODE; /* +RS_MAN_MODE */ sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); } } else { if ((fuelgauge->info.temperature/10) > 15) { if ((fuelgauge->info.p_batt_voltage < fuelgauge->info.n_tem_poff) && (fuelgauge->info.batt_voltage < fuelgauge->info.n_tem_poff) && (!ta_exist)) { dev_info(&client->dev, "%s: mode change to normal tem mix RS manual mode\n", __func__); /* mode change to mix RS manual mode */ /* RS manual value write */ if ((fuelgauge->info.p_batt_voltage < (fuelgauge->info.n_tem_poff - fuelgauge->info.n_tem_poff_offset)) && (fuelgauge->info.batt_voltage < (fuelgauge->info.n_tem_poff - fuelgauge->info.n_tem_poff_offset))) { #if defined(CONFIG_SEC_GTS210VELTE_PROJECT) || defined(CONFIG_SEC_GTS210VEWIFI_PROJECT) dev_info(&client->dev, "%s: mode change to normal tem mix RS(%x) manual mode 2 \n", __func__, (fuelgauge->info.rs_value[0]-120)>>2); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, (fuelgauge->info.rs_value[0]-120)>>2); #else sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, fuelgauge->info.rs_value[0]>>1); #endif } else { #if defined(CONFIG_SEC_GTS210VELTE_PROJECT) || defined(CONFIG_SEC_GTS210VEWIFI_PROJECT) dev_info(&client->dev, "%s: mode change to normal tem mix RS(%x) manual mode 1 \n", __func__, (fuelgauge->info.rs_value[0]-120)>>1); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, (fuelgauge->info.rs_value[0]-120)>>1); #else sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, fuelgauge->info.rs_value[0]); #endif } /* run update */ sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 0); sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 1); /* mode change */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = (ret | ENABLE_MIX_MODE) | ENABLE_RS_MAN_MODE; // +RS_MAN_MODE sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); } else { dev_info(&client->dev, "%s: mode change to mix RS auto mode\n", __func__); /* mode change to mix RS auto mode */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = (ret | ENABLE_MIX_MODE) & ~ENABLE_RS_MAN_MODE; /* -RS_MAN_MODE */ sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); } } else { if ((fuelgauge->info.p_batt_voltage < fuelgauge->info.l_tem_poff) && (fuelgauge->info.batt_voltage < fuelgauge->info.l_tem_poff) && (!ta_exist)) { dev_info(&client->dev, "%s: mode change to normal tem mix RS manual mode\n", __func__); /* mode change to mix RS manual mode */ /* RS manual value write */ if ((fuelgauge->info.p_batt_voltage < (fuelgauge->info.l_tem_poff - fuelgauge->info.l_tem_poff_offset)) && (fuelgauge->info.batt_voltage < (fuelgauge->info.l_tem_poff - fuelgauge->info.l_tem_poff_offset))) { #if defined(CONFIG_SEC_GTS210VELTE_PROJECT) || defined(CONFIG_SEC_GTS210VEWIFI_PROJECT) dev_info(&client->dev, "%s: mode change to low tem mix RS(%x) manual mode 2 \n", __func__, (fuelgauge->info.rs_value[0]-120)>>2); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, (fuelgauge->info.rs_value[0]-120)>>2); #else sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, fuelgauge->info.rs_value[0]>>1); #endif } else { #if defined(CONFIG_SEC_GTS210VELTE_PROJECT) || defined(CONFIG_SEC_GTS210VEWIFI_PROJECT) dev_info(&client->dev, "%s: mode change to low tem mix RS(%x) manual mode 1 \n", __func__, (fuelgauge->info.rs_value[0]-120)>>1); sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, (fuelgauge->info.rs_value[0]-120)>>1); #else sm5703_fg_i2c_write_word(client, SM5703_REG_RS_MAN, fuelgauge->info.rs_value[0]); #endif } /* run update */ sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 0); sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 1); /* mode change */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = (ret | ENABLE_MIX_MODE) | ENABLE_RS_MAN_MODE; /* +RS_MAN_MODE */ sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); } else { dev_info(&client->dev, "%s: mode change to mix RS auto mode\n", __func__); /* mode change to mix RS auto mode */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = (ret | ENABLE_MIX_MODE) & ~ENABLE_RS_MAN_MODE; /* -RS_MAN_MODE */ sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); } } } fuelgauge->info.p_batt_voltage = fuelgauge->info.batt_voltage; fuelgauge->info.p_batt_current = fuelgauge->info.batt_current; /* iocv error case cover end */ } unsigned int sm5703_get_soc(struct i2c_client *client) { int ret; unsigned int soc; int ta_exist; int curr_cal; int temp_cal_fact; int temp_gap=0; int high_temp_cal_fact=0, low_temp_cal_fact=0; union power_supply_propval value; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); value.intval = POWER_SUPPLY_HEALTH_UNKNOWN; psy_do_property("sm5703-charger", get, POWER_SUPPLY_PROP_HEALTH, value); dev_dbg(&client->dev, "%s: get POWER_SUPPLY_PROP_HEALTH = 0x%x\n", __func__, value.intval); ta_exist = ((value.intval == POWER_SUPPLY_HEALTH_GOOD) | fuelgauge->is_charging) && (fuelgauge->info.batt_current > 30); dev_info(&client->dev, "%s: curr_cal = 0x%x , ta_exist = %d, CNTL=0x%x\n", __func__, fuelgauge->info.curr_cal, ta_exist, sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL)); fg_abnormal_reset_check(client); fg_vbatocv_check(client, ta_exist); if (ta_exist) curr_cal = fuelgauge->info.curr_cal+(fuelgauge->info.charge_offset_cal<<8); else curr_cal = fuelgauge->info.curr_cal; temp_cal_fact = fuelgauge->info.temp_std - (fuelgauge->info.temp_fg/10); temp_cal_fact = temp_cal_fact / fuelgauge->info.temp_offset; temp_cal_fact = temp_cal_fact * fuelgauge->info.temp_offset_cal; curr_cal = curr_cal + (temp_cal_fact<<8); temp_gap = (fuelgauge->info.temperature/10) - fuelgauge->info.temp_std; if (fuelgauge->info.en_high_temp_cal && (temp_gap > 0)) { if (ta_exist) high_temp_cal_fact = fuelgauge->info.high_temp_p_cal_fact; else high_temp_cal_fact = fuelgauge->info.high_temp_n_cal_fact; curr_cal = curr_cal + (((temp_gap / fuelgauge->info.high_temp_cal_denom)*high_temp_cal_fact)<<8); } else if (fuelgauge->info.en_low_temp_cal && (temp_gap < 0)) { if (ta_exist) low_temp_cal_fact = fuelgauge->info.low_temp_p_cal_fact; else low_temp_cal_fact = fuelgauge->info.low_temp_n_cal_fact; temp_gap = -temp_gap; curr_cal = curr_cal + (((temp_gap / fuelgauge->info.low_temp_cal_denom)*low_temp_cal_fact)<<8); } dev_info(&client->dev, "%s: temp_fg = %d , batt_temp = %d, volt_cal = 0x%x, curr_cal = 0x%x, high_temp_cal_fact = %d, low_temp_cal_fact = %d\n", __func__, fuelgauge->info.temp_fg, fuelgauge->info.temperature, fuelgauge->info.volt_cal, curr_cal, high_temp_cal_fact, low_temp_cal_fact); sm5703_fg_i2c_write_word(client, SM5703_REG_CURR_CAL, curr_cal); ret = sm5703_fg_i2c_read_word(client, SM5703_REG_SOC); if (ret<0) { pr_err("%s: Warning!!!! read soc reg fail\n", __func__); soc = 500; } else { /* integer bit; */ soc = ((ret&0xff00)>>8) * 10; /* integer + fractional bit */ soc = soc + (((ret&0x00ff)*10)/256); } dev_info(&client->dev, "%s: read = 0x%x, soc = %d\n", __func__, ret, soc); fuelgauge->info.batt_soc = soc; /* temp for SM5703 FG debug */ sm5703_fg_test_read(client); /* for low temp power off test */ if (fuelgauge->info.volt_alert_flag && (fuelgauge->info.temperature < -100)) { dev_info(&client->dev, "%s: volt_alert_flag is TRUE!!!! SOC make force ZERO!!!!\n", __func__); fuelgauge->info.batt_soc = 0; return 0; } return soc; } #ifdef CONFIG_OF #if defined(CONFIG_MULTIBATT_SUPPORT) #define SDI_ADC_MAX_LIMIT 30000 static struct qpnp_vadc_chip *adc_client; static enum qpnp_vadc_channels batt_id_adc_channel; static void sm5703_adc_ap_init(struct sec_fuelgauge_info *fuelgauge) { if (!(&fuelgauge->client->dev)) { pr_err("%s : can't get fuelgauge dev \n", __func__); } else { adc_client = qpnp_get_vadc(&fuelgauge->client->dev, "sm5703-fuelgauge"); if (IS_ERR(adc_client)) { int rc; rc = PTR_ERR(adc_client); if (rc != -EPROBE_DEFER) pr_err("%s: Fail to get vadc %d\n", __func__, rc); } } } static int sm5703_adc_ap_read(int channel) { struct qpnp_vadc_result result; int data = -1; int rc; switch (channel) { case SEC_BAT_ADC_CHANNEL_BAT_CHECK : rc = qpnp_vadc_read(adc_client, batt_id_adc_channel, &result); if (rc) { pr_err("%s: Unable to read batt adc=%d, batt_id_adc_channel=%d\n", __func__, rc, batt_id_adc_channel); return 0; } data = result.adc_code; break; default : break; } pr_info("%s: data(%d)\n", __func__, data); return data; } static int sm5703_get_battery_id(struct sec_fuelgauge_info *fuelgauge,enum sec_battery_adc_channel channel) { int batt_adc; fuelgauge->info.battery_typ = SDI_BATTERY_TYPE; batt_id_adc_channel = P_MUX2_1_1; pr_info("%s channel = %d \n", __func__, channel); if (channel == SEC_BAT_ADC_CHANNEL_BAT_CHECK) { sec_mpp_mux_control(BATT_ID_MUX_SEL_NUM, SEC_MUX_SEL_BATT_ID, 1); batt_adc = sm5703_adc_ap_read(channel); sec_mpp_mux_control(BATT_ID_MUX_SEL_NUM, SEC_MUX_SEL_BATT_ID, 0); if (batt_adc > SDI_ADC_MAX_LIMIT) { fuelgauge->info.battery_typ = ATL_BATTERY_TYPE; pr_info("%s: batt_id_adc = (%d), battery type (%d)\n", __func__, batt_adc, fuelgauge->info.battery_typ); return ATL_BATTERY_TYPE; } else { fuelgauge->info.battery_typ = SDI_BATTERY_TYPE; pr_info("%s: batt_id_adc = (%d), battery type (%d)\n", __func__, batt_adc, fuelgauge->info.battery_typ); return SDI_BATTERY_TYPE; } pr_info("%s : ADC not in range batt_id_adc = (%d)\n", __func__, batt_adc); } return SDI_BATTERY_TYPE; } #else static int sm5703_get_battery_id(struct sec_fuelgauge_info *fuelgauge) { /* sm5703fg does not support this function */ return 0; } #endif #define PROPERTY_NAME_SIZE 128 #define PINFO(format, args...) \ printk(KERN_INFO "%s() line-%d: " format, \ __func__, __LINE__, ## args) #define DECL_PARAM_PROP(_id, _name) {.id = _id, .name = _name,} #if defined(CONFIG_BATTERY_AGE_FORECAST) static int temp_parse_dt(struct sec_fuelgauge_info *fuelgauge) { struct device_node *np = of_find_node_by_name(NULL, "battery"); int len=0, ret; const u32 *p; if (np == NULL) { pr_err("%s np NULL\n", __func__); } else { p = of_get_property(np, "battery,age_data", &len); if (p) { pr_info("%s --------- 1 %d \n", __func__,len); fuelgauge->pdata->num_age_step = len / sizeof(sec_age_data_t); pr_info("%s --------- 2\n", __func__); fuelgauge->pdata->age_data = kzalloc(len, GFP_KERNEL); pr_info("%s --------- 3\n", __func__); ret = of_property_read_u32_array(np, "battery,age_data", (u32 *)fuelgauge->pdata->age_data, len/sizeof(u32)); pr_info("%s --------- 4\n", __func__); if (ret) { pr_err("%s failed to read battery->pdata->age_data: %d\n", __func__, ret); kfree(fuelgauge->pdata->age_data); fuelgauge->pdata->age_data = NULL; fuelgauge->pdata->num_age_step = 0; } pr_info("%s num_age_step : %d\n", __func__, fuelgauge->pdata->num_age_step); for (len = 0; len < fuelgauge->pdata->num_age_step; ++len) { pr_info("[%d/%d]cycle:%d, float:%d, full_v:%d, recharge_v:%d, soc:%d\n", len, fuelgauge->pdata->num_age_step-1, fuelgauge->pdata->age_data[len].cycle, fuelgauge->pdata->age_data[len].float_voltage, fuelgauge->pdata->age_data[len].full_condition_vcell, fuelgauge->pdata->age_data[len].recharge_condition_vcell, fuelgauge->pdata->age_data[len].full_condition_soc); } } else { fuelgauge->pdata->num_age_step = 0; pr_err("%s there is not age_data\n", __func__); } } return 0; } #endif static int sm5703_fg_parse_dt(struct sec_fuelgauge_info *fuelgauge) { struct device *dev = &fuelgauge->client->dev; struct device_node *np = dev->of_node; char prop_name[PROPERTY_NAME_SIZE]; int battery_id = -1; #ifdef ENABLE_BATT_LONG_LIFE int v_max_table[5]; int q_max_table[5]; #endif int table[16]; int rce_value[3]; int rs_value[4]; int mix_value[2]; int topoff_soc[2]; int ext_temp_cal[8] = {1, 7, 1, 1, 1, 10, -1, -1}; int set_temp_poff[4] = {3350,70,3300,70}; int ret; int i, j; BUG_ON(dev == 0); BUG_ON(np == 0); /* get battery_params node */ np = of_find_node_by_name(of_node_get(np), "battery_params"); if (np == NULL) { PINFO("Cannot find child node \"battery_params\"\n"); return -EINVAL; } #if defined(CONFIG_MULTIBATT_SUPPORT) /*To initialize batt_id_adc channel*/ sm5703_adc_ap_init(fuelgauge); #endif /* get battery_id */ if (of_property_read_u32(np, "battery,id", &battery_id) < 0) PINFO("not battery,id property\n"); if (battery_id == -1) #if defined(CONFIG_MULTIBATT_SUPPORT) battery_id = sm5703_get_battery_id(fuelgauge, SEC_BAT_ADC_CHANNEL_BAT_CHECK); #else battery_id = sm5703_get_battery_id(fuelgauge); #endif PINFO("battery id = %d\n", battery_id); #ifdef ENABLE_BATT_LONG_LIFE snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "v_max_table"); ret = of_property_read_u32_array(np, prop_name, v_max_table, fuelgauge->pdata->num_age_step); if(ret < 0){ PINFO("Can get prop %s (%d)\n", prop_name, ret); for (i = 0; i pdata->num_age_step; i++){ fuelgauge->info.v_max_table[i] = fuelgauge->info.battery_table[DISCHARGE_TABLE][SM5703_FG_TABLE_LEN-1]; PINFO("%s = \n", prop_name, i, fuelgauge->info.v_max_table[i]); } }else{ for (i = 0; i < fuelgauge->pdata->num_age_step; i++){ fuelgauge->info.v_max_table[i] = v_max_table[i]; PINFO("%s = \n", prop_name, i, fuelgauge->info.v_max_table[i]); } } snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "q_max_table"); ret = of_property_read_u32_array(np, prop_name, q_max_table,fuelgauge->pdata->num_age_step); if(ret < 0){ PINFO("Can get prop %s (%d)\n", prop_name, ret); for (i = 0; i < fuelgauge->pdata->num_age_step; i++){ fuelgauge->info.q_max_table[i] = 100; PINFO("%s = \n", prop_name, i, fuelgauge->info.q_max_table[i]); } }else{ for (i = 0; i < fuelgauge->pdata->num_age_step; i++){ fuelgauge->info.q_max_table[i] = q_max_table[i]; PINFO("%s = \n", prop_name, i, fuelgauge->info.q_max_table[i]); } } fuelgauge->chg_full_soc = fuelgauge->pdata->age_data[0].full_condition_soc; fuelgauge->info.v_max_now = fuelgauge->info.v_max_table[0]; fuelgauge->info.q_max_now = fuelgauge->info.q_max_table[0]; PINFO("%s = , , \n", prop_name, fuelgauge->info.v_max_now, fuelgauge->info.q_max_now, fuelgauge->chg_full_soc); #endif /* get battery_table */ for (i = DISCHARGE_TABLE; i < TABLE_MAX; i++) { snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s%d", battery_id, "battery_table", i); ret = of_property_read_u32_array(np, prop_name, table, 16); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); } for (j = 0; j <= SM5703_FG_TABLE_LEN; j++) { fuelgauge->info.battery_table[i][j] = table[j]; /* PINFO("%s = \n", prop_name, i, j, table[j]); */ } } /* get rce */ for (i = 0; i < 3; i++) { snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "rce_value"); ret = of_property_read_u32_array(np, prop_name, rce_value, 3); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); } fuelgauge->info.rce_value[i] = rce_value[i]; } PINFO("%s = <0x%x 0x%x 0x%x>\n", prop_name, rce_value[0], rce_value[1], rce_value[2]); /* get dtcd_value */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "dtcd_value"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.dtcd_value, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n",prop_name, fuelgauge->info.dtcd_value); /* get rs_value */ for (i = 0; i < 4; i++) { snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "rs_value"); ret = of_property_read_u32_array(np, prop_name, rs_value, 4); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); } fuelgauge->info.rs_value[i] = rs_value[i]; } PINFO("%s = <0x%x 0x%x 0x%x 0x%x>\n", prop_name, rs_value[0], rs_value[1], rs_value[2], rs_value[3]); /* get vit_period */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "vit_period"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.vit_period, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n",prop_name, fuelgauge->info.vit_period); /* get mix_value */ for (i = 0; i < 2; i++) { snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "mix_value"); ret = of_property_read_u32_array(np, prop_name, mix_value, 2); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); } fuelgauge->info.mix_value[i] = mix_value[i]; } PINFO("%s = <0x%x 0x%x>\n", prop_name, mix_value[0], mix_value[1]); /* battery_type */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "battery_type"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.battery_type, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <%d>\n", prop_name, fuelgauge->info.battery_type); /* TOP OFF SOC */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "topoff_soc"); ret = of_property_read_u32_array(np, prop_name, topoff_soc, 2); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); fuelgauge->info.enable_topoff_soc = topoff_soc[0]; fuelgauge->info.topoff_soc = topoff_soc[1]; PINFO("%s = <0x%x 0x%x>\n", prop_name, fuelgauge->info.enable_topoff_soc, fuelgauge->info.topoff_soc); /* VOL & CURR CAL */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "volt_cal"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.volt_cal, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n", prop_name, fuelgauge->info.volt_cal); snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "curr_cal"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.curr_cal, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n", prop_name, fuelgauge->info.curr_cal); /* temp_std */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "temp_std"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.temp_std, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <%d>\n", prop_name, fuelgauge->info.temp_std); /* temp_offset */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "temp_offset"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.temp_offset, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <%d>\n", prop_name, fuelgauge->info.temp_offset); /* temp_offset_cal */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "temp_offset_cal"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.temp_offset_cal, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n", prop_name, fuelgauge->info.temp_offset_cal); /* charge_offset_cal */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "charge_offset_cal"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.charge_offset_cal, 1); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); PINFO("%s = <0x%x>\n", prop_name, fuelgauge->info.charge_offset_cal); /* min_charge_curr */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "min_charge_curr"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.min_charge_curr, 1); if (ret < 0) { fuelgauge->info.min_charge_curr = 200; PINFO("Can get prop %s (%d)\n", prop_name, ret); } PINFO("%s = <%d>\n", prop_name, fuelgauge->info.min_charge_curr); /* ext_temp_calc */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "ext_temp_cal"); ret = of_property_read_u32_array(np, prop_name, ext_temp_cal, 8); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); fuelgauge->info.en_high_temp_cal = ext_temp_cal[0]; fuelgauge->info.high_temp_cal_denom = ext_temp_cal[1]; fuelgauge->info.high_temp_p_cal_fact = ext_temp_cal[2]; fuelgauge->info.high_temp_n_cal_fact = ext_temp_cal[3]; fuelgauge->info.en_low_temp_cal = ext_temp_cal[4]; fuelgauge->info.low_temp_cal_denom = ext_temp_cal[5]; fuelgauge->info.low_temp_p_cal_fact = ext_temp_cal[6]; fuelgauge->info.low_temp_n_cal_fact = ext_temp_cal[7]; PINFO("%s = <%d, %d, %d, %d, %d, %d, %d, %d>\n", prop_name, fuelgauge->info.en_high_temp_cal, fuelgauge->info.high_temp_cal_denom, fuelgauge->info.high_temp_p_cal_fact, fuelgauge->info.high_temp_n_cal_fact, fuelgauge->info.en_low_temp_cal, fuelgauge->info.low_temp_cal_denom, fuelgauge->info.low_temp_p_cal_fact, fuelgauge->info.low_temp_n_cal_fact); /* tem poff level */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "tem_poff"); ret = of_property_read_u32_array(np, prop_name, set_temp_poff, 4); if (ret < 0) PINFO("Can get prop %s (%d)\n", prop_name, ret); fuelgauge->info.n_tem_poff = set_temp_poff[0]; fuelgauge->info.n_tem_poff_offset = set_temp_poff[1]; fuelgauge->info.l_tem_poff = set_temp_poff[2]; fuelgauge->info.l_tem_poff_offset = set_temp_poff[3]; PINFO("%s = <%d, %d, %d, %d>\n", prop_name, fuelgauge->info.n_tem_poff, fuelgauge->info.n_tem_poff_offset, fuelgauge->info.l_tem_poff, fuelgauge->info.l_tem_poff_offset); /* V_ALARM */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "v_alarm"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.value_v_alarm, 1); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); fuelgauge->info.value_v_alarm = 3200; } PINFO("%s = <%d>\n", prop_name, fuelgauge->info.value_v_alarm); /* batt data version */ snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", battery_id, "data_ver"); ret = of_property_read_u32_array(np, prop_name, &fuelgauge->info.data_ver, 1); if (ret < 0) { PINFO("Can get prop %s (%d)\n", prop_name, ret); fuelgauge->info.data_ver = 0; } PINFO("%s = <%d>\n", prop_name, fuelgauge->info.data_ver); return 0; } #else static int sm5703_fg_parse_dt(struct sec_fuelgauge_info *fuelgauge) { return 0; } #endif bool sec_hal_fg_init(struct i2c_client *client) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); struct battery_data_t *battery_data; dev_info(&client->dev, "sm5703 sec_hal_fg_init...\n"); mutex_init(&fuelgauge->info.param_lock); mutex_lock(&fuelgauge->info.param_lock); if (client->dev.of_node) { // Load battery data from DTS sm5703_fg_parse_dt(fuelgauge); } else { // Copy battery data from platform data battery_data = &get_battery_data(fuelgauge); fuelgauge->info.battery_type = battery_data->battery_type; } //struct battery_data_t *battery_data = &get_battery_data(fuelgauge); sm5703_fg_init(client, false); sm5703_pr_ver_info(client); fuelgauge->info.temperature = 250; mutex_unlock(&fuelgauge->info.param_lock); dev_info(&client->dev, "sm5703 hal fg init OK\n"); return true; } bool sec_hal_fg_suspend(struct i2c_client *client) { dev_dbg(&client->dev, "%s: sec_hal_fg_suspend\n", __func__); return true; } bool sec_hal_fg_resume(struct i2c_client *client) { dev_dbg(&client->dev, "%s: sec_hal_fg_resume\n", __func__); return true; } bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); int ret; int value_v_alarm, value_soc_alarm; if (soc >= 0) { dev_info(&client->dev, "%s: sec_hal_fg_fuelalert_init\n", __func__); /* remove interrupt */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_INTFG); /* check status */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_STATUS); /* remove all mask */ sm5703_fg_i2c_write_word(client,SM5703_REG_INTFG_MASK, 0x0000); /* enable volt and soc alert only, mask other alerts */ ret = MASK_H_TEM_INT | MASK_L_TEM_INT; #ifdef CONFIG_SEC_FACTORY /* mask voltage/soc alert in Factory test*/ ret = ret | MASK_L_VOL_INT | MASK_L_SOC_INT; #endif sm5703_fg_i2c_write_word(client,SM5703_REG_INTFG_MASK,ret); fuelgauge->info.irq_ctrl = ~(ret); /* set volt and soc alert threshold */ value_v_alarm = (((fuelgauge->info.value_v_alarm)<<8)/1000); sm5703_fg_i2c_write_word(client, SM5703_REG_V_ALARM, value_v_alarm); value_soc_alarm = 0x0100; // 1.00% sm5703_fg_i2c_write_word(client, SM5703_REG_SOC_ALARM, value_soc_alarm); /* update parameters */ sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 0); sm5703_fg_i2c_write_word(client, SM5703_REG_PARAM_RUN_UPDATE, 1); /* enable volt alert and low soc control, disable other alerts */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); ret = ret | ENABLE_V_ALARM | ENABLE_SOC_ALARM; ret = ret & (~ENABLE_T_H_ALARM & ~ENABLE_T_L_ALARM); #ifdef CONFIG_SEC_FACTORY /* disable voltage/soc alert in Factory test*/ ret = ret & (~ENABLE_V_ALARM) & (~ENABLE_SOC_ALARM); #endif sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, ret); dev_info(&client->dev, "%s: irq_ctrl=0x%x, REG_CNTL=0x%x, V_ALARM=0x%x, SOC_ALARM=0x%x \n", __func__, fuelgauge->info.irq_ctrl, ret, value_v_alarm, value_soc_alarm); } /* alert flag init*/ fuelgauge->info.soc_alert_flag = false; fuelgauge->is_fuel_alerted = false; return true; } bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); int ret; dev_info(&client->dev, "%s: sec_hal_fg_is_fuelalerted\n", __func__); /* alert process */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_INTFG); dev_info(&client->dev, "%s: SM5703_REG_INTFG(0x%x)\n", __func__, ret); if (ret & fuelgauge->info.irq_ctrl) { /* check status */ ret = sm5703_fg_i2c_read_word(client, SM5703_REG_STATUS); dev_info(&client->dev, "%s: SM5703_REG_STATUS(0x%x)\n", __func__, ret); if (ret & fuelgauge->info.irq_ctrl) { return true; } } return false; } /* capacity is 0.1% unit */ static void sec_fg_get_scaled_capacity( struct sec_fuelgauge_info *fuelgauge, union power_supply_propval *val) { val->intval = (val->intval < fuelgauge->pdata->capacity_min) ? 0 : ((val->intval - fuelgauge->pdata->capacity_min) * 1000 / (fuelgauge->capacity_max - fuelgauge->pdata->capacity_min)); dev_dbg(&fuelgauge->client->dev, "%s: scaled capacity (%d.%d)\n", __func__, val->intval/10, val->intval%10); } /* capacity is integer */ static void sec_fg_get_atomic_capacity( struct sec_fuelgauge_info *fuelgauge, union power_supply_propval *val) { if (fuelgauge->pdata->capacity_calculation_type & SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) { if (fuelgauge->capacity_old < val->intval) val->intval = fuelgauge->capacity_old + 1; else if (fuelgauge->capacity_old > val->intval) val->intval = fuelgauge->capacity_old - 1; } /* keep SOC stable in abnormal status */ if (fuelgauge->pdata->capacity_calculation_type & SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) { if (!fuelgauge->is_charging && fuelgauge->capacity_old < val->intval) { dev_err(&fuelgauge->client->dev, "%s: capacity (old %d : new %d)\n", __func__, fuelgauge->capacity_old, val->intval); val->intval = fuelgauge->capacity_old; } } /* updated old capacity */ fuelgauge->capacity_old = val->intval; } static int sec_fg_check_capacity_max( struct sec_fuelgauge_info *fuelgauge, int capacity_max) { int new_capacity_max = capacity_max; if (new_capacity_max < (fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin - 10)) { new_capacity_max = (fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin); dev_info(&fuelgauge->client->dev, "%s: set capacity max(%d --> %d)\n", __func__, capacity_max, new_capacity_max); } else if (new_capacity_max > (fuelgauge->pdata->capacity_max + fuelgauge->pdata->capacity_max_margin)) { new_capacity_max = (fuelgauge->pdata->capacity_max + fuelgauge->pdata->capacity_max_margin); dev_info(&fuelgauge->client->dev, "%s: set capacity max(%d --> %d)\n", __func__, capacity_max, new_capacity_max); } return new_capacity_max; } static int sec_fg_calculate_dynamic_scale( struct sec_fuelgauge_info *fuelgauge, int capacity) { union power_supply_propval raw_soc_val; raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW; if (!sec_hal_fg_get_property(fuelgauge->client, POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val)) return -EINVAL; raw_soc_val.intval /= 10; if (raw_soc_val.intval < fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin) { fuelgauge->capacity_max = fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin; dev_dbg(&fuelgauge->client->dev, "%s: capacity_max (%d)", __func__, fuelgauge->capacity_max); } else { fuelgauge->capacity_max = (raw_soc_val.intval > fuelgauge->pdata->capacity_max + fuelgauge->pdata->capacity_max_margin) ? (fuelgauge->pdata->capacity_max + fuelgauge->pdata->capacity_max_margin) : raw_soc_val.intval; dev_dbg(&fuelgauge->client->dev, "%s: raw soc (%d)", __func__, fuelgauge->capacity_max); } if (capacity != 100) { fuelgauge->capacity_max = sec_fg_check_capacity_max( fuelgauge, (fuelgauge->capacity_max * 100 / capacity)); } else { fuelgauge->capacity_max = (fuelgauge->capacity_max * 99 / 100); } /* update capacity_old for sec_fg_get_atomic_capacity algorithm */ fuelgauge->capacity_old = capacity; dev_info(&fuelgauge->client->dev, "%s: %d is used for capacity_max\n", __func__, fuelgauge->capacity_max); return fuelgauge->capacity_max; } bool sec_hal_fg_full_charged(struct i2c_client *client) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); fuelgauge->info.flag_full_charge = 1; dev_dbg(&client->dev, "%s: full_charged\n", __func__); return true; } bool sm5703_fg_reset(struct i2c_client *client) { dev_dbg(&client->dev, "%s: sec_hal_fg_reset\n", __func__); // delay 50ms msleep(50); // SW reset code sm5703_fg_i2c_write_word(client, 0x90, 0x0008); // delay 400ms msleep(400); // init code sm5703_fg_init(client, false); return true; } static void sm5703_fg_reset_capacity_by_jig_connection(struct sec_fuelgauge_info *fuelgauge) { union power_supply_propval value; int ret; ret = sm5703_fg_i2c_read_word(fuelgauge->client, SM5703_REG_CNTL); ret |= 0x0010; sm5703_fg_i2c_write_word(fuelgauge->client, SM5703_REG_CNTL, ret); /* If JIG is attached, the voltage is set as 1079 */ value.intval = 1079; psy_do_property("battery", set, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); } static int sm5703_fg_get_jig_mode_real_vbat(struct i2c_client *client) { int cntl, ret; cntl = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); pr_info("%s: start, CNTL=0x%x\n", __func__, cntl); if (sm5703_fg_check_reg_init_need(client)) { return -1; } cntl = cntl | ENABLE_MODE_nENQ4; sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, cntl); msleep(300); ret = sm5703_get_vbat(client); pr_info("%s: jig mode real batt V = %d, CNTL=0x%x\n", __func__, ret, cntl); cntl = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); cntl = cntl & (~ENABLE_MODE_nENQ4); sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, cntl); pr_info("%s: end_1, CNTL=0x%x\n", __func__, cntl); msleep(300); cntl = sm5703_fg_i2c_read_word(client, SM5703_REG_CNTL); cntl = cntl & (~ENABLE_MODE_nENQ4); sm5703_fg_i2c_write_word(client, SM5703_REG_CNTL, cntl); pr_info("%s: end_2, CNTL=0x%x\n", __func__, cntl); return ret; } bool sec_hal_fg_get_property(struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) { union power_supply_propval value; struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); psy_do_property("sm5703-charger", get, POWER_SUPPLY_PROP_STATUS, value); fuelgauge->info.flag_full_charge = (value.intval == POWER_SUPPLY_STATUS_FULL) ? 1 : 0; fuelgauge->info.flag_chg_status = (value.intval == POWER_SUPPLY_STATUS_CHARGING) ? 1 : 0; /* dev_info(&client->dev, "%s: psp=%d, val->intval=%d\n", __func__, psp, val->intval); */ switch (psp) { /* Cell voltage (VCELL, mV) */ case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = sm5703_get_vbat(client); break; /* Additional Voltage Information (mV) */ case POWER_SUPPLY_PROP_VOLTAGE_AVG: switch (val->intval) { case SEC_BATTERY_VOLTAGE_AVERAGE: sm5703_get_vbat(client); val->intval = fuelgauge->info.batt_avgvoltage; break; case SEC_BATTERY_VOLTAGE_OCV: val->intval = sm5703_get_ocv(client); break; } break; case POWER_SUPPLY_PROP_PRESENT: /* SM5703 does not support this property */ sm5703_fg_get_batt_present(client); break; case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: val->intval = sm5703_fg_get_jig_mode_real_vbat(fuelgauge->client) / 10; break; /* Current (mA) */ case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = sm5703_get_curr(client); break; /* Average Current (mA) */ case POWER_SUPPLY_PROP_CURRENT_AVG: sm5703_get_curr(client); val->intval = fuelgauge->info.batt_avgcurrent; break; case POWER_SUPPLY_PROP_CHARGE_FULL: val->intval = (fuelgauge->info.batt_soc >= 1000) ? true : false; break; /* SOC (%) */ case POWER_SUPPLY_PROP_CAPACITY: /* SM5703 F/G unit is 0.1%, raw ==> convert the unit to 0.01% */ if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) val->intval = sm5703_get_soc(client) * 10; else val->intval = sm5703_get_soc(client); break; /* Battery Temperature */ case POWER_SUPPLY_PROP_TEMP: /* Target Temperature */ case POWER_SUPPLY_PROP_TEMP_AMBIENT: val->intval = sm5703_get_temperature(client); break; default: return false; } return true; } static int sm5703_fg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_fuelgauge_info *fuelgauge = container_of(psy, struct sec_fuelgauge_info, psy_fg); int soc_type = val->intval; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_AVG: case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TEMP_AMBIENT: if (!sec_hal_fg_get_property(fuelgauge->client, psp, val)) return -EINVAL; if (psp == POWER_SUPPLY_PROP_CAPACITY) { if (soc_type == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) break; if (fuelgauge->pdata->capacity_calculation_type & (SEC_FUELGAUGE_CAPACITY_TYPE_SCALE | SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)) sec_fg_get_scaled_capacity(fuelgauge, val); /* capacity should be between 0% and 100% * (0.1% degree) */ if (val->intval > 1000) val->intval = 1000; if (val->intval < 0) val->intval = 0; /* get only integer part */ val->intval /= 10; if ((!fuelgauge->is_charging || (fuelgauge->is_charging && fuelgauge->info.batt_current < 0)) && (fuelgauge->force_dec_mode == SM5703_COLD_MODE)) { pr_info("%s : SW V EMPTY. Decrease SOC\n", __func__); val->intval = 0; } else if ((fuelgauge->force_dec_mode == SM5703_RECOVERY_MODE) && (val->intval == fuelgauge->capacity_old)) { fuelgauge->force_dec_mode = SM5703_NORMAL_MODE; } /* check whether doing the wake_unlock */ if ((val->intval > fuelgauge->pdata->fuel_alert_soc) && fuelgauge->is_fuel_alerted) { wake_unlock(&fuelgauge->fuel_alert_wake_lock); sec_hal_fg_fuelalert_init(fuelgauge->client, fuelgauge->pdata->fuel_alert_soc); } /* (Only for atomic capacity) * In initial time, capacity_old is 0. * and in resume from sleep, * capacity_old is too different from actual soc. * should update capacity_old * by val->intval in booting or resume. */ if (fuelgauge->initial_update_of_soc && fuelgauge->force_dec_mode == SM5703_NORMAL_MODE) { /* updated old capacity */ fuelgauge->capacity_old = val->intval; fuelgauge->initial_update_of_soc = false; break; } if (fuelgauge->pdata->capacity_calculation_type & (SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC | SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL)) sec_fg_get_atomic_capacity(fuelgauge, val); } break; case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: val->intval = fuelgauge->capacity_max; break; case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_CHARGE_FULL: return -ENODATA; default: return -EINVAL; } return 0; } bool sec_hal_fg_set_property(struct i2c_client *client, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); dev_info(&client->dev, "%s: psp=%d\n", __func__, psp); switch (psp) { /* Battery Temperature */ case POWER_SUPPLY_PROP_TEMP: fuelgauge->info.temperature = val->intval; break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: break; default: return false; } return true; } ssize_t sec_hal_fg_show_attrs(struct device *dev, const ptrdiff_t offset, char *buf) { struct power_supply *psy = dev_get_drvdata(dev); struct sec_fuelgauge_info *fg = container_of(psy, struct sec_fuelgauge_info, psy_fg); int i = 0; dev_dbg(dev, "%s: offset=%td\n", __func__, offset); switch (offset) { case FG_REG: break; case FG_DATA: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", fg->info.batt_soc); break; default: i = -EINVAL; break; } return i; } ssize_t sec_hal_fg_store_attrs(struct device *dev, const ptrdiff_t offset, const char *buf, size_t count) { int ret = 0; dev_dbg(dev, "%s: offset=%td\n", __func__, offset); switch (offset) { case FG_REG: break; case FG_DATA: break; default: ret = -EINVAL; break; } return ret; } static int sm5703_fg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_fuelgauge_info *fuelgauge = container_of(psy, struct sec_fuelgauge_info, psy_fg); switch (psp) { case POWER_SUPPLY_PROP_STATUS: break; case POWER_SUPPLY_PROP_CHARGE_FULL: if (fuelgauge->pdata->capacity_calculation_type & SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) { #if defined(CONFIG_PREVENT_SOC_JUMP) sec_fg_calculate_dynamic_scale(fuelgauge, val->intval); #else sec_fg_calculate_dynamic_scale(fuelgauge, 100); #endif #ifdef ENABLE_BATT_LONG_LIFE pr_info("%s: POWER_SUPPLY_PROP_CHARGE_FULL : q_max_now = 0x%x \n", __func__, fuelgauge->info.q_max_now); if (fuelgauge->info.q_max_now != fuelgauge->info.q_max_table[get_v_max_index_by_cycle(fuelgauge->client)]) { if (!sm5703_fg_reset(fuelgauge->client)) return -EINVAL; } #endif } break; case POWER_SUPPLY_PROP_ONLINE: fuelgauge->cable_type = val->intval; if (val->intval == POWER_SUPPLY_TYPE_BATTERY) fuelgauge->is_charging = false; else { fuelgauge->is_charging = true; if (fuelgauge->force_dec_mode != SM5703_NORMAL_MODE) { fuelgauge->force_dec_mode = SM5703_NORMAL_MODE; fuelgauge->initial_update_of_soc = true; sec_hal_fg_fuelalert_init(fuelgauge->client, fuelgauge->pdata->fuel_alert_soc); } } break; case POWER_SUPPLY_PROP_ENERGY_NOW: sm5703_fg_reset_capacity_by_jig_connection(fuelgauge); break; case POWER_SUPPLY_PROP_CAPACITY: if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) { fuelgauge->initial_update_of_soc = true; if (!sm5703_fg_reset(fuelgauge->client)) return -EINVAL; else break; } case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TEMP_AMBIENT: if (!sec_hal_fg_set_property(fuelgauge->client, psp, val)) return -EINVAL; break; case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: break; case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: dev_info(&fuelgauge->client->dev, "%s: capacity_max changed, %d -> %d\n", __func__, fuelgauge->capacity_max, val->intval); fuelgauge->capacity_max = sec_fg_check_capacity_max(fuelgauge, val->intval); fuelgauge->initial_update_of_soc = true; break; #if defined(CONFIG_BATTERY_AGE_FORECAST) case POWER_SUPPLY_PROP_CAPACITY_LEVEL: pr_info("%s: full condition soc changed, %d -> %d\n", __func__, fuelgauge->chg_full_soc, val->intval); fuelgauge->chg_full_soc = val->intval; break; #endif case POWER_SUPPLY_PROP_CHARGE_TYPE: sm5703_fg_reset_capacity_by_jig_connection(fuelgauge); break; default: return -EINVAL; } return 0; } bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) { struct sec_fuelgauge_info *fuelgauge = irq_data; struct i2c_client *client = fuelgauge->client; int fg_alert_status; dev_info(&client->dev, "%s: is_fuel_alerted=%d \n", __func__, is_fuel_alerted); if (is_fuel_alerted) { fg_alert_status = sm5703_fg_i2c_read_word(client, SM5703_REG_STATUS); dev_info(&client->dev, "%s: SM5703_REG_STATUS(0x%x)\n", __func__, fg_alert_status); fg_alert_status &= fuelgauge->info.irq_ctrl; if (!fg_alert_status) { wake_unlock(&fuelgauge->fuel_alert_wake_lock); } /* not use SOC alarm if (fg_alert_status & fuelgauge->info.irq_ctrl & ENABLE_SOC_ALARM) { fuelgauge->info.soc_alert_flag = true; // todo more action } */ if (fg_alert_status & fuelgauge->info.irq_ctrl & ENABLE_V_ALARM) { pr_info("%s : Battery Voltage is Very Low!! SW V EMPTY ENABLE\n", __func__); fuelgauge->info.volt_alert_flag = true; fuelgauge->force_dec_mode = SM5703_COLD_MODE; } } return true; } static void sm5703_fg_isr_work(struct work_struct *work) { struct sec_fuelgauge_info *fuelgauge = container_of(work, struct sec_fuelgauge_info, isr_work.work); /* process for fuel gauge chip */ sec_hal_fg_fuelalert_process(fuelgauge, fuelgauge->is_fuel_alerted); /* process for others */ if (fuelgauge->pdata->fuelalert_process != NULL) fuelgauge->pdata->fuelalert_process(fuelgauge->is_fuel_alerted); } static irqreturn_t sm5703_fg_irq_thread(int irq, void *irq_data) { struct sec_fuelgauge_info *fuelgauge = irq_data; bool fuel_alerted; if (fuelgauge->pdata->fuel_alert_soc >= 0) { fuel_alerted = sec_hal_fg_is_fuelalerted(fuelgauge->client); dev_info(&fuelgauge->client->dev, "%s: Fuel-alert %salerted!\n", __func__, fuel_alerted ? "" : "NOT "); if (!fuelgauge->is_fuel_alerted) { wake_lock(&fuelgauge->fuel_alert_wake_lock); fuelgauge->is_fuel_alerted = true; schedule_delayed_work(&fuelgauge->isr_work, 0); } } return IRQ_HANDLED; } static int sm5703_create_attrs(struct device *dev) { int i, rc; for (i = 0; i < ARRAY_SIZE(sec_fg_attrs); i++) { rc = device_create_file(dev, &sec_fg_attrs[i]); if (rc) goto create_attrs_failed; } goto create_attrs_succeed; create_attrs_failed: dev_err(dev, "%s: failed (%d)\n", __func__, rc); while (i--) device_remove_file(dev, &sec_fg_attrs[i]); create_attrs_succeed: return rc; } ssize_t sec_fg_show_attrs(struct device *dev, struct device_attribute *attr, char *buf) { const ptrdiff_t offset = attr - sec_fg_attrs; int i = 0; switch (offset) { case FG_REG: case FG_DATA: case FG_REGS: i = sec_hal_fg_show_attrs(dev, offset, buf); break; default: i = -EINVAL; break; } return i; } ssize_t sec_fg_store_attrs(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { const ptrdiff_t offset = attr - sec_fg_attrs; int ret = 0; switch (offset) { case FG_REG: case FG_DATA: ret = sec_hal_fg_store_attrs(dev, offset, buf, count); break; default: ret = -EINVAL; break; } return ret; } #ifdef CONFIG_OF static int fuelgauge_parse_dt(struct device *dev, struct sec_fuelgauge_info *fuelgauge) { struct device_node *np = dev->of_node; sec_battery_platform_data_t *pdata = fuelgauge->pdata; int ret; /* reset, irq gpio info */ if (np == NULL) { pr_err("%s np NULL\n", __func__); } else { ret = of_get_named_gpio(np, "fuelgauge,fuel_int", 0); if (ret > 0) { pdata->fg_irq = ret; pr_info("%s reading fg_irq = %d\n", __func__, ret); } ret = of_get_named_gpio(np, "fuelgauge,bat_int", 0); if (ret > 0) { pdata->bat_irq_gpio = ret; pdata->bat_irq = gpio_to_irq(ret); pr_info("%s reading bat_int_gpio = %d\n", __func__, ret); } ret = of_property_read_u32(np, "fuelgauge,capacity_max", &fuelgauge->pdata->capacity_max); pr_info("%s : fuelgauge->pdata->capacity_max : %d\n", __func__, fuelgauge->pdata->capacity_max); if (ret < 0) pr_err("%s error reading capacity_max %d\n", __func__, ret); ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin", &fuelgauge->pdata->capacity_max_margin); pr_info("%s : fuelgauge,capacity_max_margin : %d\n", __func__, fuelgauge->pdata->capacity_max_margin); if (ret < 0) pr_err("%s error reading capacity_max_margin %d\n", __func__, ret); ret = of_property_read_u32(np, "fuelgauge,capacity_min", &fuelgauge->pdata->capacity_min); pr_info("%s : fuelgauge,capacity_min : %d\n", __func__, fuelgauge->pdata->capacity_min); if (ret < 0) pr_err("%s error reading capacity_min %d\n", __func__, ret); ret = of_property_read_u32(np, "fuelgauge,capacity_calculation_type", &pdata->capacity_calculation_type); if (ret < 0) pr_err("%s error reading capacity_calculation_type %d\n", __func__, ret); ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc", &pdata->fuel_alert_soc); if (ret < 0) pr_err("%s error reading pdata->fuel_alert_soc %d\n", __func__, ret); pdata->repeated_fuelalert = of_property_read_bool(np, "fuelgaguge,repeated_fuelalert"); pr_info("%s: fg_irq: %d, " "calculation_type: 0x%x, fuel_alert_soc: %d,\n" "repeated_fuelalert: %d\n", __func__, pdata->fg_irq, pdata->capacity_calculation_type, pdata->fuel_alert_soc, pdata->repeated_fuelalert ); } return 0; } #else static int fuelgauge_parse_dt(struct device *dev, struct synaptics_rmi4_power_data *pdata) { return -ENODEV; } #endif static int sm5703_fuelgauge_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct sec_fuelgauge_info *fuelgauge; sec_battery_platform_data_t *pdata = NULL; int ret = 0; union power_supply_propval raw_soc_val; dev_info(&client->dev, "%s: SM5703 Fuelgauge Driver Loading\n", __func__); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); if (!fuelgauge) return -ENOMEM; mutex_init(&fuelgauge->fg_lock); fuelgauge->client = client; if (client->dev.of_node) { int error; pdata = devm_kzalloc(&client->dev, sizeof(sec_battery_platform_data_t), GFP_KERNEL); if (!pdata) { dev_err(&client->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err_free; } fuelgauge->pdata = pdata; #if defined(CONFIG_BATTERY_AGE_FORECAST) temp_parse_dt(fuelgauge); #endif error = fuelgauge_parse_dt(&client->dev, fuelgauge); if (error) { dev_err(&client->dev, "%s: Failed to get fuel_int\n", __func__); } } else { dev_err(&client->dev, "%s: Failed to get of_node\n", __func__); fuelgauge->pdata = client->dev.platform_data; } i2c_set_clientdata(client, fuelgauge); if (fuelgauge->pdata->fg_gpio_init != NULL) { dev_err(&client->dev, "%s: @@@\n", __func__); if (!fuelgauge->pdata->fg_gpio_init()) { dev_err(&client->dev, "%s: Failed to Initialize GPIO\n", __func__); goto err_devm_free; } } if (!sec_hal_fg_init(fuelgauge->client)) { dev_err(&client->dev, "%s: Failed to Initialize Fuelgauge\n", __func__); goto err_devm_free; } fuelgauge->psy_fg.name = "sm5703-fuelgauge"; fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_UNKNOWN; fuelgauge->psy_fg.get_property = sm5703_fg_get_property; fuelgauge->psy_fg.set_property = sm5703_fg_set_property; fuelgauge->psy_fg.properties = sm5703_fuelgauge_props; fuelgauge->psy_fg.num_properties = ARRAY_SIZE(sm5703_fuelgauge_props); fuelgauge->capacity_max = fuelgauge->pdata->capacity_max; raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW; sec_hal_fg_get_property(fuelgauge->client, POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val); raw_soc_val.intval /= 10; if (raw_soc_val.intval > fuelgauge->pdata->capacity_max) sec_fg_calculate_dynamic_scale(fuelgauge, 100); ret = power_supply_register(&client->dev, &fuelgauge->psy_fg); if (ret) { dev_err(&client->dev, "%s: Failed to Register psy_fg\n", __func__); goto err_free; } fuelgauge->is_fuel_alerted = false; if (fuelgauge->pdata->fuel_alert_soc >= 0) { if (sec_hal_fg_fuelalert_init(fuelgauge->client, fuelgauge->pdata->fuel_alert_soc)) wake_lock_init(&fuelgauge->fuel_alert_wake_lock, WAKE_LOCK_SUSPEND, "fuel_alerted"); else { dev_err(&client->dev, "%s: Failed to Initialize Fuel-alert\n", __func__); goto err_irq; } } if (fuelgauge->pdata->fg_irq > 0) { INIT_DELAYED_WORK( &fuelgauge->isr_work, sm5703_fg_isr_work); fuelgauge->fg_irq = gpio_to_irq(fuelgauge->pdata->fg_irq); dev_info(&client->dev, "%s: fg_irq = %d\n", __func__, fuelgauge->fg_irq); if (fuelgauge->fg_irq > 0) { ret = request_threaded_irq(fuelgauge->fg_irq, NULL, sm5703_fg_irq_thread, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "fuelgauge-irq", fuelgauge); if (ret) { dev_err(&client->dev, "%s: Failed to Reqeust IRQ\n", __func__); goto err_supply_unreg; } ret = enable_irq_wake(fuelgauge->fg_irq); if (ret < 0) dev_err(&client->dev, "%s: Failed to Enable Wakeup Source(%d)\n", __func__, ret); } else { dev_err(&client->dev, "%s: Failed gpio_to_irq(%d)\n", __func__, fuelgauge->fg_irq); goto err_supply_unreg; } } fuelgauge->initial_update_of_soc = true; fuelgauge->force_dec_mode = SM5703_NORMAL_MODE; if (sec_bat_check_jig_status()) sm5703_fg_reset_capacity_by_jig_connection(fuelgauge); ret = sm5703_create_attrs(fuelgauge->psy_fg.dev); if (ret) { dev_err(&client->dev, "%s : Failed to create_attrs\n", __func__); goto err_irq; } dev_info(&client->dev, "%s: SEC Fuelgauge Driver Loaded\n", __func__); return 0; err_irq: if (fuelgauge->fg_irq > 0) free_irq(fuelgauge->fg_irq, fuelgauge); wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); err_supply_unreg: power_supply_unregister(&fuelgauge->psy_fg); err_devm_free: if (pdata) devm_kfree(&client->dev, pdata); err_free: mutex_destroy(&fuelgauge->fg_lock); kfree(fuelgauge); dev_info(&client->dev, "%s: Fuel gauge probe failed\n", __func__); return ret; } static int sm5703_fuelgauge_remove( struct i2c_client *client) { struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client); if (fuelgauge->pdata->fuel_alert_soc >= 0) wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock); return 0; } static int sm5703_fuelgauge_suspend(struct device *dev) { struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev); if (!sec_hal_fg_suspend(fuelgauge->client)) dev_err(&fuelgauge->client->dev, "%s: Failed to Suspend Fuelgauge\n", __func__); return 0; } static int sm5703_fuelgauge_resume(struct device *dev) { struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev); fuelgauge->initial_update_of_soc = true; if (!sec_hal_fg_resume(fuelgauge->client)) dev_err(&fuelgauge->client->dev, "%s: Failed to Resume Fuelgauge\n", __func__); return 0; } static void sm5703_fuelgauge_shutdown(struct i2c_client *client) { } static const struct i2c_device_id sm5703_fuelgauge_id[] = { {"sm5703-fuelgauge", 0}, {} }; static const struct dev_pm_ops sm5703_fuelgauge_pm_ops = { .suspend = sm5703_fuelgauge_suspend, .resume = sm5703_fuelgauge_resume, }; MODULE_DEVICE_TABLE(i2c, sm5703_fuelgauge_id); static struct of_device_id fuelgague_i2c_match_table[] = { { .compatible = "sm5703-fuelgauge,i2c", }, { }, }; MODULE_DEVICE_TABLE(i2c, fuelgague_i2c_match_table); static struct i2c_driver sm5703_fuelgauge_driver = { .driver = { .name = "sm5703-fuelgauge", .owner = THIS_MODULE, .of_match_table = fuelgague_i2c_match_table, #ifdef CONFIG_PM .pm = &sm5703_fuelgauge_pm_ops, #endif }, .probe = sm5703_fuelgauge_probe, .remove = sm5703_fuelgauge_remove, .shutdown = sm5703_fuelgauge_shutdown, .id_table = sm5703_fuelgauge_id, }; static int __init sm5703_fuelgauge_init(void) { pr_info("%s \n", __func__); return i2c_add_driver(&sm5703_fuelgauge_driver); } static void __exit sm5703_fuelgauge_exit(void) { i2c_del_driver(&sm5703_fuelgauge_driver); } module_init(sm5703_fuelgauge_init); module_exit(sm5703_fuelgauge_exit); MODULE_DESCRIPTION("Samsung SM5703 Fuel Gauge Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");