OpenHarmony HDF Input驱动模型分析与使用 精华
概述
输入设备是用户与计算机系统进行人机交互的主要装置之一,是用户与计算机或者其他设备通信的桥梁。常见的输入设备有键盘、鼠标、游戏杆、触摸屏等。本文档将介绍如何使用 Hi3516DV300 开发板完成基于 HDF_Input 模型的触摸屏(Touch Screen)器件驱动开发,从而使开发者快速入门。
硬件资源简介
Touch 设备与主机通讯一般采用 I2C 总线完成数据的交互,为了提高触屏数据的实时性,触屏 IC 都会提供中断支持。当有触屏事件发生时,会触发主机中断管脚完成一次中断响应。中断处理函数中主机通过 I2C 总线读取触屏 IC 寄存器完成一次数据采集。
Hi3516DV300 开发板套件所提供的触摸屏器件 IC 为 GT911,该器件采用标准 I2C 与主机通信,通过 6pin 软排线与主板连接。6pin 分布以及实物连接图如下图所示:
Input模型简介
Input模型概览
Input 驱动模型核心部分由设备管理层、公共驱动层、器件驱动层组成。器件产生的数据借助平台数据通道能力从内核传递到用户态,驱动模型通过配置文件适配不同器件及硬件平台,提高开发者的器件驱动开发效率。如下部分为模型各部分的说明:
(1)input 设备管理:为各类输入设备驱动提供 input 设备的注册、注销接口,同时统一管理 input 设备列表;
(2)input 平台驱动:指各类 input 设备的公共抽象驱动(例如触摸屏的公共驱动),负责对板级硬件进行初始化、硬件中断处理、向 manager 注册 input 设备等;
(3)input 器件驱动:指各器件厂家的差异化驱动,通过适配平台驱动预留的差异化接口,实现器件驱动开发量最小化;
(4)input 数据通道:提供一套通用的数据上报通道,各类别的 input 设备驱动均可用此通道上报 input 事件;
(5)input 配置解析:负责对 input 设备的板级配置及器件私有配置进行解析及管理。
Input模型工作流程解析
为了让开发者更清晰的了解 Input 模型工作流程,本节将对 input 模型加载的关键流程代码进行说明。
本章节为 Input 模型工作流程说明,开发者无需进行开发。
私有配置信息解析
示例代码路径:
./drivers/framework/model/input/driver/input_config_parser.c
根据 OSAL 提供的配置解析函数,可以将 hcs 文件中各字段含义进行解析,具体请参考 input_config_parser.c 中各函数的实现。如果提供的模板不能满足需求,在 hcs 文件中添加相应信息后,需要根据添加的字段开发相应的解析函数。
static int32_t ParseAttr(struct DeviceResourceIface *parser, const struct DeviceResourceNode *attrNode, BoardAttrCfg *attr)
{
int32_t ret;
// 获取inputType字段信息,保存在BoardAttrCfg结构体中
ret = parser->GetUint8(attrNode, "inputType", &attr->devType, 0);
CHECK_PARSER_RET(ret, "GetUint8");
...
return HDF_SUCCESS;
}
管理驱动层初始化及注册驱动至HDF框架
示例代码路径:
./drivers/framework/model/input/driver/hdf_input_device_manager.c
static int32_t HdfInputManagerInit(struct HdfDeviceObject *device)
{
/* 分配内存给manager,manager中将存放所有input设备 */
g_inputManager = InputManagerInstance();
...
}
struct HdfDriverEntry g_hdfInputEntry = {
.moduleVersion = 1,
.moduleName = "HDF_INPUT_MANAGER",
.Bind = HdfInputManagerBind,
.Init = HdfInputManagerInit,
.Release = HdfInputManagerRelease,
};
HDF_INIT(g_hdfInputEntry); //驱动注册入口
公共驱动层初始化及注册驱动至HDF框架
示例代码路径:
./drivers/framework/model/input/driver/hdf_touch.c
static int32_t HdfTouchDriverProbe(struct HdfDeviceObject *device)
{
...
/* 板级信息结构体内存申请及hcs配置信息解析 */
boardCfg = BoardConfigInstance(device);
...
/* 公共驱动结构体内存申请 */
touchDriver = TouchDriverInstance();
...
/* 依据解析出的板级信息进行公共资源初始化,如IIC初始化 */
ret = TouchDriverInit(touchDriver, boardCfg);
if (ret == HDF_SUCCESS) {
...
/* 添加驱动至公共驱动层驱动管理链表,当设备与驱动进行绑定时使用该链表进行查询 */
AddTouchDriver(touchDriver);
...
}
...
}
struct HdfDriverEntry g_hdfTouchEntry = {
.moduleVersion = 1,
.moduleName = "HDF_TOUCH",
.Bind = HdfTouchDriverBind,
.Init = HdfTouchDriverProbe,
.Release = HdfTouchDriverRelease,
};
HDF_INIT(g_hdfTouchEntry); //驱动注册入口
器件驱动层初始化及注册驱动至HDF框架
具体请参考适配器件私有驱动器件层驱动初始化及注册驱动至 HDF 框架部分。
具体调用逻辑串联函数
Input 模型管理层驱动 init 函数初始化了设备管理链表,公共驱动层初始化函数完成了相关结构体的内存申请。器件驱动相关信息通过 RegisterChipDevice 函数对公共驱动层相关结构体进行信息填充,同时完成了相关硬件信息的初始化(如中断注册等),绑定设备与驱动组成 inputDev 通过 RegisterInputDevice 函数向驱动管理层进行注册,在 RegisterInputDevice 函数中主要实现了将 inputDev 向设备管理链表的添加等功能。如下所示为两个函数的实现部分:
函数具体实现代码位置
./drivers/framework/model/input/driver/hdf_touch.c
int32_t RegisterChipDevice(ChipDevice *chipDev)
{
…
/* 绑定设备与驱动,从而通过InputDeviceInstance函数创建inputDev */
DeviceBindDriver(chipDev);
…
/* 主要包含器件中断注册及中断处理函数,处理函数中有数据上报用户态的数据通道 */
ChipDriverInit(chipDev);
…
/* 申请内存实例化InputDev */
inputDev = InputDeviceInstance(chipDev);
…
/* 将InputDev设备注册至input驱动管理层 */
RegisterInputDevice(inputDev);
…
}
函数具体实现代码位置
./drivers/framework/model/input/driver/hdf_input_device_manager.c
int32_t RegisterInputDevice(InputDevice *inputDev)
{
…
/* 申请ID,该ID对于不同input设备唯一 */
ret = AllocDeviceID(inputDev);
…
/* 该函数包含了对hid类设备的特殊处理,对于触摸屏驱动,该函数无实质操作; */
CreateDeviceNode(inputDev);
/* 内核态数据传送至用户态需使用IOService能力,需要申请buffer */
AllocPackageBuffer(inputDev);
/* 将input设备添加进设备全局管理链表 */
AddInputDevice(inputDev);
···
}
TouchScreen器件驱动开发
基于 Input 模型适配一款触摸屏 IC 需要完成的具体工作见下。
配置设备描述信息
驱动注册到 HDF 框架所需要的设备驱动描述信息,如驱动是否加载以及加载次序等。
配置文件路径:
./drivers/adapter/khdf/linux/hcs/device_info/device_info.hcs
device_info.hcs 中的信息主要提供给 HDF 框架使用,包含了 Input 模型各层驱动注册到 HDF 框架所必需的信息,开发者无特殊场景需求无需改动。各驱动层私有配置信息通过“deviceMatchAttr”字段与 input_config.hcs 中的“match_attr”相关内容进行匹配。
配置文件中与 input 模块相关的内容如下所示
input :: host {
hostName = "input_host";
priority = 100;
device_input_manager :: device { // Input管理层设备描述信息
device0 :: deviceNode {
policy = 2; // 向内核用户态均发布服务
priority = 100; // input管理层驱动优先级默认为100
preload = 0; // 加载该驱动
permission = 0660; // 驱动创建设备节点权限
moduleName = "HDF_INPUT_MANAGER"; // 与驱动入口moduleName匹配
serviceName = "hdf_input_host"; // HDF框架生成的节点名
deviceMatchAttr = ""; // manager目前不需要私有配置,因此为空
}
}
device_hdf_touch :: device { // Input公共驱动层设备描述信息
device0 :: deviceNode {
policy = 2; // 向内核用户态均发布服务
priority = 120; // input公共驱动优先级默认为120
preload = 0; // 加载该驱动
permission = 0660; // 驱动创建设备节点权限
moduleName = "HDF_TOUCH"; // 与驱动入口的moduleName匹配
serviceName = "hdf_input_event1"; // HDF框架生成的节点名
deviceMatchAttr = "touch_device1"; // 与 “match_attr”字段一致
}
}
device_touch_chip :: device { // Input器件驱动层信息
device0 :: deviceNode {
policy = 0; // 向内核用户态均不发布服务
priority = 130; // input器件驱动优先级默认为130
preload = 0; // 加载该驱动
permission = 0660; // 驱动创建设备节点权限
moduleName = "HDF_TOUCH_GT911"; // 与moduleName匹配
serviceName = "hdf_touch_gt911_service"; // HDF框架节点名
deviceMatchAttr = "zsj_gt911_5p5"; // 与“match_attr”字段一致
}
}
}
该配置文件中需要重点关注的字段有:
“priority”决定驱动加载顺序;
“preload”决定驱动是否加载;
“moduleName ”需要与驱动注册入口处的“moduleName ”字段保持一致;
“serviceName ”HDF 框架依据该字段创建节点名;
“deviceMatchAttr ”需要与私有配置信息中的“match_attr”字段保持一致。
通过配置设备描述信息,使得 HDF 框架通过 moduleName 与注册至驱动入口的代码相匹配,保证了驱动的正常加载,通过 priority 字段保证了各驱动的加载顺序。
配置Touchscreen器件信息
器件私有信息包括上下电时序等,平台硬件信息包括器件连接主板的 GPIO 端口信息等。
配置文件路径:
./drivers/adapter/khdf/linux/hcs/input/input_config.hcs
input_config.hcs 中的信息由驱动代码进行读取解析,主要由公共驱动层的私有配置信息及器件驱动层的私有配置信息组成。文件中的配置包含板级硬件信息及器件私有配置信息,实际业务开发时,可根据具体需求增删及修改对应内容。
pinConfig {
rstGpio = 3; // 复位管脚连接主机芯片的3号管脚
intGpio = 4; // 中断管脚连接主机芯片的4号管脚
rstRegCfg = [0x112f0094, 0x400]; // 复位管脚配置信息
intRegCfg = [0x112f0098, 0x400]; // 中断管脚配置信息
}
powerConfig {
/* 0:unused 1:ldo 2:gpio 3:pmic */
vccType = 2; // GPIO供电
vccNum = 20; // gpio20
vccValue = 1800; // 电压幅值为1800mV
vciType = 1; // LDO供电
vciNum = 12; // ldo12
vciValue = 3300; // 电压幅值为3300mV
}
featureConfig {
capacitanceTest = 0; // 容值测试
gestureMode = 0; // 手势模式
gloverMode = 0; // 手套模式
coverMode = 0; // 皮套模式
chargerMode = 0; // 充电模式
knuckleMode = 0; // 指关节模式
}
}
chipConfig { // 器件私有信息配置
template touchChip { // 模板
match_attr = "";
chipName = "gt911"; // 触摸屏IC型号
vendorName = "zsj"; // 供应商
chipInfo = "AAAA11222";
/*1~4字符代表产品名,5~6字符代表IC型号,7~9字符代表模型型号*/
busType = 0; // 0代表I2C,1代表SPI
deviceAddr = 0x5D; // 器件IC通信地址
/* 1代表上升沿触发,2代表下降沿触发,4代表高电平触发,8代表低电平触发*/
irqFlag = 2;
maxSpeed = 400; // 最大通信速率为400Hz
chipVersion = 0; // 触摸屏IC版本号
powerSequence {
/* 上电时序的配置含义说明: [类型, 状态, 方向 , 延时]
<type> 0代表空,1代表vcc电源1.8V,2代表VCI电源3.3V,
3代表复位管脚,4代表中断管脚
<status> 0代表下电或拉低,1代表上电或拉高,2代表无操作
<dir> 0代表输入方向,1代表输出方向,2代表无操作
<delay> 代表延时多少毫秒, 例如20代表延时20ms
*/
powerOnSeq = [4, 0, 1, 0, // 中断管脚配置为输出,且进行拉低
3, 0, 1, 10, // 复位管脚配置为输出,且进行拉低,延时10ms
3, 1, 2, 60, // 复位管脚无操作,且进行拉高,延时60ms
4, 2, 0, 0]; // 中断管脚配置为输入
suspendSeq = [3, 0, 2, 10]; // 复位管脚无操作,且进行拉低,延时10ms
resumeSeq = [3, 1, 2, 10]; // 复位管脚无操作,且进行拉高,延时10ms
powerOffSeq = [3, 0, 2, 10, // 复位管脚无操作,且进行拉低,延时10ms
1, 0, 2, 20]; // 电源正极管脚无操作,且进行拉低,延时20ms
}
}
chip0 :: touchChip {
/* 与设备描述配置信息中器件私有配置信息的“match_attr”字段保持一致 */
match_attr = "zsj_gt911_5p5";
/* 产品名+模组编号+芯片编号的组合信息 用于给用户态区分当前器件 */
chipInfo = "ZIDN45100";
/* IC型号的版本 */
chipVersion = 0;
}
}
}
}
}
}
示例中“touchConfig”包含了“touch0”,"touch0"包含了“boardConfig”与“chipConfig”;“boardConfig”字段包含了 Hi3516DV300 板级硬件信息,“chipConfig”包含了触摸屏器件的私有信息,如果需要替换触摸屏器件,重新配置“chipConfig”对应的字段信息即可。同时产品可以配置多款触摸屏,示例中用“touch0”代表了套件中默认的触摸屏的硬件接口以及器件的配置信息,如产品需要配置副屏,可在与“touch0”并列的位置配置“touch1”的信息。
适配器件私有驱动
Input 模型对 Input 设备开发流程进行了抽象,开发者只需要适配器件驱动层,无需改动管理驱动层以及公共驱动层。
Input 模型由三层驱动组成,开发者适配一款全新触摸屏驱动只需要适配器件驱动层即可,重点实现差异化接口,本小节以代码示例的形式展示开发者需要重点完成的工作。
触摸屏器件差异化接口适配
示例代码路径
./drivers/framework/model/input/driver/touchscreen/touch_gt911.c
static struct TouchChipOps g_gt911ChipOps = { // 器件IC接口
.Init = ChipInit, // 初始化
.Detect = ChipDetect, // 器件检测
.Resume = ChipResume, // 唤醒
.Suspend = ChipSuspend, // 休眠
.DataHandle = ChipDataHandle, // 器件数据读取
.UpdateFirmware = UpdateFirmware, // 固件升级
};
/* 不同触摸屏厂家使用的IC不一样,对应的寄存器操作也不一样,因此器件驱动层代码重点适配差异化接口部分,如下示例代码展示了GT911的数据解析*/
static int32_t ChipDataHandle(ChipDevice *device)
{
...
/* GT911获取坐标之前需先读取状态寄存器 */
reg[0] = (GT_BUF_STATE_ADDR >> ONE_BYTE_OFFSET) & ONE_BYTE_MASK;
reg[1] = GT_BUF_STATE_ADDR & ONE_BYTE_MASK;
ret = InputI2cRead(i2cClient, reg, GT_ADDR_LEN, &touchStatus, 1);
if (ret < 0 || touchStatus == GT_EVENT_INVALID) {
return HDF_FAILURE;
}
...
/* 根据状态寄存器的值读取数据寄存器数据 */
reg[0] = (GT_X_LOW_BYTE_BASE >> ONE_BYTE_OFFSET) & ONE_BYTE_MASK;
reg[1] = GT_X_LOW_BYTE_BASE & ONE_BYTE_MASK;
pointNum = touchStatus & GT_FINGER_NUM_MASK;
if (pointNum == 0 || pointNum > MAX_SUPPORT_POINT) {
HDF_LOGE("%s: pointNum is invalid, %u", __func__, pointNum);
(void)ChipCleanBuffer(i2cClient);
OsalMutexUnlock(&device->driver->mutex);
return HDF_FAILURE;
}
frame->realPointNum = pointNum;
frame->definedEvent = TOUCH_DOWN;
(void)InputI2cRead(i2cClient, reg, GT_ADDR_LEN, buf, GT_POINT_SIZE * pointNum);
/* 对获取的数据进行解析 */
ParsePointData(device, frame, buf, pointNum);
...
}
static void ParsePointData(ChipDevice *device, FrameData *frame, uint8_t *buf, uint8_t pointNum)
{
...
/* 每个坐标值由两个字节组成,对获取的单字节数据进行拼接得到最终的坐标值 */
for (i = 0; i < pointNum; i++) {
frame->fingers[i].trackId = buf[GT_POINT_SIZE * i + GT_TRACK_ID];
frame->fingers[i].y = (buf[GT_POINT_SIZE * i + GT_X_LOW] & ONE_BYTE_MASK) | ((buf[GT_POINT_SIZE * i + GT_X_HIGH] & ONE_BYTE_MASK) << ONE_BYTE_OFFSET);
frame->fingers[i].x = (buf[GT_POINT_SIZE * i + GT_Y_LOW] & ONE_BYTE_MASK) | ((buf[GT_POINT_SIZE * i + GT_Y_HIGH] & ONE_BYTE_MASK) << ONE_BYTE_OFFSET);
/* 对解析出来的坐标值进行打印 */
HDF_LOGD("%s: x = %d, y = %d", __func__, frame->fingers[i].x,
frame->fingers[i].y);
}
}
器件层驱动初始化及注册驱动至HDF框架
示例代码路径
./drivers/framework/model/input/driver/touchscreen/touch_gt911.c
static int32_t HdfGoodixChipInit(struct HdfDeviceObject *device)
{
...
/* 器件配置结构体内存申请、配置信息解析及挂载 */
chipCfg = ChipConfigInstance(device);
...
/* 器件实例化 */
chipDev = ChipDeviceInstance();
...
/* 器件信息挂载及器件私有操作挂载 */
chipDev->chipCfg = chipCfg;
chipDev->ops = &g_gt911ChipOps;
...
/* 注册器件驱动至平台驱动 */
RegisterChipDevice(chipDev);
...
}
struct HdfDriverEntry g_touchGoodixChipEntry = {
.moduleVersion = 1,
/* 该moduleName与device_info.hcs文件中器件驱动层的moduleName信息相匹配*/
.moduleName = "HDF_TOUCH_GT911",
.Init = HdfGoodixChipInit, // 器件驱动初始化函数
};
HDF_INIT(g_touchGoodixChipEntry); // 注册器件驱动至HDF框架
器件私有驱动层主要实现了各器件厂商差异较大的部分,如器件休眠唤醒、数据解析以及固件升级等。
编译入口添加
编辑 Makefile 文件,添加本示例中的内容:
文件路径:
./drivers/adapter/khdf/linux/model/input/Makefile
添加内容如下:
obj-$(CONFIG_DRIVERS_HDF_TP_5P5_GT911) += \
$(INPUT_ROOT_DIR)/touchscreen/touch_gt911.o
其中 touch_gt911.o 为本示例中追加的内容
至此,基于 HDF 框架及 Input 模型的触摸屏驱动适配完成。
总结
本文梳理了 HDF_Input 模型工作流程,重点介绍了器件驱动适配,以 Hi3516dv300 开发板触屏为例进行了详细的代码说明,希望通过本文档您能初步掌握基于 HDF 框架的 Input 设备的开发步骤与流程。
hcs文件是哪个文件了,在openharmony哪个目录下