I.MX6 bq27441 driver hacking

时间:2023-12-27 10:45:07
/*************************************************************************
* 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\n"); | |
| |
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 = ;
u8 *regs; /* Get new ID for the new battery device */
retval = idr_pre_get(&battery_id, GFP_KERNEL);
if (retval == )
return -ENOMEM;
mutex_lock(&battery_mutex);
retval = idr_get_new(&battery_id, client, &num);
mutex_unlock(&battery_mutex);
if (retval < )
return retval; name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
if (!name) {
dev_err(&client->dev, "failed to allocate device name\n");
retval = -ENOMEM;
goto batt_failed_1;
} di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di) {
dev_err(&client->dev, "failed to allocate device info data\n");
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 = ; | | | |
| | | |
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\n", 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\n", | | | | |
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, * 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\n"); | | | | | |
| | | | | |
return ; | | | | | |
| | | | | |
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[]; | | | | |
unsigned char data[]; | | | | |
int ret; | | | | |
| | | | |
if (!client->adapter) | | | | |
return -ENODEV; | | | | |
| | | | |
msg[].addr = client->addr; | | | | |
msg[].flags = ; | | | | |
msg[].buf = &reg; | | | | |
msg[].len = sizeof(reg); | | | | |
msg[].addr = client->addr; | | | | |
msg[].flags = I2C_M_RD; | | | | |
msg[].buf = data; | | | | |
if (single) | | | | |
msg[].len = ; | | | | |
else | | | | |
msg[].len = ; | | | | |
| | | | |
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); | | | | |
if (ret < ) | | | | |
return ret; | | | | |
| | | | |
if (!single) | | | | |
ret = get_unaligned_le16(data); | | | | |
else | | | | |
ret = data[]; | | | | |
| | | | |
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[]; | | | |
int ret; | | | |
| | | |
if (!client->adapter) | | | |
return -ENODEV; | | | |
| | | |
data[] = reg; | | | |
if (single) { | | | |
data[] = (unsigned char)value; | | | |
msg.len = ; | | | |
} else { | | | |
put_unaligned_le16(value, &data[]); | | | |
msg.len = ; | | | |
} | | | |
| | | |
msg.buf = data; | | | |
msg.addr = client->addr; | | | |
msg.flags = ; | | | |
| | | |
ret = i2c_transfer(client->adapter, &msg, ); | | | |
if (ret < ) | | | |
return ret; | | | |
| | | |
return ; | | | |
} | | | |
| | | |
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[]; | | |
int ret; | | |
| | |
if (!client->adapter) | | |
return -ENODEV; | | |
| | |
msg[].addr = client->addr; | | |
msg[].flags = ; | | |
msg[].buf = &reg; | | |
msg[].len = ; | | |
| | |
msg[].addr = client->addr; | | |
msg[].flags = I2C_M_RD; | | |
msg[].buf = data; | | |
msg[].len = len; | | |
| | |
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); | | |
if (ret < ) | | |
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[]; | |
| |
if (!client->adapter) | |
return -ENODEV; | |
| |
buf[] = reg; | |
memcpy(&buf[], data, sz); | |
| |
msg.buf = buf; | |
msg.addr = client->addr; | |
msg.flags = ; | |
msg.len = sz + ; | |
| |
ret = i2c_transfer(client->adapter, &msg, ); | |
if (ret < ) | |
return ret; | |
| |
return ; | |
} | |
| |
static struct dm_reg bq274xx_dm_regs[] = { <-----------------+ |
{, , , }, /* Qmax */ |
{, , , 0x81}, /* Load Select */ |
{, , , }, /* Design Capacity */ |
{, , , }, /* Design Energy */ |
{, , , }, /* Terminate Voltage */ |
{, , , }, /* 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\n", ret); |
return ret; |
} |
|
dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); |
|
bq27x00_update(di); |
|
return ; |
} |
|
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 > ) { | |
/* The timer does not have to be accurate. */ | |
set_timer_slack(&di->work.timer, poll_interval * HZ / ); | |
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 = ; |
u8 buf[]; |
bool buf_valid = false; |
struct dm_reg *dm_reg; |
|
dev_dbg(di->dev, "%s:\n", __func__); |
|
while (!rom_mode_gauge_init_completed(di) && timeout > ) { |
msleep(); |
timeout -= ; |
} |
|
if (timeout <= ) { |
dev_err(di->dev, "%s: INITCOMP not set after %d seconds\n", |
__func__, INITCOMP_TIMEOUT_MS/); |
return; |
} |
|
if (!di->dm_regs || !di->dm_regs_count) { |
dev_err(di->dev, "%s: Data not available for DM initialization\n", |
__func__); |
return; |
} |
|
enter_cfg_update_mode(di); ------------+ |
for (i = ; 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 << ; | |
blk_number |= offset >> ; | |
| |
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 >> , | |
(blk_number_prev << ) & 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 = ; | | |
u16 flags; | | |
| | |
dev_dbg(di->dev, "%s:\n", __func__); | | |
| | |
if (!unseal(di, BQ274XX_UNSEAL_KEY)) | | |
return ; | | |
| | |
control_cmd_wr(di, SET_CFGUPDATE_SUBCMD); | | |
msleep(); | | |
| | |
while (i < CFG_UPDATE_POLLING_RETRY_LIMIT) { | | |
i++; | | |
flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); | | |
if (flags & ( << )) | | |
break; | | |
msleep(); | | |
} | | |
| | |
if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) { | | |
dev_err(di->dev, "%s: failed %04x\n", __func__, flags); | | |
return ; | | |
} | | |
+------------------------------------------------+ | |
return ; | | |
} | | |
| | |
V | |
static int update_dm_block(struct bq27x00_device_info *di, u8 subclass, | |
u8 offset, u8 *data) | |
{ | |
u8 buf[]; | |
u8 cksum; | |
u8 blk_offset = offset >> ; | |
| |
dev_dbg(di->dev, "%s: subclass %d offset %d\n", | |
__func__, subclass, offset); | |
| |
di->bus.write(di, BLOCK_DATA_CONTROL, , true); | |
msleep(); | |
| |
di->bus.write(di, BLOCK_DATA_CLASS, subclass, true); | |
msleep(); | |
| |
di->bus.write(di, DATA_BLOCK, blk_offset, true); | |
msleep(); | |
| |
di->bus.blk_write(di, BLOCK_DATA, data, ); | |
msleep(); | |
print_buf(__func__, data); | |
| |
cksum = checksum(data); | |
di->bus.write(di, BLOCK_DATA_CHECKSUM, cksum, true); | |
msleep(); | |
| |
/* Read back and compare to make sure write is successful */ | |
di->bus.write(di, DATA_BLOCK, blk_offset, true); | |
msleep(); | |
di->bus.blk_read(di, BLOCK_DATA, buf, ); | |
if (memcmp(data, buf, )) { | |
dev_err(di->dev, "%s: error updating subclass %d offset %d\n", | |
__func__, subclass, offset); | |
return ; | |
} else { | |
return ; | |
} | |
} | |
| |
static int exit_cfg_update_mode(struct bq27x00_device_info *di) <-------+ |
{ |
int i = ; |
u16 flags; |
|
dev_dbg(di->dev, "%s:\n", __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 & ( << ))) |
break; |
msleep(); |
} |
|
if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) { |
dev_err(di->dev, "%s: failed %04x\n", __func__, flags); |
return ; |
} |
|
if (seal(di)) |
return ; |
else |
return ; |
} |
|
|
static void bq27x00_update(struct bq27x00_device_info *di) <------------+
{
struct bq27x00_reg_cache cache = {, };
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 >= ) {
if (is_bq27200 && (cache.flags & BQ27200_FLAG_CI)) {
dev_info(di->dev, "battery is not calibrated!
ignoring capacity values\n");
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 <= )
di->charge_design_full = bq27x00_battery_read_dcap(di);
} if (memcmp(&di->cache, &cache, sizeof(cache)) != ) {
di->cache = cache;
power_supply_changed(&di->bat);
} di->last_update = jiffies;
}