0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux SPI開發(fā)指南

嵌入式Linux那些事 ? 來源:嵌入式Linux那些事 ? 作者:嵌入式Linux那些事 ? 2023-03-06 10:23 ? 次閱讀

Linux SPI 開發(fā)指南

1 前言

1.1 文檔簡介

介紹 SPI 模塊的使用方法,方便開發(fā)人員使用。

1.2 目標(biāo)讀者

SPI 模塊的驅(qū)動開發(fā)/維護(hù)人員。

1.3 適用范圍

表 1-1: 適用產(chǎn)品列表

內(nèi)核版本 驅(qū)動文件
Linux-4.9 spi-sunxi.c
Linux-5.4 spi-sunxi.c

2 模塊介紹

2.1 模塊功能介紹

SPI 是一種高速、高效率的串行接口技術(shù)。通常由一個主模塊和一個或多個從模塊組成,主模塊選擇一個從模塊進(jìn)行同步通信,從而完成數(shù)據(jù)的交換,被廣泛應(yīng)用于 ADC、LCD 等設(shè)備與 MCU 之間。全志的 spi 控制器支持以下功能:

? 全雙工同步串行接口。

? 支持 5 種時鐘源選擇。

? 支持 master 和 slave 兩種配置。

? 四個 cs 片選支持。

? 8bit 寬度和 64 字節(jié) fifo 深度。

? cs 和 clk 的極性和相位可配置。

? 支持使用 DMA

? 支持四種通信模式。

? 批量生產(chǎn)支持最大的 io 速率 100MHz。

? 支持 3 線、4 線 SPI 模式。

? 支持可編程串行行數(shù)據(jù)幀長:0~32bits。

? 支持 Standard SPI/Dual-Output/Dual-input SPI/Dual i/O SPI/ 和 Quad-Output/Quad Input SPI。

2.2 相關(guān)術(shù)語介紹

2.2.1 硬件術(shù)語

表 2-1: 硬件術(shù)語

術(shù)語 解釋說明
SPI Serial Peripheral Interface,同步串行外設(shè)接口

2.2.2 軟件術(shù)語

表 2-2: 軟件術(shù)語

術(shù)語 解釋說明
Sunxi 指 Allwinner 的一系列 SOC 硬件平臺
SPI Master SPI 主設(shè)備
SPI Device 指 SPI 外部設(shè)備

2.3 模塊配置介紹

2.3.1 device tree 配置說明

在不同的 Sunxi 硬件平臺中,SPI 控制器的數(shù)目也不同,但對于每一個 SPI 控制器來說,在設(shè)備樹中配置參數(shù)相似,平臺設(shè)備樹文件的路徑為:kernel/內(nèi)核版本/arch/arm64(32 位平臺arm)/boot/dts/sunxi/CHIP.dtsi(CHIP 為研發(fā)代號,如 sun50iw10p1 等),對于配置 SPI1而言,如下:

spi1: spi@05011000 {

#address-cells = <1>;

#size-cells = <0>;

compatible = "allwinner,sun50i-spi"; //具體的設(shè)備,用于驅(qū)動和設(shè)備的綁定

device_type = "spi1"; //設(shè)備節(jié)點(diǎn)名稱

reg = <0x0 0x05011000 0x0 0x1000>; //總線寄存器配置

interrupts = ;//總線中斷號、中斷類型

clocks = <&clk_pll_periph0>, <&clk_spi1>; //設(shè)備使用的時鐘

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腳數(shù)量

spi1_cs_bitmap = <1>; /* cs0- 0x1; cs1-0x2, cs0&cs1-0x3. */

status = "disabled"; //控制器是否使能

};

在 Linux-5.4 版本內(nèi)核中,與 Linux-4.9 內(nèi)核配置有稍許差異,主要在于 clock 和 dma 的配置上:

