SylixOS基於Nuc970平臺的SD驅動移植

1. 適用範圍

    本文檔爲實現Nuc970平臺的SD驅動總結,提供一些SylixOS SD驅動移植方法的參考。

2. 原理概述

2.1 控制器類型

   SD控制器有兩種類型,分爲SD標準控制器(SDHCI)SD非標準控制器

    SylixOS Base代碼中實現了SDHCI的驅動,但Nuc970的SD控制器是非標準控制器,其功能都需要在BSP中單獨實現。

2.2  命令、應答、數據

    SD傳輸過程中會有命令、應答和數據三個概念存在。

    命令和應答都是在CMD線上傳輸的,數據在DAT線上進行傳輸。

2.2.1 命令

   命令有四種類型:
   廣播無應答命令(bc)    此命令SD總線上的各個設備都會接收到。
   廣播有應答命令(bcr)   此命令當SD總線上各個設備的CMD線都是獨立時,都會接收到,並且各自獨立的在CMD線上產生應答。
   點對點無數據命令(ac)  此命令不會在DAT線上產生數據。
   點對點有數據命令(adtc) 此命令會在DAT線上產生數據。

    有的命令是可以帶參數的,在SD標準協議中定義瞭如圖 2-1所示的命令,標準協議對每條命令是否可以帶參數、所帶參數的格式、應答類型以及功能都進行了詳細地定義。

2-1 SD標準協議中定義的CMD

2.2.2 應答

    應答有五種類型:

   (1)R1和R1b

    R1和R1b是最常見的應答類型,如圖 2-2所示。R1b較R1不同之處爲應答產生後R1b會在DAT線上產生busy標誌。

2-2 R1R1b應答

    (2)R2

    R2應答如圖 2-3所示,用於讀取SD卡中的CID和CSD寄存器,CID和CSD寄存器記錄了SD卡的相關信息。

2-3 R2應答

    (3)R3

    R3應答如圖 2-4所示,用於讀取SD卡中的OCR寄存器,OCR寄存器記錄了SD卡電壓支持情況。

2-4 R3應答

    (4)R6

    R6應答如圖 2-5所示,用於獲取SD卡中的RCA,RCA寄存器用於存儲SD卡識別後被設置的16bits卡地址。

2-5 R6應答

    (5)R7

     R7應答如圖 2-6所示,用於獲取SD卡接口狀態,比如電壓是否處於可兼容狀態。

2-6 R7應答

2.2.3 數據

   SD的數據傳輸分爲單塊傳輸和多塊傳輸兩種。

   單塊讀取的流程如圖 2-7所示。

2-7單塊讀取流程

多塊讀取的流程如圖 2-8所示。

2-8多塊讀取流程

2.3 SD卡識別流程

   SD卡在被插入SD卡槽後,系統會發送一系列的命令對SD卡進行初始化、獲取電壓支持等操作。當SD卡被成功識別後,系統會根據SD卡的CID和CSD讀取出如所示的SD卡信息,如圖 2-9所示。

2-9 SD卡信息

   SD卡基本識別流程如圖 2-10所示,識別之後的SD卡會在/dev/blk下創建設備節點sdcard-0。


 

2-10 SD卡識別流程

2.4 文件系統掛載

   SD識別之後,系統會讀取SD卡中的首扇區,其中定義了根目錄入口簇號、FAT表佔用扇區數、文件系統信息扇區號等信息,SD協議棧會根據這些信息讀取到目錄節點、文件節點等信息,並完成文件系統的掛載。

3. 技術實現

3.1 驅動框架

3.1.1 SD總線適配器創建

   SD驅動需要首先創建SD總線適配器,如程序清單 3-1所示,創建適配器時需要註冊總線的操作集。

程序清單 3-1 SD總線適配器創建


_G_sdfunc.SDFUNC_pfuncMasterXfer = __sdTransfer;
_G_sdfunc.SDFUNC_pfuncMasterCtl  = __sdIoCtl;
iRet = API_SdAdapterCreate(__SDHOST_NAME,&_G_sdfunc);
if (iRet != ERROR_NONE) {
    printk("__err2\n");
    goto __err2;
}

3.1.2 熱插拔檢測

   Nuc970的SD控制器在拔插卡之後會產生中斷,所以直接在中斷中判斷SD卡的狀態,並通知系統創建SD存儲設備節點或移除SD存儲設備節點。

3.1.3 SD中斷處理

   SD中斷處理邏輯如圖 3-1所示,中斷處理主要包括了以下幾點內容:

  • 傳輸CRC校驗出錯

  • 應答超時出錯

  • DMA傳輸結束置位

  • 熱插拔狀態檢測


3-1 SD中斷處理流程

