天天看點

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

本次我們來說一下MMC子系統的控制器的開發部分,這部分也是和硬體平台相關的,在說這個之前,我們先來了解一下相關硬體的基礎知識和概念.

MMC

MMC全稱MultiMedia Card,由西門子公司和SanDisk公司1997年推出的多媒體記憶卡标準。MMC卡尺寸為32mm x24mm x 1.4mm,它将存貯單元和控制器一同做到了卡上,智能的控制器使得MMC保證相容性和靈活性。MMC卡具有MMC和SPI兩種工作模式,MMC模式是預設工作模式,具有MMC的全部特性。而SPI模式則是MMC協定的一個子集,主要用于低速系統。

SD

SD卡全稱Secure DigitalMemory Card,由松下、東芝和SanDisk公司于1999年8月共同開發的新一代記憶卡标準,已完全相容MMC标準。SD卡比MMC卡多了一個進行資料著作權保護的暗号認證功能,讀寫速度比MMC卡快4倍。SD卡尺寸為32mm x 24mm x2.1mm,長寬和MMC卡一樣,隻是比MMC卡厚了0.7mm,以容納更大容量的存貯單元。SD卡與MMC卡保持向上相容,也就是說,MMC卡可以被新的設有SD卡插槽的裝置存取,但是SD卡卻不可以被設有MMC插槽的裝置存取。

SDIO

SDIO全稱Secure DigitalInput and Output Card,SDIO是在SD标準上定義了一種外設接口,它使用SD的I/O接口來連接配接外圍裝置,并通過SD上的I/O資料接口與這些外圍裝置傳輸資料。現在已經有很多手持裝置支援SDIO功能,而且許多SDIO外設也被開發出來,目前常見的SDIO外設有:WIFI Card、GPS Card、Bluetooth Card等等。

eMMC

eMMC全稱Embedded MultiMediaCard,是MMC協會所制定的内嵌式存儲器标準規格,主要應用于智能手機和移動嵌入式産品等。eMMC是一種嵌入式非易失性存儲系統,由閃存和閃存控制器兩部分組成,它的一個明顯優勢是在封裝中內建了一個閃存控制器,它采用JEDEC标準BGA封裝,并采用統一閃存接口管理閃存。eMMC結構由一個嵌入式存儲解決方案組成,帶有MMC接口、快閃儲存設備及主要制器,所有這些由一個小型BGA封裝。由于采用标準封裝,eMMC也很容易更新,并不用改變硬體結構。eMMC的這種将Nand Flash晶片和控制晶片封裝在一起的設計概念,就是為了簡化産品記憶體儲器的使用,客戶隻需要采購eMMC晶片放進産品中,不需要處理其它複雜的Nand Flash相容性和管理問題,減少研發成本和研發周期。

本次是以SD卡拔插做為實驗對象,是以,這裡詳細講一下SDIO和SD卡的基礎.SDIO是一種應用很廣泛的接口,下面給出一張圖清晰地表明了這一點:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD卡實體結構

一張SD卡包括有存儲單元、存儲單元接口、電源檢測、卡及接口控制器和接口驅動器5個部分,存儲單元是存儲資料部件,存儲單元通過存儲單元接口與卡控制單元進行資料傳輸:電源檢測單元保證SD卡工作在合适的電壓下,如出現掉電或上狀态時,它會使控制單元和存儲單元接口複位:卡及接口控制單元控制SD卡的運作狀态,它包括有8個寄存器;接口驅動器控制SD卡引腳的輸入輸出。

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD卡總共有8個寄存器,用于設定或表示SD卡資訊,參考下表,這些寄存器隻能通過對應的指令通路,對SD卡進行控制操作并不是像操作控制器GPIO相關寄存器那樣一次讀寫一個寄存器的,它是通過指令來控制,SDIO定義了64個指令,每個指令都有特殊意義,可以實作某一特定功能,SD卡接收到指令後,根據指令要求對SD卡内部寄存器進行修改,程式控制中隻需要發送組合指令就可以實作SD卡的控制以及讀寫操作。

名稱 bit寬度 描述
CID 128 卡識别号(Card identification number):用來識别的卡的個體号碼(唯一的)
RCA 16 相對位址(Relative card address):卡的本地系統位址,初始化時,動态地由卡建議,主機核準。
DSR 16 驅動級寄存器(Driver Stage Register):配置卡的輸出驅動
CSD 128 卡的特定資料(Card Specific Data):卡的操作條件資訊
SCR 64 SD配置寄存器(SD Configuration Register):SD卡特殊特性資訊
OCR 32 操作條件寄存器(Operation conditions register)
SSR 512 SD狀态(SD Status):SD卡專有特征的資訊
CSR 32 卡狀态(Card Status):卡狀态資訊

SDIO總線

1).總線拓撲

SD卡一般都支援SDIO和SPI這兩種接口,共3種模式,分别是4bit模式,1bit模式和SPI模式,SD卡總線拓撲參考下圖。雖然可以共用總線,但不推薦多卡槽共用總線信号,要求一個單獨SD總線應該連接配接一個單獨的SD卡。

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD卡使用9-pin接口通信,其中3根電源線、1根時鐘線、1根指令線和4根資料線,具體說明如下:

●CLK:時鐘線,由SDIO主機産生;  

●CMD:指令控制線,SDIO主機通過該線發送指令控制SD卡,如果指令要求SD卡提供應答(響應),SD卡也是通過該線傳輸應答資訊;

●D0-3:資料線,傳輸讀寫資料:SD卡可将D0拉低表示忙狀态; 

●VDD、VSS1、VSS2:電源和地信号。

SDIO不管是從主機控制器向SD卡傳輸,還是SD卡向主機控制器傳輸都隻以CLK時鐘線的上升沿為有效。SD卡操作過程會使用兩種不同頻率的時鐘同步資料,一個是識别卡階段時鐘頻率FOD,最高為400kHz,另外一個是資料傳輸模式下時鐘頻率FPP,這個頻率按MHz來計算,可以通過配置寄存器的方式修改.

