前言
主要是想對Linux 下spi驅動框架有一個整體的把控,因此會忽略某些細節,同時里面涉及到的一些驅動基礎,比如平台驅動、設備模型等也不進行詳細說明原理。如果有任何錯誤地方,請指出,謝謝!
spi介紹
SPI接口是Motorola 首先提出的全雙工三線同步串行外圍接口,采用主從模式(Master Slave)架構。支持多slave模式應用,一般僅支持單Master。時鍾由Master控制,在時鍾移位脈沖下,數據按位傳輸,高位在前,低位在后(MSB first)。SPI接口有2根單向數據線,為全雙工通信,目前應用中的數據速率可達幾Mbps的水平。
spi傳輸詳細介紹
總線結構如下圖所示:

SPI接口共有4根信號線,分別是:設備選擇線、時鍾線、串行輸出數據線、串行輸入數據線。

- MOSI:主器件數據輸出,從器件數據輸入
- MISO:主器件數據輸入,從器件數據輸出
- SCLK: 時鍾信號,由主器件產生
- /SS: 從器件使能信號,由主器件控制
SPI接口在內部硬件實際上是兩個簡單的移位寄存器,傳輸的數據為8位,在主器件產生的從器件使能信號和移位脈沖下,按位傳輸,高位在前,低位在后。如下圖所示,在SCLK的下降沿上數據改變,上升沿一位數據被存入移位寄存器。

在一個SPI時鍾周期內,會完成如下操作:
- 主機通過MOSI線發送1位數據,從機通過該線讀取這1位數據;
- 從機通過MISO線發送1位數據,主機通過該線讀取這1位數據。這是通過移位寄存器來實現的。如下圖所示,主機和從機各有一個移位寄存器,且二者連接成環。隨着時鍾脈沖,數據按照從高位到低位的方式依次移出主機寄存器和從機寄存器,並且依次移入從機寄存器和主機寄存器。當寄存器中的內容全部移出時,相當於完成了兩個寄存器內容的交換。

SPI接口具有如下優點:
- 支持全雙工操作;
- 操作簡單;
- 數據傳輸速率較高(相對的)。
同時,它也具有如下缺點:
- 多個spi設備需要占用主機較多的管腳(每個從機都需要一根片選線);
- 只支持單個主機;
- 沒有指定的流控制,沒有應答機制確認是否接收到數據。
SPI編程主要搞清一個問題:模式
SPI有四種工作模式,具體由CPOL(Clock Polarity 時鍾極性),CPHA(Clock Phase時鍾相位)決定

當CPOL為0時,空閑的時候SCLK電平是低電平;
當CPOL為1時,空閑的時候SCLK電平是高電平;
當CPHA為0時,采集數據發生在時鍾周期的前邊緣(第一個邊緣,可能是上升緣也可能是下降緣,由CPOL決定),這同時意味着輸出數據發生在后邊緣;
當CPHA為1時,采集數據發生在時鍾周期的后邊緣(第二個邊緣,可能是上升緣也可能是下降緣,由CPOL決定),這同時意味着輸出數據發生在前邊緣;
摘抄一張網上的圖:

我們編寫spi接口的設備驅動程序的時候,最需要關心的就是spi控制器的部分和spi設備采用的是那種模式,確定模式后,我們得將spi控制器配置成一樣的模式才能正常工作。
Linux下SPI驅動框架分析
Linux的spi接口驅動實現目錄在linux-2.6.35\drivers\spi下。這個目錄和一些層次比較明顯的驅動目錄布局不同,全放在這個文件夾下,因此還是只好通過看Kconfig 和 Makefile來找找思路
先看Makefile,里面關鍵幾行:
obj-$(CONFIG_SPI_MASTER) += spi.o //這個是針對有spi控制器的soc選項,一般的soc都有spi控制器吧,呵呵
# SPI master controller drivers (bus) //下面的這些就是針對不同soc上的spi控制器的驅動了,我們可以通過make menuconfig的時候選上自己對應平台的
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o
……
……
# SPI protocol drivers (device/link on bus) #這里在最后面分析
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o
下面這些就是針對於主機作為spi從設備的時候用的,暫時貌似沒支持,畢竟現實中幾乎沒有用過,而是作為master端出現
# SPI slave controller drivers (upstream link)
# ... add above this line ...
# SPI slave drivers (protocol for that link)
# ... add above this line ...
再看Kconfig,第一個SPI選項我覺得有必要貼一下,首先只有選擇它了才能進行后面的配置,其次它的help對spi的描述說的很清楚!
menuconfig SPI
bool "SPI support"
depends on HAS_IOMEM
help
The "Serial Peripheral Interface" is a low level synchronous
protocol. Chips that support SPI can have data transfer rates
up to several tens of Mbit/sec. Chips are addressed with a
controller and a chipselect. Most SPI slaves don't support
dynamic device discovery; some are even write-only or read-only.
SPI is widely used by microcontrollers to talk with sensors,
eeprom and flash memory, codecs and various other controller
chips, analog to digital (and d-to-a) converters, and more.
MMC and SD cards can be accessed using SPI protocol; and for
DataFlash cards used in MMC sockets, SPI must always be used.
SPI is one of a family of similar protocols using a four wire
interface (select, clock, data in, data out) including Microwire
(half duplex), SSP, SSI, and PSP. This driver framework should
work with most such devices and controllers.
我們其次需要配上的選項就是SPI_MASTER(這里假設soc上是有spi控制器的,即使沒有spi控制器,這個目錄里也有實現通過gpio模式spi控制器的代碼)和SPI_S3C24XX(這里假設是s3c平台,畢竟這個平台用於學習的最多吧)
config SPI_MASTER
# boolean "SPI Master Support"
boolean
default SPI
help
If your system has an master-capable SPI controller (which
provides the clock and chipselect), you can enable that
controller and the protocol drivers for the SPI slave chips
that are connected.
config SPI_S3C24XX
tristate "Samsung S3C24XX series SPI"
depends on ARCH_S3C2410 && EXPERIMENTAL
select SPI_BITBANG
help
SPI driver for Samsung S3C24XX series ARM SoCs
於是從Makefile里得到如下語句和我們相關:
obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx_hw.o
spi_s3c24xx_hw-y := spi_s3c24xx.o
spi_s3c24xx_hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi_s3c24xx_fiq.o#這里不考慮s3c 的fiq方式
於是我們要分析的代碼主要有:spi.c spi_s3c24xx.c,瞬間沒壓力了,就兩個文件,呵呵
下面主要從三個方面來分析spi框架
- spi控制器驅動的實現(畢竟spi控制器的驅動還是有可能要接觸的)
- spi設備的驅動(我們更多的是編寫設備的驅動,還是以eeprom為例吧,雖然我很想以spi接口的nor flash驅動為例,但是那又會牽涉出mtd子系統,這個留在mtd子系統分析吧)
- spi核心層的實現(上面1、2都是以各自的驅動實現為目標,並不深入到spi核心層,也就是至於spi核心層怎么為我們提供的服務不去關心,只需要按spi核心層使用它提供的服務就是了。所以現在統一分析spi核心層,看它是怎么提供的服務)
spi控制器驅動的實現
以spi_s3c24xx.c為例,直接看s3c24xx_spi_init:
static struct platform_driver s3c24xx_spi_driver = {
.remove = __exit_p(s3c24xx_spi_remove),
.driver = {
.name = "s3c2410-spi",
.owner = THIS_MODULE,
.pm = S3C24XX_SPI_PMOPS,
},
};
static int __init s3c24xx_spi_init(void)
{
return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
}
如果是我們自己實現,也會采用這種平台驅動的方式吧,平台驅動的內部流程就不分析了,直接看匹配成功后s3c24xx_spi_probe的調用,但這里還是插入平台spi控制器設備端相關的代碼:
在arch/arm/plat-s3c24xx/devs.c中:
static struct resource s3c_spi0_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI,
.end = S3C24XX_PA_SPI + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI0,
.end = IRQ_SPI0,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &s3c_device_spi0_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
static struct resource s3c_spi1_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI + S3C2410_SPI1,
.end = S3C24XX_PA_SPI + S3C2410_SPI1 + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI1,
.end = IRQ_SPI1,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi1_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &s3c_device_spi1_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
還沒貼完,我一直在找platform_add_device將s3c_spi1_resource或者s3c_device_spi1加入到平台總線的代碼,但是沒找到!汗!!! 突然想到網上的一句話:spi的驅動處於被忽視的。現在看來有道理,在s3c soc相關代碼里只看到了gpio模擬的spi驅動,但這不影響我們分析spi控制器驅動,現在看s3c24xx_spi_probe:
它主要的工作:
- 調用spi核心層的接口分配一個spi核心層能識別的spi控制器對象
struct spi_master並額外分配出我們驅動需要的上下文數據空間sizeof(struct s3c24xx_spi),這個空間的地址可以通過核心層提供的接口提取出來:
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
hw = spi_master_get_devdata(master);
- 提取spi控制器平台設備指定的平台數據
hw->pdata = pdata = pdev->dev.platform_data;
在上面貼出的平台設備端代碼里面沒有設置平台數據,它的類型為:
struct s3c2410_spi_info {
int pin_cs; /* simple gpio cs */
unsigned int num_cs; /* total chipselects */
int bus_num; /* bus number to use. */
unsigned int use_fiq:1; /* use fiq */
void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);
void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);
};
這個其實是必須指定的,且里面的以下成員是必須初始化的:
spi核心層會用到有:
num_cs表示該控制器支持的spi設備的數量
bus_num表示該控制器衍生出的總線號,如果小於0則spi核心層采用動態分配的
spi控制器驅動會用到的有:
gpio_setup 配置spi控制器要用的管腳
set_cs 片選回調接口,用來選中某個spi設備
然后根據平台設備指定的資源(寄存器地址空間、中斷號)向操作系統請求資源空間並建立起映射為以后所用:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hw->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
hw->regs = ioremap(res->start, resource_size(res));//映射以后要操作的寄存器地址區間
hw->irq = platform_get_irq(pdev, 0);
err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); //注冊中斷
- spi硬件控制器初始化
使能spi控制器部分的時鍾
hw->clk = clk_get(&pdev->dev, "spi");
clk_enable(hw->clk);
設置spi控制器寄存器為默認狀態:
writeb(0xff, hw->regs + S3C2410_SPPRE);
writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);
配置spi控制器相關的管腳
if (hw->pdata) {
if (hw->set_cs == s3c24xx_spi_gpiocs)
gpio_direction_output(hw->pdata->pin_cs, 1);
if (hw->pdata->gpio_setup)
hw->pdata->gpio_setup(hw->pdata, 1);
}
- master初始化
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; //指定該控制器支持的模式,spi接口的設備模式必須屬於這個范疇才能被成功的加入
master->num_chipselect = hw->pdata->num_cs;//spi接口的設備號必須屬於這個范圍[0,num_cs]
master->bus_num = pdata->bus_num; //spi接口設備的總線號必須和這個匹配才會被添加到該控制器的管制下
hw->master->setup = s3c24xx_spi_setup;//每個spi設備添加的時候,都會以spi設備對象的指針為參數調用該接口,主要是讓控制器有一個能對spi設備setup的過程
hw->master->cleanup = s3c24xx_spi_cleanup;//每個spi設備刪除的時候,都會以spi設備對象的指針為參數調用該接口
- 注冊到spi核心層
status = spi_register_master(bitbang->master);
暫時不進入到spi核心層分析,這里我們只需要知道調用核心層的注冊函數后,核心層會遍歷所有注冊到核心層的設備(實際最開始是加入到一個全局鏈表里,和I2C核心層的實現類似),然后嘗試着添加每一個總線號為該控制器衍生出的總線號的設備到該spi控制器上,當然如果該spi控制器不支持某一個設備,那就取消添加這個設備,如果添加成功,那么該設備將會添加到spi總線上去,這條總線是spi核心層注冊的,用來管理spi接口的設備和spi接口的驅動。
這里假設核心層在spi_register_master里找到了一個適合的設備(總線號匹配且cs_num有效),那么setup會被調用,也就是s3c24xx_spi_setup
這里多了一個數據類型:struct s3c24xx_spi_devstate,在spi控制器的probe里面s3c有定義控制器設備的數據類型,現在又定義了spi設備的數據類型,也就是說只要是想將spi設備加入到該spi控制器,那就必須提供s3c指定的設備相關的數據類型,下面看看該數據類型的定義
/**
* s3c24xx_spi_devstate - per device data
* @hz: Last frequency calculated for @sppre field.
* @mode: Last mode setting for the @spcon field.
* @spcon: Value to write to the SPCON register.
* @sppre: Value to write to the SPPRE register.
*/
struct s3c24xx_spi_devstate {
unsigned int hz;
unsigned int mode;//這個比較重要,在前面spi硬件介紹已經說過,時鍾極性、時鍾相位
u8 spcon;
u8 sppre;
};
可以看出這些成功都是來指示spi控制器,在操作這個設備的時候應該怎么配置它的寄存器的。
從s3c24xx_spi_setup里面可以看出,即使spi接口的設備沒有提供該數據類型,該spi控制器的驅動也會使用一個默認的設置:
if (!cs) {
cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL);
if (!cs) {
dev_err(&spi->dev, "no memory for controller state\n");
return -ENOMEM;
}
cs->spcon = SPCON_DEFAULT;
cs->hz = -1;
spi->controller_state = cs;
}
然后會調用s3c24xx_spi_update_state,它其實就是將設備指定的模式、寄存器、hz等根據spi控制器的做個轉換,為以后真正操作該設備的時候拿出來用。
spi設備的驅動
以eeprom為例,看文件at25.c:
static struct spi_driver at25_driver = {
.driver = {
.name = "at25",
.owner = THIS_MODULE,
},
.probe = at25_probe,
.remove = __devexit_p(at25_remove),
};
static int __init at25_init(void)
{
return spi_register_driver(&at25_driver);
}
直接調用spi核心層提供的函數注冊,也就是說它不需要關心是哪個控制器來實現最終的spi數據傳輸。從這里也可以看出核心層的作用,分隔了控制器和設備驅動的關聯性,主要兩邊的驅動都容易實現。
前面將控制器驅動是以s3c為例的(我都有點后悔用s3c為例了,呵呵),而s3c上暫時沒有使用到spi控制器,也沒有這里at25要驅動的設備代碼,所以我下面提取設備需要添加的代碼的部分從其他平台提取,其實只要知道原理就行了,對吧!
以ti的一個soc dm365平台的arch/arm/mach-davinci/Board-dm355-evm.c為例:
static struct spi_eeprom at25640a = {
.byte_len = SZ_64K / 8,
.name = "at25640a",
.page_size = 32,
.flags = EE_ADDR2,
};
static struct spi_board_info dm355_evm_spi_info[] __initconst = {
{
.modalias = "at25",
.platform_data = &at25640a,
.max_speed_hz = 10 * 1000 * 1000, /* at 3v3 */
.bus_num = 0,
.chip_select = 0,
.mode = SPI_MODE_0,
},
};
dm355_init_spi0(BIT(0), dm355_evm_spi_info,
ARRAY_SIZE(dm355_evm_spi_info));
void __init dm355_init_spi0(unsigned chipselect_mask,
struct spi_board_info *info, unsigned len)
{
/* for now, assume we need MISO */
davinci_cfg_reg(DM355_SPI0_SDI);
/* not all slaves will be wired up */
if (chipselect_mask & BIT(0))
davinci_cfg_reg(DM355_SPI0_SDENA0);
if (chipselect_mask & BIT(1))
davinci_cfg_reg(DM355_SPI0_SDENA1);
spi_register_board_info(info, len);//這也是核心層提供的函數,用來將spi設備的數據添加的一個全局的鏈表,在spi控制器驅動加載的時候會掃描該鏈表
platform_device_register(&dm355_spi0_device);
}
注意:因為不是s3c的平台,前面將s3c平台的時候s3c有定義它要求的spi設備的數據類型struct s3c24xx_spi_devstate,而我們這是另外一個平台的spi設備,它用的是spi下eeprom通用的數據類型struct spi_eeprom。(我還是后悔沒在分析s3c spi控制器的時候就用dm365 soc作為目標,畢竟我以前也搞過dm385/8這類soc)
現在我們回到at25_init:
這里假設spi控制器已經添加,且由於spi控制器的添加,spi設備也被同時添加到spi總線,所以這里直接看at25_probe:
主要工作:
- 提取設備的數據 這個就是剛才貼出來的那段代碼設置的。
const struct spi_eeprom *chip;
chip = spi->dev.platform_data;
也就是
static struct spi_eeprom at25640a = {
.byte_len = SZ_64K / 8,
.name = "at25640a",
.page_size = 32,
.flags = EE_ADDR2,
};
- 分配並初始化一個對象用來描述一個這樣的eeprom設備,在at25中它定義了類型為
struct at25_data來描述它,也可以說它是驅動的上下文數據啦 - 創建應用層用來操作的文件,這個和I2C中eeprom的驅動實現類似,這里就不再重復原理了
sysfs_bin_attr_init(&at25->bin);
at25->bin.attr.name = "eeprom";
at25->bin.attr.mode = S_IRUSR;
at25->bin.read = at25_bin_read;
at25->bin.size = at25->chip.byte_len;
if (!(chip->flags & EE_READONLY)) {
at25->bin.write = at25_bin_write;
at25->bin.attr.mode |= S_IWUSR;
at25->mem.write = at25_mem_write;
}
貌似這樣就完成了,驅動實現at25_bin_read、at25_bin_write來處理應用層的讀寫。
下面以at25_bin_read為例分析下數據的通訊:
Spi核心定義了struct spi_transfer和struct spi_message來輔佐一次數據的傳送,那么我們在at25_bin_read里面要做的肯定就是將buf及offset、count轉換成transfer和message了,方法如下:
spi_message_init(&m);//初始化message,它代表一次傳輸
memset(t, 0, sizeof t);//初始化transfer,它代表一次傳輸中的一個事務,一次傳輸可以有多個事務
t[0].tx_buf = command; // command包含了讀命令及讀的位置偏移
t[0].len = at25->addrlen + 1;//buf的長度,也就是command的長度
spi_message_add_tail(&t[0], &m);//將該消息加入到這次傳輸中
t[1].rx_buf = buf;//要發送的數據
t[1].len = count;// 要發送的數據長度
spi_message_add_tail(&t[1], &m); //將該消息加入到這次傳輸中
最后調用status = spi_sync(at25->spi, &m);將消息送到核心層(其實就是由核心層調用控制器送出數據)。
這樣驅動就完成了,很簡單吧!主要是因為核心層幫我們做了很多事情,下面開始分析核心層的實現
spi核心層的實現
主要看spi.c文件:
static int __init spi_init(void);
postcore_initcall(spi_init);
從這里可以知道spi_init的調用(也就是spi核心層的初始化)是在驅動加載前的。
spi_init主要工作:
- 分配一個buf,這個在后面的處理中會用到它
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
- 注冊spi總線,這個總線前面有說過也用到過
status = bus_register(&spi_bus_type);
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device,
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
};
- 注冊一個spi master(控制器)類
status = class_register(&spi_master_class);
所有注冊到核心層的spi控制器都屬於這個class。
現在我們分析下在spi控制器驅動和spi接口的設備驅動中有調用過的spi核心層的接口
控制器有調用spi_register_master,下面來分析它:
- 如果總線號小於0,則動態分配一個,這個前面有提過:
if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
- 將該spi控制器(控制器也是一個設備)添加到設備模型中
dev_set_name(&master->dev, "spi%u", master->bus_num);
status = device_add(&master->dev);
- 最關鍵的部分,也是前面有提到的掃描設備並進行設備的注冊
scan_boardinfo(master);
它里面的實現:
list_for_each_entry(bi, &board_list, list) {//掃描board_list鏈表,這個鏈表里面的設備成員的添加在spi設備驅動那有貼出即spi_register_board_info。
struct spi_board_info *chip = bi->board_info;
unsigned n;
for (n = bi->n_board_info; n > 0; n--, chip++) {
if (chip->bus_num != master->bus_num)//檢查總線號
continue;
/* NOTE: this relies on spi_new_device to
* issue diagnostics when given bogus inputs
*/
(void) spi_new_device(master, chip);//最終的添加設備實現
}
}
spi_new_device:
分配並初始化一個spi_device,它的初始化是根據設備注冊時的信息來初始化的:
proxy = spi_alloc_device(master);
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->irq = chip->irq;
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
proxy->dev.platform_data = (void *) chip->platform_data;
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
最終添加的spi總線:
status = spi_add_device(proxy);
需要注意的是它里面除了調用device_add外,還調用了status = spi_setup(spi);這里面實現控制器注冊的接口的回調。
好了,spi_register_master分析完了,它主要的工作就是將自己添加的設備模型的同時,添加所有符合當前控制器號的所有spi設備到spi總線
下面分析下spi_register_driver,它就相對來說容易很多了:
int spi_register_driver(struct spi_driver *sdrv)
{
sdrv->driver.bus = &spi_bus_type;
if (sdrv->probe)
sdrv->driver.probe = spi_drv_probe;
if (sdrv->remove)
sdrv->driver.remove = spi_drv_remove;
if (sdrv->shutdown)
sdrv->driver.shutdown = spi_drv_shutdown;
return driver_register(&sdrv->driver);
}
它主要是指定了spi總線,並將驅動注冊到設備模型中
下面分析下spi_sync,這個函數是我們前面在分析應用層at25_bin_read讀的過程中遇到的一個函數:
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);//在棧上定義一個完成量用來同步
int status;
message->complete = spi_complete;//回調函數
message->context = &done;
status = spi_async(spi, message);//真正發起操作的函數,異步的
if (status == 0) {
wait_for_completion(&done);//阻塞等待完成,當spi_complete被調用時才會繼續往下走,spi_complete的調用是在本次操作完成后以回調的形式message->complete調用的
status = message->status;
}
message->context = NULL;
return status;
}
下面看spi_async:
int spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;//提取當前設備對應的spi控制器,我們最終的操作肯定是由控制器來完成的
/* Half-duplex links include original MicroWire, and ones with
* only one data pin like SPI_3WIRE (switches direction) or where
* either MOSI or MISO is missing. They can also be caused by
* software limitations.
*/
if ((master->flags & SPI_MASTER_HALF_DUPLEX)
|| (spi->mode & SPI_3WIRE)) {
struct spi_transfer *xfer;
unsigned flags = master->flags;
list_for_each_entry(xfer, &message->transfers, transfer_list) {
if (xfer->rx_buf && xfer->tx_buf)
return -EINVAL;
if ((flags & SPI_MASTER_NO_TX) && xfer->tx_buf)
return -EINVAL;
if ((flags & SPI_MASTER_NO_RX) && xfer->rx_buf)
return -EINVAL;
}
}
message->spi = spi;
message->status = -EINPROGRESS;
return master->transfer(spi, message);
調用控制器的transfer將消息發送出去,這個master->transfer是在控制器的s3c24xx_spi_probe—> spi_bitbang_start里面指定:
if (!bitbang->master->transfer)
bitbang->master->transfer = spi_bitbang_transfer;
}
於是最終會調用spi_bitbang_transfer來完成數據的發送,下面看spi_bitbang_transfer的實現:
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
struct spi_bitbang *bitbang;
unsigned long flags;
int status = 0;
m->actual_length = 0;
m->status = -EINPROGRESS;
bitbang = spi_master_get_devdata(spi->master);
spin_lock_irqsave(&bitbang->lock, flags);
if (!spi->max_speed_hz)
status = -ENETDOWN;
else {
list_add_tail(&m->queue, &bitbang->queue);
queue_work(bitbang->workqueue, &bitbang->work);//將當前消息加入到一個鏈表,采用工作隊列的方式來異步處理消息的發送
}
spin_unlock_irqrestore(&bitbang->lock, flags);
return status;
}
這里的工作隊列bitbang->workqueue和工作也是在控制器的s3c24xx_spi_probe—> spi_bitbang_start里面初始化的:
INIT_WORK(&bitbang->work, bitbang_work);
bitbang->workqueue = create_singlethread_workqueue(
dev_name(bitbang->master->dev.parent));
於是bitbang_work最終會被調用,下面看bitbang_work,它是最終控制器處理發送的函數:
主要的工作是:
從鏈表里面取出一個消息spi_message,然后將消息里面的spi_transfer一個個取出來發送出去,這里面的實現就和具體的spi硬件控制器相關了,這里不再分析了。
最后再說一下前面分析Makefile的時候有兩句:
# SPI protocol drivers (device/link on bus)
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o
這里的spidev在Kconfig描述如下:
config SPI_SPIDEV
tristate "User mode SPI device driver support"
depends on EXPERIMENTAL
help
This supports user mode SPI protocol drivers.
Note that this application programming interface is EXPERIMENTAL
and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes.
可以看出,它是為了支持在應用層實現驅動的驅動,這就類似於I2C里面的i2c-dev.c的實現吧!spidev.c是在核心層基礎之上將SPI controller模擬成一個字符型的驅動,向文件系統提供標准的文件系統接口,用來操作對應的SPI controller。
比較簡單,所以也不再分析了。
關於設備樹里spi相關的補充請參考linux驅動基礎系列--linux spi驅動框架分析(續)
------------------------------完畢!
2014年5月