spi1: spi@4026000 {

#address-cells = <1>;

#size-cells = <0>;

compatible = "allwinner,sun20i-spi"; //具體的設(shè)備,用于驅(qū)動和設(shè)備的綁定

reg = <0x0 0x04026000 0x0 0x1000>; //設(shè)備節(jié)點(diǎn)名稱

interrupts-extended = <&plic0 32 IRQ_TYPE_LEVEL_HIGH>;//總線中斷號、中斷類型

clocks = <&ccu CLK_PLL_PERIPH0>, <&ccu CLK_SPI1>, <&ccu CLK_BUS_SPI1>;//設(shè)備使用 的時

clock-names = "pll", "mod", "bus"; //設(shè)備使用的時鐘名稱

resets = <&ccu RST_BUS_SPI1>; //設(shè)備的reset時鐘

clock-frequency = <100000000>; //控制器的時鐘頻率

spi1_cs_number = <1>; //控制器cs腳數(shù)量

spi1_cs_bitmap = <1>; /* cs0- 0x1; cs1-0x2, cs0&cs1-0x3. */

dmas = <&dma 23>, <&dma 23>; //控制器使用的dms通道號

dma-names = "tx", "rx"; //控制器使用通道號對應(yīng)的名字

status = "disabled"; //控制器是否使能

};

為了在 SPI 總線驅(qū)動代碼中區(qū)分每一個 SPI 控制器,需要在 Device Tree 中的 aliases 節(jié)點(diǎn)中為每一個 SPI 節(jié)點(diǎn)指定別名:

aliases {

soc_spi0 = &spi0;

soc_spi1 = &spi1;

...

};

別名形式為字符串 “spi” 加連續(xù)編號的數(shù)字,在 SPI 總線驅(qū)動程序中可以通過 of_alias_get_id() 函數(shù)獲取對應(yīng) SPI 控制器的數(shù)字編號,從而區(qū)別每一個 SPI 控制器。

其中內(nèi)核版本為 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>;

};

內(nèi)核版本為 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 用于保存每一個板級平臺設(shè)備差異化的信息的補(bǔ)充(如 demo 板,demo2.0 板,ver1 板等等),里面的配置信息會覆蓋上面的 device tree 默認(rèn)配置信息。

board.dts 的路徑為/device/config/chips/{IC}/configs/{BOARD}/board.dts, 其中 SPI1 的具體配置如下:

說明

Linux-5.4 內(nèi)核版本中對 board.dts 語法做了修改,不再支持同名節(jié)點(diǎn)覆蓋,使用 “&” 符號引用節(jié)點(diǎn)。

&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 還有一些可配置參數(shù),如:

? spi-cpha 和 spi-cpol:配置 spi 的四種傳輸模式。

? spi-cs-high:配置 cs 引腳有效狀態(tài)時的電平。

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 配置說明

在命令行中進(jìn)入內(nèi)核 linux 目錄,執(zhí)行 make ARCH=arm64 menuconfig(32 位系統(tǒng)為 make ARCH=arm menuconfig) 進(jìn)入配置主界面 (Linux-5.4 內(nèi)核版本執(zhí)行:./build.sh menuconfig),并按以下步驟操作。

選擇 Device Drivers 選項(xiàng)進(jìn)入下一級配置,如下圖所示。

pYYBAGQFTomAC2F-AAQYhtOBlQk971.png

圖 2-1: Device Drivers 配置選項(xiàng)

選擇 SPI support 選項(xiàng),進(jìn)入下一級配置,如下圖所示。

poYBAGQFTomAYtiKAAQYhtOBlQk614.png

圖 2-2: SPI support 配置選項(xiàng)

選擇 SUNXI SPI Controller 選項(xiàng),可選擇直接編譯進(jìn)內(nèi)核,也可編譯成模塊。如下圖所示。

poYBAGQFToqAMVWsAAP54RrPAek229.png

圖 2-3: SUNXI SPI Controller 配置選項(xiàng)

如果想要放開 spi 的一些調(diào)試打印,可以選上 Debug support for SPI drivers。

