SPI master-slave驅動框架分析


SPI主要分主控制器及SPI設備兩端,兩者之間靠spi.h這個公共接口來作為抽象層。
首先來分析SPI總線:【本篇着重分析注冊及匹配流程,下篇將會詳細分析SPI master驅動的時序及實現方式】
===================================================================================================
spi bus
===================================================================================================
總線類型:
struct bus_type spi_bus_type = {
    .name        = "spi",
    .dev_attrs    = spi_dev_attrs,
    .match        = spi_match_device,
    .uevent        = spi_uevent,
    .pm        = &spi_pm,
};
注:這里的spi_bus_type沒有實現probe函數,對下面的drv.probe有重要影響。

總線注冊:【對應與/sys/bus/目錄】
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);        //注冊spi主控器類型
    return 0;
}

spi主控器為一個device,為此類型
static struct class spi_master_class = {
    .name        = "spi_master",
    .owner        = THIS_MODULE,
    .dev_release    = spi_master_release,
};

整個spi master側的設備及驅動都是依賴與平台驅動的,下面詳細跟蹤spi的整個注冊流程:
以展訊平台的8810 spi master為例:
static struct platform_device sprd_spi_controller_device[] =
{
       {
               .name = "sprd_spi",
               .id = 0,
               .dev = {
                       .dma_mask = &spi_dmamask,
                       .coherent_dma_mask = DMA_BIT_MASK(32),
                       },
               .resource = spi_resources[0],
               .num_resources = ARRAY_SIZE(spi_resources[0]),
       },
       {
               .name = "sprd_spi",
               .id = 1,
               .dev = {
                       .dma_mask = &spi_dmamask,
                       .coherent_dma_mask = DMA_BIT_MASK(32),
                       },
               .resource = spi_resources[1],
               .num_resources = ARRAY_SIZE(spi_resources[1]),
       },
};
在8810中一共有兩個spi master,由id來區別,下面是對應的MEM/IRQ資源:
static struct resource spi_resources[][2] = {
               {
                       {
                               .start = SPRD_SPI0_PHYS,
                               .end = SPRD_SPI0_PHYS + SZ_4K - 1,
                               .flags = IORESOURCE_MEM,
                       },
                       {
                               .start = IRQ_SPI0_INT,
                               .end = IRQ_SPI0_INT,
                               .flags = IORESOURCE_IRQ,
                       },
               },
               {
                       {
                               .start = SPRD_SPI1_PHYS,
                               .end = SPRD_SPI1_PHYS + SZ_4K - 1,
                               .flags = IORESOURCE_MEM,
                       },

                       {
                               .start = IRQ_SPI1_INT,
                               .end = IRQ_SPI1_INT,
                               .flags = IORESOURCE_IRQ,
                       },
               },
};
首先需要注冊平台設備:
platform_device_register(&sprd_spi_controller_device[0]);
platform_device_register(&sprd_spi_controller_device[1]);
這一步主要的工作有:
1. 初始化platform_device的dev結構
2. 設置dev的dma相關
3. 掛接設備所屬總線到platform_bus_type
4. 解析資源RES
5. 掛接dev到驅動模型

然后需要注冊平台驅動:【在master的驅動實現中】
static struct platform_driver sprd_spi_driver = {
    .driver        = {
        .name    = "sprd_spi",
        .owner    = THIS_MODULE,
    },
    .suspend    = sprd_spi_suspend,
    .resume        = sprd_spi_resume,
    .remove        = __exit_p(sprd_spi_remove),
};
初始化:
static int __init sprd_spi_init(void)
{
    return platform_driver_probe(&sprd_spi_driver, sprd_spi_probe);
}
這一步主要的工作有:
1. 填入驅動真正的probe,並注冊平台驅動【platform_driver_register】
2. 掛上平台總線platform_bus_type,檢測驅動名稱是否重復,加入bus【bus_add_driver】
3. 下面的事情是設備驅動模型里面的任務了,為驅動建立節點,建立私有driver_private,初始化設備鏈等
probe的詳細過程見上一篇【設備驅動模型】。
4. 調用probe函數,進行匹配
       4.1 int __init sprd_spi_probe(struct platform_device *pdev),這里的參數來自於哪里呢?從上面的分析來看,很明顯這個時候device/driver