2).SDIO傳輸模式

SPI mode, 1-bit mode, 4-bit mode.

Pin# SD 4-bit Mode SD 1-bit Mode SPI Mode
1 CD/DAT[3] Data Line 3 N/C Not Used CS Card Select
2 CMD Command Line CMD Command Line DI Data Input
3 VSS1 Ground VSS1 Ground VSS1 Ground
4 VDD Supply Voltage VDD Supply Voltage VDD Supply Voltage
5 CLK Clock CLK Clock SCLK Clock
6 VSS2 Ground VSS2 Ground VSS2 Ground
7 DAT[0] Data Line 0 DATA Data Line DO Data Output
8 DAT[1] Data Line 1/Interrupt IRQ Interrupt IRQ Interrupt
9 DAT[2] Data Line 2/Read Wait RW Read Wait NC Not Used

3).總線協定

SD總線通信是基于指令和資料傳輸的。通訊由一個起始位(“0”),由一個停止位(“1”)終止。SD通信一般是主機發送一個指令(Command),從裝置在接收到指令後作出響應(Response),如有需要會有資料(Data)傳輸參與.SD總線的基本互動是指令與響應互動.

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD資料是以塊(Black)形式傳輸的,SDHC卡資料塊長度一般為512位元組,資料可以從主機到卡,也可以是從卡到主機。資料塊需要CRC位來保證資料傳輸成功。CRC位由SD卡系統硬體生成。HOST控制器可以控制使用單線或4線傳輸,本開發闆設計使用4線傳輸下圖為主機向SD卡寫入資料塊操作示意:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD資料傳輸支援單塊和多塊讀寫,它們分别對應不同的操作指令,多塊寫入還需要使用指令來停止整個寫入操作。資料寫入前需要檢測SD卡忙狀态,因為SD卡在接收到資料後程式設計到存儲區過程需要一定操作時間。SD卡忙狀态通過把D0線拉低表示。資料塊讀操作與之類似,隻是無需忙狀态檢測.使用4資料線傳輸時,每次傳輸4bit資料,每根資料線都必須有起始位、終止位以及CRC位,CRC位每根資料線都要分别檢查,并把檢查結果彙總然後在資料傳輸完後通過D0線回報給主機.SD卡資料包有兩種格式,一種是正常資料(8bit寬),它先發低位元組再發高位元組,而每個位元組則是先發高位再發低位,4線傳輸示意如下圖.

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

4線同步發送,每根線發送一個位元組的其中兩個位,資料位在四線順序排列發送,DAT3資料線發較高位,DAT0資料線發較低位。另外一種資料包發送格式是寬位資料包格式,對SD 卡而言寬位資料包發送方式是針對SD卡SSR(SD狀态)寄存器内容發送的,SSR寄存器總共有512bit,在主機發出ACMD13指令後SD卡将SSR寄存器内容通過DAT線發送給主機。寬位資料包格式見下圖:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

4).指令

SD指令由主機發出,以廣播指令和尋址指令為例,廣播指令是針對與SD主機總線連接配接的所有從裝置發送的,尋址指令是指定某個位址裝置進行指令傳輸。

3.1 指令格式

SD指令格式固定為48bit,都是通過CMD線連續傳輸的(資料線不參與),見下圖:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

SD 指令的組成如下:

●起始位和終止位:指令的主體包含在起始位與終止位之間,它們都隻包含一個資料位,起始位為0,終止位為1。

●傳輸标志:用于區分傳輸方向,該位為1時表示指令,方向為主機傳輸到SD卡,該位為0時表示響應,方向為SD卡傳輸到主機。

指令主體内容包括指令、位址資訊/參數和CRC 校驗三個部分。

●指令号:它固定占用6bit,是以總共有64個指令(代号CMD0~CMD63),每個指令都有特定的用途,部分指令不适用于SD卡操作,隻是專門用于MMC卡或者SD I/O卡。

●位址/參數:每個指令有32bit位址資訊/參數用于指令附加内容,例如,廣播指令沒有位址資訊,這32bit用于指定參數,而尋址指令這32bit用于指定目标SD卡的位址。

●CRC7校驗:長度為7bit的校驗位用于驗證指令傳輸内容正确性,如果發生外部幹擾導緻傳輸資料個别位狀态改變将導緻校準失敗,也意味着指令傳輸失敗,SD卡不執行指令。

3.2 指令類型

SD指令有4種類型:  

●無響應廣播指令(bc),發送到所有卡,不傳回任務響應;

●帶響應廣播指令(bcr),發送到所有卡,同時接收來自所有卡響應;

●尋址指令(ac),發送到標明卡,DAT線無資料傳輸;

●尋址資料傳輸指令(adtc),發送到標明卡,DAT線有資料傳輸。

另外,SD卡主機子產品系統旨在為各種應用程式類型提供一個标準接口。在此環境中,需要有特定的客戶/應用程式功能。為實作這些功能,在标準中定義了兩種類型的通用指令:特定應用指令(ACMD)和正常指令(GEN_CMD)。要使用SD卡制造商特定的ACMD指令如ACMD6,需要在發送該指令之前無發送CMD55指令,告知SD卡接下來的指令為特定應用指令。CMD55指令隻對緊接的第一個指令有效,SD卡如果檢測到CMD55之後的第一條指令為ACMD則執行其特定應用功能,如果檢測發現不是ACMD指令,則執行标準指令。

3.3 指令描述

SD卡系統的指令被分為多個類,每個類支援一種“卡的功能設定”。下表列舉了SD卡部分指令資訊,更多詳細資訊可以參考SD簡易規格檔案說明,表中填充位和保留位都必須被設定為0.雖然沒有必須完全記住每個指令詳細資訊,但越熟悉指令對後面程式設計了解非常有幫助。