3.2 通信流程

    通信流程如圖 3-2所示。命令是否發送完成和應答是否產生,Nuc970都只提供了可輪詢檢查的狀態位CO和RI;當有數據進行發送時,同時需要對DO位進行置位;當有數據需要讀取時,同時需要對DI位進行置位。讀寫操作由DMA完成,DMA傳輸結束後會產生中斷。


3-2通信流程

3.3 代碼實現

   Nuc970的SD驅動以內核模塊的形式提供,整體的代碼結構如圖 3-3所示。

3-3 SD驅動文件結構

3.3.1 發送命令

   發送命令的基本代碼實現如程序清單 3-2所示。

程序清單 3-2發送命令__mciSendCmd

INT __mciSendCmd (PSDIO_DAT psdio)
{
    ……
    __SD_FUNCTION_ENABLE();
 
    uiRegCtl = readl(REG_SDH_CTL)&
               (~((BIT_CTL_CMD_CODE_MASK)|
               (BIT_CTL_EN_MASK)));
 
    if (psdio->SDIO_bData) {
        /*
         * 有數據發送
         */
        ……
        psdio->SDIO_eCompleteWhat = COMPLETION_XFERFINISH_RSPFIN;
 
    } else if(SD_CMD_TEST_RSP((psdio->SDIO_psdcmd), SD_RSP_PRESENT)) {
        /*
         * 只有應答,沒有數據發送
         */
        ……
        psdio->SDIO_eCompleteWhat = COMPLETION_RSPFIN;
 
    } else {
        /*
         * 只發送命令
         */
        psdio->SDIO_eCompleteWhat = COMPLETION_CMDSENT;
    }
 
    writel(psdio->SDIO_psdcmd->SDCMD_uiArg, REG_SDH_CMD);        /* 將參數填入命令寄存器 */
 
    uiRegCtl |= (psdio->SDIO_psdcmd->SDCMD_uiOpcode << 8) |      /* 將Opcode填入寄存器   */
                (BIT_CTL_CO_EN);
    psdio->SDIO_ucEvent |= SD_EVENT_CMD_OUT;                     /* 添加發送命令事件     */
    writel(uiRegCtl, REG_SDH_CTL);
 
    __mciWakeupQueue(psdio->SDIO_hSdioSync);                     /* 通知事件處理線程     */
 
    return(ERROR_NONE);
}

   發送命令之後會喚醒等待事件線程,如程序清單 3-3所示,在此線程中會輪詢相關狀態位,判斷髮送和應答狀態。

程序清單 3-3事件處理線程

#define __WAIT_COMPLETE(ulMsk, x) while (1) { \
                                      if (!(readl(REG_SDH_CTL) & ulMsk)) { \
                                          x = 1; \
                                          break; \
                                      } \
                                  }
 
static PVOID __mciSdioThread (PVOID pvArg)
{
    ……
    for (;;) {
        __mciIoWaitQueue(psdio->SDIO_hSdioSync);                  /* 等待事件處理消息  */
        ……
        if (ucEvent & SD_EVENT_CMD_OUT) {
            __WAIT_COMPLETE(BIT_CTL_CO_EN, ucCompleted);          /* 等待命令發送完成  */
        }
        ……
        if (ucCompleted) {
           __sdCompletedCommand(psdio, ucEvent);                  /* 通知事件處理完成  */
        }
    }
 
    return(LW_NULL);
}

3.3.2 接收應答

   接收應答需要在調用發送命令之後,只是對於需要產生應答的命令,在發送時同時需要設置應答接收類型和事件,代碼實現如程序清單 3-4所示。

程序清單 3-4發送命令時設置應答

INT __mciSendCmd (PSDIO_DAT psdio)
{
    ……
    __SD_FUNCTION_ENABLE();
    ……
    if (psdio->SDIO_bData) {
        /*
         * 需要應答
         */
        psdio->SDIO_ucEvent      |= SD_EVENT_RSP_IN;
        psdio->SDIO_eCompleteWhat = COMPLETION_XFERFINISH_RSPFIN;
 
    } else if(SD_CMD_TEST_RSP((psdio->SDIO_psdcmd), SD_RSP_PRESENT)) {
        /*
         * 需要應答
         */
        if ((psdio->SDIO_psdcmd->SDCMD_uiFlag & SD_RSP_R2) == SD_RSP_R2) {
            /*
             * 應答類型爲R2
             */
            uiRegCtl            |= BIT_CTL_R2_EN;
            psdio->SDIO_ucEvent |= SD_EVENT_RSP2_IN;
        } else {
            /*
             * 應答類型爲R1
             */
            uiRegCtl            |= BIT_CTL_RI_EN;
            psdio->SDIO_ucEvent |= SD_EVENT_RSP_IN;
        }
        psdio->SDIO_eCompleteWhat = COMPLETION_RSPFIN;
 
    } else {
        psdio->SDIO_eCompleteWhat = COMPLETION_CMDSENT;
    }
    ……                                                              /*    發送命令      */
    __mciWakeupQueue(psdio->SDIO_hSdioSync);                          /* 通知事件處理線程 */
 
    return(ERROR_NONE);
}

    同樣,在事件處理線程中需要對應答狀態進行輪詢,如程序清單 3-5所示。

