目录
1.芯片简介
1.1 模块与接口
dw_apb_ssi支持全双工的master和slave模式,支持dma传输,支持SPI(Motorola Serial Peripheral Interface),SSP(Texas Instruments Serial Protocol),National Semiconductor Microwire三种接口类型。支持Transmit and Receive,Transmit Only,Receive Only,EEPROM Read四种传输模式。
APB Interface用于寄存器访问和数据收发。
DMA Interface用于跟DMA控制器通信,进行握手/传输请求/传输应答等控制信号的交互。
1.2 非DMA传输
master模式数据传输流程流程如下:
(1) 软件配置控制寄存器设置传输模式,分频系数,FIFO收发门限,中断使能,选择device片选,然后使能控制器,发起传输。
(2) 软件写Tx FIFO,SPI传输的特点决定了有发送才有接收,发送多少数据,接收多少数据。如果传输数据较长,那么要考虑根据FIFO的长度将数据分片。如果只是发送数据,那么直接发送有效的数据内容即可。接收数据时一般配置为Transmit and Receive模式,此时为了接收数据,需要持续在发送方向发送dummy数据。
(3) 软件持续从Rx FIFO中读出数据。
非DMA传输可以采取轮询或者中断方式,uboot下采用轮询模式,内核驱动两种方式都支持。
1.3 DMA传输
1.3.1 DMA寄存器
DMA传输方式需要通用DMA控制器驱动配合。dw ssi引出一组dma控制信号与DMAC交互。
dma_tx_req/dma_rx_req/dma_tx_single/dma_rx_single 是ssi发给dmac的请求信号。
dma_tx_ack/dma_rx_ack 是dmac发给ssi的应答信号。
此外,dw ssi还提供了3个DMA相关的寄存器用于使能DMA收发和设置DMA水线。
DMACR用于使能DMA传输,DMATDLR配置tx fifo水线,DMARDLR配置rx fifo水线。
当tx fifo中的待发数据个数向下减少到水线值时,ssi向dmac发起dma_tx_req,此时dmac应该往tx fifo中注入数据,使得待发数据个数重新达到水线之上。
然后随着ssi逐步发出数据,待发数据个数又开始减少到达水线,于是再次发起dma_tx_req,提示dmac再次注入数据,如此循环。
水线设置低,一次burst发送的数据个数较多,ssi向dmac发起dma_tx_req的频率相对较低;水线设置高,一次burst发送的数据个数较少,ssi向dmac发起dma_tx_req的频率相对较高。
dmac应该及时注入数据,如果水线设置较低,tx fifo很快就会将fifo中剩余的数据发完,而dmac还没来得及注入足够的数据,就会发生下溢(ssi无数据可发)。
当rx fifo中的待收数据个数向上增长到水线值时,发起dma_rx_req,此时dmac应该从rx fifo中取走数据,使得待收数据个数降到水线之下。
然后随着ssi逐步接收数据,待收数据个数又开始增长到达水线,于是再次发起dma_rx_req,提示dmac再次取走数据,如此循环。
水线设置低,一次burst接收的数据个数较少,ssi向dmac发起dma_rx_req的频率相对较高;水线设置高,一次burst接收的数据个数较多,ssi向dmac发起dma_rx_req的频率相对较低。
dmac应该及时取走数据,如果水线设置较高,rx fifo空闲空间很快将被耗尽,而dmac还没来得及取走数据,就会发生上溢(ssi无空间保存新接收的数据)。
1.3.2 DMA传输分片
一次block传输12个data item,分为3次burst,每次burst传输4个data item。
一次block传输15个data item,分为3次burst和3次single,每次burst传输4个data item,每次single传输1个data item。
1.3.3 DMA水线设置
dw_ssi通常用于MEM <-> DEVICE 这种对接场景,发送方向DST device侧发送速率较低,接收方向SRC device侧接收速率较低。以下是手册建议的水线配置:
DMA.CTLx.DEST_MSIZE = FIFO_DEPTH - SSI.DMATDLR
DMA.CTLx.SRC_MSIZE = SSI.DMARDLR + 1
2.调试问题
2.1 uboot
2.1.1 Read Flash ID失败
【问题现象】
uboot命令行视图执行sf probe命令,未读到有效数据。
先确认SPI相关引脚的复用配置,确认为SPI模式。
然后对比A53验证代码,可以读到ID,说明硬件没问题。不同在于收发数据流程:A53中将opcode和dummy data合并在一起,在一个transfer里发送到device,但uboot没有这样的处理。
【问题原因】
使用dw ssi控制device片选信号时,包括READ ID在内的read reg/read data流程要求
opcode/addr/dummy一起发给device,如果是读取数据,tx方向需要继续发送dummy数据,接收方向在接收数据后需要过滤掉无效数据(对应opcode/addr/dummy收到的内容)。
2.1.2 一次只能读出27B有效内容
【问题现象】
从offset=0开始连续读取37B,只有前面27B有效,后面10B内容为0xff。
从offset=0开始读0x25(37B)数据,因为fifo为32B,所以软件中读操作拆分成2段:
第一段:tx opcode=0bh, addr=0x000000, dummy=0xff, tx padding 27B, 同时rx 27B data
第二段:tx opcode=0bh, addr=0x00001b, dummy=0xff, tx padding 10B, 同时rx 10B data
但从实际结果看,第一段读到的内容是正确的,从0x00 - 0x1a;
第二段读到的内容是全FF,正确的数据应该是0x1b - 0x24。
【问题原因】
Read data使用的address必须以小端方式发送给device,offset=0x00时,大小端都一样,所以可以读到正确的内容,offset=0x1b时,实际发送的addr是0x1b0000,该地址实际上未被写入内容,读到的就是0xff。正确的做法是将地址转成小端。
2.2 kernel
2.2.1 spi访问读取内容无效
【问题现象】
内核spi框架传输opcode/addr/dummy与后面的data传输,分成2次transfer完成,这样无法得到正确的数据。像uboot那样将2次transfer拼接成一个,可以得到正确的结果。但是这样一来在spi访问前后增加了2次内存拷贝,开销很大。
抓波形可以看到,分2次传输时,cs中间自动拉高了,接下来opcode=0x9f对应的接收内容是0x00。
而合并成1次transfer时,只出一次片选,opcode=0x9f读到的内容确实是FLASH的JEDEC ID。
【问题原因】
控制器驱动cs时,2次transfer中间cs自动拉高,相当于中断了完整的SPI访问流程,导致得不到正确的数据。将cs引脚配置为gpio模式,由软件控制,2次tranfer过程全程使能cs不拉高,可以得到正确的数据内容。
对应的dts新增cs-gpios配置项:
spi0: spi@f0da0000 {
compatible = "snps,dw-apb-ssi";
pinctrl-names = "default";
pinctrl-0 = <&PA26_pinctrl>;
cs-gpios = <&porta 26 GPIO_ACTIVE_LOW>;
};
2.2.2 dma传输rx超时
【问题现象】
dma方式从flash读48 byte内容,dma tx结束,dma rx超时。
【问题原因】
dw DMAC控制器的硬件实现限制最大发送/接收的Burst Len为4,而dw ssi DMARDLR配置接收水线为16,大于DMA通道的Burst Len,使得传输过程最后rx fifo存有数据,但是因为达不到水线,无法触发dma_rx_req,导致数据残留在rx fifo中未被软件接收,dma rx超时。
修改dts中DMAC的burst能力,限制为4,这样相应的dw ssi rx fifo接收水线DMARDLR也配置为4,可以保证rx fifo中数据被完整接收。
注意:虽然ssi DR的宽度为32bits,但一次只能收发8bit数据。
tx方向,传输一个data item意味着从DDR取8bit数据,填入ssi fifo 32bits entry(DR寄存器)的低8位,一次burst传4个data item,也就是4B;
rx方向,传输一个data item意味着ssi fifo 32bits entry(DR寄存器)读入32位数据,取低8位填入DDR,一次burst传4个data item,也就是4B。
DMA的DST和SRC虽然数据宽度不同,但是一次传输的有效数据都是4B,所以dma_tx_req和dma_rx_req最终的次数也是一致的。
下面分析软件从FLASH offset = 0处读取48B数据的dma流程。
有问题的配置对应的DMA处理流程。
发送方向:
(1) ssi tx fifo空,ssi发起dma_tx_req,dmac响应请求,发起一次tx burst,注入DEST_MSIZE个数据(4个),一共写入4*8bits = 4B。
(2)此时仍然tx fifo有效数据仍低于DMATDLR水线,于是ssi再次发起若干次dma_tx_req,dmac响应请求,每次都注入DEST_MSIZE个数据,直到tx fifo中数据个数高于DMATDLR水线。
(3) 随着ssi发送数据,当tx fifo中数据个数下降到DMATDLR水线,ssi再次发起dma_tx_req,dmac响应请求,发起一次tx burst,再注入DEST_MSIZE个数据。
下面重复步骤(3)直到所有数据均写入tx fifo,等待ssi陆续发完。
接收方向:
(1) 由于tx方向持续发送数据,在接收方向上也会持续接收数据。
当ssi rx fifo中数据上涨到达(DMARDLR + 1)水线,ssi发起dma_rx_req,dmac响应请求,发起一次rx burst,读出SRC_MSIZE个数据(4个),一共读出4*8bits = 4B。
(2) 如果此时ssi rx fifo中数据个数仍高于(DMARDLR + 1)水线,那么ssi仍会持续发起dma_rx_req,dmac也会持续读出数据,直到ssi rx fifo中数据个数低于(DMARDLR + 1)水线。
(3) 这里DMARDLR + 1 = 16,而SRC_MSIZE = 4,这就使得ssi rx fifo中会残留12B数据,因为达不到水线,无法通知dmac取走,于是导致rx方向最终超时失败。
正确的配置对应的DMA处理流程。
发送方向:跟之前相同
接收方向:DMARDLR + 1 = SRC_MSIZE = 4,保证了一次rx burst读走rx fifo中所有的数据,没有残留,所以rx 方向可以顺利结束。
残留数据的验证:dma超时后读取RXFLR,确认此时rx fifo中是否有数据,有的话直接dump DR寄存器内容,确认正好就是最后12B数据内容。
3. 内核SPI驱动
3.1 SPI框架
Linux SPI 驱动结构中,将 SPI 相关的驱动分为了几部分:
(1) SPI 主机以及主机驱动:SoC 的 SPI Controller 部分的驱动
(2) SPI 外设驱动描述:比如 SPI Flash 驱动
(3) SPI 从设备描述:比如 SPI Flash 设备
(4) SPI 传输层描述:spi_transfer 和 spi_message 组成
SPI 主机控制器部分是整个 SPI 系统的核心存在,它并不属于 SPI 下的 bus、device、drvier 这一组结构,因为他并不是挂接到 bus 上的 device,更不是对应挂接在 bus 上 device 的 driver,而是相对独立的一个存在,所以 SPI 控制器部分,是连接到 platform 下的,并执行 platform 的 probe。
在数据发送的结构部分,内核将其抽象如下,一次传输封装为message,一个message包含一个或多个transfer。
3.2 spi-dw驱动
spi读写流程,device驱动封装读写接口,调用spi框架的传输API spi_sync(),执行device所属的控制器驱动挂接的transfer_one()接口,实现总线层面的数据访问,传输完成后,spi_sync()返回阻塞的读写进程,得到spi访问的结果。
以SPI FLASH驱动m25p80为例:
read流程
(1) mtd_debug 或者其它应用程序 -> m25p80_read_reg()
-> spi_sync(spi, &message)
-> spi_transfer_one_message() -> dw_spi_transfer_one()
-> wait_for_completion_timeout() 阻塞
(2) dw ssi控制器spi传输
dw_spi_irq() -> dw_reader() -> complete() 通知阻塞流程继续往下执行
(3) mtd_debug 或者其它应用程序得到spi访问数据。
其中dw ssi控制器的传输接口dw_spi_transfer_one()支持三种传输方式:轮询,中断,dma。
(1)轮询
调用dw_spi_poll_transfer(dws, transfer)完成传输,边发边收。
函数返回0。
(2)中断
调用dw_spi_irq_setup(dws)设置中断,后续数据收发在ssi中断处理中完成。
函数返回1。
(3)dma
调用dw_spi_dma_transfer()触发dma传输,后续传输由dma完成,dma中断确认完成状态。spi-dw驱动在初始化时需要事先从通用dma申请通道资源。
- spi框架中先map buf,再传输message,提交dma描述符,挂接回调,传输发起后阻塞等待completion;
- dma传输,dma中断处理确认通道传输完成,调度vchan tasklet;
- vchan tasklet调用dma描述符的callback回调,complete传输;
- spi框架从阻塞处继续执行,unmap buf,message传输完成。
函数返回0。
4.调试命令
4.1 uboot
(1) sf 相关命令
(2) sspi [<bus>:]<cs>[.<mode>][@<freq>] <bit_len> <dout>
4.2 kernel
4.2.1 mtd_debug
SPI FLASH读/擦/写。
# mtd_debug
usage: mtd_debug info <device>
mtd_debug read <device> <offset> <len> <dest-filename>
mtd_debug write <device> <offset> <len> <source-filename>
mtd_debug erase <device> <offset> <len>
4.2.2 debugfs
查看ssi寄存器内容。
# mount -t debugfs none /sys/kernel/debug
# cat /sys/kernel/debug/dw_spi0/registers
CTRLR0 = 0x00070000
.........................
4.2.3 module parameter
使能/去使能dma传输。
# echo 1 > /sys/module/spi_dw/parameters/dma_enable
# echo 0 > /sys/module/spi_dw/parameters/dma_enable