I.MX6 bq27441 driver hacking

/*************************************************************************
 *                 I.MX6 bq27441 driver hacking
 * 声明:
 *     本文主要是记录对电池计量芯片bq27441芯片驱动注册过程进行代码跟踪。
 *
 *                                      2016-2-19 深圳 南山平山村 曾剑锋 
 ************************************************************************/



static int __init bq27x00_battery_init(void)
{
    int ret;

    ret = bq27x00_battery_i2c_init();     -----------------------+
    if (ret)                                                     |
        return ret;                                              |
                                                                 |
    ret = bq27x00_battery_platform_init();                       |
    if (ret)                                                     |
        bq27x00_battery_i2c_exit();       -----------------------*-----+
                                                                 |     |
    return ret;                                                  |     |
}                                                                |     |
module_init(bq27x00_battery_init);                               |     |
                                                                 |     |
static void __exit bq27x00_battery_exit(void)                    |     |
{                                                                |     |
    bq27x00_battery_platform_exit();                             |     |
    bq27x00_battery_i2c_exit();                                  |     |
}                                                                |     |
module_exit(bq27x00_battery_exit);                               |     |
                                                                 |     |
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");           |     |
MODULE_DESCRIPTION("BQ27x00 battery monitor driver");            |     |
MODULE_LICENSE("GPL");                                           |     |
                                                                 |     |
                                                                 |     |
