Linux 驱动编写

因为公司业务需要,很早之前写过一个电池驱动,可以兼容TI三款电池电量计芯片(BQ28Z610、BQ40Z50、BQ27542)。一直没整理,本文记录了一个驱动开发过程。

芯片概述

驱动需要同时兼容以下三款芯片:

芯片型号 适用场景 关键特性
BQ28Z610 2节锂电池 支持阻抗跟踪,标准 SBS 命令集
BQ40Z50 多节锂电池(1-4节) 支持 SHA-1 认证,MAC 命令扩展
BQ27542 单节锂电池 简化版,低成本方案

三款芯片虽然功能差异较大,但通信总线统一为SMBus(兼容标准I2C),因此可以通过寄存器映射表的方式实现一个驱动兼容多款芯片。

主要思路:定义统一功能寄存器索引,为每颗芯片独立配置寄存器映射表,业务层读写全部调用统一索引接口,上层Power Supply子系统无感硬件差异。

I2C驱动注册与设备树匹配

驱动匹配机制

Linux I2C子系统支持两种匹配方式:

  1. ACPI / 设备树匹配:通过 compatible 字符串匹配(现代嵌入式主流方式)
  2. I2C ID Table 匹配:通过设备名称匹配(传统方式,兼容旧内核)

在驱动中,两种方式都进行了注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 设备树匹配表 */
static const struct of_device_id bq_fg_match_table[] = {
{ .compatible = "ti,bq28z610" },
{ .compatible = "ti,bq40z50" },
{ .compatible = "ti,bq27542" },
{},
};
MODULE_DEVICE_TABLE(of, bq_fg_match_table);

/* I2C ID 匹配表 */
static const struct i2c_device_id bq_fg_id[] = {
{ "bq28z610", BQ28Z610 },
{ "bq40z50", BQ40Z50 },
{ "bq27542", BQ27542 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq_fg_id);

宏作用解析:MODULE_DEVICE_TABLE 将匹配结构体导出至模块段,内核加载驱动、modprobe动态加载时,可自动遍历总线设备完成绑定。

I2C 驱动结构体注册

1
2
3
4
5
6
7
8
9
10
11
12
13
static struct i2c_driver bq_fg_driver = {
.driver = {
.name = "bq_fg",
.owner = THIS_MODULE,
.of_match_table = bq_fg_match_table,
},
.id_table = bq_fg_id,
.probe = bq_fg_probe,
.remove = bq_fg_remove,
.shutdown = bq_fg_shutdown,
};

module_i2c_driver(bq_fg_driver);

module_i2c_driver() 是一个便捷宏,它会自动生成 module_initmodule_exit 函数,分别调用 i2c_add_driver()i2c_del_driver()

设备树配置示例

同一I2C总线挂载同地址不同芯片,通过status字段按需启停,硬件底板兼容焊接三款芯片,软件按需开启节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&i2c3{
status = "okay";
i2c-scl-rising-time-ns = <345>;
i2c-scl-falling-time-ns = <11>;
// 时钟100000
clock-frequency = <100000>;
//clock-frequency = <400000>;

// i2c地址 bq27542 bq28z610
battery0: bq28z610@0b{
//status = "disabled";
status = "okay";
compatible ="ti,bq28z610";
reg = <0x0b>;

};
battery1: bq40z50@0b{
//status = "disabled";
status = "disabled";
compatible ="ti,bq40z50";
reg = <0x0b>;

};
};

芯片枚举与寄存器索引

三款芯片的寄存器地址各不相同,但功能语义一致。我们定义统一的寄存器索引枚举,再为每款芯片配置具体的地址映射表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 统一的寄存器功能索引 */
enum bq_fg_reg_idx {
BQ_FG_REG_MAC = 0, /* ManufacturerBlockAccess (MAC) */
BQ_FG_REG_TEMP, /* 电池温度 */
BQ_FG_REG_VOLT, /* 电池电压 */
BQ_FG_REG_AI, /* 平均电流 */
BQ_FG_REG_BATT_STATUS, /* 电池状态 */
BQ_FG_REG_TTE, /* 剩余时间 */
BQ_FG_REG_TTF, /* 充满时间 */
BQ_FG_REG_FCC, /* 满充容量 */
BQ_FG_REG_RM, /* 剩余容量 */
BQ_FG_REG_CC, /* 循环次数 */
BQ_FG_REG_SOC, /* 相对电量百分比 */
BQ_FG_REG_SOH, /* 健康状态 */
BQ_FG_REG_DC, /* 设计容量 */
BQ_FG_REG_MBA, /* ManufacturerBlockAccess */
BQ_FG_REG_MAC_CHKSUM, /* MAC 校验和 */
NUM_REGS,
};

/* 芯片类型枚举 */
enum bq_fg_device {
BQ28Z610,
BQ40Z50,
BQ27542
};

寄存器映射表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#define INVALID_REG_ADDR    0xFF

/* BQ28Z610 寄存器映射 */
static u8 bq28z610_regs[NUM_REGS] = {
INVALID_REG_ADDR, /* MAC */
0x08, /* TEMP */
0x09, /* VOLT */
0x0b, /* AI */
0x16, /* BATTERY_STATUS */
0x12, /* TTE */
0x13, /* TTF */
0x10, /* FCC */
0x0f, /* RM */
0x17, /* CC */
0x0d, /* SOC */
INVALID_REG_ADDR, /* SOH - 不支持 */
0x18, /* DC */
INVALID_REG_ADDR, /* MBA */
INVALID_REG_ADDR, /* MAC_CHKSUM */
};

/* BQ40Z50 寄存器映射 */
static u8 bq40z50_regs[NUM_REGS] = {
INVALID_REG_ADDR, /* MAC - 使用 ManufacturerAccess */
0x08, /* TEMP */
0x09, /* VOLT */
0x0B, /* AI */
0x16, /* BATTERY_STATUS */
0x12, /* TTE */
0x13, /* TTF */
0x10, /* FCC */
0x0f, /* RM */
0x17, /* CC */
0x0d, /* SOC */
INVALID_REG_ADDR, /* SOH */
0x18, /* DC */
INVALID_REG_ADDR, /* AltManufactureAccess */
INVALID_REG_ADDR, /* MAC_CHKSUM */
};

/* BQ27542 寄存器映射 */
static u8 bq27542_regs[NUM_REGS] = {
INVALID_REG_ADDR, /* CONTROL */
0x06, /* TEMP */
0x08, /* VOLT */
0x14, /* AVG CURRENT */
0x0a, /* FLAGS */
0x16, /* Time to empty */
INVALID_REG_ADDR, /* Time to full - 不支持 */
0x12, /* Full charge capacity */
0x10, /* Remaining Capacity */
0x2a, /* CycleCount */
0x2c, /* State of Charge */
INVALID_REG_ADDR, /* State of Health */
INVALID_REG_ADDR, /* Design Capacity */
INVALID_REG_ADDR, /* AltManufacturerAccess */
INVALID_REG_ADDR, /* MACChecksum */
};

按照功能索引一一绑定每颗芯片原生I2C寄存器地址,不支持功能统一填INVALID_REG_ADDR,i2c_probe 阶段读取芯片类型,拷贝对应映射表至私有结构体 bq->regs[],所有业务读写,只传入功能索引,底层自动适配物理寄存器地址.

SMBus 读写

BQ 系列芯片遵循 SMBus(System Management Bus)协议,Linux 内核提供了 i2c_smbus_* 系列函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 读取 16-bit 字数据 */
static int __fg_read_word(struct i2c_client *client, u8 reg, u16 *val)
{
s32 ret;
ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0) {
bq_err("i2c read word fail: can't read from reg 0x%02X\n", reg);
return ret;
}
*val = (u16)ret;
return 0;
}