程序清單 3-5事件處理線程處理應答

static PVOID __mciSdioThread (PVOID pvArg)
{
    ……
    for (;;) {
        __mciIoWaitQueue(psdio->SDIO_hSdioSync);                      /*  等待事件處理消息 */
        ……
        if (ucEvent & SD_EVENT_RSP_IN) {                              /*  應答類型R1       */
            ……
            while(1) {
               if (!(readl(REG_SDH_CTL) & BIT_CTL_RI_EN)) {           /*  等待應答         */
                   ucCompleted =1;
                   break;
               }
 
               if (readl(REG_SDH_INTSTS) & BIT_INTSTS_RITO_IF) {      /*  傳輸超時         */
                   ……
                   break;
               }
            }
        }
 
        if (ucEvent & SD_EVENT_RSP2_IN) {                             /* 應答類型R2        */
            __WAIT_COMPLETE(BIT_CTL_R2_EN, ucCompleted);
        }
        ……
        if (ucCompleted) {
            __sdCompletedCommand(psdio, ucEvent);                     /* 通知事件處理完成  */
        }
    }
 
    return(LW_NULL);
}

    當應答狀態位被置位後,就可以調用如程序清單 3-6所示代碼,讀取出應答的內容。

程序清單 3-6應答產生後調用

static VOID __sdCompletedCommand(PSDIO_DAT psdio, UINT8 ucEvent)
{
    ……
    UINT32 uiIntSts = readl(REG_SDH_INTSTS);
 
    if (uiIntSts & BIT_INTSTS_RITO_IF) {
        writel(BIT_INTSTS_RITO_IF, REG_SDH_INTSTS);
        ……
    }
 
    if (psdio->SDIO_eCompleteWhat == COMPLETION_RSPFIN ||
        psdio->SDIO_eCompleteWhat == COMPLETION_XFERFINISH_RSPFIN) {
        if (ucEvent & SD_EVENT_RSP_IN) {
            psdio->SDIO_psdcmd->SDCMD_uiResp[0] = (readl(REG_SDH_RESP0) << 8) |
                                                  (readl(REG_SDH_RESP1) & 0xff);
            psdio->SDIO_psdcmd->SDCMD_uiResp[1] = 0;
            psdio->SDIO_psdcmd->SDCMD_uiResp[2] = 0;
            psdio->SDIO_psdcmd->SDCMD_uiResp[3] = 0;
        } else if (ucEvent & SD_EVENT_RSP2_IN) {
            pucPtr = (UINT8 *)REG_SDH_FB0;
            for (i = 0, j = 0; j < 5; i += 4, j++) {
                uiTmp[j] = (*(pucPtr + i)     << 24) |
                           (*(pucPtr + i + 1) << 16) |
                           (*(pucPtr + i + 2) <<  8) |
                           (*(pucPtr + i + 3));
            }
            
            for (i = 0; i < 4; i++) {
                psdio->SDIO_psdcmd->SDCMD_uiResp[i] = ((uiTmp[i]     & 0x00ffffff) <<  8) |
                                                      ((uiTmp[i + 1] & 0xff000000) >> 24);
            }
        }
    }
    ……
 
    psdio->SDIO_eCompleteWhat = COMPLETION_NONE;
    __mciRequestDone(psdio);
}

3.3.3 讀取和寫入數據

    在數據讀寫之前需要配置DMA傳輸的塊大小,如程序清單 3-7所示。

程序清單 3-7數據傳輸前準備

static INT __sendDataPrepare (PSDIO_DAT psdio)
{
    ……
    /*
     * 設置塊傳輸的塊大小
     */
    uiBlkSiz = psdio->SDIO_psddata->SDDAT_uiBlkSize;
    uiBlkLen = psdio->SDIO_psddata->SDDAT_uiBlkNum;
    writel(uiBlkSiz - 1, REG_SDH_BLEN);
 
    if ((uiBlkSiz > 512) || (uiBlkLen >= 256)) {
        printk("ERROR: don't support read/write 256 blocks in on CMD\n");
    } else {
        uiRegCtl  = readl(REG_SDH_CTL) & ~0x00ff0000;
        uiRegCtl |= (uiBlkLen << 16);
        writel(uiRegCtl, REG_SDH_CTL);
    }
    /*
     * 設置DMA傳輸基址
     */
    writel((UINT32)psdio->SDIO_puiBuf, REG_SDH_DMASA);
 
    return(ERROR_NONE);
}

4. 總結

    本文章結合Nuc970說明了SylixOS中非標SD驅動移植的方法,標準SD驅動實現方法可能和本文介紹內容差異較大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章