指令序号 類型 參數 響應 縮寫 描述
        基本指令(Class 0)
CMD0 bc [31:0]填充位 - GO_IDLE_STATE 複位所有的卡到idle狀态
CMD2 bcr [31:0]填充位 R2 ALL_SEND_CID 通知所有卡通過CMD線傳回CID值
CMD3 bcr [31:0]填充位 R6 SEND_RELATIVE_ADDR 通知所有卡釋出新RCA
CMD4 bc [31:16]DSR[15:0]填充位 - SET_DSR 程式設計所有卡的DSR
CMD7 ac [31:16]RCA[15:0]填充位 R1b SELECT/DESELECT_CARD 選擇/取消選擇RCA 位址卡
CMD8 bcr

[31:12]保留位

[11:8]VHS[7:0]檢查模式

R7 SEND_IF_COND 發送SD卡接口條件,包含主機支援的電壓資訊,并詢問卡是否支援
CMD9 ac [31:16]RCA[15:0]填充位 R2 SEND_CSD 標明卡通過CMD線發送CSD内容
CMD10 ac [31:16]RCA[15:0]填充位 R2 SEND_CID 標明卡通過CMD線發送CID内容
CMD12 ac [31:0]填充位 R1b STOP_TRANSMISSION 強制卡停止傳輸
CMD13 ac [31:16]RCA[15:0]填充位 R1 SEND_STATUS 標明卡通過CMD線發送它狀态寄存器
CMD15 ac [31:16]RCA[15:0]填充位 - GO_INACTIVE_STATE 使標明卡進入“inactive”狀态
        面向塊的讀操作(Class 2)
CMD16 ac [31:0]塊長度 R1 SET_BLOCK_LEN 對于标準SD卡,設定塊指令的長度,對于SDHC卡塊指令長度固定為512位元組
CMD17 adtc [31:0]資料位址 R1 READ_SINGLE_BLOCK 對于标準卡,讀取SEL_BLOCK_LEN長度位元組的塊;對于SDHC卡,讀取512位元組的塊
CMD18 adtc [31:0]資料位址 R1 READ_MULTIPLE_BLOCK 連續從SD卡讀取資料塊,直到被CMD12中斷。塊長度同CMD17
        面向塊的寫操作(Class 4)
CMD24 adtc [31:0]資料位址 R1 WRITE_BLOCK 對于标準卡,寫入SEL_BLOCK_LEN長度位元組的塊;對于SDHC卡,寫入12位元組的塊
CMD25 adtc [31:0]資料位址 R1 WRITE_MILTIPLE_BLOCK 連續向SD卡寫入資料塊,直到被CMD12中斷。每塊長度同CMD17
CMD27 adtc [31:0]填充位 R1 PROGRAM_CSD 對CSD的可程式設計位進行程式設計
        擦除指令(Class 5)
CMD32 ac [31:0]資料位址 R1 ERASE_WR_BLK_START 設定擦除的起始塊位址
CMD33 ac [31:0]資料位址 R1 ERASE_WR_BLK_END 設定擦除的結束塊位址
CMD38 ac [31:0]填充位 R1b ERASE 擦除預先標明的塊
        加鎖指令(Class 7)
CMD42 adtc [31:0]保留 R1 LOCK_UNLOCK 加鎖/解鎖SD卡
        特定應用指令(Class 8)
CMD55 ac [31:16]RCA[15:0]填充位 R1 APP_CMD 指定下個指令為特定應用指令,不是标準指令
CMD56 adtc [31:1]填充位[0]讀/寫 R1 GEN_CMD 通用指令,或者特定應用指令中,用于傳輸一個資料塊,最低位為1表示讀資料,為0表示寫資料
        SD卡特定應用指令
ACMD6 ac [31:2]填充位[1:0]總線寬度 R1 SET_BUS_WIDTH 定義資料總線寬度('00'=1bit,'10'=4bit)
ACMD13 adtc [31:0]填充位 R1 SD_STATUS 發送SD狀态
ACMD41 Bcr

[32]保留位

[30]HCS(OCR[30])

[29:24]保留位

[23:0]VDD電壓(OCR[23:0])

R3 SD_SEND_OP_COND 主機要求卡發送它的支援資訊HCS)和OCR寄存器内容
ACMD51 adtc [31:0]填充位 R1 SEND_SCR 讀取配置寄存器SCR

3.4 響應

響應由SD卡向主機發出,部分指令要求SD卡作出響應,這些響應多用于回報SD卡的狀态。SDIO總共有7個響應類型(代号:R1~R7),其中SD卡沒有R4、R5類型響應。特定的指令對應有特定的響應類型,比如當主機發送CMD3指令時,可以得到響應R6.與指令一樣,SD卡的響應也是通過 CMD 線連續傳輸的。根據響應内容大小可以分為短響應和長響應。短響應是48bit長度,隻有R2類型是長響應,其長度為136bit。各個類型響應具體情況如下表。除了R3類型之外,其他響應都使用CRC7校驗來校驗,對于R2類型是使用CID和CSD寄存内部CRC7。

        R1(正常響應指令)
描述 起始位 傳輸位 指令号 卡狀态 CRC7 終止位
bit 47 46 [45:40] [39:8] [7:1]
位寬 1 1 6 32 7 1
"0" "0" x x x "1"
備注 如果有傳輸到卡的資料,那麼在資料線可能有busy信号
        R2(CID, CSD寄存器)
描述 起始位 傳輸位 保留 [127:1] 終止位
bit 135 134 [133:128] 127
位寬 1 1 6 x 1
"0" "0" "111111" CID或者CSD寄存器[127:1]為的值 "1"
備注 CID寄存器内容作為CMD2和CMD10響應,CSD寄存器内容作為CMD9響應.
        R3(OCR寄存器)