/* 写入 16-bit 字数据 */
static int __fg_write_word(struct i2c_client *client, u8 reg, u16 val)
{
s32 ret;
ret = i2c_smbus_write_word_data(client, reg, val);
if (ret < 0) {
bq_err("i2c write word fail: can't write 0x%02X to reg 0x%02X\n",
val, reg);
return ret;
}
return 0;
}

读取接口示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void fg_read_fw_version(struct bq_fg_chip *bq)
{
int ret;
u8 buf[36];

/* 发送 FW_VER 子命令 */
ret = fg_write_word(bq, bq->regs[BQ_FG_REG_MBA], FG_MAC_CMD_FW_VER);
if (ret < 0) {
bq_err("Failed to send firmware version subcommand:%d\n", ret);
return;
}

mdelay(2); /* 短暂延时等待芯片响应 */

/* 读取响应块 */
ret = fg_mac_read_block(bq, bq->regs[BQ_FG_REG_MBA], buf, 11);
if (ret < 0) {
bq_err("Failed to read firmware version:%d\n", ret);
return;
}

/* 解析版本信息 */
bq_log("FW Ver:%04X, Build:%04X\n",
buf[2] << 8 | buf[3], buf[4] << 8 | buf[5]);
bq_log("Ztrack Ver:%04X\n", buf[7] << 8 | buf[8]);
}

Power Supply框架概述

Linux 的 power_supply 子系统是电池/电源管理的标准框架,它将电池抽象为一个 “power supply” 设备,通过统一的 sysfs 接口向用户空间暴露属性。

用户空间可以通过以下路径访问:

  • /sys/class/power_supply/BAT/ — 电池属性目录
  • cat /sys/class/power_supply/BAT/capacity — 读取电量百分比
  • cat /sys/class/power_supply/BAT/voltage_now — 读取电压

属性注册

