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\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 = 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\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 = 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\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, 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\n"); | | | | | || | | | | |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\n", ret); |return ret; |} ||dev_info(di->dev, "support ver. %s enabled\n", 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:\n", __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\n", |__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\n", |__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:\n", __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\n", __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\n", | |__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\n", | |__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:\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 & (1 << 4))) |break; |msleep(100); |} ||if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) { |dev_err(di->dev, "%s: failed %04x\n", __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\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 <= 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; }
I.MX6 bq27441 driver hacking相关推荐
- I.MX6 gpio-keys driver hacking
/***************************************************************************** I.MX6 gpio-keys drive ...
- I.MX6 ar1020 SPI device driver hacking
/************************************************************************************* I.MX6 ar1020 ...
- I.MX6 U-Boot mkconfig hacking
/***************************************************************************** I.MX6 U-Boot mkconfig ...
- I.MX6 mkuserimg.sh hacking
/************************************************************************ I.MX6 mkuserimg.sh hacking ...
- I.MX6 mfgtool2-android-mx6q-sabresd-emmc.vbs hacking
/********************************************************************* I.MX6 mfgtool2-android-mx6q-s ...
- linux read phy reg,PHY
Linux 下smi/mdio总线通信 韩大卫@吉林师范大学 下面代码描述了在用户层访问smi/mdio总线, 读写phy芯片寄存器的通用代码.Linux内核2.6以上通用. 将下面代码编译后,将可执 ...
- I.MX6 Linux Serial Baud Rate hacking
/********************************************************************************* I.MX6 Linux Seria ...
- I.MX6 PHY fixup 调用流程 hacking
/*********************************************************************************** I.MX6 PHY fixup ...
- I.MX6 Goodix GT9xx touchscreen driver porting
/************************************************************************* I.MX6 Goodix GT9xx touchs ...
最新文章
- Python 基础(6)(常用数据结构)
- 提高网站访问速度的34条军规(2)
- python中文分词jieba总结
- JSP、ASP、PHP Web应用程序怎么这么多P!
- (转)http接口测试——Jmeter接口测试实例讲解
- java集合系列——List集合之ArrayList介绍(二)
- UBUNTU16.04下Teamviewer的安装
- 【Linux安装】Win10安装Ubuntu双系统(含BIOS操作)
- 【快应用】十大手机厂商共推快应用标准
- Radix Tree总结
- Utf-8编码汉字占多少个字节
- EasyExcel实现Mysql数据库Excel数据导出
- 小程序报错:Failed to load local image resource /pages/goods/NaN加载资源失败问题解决
- Power BI(二十)power pivot之Earlier函数 - DAX进阶的里程碑
- 使用fbinst生成u盘启动列表大小不能太大
- JSP网页全屏显示、退出全屏、关闭页面
- 第一章 函数 极限 连续
- MySQL导入导出 —— mysqldump 简介及常见用法
- TS-MPEG2视频数字水印演示程序
- 局域网控制计算机运行程序,局域网控制远程计算机教程的方法
热门文章
- android 各种控件颜色值的设置(使用Drawable,Color)
- server-send event object
- CCTouchDispatcher sharedDispatcher 方法过期
- SQL Server-流程控制 5,Goto 语句
- 如何将广告始终定位到网页右下角
- cinder存储服务
- 面试题-自旋锁,以及jvm对synchronized的优化
- Swoft 2 Beta 发布,基于 Swoole 的云原生协程框架
- react中ref的使用
- 关于java.util.LinkedHashMap cannot be cast to ......的解决办法