I.MX6 bq27441 driver hacking

72349 단어
/*************************************************************************
 *                 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 <[email protected]>");           |     |
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; }

좋은 웹페이지 즐겨찾기