歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux主機驅動與外設驅動分離思想

1主機、外設驅動分離的意義

在Linux設備驅動框架的設計中,除了有分層設計實現以外,還有分隔的思想。舉一個簡單的例子,假設我們要通過SPI總線訪問某外設,在這個訪問過程中,要通過操作CPU XXX上的SPI控制器的寄存器來達到訪問SPI外設YYY的目的,最簡單的方法是:

return_type xxx_write_spi_yyy(...)

{

xxx_write_spi_host_ctrl_reg(ctrl);

xxx_ write_spi_host_data_reg(buf);

while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));

...

}

如果按照這種方式來設計驅動,結果是對於任何一個SPI外設來講,它的驅動代碼都是CPU相關的。也就是說,當然用在CPU XXX上的時候,它訪問XXX的SPI主機控制寄存器,當用在XXX1的時候,它訪問XXX1的SPI主機控制寄存器:

return_type xxx1_write_spi_yyy(...)

{

xxx1_write_spi_host_ctrl_reg(ctrl);

xxx1_ write_spi_host_data_reg(buf);

while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));

...

}

這顯然是不能接受的,因為這意味著外設YYY用在不同的CPU XXX和XXX1上的時候需要不同的驅動。那麼,我們可以用如圖12.4的思想對主機控制器驅動和外設驅動進行分離。這樣的結構是,外設a、b、c的驅動與主機控制器A、B、C的驅動不相關,主機控制器驅動不關心外設,而外設驅動也不關心主機,外設只是訪問核心層的通用的API進行數據傳輸,主機和外設之間可以進行任意的組合。

圖12.4 Linux設備驅動的主機、外設驅動分離

如果我們不進行如圖12.4的主機和外設分離,外設a、b、c和主機A、B、C進行組合的時候,需要9個不同的驅動。設想一共有m個主機控制器,n個外設,分離的結果是需要m+n個驅動,不分離則需要m*n個驅動。

Linux SPI、I2C、USB、ASoC(ALSA SoC)等子系統都典型地利用了這種分離的設計思想,在本章我們先以簡單一些的SPI為例,而I2C、USB、ASoC等則在後續章節會進行詳細介紹。

2 Linux SPI主機和設備驅動

SPI(同步外設接口)是由摩托羅拉公司開發的全雙工同步串行總線,其接口由MISO(串行數據輸入),MOSI(串行數據輸出),SCK(串行移位時鐘),SS(從使能信號)四種信號構成,SS決定了唯一的與主設備通信的從設備,主設備通過產生移位時鐘來發起通訊。通訊時,數據由MOSI輸出,MISO輸入,數據在時鐘的上升或下降沿由MOSI輸出,在緊接著的下降或上升沿由MISO讀入,這樣經過8/16次時鐘的改變,完成8/16位數據的傳輸。

SPI模塊為了和外設進行數據交換,根據外設工作要求,其輸出串行同步時鐘極性(CPOL)和相位(CPHA)可以進行配置。如果 CPOL=0,串行同步時鐘的空閒狀態為低電平;如果CPOL=1,串行同步時鐘的空閒狀態為高電平。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣。SPI接口時序如圖12.5所示。

圖12.5 SPI總線時序

在Linux中,用代碼清單12.12的spi_master結構體來描述一個SPI主機控制器驅動,其主要成員是主機控制器的序號(系統中可能存在多個SPI主機控制器)、片選數量、SPI模式和時鐘設置用到的函數、數據傳輸用到的函數等。

代碼清單12.12 spi_master結構體

1 struct spi_master {

2 struct device dev;

3 s16 bus_num;

4 u16 num_chipselect;

5

6 /* 設置模式和時鐘 */

7 int (*setup)(struct spi_device *spi);

8

9 /* 雙向數據傳輸 */

10 int (*transfer)(struct spi_device *spi,

11 struct spi_message *mesg);

12

13 void (*cleanup)(struct spi_device *spi);

14 };

分配、注冊和注銷SPI主機的API由SPI核心提供:

struct spi_master * spi_alloc_master(struct device *host, unsigned size);

int spi_register_master(struct spi_master *master);

void spi_unregister_master(struct spi_master *master);

在Linux中,用代碼清單12.13的spi_driver結構體來描述一個SPI外設驅動,可以認為是spi_master的client驅動。

代碼清單12.13 spi_driver結構體

1 struct spi_driver {

2 int (*probe)(struct spi_device *spi);

3 int (*remove)(struct spi_device *spi);

4 void (*shutdown)(struct spi_device *spi);

5 int (*suspend)(struct spi_device *spi, pm_message_t mesg);

6 int (*resume)(struct spi_device *spi);

7 struct device_driver driver;

8 };

可以看出,spi_driver結構體和platform_driver結構體有極大的相似性,都有probe()、remove()、suspend()、resume()這樣的接口。是的,這幾乎是一切client驅動的習慣模板。

