/* * Copyright (c) 2013-2017, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BQ27441_DELAY msecs_to_jiffies(30000) #define BQ27441_CONTROL_STATUS 0x0000 #define BQ27441_DEVICE_TYPE 0x0001 #define BQ27441_FW_VERSION 0x0002 #define BQ27441_DM_CODE 0x0004 #define BQ27441_PREV_MACWRITE 0x0007 #define BQ27441_CHEM_ID 0x0008 #define BQ27441_BAT_INSERT 0x000C #define BQ27441_BAT_REMOVE 0x000D #define BQ27441_SET_HIBERNATE 0x0011 #define BQ27441_CLEAR_HIBERNATE 0x0012 #define BQ27441_SET_CFGUPDATE 0x0013 #define BQ27441_SHUTDOWN_ENABLE 0x001B #define BQ27441_SHUTDOWN 0x001C #define BQ27441_SEALED 0x0020 #define BQ27441_PULSE_SOC_INT 0x0023 #define BQ27441_RESET 0x0041 #define BQ27441_SOFT_RESET 0x0042 #define BQ27441_CONTROL_1 0x00 #define BQ27441_CONTROL_2 0x01 #define BQ27441_TEMPERATURE 0x02 #define BQ27441_VOLTAGE 0x04 #define BQ27441_FLAGS 0x06 #define BQ27441_FLAGS_1 0x07 #define BQ27441_FLAGS_FC_DETECT BIT(1) #define BQ27441_FLAGS_ITPOR (1 << 5) #define BQ27441_NOMINAL_AVAIL_CAPACITY 0x08 #define BQ27441_FULL_AVAIL_CAPACITY 0x0a #define BQ27441_REMAINING_CAPACITY 0x0c #define BQ27441_FULL_CHG_CAPACITY 0x0e #define BQ27441_AVG_CURRENT 0x10 #define BQ27441_STANDBY_CURRENT 0x12 #define BQ27441_MAXLOAD_CURRENT 0x14 #define BQ27441_AVERAGE_POWER 0x18 #define BQ27441_STATE_OF_CHARGE 0x1c #define BQ27441_INT_TEMPERATURE 0x1e #define BQ27441_STATE_OF_HEALTH 0x20 #define BQ27441_BLOCK_DATA_CHECKSUM 0x60 #define BQ27441_BLOCK_DATA_CONTROL 0x61 #define BQ27441_DATA_BLOCK_CLASS 0x3E #define BQ27441_DATA_BLOCK 0x3F #define BQ27441_QMAX_CELL_1 0x40 #define BQ27441_QMAX_CELL_2 0x41 #define BQ27441_RESERVE_CAP_1 0x43 #define BQ27441_RESERVE_CAP_2 0x44 #define BQ27441_DESIGN_CAPACITY_1 0x4A #define BQ27441_DESIGN_CAPACITY_2 0x4B #define BQ27441_DESIGN_ENERGY_1 0x4C #define BQ27441_DESIGN_ENERGY_2 0x4D #define BQ27441_TAPER_RATE_1 0x5B #define BQ27441_TAPER_RATE_2 0x5C #define BQ27441_TERMINATE_VOLTAGE_1 0x50 #define BQ27441_TERMINATE_VOLTAGE_2 0x51 #define BQ27441_V_CHG_TERM_1 0x41 #define BQ27441_V_CHG_TERM_2 0x42 #define BQ27441_BATTERY_LOW 15 #define BQ27441_BATTERY_FULL 100 #define BQ27441_CC_GAIN 0x44 #define BQ27441_CC_DELTA 0x48 #define BQ27441_MAX_REGS 0x7F #define BQ27441_DESIGN_CAPACITY_DEFAULT 7800 #define BQ27441_DESIGN_ENERGY_DEFAULT 28080 #define BQ27441_TAPER_RATE_DEFAULT 100 #define BQ27441_TERMINATE_VOLTAGE_DEFAULT 3200 #define BQ27441_VAT_CHG_TERM_DEFAULT 4100 #define BQ27441_CC_GAIN_DEFAULT 0x7F7806C9 #define BQ27441_CC_DELTA_DEFAULT 0x940D197A #define BQ27441_QMAX_CELL_DEFAULT 16384 #define BQ27441_RESERVE_CAP_DEFAULT 0 #define BQ27441_OPCTEMPS_MASK 0x01 #define BQ27441_OPCONFIG_1 0x40 #define BQ27441_OPCONFIG_2 0x41 #define BQ27441_CHARGE_CURNT_THRESHOLD 2000 #define BQ27441_INPUT_POWER_THRESHOLD 7000 #define BQ27441_BATTERY_SOC_THRESHOLD 90 #define MAX_STRING_PRINT 50 struct bq27441_chip { struct i2c_client *client; struct delayed_work work; struct delayed_work fc_work; struct power_supply *battery; struct power_supply_desc psy_desc; struct bq27441_platform_data *pdata; struct battery_gauge_dev *bg_dev; struct regmap *regmap; /* battery voltage */ int vcell; /* battery capacity */ int soc; /* State Of Charge */ int status; /* battery health */ int health; /* battery capacity */ int capacity_level; int temperature; int read_failed; int full_capacity; int design_energy; int taper_rate; int terminate_voltage; int v_chg_term; u32 cc_gain; u32 cc_delta; u32 qmax_cell; u32 reserve_cap; int lasttime_soc; int lasttime_status; int shutdown_complete; int charge_complete; int print_once; bool enable_temp_prop; bool full_charge_state; bool fcc_fg_initialize; bool low_battery_shutdown; u32 full_charge_capacity; struct mutex mutex; }; struct bq27441_chip *bq27441_data; static const struct regmap_config bq27441_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = BQ27441_MAX_REGS, }; static int bq27441_read_u32(struct i2c_client *client, u8 reg) { int ret; u32 val; struct bq27441_chip *chip = i2c_get_clientdata(client); ret = regmap_raw_read(chip->regmap, reg, (u8 *) &val, sizeof(val)); if (ret < 0) { dev_err(&client->dev, "error reading reg: 0x%x\n", reg); return ret; } return val; } static int bq27441_read_word(struct i2c_client *client, u8 reg) { int ret; u16 val; struct bq27441_chip *chip = i2c_get_clientdata(client); ret = regmap_raw_read(chip->regmap, reg, (u8 *) &val, sizeof(val)); if (ret < 0) { dev_err(&client->dev, "error reading reg: 0x%x\n", reg); return ret; } return val; } static int bq27441_read_byte(struct i2c_client *client, u8 reg) { int ret; u8 val; struct bq27441_chip *chip = i2c_get_clientdata(client); ret = regmap_raw_read(chip->regmap, reg, (u8 *) &val, sizeof(val)); if (ret < 0) { dev_err(&client->dev, "error reading reg: 0x%x\n", reg); return ret; } return val; } static int bq27441_write_byte(struct i2c_client *client, u8 reg, u8 value) { struct bq27441_chip *chip = i2c_get_clientdata(client); int ret; ret = regmap_write(chip->regmap, reg, value); if (ret < 0) dev_err(&client->dev, "%s(): Failed in writing register 0x%02x err %d\n", __func__, reg, ret); return ret; } static int bq27441_get_battery_soc(struct battery_gauge_dev *bg_dev) { struct bq27441_chip *chip = battery_gauge_get_drvdata(bg_dev); int val; val = bq27441_read_word(chip->client, BQ27441_STATE_OF_CHARGE); if (val < 0) dev_err(&chip->client->dev, "%s: err %d\n", __func__, val); else val = battery_gauge_get_adjusted_soc(chip->bg_dev, chip->pdata->threshold_soc, chip->pdata->maximum_soc, val * 100); return val; } static int bq27441_update_soc_voltage(struct bq27441_chip *chip) { int val; val = bq27441_read_word(chip->client, BQ27441_VOLTAGE); if (val < 0) dev_err(&chip->client->dev, "%s: err %d\n", __func__, val); else chip->vcell = val; val = bq27441_read_word(chip->client, BQ27441_STATE_OF_CHARGE); if (val < 0) dev_err(&chip->client->dev, "%s: err %d\n", __func__, val); else chip->soc = battery_gauge_get_adjusted_soc(chip->bg_dev, chip->pdata->threshold_soc, chip->pdata->maximum_soc, val * 100); if (chip->low_battery_shutdown && chip->soc == 0) chip->soc = 1; if (chip->soc == BQ27441_BATTERY_FULL && chip->charge_complete) { chip->status = POWER_SUPPLY_STATUS_FULL; chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; chip->health = POWER_SUPPLY_HEALTH_GOOD; } else if (chip->soc < BQ27441_BATTERY_LOW) { chip->status = chip->lasttime_status; chip->health = POWER_SUPPLY_HEALTH_DEAD; chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; } else { chip->charge_complete = 0; chip->status = chip->lasttime_status; chip->health = POWER_SUPPLY_HEALTH_GOOD; chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; } return 0; } static void bq27441_work(struct work_struct *work) { struct bq27441_chip *chip; chip = container_of(work, struct bq27441_chip, work.work); mutex_lock(&chip->mutex); if (chip->shutdown_complete) { mutex_unlock(&chip->mutex); return; } bq27441_update_soc_voltage(chip); if (chip->soc != chip->lasttime_soc || chip->status != chip->lasttime_status) { chip->lasttime_soc = chip->soc; power_supply_changed(chip->battery); } mutex_unlock(&chip->mutex); battery_gauge_report_battery_soc(chip->bg_dev, chip->soc); schedule_delayed_work(&chip->work, BQ27441_DELAY); } static int bq27441_battemps_enable(struct bq27441_chip *chip) { struct i2c_client *client = chip->client; u8 temp; int ret; int old_opconfig; int old_csum, new_csum; unsigned long timeout = jiffies + HZ; /* Unseal the fuel gauge for data access */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x80); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x80); if (ret < 0) goto fail; /* setup fuel gauge state data block block for ram access */ ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CONTROL, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK_CLASS, 0x40); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK, 0x00); if (ret < 0) goto fail; mdelay(1); old_opconfig = bq27441_read_word(client, BQ27441_OPCONFIG_1); /* if the TEMPS bit is set seal the fuel gauge and return */ if (BQ27441_OPCTEMPS_MASK & be16_to_cpu(old_opconfig)) { dev_info(&chip->client->dev, "FG TEMPS already enabled\n"); goto seal; } /* read check sum */ old_csum = bq27441_read_byte(client, BQ27441_BLOCK_DATA_CHECKSUM); /* place the fuel gauge into config update */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x13); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x00); if (ret < 0) goto fail; while (!(bq27441_read_byte(client, BQ27441_FLAGS) & 0x10)) { if (time_after(jiffies, timeout)) { dev_warn(&chip->client->dev, "timeout waiting for cfg update\n"); goto fail; } msleep(20); } /* update TEMPS config to fuel gauge */ ret = bq27441_write_byte(client, BQ27441_OPCONFIG_2, ((old_opconfig >> 8) | BQ27441_OPCTEMPS_MASK)); if (ret < 0) goto fail; temp = (255 - old_csum - (old_opconfig & 0xFF) - ((old_opconfig >> 8) & 0xFF)) % 256; new_csum = 255 - ((temp + (old_opconfig & 0xFF) + ((old_opconfig >> 8) | BQ27441_OPCTEMPS_MASK)) % 256); ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CHECKSUM, new_csum); if (ret < 0) goto fail; seal: /* seal the fuel gauge before exit */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x20); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x00); if (ret < 0) goto fail; return 0; fail: dev_info(&chip->client->dev, "FG OPCONFIG TEMPS enable failed\n"); return -EIO; } static int bq27441_initialize_cc_cal_block(struct bq27441_chip *chip) { struct i2c_client *client = chip->client; int old_csum, new_csum; u8 temp; u32 old_cc_gain, old_cc_delta; u8 addr, val; int i = 0; int ret; /* setup cc cal data block block for ram access */ ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CONTROL, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK_CLASS, 105); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK, 0x00); if (ret < 0) goto fail; mdelay(1); /* read check sum */ old_csum = bq27441_read_byte(client, BQ27441_BLOCK_DATA_CHECKSUM); /* read all the old values that we want to update */ old_cc_gain = bq27441_read_u32(client, BQ27441_CC_GAIN); old_cc_delta = bq27441_read_u32(client, BQ27441_CC_DELTA); dev_info(&chip->client->dev, "cc cal values:\n gain old: %08x new: %08x\n" "delta old: %08x new: %08x\n", be32_to_cpu(old_cc_gain), chip->cc_gain, be32_to_cpu(old_cc_delta), chip->cc_delta); if (be32_to_cpu(old_cc_delta) != chip->cc_delta || be32_to_cpu(old_cc_gain) != chip->cc_gain) { temp = (255 - old_csum - (old_cc_gain & 0xFF) - ((old_cc_gain >> 8) & 0xFF) - ((old_cc_gain >> 16) & 0xFF) - ((old_cc_gain >> 24) & 0xFF) - (old_cc_delta & 0xFF) - ((old_cc_delta >> 8) & 0xFF) - ((old_cc_delta >> 16) & 0xFF) - ((old_cc_delta >> 24) & 0xFF)) % 256; new_csum = 255 - (((temp + (chip->cc_gain & 0xFF) + ((chip->cc_gain >> 8) & 0xFF) + ((chip->cc_gain >> 16) & 0xFF) + ((chip->cc_gain >> 24) & 0xFF) + (chip->cc_delta & 0xFF) + ((chip->cc_delta >> 8) & 0xFF) + ((chip->cc_delta >> 16) & 0xFF) + ((chip->cc_delta >> 24) & 0xFF) )) % 256); /* program cc_gain */ for (i = 0; i < sizeof(u32); i++) { val = (chip->cc_gain >> (sizeof(u32) - i - 1)*8) & 0xFF; addr = BQ27441_CC_GAIN + i; ret = bq27441_write_byte(client, addr, val); if (ret < 0) goto fail; } /* program cc_delta */ for (i = 0; i < sizeof(u32); i++) { val = ((chip->cc_delta >> (sizeof(u32) - i - 1)*8) & 0xFF); addr = BQ27441_CC_DELTA + i; ret = bq27441_write_byte(client, addr, val); if (ret < 0) goto fail; } ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CHECKSUM, new_csum); if (ret < 0) goto fail; } return 0; fail: return -EIO; } static int bq27441_initialize(struct bq27441_chip *chip) { struct i2c_client *client = chip->client; int old_csum; u8 temp; int new_csum; int old_des_cap; int old_des_energy; int old_taper_rate; int old_terminate_voltage; int old_v_chg_term; u32 old_qmax_cell; u32 old_reserve_cap; int flags_lsb; unsigned long timeout = jiffies + HZ; int ret; flags_lsb = bq27441_read_byte(client, BQ27441_FLAGS); /* Unseal the fuel gauge for data access */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x80); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x80); if (ret < 0) goto fail; /* setup fuel gauge state data block block for ram access */ ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CONTROL, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK_CLASS, 0x52); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK, 0x00); if (ret < 0) goto fail; /* read check sum */ mdelay(1); old_csum = bq27441_read_byte(client, BQ27441_BLOCK_DATA_CHECKSUM); /* read all the old values that we want to update */ old_qmax_cell = bq27441_read_word(client, BQ27441_QMAX_CELL_1); old_reserve_cap = bq27441_read_word(client, BQ27441_RESERVE_CAP_1); old_des_cap = bq27441_read_word(client, BQ27441_DESIGN_CAPACITY_1); old_des_energy = bq27441_read_word(client, BQ27441_DESIGN_ENERGY_1); old_taper_rate = bq27441_read_word(client, BQ27441_TAPER_RATE_1); old_terminate_voltage = bq27441_read_word(client, BQ27441_TERMINATE_VOLTAGE_1); dev_info(&chip->client->dev, "FG values:\n capacity old: %d new: %d\n" "design_energy old:%d new:%d\n" "taper_rate old:%d new:%d\n" "qmax_cell old:%d new:%d\n" "reserve_cap old:%d new:%d\n" "terminate_voltage old:%d new:%d\n" "itpor flag:%d checksum:%d\n", be16_to_cpu(old_des_cap), chip->full_capacity, be16_to_cpu(old_des_energy), chip->design_energy, be16_to_cpu(old_taper_rate), chip->taper_rate, be16_to_cpu(old_qmax_cell), chip->qmax_cell, be16_to_cpu(old_reserve_cap), chip->reserve_cap, be16_to_cpu(old_terminate_voltage), chip->terminate_voltage, (flags_lsb & BQ27441_FLAGS_ITPOR), old_csum); /* if the values match with the required ones */ /* or if POR bit is not set seal the fuel gauge and return */ if ((chip->full_capacity == be16_to_cpu(old_des_cap)) && (chip->design_energy == be16_to_cpu(old_des_energy)) && (chip->taper_rate == be16_to_cpu(old_taper_rate)) && (chip->terminate_voltage == be16_to_cpu(old_terminate_voltage)) && (!(flags_lsb & BQ27441_FLAGS_ITPOR)) && !chip->fcc_fg_initialize) { dev_info(&chip->client->dev, "FG is already programmed\n"); goto seal; } /* place the fuel gauge into config update */ dev_info(&chip->client->dev, "Programming Bq27441!\n"); ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x13); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x00); if (ret < 0) goto fail; while (!(bq27441_read_byte(client, BQ27441_FLAGS) & 0x10)) { if (time_after(jiffies, timeout)) { dev_warn(&chip->client->dev, "timeout waiting for cfg update\n"); return -ETIMEDOUT; } msleep(20); } /* update new config to fuel gauge */ ret = bq27441_write_byte(client, BQ27441_QMAX_CELL_1, (chip->qmax_cell & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_QMAX_CELL_2, chip->qmax_cell & 0xFF); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_RESERVE_CAP_1, (chip->reserve_cap & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_RESERVE_CAP_2, chip->reserve_cap & 0xFF); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DESIGN_CAPACITY_1, (chip->full_capacity & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DESIGN_CAPACITY_2, chip->full_capacity & 0xFF); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DESIGN_ENERGY_1, (chip->design_energy & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DESIGN_ENERGY_2, chip->design_energy & 0xFF); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_TAPER_RATE_1, (chip->taper_rate & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_TAPER_RATE_2, chip->taper_rate & 0xFF); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_TERMINATE_VOLTAGE_1, (chip->terminate_voltage & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_TERMINATE_VOLTAGE_2, chip->terminate_voltage & 0xFF); if (ret < 0) goto fail; /* calculate the new checksum */ temp = (255 - old_csum - (old_des_cap & 0xFF) - ((old_des_cap >> 8) & 0xFF) - (old_des_energy & 0xFF) - ((old_des_energy >> 8) & 0xFF) - (old_taper_rate & 0xFF) - ((old_taper_rate >> 8) & 0xFF) - (old_qmax_cell & 0xFF) - ((old_qmax_cell >> 8) & 0xFF) - (old_reserve_cap & 0xFF) - ((old_reserve_cap >> 8) & 0xFF) - (old_terminate_voltage & 0xFF) - ((old_terminate_voltage >> 8) & 0xFF)) % 256; new_csum = 255 - (((temp + (chip->full_capacity & 0xFF) + ((chip->full_capacity & 0xFF00) >> 8) + (chip->design_energy & 0xFF) + ((chip->design_energy & 0xFF00) >> 8) + (chip->taper_rate & 0xFF) + ((chip->taper_rate & 0xFF00) >> 8) + (chip->qmax_cell & 0xFF) + ((chip->qmax_cell & 0xFF00) >> 8) + (chip->reserve_cap & 0xFF) + ((chip->reserve_cap & 0xFF00) >> 8) + (chip->terminate_voltage & 0xFF) + ((chip->terminate_voltage & 0xFF00) >> 8) )) % 256); ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CHECKSUM, new_csum); if (ret < 0) goto fail; /* setup fuel gauge state data block page 1 for ram access */ ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CONTROL, 0x00); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK_CLASS, 0x52); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_DATA_BLOCK, 0x01); if (ret < 0) goto fail; mdelay(1); old_csum = bq27441_read_byte(client, BQ27441_BLOCK_DATA_CHECKSUM); old_v_chg_term = bq27441_read_word(client, BQ27441_V_CHG_TERM_1); ret = bq27441_write_byte(client, BQ27441_V_CHG_TERM_1, (chip->v_chg_term & 0xFF00) >> 8); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_V_CHG_TERM_2, chip->v_chg_term & 0xFF); if (ret < 0) goto fail; temp = (255 - old_csum - (old_v_chg_term & 0xFF) - ((old_v_chg_term >> 8) & 0xFF)) % 256; new_csum = 255 - ((temp + (chip->v_chg_term & 0xFF) + ((chip->v_chg_term & 0xFF00) >> 8) ) % 256); ret = bq27441_write_byte(client, BQ27441_BLOCK_DATA_CHECKSUM, new_csum); if (ret < 0) goto fail; /* program the cc_cal block */ if (bq27441_initialize_cc_cal_block(chip) < 0) goto fail; /* exit config update mode */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x42); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x00); if (ret < 0) goto fail; while (!(bq27441_read_byte(client, BQ27441_FLAGS) & 0x10)) { if (time_after(jiffies, timeout)) { dev_warn(&chip->client->dev, "timeout waiting for cfg update\n"); return -ETIMEDOUT; } msleep(20); } seal: /* seal the fuel gauge before exit */ ret = bq27441_write_byte(client, BQ27441_CONTROL_1, 0x20); if (ret < 0) goto fail; ret = bq27441_write_byte(client, BQ27441_CONTROL_2, 0x00); if (ret < 0) goto fail; return 0; fail: return -EIO; } static int bq27441_get_temperature(void) { int val; int retries = 2; int i; if (bq27441_data->shutdown_complete) return bq27441_data->temperature; for (i = 0; i < retries; i++) { val = bq27441_read_word(bq27441_data->client, BQ27441_TEMPERATURE); if (val < 0) continue; } if (val < 0) { bq27441_data->read_failed++; dev_err(&bq27441_data->client->dev, "%s: err %d\n", __func__, val); if (bq27441_data->read_failed > 50) return val; return bq27441_data->temperature; } bq27441_data->read_failed = 0; bq27441_data->temperature = val / 100; return val / 100; } static enum power_supply_property bq27441_battery_props[] = { POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_CHARGER_STANDARD, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CURRENT_NOW, }; static int bq27441_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct bq27441_chip *chip = power_supply_get_drvdata(psy); int temperature; int power_mw = 0, input_curnt_ma = 0; int ret = 0; mutex_lock(&chip->mutex); if (chip->shutdown_complete) { mutex_unlock(&chip->mutex); return -EINVAL; } switch (psp) { case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_STATUS: val->intval = chip->status; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = 1000 * chip->vcell; break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = chip->soc; if (chip->print_once && (chip->soc > 15 || chip->soc < 5)) chip->print_once = 0; if (chip->soc == 15 && chip->print_once != 15) { chip->print_once = 15; dev_warn(&chip->client->dev, "System Running low on battery - 15 percent\n"); } if (chip->soc == 10 && chip->print_once != 10) { chip->print_once = 10; dev_warn(&chip->client->dev, "System Running low on battery - 10 percent\n"); } if (chip->soc == 5 && chip->print_once != 5) { chip->print_once = 5; dev_warn(&chip->client->dev, "System Running low on battery - 5 percent\n"); } break; case POWER_SUPPLY_PROP_HEALTH: val->intval = chip->health; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: val->intval = chip->capacity_level; break; case POWER_SUPPLY_PROP_CHARGER_STANDARD: val->strval = "unknown"; input_curnt_ma = battery_gauge_get_input_current_limit( chip->bg_dev); battery_gauge_get_input_power(chip->bg_dev, &power_mw); power_mw = abs(power_mw); if (input_curnt_ma >= BQ27441_CHARGE_CURNT_THRESHOLD && power_mw <= BQ27441_INPUT_POWER_THRESHOLD && chip->soc < BQ27441_BATTERY_SOC_THRESHOLD) val->strval = "sub-standard"; else if (input_curnt_ma == 0 && power_mw == 0) val->strval = "no-charger"; else val->strval = "standard"; break; case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TEMP_INFO: if (chip->enable_temp_prop) { temperature = bq27441_get_temperature(); val->intval = temperature; } else if (chip->pdata->tz_name) { ret = battery_gauge_get_battery_temperature( chip->bg_dev, &temperature); if (ret < 0) { dev_err(&chip->client->dev, "temp invalid %d\n", ret); chip->read_failed++; if (chip->read_failed > 50) break; temperature = chip->temperature; ret = 0; } else { chip->read_failed = 0; chip->temperature = temperature; } val->intval = temperature * 10; } else { ret = -EINVAL; } break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: val->intval = 0; input_curnt_ma = battery_gauge_get_input_current_limit( chip->bg_dev); if (input_curnt_ma > 0) val->intval = input_curnt_ma * 1000; else ret = input_curnt_ma; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = 0; ret = battery_gauge_get_battery_current(chip->bg_dev, &input_curnt_ma); if (!ret) val->intval = 1000 * input_curnt_ma; break; default: ret = -EINVAL; break; } mutex_unlock(&chip->mutex); return ret; } static int bq27441_update_battery_status(struct battery_gauge_dev *bg_dev, enum battery_charger_status status) { struct bq27441_chip *chip = battery_gauge_get_drvdata(bg_dev); int val; mutex_lock(&chip->mutex); if (chip->shutdown_complete) { mutex_unlock(&chip->mutex); return 0; } val = bq27441_read_word(chip->client, BQ27441_STATE_OF_CHARGE); if (val < 0) dev_err(&chip->client->dev, "%s: err %d\n", __func__, val); else chip->soc = battery_gauge_get_adjusted_soc(chip->bg_dev, chip->pdata->threshold_soc, chip->pdata->maximum_soc, val * 100); if (chip->low_battery_shutdown && chip->soc == 0) chip->soc = 1; if (status == BATTERY_CHARGING) { chip->charge_complete = 0; chip->status = POWER_SUPPLY_STATUS_CHARGING; } else if (status == BATTERY_CHARGING_DONE) { if (chip->soc == BQ27441_BATTERY_FULL) { chip->charge_complete = 1; chip->status = POWER_SUPPLY_STATUS_FULL; chip->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; } goto done; } else { chip->status = POWER_SUPPLY_STATUS_DISCHARGING; chip->charge_complete = 0; } chip->lasttime_status = chip->status; done: mutex_unlock(&chip->mutex); power_supply_changed(chip->battery); dev_dbg(&chip->client->dev, "%s() Battery status: %d and SoC: %d%% UI status: %d\n", __func__, status, chip->soc, chip->status); return 0; } static struct battery_gauge_ops bq27441_bg_ops = { .update_battery_status = bq27441_update_battery_status, .get_battery_temp = bq27441_get_temperature, .get_battery_soc = bq27441_get_battery_soc, }; static struct battery_gauge_info bq27441_bgi = { .cell_id = 0, .bg_ops = &bq27441_bg_ops, .current_channel_name = "battery-current", }; static void bq27441_fc_work(struct work_struct *work) { struct bq27441_chip *chip; int val = 0; chip = container_of(to_delayed_work(work), struct bq27441_chip, fc_work); if (chip->full_charge_capacity) { val = bq27441_read_word(chip->client, BQ27441_FULL_CHG_CAPACITY); if (val < 0) { dev_err(&chip->client->dev, "FCC read failed %d\n", val); } else if (val < chip->full_charge_capacity) { dev_info(&chip->client->dev, "Full charge capacity %d\n", val); chip->fcc_fg_initialize = true; val = bq27441_initialize(chip); if (val < 0) dev_err(&chip->client->dev, "chip init failed:%d\n", val); schedule_delayed_work(&chip->fc_work, BQ27441_DELAY); return; } else dev_info(&chip->client->dev, "Full Charge Capacity %d\n", val); chip->fcc_fg_initialize = false; } dev_info(&chip->client->dev, "Full charge status: %d\n", chip->full_charge_state); battery_gauge_fc_state(chip->bg_dev, chip->full_charge_state); } static ssize_t bq27441_show_lowbat_shutdown_state(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct bq27441_chip *bq27441 = i2c_get_clientdata(client); mutex_lock(&bq27441->mutex); if (bq27441->shutdown_complete) { mutex_unlock(&bq27441->mutex); return -EIO; } mutex_unlock(&bq27441->mutex); if (bq27441->low_battery_shutdown) return snprintf(buf, MAX_STRING_PRINT, "disabled\n"); else return snprintf(buf, MAX_STRING_PRINT, "enabled\n"); } static ssize_t bq27441_set_lowbat_shutdown_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct bq27441_chip *bq27441 = i2c_get_clientdata(client); bool enabled; if ((*buf == 'E') || (*buf == 'e')) enabled = true; else if ((*buf == 'D') || (*buf == 'd')) enabled = false; else return -EINVAL; mutex_lock(&bq27441->mutex); if (bq27441->shutdown_complete) { mutex_unlock(&bq27441->mutex); return -EIO; } if (enabled) bq27441->low_battery_shutdown = false; else bq27441->low_battery_shutdown = true; mutex_unlock(&bq27441->mutex); return count; } static DEVICE_ATTR(low_battery_shutdown, (S_IRUGO | (S_IWUSR | S_IWGRP)), bq27441_show_lowbat_shutdown_state, bq27441_set_lowbat_shutdown_state); static struct attribute *bq27441_attributes[] = { &dev_attr_low_battery_shutdown.attr, NULL }; static const struct attribute_group bq27441_attr_group = { .attrs = bq27441_attributes, }; static irqreturn_t bq27441_irq(int id, void *dev) { struct bq27441_chip *chip = dev; struct i2c_client *client = chip->client; bq27441_update_soc_voltage(chip); power_supply_changed(chip->battery); dev_info(&client->dev, "%s() Battery Voltage %dmV and SoC %d%%\n", __func__, chip->vcell, chip->soc); if (chip->soc == BQ27441_BATTERY_FULL && !chip->full_charge_state) { chip->full_charge_state = 1; schedule_delayed_work(&chip->fc_work, 0); } else if (chip->soc < BQ27441_BATTERY_FULL && chip->full_charge_state) { chip->full_charge_state = 0; schedule_delayed_work(&chip->fc_work, 0); } return IRQ_HANDLED; } static void of_bq27441_parse_platform_data(struct i2c_client *client, struct bq27441_platform_data *pdata) { u32 tmp; char const *pstr; bool dt_param_not_found = 0; struct device_node *np = client->dev.of_node; if (!of_property_read_u32(np, "ti,design-capacity", &tmp)) pdata->full_capacity = (unsigned long)tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read design-capacity\n"); } if (!of_property_read_u32(np, "ti,design-energy", &tmp)) pdata->full_energy = (unsigned long)tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read design-energy\n"); } if (!of_property_read_u32(np, "ti,taper-rate", &tmp)) pdata->taper_rate = (unsigned long)tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read taper-rate\n"); } if (!of_property_read_u32(np, "ti,terminate-voltage", &tmp)) pdata->terminate_voltage = (unsigned long)tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read terminate-voltage\n"); } if (!of_property_read_u32(np, "ti,v-at-chg-term", &tmp)) pdata->v_at_chg_term = (unsigned long)tmp; if (!of_property_read_u32(np, "ti,cc-gain", &tmp)) pdata->cc_gain = tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read cc-gain\n"); } if (!of_property_read_u32(np, "ti,cc-delta", &tmp)) pdata->cc_delta = tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read cc-delta\n"); } if (!of_property_read_u32(np, "ti,qmax-cell", &tmp)) pdata->qmax_cell = tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read qmax-cell\n"); } if (!of_property_read_u32(np, "ti,reserve-cap-mah", &tmp)) pdata->reserve_cap = tmp; else { dt_param_not_found = 1; dev_warn(&client->dev, "fail to read reserve-cap-mah\n"); } if (!of_property_read_string(np, "ti,tz-name", &pstr)) pdata->tz_name = pstr; else dev_warn(&client->dev, "Failed to read tz-name\n"); if (!of_property_read_u32(np, "ti,kernel-threshold-soc", &tmp)) pdata->threshold_soc = tmp; if (!of_property_read_u32(np, "ti,kernel-maximum-soc", &tmp)) pdata->maximum_soc = tmp; else pdata->maximum_soc = 100; if (!of_property_read_u32(np, "ti,full-charge-capacity", &tmp)) pdata->full_charge_capacity = tmp; pdata->enable_temp_prop = of_property_read_bool(np, "ti,enable-temp-prop"); pdata->support_battery_current = of_property_read_bool(np, "io-channel-names"); if (dt_param_not_found) dev_warn(&client->dev, "All the FG properties not provided in DT\n"); } static int bq27441_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct bq27441_chip *chip; struct power_supply_config bq27441_psy_cfg = {}; int ret; chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->client = client; if (client->dev.of_node) { chip->pdata = devm_kzalloc(&client->dev, sizeof(*chip->pdata), GFP_KERNEL); if (!chip->pdata) return -ENOMEM; of_bq27441_parse_platform_data(client, chip->pdata); } else { chip->pdata = client->dev.platform_data; } if (!chip->pdata) return -ENODATA; chip->full_capacity = 1200; chip->print_once = 0; chip->full_charge_state = 0; chip->fcc_fg_initialize = 0; chip->low_battery_shutdown = 0; chip->full_capacity = chip->pdata->full_capacity ?: BQ27441_DESIGN_CAPACITY_DEFAULT; chip->design_energy = chip->pdata->full_energy ?: BQ27441_DESIGN_ENERGY_DEFAULT; chip->taper_rate = chip->pdata->taper_rate ?: BQ27441_TAPER_RATE_DEFAULT; chip->terminate_voltage = chip->pdata->terminate_voltage ?: BQ27441_TERMINATE_VOLTAGE_DEFAULT; chip->v_chg_term = chip->pdata->v_at_chg_term ?: BQ27441_VAT_CHG_TERM_DEFAULT; chip->cc_gain = chip->pdata->cc_gain ?: BQ27441_CC_GAIN_DEFAULT; chip->cc_delta = chip->pdata->cc_delta ?: BQ27441_CC_DELTA_DEFAULT; chip->qmax_cell = chip->pdata->qmax_cell ?: BQ27441_QMAX_CELL_DEFAULT; chip->reserve_cap = chip->pdata->reserve_cap ?: BQ27441_RESERVE_CAP_DEFAULT; chip->enable_temp_prop = chip->pdata->enable_temp_prop; chip->full_charge_capacity = chip->pdata->full_charge_capacity; dev_info(&client->dev, "Battery capacity is %d\n", chip->full_capacity); bq27441_data = chip; mutex_init(&chip->mutex); chip->shutdown_complete = 0; i2c_set_clientdata(client, chip); chip->psy_desc.name = "battery"; chip->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY, chip->psy_desc.get_property = bq27441_get_property, chip->psy_desc.properties = bq27441_battery_props, chip->psy_desc.num_properties = ARRAY_SIZE(bq27441_battery_props), chip->status = POWER_SUPPLY_STATUS_DISCHARGING; chip->lasttime_status = POWER_SUPPLY_STATUS_DISCHARGING; chip->charge_complete = 0; if (chip->pdata->tz_name) bq27441_battery_props[0] = POWER_SUPPLY_PROP_TEMP_INFO; /* Remove current property if it is not supported */ if (!chip->pdata->support_battery_current) chip->psy_desc.num_properties--; if (!chip->enable_temp_prop && chip->pdata->tz_name) chip->temperature = 23; chip->regmap = devm_regmap_init_i2c(client, &bq27441_regmap_config); if (IS_ERR(chip->regmap)) { ret = PTR_ERR(chip->regmap); dev_err(&client->dev, "regmap init failed with err %d\n", ret); goto error; } /* Dummy read to check if the slave is present */ ret = bq27441_read_word(chip->client, BQ27441_VOLTAGE); if (ret < 0) { dev_err(&chip->client->dev, "Exiting driver as xfer failed\n"); goto error; } bq27441_bgi.tz_name = chip->pdata->tz_name; chip->bg_dev = battery_gauge_register(&client->dev, &bq27441_bgi, chip); if (IS_ERR(chip->bg_dev)) { ret = PTR_ERR(chip->bg_dev); dev_err(&client->dev, "battery gauge register failed: %d\n", ret); goto bg_err; } bq27441_psy_cfg.drv_data = chip; chip->battery = power_supply_register(&client->dev, &chip->psy_desc, &bq27441_psy_cfg); if (IS_ERR(chip->battery)) { ret = PTR_ERR(chip->battery); dev_err(&client->dev, "failed: power supply register\n"); goto error; } ret = bq27441_initialize(chip); if (ret < 0) dev_err(&client->dev, "chip init failed - %d\n", ret); if (chip->enable_temp_prop) bq27441_battemps_enable(chip); bq27441_update_soc_voltage(chip); INIT_DEFERRABLE_WORK(&chip->work, bq27441_work); schedule_delayed_work(&chip->work, 0); INIT_DELAYED_WORK(&chip->fc_work, bq27441_fc_work); if (client->irq) { ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, bq27441_irq, IRQF_ONESHOT | IRQF_TRIGGER_FALLING, dev_name(&client->dev), chip); if (ret < 0) { dev_err(&client->dev, "%s: request IRQ %d fail, err = %d\n", __func__, client->irq, ret); client->irq = 0; goto irq_reg_error; } } device_set_wakeup_capable(&client->dev, 1); device_wakeup_enable(&client->dev); dev_info(&client->dev, "Battery Voltage %dmV and SoC %d%%\n", chip->vcell, chip->soc); if (chip->soc == BQ27441_BATTERY_FULL && !chip->full_charge_state) { chip->full_charge_state = 1; schedule_delayed_work(&chip->fc_work, 0); } ret = sysfs_create_group(&client->dev.kobj, &bq27441_attr_group); if (ret < 0) { dev_err(&client->dev, "sysfs create failed %d\n", ret); return ret; } return 0; irq_reg_error: cancel_delayed_work_sync(&chip->work); cancel_delayed_work_sync(&chip->fc_work); bg_err: power_supply_unregister(chip->battery); error: mutex_destroy(&chip->mutex); return ret; } static int bq27441_remove(struct i2c_client *client) { struct bq27441_chip *chip = i2c_get_clientdata(client); battery_gauge_unregister(chip->bg_dev); power_supply_unregister(chip->battery); cancel_delayed_work_sync(&chip->work); mutex_destroy(&chip->mutex); return 0; } static void bq27441_shutdown(struct i2c_client *client) { struct bq27441_chip *chip = i2c_get_clientdata(client); if (chip->client->irq) disable_irq(chip->client->irq); mutex_lock(&chip->mutex); chip->shutdown_complete = 1; mutex_unlock(&chip->mutex); cancel_delayed_work_sync(&chip->work); dev_info(&chip->client->dev, "At shutdown Voltage %dmV and SoC %d%%\n", chip->vcell, chip->soc); } #ifdef CONFIG_PM_SLEEP static int bq27441_suspend(struct device *dev) { struct bq27441_chip *chip = dev_get_drvdata(dev); cancel_delayed_work_sync(&chip->work); if (device_may_wakeup(&chip->client->dev) && chip->client->irq) enable_irq_wake(chip->client->irq); dev_info(&chip->client->dev, "At suspend Voltage %dmV and SoC %d%%\n", chip->vcell, chip->soc); return 0; } static int bq27441_resume(struct device *dev) { struct bq27441_chip *chip = dev_get_drvdata(dev); if (device_may_wakeup(&chip->client->dev) && chip->client->irq) disable_irq_wake(chip->client->irq); mutex_lock(&chip->mutex); bq27441_update_soc_voltage(chip); power_supply_changed(chip->battery); mutex_unlock(&chip->mutex); dev_info(&chip->client->dev, "At resume Voltage %dmV and SoC %d%%\n", chip->vcell, chip->soc); schedule_delayed_work(&chip->work, BQ27441_DELAY); return 0; } #endif /* CONFIG_PM */ static SIMPLE_DEV_PM_OPS(bq27441_pm_ops, bq27441_suspend, bq27441_resume); #ifdef CONFIG_OF static const struct of_device_id bq27441_dt_match[] = { { .compatible = "ti,bq27441" }, { }, }; MODULE_DEVICE_TABLE(of, bq27441_dt_match); #endif static const struct i2c_device_id bq27441_id[] = { { "bq27441", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, bq27441_id); static struct i2c_driver bq27441_i2c_driver = { .driver = { .name = "bq27441", .of_match_table = of_match_ptr(bq27441_dt_match), .pm = &bq27441_pm_ops, }, .probe = bq27441_probe, .remove = bq27441_remove, .id_table = bq27441_id, .shutdown = bq27441_shutdown, }; static int __init bq27441_init(void) { return i2c_add_driver(&bq27441_i2c_driver); } fs_initcall_sync(bq27441_init); static void __exit bq27441_exit(void) { i2c_del_driver(&bq27441_i2c_driver); } module_exit(bq27441_exit); MODULE_AUTHOR("Chaitanya Bandi "); MODULE_DESCRIPTION("BQ27441 Fuel Gauge"); MODULE_LICENSE("GPL");