通過第一篇文章,我們已經知道,整個SPI驅動架構可以分為協議驅動、通用介面層和控制器驅動三大部分。其中,控制器驅動負責最底層的資料收發工作,為了完成資料的收發工作,控制器驅動需要完成以下這些功能:
1. 申請必要的硬體資源,例如中斷,DMA通道,DMA記憶體緩衝區等等;
2. 配置SPI控制器的工作模式和參數,使之可以和相應的裝置進行正確的資料交換工作;
3. 向通用介面層提供介面,使得上層的協議驅動可以通過通用介面層存取控制器驅動;
4. 配合通用介面層,完成資料訊息佇列的排隊和處理,直到訊息佇列變空為止;
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝。
/*****************************************************************************************************/
定義控制器裝置
SPI控制器遵循linux的裝置模型架構,所以,一個SPI控制器在代碼中對應一個device結構,對於嵌入式系統,我們通常把SPI控制器作為一個平台裝置來對待,所以,對於我們來說,只要在板級的代碼中為SPI控制器定義一個platform_device結構即可。下面以Samsung的SOC晶片:S3C6410,做為例子,看看如何定義這個platform_device。以下的代碼來自:/arch/arm/plat-samsung/devs.c中:
static struct resource s3c64xx_spi0_resource[] = {[0] = DEFINE_RES_MEM(S3C_PA_SPI0, SZ_256),[1] = DEFINE_RES_DMA(DMACH_SPI0_TX),[2] = DEFINE_RES_DMA(DMACH_SPI0_RX),[3] = DEFINE_RES_IRQ(IRQ_SPI0),};struct platform_device s3c64xx_device_spi0 = {.name= "s3c6410-spi",.id= 0,.num_resources= ARRAY_SIZE(s3c64xx_spi0_resource),.resource= s3c64xx_spi0_resource,.dev = {.dma_mask= &samsung_device_dma_mask,.coherent_dma_mask= DMA_BIT_MASK(32),},};由此可見,在這個platform_device中,我們定義了控制器所需的寄存器地址、DMA通道資源和IRQ編號,裝置的名字定義為:s3c64xx-spi,這個名字用於後續和相應的控制器驅動相匹配。在machine的初始化代碼中,我們需要註冊這個代表SPI控制器的平台裝置,另外,也會通過s3c64xx_spi0_set_platdata函數設定平台相關的參數供後續的控制器驅動使用:
static struct platform_device *crag6410_devices[] __initdata = { ...... &s3c64xx_device_spi0, ......};static void __init xxxx_machine_init(void){ s3c64xx_spi0_set_platdata(NULL, 0, 2); //註冊平台裝置 platform_add_devices(crag6410_devices, ARRAY_SIZE(crag6410_devices));}s3c64xx_spi0_set_platdata函數的定義如下:
void __init s3c64xx_spi0_set_platdata(int (*cfg_gpio)(void), int src_clk_nr,int num_cs){struct s3c64xx_spi_info pd;......pd.num_cs = num_cs;pd.src_clk_nr = src_clk_nr;pd.cfg_gpio = (cfg_gpio) ? cfg_gpio : s3c64xx_spi0_cfg_gpio; ......s3c_set_platdata(&pd, sizeof(pd), &s3c64xx_device_spi0);}上述函數主要是指定了控制器使用到的gpio配置、片選引腳個數和時鐘配置等資訊。這些資訊在後面的控制器驅動中要使用到。
註冊SPI控制器的platform_driver
上一節中,我們把SPI控制器註冊為一個platform_device,相應地,對應的驅動就應該是一個平台驅動:platform_driver,它們通過platform bus進行相互匹配。以下的代碼來自:/drivers/spi/spi-s3c64xx.c
static struct platform_driver s3c64xx_spi_driver = { .driver = { .name = "s3c64xx-spi", .owner = THIS_MODULE, .pm = &s3c64xx_spi_pm, .of_match_table = of_match_ptr(s3c64xx_spi_dt_match), }, .remove = s3c64xx_spi_remove, .id_table = s3c64xx_spi_driver_ids,};MODULE_ALIAS("platform:s3c64xx-spi");static int __init s3c64xx_spi_init(void){ return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);}subsys_initcall(s3c64xx_spi_init);顯然,系統初始化階段(subsys_initcall階段),通過s3c64xx_spi_init(),註冊了一個平台驅動,該驅動的名字正好也是:s3c64xx-spi,自然地,平台匯流排會把它和上一節定義的platform_device匹配上,並且觸發probe回調被調用(就是s3c64xx_spi_probe函數)。當然,這裡的匹配是通過id_table欄位完成的:
static struct platform_device_id s3c64xx_spi_driver_ids[] = { { .name = "s3c2443-spi", .driver_data = (kernel_ulong_t)&s3c2443_spi_port_config, }, { .name = "s3c6410-spi", .driver_data = (kernel_ulong_t)&s3c6410_spi_port_config, }, ...... { },};
註冊spi_master
在linux裝置模型看來,代表SPI控制器的是第一節所定義的platform_device結構,但是對於SPI通用介面層來說,代表控制器的是spi_master結構,關於spi_master結構的描述,請參看第二篇文章:Linux SPI匯流排和裝置驅動架構之二:SPI通用介面層。我們知道,裝置和驅動匹配上後,驅動的probe回呼函數就會被調用,而probe回呼函數正是對驅動程式和裝置進行初始化的合適時機,本例中,對應的probe回調是:s3c64xx_spi_probe:
static int s3c64xx_spi_probe(struct platform_device *pdev){ ...... /* 分配一個spi_master結構 */ master = spi_alloc_master(&pdev->dev, sizeof(struct s3c64xx_spi_driver_data)); ...... platform_set_drvdata(pdev, master); ...... master->dev.of_node = pdev->dev.of_node; master->bus_num = sdd->port_id; master->setup = s3c64xx_spi_setup; master->cleanup = s3c64xx_spi_cleanup; master->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer; master->transfer_one_message = s3c64xx_spi_transfer_one_message; master->unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer; master->num_chipselect = sci->num_cs; master->dma_alignment = 8; master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) | SPI_BPW_MASK(8); /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->auto_runtime_pm = true; ...... /* 向通用介面層註冊spi_master結構 */ if (spi_register_master(master)) { dev_err(&pdev->dev, "cannot register SPI master\n"); ret = -EBUSY; goto err3; } ......}上述函數,除了完成必要的硬體資源初始化工作以外,最重要的工作就是通過spi_alloc_master函數分配了一個spi_master結構,初始化該結構,最終通過spi_register_master函數完成了對控制器的註冊工作。從代碼中我們也可以看出,spi_master結構中的幾個重要的回呼函數已經被賦值,這幾個回呼函數由通用介面層在合適的時機被調用,以便完成控制器和裝置之間的資料交換工作。
實現spi_master結構的回呼函數
事實上,SPI控制器驅動程式的主要工作,就是要實現spi_master結構中的幾個回呼函數,其它的工作邏輯,均由通用介面層幫我們完成,通用介面層會在適當的時機調用這幾個回呼函數,這裡我只是介紹一下各個回呼函數的作用,具體的實現例子,請各位自行閱讀代碼樹中各個平台的例子(代碼位於:/drivers/spi/)。 int (*setup)(struct spi_device *spi) 當協議驅動希望修改控制器的工作模式或參數時,會調用通用介面層提供的API:spi_setup(),該API函數最後會調用setup回呼函數來完成設定工作。 int (*transfer)(struct spi_device *spi, struct spi_message *mesg)
目前已經可以不用我們自己實現該回呼函數,初始化時直接設為NULL即可,目前的通用介面層已經實現了訊息佇列化,註冊spi_master時,通用介面層會提供實現好的通用函數。現在只有一些老的驅動還在使用該回調方式,新的驅動應該停止使用該回呼函數,而是應該使用隊列化的transfer_one_message回調。需要注意的是,我們只能選擇其中一種方式,設定了transfer_one_message回調,就不能設定transfer回調,反之亦然。 void (*cleanup)(struct spi_device *spi)
當一個SPI從裝置(spi_device結構)被釋放時,該回呼函數會被調用,以便釋放該從裝置所佔用的硬體資源。 int (*prepare_transfer_hardware)(struct spi_master *master) int (*unprepare_transfer_hardware)(struct spi_master *master)
這兩個回呼函數用於在發起一個資料傳送過程前和後,給控制器驅動一個機會,申請或釋放某些必要的硬體資源,例如DMA資源和記憶體資源等等。 int (*prepare_message)(struct spi_master *master, struct spi_message *message) int (*unprepare_message)(struct spi_master *master, struct spi_message *message)
這兩個回呼函數也是用於在發起一個資料傳送過程前和後,給控制器驅動一個機會,對message進行必要的預先處理或後處理,比如根據message需要交換資料的從裝置,設定控制器的正確工作時鐘、字長和工作模式等。 int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg) 當通用介面層發現master的隊列中有訊息需要傳送時,會調用該回呼函數,所以該函數是真正完成一個訊息傳送的工作函數,當傳送工作完成時,應該調用spi_finalize_current_message函數,以便通知通用介面層,發起隊列中的下一個訊息的傳送工作。