文章目錄
1 6UL的USDHC簡介
6UL USDHC(Ultra Secured Digital Host Controller)是一個很強大的IP。它支持SD/SDIO/MMC協議。
USDHC的IP很複雜,光寄存器就有30多個,每一個寄存器都是十幾個控制位。這裏爲了簡化demo,只支持了QEMU上的SD標準卡的讀寫,很多USDHC的功能都沒用到,因此本節着重用到的一些功能做介紹。USDHC IP詳細的文檔參考6UL Reference Mannual,Chapter 58
Ultra Secured Digital Host Controller (uSDHC)
1.1 USDHC Block Diagram
- CLK(input):外部輸入時鐘
- CLK(output):外部輸出時鐘,主要用於SD卡的SCK。
- CMD(input)/(output):input是卡輸入到USDHC的CMD信號,output是USDHC到卡的CMD信號。
- DATA[7:0]:同樣的,input是外部卡輸入到USDHC,output是USDHC輸出到卡,用的是同一個引腳,只是在IP內部是分input,output。
- DLL:用於輸入信號延時。當輸入信號從SD卡到USDHC內部時,是有延時存在的,而此時如果用USDHC的CLK去採樣,會導致採樣的數據出現偏差,因此DLL會對USDHC內部的CLK進行delay去適應CMD,DATA線的延時。有一些卡有外部DQS,那可以使用外部DQS。
- Interrupt Generator:USDHC中產生中斷的模塊,當有中斷髮生後,Interrupt會產生IRQ信號給CPU。
- Register Bank:CPU使用IPS Bus訪問,軟件訪問USDHC的接口。
- PIO,Buffer Control,TX/RX Buffer, SRAM, 這一部分都是跟USDHC內部的數據輸入輸出有關,對於軟件來說是透明的。
- DMA:USDHC內部有多種DMA用於搬運數據
- CMD_CTRL,DATA_CTRL,CLK_CTRL:通過寄存器裏的配置產生CMD,CLK和DATA,然後輸出到SD總線上。
1.2 USDHC支持的模式
USDHC對於SD卡的支持如下:
- SD 1-bit
- SD 4-bit
- Identification Mode (up to 400 kHz)
- SD/SDIO full speed mode (up to 25 MHz)
- SD/SDIO high speed mode (up to 50 MHz)
- SD/SDIO UHS-I mode (up to 208 MHz in SDR mode, up to 50 MHz in DDR mode)
demo中使用了SD-4bit, SD/SDIO full speed mode。
1.3 外部信號
demo中用到了SD1_CLK, SD1_CMD, SD1_DATA0/1/2/3,這些pin的iomux默認ALT0就是USDHC1的引腳,因此在代碼中不需要去配這些pin的iomux。
1.4 Data Buffer
USDHC用了一個可以配置的data buffer來傳輸SD bus的數據。 這個data buffer是在SD卡和CPU之間的一個臨時性buffer。 讀寫watermark level都是可配置的,大小從1到128 word(512 bytes)。每一筆讀寫的burst length也可以配置,最大31個word。
USDHC 提供了三種訪問data buffer的方式:
- CPU 輪詢
- 外部DMA
- 內部DMA:包括Simple DMA和ADMA。
demo中使用了第三種的ADMA的方式來進行數據讀寫。
1.5 ADMA
1.5.1 ADMA Engine
USDHC內部DMA實現了一個DMA和AHB主機。
在SD Host Controller標準中,定義了一種叫ADMA的傳輸算法。對於以前的simple dma來說,一旦一個page傳輸完成,就會產生一箇中斷給CPU,然後CPU需要重新去寫DMA的系統地址。
ADMA定義了一種可編程的ADMA descriptor標。主機驅動能自動計算出下一個page的地址而不需要在傳輸完一個page後再去重新寫DMA的寄存器。這就減少了SD控制機像CPU中斷的次數,提高了吞吐。
USDHC 實現了兩種ADMA:
- ADMA1: 只支持4K對齊的內存地址。
- ADMA2:沒有對齊的限制。
兩種ADMA的descirptor表是不同的,文中使用了ADMA2。
ADMA能夠識別不同的descriptor,如果"END"標誌位被設立了,那麼在ADMA執行完該descriptor後就會停止。
1.5.2 ADMA2 Descriptor格式
ADMA2包含以下幾種descriptor:
- Valid/Invalid descriptor.
- Nop descriptor.
- Rsv descriptor.
- Set data length & address descriptor.
- Link descriptor.
- Interrupt flag & End flag
ADMA2 descriptor的格式如下圖,每一個descriptor佔用64個bit:
- [63:32]存放着改描述符指向的內存地址
- [31:16]存放該描述符傳輸塊的長度
- [5:4]是該描述符的屬性,00爲NOP描述符,01位Rsv描述符,10爲傳輸描述符,11爲Link描述符。
- [2]爲中斷使能位,當該描述符傳輸完成後,是否要產生DMA中斷。
- [1]爲END標識符,標識該描述符爲最後一個描述符
- [0]爲Valid標識符,標識該描述符是否是一個有效的描述符
ADMA2描述符的數據結構如下圖: - System address register存着描述符表的地址,即第0個描述符的地址
- 如果該描述符不是link描述符,那麼硬件會順序的往下取描述符
- 如果該描述符是link描述符,那麼硬件下一個會去查找到link的那個描述符。
USDHC會根據當前描述符去做數據搬運操作。
1.6 Register
USDHC的寄存器非常多而且demo中只用到了一部分寄存器,這裏不一一介紹了,在第三節中通過代碼來介紹所使用到的寄存器。
2 SD協議簡介
[注:本節節選自SD2.0標準協議完整版]
SD卡協議也非常長,這裏只截取了部分重要的概念,同樣的沒辦法列出所有SD協議的細節,在第三節中通過代碼來介紹所使用的SD卡協議。
2.1 SD總線拓撲
SD 總線包含下面的信號:
CLK: 時鐘信號
CMD: 雙向命令/響應信號
DAT0-DAT3: 雙向數據信號
Vdd,Vss1,Vss2: 電源和地信號
SD 卡總線有一個主(應用),多個從(卡),同步的星型拓撲結構(圖3-2)。時鐘,電源和
地信號是所有卡都有的。命令(CMD)和數據(DAT0-3)信號是根據每張卡的,提供連續地點對點連接到所有卡。
在初始化時,處理命令會單獨發送到每個卡,允許應用程序檢測卡以及分配邏輯地址給物理卡槽。數據總是單獨發送(接收)到(從)每張卡。但是,爲了簡化卡的堆棧操作,在初始化過程結束後,所有的命令都是同時發送到所有卡。地址信息包含在命令包中。
SD 總線允許數據線的動態配置。上電後,SD 卡默認只使用DAT0 來傳輸數據。初始化之後,主機可以改變總線寬度(使用的數據線數目)。這個功能允許硬件成本和系統性能之間的簡單交換。
2.2 SD總線協議
SD 總線的通信是基於命令和數據流的。由一個起始位開始,由一個停止位終止。
● 命令(Command):命令就是一個標記,用於發起一個操作。由主機發送到單個卡(尋址命
令)或者所有卡(廣播命令)。命令在CMD 線上是連續傳輸的。
● 響應(Response):響應是一個標記,從尋址的卡或者所有卡(同步)發送給主機,作爲向
前接收到的命令的回答。響應也是在CMD 線上連續傳輸的。
● 數據(Data):數據可以從主機到卡,也可以從卡到主機。通過數據線傳輸。
卡片尋址通過使用會話地址來實現,會話地址會在初始化階段分配給卡。命令,響應和數據塊的結構在第4 章中描述。SD 總線上的基本交互是命令/響應交互(表格3-4)。這種總線交互直接在命令或者響應的結構裏面傳輸他們的信息。此外,一些操作還有數據內容。
SD 卡發送或接收的數據在塊(block)中完成。數據塊以CRC 位來保證成功。目前有單塊或多塊操作。注意:多塊操作模式在快速寫操作時更好一點。多塊傳輸以命令線上的結束命令爲結束標誌。主機端可以配置單線還是多線傳輸。
塊寫操作使用簡單的busy 來表示DAT0 數據線上的持續寫操作,不管使用幾線傳輸。
2.3 SD卡功能描述
主機和卡之間的交互都是由主機控制的。主機發送兩種命令:廣播命令,尋址(點對點)
命令。
● 廣播命令
廣播命令的目的是所有的卡。部分命令需要響應。
● 尋址(點對點)命令
尋址命令是發送給對應地址的卡的,並且會引起這張卡的響應。命令流程的簡介圖,[圖4-1]是卡識別模式,[圖4-3]是數據傳輸模式。命令列舉在命令表格中[表4-19 ~ 表4-28]。當前狀態,收到命令和後續狀態之間的關係在[表4-29]中。後面的章節中,會首先描述各種卡的操作模式。之後,定義了控制時鐘信號的限制。所有SD 卡的命令以及對應的響應,狀態轉換,錯誤條件以及時序都在後續章節
SD 卡系統(host &card)定義了兩種操作模式:
● 卡識別模式
在復位後,查找總線上的新卡的時候,主機會處於“卡識別模式”。卡在復位後會處於
識別模式,直到收到SEND_RCA(CMD3)命令.
● 數據傳輸模式
當RCA 第一次發佈後,卡會處於“數據傳輸模式”。主機會在總線上所有的卡都被識別後進入這個模式。
卡狀態(Card state) | 操作模式(Operation mode) |
---|---|
無效狀態(Inactive State) | 無效模式(Inactive) |
空閒狀態(Idle State) 準備狀態(Ready State) 識別狀態(Identification State) |
卡識別模式(Card identification mode) |
待機狀態(Stand-by State) 傳輸狀態(Transfer State) 發送數據狀態(Sending-data State) 接收數據狀態(Receive-data State) 編程狀態(Programming State) 斷開連接狀態(Disconnect State) |
數據傳輸模式(Data transfer mode) |
在卡識別模式下,主機會復位所有處於“卡識別模式”的卡,確認工作電壓範圍,識別
卡,並且要求他們發佈相對卡地址(Relative Card Address)。這個操作是通過卡各自的CMD線完成的。卡識別模式下,所有數據通信都只通過數據線完成。
總線激活後, 主機啓動卡的初始化和識別進程( 見圖-2) 。初始化進程以命令
SD_SEND_OP_COND(ACMD41)作爲開始,通過設置操作條件和OCR 的HCS 位來進行。HCS(HighCapacity Support)位爲1,表示主機支持高容量SD 卡。爲0 表示不支持。
CMD8 擴展了ACMD41 的功能;參數裏的HCS 位以及響應裏的CCS(Card Capacity Status)位。HCS 會被不迴應CMD8 的卡忽視掉。然而,如果卡不迴應CMD8,主機應該設置HCS 爲0。
標準容量卡會忽略HCS。如果HCS 設置爲0,那麼高容量SD 卡永遠都不會返回ready 狀態(保持busy 位爲0)。卡通過OCR 的busy 位來通知主機ACMD41 的初始化完成了。設置busy 位爲0 表示卡仍然在初始化。設置busy 位爲1,表示已經完成初始化。主機會重複發送ACMD41,直到busy 爲被設置爲1。
卡片只在第一個ACMD41 的命令時,檢查操作條件和OCR 裏面的HCS 位。當重複ACMD41的時候,除了CMD0,主機不應該再發其他命令。
如果卡響應了CMD8,那麼ACMD41 的響應就包括了CCS 字段信息。當卡返回“ready”的時候,CCS 是有效的(busy 位設置爲1)。
CCS=1 表示卡是高容量SD 卡;CCS=0 表示卡是普通SD 卡。
在系統中,主機遵照相同的初始化順序來初始化所有的新卡。不兼容的卡會進入
“Inactive”狀態。主機接着就會發送命令ALL_SEND_CID(CMD2)給每一個卡,來得到他們的CID 號。未識別的卡(處於Ready 狀態的)發送自己的CID 作爲響應。當卡發送了CID 之後,
它就進入“Identification”狀態。之後主機發送SEND_RELATIVE_ADDR(CMD3)命令,通知卡發佈一個新的相對地址(RCA),這個地址比CID 短,用於作爲將來數據傳輸模式的地址。一旦收到RCA,卡就會變爲“Stand-by”狀態。這時,如果主機想要分配另一個RCA 號,它可以再發送一個CMD3,通知卡重新發佈一個RCA 號。最後一個產生的RCA 纔是有效的。主機會重複識別進程,爲系統中的每個卡循環發送“CMD2”和“CMD3”。
3 QEMU SD卡讀寫demo
源碼鏈接
整個SD卡驅動代碼結構樹如下
include
imx_usdhc.h
sd_card.h
device
sd_card.c
driver
imx_usdhc.c
imx_usdhc.h定義了usdhc控制器,sd_card.h抽象了sd_card的操作。
3.1 USDHC層
include/imx_usdhc.h
#ifndef __IMX_USDHC__
#define __IMX_USDHC__
#include <stdint.h>
#include <stdbool.h>
#include <imx_uart.h>
#include <sd_card.h>
typedef struct imx_usdhc_tag
{
volatile uint32_t dma_sys_addr; //<00h
volatile uint32_t blk_att; //<04h
volatile uint32_t cmd_arg; //<08h
volatile uint32_t cmd_xfr_type; //<0Ch
volatile uint32_t cmd_rsp0; //<10h
volatile uint32_t cmd_rsp1; //<14h
volatile uint32_t cmd_rsp2; //<18h
volatile uint32_t cmd_rsp3; //<1Ch
volatile uint32_t data_buff_acc_port; //<20H
volatile uint32_t pres_state; //<24h
volatile uint32_t prot_ctrl; //<28h
volatile uint32_t sys_ctrl; //<2Ch
volatile uint32_t int_status; //<30h
volatile uint32_t int_status_en; //<34h
volatile uint32_t int_singal_en; //<38h
volatile uint32_t autocmd12_err_status; //<3Ch
volatile uint32_t host_ctrl_cap; //<40h
volatile uint32_t wtmk_lvl; //<44h
volatile uint32_t mix_ctrl; //<48h
volatile uint32_t reserve1; //<4Ch
volatile uint32_t force_event; //<50h
volatile uint32_t adma_error_status; //<54h
volatile uint32_t adma_sys_addr; //<58h
volatile uint32_t reserve2; //<5Ch
volatile uint32_t dll_ctrl; //<60h
volatile uint32_t dll_status; //<64h
volatile uint32_t clk_tune_ctrl_status; //<68h
volatile uint32_t reserve3; //<6Ch
volatile uint32_t reserve4[20]; //<70h-BFh
volatile uint32_t vend_spec; //<C0h
volatile uint32_t mmc_boot; //<C4h
volatile uint32_t vend_spec2; //<C8h
volatile uint32_t tuning_ctrl; //<CCh
} imx_usdhc_t;
首先根據USDHC的register memory map定義了usdhc控制器的結構體imx_usdhc_t, 這部分很簡單,單純的體力活。然後就是根據register memory map裏的每個bit編寫相應的XXX_SHIFT和XXX_MASK。同樣的是體力活,這裏就列出了幾個bit,全部的代碼有幾百行,可以直接看github裏的代碼。
include/imx_usdhc.h
......
#define USDHC_BLKATT_BLKSIZE_SHIFT 0UL
#define USDHC_BLKATT_BLKSIZE_MASK (0xFFFUL << USDHC_BLKATT_BLKSIZE_SHIFT)
#define USDHC_BLKATT_BLKCNT_SHIFT 16UL
#define USDHC_BLKATT_BLKCNT_MASK (0xFFFFUL << USDHC_BLKATT_BLKCNT_SHIFT)
#define USDHC_CMD_XFRTYPE_CMDINX_SHIFT 24UL
#define USDHC_CMD_XFRTYPE_CMDINX_MASK (0x3FUL << USDHC_CMD_XFRTYPE_CMDINX_SHIFT)
#define USDHC_CMD_XFRTYPE_CMDTYPE_SHIFT 22UL
#define USDHC_CMD_XFRTYPE_CMDTYPE_MASK (0x3UL << USDHC_CMD_XFRTYPE_CMDTYPE_SHIFT)
.....
然後我們根據ADMA2 描述符那節所定義的格式定義出ADMA的描述符adma_bd_t;
include/imx_usdhc.h
typedef struct {
uint8_t att;
uint8_t reserved;
uint16_t len;
uint32_t addr;
} __attribute__((packed)) adma_bd_t;
3.1.1 usdhc控制器初始化
首先在頭文件裏先聲明初始化函數ushdc_init。
imx_usdhc.h
extern bool usdhc_init(void *);
初始化的流程如下
接着在c文件裏實現該函數
imx_usdhc.c
bool usdhc_init(void *host)
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
USDHC_TRACE("%s: usdhc:%x\n", __func__, usdhc);
//STUB: iomux config here
//STUB: clock config here
usdhc->sys_ctrl |= USDHC_SYS_CTRL_RSTA_MASK;
while ((usdhc->sys_ctrl & USDHC_SYS_CTRL_RSTA_MASK) != 0)
;
usdhc_set_data_width(usdhc, USDHC_DWT_1BIT);
usdhc_set_endian_mode(usdhc, USDHC_EMODE_LITTLE_ENDIAN);
USDHC_TRACE("%s: disable usdhc dll\n", __func__);
usdhc->dll_ctrl &= ~USDHC_DLL_CTRL_DLL_CTRL_ENABLE_MASK;
USDHC_TRACE("%s: select adma2\n", __func__);
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_DMASEL_MASK;
usdhc->prot_ctrl |= (2 << USDHC_PROT_CTRL_DMASEL_SHIFT) & USDHC_PROT_CTRL_DMASEL_MASK;
usdhc_set_clock(usdhc, IDENTIFICATION_FREQ);
usdhc_initialization_active(usdhc);
return true;
}
代碼中首先復位了USDHC控制器。
usdhc->sys_ctrl |= USDHC_SYS_CTRL_RSTA_MASK;
while ((usdhc->sys_ctrl & USDHC_SYS_CTRL_RSTA_MASK) != 0)
;
根據RSTA的定義,復位USDHC首先往RSTA中寫入1,然後等待RSTA爲0即可
接着調用了usdhc_set_data_width(usdhc, USDHC_DWT_1BIT); 設置USDHC數據位寬爲1bit。usdhc_set_data_width的實現在同一個文件中。代碼實現很簡單,僅僅只是設置了uSDHCx_PROT_CTRL的DTW位。
imx_usdhc.c
void usdhc_set_data_width(void *host, uint8_t dtw)
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_DTW_MASK;
usdhc->prot_ctrl |= (dtw << USDHC_PROT_CTRL_DTW_SHIFT) & USDHC_PROT_CTRL_DTW_MASK;
}
根據DTW位的定義,定義宏如下:
imx_usdhc.h
#define USDHC_DWT_1BIT 0
#define USDHC_DWT_4BIT 1
#define USDHC_DWT_8BIT 2
然後根據流程,調用usdhc_set_endian_mode(usdhc, USDHC_EMODE_LITTLE_ENDIAN); 設置USDHC的字節序爲小端模式。usdhc_set_endian_mode實現如下
imx_usdhc.c
void usdhc_set_endian_mode(void *host, uint8_t emode)
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_EMODE_MASK;
usdhc->prot_ctrl |= (emode << USDHC_PROT_CTRL_EMODE_SHIFT) & USDHC_PROT_CTRL_EMODE_MASK;
}
imx_usdhc.h
#define USDHC_EMODE_BIG_ENDIAN 0
#define USDHC_EMODE_HALF_WORD_BIG_ENDIAN 1
#define USDHC_EMODE_LITTLE_ENDIAN 2
usdhc->dll_ctrl &= ~USDHC_DLL_CTRL_DLL_CTRL_ENABLE_MASK;
清了DLL_CTRL寄存器的enable位,關閉DLL。
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_DMASEL_MASK;
usdhc->prot_ctrl |= (2 << USDHC_PROT_CTRL_DMASEL_SHIFT) & USDHC_PROT_CTRL_DMASEL_MASK;
這兩行代碼選擇了ADMA2作爲數據傳輸的方式,起定義如下:
然後調用了usdhc_set_clock(usdhc, IDENTIFICATION_FREQ); 將clock設置爲識別模式的clock。這裏注意的是,這裏的usdhc_set_clock並沒有改變clock的實際頻率,在QEMU上對於timing的仿真是沒有什麼意義的,因爲都是純軟件的。但在真實的芯片上需要去改變clock頻率,否則過高的clock會使SD卡在初始化的時候失敗。
最後調用了usdhc_initialization_active 去激活SD總線。
imx_usdhc.c
void usdhc_initialization_active(void *host)
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
usdhc->sys_ctrl |= USDHC_SYS_CTRL_INITA_MASK;
while ((usdhc->sys_ctrl & USDHC_SYS_CTRL_INITA_MASK) != 0)
;
}
3.1.2 usdhc發送命令
usdhc中發送命令需要操作以下寄存器:
- Command Argument (uSDHC1_CMD_ARG)
- Command Transfer Type (uSDHC1_CMD_XFR_TYP)
- Interrupt Status (uSDHC1_INT_STATUS)
- Interrupt Status Enable (uSDHC1_INT_STATUS_EN)
- Mixer Control (uSDHC1_MIX_CTRL)
- Protocol Control (uSDHC1_PROT_CTRL)
先貼出代碼,然後一一解釋
imx_usdhc.c
bool usdhc_send_command(void *host, uint8_t cmd_idx, uint32_t arg)
{
usdhc_cmd_t cmd;
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
uint32_t temp;
bool ret = true;
USDHC_TRACE("%s: cmd:%d\n", __func__, cmd_idx);
if (cmd_idx == CMD0) {
usdhc_create_cmd(&cmd, CMD0, 0, READ, RESPONSE_NONE,
false, false, false, false);
} else if (cmd_idx == CMD55) {
usdhc_create_cmd(&cmd, CMD55, 0, READ, RESPONSE_48,
false, true, true, false);
} else if (cmd_idx == ACMD41) {
usdhc_create_cmd(&cmd, ACMD41, arg, READ, RESPONSE_48,
false, false, false, false);
} else if (cmd_idx == CMD2) {
usdhc_create_cmd(&cmd, CMD2, 0, READ, RESPONSE_136,
false, true, false, false);
} else if (cmd_idx == CMD3) {
usdhc_create_cmd(&cmd, CMD3, arg, READ, RESPONSE_48,
false, true, true, false);
} else if (cmd_idx == CMD7) {
usdhc_create_cmd(&cmd, CMD7, arg, READ, RESPONSE_48_CHECK_BUSY,
false, true, true, false);
} else if (cmd_idx == CMD16) {
usdhc_create_cmd(&cmd, CMD16, arg, READ, RESPONSE_48,
false, true, true, false);
} else if (cmd_idx == CMD18) {
usdhc_create_cmd(&cmd, CMD18, arg, READ, RESPONSE_48,
true, true, true, true);
} else if (cmd_idx == CMD25) {
usdhc_create_cmd(&cmd, CMD25, arg, WRITE, RESPONSE_48,
true, true, true, true);
}
usdhc->int_status = 0x117f01ff; /*clear interrupt status register*/
usdhc->int_status_en |= 0x007f013f; /*enable all the interrupt status*/
if (cmd.dma_en == true) {
usdhc->int_status_en |= USDHC_INT_STATUS_EN_DINTSEN_MASK;
}
/* wait cmd line free */
while ((usdhc->pres_state & USDHC_PRES_STATE_CIHB_MASK) != 0);
/* wait data line free */
if (cmd.dat_pres == true) {
while ((usdhc->pres_state & USDHC_PRES_STATE_CDIHB_MASK) != 0);
}
/* write command arugment in the Command Argument Register */
usdhc->cmd_arg = cmd.arg;
if (cmd.dma_en == false) {
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_DMASEL_MASK;
} else {
usdhc->prot_ctrl |= (2 << USDHC_PROT_CTRL_DMASEL_SHIFT) & USDHC_PROT_CTRL_DMASEL_MASK;
}
temp = usdhc->mix_ctrl & 0xFFFFFFC0;
if (cmd.dma_en == true)
temp |= USDHC_MIX_CTRL_DMAEN_MASK;
else
temp &= ~USDHC_MIX_CTRL_DMAEN_MASK;
if (cmd.blk_cnt_en_chk == true)
temp |= USDHC_MIX_CTRL_BCEN_MASK;
else
temp &= ~USDHC_MIX_CTRL_BCEN_MASK;
if (cmd.auto_cmd12_en == true)
temp |= USDHC_MIX_CTRL_AC12EN_MASK;
else
temp &= ~USDHC_MIX_CTRL_AC12EN_MASK;
if (cmd.ddr_en == true)
temp |= USDHC_MIX_CTRL_DDREN_MASK;
else
temp &= ~USDHC_MIX_CTRL_DDREN_MASK;
if (cmd.xfer_type == READ)
temp |= USDHC_MIX_CTRL_DTDSEL_MASK;
else
temp &= ~USDHC_MIX_CTRL_DTDSEL_MASK;
if (cmd.blk_type == MULTIPLE_BLK)
temp |= USDHC_MIX_CTRL_MSBSEL_MASK;
else
temp &= ~USDHC_MIX_CTRL_MSBSEL_MASK;
usdhc->mix_ctrl = temp;
temp = usdhc->cmd_xfr_type;
temp &= ~USDHC_CMD_XFRTYPE_RSPTYPE_MASK;
switch (cmd.resp_format) {
case RESPONSE_NONE:
default:
temp |= (0 << USDHC_CMD_XFRTYPE_RSPTYPE_SHIFT) & USDHC_CMD_XFRTYPE_RSPTYPE_MASK;
break;
case RESPONSE_136:
temp |= (1 << USDHC_CMD_XFRTYPE_RSPTYPE_SHIFT) & USDHC_CMD_XFRTYPE_RSPTYPE_MASK;
break;
case RESPONSE_48:
temp |= (2 << USDHC_CMD_XFRTYPE_RSPTYPE_SHIFT) & USDHC_CMD_XFRTYPE_RSPTYPE_MASK;
break;
case RESPONSE_48_CHECK_BUSY:
temp |= (3 << USDHC_CMD_XFRTYPE_RSPTYPE_SHIFT) & USDHC_CMD_XFRTYPE_RSPTYPE_MASK;
break;
}
if (cmd.crc_chk == true)
temp |= USDHC_CMD_XFRTYPE_CCCEN_MASK;
else
temp &= ~USDHC_CMD_XFRTYPE_CCCEN_MASK;
if (cmd.cmdidx_chk == true)
temp |= USDHC_CMD_XFRTYPE_CICEN_MASK;
else
temp &= ~USDHC_CMD_XFRTYPE_CICEN_MASK;
if (cmd.dat_pres == true)
temp |= USDHC_CMD_XFRTYPE_DPSEL_MASK;
else
temp &= ~USDHC_CMD_XFRTYPE_DPSEL_MASK;
temp &= ~USDHC_CMD_XFRTYPE_CMDINX_MASK;
temp |= (cmd.cmd << USDHC_CMD_XFRTYPE_CMDINX_SHIFT) & USDHC_CMD_XFRTYPE_CMDINX_MASK;
usdhc->cmd_xfr_type = temp;
if (cmd.dma_en == false) {
/* DMAE|CIE|CEBE|CCE|CTOE|CC */
USDHC_TRACE("%s wait DMAE|CIE|CEBE|CCE|CTOE|CC\n", __func__);
while ((usdhc->int_status & 0x100F0001) == 0);
} else {
USDHC_TRACE("%s wait DMAE|DEBE|DCE|DTOE|CIE|CEBE|CCE|CTOE|TC\n", __func__);
/* DMAE|DEBE|DCE|DTOE|CIE|CEBE|CCE|CTOE|TC */
while((usdhc->int_status & 0x107F0002) == 0);
}
/* mask all the signals */
usdhc->int_singal_en = 0;
/* check CCE or CTOE error */
if ((usdhc->int_status & USDHC_INT_STATUS_CCE_MASK) != 0) {
ret = false;
USDHC_TRACE("%s Command CRC error\n", __func__);
goto cleanup;
}
if ((usdhc->int_status & USDHC_INT_STATUS_CTOE_MASK) != 0) {
ret = false;
USDHC_TRACE("%s Command Timeout error\n", __func__);
goto cleanup;
}
cleanup:
USDHC_TRACE("%s, ret:%d\n", __func__, ret);
return ret;
}
輸入的參數第一個是cmd的id,第二個是cmd的argument。代碼的第一步是根據傳入的cmd的id來創建一個cmd。cmd的結構體是由我們自己定義的,這個並非硬件spec定義的。主要定義了一些標誌位用於操作寄存器。先來看一看usdhc_cmd_t的定義。
imx_usdhc.h
#define WRITE 0
#define READ 1
#define RESPONSE_NONE 0
#define RESPONSE_136 1
#define RESPONSE_48 2
#define RESPONSE_48_CHECK_BUSY 3
#define SINGLE_BLK 0
#define MULTIPLE_BLK 1
typedef struct usdhc_cmd_tag{
uint32_t cmd;
uint32_t arg;
uint8_t xfer_type;
uint8_t resp_format;
bool dat_pres;
bool crc_chk;
bool cmdidx_chk;
bool blk_cnt_en_chk;
uint8_t blk_type;
bool dma_en;
bool auto_cmd12_en;
bool ddr_en;
} usdhc_cmd_t;
-
cmd是cmd的idx
-
arg記錄的是發送cmd所需要的的argument
-
xfer_type有READ和WRITE,這一個flag用於選擇uSDHCx_MIX_CTRL中的DTDSEL位。
-
resp_format這個flag標識cmd response的格式。用來設置uSDHCx_CMD_XFR_TYP中的RSPTYP位
-
dat_pres用於標識該cmd是否有數據傳輸。用於設置uSDHCx_CMD_XFR_TYP中的DPSEL位。
-
crc_chk標識是否需要對cmd的crc進行檢查,用於設置uSDHCx_CMD_XFR_TYP中的CCCEN位。
-
cmdidx_chk標識是否對cmd的id進行check,用於設置uSDHCx_CMD_XFR_TYP中的CICEN位。
-
blk_cnt_en_chk用於設置uSDHCx_MIX_CTRL的BCEN位
-
blk_type用於設置uSDHCx_MIX_CTRL的MSBSEL位
-
dma_en用於設置uSDHCx_MIX_CTRL中的DMAEN位。
-
auto_cmd12_en用於設置uSDHCx_MIX_CTRL的AC12EN位。當該位設置後,在傳輸多個block的時候,當一個block傳輸完成後,硬件會自動發一個CMD12命令。
-
ddr_en 用於設置uSDHCx_MIX_CTRL中的DDR_EN位。
usdhc_create_cmd 的實現很簡單,就是將傳入的參數賦值到usdhc_cmd_t中的各個字段。
imx_usdhc.c
static void usdhc_create_cmd(usdhc_cmd_t *cmd,
uint32_t idx,
uint32_t arg,
uint8_t xfer_type,
uint8_t format,
bool data_pres,
bool crc_chk,
bool cmd_idx_chk,
bool dma_en)
{
cmd->cmd = idx;
cmd->arg = arg;
cmd->xfer_type = xfer_type;
cmd->resp_format = format;
cmd->dat_pres = data_pres;
cmd->crc_chk = crc_chk;
cmd->cmdidx_chk = cmd_idx_chk;
if (dma_en) {
cmd->blk_cnt_en_chk = true;
cmd->blk_type = MULTIPLE_BLK;
cmd->auto_cmd12_en = true;
}
else {
cmd->blk_cnt_en_chk = false;
cmd->blk_type = SINGLE_BLK;
cmd->auto_cmd12_en = false;
}
cmd->dma_en = dma_en;
cmd->ddr_en = false;
}
demo中使用了ADMA2,所以當dma_en被設置了之後,AC12EN也需要被設置,不然的話在多個塊傳輸的時候會失敗。
有了該函數後,我們就可以看看每一個cmd的配置了。
- CMD0: 沒有參數,沒有response,所有的check都關閉。不需要使能DMA
if (cmd_idx == CMD0) {
usdhc_create_cmd(&cmd, CMD0, 0, READ, RESPONSE_NONE,
false, false, false, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD0 | bc | 00000000 | - | GO_IDLE_STATE | 復位設備至idle狀態 |
- CMD55: CMD55的地址0,在沒有分配RCA之前,使用0地址進行訪問。RESPONSE格式爲48bit的響應。打開crc_chk和cmd_idx_chk。沒有數據傳輸,故data_pres和dma_en不使能。
} else if (cmd_idx == CMD55) {
usdhc_create_cmd(&cmd, CMD55, 0, READ, RESPONSE_48,
false, true, true, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD55 | ac | [31:16]RCA [15:0]填充位 | R1 | APP_CMD | 告訴卡,下個命令是特定應用命令,而不是標準命令。 |
- ACMD41:
else if (cmd_idx == ACMD41) {
usdhc_create_cmd(&cmd, ACMD41, arg, READ, RESPONSE_48,
false, false, false, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
ACMD41 | bcr | [31]保留位 [30]HCS(OCR30) [29:24]保留位 [23:0]VddVdd 電壓(OCR[23:0]) | R3 | SD_SEND_OP_COND | 發送卡的支持信息(HCS),並要求卡通過命令線返回OCR 寄存器內容。當卡收到SEND_IF_COND 時,HCS 是有效的。保留位設爲0。CCS 位對應OCR[30] |
- CMD2: 用於獲取CID寄存器,CID寄存器返回136bit的響應,參數爲0。
else if (cmd_idx == CMD2) {
usdhc_create_cmd(&cmd, CMD2, 0, READ, RESPONSE_136,
false, true, false, false);
CMD2獲取CID
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD2 | bc | [31:0] 填充位 | R2 | ALL_SEND_CID | 請求設備在CMD線發送其CID編號 |
- CMD3:獲取RCA,起響應是48位的,參數爲SD卡的RCA,發送完命令後SD卡會返回SD卡自身的RCA給
else if (cmd_idx == CMD3) {
usdhc_create_cmd(&cmd, CMD3, arg, READ, RESPONSE_48,
false, true, true, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD3 | ac | [31:16] RCA [15:0] 填充位 | R1 | SET_RELATIVE_ADDR | 分配相對地址到設備 |
- CMD7 設置transfer狀態。選中卡,參數是卡的RCA,響應爲48bit的響應。
} else if (cmd_idx == CMD7) {
usdhc_create_cmd(&cmd, CMD7, arg, READ, RESPONSE_48_CHECK_BUSY,
false, true, true, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD7 | ac | [31:16] RCA [15:0] 填充位 | R2 | SELECT/DESELECT_C ARD | 在stand-by和transfer狀態之間或program- ming和disconnect狀態之間切換設備的命令。兩種情況下,設備以其自己的相對地址被選定並以其他地址被取消選定;地址0取消所有設備的選定。 |
- CMD16: CMD16用於設置SD卡的位寬,參數是設置的block長度,響應爲48bit的響應,沒有數據傳輸,故dma_en和data_pres都沒有設置
else if (cmd_idx == CMD16) {
usdhc_create_cmd(&cmd, CMD16, arg, READ, RESPONSE_48,
false, true, true, false);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD16 | ac | [31:0]塊長度 | R1 | SET_BLOCKLEN | 對於標準SD 卡來說,這個命令會設置所有塊命令的長度(字節)。默認的塊長度是512Byte。只有當CSD 允許部分塊讀取操作,設置的長度纔對存儲訪問命令有效。對於高容量SD 卡來說,CMD16 設置的塊長度對於讀寫命令來說沒有硬性,因爲塊長度是固定的512Byte。這個命令只對加鎖/解鎖命令有效。不管哪種,只要塊長度設置大於512Byte,就報錯BLOCK_LEN_ERROR。 |
- CMD18: 讀數據。參數爲讀數據塊的起始塊地址,48bit的響應。有數據傳輸,所以data_pres和dma_en都設上
} else if (cmd_idx == CMD18) {
usdhc_create_cmd(&cmd, CMD18, arg, READ, RESPONSE_48,
true, true, true, true);
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD18 | adtc | [31:0] 數據地址1 | R1 | READ_MULTIPLE_ BLOCK | 從設備向主機連續傳輸數據塊,直至被停止命令中斷,或所要求傳輸的塊數。 |
- CMD25: 寫數據。參數爲寫數據塊的起始塊地址,48bit的響應。有數據傳輸,所以data_pres和dma_en都設上。數據方向爲輸出。
} else if (cmd_idx == CMD25) {
usdhc_create_cmd(&cmd, CMD25, arg, WRITE, RESPONSE_48,
true, true, true, true);
}
命令索引 | 類型 | 參數 | 應答 | 縮寫 | 命令說明 |
---|---|---|---|---|---|
CMD25 | adtc | [31:0] 數據地址1 | R1 | WRITE_MULTIPLE_ BLOCK | 連續寫數據塊直到STOP_TRANSMISSION 命令被髮送。塊長度和WRITE_BLOCK 一致。 |
到這cmd的就能根據cmd的索引創建好了。接下來就是根據不同的配置來配置以下寄存器
- Command Argument (uSDHC1_CMD_ARG)
- Command Transfer Type (uSDHC1_CMD_XFR_TYP)
- Interrupt Status (uSDHC1_INT_STATUS)
- Interrupt Status Enable (uSDHC1_INT_STATUS_EN)
- Mixer Control (uSDHC1_MIX_CTRL)
- Protocol Control (uSDHC1_PROT_CTRL)
usdhc->int_status = 0x117f01ff; /*clear interrupt status register*/
usdhc->int_status_en |= 0x007f013f; /*enable all the interrupt status*/
if (cmd.dma_en == true) {
usdhc->int_status_en |= USDHC_INT_STATUS_EN_DINTSEN_MASK;
}
/* wait cmd line free */
while ((usdhc->pres_state & USDHC_PRES_STATE_CIHB_MASK) != 0);
/* wait data line free */
if (cmd.dat_pres == true) {
while ((usdhc->pres_state & USDHC_PRES_STATE_CDIHB_MASK) != 0);
}
/* write command arugment in the Command Argument Register */
usdhc->cmd_arg = cmd.arg;
if (cmd.dma_en == false) {
usdhc->prot_ctrl &= ~USDHC_PROT_CTRL_DMASEL_MASK;
} else {
usdhc->prot_ctrl |= (2 << USDHC_PROT_CTRL_DMASEL_SHIFT) & USDHC_PROT_CTRL_DMASEL_MASK;
}
temp = usdhc->mix_ctrl & 0xFFFFFFC0;
usdhc->mix_ctrl = temp;
......
temp &= ~USDHC_CMD_XFRTYPE_CMDINX_MASK;
temp |= (cmd.cmd << USDHC_CMD_XFRTYPE_CMDINX_SHIFT) & USDHC_CMD_XFRTYPE_CMDINX_MASK;
usdhc->cmd_xfr_type = temp;
當Command Transfer Type一旦寫入命令,USDHC就會將數據發送到SD總線上。而後只要等待相應的中斷狀態標誌位即可。
if (cmd.dma_en == false) {
/* DMAE|CIE|CEBE|CCE|CTOE|CC */
USDHC_TRACE("%s wait DMAE|CIE|CEBE|CCE|CTOE|CC\n", __func__);
while ((usdhc->int_status & 0x100F0001) == 0);
} else {
USDHC_TRACE("%s wait DMAE|DEBE|DCE|DTOE|CIE|CEBE|CCE|CTOE|TC\n", __func__);
/* DMAE|DEBE|DCE|DTOE|CIE|CEBE|CCE|CTOE|TC */
while((usdhc->int_status & 0x107F0002) == 0);
}
/* mask all the signals */
usdhc->int_singal_en = 0;
當有cmd crc錯誤和timeout錯誤的時候,函數返回失敗
/* check CCE or CTOE error */
if ((usdhc->int_status & USDHC_INT_STATUS_CCE_MASK) != 0) {
ret = false;
USDHC_TRACE("%s Command CRC error\n", __func__);
goto cleanup;
}
if ((usdhc->int_status & USDHC_INT_STATUS_CTOE_MASK) != 0) {
ret = false;
USDHC_TRACE("%s Command Timeout error\n", __func__);
goto cleanup;
}
3.1.3 usdhc接收響應
響應的結構體很簡單,定義在sd_card.h中,一共有4個word的響應和1個byte的響應格式標誌符。
sd_card.h
typedef struct sd_resp_tag {
uint32_t rsp0;
uint32_t rsp1;
uint32_t rsp2;
uint32_t rsp3;
uint8_t resp_format;
} sd_resp_t;
獲取響應的函數實現如下,只需要去讀Command Response0/1/2/3四個寄存器即可。
imx_usdhc.c
void usdhc_get_response(void *host, sd_resp_t *rsp)
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
rsp->rsp0 = usdhc->cmd_rsp0;
rsp->rsp1 = usdhc->cmd_rsp1;
rsp->rsp2 = usdhc->cmd_rsp2;
rsp->rsp3 = usdhc->cmd_rsp3;
}
3.1.4 usdhc讀取一個block
控制usdhc的步驟只需兩步:
- 設置好ADMA描述符表, 這裏因爲只有傳輸一個block,所以該描述符表只有一個描述符。其地址設置爲目標內存地址,長度爲512 byte,即一個block,屬性設爲TRANS,VALID, END。ADMA完成這一個描述符後就結束傳輸。
imx_usdhc.c
bool usdhc_read_block(void *host, uint8_t *dst, uint32_t blk_idx)
{
adma_bd_t adma_bd;
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
USDHC_TRACE("%s dst:%x, blk_idx:0x%x\n", __func__, dst, blk_idx);
adma_bd.addr = (uint32_t)dst;
adma_bd.len = 512;
adma_bd.att = 0x20 | 0x02 | 0x01;
- 然後打開所有中斷標誌位,配置ADMA System Address寄存器爲描述符表的地址,設置好傳輸的block個數和block的大小,以及wtmk_lvl的大小。
imx_usdhc.c
usdhc->int_status = 0x117f01ff; /* clear all the interrupt */
usdhc->adma_sys_addr = (uint32_t)&adma_bd;
usdhc->blk_att = (1 << USDHC_BLKATT_BLKCNT_SHIFT) | 512;
usdhc->wtmk_lvl = 0x00000080;
- 發送CMD18,USDHC就會發出一個CMD18的傳輸到SD總線上。
imx_usdhc.c
return usdhc_send_command(usdhc, CMD18, blk_idx);
}
3.1.5 usdhc寫入一個block
USDHC寫入一個block的過程和讀取一個block過程相似,唯一的不同就是發送的命令不同。讀取使用CMD18,寫入使用CMD25即可。
imx_usdhc.c
bool usdhc_write_block(void *host, uint8_t *src, uint32_t blk_idx)
{
adma_bd_t adma_bd;
imx_usdhc_t *usdhc = (imx_usdhc_t *)host;
USDHC_TRACE("%s src:%x, blk_idx:0x%x\n", __func__, src, blk_idx);
adma_bd.addr = (uint32_t)src;
adma_bd.len = 512;
adma_bd.att = 0x20 | 0x02 | 0x01;
usdhc->int_status = 0x117f01ff;
usdhc->adma_sys_addr = (uint32_t)&adma_bd;
usdhc->blk_att = (1 << USDHC_BLKATT_BLKCNT_SHIFT) | 512;
usdhc->wtmk_lvl = 0x00000080;
return usdhc_send_command(usdhc, CMD25, blk_idx);
}
3.2 SD協議層
3.2.1 sd_card.h
#ifndef __SDCARD_H__
#define __SDCARD_H__
#include <stdint.h>
#include <stdbool.h>
typedef struct sdcard_tag{
void *host;
uint32_t rca;
uint32_t ocr;
uint32_t cid[4];
char product_name[6];
uint8_t major;
uint8_t minor;
bool (*host_init)(void *);
bool (*send_cmd)(void *, uint8_t, uint32_t);
void (*get_resp)(void *, sd_resp_t *);
bool (*read_block)(void *, uint8_t *, uint32_t);
bool (*write_block)(void *, uint8_t *, uint32_t);
} sdcard_t;
#endif
首先我們定義sdcard的結構體sdcard_t。
- host爲sd卡對應的控制器,本例中對應了usdhc。
- rca保存着這張卡的地址。
- ocr保存這張卡的ocr寄存器的值。
- cid保存這張卡的cid寄存器的值。
- product_name記錄這張卡的產品名稱。
- major和minor記錄這張卡的版本號。
- host_init指向了控制器的初始化函數,用來初始化sd卡對應的控制器。
- send_cmd指向了控制器的發送命令函數。
- get_resp指向控制器的讀響應函數。
- read_block指向控制器的讀block函數
- write_block指向控制器的寫block函數
然後就是聲明三個sd卡的API。
extern uint32_t sdcard_init(sdcard_t *);
extern uint32_t sdcard_read_block(sdcard_t *, uint8_t *, uint32_t);
extern uint32_t sdcard_write_block(sdcard_t *, uint8_t *, uint32_t);
3.2.2 SD卡初始化
sd_card.c
sd卡初始化分兩步:
- 調用sdcard->host_init初始化host控制器
- 調用sdcard_device_init初始化SD卡設備
uint32_t sdcard_init(sdcard_t *sdcard)
{
uint32_t ret = SDCARD_SUCCESS;
SDCARD_TRACE("%s entry\n", __func__);
if ((sdcard == NULL) || (sdcard->host_init == NULL)) {
ret = SDCARD_PARAM_NULL;
goto cleanup;
}
if (sdcard->host_init(sdcard->host) == false) {
ret = SDCARD_HOST_INIT_FAILURE;
goto cleanup;
}
ret= sdcard_device_init(sdcard);
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
```c
static uint32_t sdcard_device_init(sdcard_t *sdcard)
{
uint32_t status = SDCARD_SUCCESS;
SDCARD_TRACE("%s entry\n", __func__);
#define SD_INIT_SEQUENCE(func) if ((status = func(sdcard)) != SDCARD_SUCCESS) return status
SD_INIT_SEQUENCE(sdcard_go_idle_cmd0);
SD_INIT_SEQUENCE(sdcard_send_cmd55);
SD_INIT_SEQUENCE(sdcard_get_ocr_acmd41);
SD_INIT_SEQUENCE(sdcard_get_cid_cmd2);
SD_INIT_SEQUENCE(sdcard_set_rca_cmd3);
SD_INIT_SEQUENCE(sdcard_select_card_cmd7);
SD_INIT_SEQUENCE(sdcard_set_blk_len_cmd16);
return status;
}
sdcard_device_init函數實現了SD卡的初始化流程:
- 調用sdcard_go_idle_cmd0復位SD卡,該函數就是調用host的send_cmd函數發出cmd0。
static uint32_t sdcard_go_idle_cmd0(sdcard_t *sdcard)
{
return sdcard->send_cmd(sdcard->host, CMD0, 0) == true ?
SDCARD_SUCCESS : SDCARD_SEND_COMMADN_FAILURE;
}
- 調用sdcard_send_cmd55和sdcard_get_ocr_acmd41獲取卡的OCR寄存器,在demo中並沒有根據獲得的OCR進行電壓處理。在實際的芯片上是要根據卡得到的OCR進行電壓的適配,比如切換IO的電壓,這個過程叫做電壓檢測。因爲是純軟件模擬,但並沒有模擬IO口電壓這類模擬電路,因此這離僅讀回OCR寄存器,不作任何處理。
static uint32_t sdcard_send_cmd55(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, CMD55, 0);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
static uint32_t sdcard_get_ocr_acmd41(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, ACMD41, 0xff800000);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
- 調用sdcard_get_cid_cmd2獲取卡的CID寄存器,獲取響應後解析出產品名稱並記錄打印下來。
static uint32_t sdcard_get_cid_cmd2(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, CMD2, 0);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
sdcard->cid[0] = resp.rsp0;
sdcard->cid[1] = resp.rsp1;
sdcard->cid[2] = resp.rsp2;
sdcard->cid[3] = resp.rsp3;
sdcard->product_name[5] = 0;
sdcard->product_name[4] = sdcard->cid[2] & (0xff);
sdcard->product_name[3] = (sdcard->cid[2] & (0xff << 8)) >> 8;
sdcard->product_name[2] = (sdcard->cid[2] & (0xff << 16)) >> 16;
sdcard->product_name[1] = (sdcard->cid[2] & (0xff << 24)) >> 24;
sdcard->product_name[0] = sdcard->cid[3] & (0xff);
SDCARD_TRACE("%s: sd card product name:%s\n", __func__, sdcard->product_name);
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
- 調用sdcard_set_rca_cmd3獲取卡的RCA並且記錄下來。
static uint32_t sdcard_set_rca_cmd3(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, CMD3, 0);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard->rca = resp.rsp0;
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
- 調用sdcard_select_card_cmd7使卡進入傳輸模式,即選中該SD卡
static uint32_t sdcard_select_card_cmd7(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, CMD7, sdcard->rca);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
- 調用sdcard_set_blk_len_cmd16設置卡的block大小
static uint32_t sdcard_set_blk_len_cmd16(sdcard_t *sdcard)
{
bool res = true;
uint32_t ret = SDCARD_SUCCESS;
sd_resp_t resp;
res = sdcard->send_cmd(sdcard->host, CMD16, 512);
if (res != true) {
ret = SDCARD_SEND_COMMADN_FAILURE;
goto cleanup;
}
sdcard->get_resp(sdcard->host, &resp);
sdcard_dump_response(&resp);
ret = SDCARD_SUCCESS;
cleanup:
SDCARD_TRACE("%s ret:%d\n", __func__, ret);
return ret;
}
至此SD卡的初始化完成了,其過程有點類似於USB的枚舉,但是比起USB的枚舉還是簡單的多。
3.2.3 SD卡讀寫
SD卡初始化完成後,讀寫就非常簡單了。在sd_card.c中僅僅只是調用了host的read_block和write_block函數。
uint32_t sdcard_read_block(sdcard_t *sdcard, uint8_t *dst, uint32_t blk_idx)
{
return sdcard->read_block(sdcard->host, dst, blk_idx) == true ?
SDCARD_SUCCESS : SDCARD_SEND_COMMADN_FAILURE;
}
uint32_t sdcard_write_block(sdcard_t *sdcard, uint8_t *src, uint32_t blk_idx)
{
return sdcard->write_block(sdcard->host, src, blk_idx) == true ?
SDCARD_SUCCESS : SDCARD_SEND_COMMADN_FAILURE;
}
3.3 測試函數
測試函數很簡單,首先定義了sdcard_t對象,然後將usdhc host端的函數賦給sdcard各個接口,這樣sdcard_t就對應上了usdhc的這個host。
entry.c
static void test_sdcard()
{
sdcard_t sdcard;
imx_usdhc_t *usdhc = (imx_usdhc_t *)0x02190000;
uint8_t buf[512];
uint8_t buf2[512];
uint32_t i;
sdcard.host = usdhc;
sdcard.rca = 0x45670000;
sdcard.host_init = usdhc_init;
sdcard.send_cmd = usdhc_send_command;
sdcard.get_resp = usdhc_get_response;
sdcard.read_block = usdhc_read_block;
sdcard.write_block = usdhc_write_block;
```c
然後將測試buf前16個byte打印出來
```c
printf("\ninit buf as 0\n");
for (i = 0; i < 16; i++) {
printf("%x ", buf[i]);
}
printf("\n");
接着初始化sd卡,然後從卡上讀取一個block出來到buf中,並將讀出的數據打印出來。並將buf2的數據在buf的數據加上1
sdcard_init(&sdcard);
sdcard_read_block(&sdcard, buf, 0);
printf("\nread sdcard before write\n");
for (i = 0; i < 16; i++) {
printf("%x ", buf[i]);
buf2[i] = buf[i] + 1;
}
printf("\n");
將處理過的buf2寫入sd卡的第0個block。然後再將其讀出到buf中並打印出來,可以看到sd卡前16個byte已經加了1了。
printf("write sdcard by add 1\n");
sdcard_write_block(&sdcard, buf2, 0);
sdcard_read_block(&sdcard, buf, 0);
printf("\nread sdcard after write\n");
for (i = 0; i < 16; i++) {
printf("%x ", buf[i]);
}
printf("\n");
while(1);
}
測試使用了一個test.img的文件作爲測試文件,以其中一次測試爲例。在測試程序運行前,前16個byte的數據是
00000000: 0405 0607 0809 0a0b 0c0d 0e0f 1011 1213 ................
在運行完程序後,前16個byte的數據皆增加了1。
00000000: 0506 0708 090a 0b0c 0d0e 0f10 1112 1314 ................
以下就是test_sdcard運行的完整log,注意需要把#define SDCARD_DEBUG定義在頭文件中打開SD協議層的打印。
hello imx6ul bare metal:00000000
init buf as 0
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
sdcard_init entry
sdcard_device_init entry
sdcard_dump_response: rsp0:00000120
sdcard_dump_response: rsp1:00000000
sdcard_dump_response: rsp2:00000000
sdcard_dump_response: rsp3:00000000
sdcard_send_cmd55 ret:0
sdcard_dump_response: rsp0:80ffff00
sdcard_dump_response: rsp1:00000000
sdcard_dump_response: rsp2:00000000
sdcard_dump_response: rsp3:00000000
sdcard_get_ocr_acmd41 ret:0
sdcard_dump_response: rsp0:beef0062
sdcard_dump_response: rsp1:2101dead
sdcard_dump_response: rsp2:51454d55
sdcard_dump_response: rsp3:00aa5859
sdcard_get_cid_cmd2: sd card product name:YQEMU
sdcard_get_cid_cmd2 ret:0
sdcard_dump_response: rsp0:45670500
sdcard_dump_response: rsp1:00000000
sdcard_dump_response: rsp2:00000000
sdcard_dump_response: rsp3:00000000
sdcard_set_rca_cmd3 ret:0
sdcard_dump_response: rsp0:00000700
sdcard_dump_response: rsp1:00000000
sdcard_dump_response: rsp2:00000000
sdcard_dump_response: rsp3:00000000
sdcard_select_card_cmd7 ret:0
sdcard_dump_response: rsp0:00000900
sdcard_dump_response: rsp1:00000000
sdcard_dump_response: rsp2:00000000
sdcard_dump_response: rsp3:00000000
sdcard_set_blk_len_cmd16 ret:0
sdcard_init ret:0
read sdcard before write
00000007 00000008 00000009 0000000a 0000000b 0000000c 0000000d 0000000e 0000000f 00000010 00000011 00000012 00000013 00000014 00000015 00000016
write sdcard by add 1
read sdcard after write
00000008 00000009 0000000a 0000000b 0000000c 0000000d 0000000e 0000000f 00000010 00000011 00000012 00000013 00000014 00000015 00000016 00000017
4 移植FATFS
本節移植最新的FATFS到系統上。
首先下載最新的FATFS:
FatFs R0.14
然後把FATFS目錄下的c文件放到fatfs目錄,將頭文件放在include目錄下如下:
4.1 FATFS初始化
移植FATFS,我們只需要重寫diskio.c即可。
首先實現disk_initialize函數。
diskio.c
#define DEV_SD 0
static sdcard_t s_sdcard;
static bool s_is_sdcard_init;
DSTATUS disk_initialize (
BYTE pdrv
)
{
DSTATUS stat;
uint32_t result;
s_is_sdcard_init = false;
switch(pdrv) {
case DEV_SD:
result = disk_init_sdcard();
break;
default:
stat = STA_NODISK;
break;
}
if (result != SDCARD_SUCCESS) {
stat = STA_NOINIT;
} else {
s_is_sdcard_init = true;
stat = RES_OK;
}
DSIKIO_TRACE("%s stat:%x\n", __func__, stat);
return stat;
}
代碼很簡單,demo中只支持一個SD標準卡設備,當pdrv不是SD卡的時候,返回STA_NODISK。當pdrv是SD卡的時候,調用disk_init_sdcard初始化SD。
static uint32_t disk_init_sdcard()
{
imx_usdhc_t *usdhc = (imx_usdhc_t *)0x02190000;
DSIKIO_TRACE("%s entry\n", __func__);
s_sdcard.host = usdhc;
s_sdcard.rca = 0x45670000;
s_sdcard.host_init = usdhc_init;
s_sdcard.send_cmd = usdhc_send_command;
s_sdcard.get_resp = usdhc_get_response;
s_sdcard.read_block = usdhc_read_block;
s_sdcard.write_block = usdhc_write_block;
return sdcard_init(&s_sdcard);
}
disk_init_sdcard的代碼與上一節的測試程序相似,將SD卡和usdhc綁定起來,然後調用sdcard_init初始化卡即可。
4.2 FATFS讀寫函數
disk_read 的實現也很簡單,最終就是調用sdcard_read去調用驅動讀block上的數據。這裏需要注意的是傳入sdcard_read的地址是sector * 512。這是由於qemu上的SD卡是標準卡。數據地址在標準卡中是以字節爲單位的,而高容量卡中,是以塊(512byte)爲單位的。
DRESULT disk_read (
BYTE pdrv,
BYTE *buff,
LBA_t sector,
UINT count
)
{
DSTATUS ret;
uint32_t res = SDCARD_SUCCESS;
if (pdrv > 0) {
ret = STA_NODISK;
goto cleanup;
}
DSIKIO_TRACE("%s buff:%x, sector:%x, count:%x\n", __func__,
buff, sector, count);
while ((count > 0) || (res != SDCARD_SUCCESS)) {
res = sdcard_read_block(&s_sdcard, buff, sector * 512);
count--;
sector++;
}
ret = (res == SDCARD_SUCCESS) ? RES_OK : RES_PARERR;
cleanup:
DSIKIO_TRACE("%s ret:%x, buff[0]:%x, buff[1]:%x\n", __func__, ret, buff[0], buff[1]);
return ret;
}
disk_write的實現與disk_read類似,最終調用sdcard_write_block去寫SD卡。
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv,
const BYTE *buff,
LBA_t sector,
UINT count
)
{
DSTATUS ret;
uint32_t res = SDCARD_SUCCESS;
if (pdrv > 0) {
ret = STA_NODISK;
goto cleanup;
}
DSIKIO_TRACE("%s buff:%x, sector:%x, count:%x\n", __func__,
buff, sector, count);
while ((count > 0) || (res != SDCARD_SUCCESS)) {
res = sdcard_write_block(&s_sdcard, buff, sector * 512);
count--;
sector++;
}
ret = (res == SDCARD_SUCCESS) ? RES_OK : RES_PARERR;
cleanup:
DSIKIO_TRACE("%s ret:%x, buff[0]:%x, buff[1]:%x\n", __func__, ret, buff[0], buff[1]);
return ret;
}
#endif
4.3 其他函數實現
DSTATUS disk_status (
BYTE pdrv
)
{
DSTATUS stat;
switch(pdrv) {
case DEV_SD:
stat = s_is_sdcard_init == true ? RES_OK : STA_NOINIT;
break;
default:
stat = STA_NODISK;
break;
}
return stat;
}
DRESULT disk_ioctl (
BYTE pdrv,
BYTE cmd,
void *buff
)
{
return RES_OK;
}
至此,FATFS的移植就完成了,很簡單,實現這5個函數即可。
4.4 測試
4.4.1 創建測試文件系統
創建一個128M的FAT32文件系統文件
~/6ul_study/6ul_bare_metal$ mkfs.msdos -F 32 -C testfs.img 131072
mkfs.fat 4.1 (2017-01-24)
掛載到/mnt/sdcard下,添加aa.txt,往aa.txt中寫入"aa.txt:hello fatfs!"。然後umount掉,這樣一個測試的文件系統就創建了。包含一個aa.txt的FAT32文件系統。
~/6ul_study/6ul_bare_metal$sudo mount -t msdos -o loop testfs.img /mnt/sdcard/
~/6ul_study/6ul_bare_metal$sudo vim /mnt/sdcard/aa.txt
~/6ul_study/6ul_bare_metal$ ls /mnt/sdcard/
aa.txt
~/6ul_study/6ul_bare_metal$ sudo umount /mnt/sdcard
~/6ul_study/6ul_bare_metal$ fdisk -l testfs.img
Disk testfs.img: 128 MiB, 134217728 bytes, 262144 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
4.4.2 測試函數
掛載SD卡上的文件系統到fs,然後dump出 fs的信息。
entry.c
static void test_fatfs()
{
FATFS fs;
FIL file;
char buf[64];
char *test_str = "test fatfs string";
uint32_t len = 0;
FRESULT res = 0;
printf("%s entry\n", __func__);
res = f_mount(&fs,"0:",1);
printf("%s f_mount res:%d\n", __func__, res);
然後打開文件aa.txt,讀出內容到buf中,並打印。
res = f_open(&file, "aa.txt", FA_READ);
printf("%s f_open res:%d\n", __func__, res);
f_read(&file, buf, 64, &len);
printf("%s read content: buf:%s\n", __func__, buf);
f_close(&file);
然後打開一個不存在的文件bb.txt,以FA_CREATE_NEW | FA_WRITE方式打開,將char *test_str = “test fatfs string”;寫入到bb.txt中
res = f_open(&file, "bb.txt", FA_CREATE_NEW | FA_WRITE);
f_write(&file, test_str, 64, &len);
f_close(&file);
最後再重新打開bb.txt讀取文件內容到buf中打印出來
res = f_open(&file, "bb.txt", FA_READ);
printf("%s f_open res:%d\n", __func__, res);
f_read(&file, buf, 64, &len);
printf("%s read content: buf:%s\n", __func__, buf);
f_close(&file);
while(1);
}
運行log:
:~/6ul_study/6ul_bare_metal$ make fatfs
hello imx6ul bare metal:00000000
test_fatfs entry
test_fatfs f_mount res:0
===========dump_fatfs=========
fs_type: 00000003
pdrv: 00000000
csize: 00000001
n_fats: 00000002
wflag: 00000000
fsi_flag: 00000000
id: 00000001
n_rootdir: 00000000
last_clst:00000006
free_clst:0003f01b
n_fatent:0003f020
fsize:000007e1
volbase:00000000
fatbase:00000020
dirbase:00000002
winsect:00000001
======================
test_fatfs f_open res:0
test_fatfs read content: buf:aa.txt:hello fatfs!
test_fatfs f_open res:0
test_fatfs read content: buf:test fatfs string
最後重新掛載一下testfs.img,可以看到在該文件系統下存在了bb.txt
~/6ul_study/6ul_bare_metal$ sudo mount -t msdos -o loop testfs.img /mnt/sdcard/
~/6ul_study/6ul_bare_metal$ ls /mnt/sdcard/
aa.txt bb.txt