1
2
3
4
5
6
7
8
9
10
11
static enum power_supply_property fg_props[] = {
POWER_SUPPLY_PROP_STATUS, /* 充电状态 */
POWER_SUPPLY_PROP_PRESENT, /* 电池是否存在 */
POWER_SUPPLY_PROP_VOLTAGE_NOW, /* 当前电压 */
POWER_SUPPLY_PROP_CURRENT_NOW, /* 当前电流 */
POWER_SUPPLY_PROP_CAPACITY, /* 剩余电量百分比 */
POWER_SUPPLY_PROP_CAPACITY_LEVEL, /* 电量级别 */
POWER_SUPPLY_PROP_TEMP, /* 温度 */
POWER_SUPPLY_PROP_CHARGE_FULL, /* 满充容量 */
POWER_SUPPLY_PROP_TECHNOLOGY, /* 电池技术类型 */
};

属性读取回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
static int fg_get_property(struct power_supply *psy, 
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq_fg_chip *bq = power_supply_get_drvdata(psy);
int ret;

switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = fg_get_batt_status(bq);
break;

case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = fg_read_volt(bq);
mutex_lock(&bq->data_lock);
if (ret >= 0)
bq->batt_volt = ret;
val->intval = bq->batt_volt * 1000; /* 转换为微伏 */
mutex_unlock(&bq->data_lock);
break;

case POWER_SUPPLY_PROP_CAPACITY:
if (bq->fake_soc >= 0) { /* 支持模拟电量(调试用) */
val->intval = bq->fake_soc;
break;
}
ret = fg_read_rsoc(bq);
mutex_lock(&bq->data_lock);
if (ret >= 0)
bq->batt_soc = ret;
else
bq->batt_soc = 0; /* 读失败时返回 0,避免上层异常 */
val->intval = bq->batt_soc;
mutex_unlock(&bq->data_lock);
break;

case POWER_SUPPLY_PROP_TEMP:
if (bq->fake_temp != -EINVAL) { /* 支持模拟温度 */
val->intval = bq->fake_temp;
break;
}
ret = fg_read_temperature(bq);
mutex_lock(&bq->data_lock);
if (ret > 0)
bq->batt_temp = ret;
else
bq->batt_temp = 0;
val->intval = bq->batt_temp;
mutex_unlock(&bq->data_lock);
break;

/* ... 其他属性处理 */

default:
return -EINVAL;
}
return 0;
}

Power Supply 注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int fg_psy_register(struct bq_fg_chip *bq)
{
struct power_supply_config fg_psy_cfg = {};

bq->fg_psy_d.name = "BAT"; /* 用户空间看到的名称 */
bq->fg_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
bq->fg_psy_d.properties = fg_props;
bq->fg_psy_d.num_properties = ARRAY_SIZE(fg_props);
bq->fg_psy_d.get_property = fg_get_property;
bq->fg_psy_d.set_property = fg_set_property; /* 支持写属性 */
bq->fg_psy_d.property_is_writeable = fg_prop_is_writeable;

fg_psy_cfg.drv_data = bq;
fg_psy_cfg.num_supplicants = 0;

bq->fg_psy = devm_power_supply_register(bq->dev,
&bq->fg_psy_d,
&fg_psy_cfg);
if (IS_ERR(bq->fg_psy)) {
bq_err("Failed to register fg_psy");
return PTR_ERR(bq->fg_psy);
}
return 0;
}

中断与状态监控

ALERT 中断处理

BQ 芯片的ALERT 引脚在电池状态变化时触发(如低电量、过温、充满等)。驱动中注册线程化中断处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static irqreturn_t fg_btp_irq_thread(int irq, void *dev_id)
{
struct bq_fg_chip *bq = dev_id;

mutex_lock(&bq->irq_complete);
bq->irq_waiting = true;

if (!bq->resume_completed) {
pr_info("IRQ triggered before device resume\n");
if (!bq->irq_disabled) {
disable_irq_nosync(irq);
bq->irq_disabled = true;
}
mutex_unlock(&bq->irq_complete);
return IRQ_HANDLED;
}
bq->irq_waiting = false;

/* 读取状态并更新 */
fg_read_status(bq);
fg_update_status(bq);

/* 通知 power_supply 框架属性变化 */
power_supply_changed(bq->fg_psy);

mutex_unlock(&bq->irq_complete);
return IRQ_HANDLED;
}

定时轮询工作队列

除了中断驱动,还启用了延时工作队列进行定时轮询(5 秒间隔),确保数据及时更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void fg_monitor_workfunc(struct work_struct *work)
{
struct bq_fg_chip *bq = container_of(work, struct bq_fg_chip,
monitor_work.work);

fg_update_status(bq);
fg_dump_registers(bq); /* 调试时打印寄存器 */

/* 上报电池信息变化 */
power_supply_changed(bq->fg_psy);

/* 重新调度,5 秒后再次执行 */
schedule_delayed_work(&bq->monitor_work, 5 * HZ);
}

probe() 中初始化:

1
2
INIT_DELAYED_WORK(&bq->monitor_work, fg_monitor_workfunc);
schedule_delayed_work(&bq->monitor_work, 5 * HZ);

remove() 中取消:

1
cancel_delayed_work_sync(&bq->monitor_work);

一些优势

  • 多芯片兼容,通过寄存器映射表映射,支持一个驱动兼容多个电池芯片
  • 数据主动上报,避免系统更新电池延迟