2.4 源碼結(jié)構(gòu)介紹

SPI 總線驅(qū)動的源代碼位于內(nèi)核在 drivers/spi 目錄下:

drivers/spi/

├── spi-sunxi.c // Sunxi平臺的SPI控制器驅(qū)動代碼

├── spi-sunxi.h // 為Sunxi平臺的SPI控制器驅(qū)動定義了一些宏、數(shù)據(jù)結(jié)構(gòu)

2.5 驅(qū)動框架介紹

Linux 中 SPI 體系結(jié)構(gòu)分為三個層次,如下圖所示。

pYYBAGQFTouAPl9EAAECUfeOss0305.png

圖 2-4: Linux SPI 體系結(jié)構(gòu)圖

2.5.1 用戶空間

包括所有使用 SPI 設(shè)備的應(yīng)用程序,在這一層用戶可以根據(jù)自己的實(shí)際需求,將 spi 設(shè)備進(jìn)行一些特殊的處理,此時控制器驅(qū)動程序并不清楚和關(guān)注設(shè)備的具體功能,SPI 設(shè)備的具體功能是由用戶層程序完成的。例如,和 MTD 層交互以便把 SPI 接口的存儲設(shè)備實(shí)現(xiàn)為某個文件系統(tǒng),和TTY 子系統(tǒng)交互把 SPI 設(shè)備實(shí)現(xiàn)為一個 TTY 設(shè)備,和網(wǎng)絡(luò)子系統(tǒng)交互以便把一個 SPI 設(shè)備實(shí)現(xiàn)為一個網(wǎng)絡(luò)設(shè)備,等等。當(dāng)然,如果是一個專有的 SPI 設(shè)備,我們也可以按設(shè)備的協(xié)議要求,實(shí)現(xiàn)自己的專有協(xié)議驅(qū)動。同時這部分我們不用關(guān)注。

2.5.2 內(nèi)核空間

內(nèi)核空間我們同樣的會分為一下三部分:

2.5.2.1 SPI 控制器驅(qū)動層

考慮到連接在 SPI 控制器上的設(shè)備的可變性,在內(nèi)核沒有配備相應(yīng)的協(xié)議驅(qū)動程序,對于這種情況,內(nèi)核為我們準(zhǔn)備了通用的 SPI 設(shè)備驅(qū)動程序,該通用設(shè)備驅(qū)動程序向用戶空間提供了控制 SPI 控制的控制接口,具體的協(xié)議控制和數(shù)據(jù)傳輸工作交由用戶空間根據(jù)具體的設(shè)備來完成,在這種方式中,只能采用同步的方式和 SPI 設(shè)備進(jìn)行通信,所以通常用于一些數(shù)據(jù)量較少的簡單SPI 設(shè)備。

這一層對應(yīng)于我們內(nèi)核中的 spidev.c 這個標(biāo)準(zhǔn)的 spi 設(shè)備驅(qū)動,或者我司的 spi–nand.c,支持 spi 協(xié)議的 nand 驅(qū)動等。針對特定的 SPI 設(shè)備,實(shí)現(xiàn)具體的功能,包括 read,write 以及 ioctl 等對用戶層操作的接口。SPI 總線驅(qū)動主要實(shí)現(xiàn)了適用于特定 SPI 控制器的總線讀寫方法,并注冊到 Linux 內(nèi)核的 SPI 架構(gòu),SPI 外設(shè)就可以通過 SPI 架構(gòu)完成設(shè)備和總線的適配。但是總線驅(qū)動本身并不會進(jìn)行任何的通訊,它只是提供通訊的實(shí)現(xiàn),等待設(shè)備驅(qū)動來調(diào)用其函數(shù)。SPI Core 的管理正好屏蔽了 SPI 總線驅(qū)動的差異,使得 SPI 設(shè)備驅(qū)動可以忽略各種總線控制器的不同,不用考慮其如何與硬件設(shè)備通訊的細(xì)節(jié)。