描述 起始位 傳輸位 保留 OCR寄存器 保留 終止位
bit 47 46 [45:40] [39:8] [7:1]
位寬 1 1 6 32 7 1
"0" "0" "111111" x "111111" "1"
備注 OCR寄存器的值作為ACMD41的響應
        R6(釋出的RCA寄存器響應)
描述 起始位 傳輸位 CMD3 RCA寄存器 卡狀态位 CRC7 終止位
bit 47 46 [45:40] [39:8] [7:1]
位寬 1 1 6 16 16 7 1
"0" "0" "000011" x x x "1"
備注 專用于指令CMD3的響應
        R7(釋出的RCA寄存器響應)
描述 起始位 傳輸位 CMD8 保留 接收電壓 檢測模式 CRC7 終止位
bit 47 46 [45:40] [39:20] [19:16] [15:8] [7:1]
位寬 1 1 6 20 4 8 7 1
"0" "0" "001000" "00000h" x x x "1"
備注 專用于指令CMD8的響應,傳回卡支援電壓範圍和檢測模式

4). SD卡的操作模式及切換

4.1 SD卡的操作模式

SD卡系統(包括主機和SD卡)定義了兩種操作模式:卡識别模式和資料傳輸模式。在系統複位後,主機處于卡識别模式,尋找總線上可用的SDIO裝置;同時,SD卡也處于卡識别模式,直到被主機識别到,即當SD卡接收到SEND_RCA(CMD3)指令後,SD卡就會進入資料傳輸模式,而主機在總線上所有卡被識别後也進入資料傳輸模式。在每個操作模式下,SD卡都有幾種狀态,參考下表,通過指令控制實作卡狀态的切換。

操作模式 SD卡狀态
無效模式(Inactive) 無效狀态(Inactive State)
卡識别模式(Card identification mode) 空閑狀态(Idle State)
準備狀态(Ready State)
識别狀态(Identification State)
資料傳輸模式(Data transfer mode) 待機狀态(Stand-by State)
傳輸狀态(Transfer State)
發送資料狀态(Sending-data State)
接收資料狀态(Receive-data State)
程式設計狀态(Programming State)
斷開連接配接狀态(Disconnect State)

4.2 卡識别模式

在卡識别模式下,主機會複位所有處于“卡識别模式”的SD卡,确認其工作電壓範圍,識别SD卡類型,并且擷取SD卡的相對位址(卡相對位址較短,便于尋址)。在卡識别過程中,要求SD卡工作在識别時鐘頻率FOD的狀态下。卡識别模式下SD卡狀态轉換如下圖。

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

主機上電後,所有卡處于空閑狀态,包括目前處于無效狀态的卡。主機也可以發送GO_IDLE_STATE(CMD0)讓所有卡軟複位進而進入空閑狀态,但目前處于無效狀态的卡并不會複位。

主機在開始與卡通信前,需要先确定雙方在互相支援的電壓範圍内。SD卡有一個電壓支援範圍,主機目前電壓必須在該範圍可能才能與卡正常通信。SEND_IF_COND(CMD8)指令就是用于驗證卡接口操作條件的(主要是電壓支援)。卡會根據指令的參數來檢測操作條件比對性,如果卡支援主機電壓就産生響應,否則不響應。而主機則根據響應内容确定卡的電壓比對性。CMD8是SD卡标準V2.0版本才有的新指令,是以如果主機有接收到響應,可以判斷卡為V2.0或更高版本SD卡。

SD_SEND_OP_COND(ACMD41)指令可以識别或拒絕不比對它的電壓範圍的卡。ACMD41指令的VDD電壓參數用于設定主機支援電壓範圍,卡響應會傳回卡支援的電壓範圍。對于對CMD8有響應的卡,把ACMD41指令的HCS位設定為1,可以測試卡的容量類型,如果卡響應的CCS位為1說明為高容量SD卡,否則為标準卡。卡在響應ACMD41之後進入準備狀态,不響應ACMD41的卡為不可用卡,進入無效狀态。ACMD41是應用特定指令,發送該指令之前必須先發CMD55。

ALL_SEND_CID(CMD2)用來控制所有卡傳回它們的卡識别号(CID),處于準備狀态的卡在發送CID之後就進入識别狀态。之後主機就發送SEND_RELATIVE_ADDR(CMD3)指令,讓卡自己推薦一個相對位址(RCA)并響應指令。這個RCA是16bit位址,而CID是128bit位址,使用RCA簡化通信。卡在接收到CMD3并發出響應後就進入資料傳輸模式,并處于待機狀态,主機在擷取所有卡RCA之後也進入資料傳輸模式。

4.3 資料傳輸模式

隻有SD卡系統處于資料傳輸模式下才可以進行資料讀寫操作。資料傳輸模式下可以将主機SD時鐘頻率設定為FPP,預設最高為25MHz,頻率切換可以通過CMD4指令來實作。資料傳輸模式下,SD卡狀态轉換過程見下圖。

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

CMD7用來標明和取消指定的卡,卡在待機狀态下還不能進行資料通信,因為總線上可能有多個卡都是出于待機狀态,必須選擇一個RCA位址目标卡使其進入傳輸狀态才可以進行資料通信。同時通過CMD7指令也可以讓已經被選擇的目标卡傳回到待機狀态。

資料傳輸模式下的資料通信都是主機和目标卡之間通過尋址指令點對點進行的。卡處于傳輸狀态下可以使用CMD那張表中面向塊的讀寫以及擦除指令對卡進行資料讀寫、擦除。CMD12可以中斷正在進行的資料通信,讓卡傳回到傳輸狀态。CMD0和CMD15會中止任何資料程式設計操作,傳回卡識别模式,這可能導緻卡資料被損壞。

下面我們看一下Exynos4的資源情況:

1).電路圖如下:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式
⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

2).核心闆:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

3).SoC feature:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

4).SDMMC Clock Domain

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

MMC子系統

系統架構:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

Linux MMC子系統主要分成三個部分:

●MMC核心層:完成不同協定和規範的實作,為host層和裝置驅動層提供接口函數。MMC核心層由三個部分組成:MMC,SD和SDIO,分别為三類裝置驅動提供接口函數;

●Host驅動層:針對不同主機端的SDHC、MMC控制器的驅動;

●Client驅動層:針對不同用戶端的裝置驅動程式。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等裝置驅動。

代碼結構:

MMC子系統代碼主要在drivers/mmc目錄下,共有三個目錄:

Card:與塊裝置調用相關驅動,如MMC/SD卡裝置驅動,SDIOUART;

Core:整個MMC的核心層,這部分完成不同協定和規範的實作,為host層和裝置驅動層提供接口函數;

Host:針對不同主機端的SDHC、MMC控制器的驅動,這部分需要由驅動工程師來完成;

注冊流程:

在linux系統中,系統啟動時将加載相關配置的驅動子產品,而各子產品的加載将通過各自之間相應的結構關系進行先後順序進行裝置注冊,下面是mmc子系統的注冊流程:

core —> host —> card

core層的注冊主要建立兩條虛拟總線mmc_bus和sdio_bus,為host層

host注冊主要為相關控制器的初始化及配置參數

card層主要用于與block裝置進行綁定,為資料讀寫準備

MMC控制器驅動的軟體架構

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

MMC子系統讀寫流程

讀流程

資料讀指令:CMD17和CMD18(single block和multiple block)

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

寫流程

資料讀指令:CMD24和CMD25(single block和multiple block)

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

下面是我實作的代碼,主要是根據原廠的本地化了一下,差別不大:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>

#include <linux/mmc/host.h>

#include <plat/sdhci.h>
#include <plat/regs-sdhci.h>

#include "sdhci.h"


#define MAX_BUS_CLK         (4)

/**
 * struct sdhci_exynos4 - EXYNOS4 SDHCI instance
 * @host: The SDHCI host created
 * @pdev: The platform device we where created from.
 * @ioarea: The resource created when we claimed the IO area.
 * @pdata: The platform data for this controller.
 * @cur_clk: The index of the current bus clock.
 * @clk_io: The clock for the internal bus interface.
 * @clk_bus: The clocks that are available for the SD/MMC bus clock.
 */
struct sdhci_exynos4 {
    struct sdhci_host       *host;
    struct platform_device  *pdev;
    struct resource         *ioarea;
    struct s3c_sdhci_platdata *pdata;
    unsigned int            cur_clk;
    int                     ext_cd_irq;
    int                     ext_cd_gpio;
    struct clk              *clk_io;
    struct clk              *clk_bus[MAX_BUS_CLK];
};

struct sdhci_exynos4_drv_data {
    unsigned int sdhci_quirks;
};


static inline struct sdhci_exynos4 *
to_exynos4(struct sdhci_host *host)
{
    return sdhci_priv(host);
}

/**
 * get_curclk - convert ctrl2 register to clock source number
 * @ctrl2: Control2 register value.
 */
static u32
get_curclk(u32 ctrl2)
{
    ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK;
    ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;

    return ctrl2;
}

static void
sdhci_exynos4_check_sclk(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2);

    if(get_curclk(tmp) != ourhost->cur_clk) {
        printk("%s restored ctrl2 clock setting\n", __func__);

        tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
        tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
        writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2);
    }
}

static unsigned int
sdhci_exynos4_consider_clock(struct sdhci_exynos4 *ourhost,
                             unsigned int src,
                             unsigned int wanted)
{
    unsigned long rate;
    struct clk *clksrc = ourhost->clk_bus[src];
    int div;

    if(!clksrc)
        return UINT_MAX;

    /**
     * if controller uses a non-standard clock division, find the best clock
     * speed possible with selected clock source and skip the division.
    */
    if(ourhost->host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) {
        rate = clk_round_rate(clksrc, wanted);
        return wanted - rate;
    }

    rate = clk_get_rate(clksrc);

    for(div = 1; div < 256; div *= 2) {
        if((rate / div) <= wanted)
            break;
    }

    printk("%s clk %d: rate %ld, want %d, got %ld\n", __func__, src, rate, wanted, rate / div);

    return (wanted - (rate / div));
}

/**
 * sdhci_exynos4_get_max_clk - callback to get maximum clock frequency.
 * @host: The SDHCI host instance.
 *
 * Callback to return the maximum clock rate acheivable by the controller.
*/
static unsigned int
sdhci_exynos4_get_max_clk(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    struct clk *busclk;
    unsigned int rate, max;
    int clk;

    sdhci_exynos4_check_sclk(host);

    for(max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
        busclk = ourhost->clk_bus[clk];
        if(!busclk)
            continue;

        rate = clk_get_rate(busclk);
        if(rate > max)
            max = rate;
    }

    return max;
}

/**
 * sdhci_exynos4_set_clock - callback on clock change
 * @host: The SDHCI host being changed
 * @clock: The clock rate being requested.
 *
 * When the card's clock is going to be changed, look at the new frequency
 * and find the best clock source to go with it.
*/
static void
sdhci_exynos4_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned int best = UINT_MAX;
    unsigned int delta;
    int best_src = 0;
    int src;
    u32 ctrl;

    // don't bother if the clock is going off
    if(0 == clock)
        return;

    for(src = 0; src < MAX_BUS_CLK; src++) {
        delta = sdhci_exynos4_consider_clock(ourhost, src, clock);
        if(delta < best) {
            best = delta;
            best_src = src;
        }
    }

    printk("%s selected source %d, clock %d, delta %d\n", __func__, best_src, clock, best);

    // select the new clock source
    if(ourhost->cur_clk != best_src) {
        struct clk *clk = ourhost->clk_bus[best_src];

        // turn clock off to card before changing clock source
        writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);

        ourhost->cur_clk = best_src;
        host->max_clk = clk_get_rate(clk);

        ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
        ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
        ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
        writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
    }

    // reprogram default hardware configuration
    writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, host->ioaddr + S3C64XX_SDHCI_CONTROL4);

    ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
    ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR |
             S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK |
             S3C_SDHCI_CTRL2_ENFBCLKRX |
             S3C_SDHCI_CTRL2_DFCNT_NONE |
             S3C_SDHCI_CTRL2_ENCLKOUTHOLD);

    writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);

    // reconfigure the controller for new clock rate
    ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0);
    if(clock < 25 * 1000000)
        ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2);

    writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3);
}

