生活随笔
收集整理的這篇文章主要介紹了
Linux SPI总线和设备驱动架构之三:SPI控制器驱动
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
通過第一篇文章,我們已經(jīng)知道,整個SPI驅(qū)動架構(gòu)可以分為協(xié)議驅(qū)動、通用接口層和控制器驅(qū)動三大部分。其中,控制器驅(qū)動負責最底層的數(shù)據(jù)收發(fā)工作,為了完成數(shù)據(jù)的收發(fā)工作,控制器驅(qū)動需要完成以下這些功能:
1. ? ?申請必要的硬件資源,例如中斷,DMA通道,DMA內(nèi)存緩沖區(qū)等等;
2. ? ?配置SPI控制器的工作模式和參數(shù),使之可以和相應的設備進行正確的數(shù)據(jù)交換工作;
3. ? ?向通用接口層提供接口,使得上層的協(xié)議驅(qū)動可以通過通用接口層訪問控制器驅(qū)動;
4. ? ?配合通用接口層,完成數(shù)據(jù)消息隊列的排隊和處理,直到消息隊列變空為止;
/*****************************************************************************************************/
聲明:本博內(nèi)容均由http://blog.csdn.net/droidphone原創(chuàng),轉(zhuǎn)載請注明出處,謝謝!
/*****************************************************************************************************/
定義控制器設備
SPI控制器遵循linux的設備模型框架,所以,一個SPI控制器在代碼中對應一個device結(jié)構(gòu),對于嵌入式系統(tǒng),我們通常把SPI控制器作為一個平臺設備來對待,所以,對于我們來說,只要在板級的代碼中為SPI控制器定義一個platform_device結(jié)構(gòu)即可。下面以Samsung的SOC芯片:S3C6410,做為例子,看看如何定義這個platform_device。以下的代碼來自:/arch/arm/plat-samsung/devs.c中:
[cpp]?view plaincopy
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,這個名字用于后續(xù)和相應的控制器驅(qū)動相匹配。在machine的初始化代碼中,我們需要注冊這個代表SPI控制器的平臺設備,另外,也會通過s3c64xx_spi0_set_platdata函數(shù)設置平臺相關(guān)的參數(shù)供后續(xù)的控制器驅(qū)動使用:
[cpp]?view plaincopy
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函數(shù)的定義如下:
[cpp]?view plaincopy
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);?? }??
上述函數(shù)主要是指定了控制器使用到的gpio配置、片選引腳個數(shù)和時鐘配置等信息。這些信息在后面的控制器驅(qū)動中要使用到。
注冊SPI控制器的platform_driver
上一節(jié)中,我們把SPI控制器注冊為一個platform_device,相應地,對應的驅(qū)動就應該是一個平臺驅(qū)動:platform_driver,它們通過platform bus進行相互匹配。以下的代碼來自:/drivers/spi/spi-s3c64xx.c
[cpp]?view plaincopy
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);??
顯然,系統(tǒng)初始化階段(subsys_initcall階段),通過s3c64xx_spi_init(),注冊了一個平臺驅(qū)動,該驅(qū)動的名字正好也是:s3c64xx-spi,自然地,平臺總線會把它和上一節(jié)定義的platform_device匹配上,并且觸發(fā)probe回調(diào)被調(diào)用(就是s3c64xx_spi_probe函數(shù))。當然,這里的匹配是通過id_table字段完成的:
[cpp]?view plaincopy
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控制器的是第一節(jié)所定義的platform_device結(jié)構(gòu),但是對于SPI通用接口層來說,代表控制器的是spi_master結(jié)構(gòu),關(guān)于spi_master結(jié)構(gòu)的描述,請參看第二篇文章:Linux SPI總線和設備驅(qū)動架構(gòu)之二:SPI通用接口層。我們知道,設備和驅(qū)動匹配上后,驅(qū)動的probe回調(diào)函數(shù)就會被調(diào)用,而probe回調(diào)函數(shù)正是對驅(qū)動程序和設備進行初始化的合適時機,本例中,對應的probe回調(diào)是:s3c64xx_spi_probe:
[cpp]?view plaincopy
static?int?s3c64xx_spi_probe(struct?platform_device?*pdev)?? {?? ????????......?? ?? ?????????? ????????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);?? ?????????? ????????master->mode_bits?=?SPI_CPOL?|?SPI_CPHA?|?SPI_CS_HIGH;?? ????????master->auto_runtime_pm?=?true;?? ?? ????????......?? ?????????? ????????if?(spi_register_master(master))?{?? ????????????????dev_err(&pdev->dev,?"cannot?register?SPI?master\n");?? ????????????????ret?=?-EBUSY;?? ????????????????goto?err3;?? ????????}?? ?? ????????......?? }??
上述函數(shù),除了完成必要的硬件資源初始化工作以外,最重要的工作就是通過spi_alloc_master函數(shù)分配了一個spi_master結(jié)構(gòu),初始化該結(jié)構(gòu),最終通過spi_register_master函數(shù)完成了對控制器的注冊工作。從代碼中我們也可以看出,spi_master結(jié)構(gòu)中的幾個重要的回調(diào)函數(shù)已經(jīng)被賦值,這幾個回調(diào)函數(shù)由通用接口層在合適的時機被調(diào)用,以便完成控制器和設備之間的數(shù)據(jù)交換工作。
實現(xiàn)spi_master結(jié)構(gòu)的回調(diào)函數(shù)
事實上,SPI控制器驅(qū)動程序的主要工作,就是要實現(xiàn)spi_master結(jié)構(gòu)中的幾個回調(diào)函數(shù),其它的工作邏輯,均由通用接口層幫我們完成,通用接口層會在適當?shù)臅r機調(diào)用這幾個回調(diào)函數(shù),這里我只是介紹一下各個回調(diào)函數(shù)的作用,具體的實現(xiàn)例子,請各位自行閱讀代碼樹中各個平臺的例子(代碼位于:/drivers/spi/)。
int (*setup)(struct spi_device *spi)
當協(xié)議驅(qū)動希望修改控制器的工作模式或參數(shù)時,會調(diào)用通用接口層提供的API:spi_setup(),該API函數(shù)最后會調(diào)用setup回調(diào)函數(shù)來完成設置工作。
int (*transfer)(struct spi_device *spi,?struct spi_message *mesg)
目前已經(jīng)可以不用我們自己實現(xiàn)該回調(diào)函數(shù),初始化時直接設為NULL即可,目前的通用接口層已經(jīng)實現(xiàn)了消息隊列化,注冊spi_master時,通用接口層會提供實現(xiàn)好的通用函數(shù)。現(xiàn)在只有一些老的驅(qū)動還在使用該回調(diào)方式,新的驅(qū)動應該停止使用該回調(diào)函數(shù),而是應該使用隊列化的transfer_one_message回調(diào)。需要注意的是,我們只能選擇其中一種方式,設置了transfer_one_message回調(diào),就不能設置transfer回調(diào),反之亦然。
void (*cleanup)(struct spi_device *spi)
當一個SPI從設備(spi_device結(jié)構(gòu))被釋放時,該回調(diào)函數(shù)會被調(diào)用,以便釋放該從設備所占用的硬件資源。
int (*prepare_transfer_hardware)(struct spi_master *master)
int (*unprepare_transfer_hardware)(struct spi_master *master)
這兩個回調(diào)函數(shù)用于在發(fā)起一個數(shù)據(jù)傳送過程前和后,給控制器驅(qū)動一個機會,申請或釋放某些必要的硬件資源,例如DMA資源和內(nèi)存資源等等。
int (*prepare_message)(struct spi_master *master,?struct spi_message *message)
int (*unprepare_message)(struct spi_master *master,?struct spi_message *message)
這兩個回調(diào)函數(shù)也是用于在發(fā)起一個數(shù)據(jù)傳送過程前和后,給控制器驅(qū)動一個機會,對message進行必要的預處理或后處理,比如根據(jù)message需要交換數(shù)據(jù)的從設備,設定控制器的正確工作時鐘、字長和工作模式等。
int (*transfer_one_message)(struct spi_master *master,?struct spi_message *mesg)
當通用接口層發(fā)現(xiàn)master的隊列中有消息需要傳送時,會調(diào)用該回調(diào)函數(shù),所以該函數(shù)是真正完成一個消息傳送的工作函數(shù),當傳送工作完成時,應該調(diào)用spi_finalize_current_message函數(shù),以便通知通用接口層,發(fā)起隊列中的下一個消息的傳送工作。
總結(jié)
以上是生活随笔為你收集整理的Linux SPI总线和设备驱动架构之三:SPI控制器驱动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。