OpenHarmony HDF 平台驱动框架介绍——I2C模块适配
I2C模块适配
I2C模块适配的核心环节是I2cCntlr对象的创建、初始化及注册。
I2C采用的是统一服务模式,需要一个设备服务来作为I2C模块的管理器,统一处理外部访问。
1、 device_info.hcs配置
device_i2c :: device {
device0 :: deviceNode {
policy = 2;
priority = 50;
permission = 0644;
moduleName = "HDF_PLATFORM_I2C_MANAGER";
serviceName = "HDF_PLATFORM_I2C_MANAGER";
deviceMatchAttr = "hdf_platform_i2c_manager";
}
device1 :: deviceNode {
policy = 0;
priority = 55;
permission = 0644;
moduleName = "hi35xx_i2c_driver";
serviceName = "HI35XX_I2C_DRIVER";
deviceMatchAttr = "hisilicon_hi35xx_i2c";
}
}
首先第一个设备节点必须是I2C管理器,其各项参数必须如上一样设置。其中:
policy:这个同UART,具体配置为1或2取决于是否对用户态可见
moduleName: 固定为HDF_PLATFORM_I2C_MANAGER
serviceName: 固定为HDF_PLATFORM_I2C_MANAGER
deviceMatchAttr: 没有使用,可忽略
而从第二个设备节点开始,配置具体I2C控制器信息。这里device1 并不表示某一路I2C控制器,而是一个资源性质设备,用于描述一类I2C控制器的信息。
服务policy等于0,不需要发布服务;
moduleName用于指定驱动成语,需要与期望的驱动Entry中的moduleName一致;
ServiceName不需要使用,可忽略;
deviceMatchAttr用于配置控制器私有数据,要与i2c_config.hcs中对应控制器保持
同样,具体的控制器信息在i2c_config.hcs中,由具体产品在hdf.hcs中导入。
2、i2c_config.hcs中可配置多个控制器信息
root {
platform {
i2c_config {
match_attr = "hisilicon_hi35xx_i2c";
template i2c_controller {
bus = 0;
reg_pbase = 0x120b0000;
reg_size = 0xd1;
irq = 0;
freq = 400000;
clk = 50000000;
}
controller_0x120b0000 :: i2c_controller {
bus = 0;
}
controller_0x120b1000 :: i2c_controller {
bus = 1;
reg_pbase = 0x120b1000;
}
}
}
}
可以看到,这里配置了多个控制器的信息,而这些信息将在驱动程序员进行逐一解析、处理,生成对应的I2cCntlr对象。
3、I2C管理器服务的驱动由核心层实现
struct HdfDriverEntry g_i2cManagerEntry = {
.moduleVersion = 1,
.Bind = I2cManagerBind,
.Init = I2cManagerInit,
.Release = I2cManagerRelease,
.moduleName = "HDF_PLATFORM_I2C_MANAGER",
};
HDF_INIT(g_i2cManagerEntry);
驱动适配人员无需关注这个驱动的实现,有兴趣的可以去阅读源码。
drivers/framework/support/platform/src/i2c_core.c
4、适配驱动Entry只需要实现Init方法
struct HdfDriverEntry g_i2cDriverEntry = {
.moduleVersion = 1,
.Init = Hi35xxI2cInit,
.Release = Hi35xxI2cRelease,
.moduleName = "hi35xx_i2c_driver",
};
HDF_INIT(g_i2cDriverEntry);
static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device)
{
int32_t ret;
const struct DeviceResourceNode *childNode = NULL;
ret = HDF_SUCCESS;
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = Hi35xxI2cParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) {
break;
}
}
return ret;
}
在Init方法中会将i2c_config.hcs中定义的每一个节点取出,分别进行初始化
5、Hi35xxI2cParseAndInit里面很自由
static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{
int32_t ret;
struct Hi35xxI2cCntlr *hi35xx = NULL;
(void)device;
hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx));
ret = Hi35xxI2cReadDrs(hi35xx, node);
hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize);
Hi35xxI2cCntlrInit(hi35xx);
hi35xx->cntlr.priv = (void *)node;
hi35xx->cntlr.busId = hi35xx->bus;
hi35xx->cntlr.ops = &g_method;
hi35xx->cntlr.lockOps = &g_lockOps;
(void)OsalSpinInit(&hi35xx->spin);
ret = I2cCntlrAdd(&hi35xx->cntlr);
....
}
Hi35xxI2cParseAndInit会处理一个具体的I2C控制器的初始化工作,包括:
1)I2cCntlr对象的分配,Hi35xxI2cCntlr头部内嵌了一个I2cCntlr,这是一种继承。
struct Hi35xxI2cCntlr {
struct I2cCntlr cntlr;
OsalSpinlock spin;
volatile unsigned char *regBase;
uint16_t regSize;
int16_t bus;
uint32_t clk;
uint32_t freq;
uint32_t irq;
uint32_t regBasePhy;
};
2)在Hi35xxI2cReadDrs中完成节点属性的读取,并填充进hi35xx对象
3)映射寄存器基地址OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize);
4)调用Hi35xxI2cCntlrInit完成控制器的初始化
5)I2cCntlr对象的填充及调用I2cCntlrAdd注册
这里唯一形式化约束是必须创建一个合法的I2cCntlr并调用I2cCntlrAdd注册到核心层。其他Hi35xx开头的函数都是驱动适配者自由封装的,并无形式化要求。
总结
采用统一服务模式,其优点是不用为每一个I2C控制器定义一个设备节点,控制器对象的创建和注册比较自由;而缺点是要创建一个I2C管理器服务,以及一个虚拟的资源描述设备。
说明
I2C模块适配涉及到的代码示例片段来自device/hisilicon/drivers/i2c/