在SPI外設驅動中,當透過SPI總線進行數據傳輸的時候,使用了一套與CPU無關的統一的接口。這套接口的第1個關鍵數據結構就是spi_transfer,它用於描述SPI傳輸,如代碼清單12.14。

代碼清單12.14 spi_transfer結構體

1 struct spi_transfer {

2 const void *tx_buf;

3 void *rx_buf;

4 unsigned len;

5

6 dma_addr_t tx_dma;

7 dma_addr_t rx_dma;

8

9 unsigned cs_change:1;

10 u8 bits_per_word;

11 u16 delay_usecs;

12 u32 speed_hz;

13

14 struct list_head transfer_list;

15 };

而一次完整的SPI傳輸流程可能不只包含1次spi_transfer,它可能包含1個或多個spi_transfer,這些spi_transfer最終通過spi_message組織在一起,其定義如代碼清單12.15。

代碼清單12.15 spi_message結構體

1 struct spi_message {

2 struct list_head transfers;

3

4 struct spi_device *spi;

5

6 unsigned is_dma_mapped:1;

7

8 /* 完成被一個callback報告 */

9 void (*complete)(void *context);

10 void *context;

11 unsigned actual_length;

12 int status;

13

14 struct list_head queue;

15 void *state;

16 };

通過spi_message_init()可以初始化spi_message,而將spi_transfer添加到spi_message隊列的方法則是:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

發起一次spi_message的傳輸有同步和異步兩種方式,使用同步API時,會阻塞等待這個消息被處理完。同步操作時使用的API是:

int spi_sync(struct spi_device *spi, struct spi_message *message);

使用異步API時,不會阻塞等待這個消息被處理完,但是可以在spi_message的complete字段掛接一個回調函數,當消息被處理完成後,該函數會被調用。異步操作時使用的API是:

int spi_async(struct spi_device *spi, struct spi_message *message);

代碼清單12.16是非常典型的初始化spi_transfer、spi_message並進行SPI數據傳輸的例子,同時它們也是SPI核心層的2個通用API,在SPI外設驅動中可以直接調用它們進行寫和讀操作。

代碼清單12.16 SPI傳輸實例spi_write()、spi_read() API

1 static inline int

2 spi_write(struct spi_device *spi, const u8 *buf, size_t len)

3 {

4 struct spi_transfer t = {

5 .tx_buf = buf,

6 .len = len,

7 };

8 struct spi_message m;

9

10 spi_message_init(&m);

11 spi_message_add_tail(&t, &m);

12 return spi_sync(spi, &m);

13 }

14

15 static inline int

16 spi_read(struct spi_device *spi, u8 *buf, size_t len)

17 {

18 struct spi_transfer t = {

19 .rx_buf = buf,

20 .len = len,

21 };

22 struct spi_message m;

23

24 spi_message_init(&m);

25 spi_message_add_tail(&t, &m);

26 return spi_sync(spi, &m);

27 }

LDD6410開發板所使用的S3C6410的SPI主機控制器驅動位於drivers/spi/spi_s3c.h和drivers/spi/spi_s3c.c這2個文件,其主體是實現了spi_master的setup()、transfer()等成員函數。

SPI外設驅動遍布於內核的drivers、sound的各個子目錄之下,SPI只是一種總線,spi_driver的作用只是將SPI外設掛接在該總線上,因此在spi_driver的probe()成員函數中,將注冊SPI外設本身所屬設備驅動的類型。

和platform_driver對應著一個platform_device一樣,spi_driver也對應著一個spi_device;platform_device需要在BSP的板文件中添加板信息數據,而spi_device也同樣需要。spi_device的板信息用spi_board_info結構體描述,該結構體記錄SPI外設使用的主機控制器序號、片選序號、數據比特率、SPI傳輸模式(即CPOL、CPHA)等。如諾基亞770上2個SPI設備的板信息數據如代碼清單12.17,位於板文件arch/arm/mach-omap1/board-nokia770.c。

代碼清單12.17諾基亞770板文件中的spi_board_info

1 static struct spi_board_info nokia770_spi_board_info[] __initdata = {

2 [0] = {

3 .modalias = "lcd_mipid",

4 .bus_num = 2, /* 用到的SPI主機控制器序號 */

5 .chip_select = 3, /* 使用哪一號片選 */

6 .max_speed_hz = 12000000, /* SPI數據傳輸比特率 */

7 .platform_data = &nokia770_mipid_platform_data,

8 },

9 [1] = {

10 .modalias = "ads7846",

11 .bus_num = 2,

12 .chip_select = 0,

13 .max_speed_hz = 2500000,

14 .irq = OMAP_GPIO_IRQ(15),

15 .platform_data = &nokia770_ads7846_platform_data,

16 },

17 };

在Linux啟動過程中,在機器的init_machine()函數中,會通過如下語句注冊這些spi_board_info:

spi_register_board_info(nokia770_spi_board_info,

ARRAY_SIZE(nokia770_spi_board_info));

這一點和啟動時通過platform_add_devices()添加platform_device非常相似。

Copyright © Linux教程網 All Rights Reserved