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 R1和R1b應答
(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驅動實現方法可能和本文介紹內容差異較大。