跳到主要内容

SPI 调试指南

SPI 硬件支持

S100 Acore 支持2路 SPI,且 SPI0,SPI1只能做 SPI Master。

S600 Acore 支持4路 SPI,且所有的 SPI 只能做 SPI Master。

RDK S600 开发板中的 SPI0与 CAN0和 CAN1复用4个引脚,由于这些引脚的物理线路已连接至 CAN 收发器;其他 SPI 控制器默认没有在 RDK S600开发板上引出,因此 Acore 在硬件层面上无法支持外部 SPI 设备接入。

软件构架

image-spi_software

如上图为 SPI 软件架构图,从下到上依次可以分为硬件 IP 层,内核层和用户空间层,下面依次对各层进行介绍。

  • 硬件 IP 层:该层为 SPI 硬件层。
  • 内核层:又可以细分为3层。
    • spi driver 层:主要实现对 SPI 硬件 IP 的操作,另外还实现了 spi framework 定义的接口。
    • spi framework 层:可以理解为 spi driver 的适配层,对下层定义了一组 driver 层需要实现的接口,对上提供了通用接口屏蔽了硬件细节。
    • spi char device 层:为用户空间提供节点,方便用户空间与内核空间进行数据交换。目前使用 kernel 自带的 spidev 字符设备。
  • app 层:为各种应用程序,这些应用程序通过调用字符设备驱动达到与内核空间数据交换的目的。

代码路径

Hobot SPI 协议代码

hobot spi 驱动相关代码都放在 $project/hobot-drivers/spi 目录下

oops@tiger$ tree . -L 1

├── Kconfig # Kconfig相关
├── README.md
└──spi_drv # spi driver相关

$project/hobot-drivers/spi/spi_drv 目录说明

oops@tiger$ tree . -L 1
├── Makefile
├── spi-dw.c # spi驱动核心代码
├── spi-dw.h
├── spi-dw-mmio.c # spi驱动mmio代码
└── spi-dw-mmio-dma.c # spi驱动dma代码

Linux SPI 框架代码

Linux spi 协议相关代码都放在 $project/kernel/drivers/spi 目录下

oops@tiger$ tree kernel/drivers/spi/
drivers/spi/
├── spi.c # spi框架代码

oops@tiger$

SPI 设备树代码

S100中涉及到 spi 配置相关的 dts 文件如下:

|-- drobot-s100-pinctrl.dtsi       # spi pinctrl相关配置
|-- drobot-s100-soc.dtsi # spi 设备节点配置
|-- drobot-s100-pdma.dtsi # spi pdma使用配置

S600中涉及到 spi 配置相关的 dts 文件如下:

|-- drobot-s600-pinctrl.dtsi       # spi pinctrl相关配置
|-- drobot-s600-soc.dtsi # spi 设备节点配置
|-- drobot-s600-pdma.dtsi # spi pdma使用配置

SPI 设备树配置说明

spi0: spi@39800000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x39800000 0x0 0x1000>;
interrupts = <GIC_SPI PERISYS_SPI0_SSI_INTR PERISYS_SPI0_SSI_INTR_TRIG_TYPE>;
status = "okay";
num-cs = <2>;
resets = <&smc_reset RST_IDX_IP_PERI_SPIM0>,
<&smc_reset RST_IDX_IP_PERI_SPIM0_APB>;
reset-names = "spi_reset";
clocks = <&scmi_smc_clk CLK_IDX_TOP_PERI_SPI_M0>;
clock-names = "spi_pclk";
power-domains = <&scmi_smc_pd PD_IDX_LSPERI_TOP>;
freq-pclk = <200000000>;
sample-delay = <1>;
pinctrl-names = "default";
pinctrl-0 = <&peri_spi0>;
dmas = <&pdma0 8 /* read channel */
&pdma0 9 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

spi1: spi@39810000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x39810000 0x0 0x1000>;
interrupts = <GIC_SPI PERISYS_SPI1_SSI_INTR PERISYS_SPI1_SSI_INTR_TRIG_TYPE>;
status = "okay";
num-cs = <2>;
resets = <&smc_reset RST_IDX_IP_PERI_SPIM1>,
<&smc_reset RST_IDX_IP_PERI_SPIM1_APB>;
reset-names = "spi_reset";
clocks = <&scmi_smc_clk CLK_IDX_TOP_PERI_SPI_M1>;
clock-names = "spi_pclk";
power-domains = <&scmi_smc_pd PD_IDX_LSPERI_TOP>;
freq-pclk = <200000000>;
sample-delay = <1>;
pinctrl-names = "default";
pinctrl-0 = <&peri_spi1>;
dmas = <&pdma0 10 /* read channel */
&pdma0 11 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

