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)入下一級配置,如下圖所示。
圖 2-1: Device Drivers 配置選項(xiàng)
選擇 SPI support 選項(xiàng),進(jìn)入下一級配置,如下圖所示。
圖 2-2: SPI support 配置選項(xiàng)
選擇 SUNXI SPI Controller 選項(xiàng),可選擇直接編譯進(jìn)內(nèi)核,也可編譯成模塊。如下圖所示。
圖 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)分為三個層次,如下圖所示。
圖 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 傳輸請求。
圖 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)。
圖 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,如下圖所示。
圖 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ū)憜栴}。
-
模塊
+關(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
+關(guān)注
關(guān)注
17文章
1688瀏覽量
91215
發(fā)布評論請先 登錄
相關(guān)推薦
評論