diff --git a/drivers/power/bq27421_battery.c b/drivers/power/bq27421_battery.c index 96b7dcbda54576f35852bbc4d4b2f8c25f1ab42d..339108d0b72dce290f4a697ffcb88f948244d1d5 100644 --- a/drivers/power/bq27421_battery.c +++ b/drivers/power/bq27421_battery.c @@ -27,6 +27,9 @@ #include <linux/mod_devicetable.h> #include <linux/power/bq27421_battery.h> +#define RETRY_CNT_EXIT_CFGUPDATE 100 +#define RETRY_CNT_ENTER_CFGUPDATE 10 + #define SOC_LOW_THRESHOLD 3 struct bq27421_chip { @@ -44,6 +47,19 @@ struct bq27421_chip { int temperature; }; +static int bq27421_read_byte(struct i2c_client *client, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + pr_debug("%s: [%x]=%x\n", __func__, reg, ret); + + return ret; +} + static int bq27421_read_word(struct i2c_client *client, u8 reg) { int ret; @@ -57,6 +73,19 @@ static int bq27421_read_word(struct i2c_client *client, u8 reg) return ret; } +static int bq27421_write_byte(struct i2c_client *client, u8 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); + + pr_debug("%s: [%x]=%x\n", __func__, reg, value); + + return ret; +} + static int bq27421_write_word(struct i2c_client *client, u8 reg, u16 value) { int ret; @@ -70,6 +99,18 @@ static int bq27421_write_word(struct i2c_client *client, u8 reg, u16 value) return ret; } +static int bq27421_write_block(struct i2c_client *client, u8 reg, + u8 *buf, u8 len) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, len, buf); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + static int bq27421_get_ctrl_status(struct bq27421_chip *chip) { struct i2c_client *client = chip->client; @@ -94,6 +135,16 @@ static bool bq27421_is_unsealed(struct bq27421_chip *chip) return !(status & 0x2000); } +static bool bq27421_ctrl_unsealed(struct bq27421_chip *chip) +{ + bq27421_write_word(chip->client, BQ27421_CONTROL, UNSEAL_KEY0); + bq27421_write_word(chip->client, BQ27421_CONTROL, UNSEAL_KEY1); + + msleep(100); + + return bq27421_is_unsealed(chip); +} + static bool bq27421_ctrl_sealed(struct bq27421_chip *chip) { bq27421_write_word(chip->client, BQ27421_CONTROL, @@ -104,6 +155,127 @@ static bool bq27421_ctrl_sealed(struct bq27421_chip *chip) return !bq27421_is_unsealed(chip); } +static bool bq27421_is_cfgupdate(struct bq27421_chip *chip) +{ + int ret; + + ret = bq27421_read_word(chip->client, BQ27421_FLAGS); + if (ret < 0) { + pr_err("%s: failed to get flags\n", __func__); + return false; + } + + return !!(ret & 0x0010); +} + +static bool bq27421_enter_cfgupdate(struct bq27421_chip *chip) +{ + int retry = 0; + + if (bq27421_is_cfgupdate(chip)) + return true; + + do { + bq27421_write_word(chip->client, BQ27421_CONTROL, + BQ27421_SCMD_CFGUPDATE); + if (bq27421_is_cfgupdate(chip)) { + return true; + } else { + retry++; + msleep(100); + } + } while (retry < RETRY_CNT_ENTER_CFGUPDATE); + + return false; + +} + +static bool bq27421_exit_cfgupdate(struct bq27421_chip *chip) +{ + int retry = 0; + + if (!bq27421_is_cfgupdate(chip)) + return true; + + do { + bq27421_write_word(chip->client, BQ27421_CONTROL, + BQ27421_SCMD_EXIT_CFGUPDATE); + if (!bq27421_is_cfgupdate(chip)) { + return true; + } else { + retry++; + msleep(100); + } + } while (retry < RETRY_CNT_EXIT_CFGUPDATE); + + return false; +} + +static void bq27421_set_fast_hibernate(struct bq27421_chip *chip) +{ + struct bq27421_platform_data *pdata = chip->pdata; + int checksum; + int i; + + mutex_lock(&chip->mutex); + + if (!bq27421_ctrl_unsealed(chip)) { + pr_err("%s: failed to set unseal\n", __func__); + mutex_unlock(&chip->mutex); + return; + } + + if (!bq27421_enter_cfgupdate(chip)) { + pr_err("%s: failed to enter config mode\n", __func__); + mutex_unlock(&chip->mutex); + return; + } + + bq27421_write_byte(chip->client, BQ27421_ECMD_BLKCTRL, 0x00); + for (i = 0; i < pdata->num_fast_hib_data; i++) { + bq27421_write_byte(chip->client, BQ27421_ECMD_DATACLASS, + pdata->fast_hib_data[i].data_class); + bq27421_write_byte(chip->client, BQ27421_ECMD_DATABLK, + pdata->fast_hib_data[i].data_block); + msleep(10); + + bq27421_write_block(chip->client, + pdata->fast_hib_data[i].data[0][0], + &pdata->fast_hib_data[i].data[0][1], 16); + bq27421_write_block(chip->client, + pdata->fast_hib_data[i].data[1][0], + &pdata->fast_hib_data[i].data[1][1], 16); + bq27421_write_byte(chip->client, BQ27421_ECMD_BLKCHECKSUM, + pdata->fast_hib_data[i].checksum); + msleep(10); + + bq27421_write_byte(chip->client, BQ27421_ECMD_DATACLASS, + pdata->fast_hib_data[i].data_class); + bq27421_write_byte(chip->client, BQ27421_ECMD_DATABLK, + pdata->fast_hib_data[i].data_block); + msleep(10); + + checksum = bq27421_read_byte(chip->client, + BQ27421_ECMD_BLKCHECKSUM); + if (checksum == (int)pdata->fast_hib_data[i].checksum) { + pr_debug("%s: successed to set ramdata[%d]\n", + __func__, i); + } else { + pr_err("%s: failed to set ramdata [%d]\n", + __func__, i); + break; + } + } + + if (!bq27421_exit_cfgupdate(chip)) + pr_err("%s: failed to exit config model\n", __func__); + + if (!bq27421_ctrl_sealed(chip)) + pr_err("%s: failed to set seal\n", __func__); + + mutex_unlock(&chip->mutex); +} + /* Must call with mutex locked */ static void bq27421_update(struct bq27421_chip *chip) { @@ -194,6 +366,8 @@ static struct bq27421_platform_data *bq27421_get_pdata(struct device *dev) { struct device_node *np = dev->of_node; struct bq27421_platform_data *pdata; + int len; + const u8 *data; if (!np) return dev->platform_data; @@ -203,7 +377,16 @@ static struct bq27421_platform_data *bq27421_get_pdata(struct device *dev) return NULL; pdata->ext_batt_psy = - of_property_read_bool(np, "bq27421,ext_batt_psy"); + of_property_read_bool(np, "ti,ext_batt_psy"); + + data = of_get_property(np, "ti,fast-hib-data", &len); + if (data && len) { + pdata->num_fast_hib_data = + len / sizeof(struct bq27421_dataram); + pdata->fast_hib_data = (struct bq27421_dataram *)data; + } else { + return NULL; + } return pdata; } @@ -290,6 +473,14 @@ static int bq27421_remove(struct i2c_client *client) return 0; } +static void bq27421_shutdown(struct i2c_client *client) +{ + struct bq27421_chip *chip = i2c_get_clientdata(client); + + /* Set fast hibernate mode to prevent leakage */ + bq27421_set_fast_hibernate(chip); +} + static int bq27421_pm_prepare(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -351,6 +542,7 @@ static struct i2c_driver bq27421_i2c_driver = { .probe = bq27421_probe, .remove = bq27421_remove, .id_table = bq27421_id, + .shutdown = bq27421_shutdown, }; module_i2c_driver(bq27421_i2c_driver); diff --git a/include/linux/power/bq27421_battery.h b/include/linux/power/bq27421_battery.h index e74b6c3ea907d81880172476d43a84d2b5356b11..e7a82e12139eeae621d6098658fb74b9ce149e91 100644 --- a/include/linux/power/bq27421_battery.h +++ b/include/linux/power/bq27421_battery.h @@ -30,6 +30,8 @@ struct bq27421_dataram { struct bq27421_platform_data { bool ext_batt_psy; + struct bq27421_dataram *fast_hib_data; + u8 num_fast_hib_data; }; /* Standard data commands */