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非常相似。 |