Linux SPI 開發指南
1 前言
1.1 文檔簡介
介紹 SPI 模塊的使用方法,方便開發人員使用。
1.2 目標讀者
SPI 模塊的驅動開發/維護人員。
1.3 適用范圍
表 1-1: 適用產品列表
內核版本 | 驅動文件 |
---|---|
Linux-4.9 | spi-sunxi.c |
Linux-5.4 | spi-sunxi.c |
2 模塊介紹
2.1 模塊功能介紹
SPI 是一種高速、高效率的串行接口技術。通常由一個主模塊和一個或多個從模塊組成,主模塊選擇一個從模塊進行同步通信,從而完成數據的交換,被廣泛應用于 ADC、LCD 等設備與 MCU 之間。全志的 spi 控制器支持以下功能:
? 全雙工同步串行接口。
? 支持 5 種時鐘源選擇。
? 支持 master 和 slave 兩種配置。
? 四個 cs 片選支持。
? 8bit 寬度和 64 字節 fifo 深度。
? cs 和 clk 的極性和相位可配置。
? 支持使用 DMA。
? 支持四種通信模式。
? 批量生產支持最大的 io 速率 100MHz。
? 支持 3 線、4 線 SPI 模式。
? 支持可編程串行行數據幀長:0~32bits。
? 支持 Standard SPI/Dual-Output/Dual-input SPI/Dual i/O SPI/ 和 Quad-Output/Quad Input SPI。
2.2 相關術語介紹
2.2.1 硬件術語
表 2-1: 硬件術語
術語 | 解釋說明 |
---|---|
SPI | Serial Peripheral Interface,同步串行外設接口 |
2.2.2 軟件術語
表 2-2: 軟件術語
術語 | 解釋說明 |
---|---|
Sunxi | 指 Allwinner 的一系列 SOC 硬件平臺 |
SPI Master | SPI 主設備 |
SPI Device | 指 SPI 外部設備 |
2.3 模塊配置介紹
2.3.1 device tree 配置說明
在不同的 Sunxi 硬件平臺中,SPI 控制器的數目也不同,但對于每一個 SPI 控制器來說,在設備樹中配置參數相似,平臺設備樹文件的路徑為:kernel/內核版本/arch/arm64(32 位平臺arm)/boot/dts/sunxi/CHIP.dtsi(CHIP 為研發代號,如 sun50iw10p1 等),對于配置 SPI1而言,如下:
spi1: spi@05011000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "allwinner,sun50i-spi"; //具體的設備,用于驅動和設備的綁定
device_type = "spi1"; //設備節點名稱
reg = <0x0 0x05011000 0x0 0x1000>; //總線寄存器配置
interrupts = ;//總線中斷號、中斷類型
clocks = <&clk_pll_periph0>, <&clk_spi1>; //設備使用的時鐘
clock-frequency = <100000000>; //控制器的時鐘頻率
pinctrl-names = "default", "sleep"; //控制器使用的Pin腳名稱
pinctrl-0 = <&spi1_pins_a &spi1_pins_b>; //控制器使用的pin腳配置
pinctrl-1 = <&spi1_pins_c>; //控制器使用的pin腳配置
spi1_cs_number = <1>; //控制器cs腳數量
spi1_cs_bitmap = <1>; /* cs0- 0x1; cs1-0x2, cs0&cs1-0x3. */
status = "disabled"; //控制器是否使能
};
在 Linux-5.4 版本內核中,與 Linux-4.9 內核配置有稍許差異,主要在于 clock 和 dma 的配置上:
spi1: spi@4026000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "allwinner,sun20i-spi"; //具體的設備,用于驅動和設備的綁定
reg = <0x0 0x04026000 0x0 0x1000>; //設備節點名稱
interrupts-extended = <&plic0 32 IRQ_TYPE_LEVEL_HIGH>;//總線中斷號、中斷類型
clocks = <&ccu CLK_PLL_PERIPH0>, <&ccu CLK_SPI1>, <&ccu CLK_BUS_SPI1>;//設備使用 的時
clock-names = "pll", "mod", "bus"; //設備使用的時鐘名稱
resets = <&ccu RST_BUS_SPI1>; //設備的reset時鐘
clock-frequency = <100000000>; //控制器的時鐘頻率
spi1_cs_number = <1>; //控制器cs腳數量
spi1_cs_bitmap = <1>; /* cs0- 0x1; cs1-0x2, cs0&cs1-0x3. */
dmas = <&dma 23>, <&dma 23>; //控制器使用的dms通道號
dma-names = "tx", "rx"; //控制器使用通道號對應的名字
status = "disabled"; //控制器是否使能
};
為了在 SPI 總線驅動代碼中區分每一個 SPI 控制器,需要在 Device Tree 中的 aliases 節點中為每一個 SPI 節點指定別名:
aliases {
soc_spi0 = &spi0;
soc_spi1 = &spi1;
...
};
別名形式為字符串 “spi” 加連續編號的數字,在 SPI 總線驅動程序中可以通過 of_alias_get_id() 函數獲取對應 SPI 控制器的數字編號,從而區別每一個 SPI 控制器。
其中內核版本為 Linux-4.9 的 spi1_pins_a, spi1_pins_b 的配置文件路徑為 kernel/linux-4.9/arch/arm64(32 位平臺為 arm)/boot/dts/sunxi/xxx-pinctrl.dtsi,具體配置如下所示:
spi1_pins_a: spi1@0 {
allwinner,pins = "PH4", "PH5", "PH6";
allwinner,pname = "spi1_sclk", "spi1_mosi",
"spi1_miso";
allwinner,function = "spi1";
allwinner,muxsel = <2>;
allwinner,drive = <1>;
allwinner,pull = <0>;
};
?
spi1_pins_b: spi1@1 {
allwinner,pins = "PH3";
allwinner,pname = "spi1_cs0";
allwinner,function = "spi1";
allwinner,muxsel = <2>;
allwinner,drive = <1>;
allwinner,pull = <1>; // only CS should be pulled up
};
?
spi1_pins_c: spi1@2 {
allwinner,pins = "PH3", "PH4", "PH5", "PH6";
allwinner,function = "io_disabled";
allwinner,muxsel = <7>;
allwinner,drive = <1>;
allwinner,pull = <0>;
};
內核版本為 Linux-5.4 的 spi1_pins_a, spi1_pins_b 的具體配置如下所示:
spi1_pins_a: spi1@0 {
pins = "PD11", "PD12", "PD13";
function = "spi1";
drive-strength = <10>;
};
?
spi1_pins_b: spi1@1 {
pins = "PD10";
function = "spi1";
drive-strength = <10>;
bias-pull-up; /* only CS should be pulled up */
};
?
spi1_pins_c: spi1@2 {
pins = "PD10", "PD11", "PD12", "PD13";
function = "gpio_in";
};
2.3.2 board.dts 配置說明
board.dts 用于保存每一個板級平臺設備差異化的信息的補充(如 demo 板,demo2.0 板,ver1 板等等),里面的配置信息會覆蓋上面的 device tree 默認配置信息。
board.dts 的路徑為/device/config/chips/{IC}/configs/{BOARD}/board.dts, 其中 SPI1 的具體配置如下:
說明
在 Linux-5.4 內核版本中對 board.dts 語法做了修改,不再支持同名節點覆蓋,使用 “&” 符號引用節點。
&spi1 {
clock-frequency = <100000000>;
pinctrl-0 = <&spi1_pins_a &spi1_pins_b>;
pinctrl-1 = <&spi1_pins_c>;
pinctrl-names = "default", "sleep";
spi_slave_mode = <0>;
status = "disabled";
?
spi_board1@0 {
device_type = "spi_board1";
compatible = "rohm,dh2228fv";
spi-max-frequency = <0x5f5e100>;
reg = <0x0>;
spi-rx-bus-width = <0x4>;
spi-tx-bus-width = <0x4>;
status = "disabled";
};
};
注意,如果要使用 spi slave 模式,請把 spi_slave_mode = <0> 修改為:spi_slave_mode = <1>。
spi_board1 還有一些可配置參數,如:
? spi-cpha 和 spi-cpol:配置 spi 的四種傳輸模式。
? spi-cs-high:配置 cs 引腳有效狀態時的電平。
spi1_pins_a, spi1_pins_b 、spi1_pins_c 的具體配置如下所示:
spi1_pins_a: spi1@0 {
pins = "PD11", "PD12", "PD13","PD14", "PD15"; /*clk mosi miso hold wp*/
function = "spi1";
drive-strength = <10>;
};
?
spi1_pins_b: spi1@1 {
pins = "PD10";
function = "spi1";
drive-strength = <10>;
bias-pull-up; // only CS should be pulled up
};
?
spi1_pins_c: spi1@2 {
allwinner,pins = "PD10", "PD11", "PD12", "PD13","PD14", "PD15";
allwinner,function = "gpio_in";
allwinner,muxsel = <0>;
drive-strength = <10>;
};
2.3.3 menuconfig 配置說明
在命令行中進入內核 linux 目錄,執行 make ARCH=arm64 menuconfig(32 位系統為 make ARCH=arm menuconfig) 進入配置主界面 (Linux-5.4 內核版本執行:./build.sh menuconfig),并按以下步驟操作。
選擇 Device Drivers 選項進入下一級配置,如下圖所示。
圖 2-1: Device Drivers 配置選項
選擇 SPI support 選項,進入下一級配置,如下圖所示。
圖 2-2: SPI support 配置選項
選擇 SUNXI SPI Controller 選項,可選擇直接編譯進內核,也可編譯成模塊。如下圖所示。
圖 2-3: SUNXI SPI Controller 配置選項
如果想要放開 spi 的一些調試打印,可以選上 Debug support for SPI drivers。
2.4 源碼結構介紹
SPI 總線驅動的源代碼位于內核在 drivers/spi 目錄下:
drivers/spi/
├── spi-sunxi.c // Sunxi平臺的SPI控制器驅動代碼
├── spi-sunxi.h // 為Sunxi平臺的SPI控制器驅動定義了一些宏、數據結構
2.5 驅動框架介紹
Linux 中 SPI 體系結構分為三個層次,如下圖所示。
圖 2-4: Linux SPI 體系結構圖
2.5.1 用戶空間
包括所有使用 SPI 設備的應用程序,在這一層用戶可以根據自己的實際需求,將 spi 設備進行一些特殊的處理,此時控制器驅動程序并不清楚和關注設備的具體功能,SPI 設備的具體功能是由用戶層程序完成的。例如,和 MTD 層交互以便把 SPI 接口的存儲設備實現為某個文件系統,和TTY 子系統交互把 SPI 設備實現為一個 TTY 設備,和網絡子系統交互以便把一個 SPI 設備實現為一個網絡設備,等等。當然,如果是一個專有的 SPI 設備,我們也可以按設備的協議要求,實現自己的專有協議驅動。同時這部分我們不用關注。
2.5.2 內核空間
內核空間我們同樣的會分為一下三部分:
2.5.2.1 SPI 控制器驅動層
考慮到連接在 SPI 控制器上的設備的可變性,在內核沒有配備相應的協議驅動程序,對于這種情況,內核為我們準備了通用的 SPI 設備驅動程序,該通用設備驅動程序向用戶空間提供了控制 SPI 控制的控制接口,具體的協議控制和數據傳輸工作交由用戶空間根據具體的設備來完成,在這種方式中,只能采用同步的方式和 SPI 設備進行通信,所以通常用于一些數據量較少的簡單SPI 設備。
這一層對應于我們內核中的 spidev.c 這個標準的 spi 設備驅動,或者我司的 spi–nand.c,支持 spi 協議的 nand 驅動等。針對特定的 SPI 設備,實現具體的功能,包括 read,write 以及 ioctl 等對用戶層操作的接口。SPI 總線驅動主要實現了適用于特定 SPI 控制器的總線讀寫方法,并注冊到 Linux 內核的 SPI 架構,SPI 外設就可以通過 SPI 架構完成設備和總線的適配。但是總線驅動本身并不會進行任何的通訊,它只是提供通訊的實現,等待設備驅動來調用其函數。SPI Core 的管理正好屏蔽了 SPI 總線驅動的差異,使得 SPI 設備驅動可以忽略各種總線控制器的不同,不用考慮其如何與硬件設備通訊的細節。
2.5.2.2 SPI 通用接口封裝層
為了簡化 SPI 驅動程序的編程工作,同時也為了降低協議驅動程序和控制器驅動程序的耦合程度,內核把控制器驅動和協議驅動的一些通用操作封裝成標準的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。這樣的好處是,對于控制器驅動程序,只要實現標準的接口回調 API,并把它注冊到通用接口層即可,無需直接和協議層驅動程序進行交互。而對于協議層驅動來說,只需通過通用接口層提供的 API 即可完成設備和驅動的注冊,并通過通用接口層的API 完成數據的傳輸,無需關注 SPI 控制器驅動的實現細節。這一層對應于驅動中的 spi.c 文件,是內核原生的文件。
2.5.2.3 SPI 控制器驅動層
為了簡化 SPI 驅動程序的編程工作,同時也為了降低協議驅動程序和控制器驅動程序的耦合程度,內核把控制器驅動和協議驅動的一些通用操作封裝成標準的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。這樣的好處是,對于控制器驅動程序,只要實現標準的接口回調 API,并把它注冊到通用接口層即可,無需直接和協議層驅動程序進行交互。而對于協議層驅動來說,只需通過通用接口層提供的 API 即可完成設備和驅動的注冊,并通過通用接口層的 API 完成數據的傳輸,無需關注 SPI 控制器驅動的實現細節。
這一層是我們關注的重點,在后文介紹中會詳細的展開進行介紹。
2.5.3 硬件
這一層是實際的物理器件,其中包括我們的 spi 控制器以及與控制器相連的各個 spi 子設備,通過 spi 總線能夠與 cpu 進行數據的交互。
3 接口描述
3.1 設備注冊接口
接口定義在 include/linux/spi/spi.h,主要包含 spi_register_driver 與 spi_unregister_driver 接口,其中給出了快速注冊的 SPI 設備驅動的宏 module_spi_driver(),定義如下:
#define module_spi_driver(__spi_driveSPI module_driver(__spi_driver, spi_register_driver, spi_unregister_driver)
3.1.1 spi_register_driver()
? 函數原型:int spi_register_driver(struct spi_driver *sdrv)
? 功能描述: 注冊一個 SPI 設備驅動。
? 參數說明:
? sdrv,spi_driver 類型的指針,其中包含了 SPI 設備的名稱、probe 等接口信息。
? 返回值:返回 0 表示成功,返回其他值表示失敗。
3.1.2 spi_unregister_driver()
? 函數原型:void spi_unregister_driver(struct spi_driver *sdrv)
? 功能描述:注銷一個 SPI 設備驅動。
? 參數說明:
? sdrv,spi_driver 類型的指針,其中包含了 SPI 設備的名稱、probe 等接口信息。
? 返回值:無
3.2 數據傳輸接口
SPI 設備驅動使用 “struct spi_message” 向 SPI 總線請求讀寫 I/O。一個 spi_message 中包含了一個操作序列,每一個操作稱作 spi_transfer,這樣方便 SPI 總線驅動中串行的執行一個個原子的序列。內核線程使用隊列實現了異步傳輸的功能,對于同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成即可返回,返回后,該發起者可以立刻又發起一個 message,而這時上一個 message 還沒有處理完。對于另外一個不同的發起者來說,也有可能同時發起一次 message 傳輸請求。
圖 3-1: Linux SPI 數據傳輸流程
struct spi_transfer { const void *tx_buf; void *rx_buf; unsigned len; dma_addr_t tx_dma; dma_addr_t rx_dma; unsigned cs_change:1; u8 bits_per_word; u16 delay_usecs; u32 speed_hz; struct list_head transfer_list; }; struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned actual_length; int status; struct list_head queue; void *state; };
3.2.1 spi_message_init()
? 函數原型:void spi_message_init(struct spi_message *m)
? 功能描述:初始化一個 SPI message 結構,主要是清零和初始化 transfer 隊列。
? 參數說明:
? m:spi_message 類型的指針。
? 返回值:無
3.2.2 spi_message_add_tail()
? 函數原型:void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
? 功能描述:向 SPI message 中添加一個 transfer。
? 參數說明:
? t: 指向待添加到 SPI transfer 結構;
? m:spi_message 類型的指針。
? 返回值:無
3.2.3 spi_sync()
? 函數原型:int spi_sync(struct spi_device *spi, struct spi_message *message)
? 功能描述:啟動、并等待 SPI 總線處理完指定的 SPI message。
? 參數說明:
? spi,指向當前的 SPI 設備;
? m,spi_message 類型的指針,其中有待處理的 SPI transfer 隊列。
? 返回值:0,成功;小于 0,失敗。
4 模塊使用范例
4.1 內核原生驅動范例
驅動文件在 drivers/spi/spidev.c,此驅動是 Linux 內核自帶的一個 spidev 通用驅動。其中調用 spi_register_driver() 注冊 SPI 驅動,方便使用者實現 SPI message 數據的讀寫。
static int __init spidev_init(void) { int status; /* Claim our 256 reserved device numbers. Then register a class * that will key udev/mdev to add/remove /dev nodes. Last, register * the driver which manages those device numbers. */ BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); if (status < 0) ? ? ? ?return status; ? ?spidev_class = class_create(THIS_MODULE, "spidev"); ? ?if (IS_ERR(spidev_class)) { ? ? ? ?unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); ? ? ? ?return PTR_ERR(spidev_class); ? ?} ? ?status = spi_register_driver(&spidev_spi_driver); ? ?if (status < 0) { ? ? ? ?class_destroy(spidev_class); ? ? ? ?unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); ? ?} ? ?return status; } module_init(spidev_init); static void __exit spidev_exit(void) { ? ?spi_unregister_driver(&spidev_spi_driver); ? ?class_destroy(spidev_class); ? ?unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } module_exit(spidev_exit);
同時需要在對應的 spi 控制器的 dts 下加上 spi 子設備的設備信息描述,具體的配置信息如下所示:
&spi1 { clock-frequency = <100000000>; pinctrl-0 = <&spi1_pins_a &spi1_pins_b>; pinctrl-1 = <&spi1_pins_c>; pinctrl-names = "default", "sleep"; spi_slave_mode = <0>; status = "disabled"; spi_board1@0 { device_type = "spi_board1"; compatible = "rohm,dh2228fv"; spi-max-frequency = <0x5f5e100>; reg = <0x0>; spi-rx-bus-width = <0x4>; spi-tx-bus-width = <0x4>; status = "disabled"; }; };
對于 spi 控制器的描述在這里不再重復的陳述,這里的 spi_board1@0 就是我們虛擬的一個 spi 從設備,
? device_type :表示設備的類型;
? compatible :驅動匹配信息;
? spi-max-frequency :從設備的最大頻率;
? reg :從設備的寄存器地址;
? spi-rx-bus-width:對從設備進行數據讀取時使用的 data 數據線個數;
? spi-tx-bus-width :對從設備進行數據寫入時使用的 data 數據線個數;
? status :從設備的狀態;
在 menuconfig(Device Drivers->SPI support)里面配置上 User mode SPI device driver support 選項。
圖 4-1: spidev
編譯燒錄固件之后會在小機文件系統的/dev 目錄下發現 spidevX.0(X=0~2) 設備,可以對 spidevX.0 進行讀寫操作。或者使用 Linux 自帶的 spi 工具:在 tina/lichee/linux-5.4/tools 目錄下, 運行如下命令:
make spi
然后在 tina/lichee/linux-5.4/tools/spi/下會有 spidev_test 可執行文件,拷貝到小機根文件系統中,運行如下命令即可進行測試:
/spidev_test -D /dev/spidevX.0
4.2 Slave 模式驅動范例
需要在 board.dts 中相應的 SPI 節點設備配置 spi_slave_mode = <1>。
4.2.1 Slave 寫數據
以 spidev1.0 設備為例,發送 0~9 十個數據:
#define DEVICE_NAME "/dev/spidev1.0" #define HEAD_LEN 5 #define PKT_MAX_LEN 0x40 #define STATUS_LEN 0x01 #define SUNXI_OP_WRITE 0x01 #define SUNXI_OP_READ 0x03 #define STATUS_WRITABLE 0x02 #define STATUS_READABLE 0x04 #define WRITE_DELAY 200 #define READ_DELAY 100000 void dump_data(unsigned char *buf, unsigned int len) { unsigned int i; unsigned char tmp[len*2], cnt = 0; for (i = 0; i < len; i++) { ? ? ? ?if (i%0x10== 0) ? ? ? ?cnt += sprintf(tmp + cnt, "0x%08x: ", i); ? ? ? ?cnt += sprintf(tmp + cnt, "%02x ", buf[i]); ? ? ? ?if ( (i%0x10== 0x0f) || (i == (len -1)) ) { ? ? ? ? ? ?printf("%sn", tmp); ? ? ? ? ? ?cnt = 0; ? ? ? ?} ? ?} } void batch_rand(char *buf, unsigned int length) { ? ?unsigned int i; ? ?srand(time(0)); ? ?for(i = 0; i < length; i++) { ? ?*(buf + i) = rand() % 256; ? ?} } int main(int argc, const char *argv[]) { ? ?unsigned int length = 0, test_len; ? ?char wbuf_head[HEAD_LEN] = {SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00}; ? ?char rbuf_head[HEAD_LEN] = {SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00}; ? ?char wbuf[PKT_MAX_LEN], rbuf[PKT_MAX_LEN], i, time; ? ?int fd, ret; test_len = 10;//send 10 numbers if (test_len > PKT_MAX_LEN) { printf("invalid argument, numbers must less 64Bn"); return -1; } wbuf_head[4] = test_len; rbuf_head[4] = test_len; for (i = 0; i < test_len; i++) wbuf[i] = i; printf("wbuf:n"); dump_data(wbuf, test_len); ? ?fd = open(DEVICE_NAME, O_RDWR); ? ?if (fd <= 0) { ? ? ? ?printf("Fail to to open %sn", DEVICE_NAME); ? ? ? ?ret = -1; ? ?return ret; ? ?} ? ?{//write ? ? ? ?if (write(fd, wbuf_head, HEAD_LEN) != HEAD_LEN) { ? ? ? ? ? ?printf("W Fail to write headn"); ? ? ? ? ? ?ret = -1; ? ? ? ? ? ?goto err; ? ? } else ? ? ? ?printf("W write head successfuln"); ? ? usleep(WRITE_DELAY); ? ? ? ?if (write(fd, wbuf, test_len) != test_len) { ? ? ? ? ? ?printf("W Fail to write datan"); ? ? ? ? ? ?ret = -1; ? ? ? ? ? ?goto err; ? ? ? ?} else ? ? ? ? ? ?printf("W write data successfuln"); ? ? ? ?usleep(READ_DELAY); ? ?} err: ? ?if (fd > 0) close(fd); return ret; }
4.2.2 Slave 讀數據
以 spidev1.0 設備為例,讀十個數據:
#define DEVICE_NAME "/dev/spidev1.0" #define HEAD_LEN 5 #define PKT_MAX_LEN 0x40 #define STATUS_LEN 0x01 #define SUNXI_OP_WRITE 0x01 #define SUNXI_OP_READ 0x03 #define STATUS_WRITABLE 0x02 #define STATUS_READABLE 0x04 #define WRITE_DELAY 200 #define READ_DELAY 100000 void dump_data(unsigned char *buf, unsigned int len) { unsigned int i; unsigned char tmp[len*2], cnt = 0; for (i = 0; i < len; i++) { ? ? ? ?if (i%0x10== 0) ? ? ? ?cnt += sprintf(tmp + cnt, "0x%08x: ", i); ? ? ? ?cnt += sprintf(tmp + cnt, "%02x ", buf[i]); ? ? ? ?if ( (i%0x10== 0x0f) || (i == (len -1)) ) { ? ? ? ? ? ?printf("%sn", tmp); ? ? ? ? ? ?cnt = 0; ? ? ? ?} ? ?} } void batch_rand(char *buf, unsigned int length) { ? ?unsigned int i; ? ?srand(time(0)); ? ?for(i = 0; i < length; i++) { ? ?*(buf + i) = rand() % 256; ? ?} } int main(int argc, const char *argv[]) { ? ?unsigned int length = 0, test_len; ? ?char wbuf_head[HEAD_LEN] = {SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00}; ? ?char rbuf_head[HEAD_LEN] = {SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00}; ? ?char wbuf[PKT_MAX_LEN], rbuf[PKT_MAX_LEN], i, time; ? ?int fd, ret; ? ?test_len = 10; ? ?if (test_len > PKT_MAX_LEN) { printf("inval argument, numbers must less 64Bn"); return -1; } wbuf_head[4] = test_len; rbuf_head[4] = test_len; fd = open(DEVICE_NAME, O_RDWR); if (fd <= 0) { ? ? ? ?printf("Fail to to open %sn", DEVICE_NAME); ? ? ? ?ret = -1; ? ? ? ?return ret; ? ?} ? ?{//read ? ? ? ?if (write(fd, rbuf_head, HEAD_LEN) != HEAD_LEN) { ? ? ? ? ? ?printf("R Fail to write headn"); ? ? ? ? ? ?ret = -1; ? ? ? ? ? ?goto err; ? ? ? ?} else ? ? ? ?printf("R write head successfuln"); ? ? ? ?usleep(READ_DELAY); ? ? ? ?if (read(fd, rbuf, test_len) != test_len) { ? ? ? ? ? ?printf("R Fail to read datan"); ? ? ? ? ? ?ret = -1; ? ? ? ? ? ?goto err; ? ? ? ?} else ? ? ? ? ? ?printf("R read data successfuln"); ? ? ? ? ? ?usleep(READ_DELAY); ? ?} ? ?printf("rbuf:n"); ? ?dump_data(rbuf, test_len); err: if (fd > 0) close(fd); return ret; }
4.2.3 Slave 使用 & 測試
4.2.3.1 環境搭建
4.2.3.1.1 硬件環境
本此測試使用兩塊開發板搭建環境,一塊做 master,一塊做 slave。
將 MASTER 與 SLAVE 的 SPI1 的 CS、CLK 按名字對應連接起來,MASTER 的 MOSI 接SLAVE 的 MOSI,MASTER 的 MISO 接 SLAVE 的 MISO,將兩塊開發板共地。
4.2.3.1.2 Menuconfig
打 開 menuconfig 的 CONFIG_SPI_SUNXI 與 CONFIG_SPI_SPIDEV,如下圖所示。
圖 4-2: menuconfig
4.2.3.1.3 DTS
設備樹路徑:device/config/chips/xxx(t507)/configs/xxx(demo2.0)/board.dts,添加以下節點:
spi1: spi@05011000 { pinctrl-0 = <&spi1_pins_a &spi1_pins_b>; pinctrl-1 = <&spi1_pins_c>; spi_slave_mode = <0>; status = "okay"; spi_board1 { device_type = "spi_board1"; compatible = "rohm,dh2228fv"; spi-max-frequency = <30000000>; reg = <0x0>; spi-rx-bus-width = <0x1>; spi-tx-bus-width = <0x1>; }; };
注:spi_slave_mode = <0> 為 Master 配置;spi_slave_mode = <1>,為 Slave 配置
4.2.3.2 測試
分別設置 Master 和 Salve 的 DTS,并編譯出對應固件,燒寫固件。
4.2.3.2.1 Slave
Slave 端執行下列命令,打開 Slave 的調試打印,這樣可以看到讀寫的數據。
4.2.3.3 測試結果
Maset source data 和 target data 打印數據一致,即表明測試通過。
-------------------------------------------- n test -------------------------------------------- W write head successful W write data successful source data: 0x00000000: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 0x00000010: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a R write head successful R read data successful target data: 0x00000000: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 0x00000010: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a slave function [PASS]
4.2.3.4 自定制說明
用戶可以自定制從設備功能,要操作從設備,需要發送 5 個 byte 的操作請求,說明如下:
第 1 個 Byte:操作碼
SUNXI_OP_WRITE 0x01 SUNXI_OP_READ 0x03 //讀寫是相對于master
第 2~4 個 Byte:地址(2 是高位地址)
第 5 給 Byte:長度(長度要求小于 64Byte)
4.2.3.4.1 操作碼添加
現在我們只支持讀寫操作,用戶自行拓展,在drivers/spi/spi-sunxi.c 的sunxi_spi_slave_handle_head函數中添加命令對應的操作函數
if (head->op_code == SUNXI_OP_WRITE) { sunxi_spi_slave_cpu_rx_config(sspi); } else if (head->op_code == SUNXI_OP_READ) { sunxi_spi_slave_cpu_tx_config(sspi); } else { dprintk(DEBUG_INFO, "[spi%d] pkt head opcode errn", sspi->master->bus_num); ret = -1; goto err1; }
4.2.3.4.2 地址及緩存
第 2~4 個 Byte 的地址是用于指定讀寫緩存數據,緩存大小宏在drivers/spi/spi-slave-protocol.h中定義,用戶自行設置,單位 Byte
#define STORAGE_SIZE 128
4.2.3.4.3 長度
每次讀寫數據長度要求小于 64Byte,由于 SPI RX/TX 的 FIFO 緩存大小為 64Byte,為了防止讀寫時有一端設備沒有及時拿走數據導致 buf 溢出,一次傳輸要求長度小于 64Byte,如果要讀寫大于 64Byte 數據,可分多次進行傳輸,地址偏移好就沒問題。
5 FAQ
5.1 調試節點
5.1.1 /sys/module/spi_sunxi/parameters/debug
默認情況下 debug 為 1,不打開調試信息。
echo 255 > /sys/module/spi_sunxi/parameters/debug
即可打開調試信息。
5.1.2 /sys/devices/platform/soc/spi1/info
此節點文件可以打印出當前 SPI1 通道的一些硬件資源信息。
cat /sys/devices/platform/soc/spi1/info
5.1.3 /sys/devices/platform/soc/spi1/status
此節點文件可以打印出當前 SPI1 通道的一些運行狀態信息,包括控制器的各寄存器值。
cat /sys/devices/platform/soc/spi1/status
5.2 常見問題
5.3 dts 中設置使能不生效
問題現象:在 board.dts 中配置 spi 的 statue 狀態為 “okay”,但是啟動 Linux 內核卻發現 spi控制器未使能。問題分析:可能狀態配置有誤,亦或者錯誤使用其他的控制器例如 spi0。
問題排查步驟:
? 步驟 1:這種問題一般是由于在設備樹里,你的設備依賴了別的設備,但是這個設備沒能 probe 成功,從而導致你的設備無法 probe。建議對 spi 依賴的 dma 模塊進行排查,檢查 dma 在 menuconfig 中是否被打開;
? 步驟 2:在 out/目錄下搜索.sunxi.dts 并打開:
find -name ".sunxi.dts"
在文件里找到對應的節點,檢查對應的 spi 是否配置成功。
? 步驟 3:在小機 uboot 控制臺通過 fdt list spi* 命令查看 dts,是否使能 SPI 成功(status =“okay”),如果還是 disable,則可能 spi 在 uboot 階段被 disable 掉了(一般 spi0 會保留給 flash 使用,spi0 會在 uboot 階段關閉掉)。
5.4 SPI-Flash 數據傳輸異常
問題現象:寫入與讀出數據不一致。
? 步驟 1:進行兼容性排查。以 nor flash 為例,有些物料兼容性不好,會造成讀寫出錯。這個時候可以先確認下次款物料是否在支持列表內。若不在,試著更換物料再做測試。
? 步驟 2:驅動調試。此類問題范圍比較大,但是可以從基礎調試手段著手跟蹤調試。一般思路是打開數據打印,看寫入的值是否傳到 SPI 總線驅動處理,然后同樣的看 SPI 總線驅動剛讀出來的數據與前面寫的打印數據是否一致,來判斷是哪個環節造成讀寫出錯,這個辦法可以拓展到其他層次,以確認是文件系統層、MTD 層、SPI 總線驅動層的讀或寫問題。
-
模塊
+關注
關注
7文章
2716瀏覽量
47543 -
內核
+關注
關注
3文章
1375瀏覽量
40311 -
Linux
+關注
關注
87文章
11313瀏覽量
209748 -
SPI
+關注
關注
17文章
1707瀏覽量
91702
發布評論請先 登錄
相關推薦
評論