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 <[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 = ® | | | | |
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 = ® | | |
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;
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.