來源: Ramax 喵董
目前進行中的項目是在 DaVinci 平台上開發 SPI
控制器驅動程式,不過由於手邊缺乏硬體平台可供測試的關係,現在仍然進度緩慢。不過這期間也研究了一下 Linux SPI
子系統,稍微有一些心得,來和各位分享一下。
SPI 是 Serial Peripheral Interface
的縮寫,它是一種串列式的 IO 介面,時脈約 1~ 70MHz,詳細資料可見 [1]。 DaVinci DM6446 上配備了一個 SPI
控制器,支援二個 chip select,時脈可達 33MHz。
Linux SPI 子系統將驅動程式分為三種類型:
- SPI
bus driver:
主要目的為提供 API 給下層的 master driver 及上層的 protocol driver (稍後會提)來註冊,並傳送封包至下層的 driver。 - SPI master driver:
主要工作為初始化控制器,並負 責對暫存器寫入以及中斷處理。 - SPI protocol driver:
主要為發送 SPI 訊息封包至所選擇的裝置。
從
這樣的架構來看,bus driver 扮演了一個抽象層的角色,對上隱藏了硬體的細節,對下則隱藏了對特定裝置的通訊協定。是故 master
driver 並不需要知道控制器接了什麼周邊,它只專注在如何操作控制器來將訊息封包送出;而 protocol driver
只需專心於和特定週邊的溝通,而不需了解要如何傳送訊息封包。
在子系統中,訊息封包的格式定義在 spi.h 的
spi_message 結構中。protocol driver 要傳送資料,只需填上 spi_message 結構相對應的欄位,並呼叫
spi_sync 以起始傳送。或者也可直接呼叫 spi_read/spi_write,參數則填入你的 buffer pointer
以及長度即可。
一般在嵌入式系統中,SPI
周邊多是直接固定在板子上,且硬體並無特別的方式可供偵測,因此我們需要在平台初始化時預先定義系統包含那些 SPI 周邊,而子系統也定義一個結構
spi_board_info 提供給開發者填入相對應的資訊,例如:
struct spi_board_info devices[]
= {
{
.modalias = "mydevice",
.max_speed_hz = 6000000,
.bus_num = 0,
.chip_select = 1,
.mode = SPI_MODE_1,
},
{
......
},
};
接著呼叫
spi_register_board_info 來註冊這些裝置。需要注意的是,modalias 欄位的值必須與 protocol driver
註冊的名字相同;bus driver 會以該值作為比對的依據。若需在執行時期註冊新的裝置,則可呼叫 spi_new_device 來加入。
關
於 master driver 的部分留待下一篇來討論。
相關連結:
[1] Serial Peripheral Interface Bus
[2]
TMS320DM644x DMSoC Serial
Peripheral Interface (SPI) User's Guide
我們在上
一篇文章中提到,master driver 負責初始化硬體控制器,寫入暫存器以及中斷處理。換句話說,它必須要能夠做到低階的硬體控制;在以
ARM 為基礎的 SoC 當中,也就是要實際上對 I/O memory 做讀寫的動作。因此撰寫 master driver 前,必須要先熟讀
SoC 的使用手冊,充分了解如何操作這些暫存器以支援相關的功能。
此外,master driver
還必須要將從上層傳送來的訊息封包加以解析,並將傳送該封包所需要的硬體參數設定事先寫入暫存器,最後才起始資料傳送。如果控制器支援中斷,那麼
master driver 也必須先註冊中斷常式以便處理例外狀況。接下來我們就來看看一些細節部分的資料結構與函式。
一般在 SoC
系統下,SPI 控制器會以 platform device 的形式存在,是故我們會將 master driver 註冊為一種 platform
driver,接著在 probe 函式中呼叫 spi_alloc_master,該函式宣告如下:
struct
spi_master *spi_alloc_master(struct device *host, unsigned size);
該
函式會分配一塊記憶體並回傳指向 spi_master 結構的指標,參數 size 則可以指定額外的空間大小,該空間可以透過
spi_master_get_devdata 來取得,開發者可用以存放額外的資訊。接著我們需要設定一些關於該控制器的資訊,如:
master->bus_num
= 0;
master->num_chipselect = 1;
master->setup = mysetup;
master->transfer
= mytransfer;
master->cleanup = mycleanup;
其中 bus_num
是代表控制器的識別號碼,若指定 -1 則表示由系統動態分配。num_chipselect 是這個控制器所能連接的裝置編號的最大值,1
代表最多可連接 2 個裝置 (0, 1)。最後呼叫 spi_register_master 來向系統註冊即可。
當開始要進行傳輸
時,在上面程式片段中的 setup 會在傳送前被呼叫,讓 master driver 根據周邊所能接受的傳輸模式參數來設定控制器;接著
transfer 會被呼叫,其參數包括要傳送的周邊以及指向 spi_message 結構的指標。其中 spi_message
結構還包含了一個或多個要傳輸的 buffer,定義在 spi_transfer 結構中。要注意的是,由於 transfer
函式中不可呼叫任何會進入睡眠的函式,是故實際的傳輸會由另外產生的 worker thread 來達成;並且在傳輸完成後,呼叫
spi_message 結構中的 complete 函式以通知 bus driver。最後則是 cleanup
函式被呼叫以釋放進行傳輸時所使用的資源。
另外當要卸載核心模組時,需要呼叫 spi_unregister_master
以解除註冊。
下一篇我們將會來討論 protocol driver 的部分。
在上二篇的文章中我們提到了 Linux SPI 子系統的架構以及如何撰寫 master driver,接下來則是要討論如何撰寫 protocol
driver。
如同 master driver,要讓核心能夠使用 protocol
driver,必須先註冊我們所要驅動的硬體以及對應的驅動程式。硬體裝置的註冊有兩種方法:
- 事先在核心平台相關初始化
程式碼中,填入相關的周邊硬體資訊在 spi_board_info 結構中。該結構的宣告如下:
struct spi_board_info {
char modalias[KOBJ_NAME_LEN];
const void *platform_data;
void *controller_data;
int irq;
u32 max_speed_hz;
u16 bus_num;
u16 chip_select;
u8 mode;
}
其中 modalias 必須與 protocol driver 的名稱相同以供核心比對;其他欄位的意義可參照 include/spi/spi.h 檔案中的說明。完成後呼叫 spi_register_board_info 來註冊這些裝置。 - 若 是無法確定會接上什麼裝置,則可以在核心模組程式碼中,填入上述的結構,但改為呼叫 spi_new_device 來註冊該裝置。函式中的 master 參數可透過呼叫 spi_busnum_to_master 來取得。
在註冊時,核心會為每個裝置配置一個
spi_device 結構,將上述 spi_board_info 結構中的資訊填入對應的欄位中。
接下來我們要將我們所撰寫的
protocol driver 回呼函式填入 spi_driver 結構中,如下所示:
struct spi_driver
mydriver {
.probe = mydriver_probe,
.remove =
mydriver_remove,
.driver = {
.name = "mydriver",
.owner
= THIS_MODULE,
}
}
接著呼叫 spi_register_driver 來註冊新的
protocol driver。
結語
從這
一系列文章,我們可以了解到,即使是像 SPI
如此簡單的硬體介面,為了讓驅動程式更具可移植性及模組化,核心開發者設計了分層式的架構,讓每個驅動程式能夠各司其職,互不干擾。這樣的架構在其他核心
子系統中也常會見到,也許名稱不同,但基本理念仍是相同的。
留言列表