2.5.2.2 SPI 通用接口封裝層

為了簡化 SPI 驅(qū)動程序的編程工作,同時也為了降低協(xié)議驅(qū)動程序和控制器驅(qū)動程序的耦合程度,內(nèi)核把控制器驅(qū)動和協(xié)議驅(qū)動的一些通用操作封裝成標(biāo)準(zhǔn)的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。這樣的好處是,對于控制器驅(qū)動程序,只要實(shí)現(xiàn)標(biāo)準(zhǔn)的接口回調(diào) API,并把它注冊到通用接口層即可,無需直接和協(xié)議層驅(qū)動程序進(jìn)行交互。而對于協(xié)議層驅(qū)動來說,只需通過通用接口層提供的 API 即可完成設(shè)備和驅(qū)動的注冊,并通過通用接口層的API 完成數(shù)據(jù)的傳輸,無需關(guān)注 SPI 控制器驅(qū)動的實(shí)現(xiàn)細(xì)節(jié)。這一層對應(yīng)于驅(qū)動中的 spi.c 文件,是內(nèi)核原生的文件。

2.5.2.3 SPI 控制器驅(qū)動層

為了簡化 SPI 驅(qū)動程序的編程工作,同時也為了降低協(xié)議驅(qū)動程序和控制器驅(qū)動程序的耦合程度,內(nèi)核把控制器驅(qū)動和協(xié)議驅(qū)動的一些通用操作封裝成標(biāo)準(zhǔn)的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。這樣的好處是,對于控制器驅(qū)動程序,只要實(shí)現(xiàn)標(biāo)準(zhǔn)的接口回調(diào) API,并把它注冊到通用接口層即可,無需直接和協(xié)議層驅(qū)動程序進(jìn)行交互。而對于協(xié)議層驅(qū)動來說,只需通過通用接口層提供的 API 即可完成設(shè)備和驅(qū)動的注冊,并通過通用接口層的 API 完成數(shù)據(jù)的傳輸,無需關(guān)注 SPI 控制器驅(qū)動的實(shí)現(xiàn)細(xì)節(jié)。

這一層是我們關(guān)注的重點(diǎn),在后文介紹中會詳細(xì)的展開進(jìn)行介紹。

2.5.3 硬件

這一層是實(shí)際的物理器件,其中包括我們的 spi 控制器以及與控制器相連的各個 spi 子設(shè)備,通過 spi 總線能夠與 cpu 進(jìn)行數(shù)據(jù)的交互。

3 接口描述

3.1 設(shè)備注冊接口

接口定義在 include/linux/spi/spi.h,主要包含 spi_register_driver 與 spi_unregister_driver 接口,其中給出了快速注冊的 SPI 設(shè)備驅(qū)動的宏 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()

? 函數(shù)原型:int spi_register_driver(struct spi_driver *sdrv)

? 功能描述: 注冊一個 SPI 設(shè)備驅(qū)動。

? 參數(shù)說明:

? sdrv,spi_driver 類型的指針,其中包含了 SPI 設(shè)備的名稱、probe 等接口信息。

? 返回值:返回 0 表示成功,返回其他值表示失敗。

3.1.2 spi_unregister_driver()

? 函數(shù)原型:void spi_unregister_driver(struct spi_driver *sdrv)

? 功能描述:注銷一個 SPI 設(shè)備驅(qū)動。

? 參數(shù)說明:

? sdrv,spi_driver 類型的指針,其中包含了 SPI 設(shè)備的名稱、probe 等接口信息。

? 返回值:無

3.2 數(shù)據(jù)傳輸接口