/**
 * sdhci_exynos4_get_min_clock - callback to get minimal supported clock value
 * @host: The SDHCI host being queried
 *
 * To init mmc host properly a minimal clock value is needed. For high system
 * bus clock's values the standard formula gives values out of allowed range.
 * The clock still can be set to lower values, if clock source other then
 * system bus is selected.
*/
static unsigned int
sdhci_exynos4_get_min_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned int delta, min = UINT_MAX;
    int src;

    for(src = 0; src < MAX_BUS_CLK; src++) {
        delta = sdhci_exynos4_consider_clock(ourhost, src, 0);
        if(UINT_MAX == delta)
            continue;

        // delta is a negative value in this case
        if(-delta < min)
            min = -delta;
    }

    return min;
}

/**
 * sdhci_exynos4_platform_8bit_width - support 8bit buswidth
 * @host: The SDHCI host being queried
 * @width: MMC_BUS_WIDTH_ macro for the bus width being requested
 *
 * We have 8-bit width support but is not a v3 controller.
 * So we add platform_8bit_width() and support 8bit width.
 */
static int
sdhci_exynos4_platform_8bit_width(struct sdhci_host *host, int width)
{
    u8 ctrl;

    ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);

    switch(width)
    {
        case MMC_BUS_WIDTH_8:
            ctrl |= SDHCI_CTRL_8BITBUS;
            ctrl &= ~SDHCI_CTRL_4BITBUS;
            break;
        
        case MMC_BUS_WIDTH_4:
            ctrl |= SDHCI_CTRL_4BITBUS;
            ctrl &= ~SDHCI_CTRL_8BITBUS;
            break;

        default:
            ctrl &= ~SDHCI_CTRL_4BITBUS;
            ctrl &= ~SDHCI_CTRL_8BITBUS;
            break;
    }

    sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);

    return 0;
}

static struct sdhci_ops sdhci_exynos4_ops = {
    .get_max_clock      = sdhci_exynos4_get_max_clk,
    .set_clock          = sdhci_exynos4_set_clock,
    .get_min_clock      = sdhci_exynos4_get_min_clock,
    .platform_8bit_width= sdhci_exynos4_platform_8bit_width,
};

// sdhci_cmu_set_clock - callback on clock change
static void
sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned long timeout;
    u16 clk = 0;

    // don't bother if the clock is going off
    if(0 == clock) {
        clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
        clk &= ~SDHCI_CLOCK_CARD_EN;
        sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
        return;    
    }

    sdhci_exynos4_set_clock(host, clock);

    clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock);

    host->clock = clock;

    clk = SDHCI_CLOCK_INT_EN;
    sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);

    // wait max 20 ms
    timeout = 20;
    while(!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) {
        if(0 == timeout) {
            printk("%s %s: Internal clock never stabilised.\n", __func__, mmc_hostname(host->mmc));
            return;
        }

        timeout--;
        mdelay(1);
    }

    clk |= SDHCI_CLOCK_CARD_EN;
    sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
}

// sdhci_cmu_get_min_clock - callback to get minimal supported clock value
static unsigned int
sdhci_cmu_get_min_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);

    /**
     * initial clock can be in the frequency range of
     * 100KHz-400KHz, so we set it as max value.
    */
    return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000);
}

// sdhci_cmu_get_max_clock - callback to get maximum clock frequency
static unsigned int
sdhci_cmu_get_max_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);

    return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX);
}

static inline struct sdhci_exynos4_drv_data *
sdhci_exynos4_get_driver_data(struct platform_device *pdev)
{
    return (struct sdhci_exynos4_drv_data *)
            platform_get_device_id(pdev)->driver_data;
}

static void 
sdhci_exynos4_notify_change(struct platform_device *dev, int state)
{
    struct sdhci_host *host = platform_get_drvdata(dev);
    unsigned long flags;

    if(host) {
        spin_lock_irqsave(&host->lock, flags);
        if(state) {
            dev_dbg(&dev->dev, "card inserted.\n");
            host->flags     &= ~SDHCI_DEVICE_DEAD;
            host->quirks    |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
        } else {
            dev_dbg(&dev->dev, "card remove.\n");
            host->flags     |= SDHCI_DEVICE_DEAD;
            host->quirks    &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
        }

        tasklet_schedule(&host->card_tasklet);
        spin_unlock_irqrestore(&host->lock, flags);
    }
}

static irqreturn_t
sdhci_exynos4_gpio_card_detect_thread(int irq, void *dev_id)
{
    struct sdhci_exynos4 *sc = dev_id;
    int status = gpio_get_value(sc->ext_cd_gpio);
    if(sc->pdata->ext_cd_gpio_invert)
        status = !status;
    sdhci_exynos4_notify_change(sc->pdev, status);

    return IRQ_HANDLED;
}

static void
sdhci_exynos4_setup_card_detect_gpio(struct sdhci_exynos4 *sc)
{
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    struct device *dev = &sc->pdev->dev;

    if(0 == gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD")) {
        sc->ext_cd_gpio = pdata->ext_cd_gpio;
        sc->ext_cd_irq  = gpio_to_irq(pdata->ext_cd_gpio);

        if(sc->ext_cd_irq &&
            request_threaded_irq(sc->ext_cd_irq, NULL,
                                sdhci_exynos4_gpio_card_detect_thread,
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                dev_name(dev), sc) == 0) {
            int status = gpio_get_value(sc->ext_cd_gpio);
            
            if(pdata->ext_cd_gpio_invert)
                status = !status;
            
            sdhci_exynos4_notify_change(sc->pdev, status);
        } else {
            printk("%s cannot request irq for card detect\n", __func__);
            sc->ext_cd_irq = 0;
        }
    } else {
        printk("%s cannot request gpio for card detect\n", __func__);
    }
}

