日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux SPI总线和设备驱动架构之二:SPI通用接口层

發(fā)布時(shí)間:2025/3/21 linux 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux SPI总线和设备驱动架构之二:SPI通用接口层 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

通過(guò)上一篇文章的介紹,我們知道,SPI通用接口層用于把具體SPI設(shè)備的協(xié)議驅(qū)動(dòng)和SPI控制器驅(qū)動(dòng)聯(lián)接在一起,通用接口層除了為協(xié)議驅(qū)動(dòng)和控制器驅(qū)動(dòng)提供一系列的標(biāo)準(zhǔn)接口API,同時(shí)還為這些接口API定義了相應(yīng)的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)一部分是SPI設(shè)備、SPI協(xié)議驅(qū)動(dòng)和SPI控制器的數(shù)據(jù)抽象,一部分是為了協(xié)助數(shù)據(jù)傳輸而定義的數(shù)據(jù)結(jié)構(gòu)。另外,通用接口層還負(fù)責(zé)SPI系統(tǒng)與Linux設(shè)備模型相關(guān)的初始化工作。本章的我們就通過(guò)這些數(shù)據(jù)結(jié)構(gòu)和API的討論來(lái)對(duì)整個(gè)通用接口層進(jìn)行深入的了解。

/*****************************************************************************************************/
聲明:本博內(nèi)容均由http://blog.csdn.net/droidphone原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處,謝謝!
/*****************************************************************************************************/

SPI通用接口層的代碼集中在:/drivers/spi/spi.c中。

SPI設(shè)備模型的初始化


通常地,根據(jù)linux設(shè)備模型的組織方式,各種設(shè)備會(huì)掛在合適的總線上,設(shè)備驅(qū)動(dòng)和設(shè)備通過(guò)總線互相進(jìn)行匹配,使得設(shè)備能夠找到正確的驅(qū)動(dòng)程序進(jìn)行控制和驅(qū)動(dòng)。同時(shí),性質(zhì)相似的設(shè)備可以歸為某一個(gè)類(lèi)的設(shè)備,它們具有某些共同的設(shè)備屬性,在設(shè)備模型上就是所謂的class。SPI設(shè)備也不例外,它們也遵循linux的設(shè)備模型的規(guī)則:

[cpp]?view plaincopy
  • struct?bus_type?spi_bus_type?=?{??
  • ????????.name???????????=?"spi",??
  • ????????.dev_attrs??????=?spi_dev_attrs,??
  • ????????.match??????????=?spi_match_device,??
  • ????????.uevent?????????=?spi_uevent,??
  • ????????.pm?????????????=?&spi_pm,??
  • };????
  • ??????
  • static?struct?class?spi_master_class?=?{??
  • ????????.name???????????=?"spi_master",??
  • ????????.owner??????????=?THIS_MODULE,??
  • ????????.dev_release????=?spi_master_release,??
  • };??
  • ??
  • static?int?__init?spi_init(void)??
  • {??
  • ????????int?????status;??
  • ??
  • ????????buf?=?kmalloc(SPI_BUFSIZ,?GFP_KERNEL);??
  • ????????......??
  • ????????status?=?bus_register(&spi_bus_type);??
  • ????????......??
  • ????????status?=?class_register(&spi_master_class);??
  • ????????......??
  • ????????return?0;??
  • ????????......??
  • }??
  • ??
  • ??
  • postcore_initcall(spi_init);??
  • 可見(jiàn),在初始化階段,spi_init函數(shù)向系統(tǒng)注冊(cè)了一個(gè)名為spi的總線類(lèi)型,同時(shí)也為SPI控制器注冊(cè)了一個(gè)名為spi_master的設(shè)備類(lèi)。這樣,以后在sysfs中就會(huì)出現(xiàn)以下兩個(gè)文件節(jié)點(diǎn):

    • sys/bus/spi
    • sys/class/spi_master
    代表spi總線的spi_bus_type結(jié)構(gòu)的match字段指向了spi_match_device函數(shù),該函數(shù)用于匹配spi總線上的設(shè)備和驅(qū)動(dòng),具體的代碼這里就不貼了,各位可以自行查看內(nèi)核的代碼樹(shù)。

    spi_master結(jié)構(gòu)


    SPI控制器負(fù)責(zé)按照設(shè)定的物理信號(hào)格式在主控和spi設(shè)備之間交換數(shù)據(jù),SPI控制器數(shù)據(jù)是如何被傳輸?shù)?#xff0c;而不關(guān)心數(shù)據(jù)的內(nèi)容。SPI通用接口層用spi_master結(jié)構(gòu)來(lái)表示一個(gè)spi控制器,我們看看它的主要字段的意義:

    字段名稱(chēng)描述
    struct device ? devspi控制器對(duì)應(yīng)的device結(jié)構(gòu)
    struct list_head list系統(tǒng)可能有多個(gè)控制器,用該鏈表鏈接在一個(gè)全局鏈表變量上
    s16 ? ? ? ? ? ? bus_num該控制器對(duì)應(yīng)的spi總線編號(hào),從0開(kāi)始,通常由板級(jí)代碼設(shè)定
    u16 ? ? ? ? ? ? num_chipselect連接到該spi控制器的片選信號(hào)的個(gè)數(shù)
    u16 ? ? ? ? ? ? mode_bits工作模式,由驅(qū)動(dòng)解釋該模式的意義
    u32 ? ? ? ? ? ? min_speed_hz最低工作時(shí)鐘
    u32 ? ? ? ? ? ? max_speed_hz最高工作時(shí)鐘
    u16 ? ? ? ? ? ? flags用于設(shè)定某些限制條件的標(biāo)志位
    int ? ? ? ? ? ? (*setup)(struct spi_device *spi)回調(diào)函數(shù),用于設(shè)置某個(gè)spi設(shè)備在該控制器上的工作參數(shù)
    int ? ? ? ? ? ? (*transfer)(......)回調(diào)函數(shù),用于把包含數(shù)據(jù)信息的mesg結(jié)構(gòu)加入控制器的消息鏈表中
    void ? ? ? ? ? (*cleanup)(struct spi_device *spi)回調(diào)函數(shù),當(dāng)spi_master被釋放時(shí),該函數(shù)被調(diào)用
    struct kthread_worker ? kworker用于管理數(shù)據(jù)傳輸消息隊(duì)列的工作隊(duì)列線程
    struct kthread_work ? ? pump_messages具體實(shí)現(xiàn)數(shù)據(jù)傳輸隊(duì)列的工作隊(duì)列
    struct list_head ? ? ? ?queue該控制器的消息隊(duì)列,所有等待傳輸?shù)南㈥?duì)列掛在該鏈表下
    struct spi_message ? ? ?*cur_msg當(dāng)前正帶處理的消息隊(duì)列
    int (*prepare_transfer_hardware)(......)回調(diào)函數(shù),正式發(fā)起傳送前會(huì)被調(diào)用,用于準(zhǔn)備硬件資源
    int (*transfer_one_message)(......)單個(gè)消息的原子傳送回調(diào)函數(shù),隊(duì)列中的每個(gè)消息都會(huì)調(diào)用一次該回調(diào)來(lái)完成傳輸工作
    int (*unprepare_transfer_hardware)(......)清理回調(diào)函數(shù)
    int ? ? ? ? ? ? ? ? ? ? *cs_gpios片選信號(hào)所用到的gpio
    ??

    spi_master結(jié)構(gòu)通常由控制器驅(qū)動(dòng)定義,然后通過(guò)以下通用接口層的API注冊(cè)到系統(tǒng)中:

    • int spi_register_master(struct spi_master *master);

    spi_device結(jié)構(gòu)


    SPI通用接口層用spi_device結(jié)構(gòu)來(lái)表示一個(gè)spi設(shè)備,它的各個(gè)字段的意義如下:

    struct device ? ? ? ? ? dev代表該spi設(shè)備的device結(jié)構(gòu)
    struct spi_master ? ? ? *master指向該spi設(shè)備所使用的控制器
    u32 ? ? max_speed_hz該設(shè)備的最大工作時(shí)鐘頻率
    u8 ? ? ?chip_select在控制器中的片選引腳編號(hào)索引
    u16 ? ? mode設(shè)備的工作模式,包括時(shí)鐘格式,片選信號(hào)的有效電平等等
    u8 ? ? ?bits_per_word設(shè)備每個(gè)單位數(shù)據(jù)所需要的比特?cái)?shù)
    int ? ? irq設(shè)備使用的irq編號(hào)
    char ? ?modalias[SPI_NAME_SIZE]該設(shè)備的名字,用于spi總線和驅(qū)動(dòng)進(jìn)行配對(duì)
    int ? ? cs_gpio片選信號(hào)的gpio編號(hào),通常不用我們自己設(shè)置,接口層會(huì)根據(jù)上面的chip_select字段在spi_master結(jié)構(gòu)中進(jìn)行查找并賦值

    要完成向系統(tǒng)增加并注冊(cè)一個(gè)SPI設(shè)備,我們還需要另一個(gè)數(shù)據(jù)結(jié)構(gòu):

    [cpp]?view plaincopy
  • struct?spi_board_info?{??
  • ????????char????????????modalias[SPI_NAME_SIZE];??
  • ????????const?void??????*platform_data;??
  • ????????void????????????*controller_data;??
  • ????????int?????????????irq;??
  • ????????u32?????????????max_speed_hz;??
  • ????????u16?????????????bus_num;??
  • ????????u16?????????????chip_select;??
  • ????????u16?????????????mode;??
  • };??
  • spi_board_info結(jié)構(gòu)大部分字段和spi_device結(jié)構(gòu)相對(duì)應(yīng),bus_num字段則用來(lái)指定所屬的控制器編號(hào),通過(guò)spi_board_info結(jié)構(gòu),我們可以有兩種方式向系統(tǒng)增加spi設(shè)備。第一種方式是在SPI控制器驅(qū)動(dòng)已經(jīng)被加載后,我們使用通用接口層提供的如下API來(lái)完成:

    • struct spi_device *spi_new_device(struct spi_master *master,?struct spi_board_info *chip);
    第二種方式是在板子的初始化代碼中,定義一個(gè)spi_board_info數(shù)組,然后通過(guò)以下API注冊(cè)spi_board_info:

    • int spi_register_board_info(struct spi_board_info const *info, unsigned n);
    上面這個(gè)API會(huì)把每個(gè)spi_board_info掛在全局鏈表變量board_list上,并且遍歷已經(jīng)在系統(tǒng)中注冊(cè)了的控制器,匹配上相應(yīng)的控制器并取得它們的spi_master結(jié)構(gòu)指針,最終也會(huì)通過(guò)spi_new_device函數(shù)添加SPI設(shè)備。因?yàn)閟pi_register_board_info可以在板子的初始化代碼中調(diào)用,可能這時(shí)控制器驅(qū)動(dòng)尚未加載,此刻無(wú)法取得相應(yīng)的spi_master指針,不過(guò)不要擔(dān)心,控制器驅(qū)動(dòng)被加載時(shí),一定會(huì)調(diào)用spi_register_master函數(shù)來(lái)注冊(cè)spi_master結(jié)構(gòu),而spi_register_master函數(shù)會(huì)反過(guò)來(lái)遍歷全局鏈表board_list上的spi_board_info,然后通過(guò)spi_new_device函數(shù)添加SPI設(shè)備。

    spi_driver結(jié)構(gòu)


    根據(jù)linux的設(shè)備模型,有device就必定有driver與之對(duì)應(yīng),上一節(jié)介紹的spi_device結(jié)構(gòu)中內(nèi)嵌了device結(jié)構(gòu)字段dev,同樣地,代表驅(qū)動(dòng)程序的spi_driver結(jié)構(gòu)也內(nèi)嵌了device_driver結(jié)構(gòu):

    [cpp]?view plaincopy
  • struct?spi_driver?{??
  • ????????const?struct?spi_device_id?*id_table;??
  • ????????int?????????????????????(*probe)(struct?spi_device?*spi);??
  • ????????int?????????????????????(*remove)(struct?spi_device?*spi);??
  • ????????void????????????????????(*shutdown)(struct?spi_device?*spi);??
  • ????????int?????????????????????(*suspend)(struct?spi_device?*spi,?pm_message_t?mesg);??
  • ????????int?????????????????????(*resume)(struct?spi_device?*spi);??
  • ????????struct?device_driver????driver;??
  • };??
  • id_table字段用于指定該驅(qū)動(dòng)可以驅(qū)動(dòng)的設(shè)備名稱(chēng),總線的匹配函數(shù)會(huì)把id_table中指定的名字和spi_device結(jié)構(gòu)中的modalias字段進(jìn)行比較,兩者相符即表示匹配成功,然后出發(fā)spi_driver的probe回調(diào)函數(shù)被調(diào)用,從而完成驅(qū)動(dòng)程序的初始化工作。通用接口層提供如下API來(lái)完成spi_driver的注冊(cè):

    [cpp]?view plaincopy
  • int?spi_register_driver(struct?spi_driver?*sdrv)??
  • {??
  • ????????sdrv->driver.bus?=?&spi_bus_type;??
  • ????????if?(sdrv->probe)??
  • ????????????????sdrv->driver.probe?=?spi_drv_probe;??
  • ????????if?(sdrv->remove)??
  • ????????????????sdrv->driver.remove?=?spi_drv_remove;??
  • ????????if?(sdrv->shutdown)??
  • ????????????????sdrv->driver.shutdown?=?spi_drv_shutdown;??
  • ????????return?driver_register(&sdrv->driver);??
  • }??
  • 需要注意的是,這里的spi_driver結(jié)構(gòu)代表的是具體的SPI協(xié)議驅(qū)動(dòng)程序。

    spi_message和spi_transfer結(jié)構(gòu)


    要完成和SPI設(shè)備的數(shù)據(jù)傳輸工作,我們還需要另外兩個(gè)數(shù)據(jù)結(jié)構(gòu):spi_message和spi_transfer。spi_message包含了一個(gè)的spi_transfer結(jié)構(gòu)序列,一旦控制器接收了一個(gè)spi_message,其中的spi_transfer應(yīng)該按順序被發(fā)送,并且不能被其它spi_message打斷,所以我們認(rèn)為spi_message就是一次SPI數(shù)據(jù)交換的原子操作。下面我們看看這兩個(gè)數(shù)據(jù)結(jié)構(gòu)的定義:

    [cpp]?view plaincopy
  • struct?spi_message?{??
  • ????????struct?list_head????????transfers;??
  • ??
  • ????????struct?spi_device???????*spi;??
  • ??
  • ????????unsigned????????????????is_dma_mapped:1;??
  • ??
  • ????????/*?completion?is?reported?through?a?callback?*/??
  • ????????void????????????????????(*complete)(void?*context);??
  • ????????void????????????????????*context;??
  • ????????unsigned????????????????frame_length;??
  • ????????unsigned????????????????actual_length;??
  • ????????int?????????????????????status;??
  • ??
  • ????????struct?list_head????????queue;??
  • ????????void????????????????????*state;??
  • };??
  • 鏈表字段queue用于把該結(jié)構(gòu)掛在代表控制器的spi_master結(jié)構(gòu)的queue字段上,控制器上可以同時(shí)被加入多個(gè)spi_message進(jìn)行排隊(duì)。另一個(gè)鏈表字段transfers則用于鏈接掛在本message下的spi_tranfer結(jié)構(gòu)。complete回調(diào)函數(shù)則會(huì)在該message下的所有spi_transfer都被傳輸完成時(shí)被調(diào)用,以便通知協(xié)議驅(qū)動(dòng)處理接收到的數(shù)據(jù)以及準(zhǔn)備下一批需要發(fā)送的數(shù)據(jù)。我們?cè)賮?lái)看看spi_transfer結(jié)構(gòu):

    [cpp]?view plaincopy
  • struct?spi_transfer?{??
  • ????????const?void??????*tx_buf;??
  • ????????void????????????*rx_buf;??
  • ????????unsigned????????len;??
  • ??
  • ????????dma_addr_t??????tx_dma;??
  • ????????dma_addr_t??????rx_dma;??
  • ??
  • ????????unsigned????????cs_change:1;??
  • ????????u8??????????????tx_nbits;??
  • ????????u8??????????????rx_nbits;??
  • ????????u8??????????????bits_per_word;??
  • ????????u16?????????????delay_usecs;??
  • ????????u32?????????????speed_hz;??
  • ??
  • ????????struct?list_head?transfer_list;??
  • };??
  • 首先,transfer_list鏈表字段用于把該transfer掛在一個(gè)spi_message結(jié)構(gòu)中,tx_buf和rx_buf提供了非dma模式下的數(shù)據(jù)緩沖區(qū)地址,len則是需要傳輸數(shù)據(jù)的長(zhǎng)度,tx_dma和rx_dma則給出了dma模式下的緩沖區(qū)地址。原則來(lái)講,spi_transfer才是傳輸?shù)淖钚挝?#xff0c;之所以又引進(jìn)了spi_message進(jìn)行打包,我覺(jué)得原因是:有時(shí)候希望往spi設(shè)備的多個(gè)不連續(xù)的地址(或寄存器)一次性寫(xiě)入,如果沒(méi)有spi_message進(jìn)行把這樣的多個(gè)spi_transfer打包,因?yàn)橥ǔU嬲臄?shù)據(jù)傳送工作是在另一個(gè)內(nèi)核線程(工作隊(duì)列)中完成的,不打包的后果就是會(huì)造成更多的進(jìn)程切換,效率降低,延遲增加,尤其對(duì)于多個(gè)不連續(xù)地址的小規(guī)模數(shù)據(jù)傳送而言就更為明顯。
    通用接口層為我們提供了一系列用于操作和維護(hù)spi_message和spi_transfer的API函數(shù),這里也列一下。

    用于初始化spi_message結(jié)構(gòu):

    • void spi_message_init(struct spi_message *m);
    把一個(gè)spi_transfer加入到一個(gè)spi_message中(注意,只是加入,未啟動(dòng)傳輸過(guò)程),和移除一個(gè)spi_transfer:

    • void?spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
    • ?void?spi_transfer_del(struct spi_transfer *t);
    以上兩個(gè)API的組合,初始化一個(gè)spi_message并添加數(shù)個(gè)spi_transfer結(jié)構(gòu):

    • void?spi_message_init_with_transfers(struct spi_message *m,?struct spi_transfer *xfers, unsigned int num_xfers);
    分配一個(gè)自帶數(shù)個(gè)spi_transfer機(jī)構(gòu)的spi_message:

    • struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);
    發(fā)起一個(gè)spi_message的傳送操作:

    • 異步版本 ? ?int spi_async(struct spi_device *spi, struct spi_message *message);
    • 同步版本 ? ?int spi_sync(struct spi_device *spi, struct spi_message *message);
    利用以上這些API函數(shù),SPI設(shè)備的協(xié)議驅(qū)動(dòng)程序就可以完成與某個(gè)SPI設(shè)備的數(shù)據(jù)交換工作,同時(shí)也可以看到,因?yàn)橛型ㄓ媒涌趯拥母綦x,控制器驅(qū)動(dòng)對(duì)于協(xié)議驅(qū)動(dòng)程序來(lái)說(shuō)是透明的,也就是說(shuō),協(xié)議驅(qū)動(dòng)程序只關(guān)心具體需要處理和交換的數(shù)據(jù),無(wú)需關(guān)心控制器是如何傳送這些數(shù)據(jù)的。spi_master,spi_message,spi_transfer這幾個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)系可以用下圖來(lái)描述:




    總結(jié)一下,協(xié)議驅(qū)動(dòng)發(fā)送數(shù)據(jù)的流程大致是這樣的:

    • 定義一個(gè)spi_message結(jié)構(gòu);
    • 用spi_message_init函數(shù)初始化spi_message;
    • 定義一個(gè)或數(shù)個(gè)spi_transfer結(jié)構(gòu),初始化并為數(shù)據(jù)準(zhǔn)備緩沖區(qū)并賦值給spi_transfer相應(yīng)的字段(tx_buf,rx_buf等);
    • 通過(guò)spi_message_init函數(shù)把這些spi_transfer掛在spi_message結(jié)構(gòu)下;
    • 如果使用同步方式,調(diào)用spi_sync(),如果使用異步方式,調(diào)用spi_async();

    另外,通用接口層也為一些簡(jiǎn)單的數(shù)據(jù)傳輸提供了獨(dú)立的API來(lái)完成上述的組合過(guò)程:

    • int spi_write(struct spi_device *spi, const void *buf, size_t len); ? ?---- ? ?同步方式發(fā)送數(shù)據(jù)。
    • int?spi_read(struct spi_device *spi, void *buf, size_t len); ? ?---- ? ?同步方式接收數(shù)據(jù)。
    • int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers); ? ?---- ? 同步方式,直接傳送數(shù)個(gè)spi_transfer,接收和發(fā)送。
    • int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx); ? ?---- ? ?先寫(xiě)后讀。
    • ssize_t spi_w8r8(struct spi_device *spi, u8 cmd); ? ?---- ? ?寫(xiě)8位,然后讀8位。
    • ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);?? ?---- ? ?寫(xiě)8位,然后讀16位。

    總結(jié)

    以上是生活随笔為你收集整理的Linux SPI总线和设备驱动架构之二:SPI通用接口层的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。