spi0: spi@34900000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x34900000 0x0 0x1000>;
interrupts = <GIC_SPI HSISYS_SPI0_SSI_INTR IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
num-cs = <2>;
//resets = <&smc_reset 0>,
// <&smc_reset 0>;
//reset-names = "spi_reset";
//clocks = <&scmi_smc_0>;
//clock-names = "spi_pclk";
//power-domains = <&scmi_smc_pd 0>;
freq-pclk = <200000000>;
sample-delay = <1>;
pinctrl-names = "default";
pinctrl-0 = <&hsi_spi0_csn0_spi0_csn0 &hsi_spi0_mosi_spi0_mosi\
&hsi_spi0_miso_spi0_miso &hsi_spi0_sclk_spi0_sclk>;
dmas = <&pdma0 16 /* read channel */
&pdma0 17 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

spi1: spi@34910000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x34910000 0x0 0x1000>;
interrupts = <GIC_SPI HSISYS_SPI1_SSI_INTR IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
num-cs = <2>;
//resets = <&smc_reset 0>,
// <&smc_reset 0>;
//reset-names = "spi_reset";
//clocks = <&scmi_smc_clk 0>;
//clock-names = "spi_pclk";
//power-domains = <&scmi_smc_pd 0>;
freq-pclk = <200000000>;
sample-delay = <1>;
//pinctrl-names = "default";
//pinctrl-0 = <&hsi_spi1>;
dmas = <&pdma0 18 /* read channel */
&pdma0 19 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

spi2: spi@34920000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x34920000 0x0 0x1000>;
interrupts = <GIC_SPI HSISYS_SPI2_SSI_INTR IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
num-cs = <2>;
//resets = <&smc_reset 0>,
// <&smc_reset 0>;
//reset-names = "spi_reset";
//clocks = <&scmi_smc_clk 0>;
//clock-names = "spi_pclk";
//power-domains = <&scmi_smc_pd 0>;
freq-pclk = <200000000>;
sample-delay = <1>;
//pinctrl-names = "default";
//pinctrl-0 = <&hsi_spi2>;
dmas = <&pdma0 20 /* read channel */
&pdma0 21 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

spi3: spi@34930000 {
compatible = "hobot,hb-dw-spi";
reg-io-width = <4>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x34930000 0x0 0x1000>;
interrupts = <GIC_SPI HSISYS_SPI3_SSI_INTR IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
num-cs = <2>;
//resets = <&smc_reset 0>,
// <&smc_reset 0>;
//reset-names = "spi_reset";
//clocks = <&scmi_smc_clk 0>;
//clock-names = "spi_pclk";
//power-domains = <&scmi_smc_pd 0>;
freq-pclk = <200000000>;
sample-delay = <1>;
//pinctrl-names = "default";
//pinctrl-0 = <&hsi_spi3>;
dmas = <&pdma0 22 /* read channel */
&pdma0 23 >; /* write channel */
dma-names = "rx", "tx";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};

这里着重说明 SPI 新增的配置项

  • sample-delay:spi 控制器作 master 时,对接收数据的采样延迟值,如果出现数据 bit 位错位的情况,可以调整该值。
  • num-cs:spi 控制器作 master 时,支持 cs 个数,SPI 作 master 时,最多支持两个片选。

SPI 配置 GPIO CS

以 spi0 cs1 为例,在设备树中为 spi0 节点添加 cs-gpios 属性,将 cs1 映射到指定 GPIO:

spi0: spi@39800000 {
...
pinctrl-0 = <&peri_spi0>;
cs-gpios = <0>, /* CS0:由 SPI 控制器原生控制 */
<&peri_port0 18 GPIO_ACTIVE_LOW>; /* CS1:由 GPIO 模拟控制 */
...
};

说明:各 SPI 片选引脚对应的 GPIO 编号及设备树节点如下表所示,可直接查表填写 cs-gpios 属性。

