美文网首页
[openharmony]L2系统SPI的实现原理

[openharmony]L2系统SPI的实现原理

作者: itsenlin | 来源:发表于2022-04-13 16:14 被阅读0次

    摘要

    本文基于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.cspi_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.cspi_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的实现。
    这样调用顺序就是

    图片1.png

    总结

    在Openharmony的L2下使用SPI总线,其实最底层还是调用的linux下具体的SPI总线驱动代码。而对于SPI-slave设备使用SPI总线通信时:

    • 需要确认linux下对应的DTS的配置
    • 需要确认HDF下对应的HCS的配置
    • 调用Openharmony提供的统一接口(文件spi-if.c

    这样后续移植代码到Openharmony的L1/L0时就比较简单了。

    相关文章

      网友评论

          本文标题:[openharmony]L2系统SPI的实现原理

          本文链接:https://www.haomeiwen.com/subject/pdzosrtx.html