static int __devinit
sdhci_exynos4_probe(struct platform_device *pdev)
{
    struct s3c_sdhci_platdata *pdata;
    struct sdhci_exynos4_drv_data *drv_data;
    struct device *dev = &pdev->dev;
    struct sdhci_host *host;
    struct sdhci_exynos4 *sc;
    struct resource *res;
    int ret, irq, ptr, clks;

    if(!pdev->dev.platform_data) {
        printk("%s no device data specified\n", __func__);
        return -ENOENT;
    }

    irq = platform_get_irq(pdev, 0);
    if(irq < 0) {
        printk("%s no irq specified\n", __func__);
        return irq;
    }

    host = sdhci_alloc_host(dev, sizeof(struct sdhci_exynos4));
    if(IS_ERR(host)) {
        printk("%s sdhci_alloc_host failed !\n", __func__);
        return PTR_ERR(host);
    }

    pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
    if(!pdata) {
        ret = -ENOMEM;
        goto err_io_clk;
    }
    memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));

    drv_data = sdhci_exynos4_get_driver_data(pdev);
    sc = sdhci_priv(host);

    sc->host    = host;
    sc->pdev    = pdev;
    sc->pdata   = pdata;
    sc->ext_cd_gpio = pdata->ext_cd_gpio;

    platform_set_drvdata(pdev, host);

    sc->clk_io = clk_get(dev, "hsmmc");
    if(IS_ERR(sc->clk_io)) {
        printk("%s get clock failed !\n", __func__);
        ret = PTR_ERR(sc->clk_io);
        goto err_io_clk;
    }

    // enable the local io clock and keep it running for the moment
    clk_enable(sc->clk_io);

    for(clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
        struct clk *clk;
        char name[14] = {0};

        snprintf(name, 14, "mmc_busclk.%d", ptr);
        clk = clk_get(dev, name);
        if(IS_ERR(clk))
            continue;
        
        clks++;
        sc->clk_bus[ptr] = clk;

        // save current clock index to know which clock bus
        // is used later in overriding functions.
        sc->cur_clk = ptr;

        clk_enable(clk);

        printk("%s clock source %d: %s (%ld Hz)\n", __func__, ptr, name, clk_get_rate(clk));
    }

    if(0 == clks) {
        printk("%s find any bus clocks failed !\n", __func__);
        ret = -ENOENT;
        goto err_no_busclks;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    host->ioaddr = devm_request_and_ioremap(&pdev->dev, res);
    if(!host->ioaddr) {
        printk("%s map register failed !\n", __func__);
        ret = -ENXIO;
        goto err_req_regs;
    }

    // Ensure we have minimal gpio selected CMD/CLK/Detect
    if(pdata->cfg_gpio)
        pdata->cfg_gpio(pdev, pdata->max_width);

    host->hw_name   = "samsung-hsmmc";
    host->ops       = &sdhci_exynos4_ops;
    host->quirks    = 0;
    host->irq       = irq;

    // setup quirks for the controller
    host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
    host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
    if(drv_data)
        host->quirks |= drv_data->sdhci_quirks;

#ifndef CONFIG_MMC_SDHCI_S3C_DMA
    // we currently see overruns on errors, so disable the SDMA support as well
    host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
#endif

    /**
     * IT seems we do not get an DATA transfer complete on non-busy
     * transfers, not sure if this is a problem with this specific
     * SDHCI block, or a missing configuration that needs to be set.
    */
    host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;

    // this host supports the Auto CMD12
    host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;

    if(S3C_SDHCI_CD_NONE == pdata->cd_type
       || S3C_SDHCI_CD_PERMANENT == pdata->cd_type)
        host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

    if(S3C_SDHCI_CD_GPIO == pdata->cd_type)
        host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

    if(S3C_SDHCI_CD_PERMANENT == pdata->cd_type)
        host->mmc->caps = MMC_CAP_NONREMOVABLE;

    switch(pdata->max_width)
    {
        case 8:
            host->mmc->caps |= MMC_CAP_8_BIT_DATA;
        case 4:
            host->mmc->caps |= MMC_CAP_4_BIT_DATA;
            break;
    }

    if(pdata->pm_caps)
        host->mmc->pm_caps |= pdata->pm_caps;

    host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE);

    // HSMMC on Samsung SoCs uses SDCLK as timeout clock
    host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;

    // if controller does not have internal clock divider,
    // we can use overriding functions instead of default.
    if(host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) {
        sdhci_exynos4_ops.set_clock     = sdhci_cmu_set_clock;
        sdhci_exynos4_ops.get_min_clock = sdhci_cmu_get_min_clock;
        sdhci_exynos4_ops.get_max_clock = sdhci_cmu_get_max_clock;
    }

    // it supports additional host capabilities if needed
    if(pdata->host_caps)
        host->mmc->caps |= pdata->host_caps;

    if(pdata->host_caps2)
        host->mmc->caps2 |= pdata->host_caps2;

    pm_runtime_enable(&pdev->dev);
    pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
    pm_runtime_use_autosuspend(&pdev->dev);
    pm_suspend_ignore_children(&pdev->dev, 1);

    ret = sdhci_add_host(host);
    if(ret) {
        printk("%s sdhci_add_host failed !\n", __func__);
        pm_runtime_forbid(&pdev->dev);
        pm_runtime_get_noresume(&pdev->dev);
        goto err_req_regs;
    }

    /**
     * the following two methods of card detection might call
     * sdhci_exynos4_notify_change() immediately, so they can be
     * called only aftr sdhci_add_host(). setup errors are ignored.
    */
    if(S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_init)
        pdata->ext_cd_init(&sdhci_exynos4_notify_change);

    if(S3C_SDHCI_CD_GPIO == pdata->cd_type
       && gpio_is_valid(pdata->ext_cd_gpio))
        sdhci_exynos4_setup_card_detect_gpio(sc);

    return 0;