引脚GPIO设备树
SPI0_CSN0GPIO0[17]<&peri_port0 17>
SPI0_CSN1GPIO0[18]<&peri_port0 18>
SPI1_CSN0GPIO0[22]<&peri_port0 22>
SPI1_CSN1GPIO0[23]<&peri_port0 23>
引脚GPIO设备树
SPI0_CSN0GPIO1[30]<&hsi_port1 30>
SPI0_CSN1GPIO1[31]<&hsi_port1 31>
SPI1_CSN0GPIO1[10]<&hsi_port1 10>
SPI1_CSN1GPIO1[20]<&hsi_port1 20>
SPI2_CSN0GPIO1[16]<&hsi_port1 16>
SPI2_CSN1GPIO0[30]<&hsi_port0 30>
SPI3_CSN0GPIO1[0]<&hsi_port1 0>
SPI3_CSN1GPIO0[31]<&hsi_port0 31>

注意:S600 的 SPI 引脚电平为 1.8V,请注意与外设的电平匹配。

另外,需要在 source/hobot-drivers/kernel-dts/drobot-xxx-pinctrl.dtsi 中找到 peri_spi0, 将 cs1 相关引脚从 pinmux 和 pinconf 中移除(避免与 GPIO 配置冲突):

peri_spi0: peri_spi0_func {
pinmux {
function = "peri_spi0";
pins = "peri_spi0_csn0", "peri_spi0_mosi",
"peri_spi0_miso", "peri_spi0_sclk";
};
pinconf {
pins = "peri_spi0_csn0", "peri_spi0_mosi",
"peri_spi0_miso", "peri_spi0_sclk";
drive-strength = <1>;
};
};

SPI 验证及调试

本小节主要介绍 S100 SPI 基本功能如何验证,包括环境如何配置,测试命令的执行及测试代码存放位置等。

本小节主要介绍 S600 SPI 基本功能如何验证,包括环境如何配置,测试命令的执行及测试代码存放位置等。

测试环境准备

spidev_test 是一个开源的 SPI 测试工具,用户可以直接从 linux 源码如下目录获取并编译使用。

源码位置:kernel/tools/spi/spidev_test.c。

spidev_test 常见参数说明如下:

root@ubuntu:/map# ./spidev_test -h
./spidev_test: invalid option -- 'h'
Usage: ./spidev_test [-DsbdlHOLC3vpNR24SI]
-D --device device to use (default /dev/spidev1.1)
-s --speed max speed (Hz)
-d --delay delay (usec)
-b --bpw bits per word
-i --input input data from a file (e.g. "test.bin")
-o --output output data to a file (e.g. "results.bin")
-l --loop loopback
-H --cpha clock phase
-O --cpol clock polarity
-L --lsb least significant bit first
-C --cs-high chip select active high
-3 --3wire SI/SO signals shared
-v --verbose Verbose (show tx buffer)
-p Send data (e.g. "1234\xde\xad")
-N --no-cs no chip select
-R --ready slave pulls low to pause
-2 --dual dual transfer
-4 --quad quad transfer
-8 --octal octal transfer
-S --size transfer size
-I --iter iterations

SPI 内部回环测试

SPI 内部回环测试仅 SPI Master 支持,其原理是 SPI 硬件 IP 的 tx fifo 将数据发给 rx fifo 从而形成回环。

测试命令及结果参考如下:

root@ubuntu:/map# ./spidev_test -D /dev/spidev1.0 -s 1000000 -S 100 -l -v -p "\x01\x02\x03\x04"
spi mode: 0x20
bits per word: 8
max speed: 1000000 Hz (1000 kHz)
TX | 01 02 03 04 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |....|
RX | 01 02 03 04 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |....|

SPI 外部回环测试

SPI 外部回环测试指 SPI Master 接 SPI Slave。

Master 可以选择 SPI1,SPI Slave 选择外部 SPI 设备(客户自行选择)。

S100侧的发送测试命令参考如下:

root@ubuntu:/map# ./spidev_test -D /dev/spidev1.0 -s 1000000 -S 100  -v -p "\x01\x02\x03\x04"
spi mode: 0x0
bits per word: 8
max speed: 1000000 Hz (1000 kHz)
TX | 01 02 03 04 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |....|
RX | FF FF FF FF __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |....|

在 Slave 设备侧将收到 S100侧 Master 发送的数据。

注:在进行外部回环测试时,需要先执行 SPI Slave 程序,再执行 SPI Master 程序。假如先执行 SPI Master 程序,后执行 SPI Slave 程序,可能会由于 Master 与 Slave 不同步导致 SPI 接收数据出现丢失。

暂不支持该测试。