一、SD/MMC/SDIO概念區分
SD(SecureDigital)與 MMC(MultimediaCard)
SD 是一種 flash memory card 的標準,也就是一般常見的 SD 記憶卡,而 MMC 則是較早的一種記憶卡標準,目前已經被 SD 標準所取代。在維基百科上有相當詳細的 SD/MMC 規格說明:[http://zh.wikipedia.org/wiki/Secure_Digital]。
SDIO(SecureDigital I/O)
SDIO 是目前我們比較關心的技術,SDIO 故名思義,就是 SD 的 I/O 接口(interface)的意思,不過這樣解釋可能還有點抽像。更具體的說明,SD 本來是記憶卡的標準,但是現在也可以把 SD 拿來插上一些外圍接口使用,這樣的技術便是 SDIO。
所以 SDIO 本身是一種相當單純的技術,透過 SD 的 I/O 接腳來連接外部外圍,並且透過 SD 上的 I/O 數據接位與這些外圍傳輸數據,而且 SD 協會會員也推出很完整的 SDIO stack 驅動程序,使得 SDIO 外圍(我們稱為 SDIO 卡)的開發與應用變得相當熱門。
現在已經有非常多的手機或是手持裝置都支持 SDIO 的功能(SD 標準原本就是針對 mobile device 而制定),而且許多 SDIO 外圍也都被開發出來,讓手機外接外圍更加容易,並且開發上更有彈性(不需要內建外圍)。目前常見的 SDIO 外圍(SDIO 卡)有:
· Wi-Fi card(無線網絡卡)
· CMOS sensor card(照相模塊)
· GPS card
· GSM/GPRS modem card
· Bluetooth card
· Radio/TV card(很好玩)
SDIO 的應用將是未來嵌入式系統最重要的接口技術之一,並且也會取代目前 GPIO 式的 SPI 接口。
SD/SDIO 的傳輸模式
SD 傳輸模式有以下 3 種:
· SPI mode(required)
· 1-bit mode
· 4-bit mode
SDIO 同樣也支持以上 3 種傳輸模式。依據 SD 標準,所有的 SD(記憶卡)與 SDIO(外圍)都必須支持 SPI mode,因此 SPI mode 是「required」。此外,早期的 MMC 卡(使用 SPI 傳輸)也能接到 SD 插糟(SD slot),並且使用 SPI mode 或 1-bit mode 來讀取。
Secure digital I/Ocard,pin out
SD 的 MMCMode
SD 也能讀取 MMC 內存,雖然 MMC 標準上提到,MMC 內存不見得要支持 SPI mode(但是一定要支持 1-bit mode),但是市面上能看到的 MMC 卡其實都有支持 SPI mode。因此,我們可以把 SD 設定成 SPI mode 的傳輸方式來讀取 MMC 記憶卡。
SD 的 MMC Mode 就是用來讀取 MMC 卡的一種傳輸模式。不過,SD 的 MMC Mode 雖然也是使用 SPI mode,但其物理特性仍是有差異的:
· MMC 的 SPI mode 最大傳輸速率為 20 Mbit/s;
· SD 的 SPI mode 最大傳輸速率為 25 Mbit/s。
為避免混淆,有時也用 SPI/MMC mode 與 SPI/SD mode 的寫法來做清楚區別
參考網站:https://www.sdcard.org/developers/overview/capacity/
http://www.interfacebus.com/Secure_Digital_IO_Card_Pinout.html
二、MMC子系統介紹
MMC代碼分布
MMC子系統代碼主要在drivers/mmc目錄下,共有三個目錄:
Card:存放快閃記憶體卡(塊設備)的相關驅動,如MMC/SD卡設備驅動,SDIOUART;
Host:針對不同主機端的SDHC、MMC控制器的驅動,這部分需要由驅動工程師來完成;
Core:整個MMC的核心層,這部分完成不同協議和規範的實現,為host層和設備驅動層提供接口函數。
MMC子系統框架
Linux MMC子系統主要分成三個部分:
MMC核心層:完成不同協議和規範的實現,為host層和設備驅動層提供接口函數。MMC核心層由三個部分組成:MMC,SD和SDIO,分別為三類設備驅動提供接口函數;
Host 驅動層:針對不同主機端的SDHC、MMC控制器的驅動;
Client 驅動層:針對不同客戶端的設備驅動程序。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等設備驅動。
三、SD 總線協議
SD總線通信是基於指令和數據比特流,起始位開始和停止位結束。SD總線通信有三個元素:
Command:由host發送到卡設備,使用CMD線發送;
Response:從card端發送到host端,作為對前一個CMD的相應,通過CMD線發送;
Data:即能從host傳輸到card,也能從card傳輸到host,通過data線傳輸。
Commands
以下是四種用於控制卡設備的指令類型,每個command都是固定的48位長度:
1、broadcast commands(bc), no response:廣播類型的指令,不需要有響應;
2、broadcast commands with response(bcr):廣播類型的指令且需要響應;
3、addressed(point-to-point) commands(ac):由HOST發送到指定的卡設備,沒有數據的傳輸;
4、address(point-to-point) data transfercommands(adtc):由HOST發送到指定的卡設備且伴隨有數據傳輸。
指令格式:
Card register
幾個主要的寄存器:OCR,CID,CSD,RCA和SCR。
Operation condition register(OCR):32位的OCR包含卡設備支持的工作電壓表;
Card identification number register (CID):包含用於在卡識別階段的卡信息,包括製造商ID,產品名等;
Card specific data register(CSD):CSD寄存器提供了如何訪問卡設備的信息,包括定義了數據格式,錯誤校驗類型,最大訪問次數,數據傳輸率等;
Relative card address register(RCA):存放在卡識別階段分配的相對卡地址,預設相對卡地址為0000h;
SD card configuration register(SCR):SCR是一個配置寄存器,用於配置SD memory card的特殊功能。
Response
所有的response都通過CMD線發送到host端,R4和R5響應類型是SDIO中特有的:
1、R1(normal response command):用來響應常用指令;
2、R2(CID,CSD register):用來響應CMD2和CMD10或CMD9,並把CID或CSD寄存器作為響應數據;
3、R3(OCR register):用來響應ACMD41指令,並把OCR寄存器作為響應數據;
4、R6(published RCA response):分配相對卡地址的響應;
5、R7(card interface condition):響應CMD8,返回卡支持的電壓信息;
6、R4(CMD5):響應CMD5,並把OCR寄存器作為響應數據;
7、R5(CMD52):CMD52是一個讀寫寄存器的指令,R5用於CMD52的響應;
Response 格式:
***詳情請參考spec***
四、SD初始化流程
當host上電後,使所有的卡設備處於卡識別模式,完成設置有效操作電壓範圍,卡識別和請求卡相對地址等操作。
1、發送指令CMD0使卡設備處於idle狀態;
2、發送指令CMD8,如果卡設備有response,說明此卡為SD2.0以上;
3、發送指令CMD55+ACMD41,該指令是用來探測卡設備的工作電壓是否符合host端的要求;
在發送ACMD41這類指令之前需要先發送CMD55指令,在SDIO中ACMD41指令被CMD5替代。
4、發送指令CMD11轉換工作電壓到1.8V;
5、發送指令CMD2獲取CIA;
6、發送指令CMD3獲取RCA(relative card address)
SD初始化分析
系統上電時,SDI控制器會去掃描總線上的所有設備,然後對掛在總線上卡設備進行初始化。進行掃描和初始化工作都是由mmc_scan函數來完成,以下是Linux驅動中初始化流程圖(感謝同事Linkin的圖)。
SDIO、SD和MMC這三者的初始化流程稍有不同,是向下兼容的。
五、SD卡調試關鍵點:
1. 上電時要延時足夠長的時間給 SD 卡一個準備過程,在我的程序裡是 5 秒,根據不同的卡設置不同的延時時間。 SD 卡初始化第一步在發送 CMD 命令之前,在片選有效的情況下首先要發送至少 74 個時鐘,否則將有可能出現 SD 卡不能初始化的問題。
2. SD 卡發送復位命令 CMD0 後,要發送版本查詢命令 CMD8 ,返回狀態一般分兩種,若返回 0x01 表示此 SD 卡接受 CMD8, 也就是說此 SD 卡支持版本 2 ;若返回 0x05 則表示此 SD 卡支持版本 1 。因為不同版本的 SD 卡操作要求有不一樣的地方,所以務必查詢 SD 卡的版本號,否則也會出現 SD 卡無法正常工作的問題。
3. 理論上要求發送 CMD58 獲得 SD 卡電壓參數,但實際過程中由於事先都知道了 SD 卡的工作電壓,因此可省略這一步簡化程序。協議書上也建議儘量不要用這個命令。
4. SD 卡讀寫超時時間要按照協議說明書書上的給定值 ( 讀超時: 100ms ;寫超時: 250ms) ,這個值要在程序中準確計算出來,否則將會出現不能正常讀寫數據的問題。我自己定義了一個計算公式:超時時間 =( 8/clk )*arg 。
5. 2GB 以內的 SD 卡 ( 標準卡 ) 和 2GB 以上的 SD 卡 ( 大容量卡 ) 在地址訪問形式上不同,這一點尤其要注意,否則將會出現無法讀寫數據的問題。如標準卡在讀寫操作時,對讀或寫命令令牌當中的地址域符初值 0x10 ,表示對第 16 個字節以後的地址單元進行操作 ( 前提是此 SD 卡支持偏移讀寫操作 ) ,而對大容量卡讀或寫命令令牌當中的地址域符初值 0x10 時,則表示對第 16 塊進行讀寫操作,而且大容量卡只支持塊讀寫操作,塊大小固定為 512 字節,對其進行字節操作將會出錯。
6. 對某一塊要進行寫操作時最好先執行擦出命令,這樣寫入的速度就能大大提高。進行擦除操作時不管是標準卡還是大容量卡都按塊操作執行,也就是一次擦除至少 512 字節。
7. 對標準卡進行字節操作時,起始和終止必須在一個物理扇區內,否則將不能進行讀寫操作。實際操作過程中建議用塊操作以提高效率。不管是標準卡還是大容量卡一個讀寫命令只能對一個塊進行操作,不允許跨物理層地址操作。
8. 在寫數據塊前要先寫入若干個 dummy data 字節,寫完一個塊數據時,主機要監測 MISO 數據線,如果從機處於忙狀態這根數據線會保持低電平,這樣主機就可以根據這根數據線的狀態以決定是否發送下一個命令,在從機沒有釋放 MISO 數據線之前,主機絕對不能執行其他命令,否則將會導致寫入的數據出錯,而且從機也不會響應主機的命令。
9. 在 SPI 模式下, CRC 校驗是被忽略的,但依然要求主從機發送 CRC 碼,只是數值可以是任意值,一般主機的 CRC 碼通常設為 0x00 或 0xFF 。
讀多塊操作和寫多塊操作的傳輸停止形式不一樣,讀多塊操作時用用命令 CMD12 終止傳輸,而寫多塊操作時用 Stop Tran Token( 停止傳輸令牌,值為 0xFD) 終止傳輸。
---
1、初始化步驟:
(1) 延時至少 74clock,等待SD卡內部操作完成,在MMC協議中有明確說明。
(2) CS低電平選中SD卡。
(3) 發送 CMD0 ,需要返回 0x01 ,進入 Idle 狀態
(4) 為了區別SD卡是2.0還是1.0,或是MMC卡,這裡根據協議向上兼容的原理,首先發送只有SD2.0才有的命令CMD8,如果CMD8返回無錯誤,則初步判斷為2.0卡,進一步發送命令循環發送 CMD55+ACMD41 ,直到返回 0x00 ,確定SD2.0卡初始化成功,進入Ready 狀態,再發送CMD58命令來判斷是HCSD還是SCSD,到此SD2.0卡初始化成功 。如果CMD8返回錯誤則進一步判斷為1.0卡還是MMC卡,循環發送CMD55+ACMD41 ,返回無錯誤,則為SD1.0卡,到此SD1.0卡初始成功,如果在一定的循環次數下,返回為錯誤,則進一步發送CMD1進行初始化,如果返回無錯誤,則確定為MMC卡,如果在一定的次數下,返回為錯誤,則不能識別該卡,初始結束。
(5)CS拉高。
2、讀步驟:
(1) 發送 CMD17 (單塊)或 CMD18 (多塊)讀命令,返回 0x00
(2) 接收數據開始令牌 0xfe (或 0xfc ) + 正式數據 512Bytes + CRC 校驗 2Bytes, 默認正式傳輸的數據長度是 512Bytes ,可用 CMD16 設置塊長度。
3、 寫步驟:
(1) 發送 CMD24 (單塊)或 CMD25 (多塊)寫命令,返回 0x00
(2) 發送數據開始令牌 0xfe (或 0xfc ) + 正式數據 512Bytes + CRC 校驗 2Bytes
4、 擦除步驟:
(1) 發送 CMD32 ,跟一個參數來指定首個要擦除的起始地址( SD 手冊上說是塊號)
(2) 發送 CMD33, ,指定最後的地址
(3) 發送 CMD38 ,擦除指定區間的內容
此 3 步順序不能顛倒。
六、SD卡的命令格式及解析
1.SD卡命令組成
SD卡的指令由6位元組(Byte)組成,如下:
Byte1:0 1 x x x x x x(命令號,由指令標誌定義,如CMD39為100111即16進位0x27,那麼完整的CMD39第一字節為01100111,即0x27+0x40)
Byte2-5:Command Arguments,命令參數,有些命令沒有參數
Byte6:前7位為CRC(Cyclic Redundacy Check,循環冗餘校驗)校驗位,最後一位為停止位0
2.SD卡的命令
SD卡命令共分為12類,分別為class0到class11,不同的SDd卡,主控根據其功能,支持不同的命令集,如下:
Class0 :(卡的識別、初始化等基本命令集)
CMD0:復位SD 卡.
CMD1:讀OCR寄存器.
CMD9:讀CSD寄存器.
CMD10:讀CID寄存器.
CMD12:停止讀多塊時的數據傳輸
CMD13:讀 Card_Status 寄存器
Class2 (讀卡命令集):
CMD16:設置塊的長度
CMD17:讀單塊.
CMD18:讀多塊,直至主機發送CMD12為止 .
Class4(寫卡命令集) :
CMD24:寫單塊.
CMD25:寫多塊.
CMD27:寫CSD寄存器 .
Class5 (擦除卡命令集):
CMD32:設置擦除塊的起始地址.
CMD33:設置擦除塊的終止地址.
CMD38: 擦除所選擇的塊.
Class6(防寫命令集):
CMD28:設置防寫塊的地址.
CMD29:擦除防寫塊的地址.
CMD30: Ask the card for the status of the write protection bits
class7:卡的鎖定,解鎖功能命令集
class8:申請特定命令集 。
class10 -11 :保留
3.有關sd卡驅動和fat fs的實現用了3個文件來實現。
sdboot.c為sd的驅動(可理解為pdd)層,主要實現一些對sd控制器的配置以及一些基本sd命令的實現和對sd 卡的操作。
sdmmc.c實現了從sd卡讀取nk並跳到內存去運行的代碼(基本可以理解為sd驅動的mdd層)。
sdfat.c文件就是實現fat fs的。mdd層通過fatfs來對pdd層操作以實現讀取文件。
在整個過程中遇到了很多問題,現在列舉如下:
1)sd卡初始化問題
配置gpio有關sd的功能:SDCMD, SDDAT[3:0]。
使能CLKCON中的SDI位。
時鐘以及計算公式:SDIPRE = PCLK/(CLK)-1;INICLK=300000;SDCLK=24000000; MMCCLK= 15000000
cmd0-cmd55-cmd41-cmd2-cmd3-cmd7-cmd6-cmd17
2)對sd卡操作問題
SD卡包括:一個標識寄存器CID,一個相應地址寄存器RCA,一個其他參數寄存器CSD。
對sd卡的操作是驅動通過sd controller來發相應的命令以達到讀寫等操作的:發送命令通過SDICmdCon[7:0]的除了開始2bit:CmdIndex放置要發送的命令號;SDICmdCon[8]開始發送命令來完成的。
檢測卡的插入,直接用中斷引腳的電平來判斷。
判斷插入的卡是否是sd卡,用命令cmd55和cmd41,因為mmc卡對cmd55不做回應。
命令9 就是獲取sd卡中csd寄存器的值的,該值包括很多sd卡的信息,其中就有sd卡的容量。這個值在sd卡接收到cmd9之後會以response的形式存放在sd控制器的SDI Response Register[0,1,2,3]中。在執行cmd9,cmd10等這樣的命令的時候,卡的狀態應該是不選中的,或直接在執行它們之前發送 cmd7(0)不選中卡,不然的話會timeout。
用cmd17 來讀取單個block的數據,該命令要帶地址參數(該參數通過cmd3命令來獲取),然後根據SDIDSTA和SDIFSTA狀態值來從sd 控制器的SDIDAT寄存器中讀出要讀的數據。該命令與cmd9相反,在執行它之前要選中卡。讀完一個block之後要做一些善後工作,為下次讀取做好準備,不然的話checkcmdend就要一直循環了。因為用的是每次都讀一個block,並地址要以block對齊,這樣就要考慮要讀取的地址是否是 block對齊的,長度是否夠一個block。
SDIDCON這個數據控制寄存器也很重要,一些對數據的操作形式就是在這裡設置的。
3)fat文件系統問題
根據MBR找到分區表,根據分區表找到該分區MBR[446B+4個分區表(每個16B)+2B結束符)
分區表中的第9-12位元組為該分區的啟始地址(單位沒sector),第13-16位元組為分區的長度(單位也是sector)
六、實例
一、概述
最近在研究WIFI驅動,驅動模塊為broamd4330,基於SDIO接口,所以趁機研究了一下內核中對於SDIO設備的註冊。
(我使用的linux內核版本為3.2.0 硬體為samsung 4412)
在介紹內核之前,有必要先了解一下MMC SD SDIO三種卡,從發展歷程來看,是先有MMC卡,後來有SD卡,這兩種都是純粹的存儲卡,而SDIO是什麼呢,從字面意思理解,應該是SD+IO,也就是既有存儲功能,又有IO控制功能,不過也有純IO功能的SDIO設備(本人用到的WIFI模塊就是這種)。並且,這三種卡可以使用同一個插槽,系統還能正確的識別!!,可能是由於歷史原因,在開始有Linux的時候,還只存在mmc卡(不存在SD和SDIO卡),所以在linux系統裡面關於這三種卡的名稱統統用「mmc「來命名。
下面來看一下CPU與WIFI模塊的物理連接圖
從圖上可以看出,我們的WIFI模塊接的是CPU上的mmc3,數據線,時鐘線以及命令線都一一對應。
當然在CPU一端,對於mmc3模塊,還有一個很重要的引腳--「xmmc3CDn」腳,CPU就是根據該引腳的電平高低來判斷mmc3模塊上是否有卡接入,如果電平為低,表示有卡,如果為高,表示無卡,筆者這裡將該引腳固定拉低。
同時在WIFI模塊一端,也有一個很重要的引腳--「WL_SDIO_SPI_HSCI_SEL」引腳 ,它是用來選擇模塊是工作在SD模式(低電平),還是SPI模式(高電平),筆者這裡也將該引腳固定拉低。
好了,簡單的介紹了一些概念以及硬體後,還是要回歸到程序上,從大的方面來講,MMC/SD/SDIO的驅動程序主要分為兩大塊,主設備驅動和從設備驅動。對於上面的例子來說,CPU上的MMC3模塊就是主設備,而WIFI模塊就是從設備。該系列的博文就是分析MMC主設備在內核中的註冊,以及對於同一個mmc插槽,系統是如何區分出MMC SD 以及SDIO設備的。
二、host註冊過程
上面說到了MMC/SD/SDIO(以下簡稱MMC)的驅動從大的方面來說分為主設備驅動和從設備驅動,那本文就來詳細的講述主設備驅動註冊的過程。
MMC主設備(也就是host)指的是集成於CPU內部的MMC controller,筆者用的是4412晶片,從datasheet可以看出,裡面集成了四個MMC controller,分別是mmc0,mmc1,mmc2,mmc3。 並且從上一篇文章我們知道,WIFI模塊是接在mmc3 這個host上面。
在linux系統中,將每個host設備封裝成platform_device來逐一進行註冊。
對於筆者所使用的內核(3.2.0版本)來說,每一個host設備所對應的platform_device文件位於目錄($KERNEL_SOURCE)/arch/arm/plat-samsung下,分別為dev-hsmmc.c,dev-hsmmc1.c,dev-hsmmc2.c,dev-hsmmc3.c,為了與實際WIFI模塊對應,我們重點進入dev-hsmmc3.c文件看一看:
從上圖可以看出,該文件裡面定義了一個名為s3c_device_hsmmc3的platform_device,但是定義好了的platform_device還需要有一個註冊的過程,該過程就發生在文件($KERNEL_SOURCE)/arch/arm/mach-exynos/mach-$(BOARD).c中,其中有如下的一個函數調用:
它的行為就是將數組skd4x12_devices裡面的每一個platform_device項一一註冊進系統,並且這個數組裡面就包含了上面所定義的s3c_device_hsmmc3:
所以總結來說,系統化在初始化的時候,就已經將s3c_device_hsmmc3(也就是那個host mmc3)註冊進了platform總線(其他的mmc0,mmc1,mmc2都是一個道理)。
當然,對於熟悉platform機制的朋友來說,此時僅僅只是註冊了platform_device ,而對應的platform_driver還沒有註冊。
下面就來說說這個platform_driver的註冊,它是在$(KERNEL_SOURCE)/drivers/mmc/host目錄下的sdhci-s3c.c文件中進行的,該文件中有如下的一個註冊函數調用:
其中的參數sdhci_s3c_driver就是上面所說的platform_driver,它也是定義在sdhci-s3c.c文件中,來看一下:
在對sdhci_s3c_driver進行註冊的過程中,系統會根據sdhci_s3c_driver->driver.name成員變量(此處是「s3c-sdhci」)在platform_bus 總線上尋找同名字的platform_dvice(這個過程稱之為「探測」),通過上面對s3c_device_hsmmc3的註冊分析,發現s3c_device_mmc3.name也剛好是「s3c-sdhci」,所以他倆剛好可以配對,探測成功,同時當大家查閱s3c_device_hsmmc,s3c_device_hsmmc1以及s3c_device_hsmmc2的時候發現他們的name成員變量都是「s3c-sdhci」,,所以會有四次成功的探測,每一次探測成功,就會調用sdhci_s3c_driver.probe函數---sdhci_s3c_probe,這個函數至關重要,在整個驅動註冊過程中起著核心作用。
上面文章說到了探測函數sdhci_s3c_probe,現在就來仔細分析這個函數的作用:
在分析代碼之前,先簡要的概括一下這個函數的功能:
1、既然是講host的註冊,那麼首先必須構造出一個host,這個host就是通過sdhci_alloc_host函數來構造出來的,它是一個struct sdhci_host類型的結構體。同時,也通過mmc_alloc_host函數構造了一個struct mmc_host的結構體變量mmc。
2、初始化host的時鐘,設置host的gpio等等其他一些「亂七八糟」的參數初始化(需要的時候再詳細分析)。
3、通過sdhci_add_host函數來註冊host。
下面重點來看sdhci_add_host函數
該函數主要是對mmc的註冊,同樣mmc也有很多的參數,先來看看他的操作函數集mmc->ops = &sdhci_ops
其中,request函數指針指向的函數用來處理host向從設備發送命令的請求,
set_ios用來設置電源、時鐘等等之類(需要重點關注),
get_ro用來判斷是否防寫
再來看該函數裡面的中斷註冊部分
我們先看一下mmc_add_host這個函數,它的功能就是通過device_add函數將設備註冊進linux設備模型,最終的結果就是在sys/bus/platform/devices目錄下能見到s3c-sdhci.1,s3c-sdhci.2,s3c-sdhci.3設備節點。
中斷註冊函_irq的第一個參數中斷號就取自於s3c_device_hsmmc3.resource裡面的irq參數,sdhci_irq就是中斷服務程序,該中斷函數一般在插卡、拔卡或者從設備反饋給host信息時會被調用數request
中斷服務程序
程序首先讀取寄存器NORINTSTSn的值,該寄存器中有兩個bit分別來表示卡的插入與拔出過程(注意,必須是動態變化過程,才會讓相應的兩個bit置1),那麼接下來的if語句就是從該寄存器的那兩個bit來判斷是否有卡的插入或拔出,並同時清除這兩個bit,準備下一次的檢測,緊接著就調用中斷的下半部分函數 sdhci_tasklet_card,其實這個函數也沒做什麼事情,就是判讀如果此時有卡的話就通過mmc_detect_chang函數調用mmc_rescan函數。從這個函數的名字都可以猜出個八九不離十,它的功能就是掃描所插入的卡。
掃描卡的程序
這個函數我們重點關註上述兩個地方,其實真正的掃描動作發生在函數mmc_rescan_try_freq函數裡面,該函數的第二個參數表示以什麼樣的頻率去進行掃描,那麼可選的頻率值在那個數組freqs裡面,一般當用某個頻率值掃描成功後,就直接退出了,否則,會以下一個更低的頻率值來掃描,筆者所使用的WIFI模塊就是以400KHz的頻率掃描成功的。
掃描過程
該函數首先發送復位命令(不過該命令只有SDIO類型的卡才能夠識別),然後發送CMD0,讓設備進入IDLE模式,緊接著發送CMD8,獲取該卡所支持的電壓值,最後就是重點了(從1998-2003行),從所調用的各個函數名字可以看出,它是在試探該卡是否為SDIO? SD? MMC?
那麼接下來的文章就是要分析上面的三個函數,看它是如何識別SDIO、SD、MMC的。
三、SDIO的識別和操作
從上面文章的最後,我們知道host在掃描卡的過程中,其識別的順序為SDIO SD MMC,並且從它的注釋可以看出,這個順序是很重要的。那這篇文章,我們就看看SDIO的識別過程,它對應的函數就是mmc_attach_sdio(host) (函數位於文件drivers/mmc/core/sdio.c)
這個函數大概來說做了如下的工作
1、向卡發送CMD5命令,該命令有兩個作用:
第一,通過判斷卡是否有反饋信息來判斷是否為SDIO設備(只有SDIO設備才對CMD5命令有反饋,其他卡是沒有回饋的);
第二,如果是SDIO設備,就會給host反饋電壓信息,就是說告訴host,本卡所能支持的電壓是多少多少。
2、host根據SDIO卡反饋回來的電壓要求,給其提供合適的電壓。
3、初始化該SDIO卡
4、註冊SDIO的各個功能模塊
5、註冊SDIO卡
對於以上功能的具體解釋,下面將結合程序娓娓道來
1、CMD5命令的發送
第789行的函數就是發送的CMD5命令,如果卡對該命令有回饋的話,err就是0,否則,err為非0,直接退出了;並且需要重點說明的一點就是,該函數的最後一個參數ocr,它是存儲反饋命令的,SDIO設備對CMD5的反饋命令為R4,下面來仔細分析一下這個R4,因為後面要用到這個R4命令。從SDIO spec文檔裡面,我們能得到R4命令的格式
從上圖可以看出,該命令有48位,但我們的ocr變量是32位的,那怎麼存儲呢?系統就去掉原命令的開頭8位以及結尾的8位,只保留中間的32為,也就是截短後的命令格式是如下:
具體各位的描述如下:
C -- 我還不知道
Number 0f IO functions -- 每個SDIO設備都有功能塊,這三位就記錄了該設備有多少個功能塊,最多7個
Memory Present – 指明該設備是純粹只有功能塊的設備,還是同時包含了存儲空間,如果為0就是前者,如果是1就是後者
Stuff Bits -- 沒有實際用途一般為0
I/O OCR – 該設備所能支持的電壓範圍(具體描述見sdio spec)
2、配置電壓
ocr就是我們上面講的反饋命令R4(截短之後的32位),那麼ocr&0x7f的意義是什麼呢?從R4的格式就可以看出來,其低24位就代表了所能支持的電壓範圍,我們再來詳細的看一下這24位的OCR格式
現在應該可以知道ocr&0x7f的意義了吧,就是擯棄那些保留的電壓範圍。
重點關注mmc_select_voltage
第1080行的相與 過程就是判斷host實際所支持的電壓與card所需要的電壓是否匹配,如果匹配,那麼ocr的值就非0,否則就為0
簡單介紹下第1082行的ffs函數,它的作用就是返回參數中第一個為1的bit的位置(ffs(0)=0,ffs(1)=1,ffs(8)=4),那麼該函數用在這裡的作用就是取出card需要的實際電壓是多少;
第1090行的mmc_set_ios函數裡面通過調用sdhci_set_power將host->ios.vdd所代表的電壓寫入寄存器PWRCONn中 完成那個對電壓的重新配置(想要了解更詳細的過程,請跟蹤原始碼)
3、初始化SDIO卡
第821行就是初始化SDIO卡的函數 這個函數很長,也很重要,這裡筆者就不列出其程序代碼了,只是列出其中最重要的幾條:
1、通過函數mmc_alloc_card分配一個mmc_card的變量card
2、通過讀取R4命令中的bit27(也就是Memory Present)來判斷此卡是純IO卡 ,還是同時包含存儲功能。筆者使用的WIFI模塊為純IO功能,所以card->type = MMC_TYPE_SDIO(這個很重要,以後會用到) (接下來重點分析MMC_TYPE_SDIO的情況)
3、通過發送CMD3命令獲取設備的從地址(relative addr),並且存放在變量card->rca中。筆者使用的WIFI模塊的card->rca = 1
4、通過發送CMD7,選中相應從地址的卡
5、通過調用函數mmc_set_clock設置卡工作的時鐘頻率
6、通過發送CMD52命令,設置4位數據傳輸模式
4、註冊SDIO功能模塊
847行的變量funcs存儲該SDIO卡所包含的IO功能塊的個數,851行到857行就是逐一初始化各個IO功能塊,下面來重點看一下該函數的內容:
第71行就是分配sdio_func結構體變量,該結構體存儲了功能塊的參數。
第75行就是給功能塊編號,編號是從1到7(因為一個SDIO設備最多只有7個功能塊),存儲在變量func->num中
第78行就是讀取SDIO卡中的FBR寄存器中關於該卡的功能類型的數據,存儲在func->class變量中(具體關於FBR寄存器內容,可以參考SDIO spec文檔)
第82行就是讀取SDIO卡中的CIS寄存器的內容
上面的程序就是將功能模塊逐個的註冊進設備模型,這裡想重點說明一下註冊的名稱(name),它是由三部分組成的,每部分之間用冒號隔開,(即 host的名稱:rca:功能塊編號)。
具體到筆者使用的WIFI模塊,因為其host名稱是mmc2 ,rca = 1,並且有兩個功能模塊(功能模塊編號分別是1和2),所以在/sys/bus/sdio/devices目錄下能見到如下兩個設備名
mmc2:0001:1
mmc2:0001:2
5、註冊SDIO卡
上面的mmc_add_card函數就是註冊card了(這個card是在第3部分,初始化SDIO卡 裡面分配和定義的)
第259行就是給card命名,格式為host名字:從地址,對於筆者的WIFI模塊 就是mmc2:0001
第261到273行就是根據card->type來分辨出card的類型,給賦予相應的字符串,筆者的WIFI模塊就是"SDIO"
第275行就是列印信息,具體不解釋 筆者的列印信息為 mmc2:new high speed SDIO card at address 0001(通常可以通過查看內核啟動信息中是否有該語句來判斷card是否被正確識別)
第283行 就是將card註冊進linux設備模型 註冊結果就是可以在/sys/bus/mmc/devices目錄下見到card 的名字,筆者的就是mmc2:0001