都已經明確掛到了平台總線下,那么這里的平台設備為此驅動所屬的bus下的所有設備。
       4.2 在probe函數內部,可以區分也可以不區分具體的platform device, 這里分成兩種模式:
        a. 不同的spi master使用自己獨立的master driver
        b. 使用相同的spi master driver,但是需要生成兩個master是必須的,並且由不同的bus_num來提供給device掛接
    這里涉及到兩個匹配的過程:
        master device <-----> master driver
        master driver <-----> spi slave
        第一個過程可以由device的name + id來判斷,並由id來轉化成master的bus_num,第二個過程則直接由device的bus_num項與master匹配
    4.3 生成具體的master后,需要掛接標准接口實現到spi master,如:setup/transfer/cleanup等
    4.4 這里可以有兩個數據位置值得注意,其一是master內部的private data,第二個是dev下的master
        master = spi_alloc_master(&pdev->dev, sizeof *sprd_data);/spi_master_get_devdata(master);
        platform_set_drvdata(pdev, master);/platform_get_drvdata(pdev);
    4.5 還有就是一些資源的解析到private data中,設置dma 參數,中斷及相關的變量/隊列等
    4.6 最重要的一個步驟,注冊master到整個SPI系統中【注,不是bus!,master作為一個抽象,代表了spi的master端的所有功能及控制】
        ret = spi_register_master(master);
        a.初始化一些標准接口,如用於傳輸spi_message的消息隊列
        b.加入到全局鏈尾,spi_master_list
        c.匹配已經存在的board_list鏈,並進行匹配,spi_match_master_to_boardinfo,這里主要進行兩步操作:第一是匹配bus_num,第二步是
    進行實際的slave與master的匹配工作,
    struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)【分析在slave后面】
注:這里的觸發spi_match_master_to_boardinfo流程的源頭可能有兩個:
1. 注冊master:spi_register_master
2. 注冊spi board:spi_register_board_info


系統為了SPI slave及master的匹配,聲明了兩個靜態鏈表
static LIST_HEAD(board_list);                    宿主為board_info->{list + spi_board_info}
static LIST_HEAD(spi_master_list);                宿主為spi_master【已包含list_head】
一個用於存儲spi_board_info【slave端會詳細描述】,一個用於存儲master鏈。

slave端的device聲明:
static struct spi_board_info openhone_spi_devices_wifi[] = {
    {
     .modalias = "spi_slot0",    // "spidev" --> spidev_spi
     .chip_select = SPRD_3RDPARTY_SPI_WIFI_CS,
     //.max_speed_hz = 24 * 1000 * 1000,    wch
      .max_speed_hz = 8 * 1000 * 1000,   
     .mode = SPI_CPOL | SPI_CPHA,
     },
};
slave端的注冊流程:
1.標准做法
spi_register_board_info

2.展訊做法
gps_spi_dev = sprd_spi_wifi_device_register(1, NULL);
struct spi_device *sprd_spi_wifi_device_register(int master_bus_num, struct spi_board_info *chip)
{
    return sprd_spi_device_register(master_bus_num, chip, SPRD_3RDPARTY_SPI_WIFI_CS);
}
struct spi_device *sprd_spi_device_register(int master_bus_num,struct spi_board_info *chip,int type)
展訊的實現流程較為混亂,梳理后的方式如下:【沒有采用標准的做法】
一共需要兩個參數:1. 與master相對應的bus_num; 2. 與spi_board_info spi slave device相對應的CS【每個設備需要一個獨立的CS】
找到這兩個設備后:
使用1獲取到master結構體,使用2獲取到spi_board_info結構體,直接調用 spi_new_device
標准的做法為:
只需要注冊spi_register_board_info即可,相關的匹配流程交給kernel去匹配。【boardinfo中含有bus_num/cs等,不需要單獨拿出來自己匹配】

