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 设备接入。
软件构架
如上图为 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_CSN0 | GPIO0[17] | <&peri_port0 17> |
| SPI0_CSN1 | GPIO0[18] | <&peri_port0 18> |
| SPI1_CSN0 | GPIO0[22] | <&peri_port0 22> |
| SPI1_CSN1 | GPIO0[23] | <&peri_port0 23> |
| 引脚 | GPIO | 设备树 |
|---|---|---|
| SPI0_CSN0 | GPIO1[30] | <&hsi_port1 30> |
| SPI0_CSN1 | GPIO1[31] | <&hsi_port1 31> |
| SPI1_CSN0 | GPIO1[10] | <&hsi_port1 10> |
| SPI1_CSN1 | GPIO1[20] | <&hsi_port1 20> |
| SPI2_CSN0 | GPIO1[16] | <&hsi_port1 16> |
| SPI2_CSN1 | GPIO0[30] | <&hsi_port0 30> |
| SPI3_CSN0 | GPIO1[0] | <&hsi_port1 0> |
| SPI3_CSN1 | GPIO0[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 接收数据出现丢失。
暂不支持该测试。