• Linux内核之SPI协议


    SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行的行业标准,但是并没有像I2C那样有标准文档,它还有主从、可片选的特性。

    SPI
    图源自Serial Peripheral Interface-wikipedia

    时序图
    SPI时序图
    放个经典老图,来源未知。相位和极性决定了采样点,主从采样点一致时数据正确,不一致时会导致数据错误但硬件自己其实无法察觉。

    作为一种事实标准,SPI还衍生出众多的总线协议:eSPI DSPI QSPI QPI,以及仅有MOSI\SCLK两根线的单工SPI,甚至有的还有DTR双边采样特性。

    应用场景:

    • 读写寄存器
    • 中型数据传输(速率一般为1MHz~20MHz)
    • EEPROM、FLASH等储存器件
    • 一些固件升级

    Linux

    文档:https://www.kernel.org/doc/html/v5.14/spi/index.html
    源码:drivers/spi
    image

    分层没有I2C那样好,主打的就是一堆,主打能用就行。一个目录放满...

    头文件 include/linux/spi/

    公共的结构体位于 include/linux/spi/spi.h

    spi_controller 结构体(SPI控制器实例)

    关键的收发函数指针都在这里。这个结构体会由 spi-xxxx.c 控制器驱动进行初始化。

    spi.c里实现了transfer_one_messagetransfer接口
    spi_transfer_one_message
    spi_transfer_one_message

    一般SoC厂商会实现:

    spi_driver结构体

    注释里写着:主机端的协议驱动

    struct spi_driver - Host side "protocol" driver`

    • id_table,就是一个识别而已。里面是name和一个私有64位数据。可以分别通过 spi.c的spi_get_device_idspi_get_device_match_data函数获取
    • probe/remove作用是将driver绑定/解绑定到 spi device
    • device_driver的作用有点大,这里的SPI作为其他驱动的底层支持,例如我们有基于SPI的RTC、PHY等等器件,那么就会用到这个。

    spi_device结构体(从机设备实例)

    位于 include/linux/spi
    注释很清楚了,就不解释了

    spi.c(SPI总线驱动)

    路径 drivers/spi/spi.c

    虽然代码还不错,但是一个源码文件5000行不是梦...呕~,如果没有搜索功能,找个probe都要找半天

    首先是初始化,postcore_initcall(spi_init); 该函数在初始化时就被调用。

    初始化,可以查看 include/linux/init.h和段定义include/asm-generic/vmlinux.lds.h,数字越小优先级越高。postcore_initcall排第四位,优先级非常高。

    spi_init的主要操作:分配内存<=32b,总线注册, class类注册,可选功能:CONFIG_SPI_SLAVE从机注册,CONFIG_OF_DYNAMIC设备树,CONFIG_ACPI注册即ACPI表
    spi_init图

    注册用到的结构体:

    • spi_dev_groups 是sysfs组,里面一车面包人(很多sysfs读写接口,从56行到311行都是: drivers/spi/spi.c#L56-L311
      image

    • spi_match_device 很简单,就是提供匹配函数,spidev.c后面会用到。其实就是override-设备树-ACPI-id_table这样优先级匹配而已。

    • spi_uevent 是uevent事件回调,uevent提供了“用户空间通知”的功能实现,当内核中有Kobject的增加/删除/修改等动作时,会通知用户空间

    • spi_shutdown 是关闭回调,这里只是调用dev->driver->shutdown函数而已。

    • spi_probe/spi_remove
      其中spi_probe是这样的:

    1. 从设备树节点获取并配置:时钟、irq中断、
    2. dev_pm_domain_attach 附加到电源管理域(power manager domain)上
    3. 调用struct spi_device->struct device dev->struct device_driver driver的probe函数
      spi_probe图

    可选功能注册的结构体是

    • spi_controller_release 就一个kfree释放内存
    • spi_master_groupsspi_slave_groups就是一车面包人(sysfs组

    其他的就都通过EXPORT_SYMBOL_GPL宏向外暴露函数API了,多达40个...

    SPI数据传输-内核态API

    暴露API:EXPORT_SYMBOL_GPL(spi_sync);
    流程:spi_sync==互斥锁=>__spi_sync
    作用:将spi_message

    另外还有一个异步API spi_async,里面用了队列,队列空则使用同步,队列不空则调用__spi_async将内容通过spi_controller->transfer(spi, message)发送。

    spi-xxx.c 片上SPI控制器驱动-原厂

    如赛灵思的SOC会使用drivers/spi/spi-xilinx.c#platform_driver 的probe来初始化spi_controller结构体以及SoC片上SPI控制器。如下图440行就是SPI控制器采用bitbang方式收发函数的指针赋值。
    spi-xilinx.c

    spidev.c (提供用户态支持的SPIDEV内核驱动)

    路径 drivers/spi/spidev.c

    image

    它是SPI userspace API的内核驱动支持。

    入口函数,注册了 字符设备、Class、spi_driver
    image

    注册字符设备 chrdev

    其字符设备的fops如下

    定义了 open/close/read/write/64位ioctl/32位ioctl/llseek 这几个常见接口。

    • open/close写法类似,这里只截图 spidev_open
      image

    • read/write写法类似,这里只截图了spidev_write函数内容
      image
      这里将用户态数据复制到dev的私有数据spidev_data里。然后调用spidev_sync_write将私有数据写入硬件。
      spidev_sync_write队列>spidev_sync互斥锁>spidev_sync_unlocked===>位于spi.c的内核态驱动spi_sync

    image

    • ioctl里面主要是分支调用 spi_setup 配置参数
      image
      其中调用32位ioctl时,SPI_IOC_MAGIC _IOC_NR _IOC_WRITE 这三种会走32位兼容性操作,其他都会转给64位ioctl处理。
      image

    • llseek 指向 no_llseek,即没有!

    注册Class name="spidev"

    其Class的name成员值为"spidev"

    注册driver

    其中

    • name="spidev"
    • dts/acpi匹配
      image
    • id_table
      image
    • probe/remove
      这里的probe内容稍微多点

    为节省篇幅,容错处理我用/*...*/代替了。
    这里probe主要就是

    1. 获取spi总线bus_type结构体的.match成员函数指针,然后调用match函数。(override-设备树-ACPI-id_tables这样的优先级顺序)
    2. 给结构体分配内存
    3. 填充结构体
    4. 创建字符设备
    5. 配置driver的私有数据成员

    应用层

    内核给应用层准备了一个 SPI userspace API,底层就是spidev.c,只需要在内核menuconfig配置开关,然后open /dev/spidevB.C 即可。如果需要全双工,可以看看 tools/spi/spidev_fdx.c 这个例子。

    image

    另外有个很重要的事情要记得,ioctl使用的结构体,它的tx_buf/rx_buf只有64位长,而不像有些RTOS那样是个指针,所以Linux内核把形式参数做成了接收spi_ioc_transfer数组。

    总结:

    • 总线驱动 spi.c: 在内核postcore_init阶段注册bus_type{probe\match\sysfs\uevent等},这里的probe就会最后调用到SoC片上SPI控制器驱动的probe
    • 控制器驱动 spi_controller 由各SoC厂商提供 spi-xxx.c 的 platform_driver初始化片上的SPI控制器。
    • 主机驱动 spi_driver
    • 从机驱动(板载外设,自己写)
    • 内核态 spi_device
    • 用户态 spidev

    看得很折磨...


    __EOF__

  • 本文作者: YuCloud
  • 本文链接: https://www.cnblogs.com/yucloud/p/18162832/linux_kernel_driver_spi
  • 关于博主: love to create
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: Click【推荐】if the article helpful, and Welcome to discuss
  • 相关阅读:
    ​为什么流利的英语对于机器学习比数学或编程更重要
    工作流Camunda入门demo
    Ps:套索工具
    Google Earth Engine(GEE)——用geometry点来改变选的影像范围,并进行加载的APP
    食品接触材料中的玻璃制品需要做哪些检测认证?
    浅学Go下的ssti
    YOLO V5 使用
    web3 React Dapp书写订单 买入/取消操作
    【Java系列】SpringBoot 集成MongoDB 详细介绍
    使用阿里云镜像加速pip命令的包安装
  • 原文地址:https://www.cnblogs.com/yucloud/p/18162832/Linux_kernel_driver_SPI