至此:master與slave端都同時匯聚到了一起:spi_new_device,下面是這個函數的詳細描述,
在spi_new_device中並沒有判斷是否已經生成spi proxy的代碼,那么是否會同一個master/slave生成了兩次proxy?
展訊的方法中,spi_board_info並沒有注冊到board_list中,故master不會匹配到,只有等到slave主動調用spi_new_device的時候才會成功匹配,
標准的方法中,總是后加入鏈的進行匹配的工作。

spi_new_device
這個函數調用的條件是:master與slave bus_num一致,進行兩者之間的匹配
具體的工作如下:
1.生成一個父節點為master->dev的spi_device對象,並且master指向匹配的master結構【注意:這里將設備的bus指向 &spi_bus_type,后續對設備驅動有大的作用!,
而master指針則會負責后續的傳輸責任轉發】
2.獲取spi_board_info上的所有信息到spi_device對象中去
3.添加此spi_device設備到系統中去:spi_add_device
    檢查片選小於master支持的片選總數
    設置名稱為master->dev.name + cs_num
    確認沒有使用總線上其它設備已占用的cs號(從名稱判斷)【不管什么的master上配對的device都隸屬於這條系統僅有的spi總線】
    【這里有個問題:不同master上的cs號的范圍不能重復?一般cs號即為gpio號,不會重復】
    啟動master接口,setup
    添加device->dev到sys系統中去


以上都是在平台總線上的master device與driver的配對,master與slave的配對,這些對於一般的spi設備驅動開發人員都是透明的,下面來說
spi driver與spi device的配對過程:

static struct spi_driver gpsspidev_spi = {
    .driver = {
        .name =        "spi_slot0",
        .owner =    THIS_MODULE,
    },
    .probe =    gpsspidev_probe,
    .remove =    __devexit_p(mxdspidev_remove),
};
注冊到系統中去:
major= spi_register_driver(&gpsspidev_spi);
主要完成如下工作:
首先設置其bus指向spi_bus_type,然后調用driver_register【加入到bus的driver鏈表中】
一般在加入device或者driver的時候都會調用:driver_attach/device_attach
Probe的詳細過程見【設備驅動模型】
在匹配過程中__driver_attach,首先會調用spi bus type的match函數【最高匹配否認優先級】
其次是bus的probe【spi未實現,則調用driver本身的probe函數】,最后執行設備與驅動的綁定
probe成功后需要對驅動本生的一些成員進行初始化,代表我probe到設備后就要開始准備工作了。
SPI框架的match實現:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device    *spi = to_spi_device(dev);
    const struct spi_driver    *sdrv = to_spi_driver(drv);

    /* Attempt an OF style match */
    if (of_driver_match_device(dev, drv))
        return 1;

    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    return strcmp(spi->modalias, drv->name) == 0;
}
先匹配driver的id table中的每一項中的名字是否與device匹配
其次匹配driver的name與device是否相配
一般的SPI slave驅動,直接名稱匹配即可,不需要自己進行probe

在分析的過程中,想到這么幾個問題:
總線類型與實際總線【master】的關系?
系統中不區分master的不同,而只有一條類型為spi_bus_type的spi總線,所有注冊上來的設備(spi_device)及設備驅動(spi_driver)都隸屬於
這條總線,這樣可以對整個spi體系下的所有slave設備及驅動進行管理。

spi device是什么時候掛到spi_bus_type設備鏈中去的?
spi device指向總線spi_bus_type是在spi device初始化的時候做的
反向掛是在:初始化完成之后,device_add函數中通過 bus_add_device(dev);添加到spi總線中去的

如何訪問spi設備以及訪問過程中如何分發到master?
spi slave driver會自行封裝spi message,並請求同步到slave,具體由slave掛接的master實現具體的分發功能。
這部分內容放在下一篇【SPI master驅動的時序及實現方式】詳細講解



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM