SPI核心驅動模型-dm8127
利用TI提供的已有SPI核心驅動模型,該方案需要我們瞭解核心的SPI驅動模型是如何實現的。
3.1. SPI核心驅動模型
在2.6的linux核心中,SPI的驅動架構可以分為如下三個層次:SPI 核心層、SPI控制器驅動層和SPI裝置驅動層。Linux 中SPI驅動程式碼位於drivers/spi目錄。
3.1.1. SPI核心層
SPI核心層是Linux的SPI核心部分,提供了核心資料結構的定義、SPI控制器驅動和裝置驅動的註冊、登出管理等API。其為硬體平臺無關層,向下遮蔽了物理匯流排控制器的差異,定義了統一的訪問策略和介面;其向上提供了統一的介面,以便SPI裝置驅動通過匯流排控制器進行資料收發。Linux中,SPI核心層的程式碼位於driver/spi/ spi.c。
3.1.2. SPI控制器驅動層
SPI控制器驅動層,每種處理器平臺都有自己的控制器驅動,屬於平臺移植相關層。它的職責是為系統中每條SPI匯流排實現相應的讀寫方法。在物理上,每個SPI控制器可以連線若干個SPI從裝置。
在系統開機時,SPI控制器驅動被首先裝載。一個控制器驅動用於支援一條特定的SPI匯流排的讀寫。一個控制器驅動可以用資料結構struct spi_master來描述,定義在include/liunx/spi/spi.h檔案中。
針對DM8127,控制器驅動程式為omap2_mcspi.c,SPI 控制器驅動的註冊採用Platform device和Platform driver機制。
3.1.2.1. SPI控制器的Platform device
Platform device的定義和註冊程式碼位於arch/arm/mach-omap2/devices.c中。
static struct platform_device omap2_mcspi1 = {
.name = "omap2_mcspi",
.id = 1,
.num_resources = ARRAY_SIZE(omap2_mcspi1_resources),
.resource = omap2_mcspi1_resources,
.dev = {
.platform_data = &omap2_mcspi1_config,
},
};
static struct platform_device omap2_mcspi2 = {
.name = "omap2_mcspi",
.id = 2,
.num_resources = ARRAY_SIZE(omap2_mcspi2_resources),
.resource = omap2_mcspi2_resources,
.dev = {
.platform_data = &omap2_mcspi2_config,
},
};
static struct platform_device omap2_mcspi3 = {
.name = "omap2_mcspi",
.id = 3,
.num_resources = ARRAY_SIZE(omap2_mcspi3_resources),
.resource = omap2_mcspi3_resources,
.dev = {
.platform_data = &omap2_mcspi3_config,
},
};
static struct platform_device omap2_mcspi4 = {
.name = "omap2_mcspi",
.id = 4,
.num_resources = ARRAY_SIZE(omap2_mcspi4_resources),
.resource = omap2_mcspi4_resources,
.dev = {
.platform_data = &omap2_mcspi4_config,
},
};
可以看到,這邊定義了4個SPI控制器的Platform device,id分別為“1,2,3, 4”,name都為“omap2_mcspi”,變數resource中定義了介面卡的暫存器基地址。
3.1.2.2. Platform device的註冊如下
platform_device_register(&omap2_mcspi1);
platform_device_register(&omap2_mcspi2);
platform_device_register(&omap2_mcspi3);
platform_device_register(&omap2_mcspi4);
以上四個函式在核心啟動時完成。將Platform device註冊到Platform總線上,完成註冊後,暫存器的基地址等資訊會在裝置樹中描述,此後只需利用platform_get_resource等標準介面自動獲取即可,實現了驅動和資源的分離。
3.1.2.3. SPI控制器的Platform driver
Platform driver的註冊程式碼位於核心的drivers/spi/omap2_mcspi.c中,該驅動的註冊目的是初始化DM8127的SPI 控制器,提供SPI匯流排資料傳輸的具體實現,並且向PSI核心層註冊SPI 控制器。
3.1.2.4. Platform driver的定義如下
static struct platform_driver omap2_mcspi_driver = {
.driver = {
.name = "omap2_mcspi",
.owner = THIS_MODULE,
.pm = &omap2_mcspi_pm_ops
},
.remove = __exit_p(omap2_mcspi_remove),
};
核心啟動時會呼叫函式platform_driver_probe()來註冊Platform driver,該函式是帶有probe函式的平臺驅動註冊函式,註冊時,會掃描platform bus上的所有裝置,由於匹配因子name為" omap2_mcspi ",與之前註冊的Platform device匹配成功,於是函式omap2_mcspi_probe()將被呼叫,控制器device和driver將被繫結起來。
在檔案drivers/spi/omap2_mcspi.c中會涉及到一個數據結構omap2_mcspi,這個結構專門針對DM8127的SPI控制器,程式碼如下:
struct omap2_mcspi {
struct work_struct work;
spinlock_t lock;
struct list_head msg_queue;
struct spi_master *master;
struct clk *ick;
struct clk *fck;
void __iomem *base;
unsigned long phys;
struct omap2_mcspi_dma *dma_channels;
};
msg_queue對應訊息佇列。
master對應通用的spi控制器結構。
ick和fck分別對應介面時鐘和功能時鐘。
base對應SPI控制器暫存器的虛擬地址。
phys對應SPI控制器暫存器的實體地址。
dma_channels對應SPI控制器的DMA通道。
函式omap2_mcspi_probe()的執行流程如下圖:
3.1.3. SPI裝置驅動層
SPI裝置驅動層為使用者介面層,其為使用者提供了通過SPI匯流排訪問具體裝置的介面。
SPI裝置驅動層可以用兩個模組來描述,struct spi_driver和struct spi_device。
針對DM8127上的裝置,我們利用核心提供的SPI使用者驅動程式spidev.c
3.1.2.1. DM8127上的設備註冊
裝置的建立和註冊分為兩步。
第一步:將裝置資訊加入到裝置連結串列
在板級初始化時將SPI裝置的名稱,地址和相關的資訊加入到連結串列board_list中,該連結串列定義在driver/spi/spi.c中,記錄了具體開發板上的SPI裝置資訊。
在board-ti8148ipnc.c中,裝置資訊定義如下:(這裡是我們主要修改的程式碼,新增spi裝置所對應的資訊,包括spi總訊號,也就是具體對應dm8127的哪個spi控制器;spi時鐘平率;片選,也就是對應dm8127的哪個片選訊號;工作模式即極性和相位)
struct spi_board_info __initdata ti8148_spi_slave_info[] = {
{
.modalias = "dm8127_spidev",
.irq = -1,
.max_speed_hz = 100000,
.bus_num = 1,//AD9970掛在SPI0上,片選為cs0
.chip_select = 0,
.mode = SPI_MODE_3,
},
{
.modalias = "dm8127_spidev",
.irq = -1,
.max_speed_hz = 100000,
.bus_num = 1, //AD9970掛在SPI0上,片選為cs1
.chip_select = 1,
.mode = SPI_MODE_3,
},
{
.modalias = "dm8127_spidev",
.irq = -1,
.max_speed_hz = 100000,
.bus_num = 1, //eeprom掛在SPI0上,片選為cs2
.chip_select = 2,
.mode = SPI_MODE_0,
},
{
.modalias = "dm8127_spidev",
.irq = -1,
.max_speed_hz = 100000,
.bus_num = 2, //fpga掛在SPI1上,片選為cs0
.chip_select = 0,
.mode = SPI_MODE_0,
},
}
裝置資訊通過函式spi_register_board_info()加入到連結串列board_list中,程式碼如下:
void __init ti8148_spi_init(void)
{
spi_register_board_info(ti8148_spi_slave_info,
ARRAY_SIZE(ti8148_spi_slave_info));
}
裝置加入到裝置連結串列board_list的流程圖如下:
第二步:裝置spi_device的建立並新增
spi_device的建立並新增是在SPI控制器驅動註冊過程中完成的,spi_register_master()函式在註冊SPI控制器驅動的過程會掃描SPI裝置連結串列board_list,如果該總線上有對應的SPI裝置,則建立相應的spi_device,並將其新增到SPI的裝置連結串列中。流程圖如下所示:
3.1.2.2. DM8127上的裝置驅動註冊
在drivers/spi/spidev.c中,定義了裝置驅動,程式碼如下:
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "dm8127_spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe,
.remove = __devexit_p(spidev_remove),
};
static int __init spidev_init(void)
{
status = spi_register_driver(&spidev_spi_driver);
}
註冊的簡要示意圖如下:
在模組載入的時候首先呼叫spidev_init(),然後spidev_init()呼叫函式spi_register_driver()註冊spider_spi_driver結構體。
函式spi_register_driver()初始化該驅動的匯流排為spi_bus_type,然後使用函式driver_register(&sdrv->driver)註冊該驅動,因此核心會在SPI總線上遍歷所有SPI裝置,由於該裝置驅動的匹配因子name變數為“dm8127_spidev”,因此正好和之前建立的chip->modalias為“dm8127_spidev”的SPI 裝置匹配。因此SPI裝置驅動的probe函式spidev_probe()將會被呼叫,完成一些具體裝置相關的初始化等操作,同時我們要針對不同spi裝置去配置一下spi傳輸的bits_per_word,程式碼如下:
if(spi->master->bus_num == 2)//spi1
{
spi->bits_per_word = 32;
}
else if(spi->master->bus_num == 1)//spi0
{
spi->bits_per_word = 8;
}
spi_setup(spi);