SPI 設(shè)備驅(qū)動使用 “struct spi_message” 向 SPI 總線請求讀寫 I/O。一個 spi_message 中包含了一個操作序列,每一個操作稱作 spi_transfer,這樣方便 SPI 總線驅(qū)動中串行的執(zhí)行一個個原子的序列。內(nèi)核線程使用隊(duì)列實(shí)現(xiàn)了異步傳輸?shù)墓δ?,對于同一個數(shù)據(jù)傳輸?shù)陌l(fā)起者,既然異步方式無需等待數(shù)據(jù)傳輸完成即可返回,返回后,該發(fā)起者可以立刻又發(fā)起一個 message,而這時上一個 message 還沒有處理完。對于另外一個不同的發(fā)起者來說,也有可能同時發(fā)起一次 message 傳輸請求。

poYBAGQFToyAdHYgAAJCUlrAFHA834.png

圖 3-1: Linux SPI 數(shù)據(jù)傳輸流程

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()

? 函數(shù)原型:void spi_message_init(struct spi_message *m)

? 功能描述:初始化一個 SPI message 結(jié)構(gòu),主要是清零和初始化 transfer 隊(duì)列。

? 參數(shù)說明:

? m:spi_message 類型的指針。

? 返回值:無

3.2.2 spi_message_add_tail()

? 函數(shù)原型:void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

? 功能描述:向 SPI message 中添加一個 transfer。

? 參數(shù)說明:

? t: 指向待添加到 SPI transfer 結(jié)構(gòu);

? m:spi_message 類型的指針。

? 返回值:無

3.2.3 spi_sync()

? 函數(shù)原型:int spi_sync(struct spi_device *spi, struct spi_message *message)

? 功能描述:啟動、并等待 SPI 總線處理完指定的 SPI message。

? 參數(shù)說明:

? spi,指向當(dāng)前的 SPI 設(shè)備;

? m,spi_message 類型的指針,其中有待處理的 SPI transfer 隊(duì)列。

? 返回值:0,成功;小于 0,失敗。

4 模塊使用范例

4.1 內(nèi)核原生驅(qū)動范例

驅(qū)動文件在 drivers/spi/spidev.c,此驅(qū)動是 Linux 內(nèi)核自帶的一個 spidev 通用驅(qū)動。其中調(diào)用 spi_register_driver() 注冊 SPI 驅(qū)動,方便使用者實(shí)現(xiàn) SPI message 數(shù)據(jù)的讀寫。

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);

同時需要在對應(yīng)的 spi 控制器的 dts 下加上 spi 子設(shè)備的設(shè)備信息描述,具體的配置信息如下所示:

&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 控制器的描述在這里不再重復(fù)的陳述,這里的 spi_board1@0 就是我們虛擬的一個 spi 從設(shè)備,

? device_type :表示設(shè)備的類型;

? compatible :驅(qū)動匹配信息;

? spi-max-frequency :從設(shè)備的最大頻率;

? reg :從設(shè)備的寄存器地址;

? spi-rx-bus-width:對從設(shè)備進(jìn)行數(shù)據(jù)讀取時使用的 data 數(shù)據(jù)線個數(shù);

? spi-tx-bus-width :對從設(shè)備進(jìn)行數(shù)據(jù)寫入時使用的 data 數(shù)據(jù)線個數(shù);

? status :從設(shè)備的狀態(tài);

在 menuconfig(Device Drivers->SPI support)里面配置上 User mode SPI device driver support 選項(xiàng)。

pYYBAGQFToyAKQIVAAC6ddiaJiI271.png

圖 4-1: spidev

編譯燒錄固件之后會在小機(jī)文件系統(tǒng)的/dev 目錄下發(fā)現(xiàn) spidevX.0(X=0~2) 設(shè)備,可以對 spidevX.0 進(jìn)行讀寫操作?;蛘呤褂?Linux 自帶的 spi 工具:在 tina/lichee/linux-5.4/tools 目錄下, 運(yùn)行如下命令:

make spi

然后在 tina/lichee/linux-5.4/tools/spi/下會有 spidev_test 可執(zhí)行文件,拷貝到小機(jī)根文件系統(tǒng)中,運(yùn)行如下命令即可進(jìn)行測試:

/spidev_test -D /dev/spidevX.0

4.2 Slave 模式驅(qū)動范例

需要在 board.dts 中相應(yīng)的 SPI 節(jié)點(diǎn)設(shè)備配置 spi_slave_mode = <1>。

4.2.1 Slave 寫數(shù)據(jù)

以 spidev1.0 設(shè)備為例,發(fā)送 0~9 十個數(shù)據(jù):

#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 讀數(shù)據(jù)

以 spidev1.0 設(shè)備為例,讀十個數(shù)據(jù):

#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 環(huán)境搭建

4.2.3.1.1 硬件環(huán)境

本此測試使用兩塊開發(fā)板搭建環(huán)境,一塊做 master,一塊做 slave。

將 MASTER 與 SLAVE 的 SPI1 的 CS、CLK 按名字對應(yīng)連接起來,MASTER 的 MOSI 接SLAVE 的 MOSI,MASTER 的 MISO 接 SLAVE 的 MISO,將兩塊開發(fā)板共地。

4.2.3.1.2 Menuconfig

打 開 menuconfig 的 CONFIG_SPI_SUNXI 與 CONFIG_SPI_SPIDEV,如下圖所示。

poYBAGQFTo2AN1esAANduDa1r-0132.png

圖 4-2: menuconfig

4.2.3.1.3 DTS

設(shè)備樹路徑:device/config/chips/xxx(t507)/configs/xxx(demo2.0)/board.dts,添加以下節(jié)點(diǎn):

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 測試

分別設(shè)置 Master 和 Salve 的 DTS,并編譯出對應(yīng)固件,燒寫固件。

4.2.3.2.1 Slave

Slave 端執(zhí)行下列命令,打開 Slave 的調(diào)試打印,這樣可以看到讀寫的數(shù)據(jù)。

4.2.3.3 測試結(jié)果

Maset source data 和 target data 打印數(shù)據(jù)一致,即表明測試通過。

-------------------------------------------- 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 自定制說明

用戶可以自定制從設(shè)備功能,要操作從設(shè)備,需要發(fā)送 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 操作碼添加

現(xiàn)在我們只支持讀寫操作,用戶自行拓展,在drivers/spi/spi-sunxi.c 的sunxi_spi_slave_handle_head函數(shù)中添加命令對應(yīng)的操作函數(shù)

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 的地址是用于指定讀寫緩存數(shù)據(jù),緩存大小宏在drivers/spi/spi-slave-protocol.h中定義,用戶自行設(shè)置,單位 Byte

#define STORAGE_SIZE 128

4.2.3.4.3 長度

每次讀寫數(shù)據(jù)長度要求小于 64Byte,由于 SPI RX/TX 的 FIFO 緩存大小為 64Byte,為了防止讀寫時有一端設(shè)備沒有及時拿走數(shù)據(jù)導(dǎo)致 buf 溢出,一次傳輸要求長度小于 64Byte,如果要讀寫大于 64Byte 數(shù)據(jù),可分多次進(jìn)行傳輸,地址偏移好就沒問題。

5 FAQ

5.1 調(diào)試節(jié)點(diǎn)

5.1.1 /sys/module/spi_sunxi/parameters/debug

默認(rèn)情況下 debug 為 1,不打開調(diào)試信息。

echo 255 > /sys/module/spi_sunxi/parameters/debug

即可打開調(diào)試信息。

5.1.2 /sys/devices/platform/soc/spi1/info

此節(jié)點(diǎn)文件可以打印出當(dāng)前 SPI1 通道的一些硬件資源信息。

cat /sys/devices/platform/soc/spi1/info

5.1.3 /sys/devices/platform/soc/spi1/status

此節(jié)點(diǎn)文件可以打印出當(dāng)前 SPI1 通道的一些運(yùn)行狀態(tài)信息,包括控制器的各寄存器值。

cat /sys/devices/platform/soc/spi1/status

5.2 常見問題

