| /* |
| * max17048_fuelgauge.c |
| * Samsung MAX17048 Fuel Gauge Driver |
| * |
| * Copyright (C) 2012 Samsung Electronics |
| * |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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 <linux/battery/sec_fuelgauge.h> |
| #include <linux/sec_batt.h> |
| |
| #if 0 |
| static int max17048_write_reg(struct i2c_client *client, int reg, u8 value) |
| { |
| int ret; |
| |
| ret = i2c_smbus_write_byte_data(client, reg, value); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| #endif |
| |
| static int max17048_read_reg(struct i2c_client *client, int reg) |
| { |
| int ret; |
| |
| ret = i2c_smbus_read_byte_data(client, reg); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int max17048_read_word(struct i2c_client *client, int reg) |
| { |
| int ret; |
| |
| ret = i2c_smbus_read_word_data(client, reg); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int max17048_write_word(struct i2c_client *client, int reg, u16 buf) |
| { |
| int ret; |
| |
| ret = i2c_smbus_write_word_data(client, reg, buf); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static void max17048_reset(struct i2c_client *client) |
| { |
| u16 mode, reset_cmd; |
| |
| mode = max17048_read_word(client, MAX17048_MODE_MSB); |
| |
| mode = swab16(mode); |
| reset_cmd = swab16(mode | 0x4000); |
| |
| i2c_smbus_write_word_data(client, MAX17048_MODE_MSB, reset_cmd); |
| |
| msleep(300); |
| } |
| |
| static int max17048_get_vcell(struct i2c_client *client) |
| { |
| u32 vcell; |
| u16 w_data; |
| u32 temp; |
| |
| temp = max17048_read_word(client, MAX17048_VCELL_MSB); |
| |
| w_data = swab16(temp); |
| |
| temp = ((w_data & 0xFFF0) >> 4) * 1250; |
| vcell = temp / 1000; |
| |
| dev_dbg(&client->dev, |
| "%s : vcell (%d)\n", __func__, vcell); |
| |
| return vcell; |
| } |
| |
| static int max17048_get_avg_vcell(struct i2c_client *client) |
| { |
| u32 vcell_data = 0; |
| u32 vcell_max = 0; |
| u32 vcell_min = 0; |
| u32 vcell_total = 0; |
| u32 i; |
| |
| for (i = 0; i < AVER_SAMPLE_CNT; i++) { |
| vcell_data = max17048_get_vcell(client); |
| |
| if (i != 0) { |
| if (vcell_data > vcell_max) |
| vcell_max = vcell_data; |
| else if (vcell_data < vcell_min) |
| vcell_min = vcell_data; |
| } else { |
| vcell_max = vcell_data; |
| vcell_min = vcell_data; |
| } |
| vcell_total += vcell_data; |
| } |
| |
| return (vcell_total - vcell_max - vcell_min) / (AVER_SAMPLE_CNT-2); |
| } |
| |
| static int max17048_get_ocv(struct i2c_client *client) |
| { |
| u32 ocv; |
| u16 w_data; |
| u32 temp; |
| u16 cmd; |
| |
| cmd = swab16(0x4A57); |
| max17048_write_word(client, 0x3E, cmd); |
| |
| temp = max17048_read_word(client, MAX17048_OCV_MSB); |
| |
| w_data = swab16(temp); |
| |
| temp = ((w_data & 0xFFF0) >> 4) * 1250; |
| ocv = temp / 1000; |
| |
| cmd = swab16(0x0000); |
| max17048_write_word(client, 0x3E, cmd); |
| |
| dev_dbg(&client->dev, |
| "%s : ocv (%d)\n", __func__, ocv); |
| |
| return ocv; |
| } |
| |
| /* soc should be 0.01% unit */ |
| static int max17048_get_soc(struct i2c_client *client) |
| { |
| struct sec_fuelgauge_info *fuelgauge = |
| i2c_get_clientdata(client); |
| u8 data[2] = {0, 0}; |
| int temp, soc; |
| u64 psoc64 = 0; |
| u64 temp64; |
| u32 divisor = 10000000; |
| |
| temp = max17048_read_word(client, MAX17048_SOC_MSB); |
| |
| if (get_battery_data(fuelgauge).is_using_model_data) { |
| /* [ TempSOC = ((SOC1 * 256) + SOC2) * 0.001953125 ] */ |
| temp64 = swab16(temp); |
| psoc64 = temp64 * 1953125; |
| psoc64 = div_u64(psoc64, divisor); |
| soc = psoc64 & 0xffff; |
| } else { |
| data[0] = temp & 0xff; |
| data[1] = (temp & 0xff00) >> 8; |
| |
| soc = (data[0] * 100) + (data[1] * 100 / 256); |
| } |
| |
| dev_dbg(&client->dev, |
| "%s : raw capacity (%d), data(0x%04x)\n", |
| __func__, soc, (data[0]<<8) | data[1]); |
| |
| return soc; |
| } |
| |
| static int max17048_get_current(struct i2c_client *client) |
| { |
| union power_supply_propval value; |
| |
| psy_do_property("sec-charger", get, |
| POWER_SUPPLY_PROP_CURRENT_NOW, value); |
| |
| return value.intval; |
| } |
| |
| #define DISCHARGE_SAMPLE_CNT 5 |
| static int discharge_cnt=0; |
| static int all_vcell[5] = {0,}; |
| |
| /* if ret < 0, discharge */ |
| static int sec_bat_check_discharge(int vcell) |
| { |
| int i, cnt, ret = 0; |
| |
| all_vcell[discharge_cnt++] = vcell; |
| if (discharge_cnt >= DISCHARGE_SAMPLE_CNT) |
| discharge_cnt = 0; |
| |
| cnt = discharge_cnt; |
| |
| /* check after last value is set */ |
| if (all_vcell[cnt] == 0) |
| return 0; |
| |
| for (i = 0; i < DISCHARGE_SAMPLE_CNT; i++) { |
| if (cnt == i) |
| continue; |
| if (all_vcell[cnt] > all_vcell[i]) |
| ret--; |
| else |
| ret++; |
| } |
| return ret; |
| } |
| |
| /* judge power off or not by current_avg */ |
| static int max17048_get_current_average(struct i2c_client *client) |
| { |
| union power_supply_propval value_bat; |
| union power_supply_propval value_chg; |
| int vcell, soc, curr_avg; |
| int check_discharge; |
| |
| psy_do_property("sec-charger", get, |
| POWER_SUPPLY_PROP_CURRENT_NOW, value_chg); |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_HEALTH, value_bat); |
| vcell = max17048_get_vcell(client); |
| soc = max17048_get_soc(client) / 100; |
| check_discharge = sec_bat_check_discharge(vcell); |
| |
| /* if 0% && under 3.4v && low power charging(1000mA), power off */ |
| if (!lpcharge && (soc <= 0) && (vcell < 3400) && |
| (check_discharge < 0) && |
| (((value_bat.intval == POWER_SUPPLY_HEALTH_OVERHEAT) || |
| (value_bat.intval == POWER_SUPPLY_HEALTH_COLD)))) { |
| pr_info("%s: SOC(%d), Vnow(%d), Inow(%d)\n", |
| __func__, soc, vcell, value_chg.intval); |
| curr_avg = -1; |
| } else { |
| curr_avg = value_chg.intval; |
| } |
| |
| return curr_avg; |
| } |
| |
| void sec_bat_reset_discharge(struct i2c_client *client) |
| { |
| int i; |
| |
| for (i = 0; i < DISCHARGE_SAMPLE_CNT ; i++) |
| all_vcell[i] = 0; |
| discharge_cnt = 0; |
| } |
| |
| static void max17048_get_version(struct i2c_client *client) |
| { |
| u16 w_data; |
| int temp; |
| |
| temp = max17048_read_word(client, MAX17048_VER_MSB); |
| |
| w_data = swab16(temp); |
| |
| dev_info(&client->dev, |
| "MAX17048 Fuel-Gauge Ver 0x%04x\n", w_data); |
| } |
| |
| static u16 max17048_get_rcomp(struct i2c_client *client) |
| { |
| u16 w_data; |
| int temp; |
| |
| temp = max17048_read_word(client, MAX17048_RCOMP_MSB); |
| |
| w_data = swab16(temp); |
| |
| dev_dbg(&client->dev, |
| "%s : current rcomp = 0x%04x\n", |
| __func__, w_data); |
| |
| return w_data; |
| } |
| |
| static void max17048_set_rcomp(struct i2c_client *client, u16 new_rcomp) |
| { |
| i2c_smbus_write_word_data(client, |
| MAX17048_RCOMP_MSB, swab16(new_rcomp)); |
| } |
| |
| static void max17048_rcomp_update(struct i2c_client *client, int temp) |
| { |
| struct sec_fuelgauge_info *fuelgauge = |
| i2c_get_clientdata(client); |
| union power_supply_propval value; |
| |
| int starting_rcomp = 0; |
| int new_rcomp = 0; |
| int rcomp_current = 0; |
| |
| rcomp_current = max17048_get_rcomp(client); |
| |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_STATUS, value); |
| |
| if (value.intval == POWER_SUPPLY_STATUS_CHARGING) /* in charging */ |
| starting_rcomp = get_battery_data(fuelgauge).RCOMP_charging; |
| else |
| starting_rcomp = get_battery_data(fuelgauge).RCOMP0; |
| |
| if (temp > RCOMP0_TEMP) |
| new_rcomp = starting_rcomp + ((temp - RCOMP0_TEMP) * |
| get_battery_data(fuelgauge).temp_cohot / 1000); |
| else if (temp < RCOMP0_TEMP) |
| new_rcomp = starting_rcomp + ((temp - RCOMP0_TEMP) * |
| get_battery_data(fuelgauge).temp_cocold / 1000); |
| else |
| new_rcomp = starting_rcomp; |
| |
| if (new_rcomp > 255) |
| new_rcomp = 255; |
| else if (new_rcomp < 0) |
| new_rcomp = 0; |
| |
| new_rcomp <<= 8; |
| new_rcomp &= 0xff00; |
| /* not related to RCOMP */ |
| new_rcomp |= (rcomp_current & 0xff); |
| |
| if (rcomp_current != new_rcomp) { |
| dev_dbg(&client->dev, |
| "%s : RCOMP 0x%04x -> 0x%04x (0x%02x)\n", |
| __func__, rcomp_current, new_rcomp, |
| new_rcomp >> 8); |
| max17048_set_rcomp(client, new_rcomp); |
| } |
| } |
| |
| #ifdef CONFIG_OF |
| static int max17048_parse_dt(struct device *dev, |
| struct sec_fuelgauge_info *fuelgauge) |
| { |
| struct device_node *np = dev->of_node; |
| int ret; |
| int value; |
| |
| if (np == NULL) { |
| pr_err("%s np NULL\n", __func__); |
| } else { |
| ret = of_property_read_u32(np, "fuelgauge,rcomp0", |
| &value); |
| pr_err("%s value %d\n", |
| __func__, value); |
| get_battery_data(fuelgauge).RCOMP0 = (u8)value; |
| if (ret < 0) |
| pr_err("%s error reading rcomp0 %d\n", |
| __func__, ret); |
| ret = of_property_read_u32(np, "fuelgauge,rcomp_charging", |
| &value); |
| pr_err("%s value %d\n", |
| __func__, value); |
| get_battery_data(fuelgauge).RCOMP_charging = (u8)value; |
| if (ret < 0) |
| pr_err("%s error reading rcomp_charging %d\n", |
| __func__, ret); |
| ret = of_property_read_u32(np, "fuelgauge,temp_cohot", |
| &get_battery_data(fuelgauge).temp_cohot); |
| if (ret < 0) |
| pr_err("%s error reading temp_cohot %d\n", |
| __func__, ret); |
| ret = of_property_read_u32(np, "fuelgauge,temp_cocold", |
| &get_battery_data(fuelgauge).temp_cocold); |
| if (ret < 0) |
| pr_err("%s error reading temp_cocold %d\n", |
| __func__, ret); |
| get_battery_data(fuelgauge).is_using_model_data = of_property_read_bool(np, |
| "fuelgauge,is_using_model_data"); |
| ret = of_property_read_string(np, "fuelgauge,type_str", |
| (const char **)&get_battery_data(fuelgauge).type_str); |
| if (ret < 0) |
| pr_err("%s error reading temp_cocold %d\n", |
| __func__, ret); |
| |
| pr_info("%s RCOMP0: 0x%x, RCOMP_charging: 0x%x, temp_cohot: %d," |
| "temp_cocold: %d, is_using_model_data: %d, " |
| "type_str: %s,\n", __func__, |
| get_battery_data(fuelgauge).RCOMP0, |
| get_battery_data(fuelgauge).RCOMP_charging, |
| get_battery_data(fuelgauge).temp_cohot, |
| get_battery_data(fuelgauge).temp_cocold, |
| get_battery_data(fuelgauge).is_using_model_data, |
| get_battery_data(fuelgauge).type_str |
| ); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static void fg_read_regs(struct i2c_client *client, char *str) |
| { |
| int data = 0; |
| u32 addr = 0; |
| |
| for (addr = 0x02; addr <= 0x04; addr += 2) { |
| data = max17048_read_word(client, addr); |
| sprintf(str + strlen(str), "0x%04x, ", data); |
| } |
| |
| /* "#" considered as new line in application */ |
| sprintf(str+strlen(str), "#"); |
| |
| for (addr = 0x08; addr <= 0x1a; addr += 2) { |
| data = max17048_read_word(client, addr); |
| sprintf(str + strlen(str), "0x%04x, ", data); |
| } |
| } |
| |
| bool sec_hal_fg_init(struct i2c_client *client) |
| { |
| #ifdef CONFIG_OF |
| struct sec_fuelgauge_info *fuelgauge = |
| i2c_get_clientdata(client); |
| int error; |
| |
| error = max17048_parse_dt(&client->dev, fuelgauge); |
| |
| if (error) { |
| dev_err(&client->dev, |
| "%s : Failed to get max17048 fuel_init\n", __func__); |
| return false; |
| } |
| #endif |
| pr_info("%s\n", __func__); |
| |
| max17048_get_version(client); |
| |
| return true; |
| } |
| |
| bool sec_hal_fg_suspend(struct i2c_client *client) |
| { |
| return true; |
| } |
| |
| bool sec_hal_fg_resume(struct i2c_client *client) |
| { |
| return true; |
| } |
| |
| bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc) |
| { |
| u16 temp; |
| u8 data; |
| |
| temp = max17048_get_rcomp(client); |
| data = 32 - soc; /* set soc for fuel alert */ |
| temp &= 0xff00; |
| temp += data; |
| |
| dev_dbg(&client->dev, |
| "%s : new rcomp = 0x%04x\n", |
| __func__, temp); |
| |
| max17048_set_rcomp(client, temp); |
| |
| return true; |
| } |
| |
| bool sec_hal_fg_is_fuelalerted(struct i2c_client *client) |
| { |
| u16 temp; |
| |
| temp = max17048_get_rcomp(client); |
| |
| if (temp & 0x20) /* ALRT is asserted */ |
| return true; |
| |
| return false; |
| } |
| |
| bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted) |
| { |
| return true; |
| } |
| |
| bool sec_hal_fg_full_charged(struct i2c_client *client) |
| { |
| return true; |
| } |
| |
| bool sec_hal_fg_reset(struct i2c_client *client) |
| { |
| max17048_reset(client); |
| return true; |
| } |
| |
| bool sec_hal_fg_get_property(struct i2c_client *client, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| int i, pr_cnt = 1; |
| union power_supply_propval value_bat; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = 0; |
| break; |
| /* Cell voltage (VCELL, mV) */ |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = max17048_get_vcell(client); |
| break; |
| /* Additional Voltage Information (mV) */ |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| switch (val->intval) { |
| case SEC_BATTEY_VOLTAGE_AVERAGE: |
| val->intval = max17048_get_avg_vcell(client); |
| break; |
| case SEC_BATTEY_VOLTAGE_OCV: |
| val->intval = max17048_get_ocv(client); |
| break; |
| } |
| break; |
| /* Current (mA) */ |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_STATUS, value_bat); |
| if(value_bat.intval == POWER_SUPPLY_STATUS_DISCHARGING) |
| val->intval = -max17048_get_current(client); |
| else |
| val->intval = max17048_get_current(client); |
| break; |
| /* Average Current (mA) */ |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| val->intval = max17048_get_current_average(client); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL: |
| break; |
| case POWER_SUPPLY_PROP_ENERGY_NOW: |
| break; |
| /* SOC (%) */ |
| case POWER_SUPPLY_PROP_CAPACITY: |
| if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) { |
| val->intval = max17048_get_soc(client); |
| } else { |
| val->intval = max17048_get_soc(client) / 10; |
| if (!(pr_cnt++ % 10)) { |
| pr_cnt = 1; |
| for (i = 0x02; i < 0x1C; i++) |
| printk("0x%02x(0x%02x), ", |
| i, max17048_read_reg(client, i)); |
| printk("\n"); |
| } |
| } |
| break; |
| /* Battery Temperature */ |
| case POWER_SUPPLY_PROP_TEMP: |
| /* Target Temperature */ |
| case POWER_SUPPLY_PROP_TEMP_AMBIENT: |
| break; |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| bool sec_hal_fg_set_property(struct i2c_client *client, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| sec_bat_reset_discharge(client); |
| break; |
| /* Battery Temperature */ |
| case POWER_SUPPLY_PROP_TEMP: |
| /* Target Temperature */ |
| /* temperature is 0.1 degree, should be divide by 10 */ |
| max17048_rcomp_update(client, val->intval / 10); |
| 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; |
| char *str = NULL; |
| |
| switch (offset) { |
| case FG_DATA: |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n", |
| fg->reg_data[1], fg->reg_data[0]); |
| break; |
| case FG_REGS: |
| str = kzalloc(sizeof(char)*1024, GFP_KERNEL); |
| if (!str) |
| return -ENOMEM; |
| |
| fg_read_regs(fg->client, str); |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", |
| str); |
| |
| kfree(str); |
| 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) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct sec_fuelgauge_info *fg = |
| container_of(psy, struct sec_fuelgauge_info, psy_fg); |
| int ret = 0; |
| int x = 0; |
| u16 data; |
| |
| switch (offset) { |
| case FG_REG: |
| if (sscanf(buf, "%x\n", &x) == 1) { |
| fg->reg_addr = x; |
| data = max17048_read_word( |
| fg->client, fg->reg_addr); |
| fg->reg_data[0] = (data & 0xff00) >> 8; |
| fg->reg_data[1] = (data & 0x00ff); |
| |
| dev_dbg(&fg->client->dev, |
| "%s: (read) addr = 0x%x, data = 0x%02x%02x\n", |
| __func__, fg->reg_addr, |
| fg->reg_data[1], fg->reg_data[0]); |
| ret = count; |
| } |
| break; |
| case FG_DATA: |
| if (sscanf(buf, "%x\n", &x) == 1) { |
| dev_dbg(&fg->client->dev, |
| "%s: (write) addr = 0x%x, data = 0x%04x\n", |
| __func__, fg->reg_addr, x); |
| i2c_smbus_write_word_data(fg->client, |
| fg->reg_addr, swab16(x)); |
| ret = count; |
| } |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |