摘要
本文基于Hi3516DV300开发板进行分析,此开发板使用的是海思的一款基于双核Cortex-A7的soc,提供了3路SPI总线(但是开发板上没有对外提供SPI接口,需要使用的话就需要飞线)。由于工作需要使用SPI连接了一个外设芯片进行调试,在调试过程中学习了Openharmony在L2上SPI实现的原理
DT和HDF
在分析SPI总线实现原理之前,先了解一下linux系统以及openharmony下对硬件描述最为关键的几个概念
linux系统
- DT(Device Tree),设备树是描述硬件的一种数据结构,来源于OF(OpenFirmware)。当前广泛应用于linux系统中。
- DTS(Device Tree Source),DT的源文件,也可以称为易于人理解的硬件配置文件
- DTB(Device Tree Blob),DTS被编译成二进制形式,方便linux内核中读取
- DTC(Device Tree Compiler),将DTS编译成DTB的工具,类似gcc,也提供反编译功能
openharmony
- HDF(Hardware Driver Foundation)是一种驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。(来自openharmony官网文档)
- HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码,内容以Key-Value为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。(来自openharmony官网文档)
- HCB(HDF Configuration Binary),HCS被编译成二进制形式,方便Openharmony驱动框架读取
- HC-GEN(HDF Configuration Generator),将HCS编译成HCB的编译工具
对比说明
从前面两节的几个概念描述看,Openharmony中实现了一套与linux系统下类似的硬件描述结构
linux | openharmony | 说明 |
---|---|---|
DT | HDF | 硬件描述的结构 |
DTS | HCS | 硬件结构描述的源码 |
DTB | HCB | 硬件结构描述的源码编译后的二进制文件 |
DTC | HC-GEN | 将硬件结构描述源码转换为二进制文件的编译工具 |
对比来看两者的功能基本上类似,Openharmony重新实现一套机制的好处是将Openharmony下的L0/L1/L2系统(分别对应的是liteos-m/liteos-a/linux)统一起来。但是个人理解其实还有一种实现方式,就是将linux下的dt移植到openharmony的L0/L1系统,而L2完全使用linux默认的DT,这样可能对于开发人员的学习成本会低很多(只是个人理解)。
然而还有一个问题,在分析SPI实现原理的过程中发现,openharmony的L2系统中其实DT和HDF是共存的,并不是替换的关系(只是在L0/L1上是独有的)。并且两边都有对SPI的配置及解析,两者是怎么配合的呢,下面一步步来分析一下。
linux下SPI总线的配置及实现
从前面描述看SPI总线的配置也是在DTS中,对于Hi3568DV300开发板来说对应的dts文件路径为:
linux-5.10/arch/arm/boot/dts/hi3516dv300.dtsi
linux-5.10/arch/arm/boot/dts/hi3516dv300-demb.dts
但是在源码中发现并没有这两个文件,而是在编译好版本之后在输出目录out/KERNEL_OBJ/kernel/src_tmp/
可以找到,原因就是这两个文件不是linux自带的,而是在编译的时候给内核打补丁出现的,参见patch文件kernel/linux/patches/linux-5.10/hi3516dv300_patch/hi3516dv300.patch
dtsi中SPI配置如下(三路SPI配置类似):
spi_bus1: spi@120c1000 {
compatible = "arm,pl022", "arm,primecell";
arm,primecell-periphid = <0x00800022>;
reg = <0x120c1000 0x1000>, <0x12030000 0x4>;
interrupts = <0 69 4>;
clocks = <&clock HI3516DV300_SPI1_CLK>;
clock-names = "apb_pclk";
#address-cells = <1>;
#size-cells = <0>;
num-cs = <2>;
hisi,spi_cs_sb = <2>;
hisi,spi_cs_mask_bit = <0x4>;//0100
#ifdef CONFIG_HIEDMACV310
dmas = <&hiedmacv310_0 29 29>, <&hiedmacv310_0 28 28>;
dma-names = "tx","rx";
#endif
status = "disabled";
};
而在dts文件中将status改为OK,如下
&spi_bus1{
status = "okay";
num-cs = <2>;
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
pl022,interface = <0>;
pl022,com-mode = <1>;
spi-max-frequency = <25000000>;
};
spidev@1 {
compatible = "rohm,dh2228fv";
reg = <1>;
pl022,interface = <0>;
pl022,com-mode = <0>;
spi-max-frequency = <25000000>;
};
};
在启动时就会将spi_dev挂到设备树中。
从配置的compatible
字段看,SPI总线驱动使用的是arm内核一种公共的SPI驱动pl022,驱动文件路径为kernel/linux/linux-5.10/drivers/spi/spi-pl022.c
,主要接口配置如下:
static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
{
......
master->cleanup = pl022_cleanup;
master->setup = pl022_setup;
master->auto_runtime_pm = true;
master->transfer_one_message = pl022_transfer_one_message;
master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
......
}
而linux系统对SPI总线提供了一个统一的接口,放在kernel/linux/linux-5.10/drivers/spi/spi.c
中。
Openharmony下SPI总线的配置及实现
配置
HDF驱动开发方法可以先看下官网文档
HCS配置文件路径:
vendor/<company>/<product>/hdf_config/khdf/device_info/device_info.hcs
vendor/<company>/<product>/hdf_config/khdf/platform/hi35xx_spi_config.hcs
在HDF下SPI在platform-host下,如下
platform :: host {
hostName = "platform_host";
priority = 50;
......
device_spi :: device {
......
device1 :: deviceNode {
policy = 1;
priority = 60;
permission = 0644;
moduleName = "HDF_PLATFORM_SPI";
serviceName = "HDF_PLATFORM_SPI_1";
deviceMatchAttr = "hisilicon_hi35xx_spi_1";
}
......
}
......
}
SPI私有配置
root {
platform {
spi_config {
template spi_controller {
serviceName = "";
match_attr = "";
busNum = 0;
numCs = 0;
}
controller_0x120c0000 :: spi_controller {
busNum = 0;
numCs = 1;
match_attr = "hisilicon_hi35xx_spi_0";
}
controller_0x120c1000 :: spi_controller {
match_attr = "hisilicon_hi35xx_spi_1";
busNum = 1;
numCs = 2;
}
controller_0x120c2000 :: spi_controller {
match_attr = "hisilicon_hi35xx_spi_2";
busNum = 2;
numCs = 1;
}
}
}
}
HDF下SPI总线驱动代码
代码路径为drivers/adapter/khdf/linux/platform/spi/hi35xx_spi_adapter.c
提供了下面几个接口,并将spi_driver加入到HDF框架下面
struct SpiCntlrMethod g_method = {
.Transfer = SpiAdatperTransfer,
.SetCfg = SpiAdatperSetCfg,
.GetCfg = SpiAdatperGetCfg,
.Open = SpiAdatperOpen,
.Close = SpiAdatperClose,
};
-
open
接口,通过linux的接口bus_for_each_dev
获取挂在设备树中的SPI结构体地址并赋给struct SpiCntlr
的指针变量static int32_t SpiAdatperOpen(struct SpiCntlr *cntlr) { ...... ret = bus_for_each_dev(&spi_bus_type, NULL, (void *)cntlr, SpiFindDeviceFromBus); ...... return HDF_SUCCESS; }
当找到对应设备之后,会调用回调接口
SpiFindDeviceFromBus
,如果是第一次调用需要创建一个HDF的spi_dev挂在HDF下,并初始化配置信息。static int32_t SpiFindDeviceFromBus(struct device *dev, void *para) { ...... if (spidev->master->bus_num == cntlr->busNum && spidev->chip_select == cntlr->curCs) { spi = SpiFindDeviceByCsNum(cntlr, cntlr->curCs); if (spi == NULL) { spi = SpiDevCreat(cntlr); } ...... SpiDevInit(spi, spidev); return SPI_DEV_FIND_SUCCESS; } else { put_device(&spidev->dev); return SPI_DEV_NEED_FIND_NEXT; } }
-
setcfg
接口,先将用户的配置信息转换成linux下spi配置格式,然后调用linux下spi统一接口spi_setup
static int32_t SpiAdatperSetCfg(struct SpiCntlr *cntlr, struct SpiCfg *cfg) { ...... spidev->mode = HdfSpiModeToLinuxMode(cfg->mode); ret = spi_setup(spidev); ...... }
文件
kernel/linux/linux-5.10/drivers/spi/spi.c
中spi_setup
接口调用具体的spi总线驱动的setup
接口int spi_setup(struct spi_device *spi) { ...... if (spi->controller->setup) status = spi->controller->setup(spi); ...... return status; }
前面信息可知hi3516dv300使用的是
spi-pl022.c
,具体的功能实现接口为pl022_setup
接口,就是对spi总线寄存器的操作。 -
transfer
接口,先将用户的msg转换成transfer,再调用linux提供的统一接口spi_sync
注意:Openharmony的HDF中stransfer/msg的概念与linux下正好相反。LInux下一个spi_message包含一组transfer,一个tansfer表示一次传输;HDF用一个SpiMsg表示一次传输,多次传输组合用SpiMsg数组。
static int32_t SpiAdatperTransfer(struct SpiCntlr *cntlr, struct SpiMsg *msg, uint32_t count) { ...... spi_message_init(&xfer); for (i = 0; i < count; i++) { // 将用户的message转换成transfer spi_message_add_tail(&transfer[i], &xfer); } ret = spi_sync(dev->priv, &xfer); kfree(transfer); return ret; }
文件
kernel/linux/linux-5.10/drivers/spi/spi.c
中spi_sync
接口最终调用具体总线驱动的接口``int spi_sync(struct spi_device *spi, struct spi_message *message) { int ret; mutex_lock(&spi->controller->bus_lock_mutex); ret = __spi_sync(spi, message); mutex_unlock(&spi->controller->bus_lock_mutex); return ret; } static int __spi_sync(struct spi_device *spi, struct spi_message *message) { ...... __spi_pump_messages(ctlr, false); ...... } static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread) { ...... ret = ctlr->transfer_one_message(ctlr, msg); }
前面信息可知hi3516dv300使用的是
spi-pl022.c
,具体的功能实现接口为pl022_transfer_one_message
接口,就是对spi总线寄存器的操作。
Openharmony下SPI总线的使用
从前面分析来看,Openharmony下SPI总线驱动最终调用的还是linux下对应的具体的驱动实现。这样HDF与DT之间就建立起了关联。而在Openharmony下SPI总线又给其他驱动提供了统一的驱动接口,路径为:
drivers/framework/support/platform/src/spi/spi_if.c
drivers/framework/support/platform/src/spi/spi_core.c
从这两个源文件分析,实际就是从HDF下查找HDF_PLATFORM_SPI
,从上一节分析看,这个就是drivers/adapter/khdf/linux/platform/spi/hi35xx_spi_adapter.c
的实现。
这样调用顺序就是
总结
在Openharmony的L2下使用SPI总线,其实最底层还是调用的linux下具体的SPI总线驱动代码。而对于SPI-slave设备使用SPI总线通信时:
- 需要确认linux下对应的DTS的配置
- 需要确认HDF下对应的HCS的配置
- 调用Openharmony提供的统一接口(文件
spi-if.c
)
这样后续移植代码到Openharmony的L1/L0时就比较简单了。
网友评论