5.3 dts 中設(shè)置使能不生效

問題現(xiàn)象:在 board.dts 中配置 spi 的 statue 狀態(tài)為 “okay”,但是啟動 Linux 內(nèi)核卻發(fā)現(xiàn) spi控制器未使能。問題分析:可能狀態(tài)配置有誤,亦或者錯誤使用其他的控制器例如 spi0。

問題排查步驟:

? 步驟 1:這種問題一般是由于在設(shè)備樹里,你的設(shè)備依賴了別的設(shè)備,但是這個設(shè)備沒能 probe 成功,從而導(dǎo)致你的設(shè)備無法 probe。建議對 spi 依賴的 dma 模塊進(jìn)行排查,檢查 dma 在 menuconfig 中是否被打開;

? 步驟 2:在 out/目錄下搜索.sunxi.dts 并打開:

find -name ".sunxi.dts"

在文件里找到對應(yīng)的節(jié)點(diǎn),檢查對應(yīng)的 spi 是否配置成功。

? 步驟 3:在小機(jī) uboot 控制臺通過 fdt list spi* 命令查看 dts,是否使能 SPI 成功(status =“okay”),如果還是 disable,則可能 spi 在 uboot 階段被 disable 掉了(一般 spi0 會保留給 flash 使用,spi0 會在 uboot 階段關(guān)閉掉)。

5.4 SPI-Flash 數(shù)據(jù)傳輸異常

問題現(xiàn)象:寫入與讀出數(shù)據(jù)不一致。

? 步驟 1:進(jìn)行兼容性排查。以 nor flash 為例,有些物料兼容性不好,會造成讀寫出錯。這個時候可以先確認(rèn)下次款物料是否在支持列表內(nèi)。若不在,試著更換物料再做測試。

? 步驟 2:驅(qū)動調(diào)試。此類問題范圍比較大,但是可以從基礎(chǔ)調(diào)試手段著手跟蹤調(diào)試。一般思路是打開數(shù)據(jù)打印,看寫入的值是否傳到 SPI 總線驅(qū)動處理,然后同樣的看 SPI 總線驅(qū)動剛讀出來的數(shù)據(jù)與前面寫的打印數(shù)據(jù)是否一致,來判斷是哪個環(huán)節(jié)造成讀寫出錯,這個辦法可以拓展到其他層次,以確認(rèn)是文件系統(tǒng)層、MTD 層、SPI 總線驅(qū)動層的讀或?qū)憜栴}。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 模塊
    +關(guān)注

    關(guān)注

    7

    文章

    2658

    瀏覽量

    47294
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1361

    瀏覽量

    40185
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11212

    瀏覽量

    208722
  • SPI
    SPI
    +關(guān)注

    關(guān)注

    17

    文章

    1688

    瀏覽量

    91215
