【FFH】BearPi-HM Micro 南向研究——GPIO驱动分析 原创 精华
【FFH】BearPi-HM Micro 南向研究——GPIO驱动分析
参考
概述
BearPi-HM Micro开发板板载高性能的工业级处理器STM32MP157芯片,为了使STM32MP1的GPIO驱动适配到HDF框架,厂家会对芯片GPIO的驱动与HDF框架的GPIO进行适配,也就是适配层到核心层的实现,然后核心层又会从核心层抽象出一系列接口到接口层,供开发者使用。通过这样用户就可以忽略芯片的差异,只需关注核心层和接口层,就可以实现驱动程序的开发。
在HDF框架中,GPIO的接口适配模式采用无服务模式,用于不需要在用户态提供API的设备类型,或者没有用户态和内核区分的OS系统,其关联方式是DevHandle直接指向设备对象内核态地址。
芯片管脚号计算规则
分析之前得先了解BearPi-HM Micro的GPIO引脚编号(gpio_num)规则。后面提到的许多函数都会出现gpio_num,看过led驱动例程,查询引脚图可以知道led是PA13,然后在代码中led的引脚编号是led_gpio_num=13,那么如果是PB13引脚编号应该是多少?
就去研究了一下这个引脚编号是如何得来的,最后得出BearPi-HM_Micro的引脚号的计算方法。其实就是按芯片原理图的引脚顺序一个一个排下去。
查看原理图,可以看到引脚号由P和从A开始的字母,定义为组,以及一个编号组成,即组内偏移,对应的引脚编号为
GPIOA 起始-终止:0 - 15
GPIOB 起始-终止:16 - 31
GPIOC 起始-终止: 32 - 47
GPIO号 = GPIO组索引 * 每组GPIO管脚数(16) + 组内偏移
例如:PG2 = 6*16+2=98 (7是G的字母序,从0开始)
当然具体得看芯片,例如
如图引脚号到PI11就结束了,那么下一个PZ0的引脚号就是接着PI11下一个,也就是140.
总结:引脚号其实是引脚从第一个数下来的编号,我们只需要知道它的命名规则,就如上面PX都有16个引脚,就可以计算PB,PC对应的起始编号,得到我们想要的gpio_num。
驱动开发步骤
GPIO模块适配的三个环节是配置属性文件,实例化驱动入口,以及实例化核心层接口函数。GPIO控制器分组管理所有管脚,相关参数会在属性文件中有所体现;驱动入口和接口函数的实例化环节是厂商驱动接入HDF的核心环节。
1. 配置属性文件:
在device_info.hcs文件中添加GPIO的deviceNode描述。deviceNode信息与驱动入口注册相关,器件属性值与核心层GpioCntlr成员的默认值或限制范围有密切关系。 本例只有一个GPIO控制器,如有多个器件信息,则需要在device_info文件增加deviceNode信息,以及在gpio_config文件中增加对应的器件属性。
【可选】添加gpio_config.hcs器件属性文件。也就是私有配置属性。私有配置属性如何获取和调用将在后面Stm32GpioReadDrs函数分析。相关配置信息可以在芯片手册寻找。
HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject中的property里面。
2. 实例化驱动入口
驱动开发首先需要实例化驱动入口,驱动入口必须为HdfDriverEntry(在 hdf_device_desc.h 中定义)类型的全局变量,且moduleName要和device_info.hcs中保持一致。HDF框架会将所有加载的驱动的HdfDriverEntry对象首地址汇总,形成一个类似数组的段地址空间,方便上层调用。 一般在加载驱动时HDF会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
步骤:
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
实例化Bind,Init,Release函数
因为gpio采用无服务模式,所以并不需要在Bind函数将服务接口绑定到HDF框架,Init函数主要用于初始化自定义结构体对象,初始化GpioCntlr成员,调用核心层GpioCntlrAdd函数。Release函数用于释放内存和删除控制器,该函数需要在驱动入口结构体中赋值给Release 接口,当HDF框架调用Init函数初始化驱动失败时,可以调用Release释放驱动资源。所有强制转换获取相应对象的操作前提是在Init函数中具备对应赋值的操作。
下列为HDF_STATUS相关状态的说明。
3.实例化GPIO控制器对象:
初始化GpioCntlr成员
GpioCntlr结构体定义如下
在Init函数已经对GpioCntlr进行了初始化,如下图,主要步骤就是先创建一个gpio控制器并分配内存,然后对控制器的成员进行赋值,最后调用核心层GpioCntlrAdd添加控制器。
对于GpioCntlr的实例化,主要提几点:
- cntlr.device初始化用于绑定GPIO控制器设备对象,为HdfDeviceObject类型。
- cntlr.priv,也就是我们的私有属性获取,上面在配置私有属性时已经提到私有属性保存在HdfDeviceObject中的property里面,而这一过程在厂家写的Stm32GpioReadDrs中实现。
结构体DeviceResourceIface提供用于获取有关设备资源配置树的信息的函数。
通过DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE)获取结构体句柄,然后就可以通过里面的函数来获取私有配置信息。
例如:ret = drsOps->GetUint32(node, “gpioRegBase”, &stm32gpio->gpioPhyBase, 0);就是将私有配置中的"gpioRegBase"以uint32的数据类型赋值给stm32gpio->gpioPhyBase,node是指示指向配置树节点的指针,通过node就可以定位到设备的属性树的root节点,从而读取到数据。然后最后一个参数是指示在操作失败时要填充到值所指向的内存。
实例化GpioCntlr成员GpioMethod。
上面的初始化还有一个操作stm32gpio->cntlr.ops = &g_GpioMethod;这个就是实现实例化GpioCntlr成员GpioMethod。
GpioMethod结构体在drivers/framework/support/platform/include/gpio_core.h中定义,而我们需要实例化这些接口函数。gpio_core.c 就是对硬件gpio控制器的抽象,通过gpio控制器可以配置具体的gpio电平、输入输出模式、中断等。
回到stm32mp1_gpio.c文件,厂家写的GpioMethod结构体定义g_GpioMethod如下,对gpio_core中的函数进行实例化。
厂家通过实例化上述函数,就可以实现core核心层与适配层的对接,通过调用core里面的函数就可以间接调用到厂家实现的对不同芯片的适配驱动函数。以上函数都可以在stm32mp1_gpio.c文件找到。
例如:
可以发现core核心层的函数其实就是调用控制器的GpioMethod中的函数,也就是最终会调用到厂家写的适配驱动函数。
通过适配层绑定后我们就可以调用核心层的函数来操控我们的gpio,而gpio_core的函数在同路径的gpio.if中又被封装了一层,简化了参数,不需要传入GpioCntlr这个控制器,直接传入gpio编号就可以进行控制。故如果我们想要实现gpio的一些功能,可以引用gpio_if.h这个头文件,在把路径依赖加上,就可以实现对gpio的操作。
例如:gpio.if.c中的GpioRead函数,对gpio_core.c中的GpioCntlrRead进行封装,只需要传入gpio编号及val的指针地址,就可以实现对gpio的读取操作。
在LED的例程中也可以发现编写的驱动代码对led的gpio管脚操作也是引用了gpio_if.h头文件,并且调用其中的函数来操作gpio,想了解gpio的使用可以再仔细查看代码看看它是怎么操作的,gpio的操作流程如下:
stm32mp1_gpio适配层驱动代码分析
下面是对厂家写的适配层的代码的分析。
先看看stm32mp1_gpio.h文件厂家自定义的结构体类型
stm32mp1_gpio.c驱动函数分析
前面是对组号引脚号处理函数,上面已经分析了gpio_num和组号引脚号的关系,下面这些函数是他们之间的转换关系。
厂家实现的驱动函数功能说明
总结
GPIO的操作函数都是由gpio_if提供,而gpio_if只是对gpio_core的一层封装,屏蔽了gpio控制器。gpio_core 中的函数就是调用控制器的各种方法,而厂家需要实现适配层的驱动编写stm32mp1_gpio,以此来实例化核心层驱动。
通过分析GPIO驱动对HDF框架有了更深入的理解,也了解了不同芯片是如何适配到HDF框架的,对驱动的开发流程也有了很大帮助。后续会尝试E53串口驱动的研究,厂家给的驱动函数并没有把E53的串口驱动加上,研究了挺久也没驱动起来,希望有成功使用BearPi-HM MicroE53串口的能够交流交流。
以上有些内容属于个人理解,可能有不对的地方,欢迎交流探讨。
期待大佬更多文章
超级详细透彻,自己背地里研究透彻后才发现有这样一篇宝藏文章。
不错不错,很详细