当前位置: 主页 > 资讯 > 详情
观点:Linux SPI控制器驱动教程

来源: 2023-06-16 11:30:16

一、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-cphaspi->mode= SPI_CPHACPHA=1
spi-cpolspi->mode= SPI_CPOLCPOL=1
spi-3wirespi->mode= SPI_3WIRE使用三线SPI
spi-lsb-firstspi->mode= SPI_LSB_FIRST一般spi是MSB,指定后LSB
spi-cs-highspi->mode= SPI_CS_HIGH一般片选CS是低有效,指定后高有效
spi-tx-bus-widthspi->mode= SPI_NO_TX发送方向为0,可能只是读
= SPI_TX_DUALDUAL SPI 双线半双工
= SPI_TX_QUADQUAD SPI 四线半双工
= SPI_TX_OCTALOCTAL SPI 八线半双工
spi-rx-bus-widthspi->mode= SPI_NO_RX接受方向为0,可能只是发送
= SPI_RX_DUALDUAL SPI 双线半双工
= SPI_RX_QUADQUAD SPI 四线半双工
= SPI_RX_OCTALOCTAL SPI 八线半双工
regspi->chip_select表示spi设备在第几个片选
spi-max-frequencyspi->max_speed_hz这个spi设备使用的spi传输速率,单位Hz
multi-diespi->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注册进来的时候调用一次。

相关资讯