收藏 人收藏

    評論

    相關(guān)推薦

    Linux SPI-NAND 驅(qū)動開發(fā)指南

    Linux SPI-NAND 驅(qū)動開發(fā)指南 1 概述1.1 編寫目的1.2 適用范圍1.3 相關(guān)人員3 流程設(shè)計3.1 體系結(jié)構(gòu)3.2 源碼結(jié)構(gòu)3.3 關(guān)鍵數(shù)據(jù)定義3.3.1 flash 設(shè)備信息
    的頭像 發(fā)表于 03-06 10:11 ?1508次閱讀
    <b class='flag-5'>Linux</b> <b class='flag-5'>SPI</b>-NAND 驅(qū)動<b class='flag-5'>開發(fā)指南</b>

    EAC0945 linux開發(fā)指南

    EAC0945 linux開發(fā)指南
    發(fā)表于 09-28 12:40

    EAC0945 linux開發(fā)指南

    `EAC0945 linux開發(fā)指南`
    發(fā)表于 10-31 12:18

    Rockchip Linux SDK uboot logo開發(fā)指南

    arm嵌入式vs-rk3399 板卡uboot logo 開發(fā)指南概述:本文檔主要介紹 rockchip linux sdk uboot logo 顯示的相關(guān)功能、配置以及開發(fā)過程中的注意事項(xiàng)。適用于 rockhip
    發(fā)表于 10-09 08:12

    CPLD FPGA高級應(yīng)用開發(fā)指南

    CPLD FPGA高級應(yīng)用開發(fā)指南
    發(fā)表于 04-15 10:56 ?58次下載
    CPLD FPGA高級應(yīng)用<b class='flag-5'>開發(fā)指南</b>

    Tiny6410 Linux開發(fā)指南詳解

    Tiny6410 Linux 開發(fā)指南
    發(fā)表于 07-08 17:12 ?210次下載
    Tiny6410 <b class='flag-5'>Linux</b><b class='flag-5'>開發(fā)指南</b>詳解

    A64開發(fā)板LCD開發(fā)指南

    A64開發(fā)板LCD開發(fā)指南,驅(qū)動開發(fā)指南
    發(fā)表于 06-21 17:02 ?0次下載

    彩光燈開發(fā)指南

    彩光燈開發(fā)指南
    發(fā)表于 12-29 20:15 ?0次下載

    Linux的平臺下Mini210S裸機(jī)程序開發(fā)指南

    Linux的平臺下Mini210S裸機(jī)程序開發(fā)指南
    發(fā)表于 10-29 10:52 ?59次下載
    <b class='flag-5'>Linux</b>的平臺下Mini210S裸機(jī)程序<b class='flag-5'>開發(fā)指南</b>

    Rockchip Linux SDK的開發(fā)指南的詳細(xì)資料說明

    本文檔的主要內(nèi)容詳細(xì)介紹的是Rockchip Linux SDK的開發(fā)指南的詳細(xì)資料說明。
    發(fā)表于 01-10 17:17 ?74次下載
    Rockchip <b class='flag-5'>Linux</b> SDK的<b class='flag-5'>開發(fā)指南</b>的詳細(xì)資料說明

    迅為RK3399開發(fā)板嵌入式linux開發(fā)指南

    迅為RK3399開發(fā)板嵌入式linux開發(fā)指南迅為RK3399開發(fā)板發(fā)布《北京迅為嵌入式linux開發(fā)指
    發(fā)表于 11-01 16:58 ?76次下載
    迅為RK3399<b class='flag-5'>開發(fā)</b>板嵌入式<b class='flag-5'>linux</b><b class='flag-5'>開發(fā)指南</b>

    Tina_Linux_系統(tǒng)軟件開發(fā)指南

    Tina_Linux_系統(tǒng)軟件開發(fā)指南
    的頭像 發(fā)表于 03-02 15:25 ?1747次閱讀
    Tina_<b class='flag-5'>Linux</b>_系統(tǒng)軟件<b class='flag-5'>開發(fā)指南</b>

    Tina Linux配置開發(fā)指南

    Tina Linux配置開發(fā)指南
    的頭像 發(fā)表于 03-02 15:28 ?1.6w次閱讀
    Tina <b class='flag-5'>Linux</b>配置<b class='flag-5'>開發(fā)指南</b>

    Linux NOR開發(fā)指南

    Linux NOR開發(fā)指南
    的頭像 發(fā)表于 03-06 09:55 ?908次閱讀
    <b class='flag-5'>Linux</b> NOR<b class='flag-5'>開發(fā)指南</b>

    【北京迅為】itop-龍芯2k1000開發(fā)指南Linux基礎(chǔ)入門vim 編輯器

    【北京迅為】itop-龍芯2k1000開發(fā)指南Linux基礎(chǔ)入門vim 編輯器
    的頭像 發(fā)表于 10-25 14:56 ?229次閱讀
    【北京迅為】itop-龍芯2k1000<b class='flag-5'>開發(fā)指南</b><b class='flag-5'>Linux</b>基礎(chǔ)入門vim 編輯器