static inline int __init bq27x00_battery_i2c_init(void)  <-------+     |
{                                                                      |
    int ret = i2c_add_driver(&bq27x00_battery_driver);   -----------+  |
    if (ret)                                                        |  |
        printk(KERN_ERR "Unable to register BQ27x00 i2c driver
"); |  |
                                                                    |  |
    return ret;                                                     |  |
}                                                                   |  |
                                                                    |  |
static inline void __exit bq27x00_battery_i2c_exit(void)    <-------*--+
{                                                                   |
    i2c_del_driver(&bq27x00_battery_driver);                        |
}                                                                   |
                                                                    |
                                                                    |
static const struct i2c_device_id bq27x00_id[] = {    <------+      |
    { "bq27200", BQ27200 },                                  |      |
    { "bq27500", BQ27500 },                                  |      |
    { "bq27520", BQ27520 },                                  |      |
    { "bq274xx", BQ274XX },                                  |      |
    { "bq276xx", BQ276XX },                                  |      |
    { "bq2753x", BQ2753X },                                  |      |
    {},                                                      |      |
};                                                           |      |
MODULE_DEVICE_TABLE(i2c, bq27x00_id);                        |      |
                                                             |      |
static struct i2c_driver bq27x00_battery_driver = {      <---|------+
    .driver = {                                              |
        .name = "bq27x00-battery",                           |
    },                                                       |
    .probe = bq27x00_battery_probe,                   -------*-------+
    .remove = bq27x00_battery_remove,                        |       |
    .id_table = bq27x00_id,                          --------+       |
};                                                                   |
                                                                     |
static int __init bq27x00_battery_probe(struct i2c_client *client, <-+
                 const struct i2c_device_id *id)
{
    char *name;
    struct bq27x00_device_info *di;
    int num;
    int retval = 0;
    u8 *regs;

    /* Get new ID for the new battery device */
    retval = idr_pre_get(&battery_id, GFP_KERNEL);
    if (retval == 0)
        return -ENOMEM;
    mutex_lock(&battery_mutex);
    retval = idr_get_new(&battery_id, client, &num);
    mutex_unlock(&battery_mutex);
    if (retval < 0)
        return retval;

    name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
    if (!name) {
        dev_err(&client->dev, "failed to allocate device name
");
        retval = -ENOMEM;
        goto batt_failed_1;
    }

    di = kzalloc(sizeof(*di), GFP_KERNEL);
    if (!di) {
        dev_err(&client->dev, "failed to allocate device info data
");
        retval = -ENOMEM;
        goto batt_failed_2;
    }

    di->id = num;
    di->dev = &client->dev;
    di->chip = id->driver_data;
    di->bat.name = name;
    di->bus.read = &bq27xxx_read_i2c;                -------------+
    di->bus.write = &bq27xxx_write_i2c;              -------------*-+
    di->bus.blk_read = bq27xxx_read_i2c_blk;         -------------*-*-+
    di->bus.blk_write = bq27xxx_write_i2c_blk;       -------------*-*-*-+
    di->dm_regs = NULL;                                           | | | |
    di->dm_regs_count = 0;                                        | | | |
                                                                  | | | |
    if (di->chip == BQ27200)                                      | | | |
        regs = bq27200_regs;                                      | | | |
    else if (di->chip == BQ27500)                                 | | | |
        regs = bq27500_regs;                                      | | | |
    else if (di->chip == BQ27520)                                 | | | |
        regs = bq27520_regs;                                      | | | |
    else if (di->chip == BQ2753X)                                 | | | |
        regs = bq2753x_regs;                                      | | | |
    else if (di->chip == BQ274XX) {                               | | | |
        regs = bq274xx_regs;                                      | | | |
        di->dm_regs = bq274xx_dm_regs;               -------------*-*-*-*-+
        di->dm_regs_count = ARRAY_SIZE(bq274xx_dm_regs);          | | | | |
    } else if (di->chip == BQ276XX) {                             | | | | |
        /* commands are same as bq274xx, only DM is different */  | | | | |
        regs = bq276xx_regs;                                      | | | | |
        di->dm_regs = bq276xx_dm_regs;                            | | | | |
        di->dm_regs_count = ARRAY_SIZE(bq276xx_dm_regs);          | | | | |
    } else {                                                      | | | | |
        dev_err(&client->dev,                                     | | | | |
            "Unexpected gas gague: %d
", di->chip);              | | | | |
        regs = bq27520_regs;                                      | | | | |
    }                                                             | | | | |
                                                                  | | | | |
    memcpy(di->regs, regs, NUM_REGS);                             | | | | |
                                                                  | | | | |
    di->fw_ver = bq27x00_battery_read_fw_version(di);             | | | | |
    dev_info(&client->dev, "Gas Guage fw version is 0x%04x
",    | | | | |
            di->fw_ver);                                          | | | | |
                                                                  | | | | |
    retval = bq27x00_powersupply_init(di);                 -------*-*-*-*-*-+
    if (retval)                                                   | | | | | |
        goto batt_failed_3;                                       | | | | | |
                                                                  | | | | | |
    /* Schedule a polling after about 1 min */                    | | | | | |
    schedule_delayed_work(&di->work, 60 * HZ);                    | | | | | |
                                                                  | | | | | |
    i2c_set_clientdata(client, di);                               | | | | | |
    retval = sysfs_create_group(&client->dev.kobj,                | | | | | |
            &bq27x00_attr_group);                                 | | | | | |
    if (retval)                                                   | | | | | |
        dev_err(&client->dev, "could not create sysfs files
");  | | | | | |
                                                                  | | | | | |
    return 0;                                                     | | | | | |
                                                                  | | | | | |
batt_failed_3:                                                    | | | | | |
    kfree(di);                                                    | | | | | |
batt_failed_2:                                                    | | | | | |
    kfree(name);                                                  | | | | | |
batt_failed_1:                                                    | | | | | |
    mutex_lock(&battery_mutex);                                   | | | | | |
    idr_remove(&battery_id, num);                                 | | | | | |
    mutex_unlock(&battery_mutex);                                 | | | | | |
                                                                  | | | | | |
    return retval;                                                | | | | | |
}                                                                 | | | | | |
                                                                  | | | | | |
static int bq27xxx_read_i2c(struct bq27x00_device_info *di, <-----+ | | | | |
        u8 reg, bool single)                                        | | | | |
{                                                                   | | | | |
    struct i2c_client *client = to_i2c_client(di->dev);             | | | | |
    struct i2c_msg msg[2];                                          | | | | |
    unsigned char data[2];                                          | | | | |
    int ret;                                                        | | | | |
                                                                    | | | | |
    if (!client->adapter)                                           | | | | |
        return -ENODEV;                                             | | | | |
                                                                    | | | | |
    msg[0].addr = client->addr;                                     | | | | |
    msg[0].flags = 0;                                               | | | | |
    msg[0].buf = &reg;                                              | | | | |
    msg[0].len = sizeof(reg);                                       | | | | |
    msg[1].addr = client->addr;                                     | | | | |
    msg[1].flags = I2C_M_RD;                                        | | | | |
    msg[1].buf = data;                                              | | | | |
    if (single)                                                     | | | | |
        msg[1].len = 1;                                             | | | | |
    else                                                            | | | | |
        msg[1].len = 2;                                             | | | | |
                                                                    | | | | |
    ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));      | | | | |
    if (ret < 0)                                                    | | | | |
        return ret;                                                 | | | | |
                                                                    | | | | |
    if (!single)                                                    | | | | |
        ret = get_unaligned_le16(data);                             | | | | |
    else                                                            | | | | |
        ret = data[0];                                              | | | | |
                                                                    | | | | |
    return ret;                                                     | | | | |
}                                                                   | | | | |
                                                                    | | | | |
static int bq27xxx_write_i2c(struct bq27x00_device_info *di,   <----+ | | | |
        u8 reg, int value, bool single)                               | | | |
{                                                                     | | | |
    struct i2c_client *client = to_i2c_client(di->dev);               | | | |
    struct i2c_msg msg;                                               | | | |
    unsigned char data[4];                                            | | | |
    int ret;                                                          | | | |
                                                                      | | | |
    if (!client->adapter)                                             | | | |
        return -ENODEV;                                               | | | |
                                                                      | | | |
    data[0] = reg;                                                    | | | |
    if (single) {                                                     | | | |
        data[1] = (unsigned char)value;                               | | | |
        msg.len = 2;                                                  | | | |
    } else {                                                          | | | |
        put_unaligned_le16(value, &data[1]);                          | | | |
        msg.len = 3;                                                  | | | |
    }                                                                 | | | |
                                                                      | | | |
    msg.buf = data;                                                   | | | |
    msg.addr = client->addr;                                          | | | |
    msg.flags = 0;                                                    | | | |
                                                                      | | | |
    ret = i2c_transfer(client->adapter, &msg, 1);                     | | | |
    if (ret < 0)                                                      | | | |
        return ret;                                                   | | | |
                                                                      | | | |
    return 0;                                                         | | | |
}                                                                     | | | |
                                                                      | | | |
static int bq27xxx_read_i2c_blk(struct bq27x00_device_info *di, <-----+ | | |
    u8 reg, u8 *data, u8 len)                                           | | |
{                                                                       | | |
    struct i2c_client *client = to_i2c_client(di->dev);                 | | |
    struct i2c_msg msg[2];                                              | | |
    int ret;                                                            | | |
                                                                        | | |
    if (!client->adapter)                                               | | |
        return -ENODEV;                                                 | | |
                                                                        | | |
    msg[0].addr = client->addr;                                         | | |
    msg[0].flags = 0;                                                   | | |
    msg[0].buf = &reg;                                                  | | |
    msg[0].len = 1;                                                     | | |
                                                                        | | |
    msg[1].addr = client->addr;                                         | | |
    msg[1].flags = I2C_M_RD;                                            | | |
    msg[1].buf = data;                                                  | | |
    msg[1].len = len;                                                   | | |
                                                                        | | |
    ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));          | | |
    if (ret < 0)                                                        | | |
        return ret;                                                     | | |
                                                                        | | |
    return ret;                                                         | | |
}                                                                       | | |
                                                                        | | |
static int bq27xxx_write_i2c_blk(struct bq27x00_device_info *di, <------+ | |
        u8 reg, u8 *data, u8 sz)                                          | |
{                                                                         | |
    struct i2c_client *client = to_i2c_client(di->dev);                   | |
    struct i2c_msg msg;                                                   | |
    int ret;                                                              | |
    u8 buf[33];                                                           | |
                                                                          | |
    if (!client->adapter)                                                 | |
        return -ENODEV;                                                   | |
                                                                          | |
    buf[0] = reg;                                                         | |
    memcpy(&buf[1], data, sz);                                            | |
                                                                          | |
    msg.buf = buf;                                                        | |
    msg.addr = client->addr;                                              | |
    msg.flags = 0;                                                        | |
    msg.len = sz + 1;                                                     | |
                                                                          | |
    ret = i2c_transfer(client->adapter, &msg, 1);                         | |
    if (ret < 0)                                                          | |
        return ret;                                                       | |
                                                                          | |
    return 0;                                                             | |
}                                                                         | |
                                                                          | |
static struct dm_reg bq274xx_dm_regs[] = {              <-----------------+ |
    {82, 0, 2, 1000},    /* Qmax */                                         |
    {82, 5, 1, 0x81},    /* Load Select */                                  |
    {82, 10, 2, 1340},    /* Design Capacity */                             |
    {82, 12, 2, 3700},    /* Design Energy */                               |
    {82, 16, 2, 3250},    /* Terminate Voltage */                           |
    {82, 27, 2, 110},    /* Taper rate */                                   |
};                                                                          |
                                                                            |
static int __init bq27x00_powersupply_init(             <-------------------+
        struct bq27x00_device_info *di)
{
    int ret;

    di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
    if (di->chip == BQ274XX) {
        set_properties_array(di, bq274xx_battery_props,
            ARRAY_SIZE(bq274xx_battery_props));
    } else if (di->chip == BQ276XX) {
        set_properties_array(di, bq276xx_battery_props,
            ARRAY_SIZE(bq276xx_battery_props));
    } else if (di->chip == BQ27520) {
        set_properties_array(di, bq27520_battery_props,
            ARRAY_SIZE(bq27520_battery_props));
    } else if (di->chip == BQ2753X) {
        set_properties_array(di, bq2753x_battery_props,
            ARRAY_SIZE(bq2753x_battery_props));
    } else {
        set_properties_array(di, bq27x00_battery_props,
            ARRAY_SIZE(bq27x00_battery_props));
    }
    di->bat.get_property = bq27x00_battery_get_property;
    di->bat.external_power_changed = bq27x00_external_power_changed;

    INIT_DELAYED_WORK(&di->work, bq27x00_battery_poll);    -------------+
    mutex_init(&di->lock);                                              |
                                                                        |
    ret = power_supply_register(di->dev, &di->bat);                     |
    if (ret) {                                                          |
        dev_err(di->dev, "failed to register battery: %d
", ret);      |
        return ret;                                                     |
    }                                                                   |
                                                                        |
    dev_info(di->dev, "support ver. %s enabled
", DRIVER_VERSION);     |
                                                                        |
    bq27x00_update(di);                                                 |
                                                                        |
    return 0;                                                           |
}                                                                       |
                                                                        |
static void bq27x00_battery_poll(struct work_struct *work) <------------+
{
    struct bq27x00_device_info *di =
        container_of(work, struct bq27x00_device_info, work.work);

    if (((di->chip == BQ274XX) || (di->chip == BQ276XX)) &&
        !rom_mode_gauge_dm_initialized(di)) {
        rom_mode_gauge_dm_init(di);                        -------------+
    }                                                                   |
                                                                        |
    bq27x00_update(di);                                    -------------*---+
                                                                        |   |
    if (poll_interval > 0) {                                            |   |
        /* The timer does not have to be accurate. */                   |   |
        set_timer_slack(&di->work.timer, poll_interval * HZ / 4);       |   |
        schedule_delayed_work(&di->work, poll_interval * HZ);           |   |
    }                                                                   |   |
}                                                                       |   |
                                                                        |   |
#define INITCOMP_TIMEOUT_MS        10000                                |   |
static void rom_mode_gauge_dm_init(struct bq27x00_device_info *di)  <---+   |
{                                                                           |
    int i;                                                                  |
    int timeout = INITCOMP_TIMEOUT_MS;                                      |
    u8 subclass, offset;                                                    |
    u32 blk_number;                                                         |
    u32 blk_number_prev = 0;                                                |
    u8 buf[32];                                                             |
    bool buf_valid = false;                                                 |
    struct dm_reg *dm_reg;                                                  |
                                                                            |
    dev_dbg(di->dev, "%s:
", __func__);                                    |
                                                                            |
    while (!rom_mode_gauge_init_completed(di) && timeout > 0) {             |
        msleep(100);                                                        |
        timeout -= 100;                                                     |
    }                                                                       |
                                                                            |
    if (timeout <= 0) {                                                     |
        dev_err(di->dev, "%s: INITCOMP not set after %d seconds
",         |
            __func__, INITCOMP_TIMEOUT_MS/100);                             |
        return;                                                             |
    }                                                                       |
                                                                            |
    if (!di->dm_regs || !di->dm_regs_count) {                               |
        dev_err(di->dev, "%s: Data not available for DM initialization
",  |
            __func__);                                                      |
        return;                                                             |
    }                                                                       |
                                                                            |
    enter_cfg_update_mode(di);                            ------------+     |
    for (i = 0; i < di->dm_regs_count; i++) {                         |     |
        dm_reg = &di->dm_regs[i];                                     |     |
        subclass = dm_reg->subclass;                                  |     |
        offset = dm_reg->offset;                                      |     |
                                                                      |     |
        /*                                                            |     |
         * Create a composite block number to see if the subsequent   |     |
         * register also belongs to the same 32 btye block in the DM  |     |
         */                                                           |     |
        blk_number = subclass << 8;                                   |     |
        blk_number |= offset >> 5;                                    |     |
                                                                      |     |
        if (blk_number == blk_number_prev) {                          |     |
            copy_to_dm_buf_big_endian(di, buf, offset,                |     |
                dm_reg->len, dm_reg->data);                           |     |
        } else {                                                      |     |
                                                                      |     |
            if (buf_valid)                                            |     |
                update_dm_block(di, blk_number_prev >> 8,             |     |
                    (blk_number_prev << 5) & 0xFF , buf);             |     |
            else                                                      |     |
                buf_valid = true;                                     |     |
                                                                      |     |
            read_dm_block(di, dm_reg->subclass, dm_reg->offset,       |     |
                buf);                                                 |     |
            copy_to_dm_buf_big_endian(di, buf, offset,                |     |
                dm_reg->len, dm_reg->data);                           |     |
        }                                                             |     |
        blk_number_prev = blk_number;                                 |     |
    }                                                                 |     |
                                                                      |     |
    /* Last buffer to be written */                                   |     |
    if (buf_valid)                                                    |     |
        update_dm_block(di, subclass, offset, buf); ------------------*-+   |
                                                                      | |   |
    exit_cfg_update_mode(di);                           --------------*-*-+ |
}                                                                     | | | |
                                                                      | | | |
#define CFG_UPDATE_POLLING_RETRY_LIMIT 50                             | | | |
static int enter_cfg_update_mode(struct bq27x00_device_info *di) <----+ | | |
{                                                                       | | |
    int i = 0;                                                          | | |
    u16 flags;                                                          | | |
                                                                        | | |
    dev_dbg(di->dev, "%s:
", __func__);                                | | |
                                                                        | | |
    if (!unseal(di, BQ274XX_UNSEAL_KEY))                                | | |
        return 0;                                                       | | |
                                                                        | | |
    control_cmd_wr(di, SET_CFGUPDATE_SUBCMD);                           | | |
    msleep(5);                                                          | | |
                                                                        | | |
    while (i < CFG_UPDATE_POLLING_RETRY_LIMIT) {                        | | |
        i++;                                                            | | |
        flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false);             | | |
        if (flags & (1 << 4))                                           | | |
            break;                                                      | | |
        msleep(100);                                                    | | |
    }                                                                   | | |
                                                                        | | |
    if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) {                          | | |
        dev_err(di->dev, "%s: failed %04x
", __func__, flags);         | | |
        return 0;                                                       | | |
    }                                                                   | | |
                       +------------------------------------------------+ | |
    return 1;          |                                                  | |
}                      |                                                  | |
                       |                                                  | |
                       V                                                  | |
static int update_dm_block(struct bq27x00_device_info *di, u8 subclass,   | |
    u8 offset, u8 *data)                                                  | |
{                                                                         | |
    u8 buf[32];                                                           | |
    u8 cksum;                                                             | |
    u8 blk_offset = offset >> 5;                                          | |
                                                                          | |
    dev_dbg(di->dev, "%s: subclass %d offset %d
",                       | |
        __func__, subclass, offset);                                      | |
                                                                          | |
    di->bus.write(di, BLOCK_DATA_CONTROL, 0, true);                       | |
    msleep(5);                                                            | |
                                                                          | |
    di->bus.write(di, BLOCK_DATA_CLASS, subclass, true);                  | |
    msleep(5);                                                            | |
                                                                          | |
    di->bus.write(di, DATA_BLOCK, blk_offset, true);                      | |
    msleep(5);                                                            | |
                                                                          | |
    di->bus.blk_write(di, BLOCK_DATA, data, 32);                          | |
    msleep(5);                                                            | |
    print_buf(__func__, data);                                            | |
                                                                          | |
    cksum = checksum(data);                                               | |
    di->bus.write(di, BLOCK_DATA_CHECKSUM, cksum, true);                  | |
    msleep(5);                                                            | |
                                                                          | |
    /* Read back and compare to make sure write is successful */          | |
    di->bus.write(di, DATA_BLOCK, blk_offset, true);                      | |
    msleep(5);                                                            | |
    di->bus.blk_read(di, BLOCK_DATA, buf, 32);                            | |
    if (memcmp(data, buf, 32)) {                                          | |
        dev_err(di->dev, "%s: error updating subclass %d offset %d
",    | |
            __func__, subclass, offset);                                  | |
        return 0;                                                         | |
    } else {                                                              | |
        return 1;                                                         | |
    }                                                                     | |
}                                                                         | |
                                                                          | |
static int exit_cfg_update_mode(struct bq27x00_device_info *di)   <-------+ |
{                                                                           |
    int i = 0;                                                              |
    u16 flags;                                                              |
                                                                            |
    dev_dbg(di->dev, "%s:
", __func__);                                    |
                                                                            |
    control_cmd_wr(di, BQ274XX_SOFT_RESET);                                 |
                                                                            |
    while (i < CFG_UPDATE_POLLING_RETRY_LIMIT) {                            |
        i++;                                                                |
        flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false);                 |
        if (!(flags & (1 << 4)))                                            |
            break;                                                          |
        msleep(100);                                                        |
    }                                                                       |
                                                                            |
    if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) {                              |
        dev_err(di->dev, "%s: failed %04x
", __func__, flags);             |
        return 0;                                                           |
    }                                                                       |
                                                                            |
    if (seal(di))                                                           |
        return 1;                                                           |
    else                                                                    |
        return 0;                                                           |
}                                                                           |
                                                                            |
                                                                            |
static void bq27x00_update(struct bq27x00_device_info *di)     <------------+
{
    struct bq27x00_reg_cache cache = {0, };
    bool is_bq27200 = (di->chip == BQ27200);
    bool is_bq27500 = (di->chip == BQ27500);
    bool is_bq274xx = (di->chip == BQ274XX);
    bool is_bq276xx = (di->chip == BQ276XX);

    cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, !is_bq27500);
    if (cache.flags >= 0) {
        if (is_bq27200 && (cache.flags & BQ27200_FLAG_CI)) {
            dev_info(di->dev, "battery is not calibrated!
                    ignoring capacity values
");
            cache.capacity = -ENODATA;
            cache.energy = -ENODATA;
            cache.time_to_empty = -ENODATA;
            cache.time_to_empty_avg = -ENODATA;
            cache.time_to_full = -ENODATA;
            cache.charge_full = -ENODATA;
            cache.health = -ENODATA;
        } else {
            cache.capacity = bq27x00_battery_read_soc(di);
            if (!(is_bq274xx || is_bq276xx)) {
                cache.energy = bq27x00_battery_read_energy(di);
                cache.time_to_empty =
                    bq27x00_battery_read_time(di,
                            BQ27XXX_REG_TTE);
                cache.time_to_empty_avg =
                    bq27x00_battery_read_time(di,
                            BQ27XXX_REG_TTECP);
                cache.time_to_full =
                    bq27x00_battery_read_time(di,
                            BQ27XXX_REG_TTF);
            }
            cache.charge_full = bq27x00_battery_read_fcc(di);
            cache.health = bq27x00_battery_read_health(di);
        }
        cache.temperature = bq27x00_battery_read_temperature(di);
        if (!is_bq274xx)
            cache.cycle_count = bq27x00_battery_read_cyct(di);
        cache.power_avg =
            bq27x00_battery_read_pwr_avg(di, BQ27XXX_POWER_AVG);

        /* We only have to read charge design full once */
        if (di->charge_design_full <= 0)
            di->charge_design_full = bq27x00_battery_read_dcap(di);
    }

    if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) {
        di->cache = cache;
        power_supply_changed(&di->bat);
    }

    di->last_update = jiffies;
}
原文地址:https://www.cnblogs.com/zengjfgit/p/5200888.html