一、SPI控制器驱动
SPI控制器驱动通常由硬件设备制造商提供,他们为不同的操作系统(如Linux、Windows、RTOS等)编写不同的驱动程序。驱动程序的主要功能是管理SPI控制器,向外部设备发送和接收数据,并提供对SPI接口的访问。
(资料图片仅供参考)
SPI控制器驱动需要实现以下功能:
初始化SPI控制器:驱动程序需要初始化SPI控制器,设置通信速率、数据位宽、时钟极性和相位等参数。控制SPI接口:驱动程序需要控制SPI接口,包括选择片选信号、发送和接收数据、等待传输完成等。支持多线程:驱动程序需要支持多线程访问SPI接口,以便多个应用程序可以同时使用SPI接口进行通信。但是SPI 控制器驱动本身不会直接提供接口给应用操作。基于linux kernel 5.15
二、SPI 控制器驱动注册
要记住spi控制器是芯片资源,cpu能直接寻址,因此使用的总线是platform总线。而spi设备是外部设备,是使用spi_bus_type。
SPI 控制器驱动中涉及spi_master和spi_bitbang两个对象,从spi_bitbang可以找到spi_master。
以上两个对象设置后之后,就可以使用以下接口注册,会完成大量初始化操作。
使用bitbang注册接口
int spi_bitbang_start(struct spi_bitbang *bitbang) int spi_bitbang_init(struct spi_bitbang *bitbang) int spi_register_master(struct spi_controller *ctlr)
什么是bitbang接口,暂时不知道,只知道是比较新的注册方式。
当使用这种接口的时候:
spi_bitbang_init()做的事情
spi_master不能提供transfer和transfer_one_message
方法。如果使用硬件spi提供的cs片选,那么需要spi_bitbang提供chipselect方法,同时会spi_master→set_cs使用spi_bitbang_set_cs里面去调用spi_bitbang提供chipselect方法。spi_master->use_gpio_descriptors表示使用gpio cs而不是硬件csspi_bitbang→txrx_bufs方法如果不提供,那么spi core认为你不需要使用dma传输spi数据。同时设置一些默认写好的的方法:spi_bitbang- >txrx_bufs = spi_bitbang_bufs//如果spi_bitbang- >setup没有提供,则使用默认spi_bitbang- >setup_transfer =spi_bitbang_setup_transfer;spi_master- >setup = spi_bitbang_setup;spi_master- >cleanup = spi_bitbang_cleanup;
master这几个接口一定会使用默认写好的方法:spi_master- >prepare_transfer_hardware = spi_bitbang_prepare_hardware;spi_master- >unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;spi_master- >transfer_one = spi_bitbang_transfer_one;//在初始化队列spi_controller_initialize_queue()的时候spi_master- >transfer = spi_queued_transferspi_master- >- >transfer_one_message = spi_transfer_one_message
spi_register_master()做的事情
spi控制器硬件分为主机模式和从机模式,使用spi_controller来表示,实际上也就是spi_master。他们在使用API方面有区别。
处理SPI总线号,如SPI1、SPI2中的数字,主要是根据设备树的alias处理cs-gpios片选属性,根据设备树中对cs-gpios片选属性的描述,更新spi_master→num_chipselect,分配所有gpio描述符,spi_master→cs_gpiods保存着所有gpio描述符,如在spi控制器设备树节点中使用的。&ecspi2{ ... cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >,< &gpio1 31 GPIO_ACTIVE_LOW >;//GPIO1_29 ...
增加master默认回调,继续给spi_master增加默认写好的方法spi_master- >transfer = spi_queued_transfer;spi_master- >transfer_one_message = spi_transfer_one_message;
创建工作队列,使整个spi架构运转起来,可以ps -e命令看到如下内核线程信息彩蛋,还可以给这个工作队列的内核线程设置实时调度策略降低总线延迟。
创建spi_device,**最后最重要的是of_register_spi_devices()**处理spi控制器设备树下的所有spi设备子节点,这就是spi_device的由来,spi_device表示一个spi设备,如oled等。一个spi总线下面可以挂接多个spi设备。&ecspi2{ fsl,spi-num-chipselects = < 1 >; cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >;//GPIO1_29 pinctrl-names = "default"; pinctrl-0 = < &pinctrl_ecspi2 >; num-cs = < 1 >; status = "okay"; oled: ssd13306@0{ compatible = "alientek,ssd13306";//自己写的oled驱动 spi-cpha; spi-cpol; spi-max-frequency = < 200000 >; reset-gpios = < &gpio1 27 GPIO_ACTIVE_LOW >; dc-gpios = < &gpio1 31 GPIO_ACTIVE_HIGH >; reg = < 0 >; };};
比较重要的设备树解析在of_spi_parse_dt(),关键的属性spi_device的字段对应如下:
属性 | 描述 | ||
---|---|---|---|
spi-cpha | spi->mode | = SPI_CPHA | CPHA=1 |
spi-cpol | spi->mode | = SPI_CPOL | CPOL=1 |
spi-3wire | spi->mode | = SPI_3WIRE | 使用三线SPI |
spi-lsb-first | spi->mode | = SPI_LSB_FIRST | 一般spi是MSB,指定后LSB |
spi-cs-high | spi->mode | = SPI_CS_HIGH | 一般片选CS是低有效,指定后高有效 |
spi-tx-bus-width | spi->mode | = SPI_NO_TX | 发送方向为0,可能只是读 |
= SPI_TX_DUAL | DUAL SPI 双线半双工 | ||
= SPI_TX_QUAD | QUAD SPI 四线半双工 | ||
= SPI_TX_OCTAL | OCTAL SPI 八线半双工 | ||
spi-rx-bus-width | spi->mode | = SPI_NO_RX | 接受方向为0,可能只是发送 |
= SPI_RX_DUAL | DUAL SPI 双线半双工 | ||
= SPI_RX_QUAD | QUAD SPI 四线半双工 | ||
= SPI_RX_OCTAL | OCTAL SPI 八线半双工 | ||
reg | spi->chip_select | 表示spi设备在第几个片选 | |
spi-max-frequency | spi->max_speed_hz | 这个spi设备使用的spi传输速率,单位Hz | |
multi-die | spi->multi_die | ||
"slave” | 判断spi控制器是否是从设备 |
以上属性都是spi_device的设备树属性,不是spi控制器的设备树属性。“reg”属性必须设置,表示片选。这个解析过程spi控制器驱动注册的时候交给SPI core做的。
发起传输时候的回调顺序
- >prepare_transfer_hardware(master) -------spi_bitbang_prepare_hardware() |- >prepare_message(master,msg)-----------bsp |- >transfer_one_message(msg)-----------spi_transfer_one_message() |- >transfer_one(master, spi_device,xfer)-----------spi_bitbang_transfer_one() |- >transfer_one |- >setup_transfer(spi_dev, xfer)------------bsp |- >txrx_bufs(spi_dev, xfer)-----------------bsp |- >transfer_one |- >transfer_one |- >transfer_one ...
prepare_transfer_hardware:就是设置busy字段
prepare_message:包含了硬件初始化
①设置主从模式。
②配置硬件cs使能和是否高有效。
③配置CPAH\\CPOL。
transfer_one_message:spi_transfer_one_message(),针对一个spi_message
①拉低cs
②发送所有spi_transfer,每个spi_transfer之间会有延迟,人为通过spi_transfer→delay指定。每个spi_transfer也可以有cs转换,通过设置spi_transfer→cs_change
③拉高cs
transfer_one: spi_bitbang_transfer_one(),调用setup_transfer和txrx_bufs
setup_transfer:用来配置每一个spi_transfer
①对rx tx buf作对齐
②更新是否使用dma标志,也就是说每一段transfer都可以决定用不用dma
③设置字长和时钟,也就是说每一段transfer都可以单独设置sclk和字长
txrx_bufs:写fifo或者读fifo
①读空fifo
②发送tx_buf到txfifo,硬件操作,需要处理字节对齐
③等待tx 空中断,表明一个transfer发送完了
中断:
①读rxfifo
②唤醒完成量
提出三个问题
1.每个具体的spi设备指定的reg属性怎么对应SPI控制器的cs片选?
答:在spi device注册的时候,会根据reg片选号拿到spi_master先前遍历的cs 片选gpiod描述符,取对应的那个,从cs_gpiods数组中拿。
static int __spi_add_device(struct spi_device *spi){ ... /* Descriptors take precedence */ if (ctlr- >cs_gpiods) spi- >cs_gpiod = ctlr- >cs_gpiods[spi- >chip_select]; else if (ctlr- >cs_gpios) spi- >cs_gpio = ctlr- >cs_gpios[spi- >chip_select]; ...}
2.每个具体的spi设备指定的spi通信速率,在哪里体现?
答:在spi_setup()最后检查,如果指定的速率大于spi控制器的速率,那么就使用spi控制器的速率
int spi_setup(struct spi_device *spi){ ... if (spi- >controller- >max_speed_hz && (!spi- >max_speed_hz || spi- >max_speed_hz > spi- >controller- >max_speed_hz)) spi- >max_speed_hz = spi- >controller- >max_speed_hz; ...}
3.spi 设备在用户空间看到的名称格式
答:看到的名称格式是:"%s.%u”。%s是spi控制器的名称,%u是片选。如spi1.0
root@imx6ull /sys/devices/platform/soc/2000000.bus/2000000.spba-bus/200c000.spi/spi_master/spi1# lsdevice power statistics ueventof_node spi1.0 subsystem
spi_controller_list链表保存所有spi_master
spi_device不会单独被链表组织,会被kobject组织
至此,spi_bitbang_start完成之后,无论是spi_master还是其下面每个spi_device都已经初始化完成,但是要记住,spi设备驱动还没有进行,因为以上只是构造好了当前有什么设备,有什么控制器,但是对于spi设备来说,还需要针对他的驱动,这就是spi设备驱动。比如oled,在以上过程执行完之后,仅仅给oled抽象成了一个spi device,但是要如何控制oled,还需要这个oled驱动,进行oled的读写操作。
spi_master→setup 每个spi device注册进来的时候调用一次。