err_req_regs:
    for(ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
        if(sc->clk_bus[ptr]) {
            clk_disable(sc->clk_bus[ptr]);
            clk_put(sc->clk_bus[ptr]);
        }
    }

err_no_busclks:
    clk_disable(sc->clk_io);
    clk_put(sc->clk_io);

err_io_clk:
    sdhci_free_host(host);

    return ret;
}

static int __devexit
sdhci_exynos4_remove(struct platform_device *pdev)
{
    struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
    struct sdhci_host *host = platform_get_drvdata(pdev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    int ptr = 0;

    if(S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_cleanup)
        pdata->ext_cd_cleanup(&sdhci_exynos4_notify_change);

    if(sc->ext_cd_irq)
        free_irq(sc->ext_cd_irq, sc);

    if(gpio_is_valid(sc->ext_cd_gpio))
        gpio_free(sc->ext_cd_gpio);

    sdhci_remove_host(host, 1);

    pm_runtime_disable(&pdev->dev);

    for(ptr = 0; ptr < 3; ptr++) {
        if(sc->clk_bus[ptr]) {
            clk_disable(sc->clk_bus[ptr]);
            clk_put(sc->clk_bus[ptr]);
        }
    }

    clk_disable(sc->clk_io);
    clk_put(sc->clk_io);

    sdhci_free_host(host);
    platform_set_drvdata(pdev, NULL);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int
sdhci_exynos4_suspend(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    int ret = -1;

    ret = sdhci_suspend_host(host);
    if(ret < 0)
        return ret;

    if(pdata && S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_cleanup)
        pdata->ext_cd_cleanup(&sdhci_exynos4_notify_change);

    if(sc->ext_cd_irq)
        free_irq(sc->ext_cd_irq, sc);

    return 0;
}

static int
sdhci_exynos4_resume(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    int ret = -1;

    ret = sdhci_resume_host(host);
    if(ret < 0)
        return ret;

    if(sc->ext_cd_irq && pdata && S3C_SDHCI_CD_GPIO == pdata->cd_type) {
        if(request_threaded_irq(sc->ext_cd_irq, NULL,
                                sdhci_exynos4_gpio_card_detect_thread,
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                dev_name(dev), sc) == 0) {
            int status = gpio_get_value(sc->ext_cd_gpio);
            if(pdata->ext_cd_gpio_invert)
                status = !status;
            sdhci_exynos4_notify_change(sc->pdev, status);
        } else {
            dev_warn(dev, "cannot request irq for card detect\n");
            sc->ext_cd_irq = 0;
        }
    }

    return 0;
}
#endif

#ifdef CONFIG_PM_RUNTIME
static int
sdhci_exynos4_runtime_suspend(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);

    return sdhci_runtime_suspend_host(host);
}

static int
sdhci_exynos4_runtime_resume(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);

    return sdhci_runtime_resume_host(host);
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_exynos4_pmops = {
    SET_SYSTEM_SLEEP_PM_OPS(sdhci_exynos4_suspend, sdhci_exynos4_resume)
    SET_RUNTIME_PM_OPS(sdhci_exynos4_runtime_suspend, sdhci_exynos4_runtime_resume, NULL)
};

#define SDHCI_EXYNOS4_PMOPS (&sdhci_exynos4_pmops)
#else
#define SDHCI_EXYNOS4_PMOPS NULL
#endif


#if defined (CONFIG_CPU_EXYNOS4210) || defined (CONFIG_SOC_EXYNOS4212)
static struct sdhci_exynos4_drv_data exynos4_sdhci_drv_data = {
    .sdhci_quirks = SDHCI_QUIRK_NONSTANDARD_CLOCK,
};

#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)&exynos4_sdhci_drv_data)
#else
#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)NULL)
#endif

static struct platform_device_id sdhci_exynos4_driver_ids[] = {
    {
        .name       = "s3c-sdhci",
        .driver_data= (kernel_ulong_t)NULL,
    }, {
        .name       = "exynos4-sdhci",
        .driver_data= EXYNOS4_SDHCI_DRV_DATA,
    },
    {}
};
MODULE_DEVICE_TABLE(paltform, sdhci_exynos4_driver_ids);

static struct platform_driver sdhci_exynos4_driver = {
    .probe      = sdhci_exynos4_probe,
    .remove     = __devexit_p(sdhci_exynos4_remove),
    .id_table   = sdhci_exynos4_driver_ids,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "s3c_sdhci",
        .pm     = SDHCI_EXYNOS4_PMOPS,
    },
};

module_platform_driver(sdhci_exynos4_driver);

MODULE_LICENSE("GPL");

           

有了驅動程式,我們驗證一下效果(基于官方檔案的效果,我本地化的代碼和這個效果類似):

首先在已經有驅動程式的前提下,當插入SD卡時會出現如下現象:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

當拔下SD卡時,會出現如下現象:

⑳tiny4412 Linux驅動開發之MMC子系統驅動程式

上面的驗證是有相關驅動的,如果沒有相關驅動,那麼就不會出現上面的現象,但是這種情況下仍然是可以正常啟動和操作的,原因是因為uboot裡有初始化這些器件,然後,在系統啟動後,會把kernel全部加在到RAM中去,因為是Nandflash或者功能類似的SD卡,這些不能按位操作的,隻能加載到能按位操作的RAM中去,而RAM驅動,是擁有的,是以,我們還是可以操作的.

繼續閱讀