1. 前言
本文是Linux MMC framework的第二篇,將從驅動工程師的角度,介紹MMC host controller driver有關的知識,學習并掌握如何在MMC framework的框架下,編寫MMC控制器的驅動程序。同時,通過本篇文章,我們會進一步的理解MMC、SD、SDIO等有關的基礎知識。
2. MMC host驅動介紹
MMC的host driver,是用于驅動MMC host控制器的程序,位于“drivers/mmc/host”目錄。從大的流程上看,編寫一個這樣的驅動非常簡單,只需要三步:
1)調用mmc_alloc_host,分配一個struct mmc_host類型的變量,用于描述某一個具體的MMC host控制器。
2)根據MMC host控制器的硬件特性,填充struct mmc_host變量的各個字段,例如MMC類型、電壓范圍、操作函數集等等。
3)調用mmc_add_host接口,將正確填充的MMC host注冊到MMC core中。
當然,看著簡單,一牽涉到實現細節,還是很麻煩的,后面我們會慢慢分析。
注1:分析MMC host driver的時候,Linux kernel中有大把大把的例子(例如drivers/mmc/host/pxamci.c),大家可盡情參考、學習,不必謙虛(這是學習Linux的最佳方法)。
注2:由于MMC host driver牽涉到具體的硬件controller,分析的過程中需要一些具體的硬件輔助理解,本文將以“X Project”所使用Bubblegum-96平臺為例,具體的硬件spec可參考[1]。
3. 主要數據結構
3.1 struct mmc_host
MMC core使用struct mmc_host結構抽象具體的MMC host controller,該結構的定義位于“include/linux/mmc/host.h”中,它既可以用來描述MMC控制器所具有的特性、能力(host driver關心的內容),也保存了host driver運行過程中的一些狀態、參數(MMC core關心的內容)。需要host driver關心的部分的具體的介紹如下:
parent,一個struct device類型的指針,指向該MMC host的父設備,一般是注冊該host的那個platform設備;
class_dev,一個struct device類型的變量,是該MMC host在設備模型中作為一個“設備”的體現。當然,人如其名,該設備從屬于某一個class(mmc_host_class);
ops,一個struct mmc_host_ops類型的指針,保存了該MMC host有關的操作函數集,具體可參考3.2小節的介紹;
pwrseq,一個struct mmc_pwrseq類型的指針,保存了該MMC host電源管理有關的操作函數集,具體可參考3.2小節的介紹;
f_min、f_max、f_init,該MMC host支持的時鐘頻率范圍,最小頻率、最大頻率以及初始頻率;
ocr_avail,該MMC host可支持的操作電壓范圍(具體可參考include/linux/mmc/host.h中MMC_VDD_開頭的定義);
注3:OCR(Operating Conditions Register)是MMC/SD/SDIO卡的一個32-bit的寄存器,其中有些bit指明了該卡的操作電壓。MMC host在驅動這些卡的時候,需要和Host自身所支持的電壓范圍匹配之后,才能正常操作,這就是ocr_avail的存在意義。
ocr_avail_sdio、ocr_avail_sd、ocr_avail_mmc,如果MMC host針對SDIO、SD、MMC等不同類型的卡,所支持的電壓范圍不同的話,需要通過這幾個字段特別指定。否則,不需要賦值(初始化為0);
pm_notify,一個struct notifier_block類型的變量,用于支持power management有關的notify實現;
max_current_330、max_current_300、max_current_180,當工作電壓分別是3.3v、3v以及1.8v的時候,所支持的最大操作電流(如果MMC host沒有特別的限制,可以不賦值);
caps、caps2,指示該MMC host所支持的功能特性,具體可參考3.4小節的介紹;
pm_caps,mmc_pm_flag_t類型的變量,指示該MMC host所支持的電源管理特性;
max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count、max_busy_timeout,和塊設備(如MMC、SD、eMMC等)有關的參數,在古老的磁盤時代,這些參數比較重要。對基于MMC技術的塊設備來說,硬件的性能大大提升,這些參數就沒有太大的意義了。具體可參考5.2章節有關MMC數據傳輸的介紹;
lock,一個spin lock,是MMC host driver的私有變量,可用于保護host driver的臨界資源;
ios,一個struct mmc_ios類型的變量,用于保存MMC bus的當前配置,具體可參考3.5小節的介紹;
supply,一個struct mmc_supply類型的變量,用于描述MMC系統中的供電信息,具體可參考3.6小節的介紹;
……
private,一個0長度的數組,可以在mmc_alloc_host時指定長度,由host controller driver自行支配。
3.2 struct mmc_host_ops
struct mmc_host_ops抽象并集合了MMC host controller所有的操作函數集,包括:
1)數據傳輸有關的函數
/*?
* It is optional for the host to implement pre_req and post_req in?
* order to support double buffering of requests (prepare one?
* request while another request is active).?
* pre_req() must always be followed by a post_req().?
* To undo a call made to pre_req(), call post_req() with?
* a nonzero err condition.?
*/?
void??? (*post_req)(struct mmc_host *host, struct mmc_request *req,?
??????????????????? int err);?
void??? (*pre_req)(struct mmc_host *host, struct mmc_request *req,?
?????????????????? bool is_first_req);?
void??? (*request)(struct mmc_host *host, struct mmc_request *req);
pre_req和post_req是非必需的,host driver可以利用它們實現諸如雙buffer之類的高級功能。
數據傳輸的主題是struct mmc_request類型的指針,具體可參考3.7小節的介紹。
2)總線參數的配置以及卡狀態的獲取函數
/*?
* Avoid calling these three functions too often or in a "fast path",?
* since underlaying controller might implement them in an expensive?
* and/or slow way.?
*?
* Also note that these functions might sleep, so don't call them?
* in the atomic contexts!?
*?
* Return values for the get_ro callback should be:?
*?? 0 for a read/write card?
*?? 1 for a read-only card?
*?? -ENOSYS when not supported (equal to NULL callback)?
*?? or a negative errno value when something bad happened?
*?
* Return values for the get_cd callback should be:?
*?? 0 for a absent card?
*?? 1 for a present card?
*?? -ENOSYS when not supported (equal to NULL callback)?
*?? or a negative errno value when something bad happened?
*/?
void??? (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);?
int???? (*get_ro)(struct mmc_host *host);?
int???? (*get_cd)(struct mmc_host *host);
set_ios用于設置bus的參數(ios,可參考3.5小節的介紹);get_ro可獲取card的讀寫狀態(具體可參考上面的注釋);get_cd用于檢測卡的存在狀態。
注4:注釋中特別說明了,這幾個函數可以sleep,耗時較長,沒事別亂用。
3)其它一些非主流函數,都是optional的,用到的時候再去細看即可。
3.3 struct mmc_pwrseq
MMC framework的power sequence是一個比較有意思的功能,它提供一個名稱為struct mmc_pwrseq_ops的操作函數集,集合了power on、power off等操作函數,用于控制MMC系統的供電,如下:
struct mmc_pwrseq_ops {?
??????? void (*pre_power_on)(struct mmc_host *host);?
??????? void (*post_power_on)(struct mmc_host *host);?
??????? void (*power_off)(struct mmc_host *host);?
??????? void (*free)(struct mmc_host *host);?
};
struct mmc_pwrseq {?
??????? const struct mmc_pwrseq_ops *ops;?
};
與此同時,MMC core提供了一個通用的pwrseq的管理模塊(drivers/mmc/core/pwrseq.c),以及一些簡單的pwrseq策略(如drivers/mmc/core/pwrseq_simple.c、drivers/mmc/core/pwrseq_emmc.c),最終的目的是,通過一些簡單的dts配置,即可正確配置MMC的供電,例如:
/* arch/arm/boot/dts/omap3-igep0020.dts */?
mmc2_pwrseq: mmc2_pwrseq {?
??????? compatible = "mmc-pwrseq-simple";?
??????? reset-gpios = <&gpio5 11 GPIO_ACTIVE_LOW>,????? /* gpio_139 - RESET_N_W */
????????????????????? <&gpio5 10 GPIO_ACTIVE_LOW>;????? /* gpio_138 - WIFI_PDN */
};
/* arch/arm/boot/dts/rk3288-veyron.dtsi? */?
emmc_pwrseq: emmc-pwrseq {?
??????? compatible = "mmc-pwrseq-emmc";?
??????? pinctrl-0 = <&emmc_reset>;?
??????? pinctrl-names = "default";?
??????? reset-gpios = <&gpio2 9 GPIO_ACTIVE_HIGH>;?
};
具體的細節,在需要的時候,閱讀代碼即可,這里不再贅述。
3.4 Host capabilities
通過的caps和caps2兩個字段,MMC host driver可以告訴MMC core控制器的一些特性、能力,包括(具體可參考include/linux/mmc/host.h中相關的宏定義,更為細致的使用場景和指南,需要結合實際的硬件,具體分析):
MMC_CAP_4_BIT_DATA,支持4-bit的總線傳輸;?
MMC_CAP_MMC_HIGHSPEED,支持“高速MMC時序”;?
MMC_CAP_SD_HIGHSPEED,支持“高速SD時序”;?
MMC_CAP_SDIO_IRQ,可以產生SDIO有關的中斷;?
MMC_CAP_SPI,僅僅支持SPI協議(可參考“drivers/mmc/host/mmc_spi.c”中有關的實現);?
MMC_CAP_NEEDS_POLL,表明需要不停的查詢卡的插入狀態(如果需要支持熱拔插的卡,則需要設置該feature);?
MMC_CAP_8_BIT_DATA,支持8-bit的總線傳輸;?
MMC_CAP_AGGRESSIVE_PM,支持比較積極的電源管理策略(kernel的注釋為“Suspend (e)MMC/SD at idle”);?
MMC_CAP_NONREMOVABLE,表明該MMC控制器所連接的卡是不可拆卸的,例如eMMC;?
MMC_CAP_WAIT_WHILE_BUSY,表面Host controller在向卡發送命令時,如果卡處于busy狀態,是否等待。如果不等待,MMC core在一些流程中(如查詢busy狀態),需要額外做一些處理;
MMC_CAP_ERASE,表明該MMC控制器允許執行擦除命令;?
MMC_CAP_1_8V_DDR,支持工作電壓為1.8v的DDR(Double Data Rate)mode[3];?
MMC_CAP_1_2V_DDR,支持工作電壓為1.2v的DDR(Double Data Rate)mode[3];?
MMC_CAP_POWER_OFF_CARD,可以在啟動之后,關閉卡的供電(一般只有在SDIO的應用場景中才可能用到,因為SDIO所連接的設備可能是一個獨立的設備);
MMC_CAP_BUS_WIDTH_TEST,支持通過CMD14和CMD19進行總線寬度的測試,以便選擇一個合適的總線寬度進行通信;?
MMC_CAP_UHS_SDR12、MMC_CAP_UHS_SDR25、MMC_CAP_UHS_SDR50、MMC_CAP_UHS_SDR104,它們是SD3.0的速率模式,分別表示支持25MHz、50MHz、100MHz和208MHz的SDR(Single Data Rate,相對[3]中的DDR)模式;
MMC_CAP_UHS_DDR50,它也是SD3.0的速率模式,表示支持50MHz的DDR(Double Data Rate[3])模式;?
MMC_CAP_DRIVER_TYPE_A、MMC_CAP_DRIVER_TYPE_C、MMC_CAP_DRIVER_TYPE_D,分別表示支持A/C/D類型的driver strength(驅動能力,具體可參考相應的spec);
MMC_CAP_CMD23,表示該controller支持multiblock transfers(通過CMD23);?
MMC_CAP_HW_RESET,支持硬件reset;
MMC_CAP2_FULL_PWR_CYCLE,表示該controller支持從power off到power on的完整的power cycle;
MMC_CAP2_HS200_1_8V_SDR、MMC_CAP2_HS200_1_2V_SDR,HS200是eMMC5.0支持的一種速率模式,200是200MHz的縮寫,分別表示支持1.8v和1.2v的SDR模式;
MMC_CAP2_HS200,表示同時支持MMC_CAP2_HS200_1_8V_SDR和MMC_CAP2_HS200_1_2V_SDR;?
MMC_CAP2_HC_ERASE_SZ,支持High-capacity erase size;?
MMC_CAP2_CD_ACTIVE_HIGH,CD(Card-detect)信號高有效;?
MMC_CAP2_RO_ACTIVE_HIGH,RO(Write-protect)信號高有效;?
MMC_CAP2_PACKED_RD、MMC_CAP2_PACKED_WR,允許packed read、packed write;?
MMC_CAP2_PACKED_CMD,同時支持DMMC_CAP2_PACKED_RD和MMC_CAP2_PACKED_WR;?
MMC_CAP2_NO_PRESCAN_POWERUP,在scan之前不要上電;?
MMC_CAP2_HS400_1_8V、MMC_CAP2_HS400_1_2V,HS400是eMMC5.0支持的一種速率模式,400是400MHz的縮寫,分別表示支持1.8v和1.2v的HS400模式;
MMC_CAP2_HS400,同時支持MMC_CAP2_HS400_1_8V和MMC_CAP2_HS400_1_2V;?
MMC_CAP2_HSX00_1_2V,同時支持MMC_CAP2_HS200_1_2V_SDR和MMC_CAP2_HS400_1_2V;?
MMC_CAP2_SDIO_IRQ_NOTHREAD,SDIO的IRQ的處理函數,不能在線程里面執行;?
MMC_CAP2_NO_WRITE_PROTECT,沒有物理的RO管腳,意味著任何時候都是可以讀寫的;?
MMC_CAP2_NO_SDIO,在初始化的時候,不會發送SDIO相關的命令(也就是說不支持SDIO模式)。
3.5 struct mmc_ios
struct mmc_ios中保存了MMC總線當前的配置情況,包括如下信息:
1)clock,時鐘頻率。
2)vdd,卡的供電電壓,通過“1 << vdd”可以得到MMC_VDD_x_x(具體可參考include/linux/mmc/host.h中MMC_VDD_開頭的定義),進而得到電壓信息。
3)bus_mode,兩種信號模式,open-drain(MMC_BUSMODE_OPENDRAIN)和push-pull(MMC_BUSMODE_PUSHPULL),對應不同的高低電平(可參考相應的spec,例如[2])。
4)chip_select,只針對SPI模式,指定片選信號的有效模式,包括沒有片選信號(MMC_CS_DONTCARE)、高電平有效(MMC_CS_HIGH)、低電平有效(MMC_CS_LOW)。
5)power_mode,當前的電源狀態,包括MMC_POWER_OFF、MMC_POWER_UP、MMC_POWER_ON和MMC_POWER_UNDEFINED。
6)bus_width,總線的寬度,包括1-bit(MMC_BUS_WIDTH_1)、4-bit(MMC_BUS_WIDTH_4)和8-bit(MMC_BUS_WIDTH_8)。
7)timing,符合哪一種總線時序(大多對應某一類MMC規范),包括:
MMC_TIMING_LEGACY,舊的、不再使用的規范;?
MMC_TIMING_MMC_HS,High speed MMC規范(具體可參考相應的spec,這里不再詳細介紹,下同);?
MMC_TIMING_SD_HS,High speed SD;?
MMC_TIMING_UHS_SDR12;?
MMC_TIMING_UHS_SDR25?
MMC_TIMING_UHS_SDR50?
MMC_TIMING_UHS_SDR104?
MMC_TIMING_UHS_DDR50?
MMC_TIMING_MMC_DDR52?
MMC_TIMING_MMC_HS200?
MMC_TIMING_MMC_HS400
8)signal_voltage,總線信號使用哪一種電壓,3.3v(MMC_SIGNAL_VOLTAGE_330)、1.8v(MMC_SIGNAL_VOLTAGE_180)或者1.2v(MMC_SIGNAL_VOLTAGE_120)。
9)drv_type,驅動能力,包括:
MMC_SET_DRIVER_TYPE_B?
MMC_SET_DRIVER_TYPE_A?
MMC_SET_DRIVER_TYPE_C?
MMC_SET_DRIVER_TYPE_D
3.6 struct mmc_supply
struct mmc_supply中保存了兩個struct regulator指針(如下),用于控制MMC子系統有關的供電(vmmc和vqmmc)。
struct mmc_supply {?
??????? struct regulator *vmmc;???????? /* Card power supply */?
??????? struct regulator *vqmmc;??????? /* Optional Vccq supply */?
};
關于vmmc和vqmmc,說明如下:
vmmc是卡的供電電壓,一般連接到卡的VDD管腳上。而vqmmc則用于上拉信號線(CMD、CLK和DATA[6])。
通常情況下vqmmc使用和vmmc相同的regulator,同時供電即可。
后來,一些高速卡(例如UHS SD)要求在高速模式下,vmmc為3.3v,vqmmc為1.8v,這就需要兩個不同的regulator獨立控制。
3.7 struct mmc_request
struct mmc_request封裝了一次傳輸請求,定義如下:
/* include/linux/mmc/core.h */
struct mmc_request {?
??????? struct mmc_command????? *sbc;?????????? /* SET_BLOCK_COUNT for multiblock */
??????? struct mmc_command????? *cmd;?
??????? struct mmc_data???????? *data;?
??????? struct mmc_command????? *stop;
struct completion?????? completion;?
??????? void??????????????????? (*done)(struct mmc_request *);/* completion function */
??????? struct mmc_host???????? *host;?
};
要理解這個數據結構,需要先了解MMC的總線協議(bus protocol),這里以eMMC[2]為例進行簡單的介紹(更為詳細的解釋,可參考相應的spec以及本站的文章--“eMMC 原理 4 :總線協議[7]”)。
3.7.1 MMC bus protocol
在eMMC的spec中,稱總線協議為“message-based MultiMediaCard bus protocol”,這里的message由三種信標(token)組成:
Command,用于啟動(或者停止)一次傳輸,由Host通過CMD line向Card發送;
Response,用于應答上一次的Command,由Card通過CMD line想Host發送;
Data,傳輸數據,由Host(或者Card)通過DATA lines向Card(或者Host發送)。
以上token除了Command之外,剩余的兩個(Response和Data)都是非必需的,也就是說,一次傳輸可以是:不需要應答、不需要數據傳輸的Command;需要應答、不需要數據傳輸的Command;不需要應答、需要數據傳輸的Command;不需要應答、不需要數據傳輸的Command。
Command token的格式只有一種(具體可參考[2]中“Command token format”有關的表述),長度為48bits,包括Start bit(0)、Transmitter bit(1, host command)、Content(38bits)、CRC checksum(7bits)、Stop bit(1)。
根據內容的不同,Response token的格式有5中,分別簡稱為R1/R3/R4/R5/R2,其中R1/R3/R4/R5的長度為48bits,R2為136bits(具體可參考[2]中“Response token format”有關的表述)。
對于包含了Data token的Command,有兩種類型:
Sequential commands,發送Start command之后,數據以stream的形式傳輸,直到Stop command為止。這種方式只支持1-bit總線模式,主要為了兼容舊的技術,一般不使用;
Block-oriented commands,發送Start command之后,數據以block的形式傳輸(每個block的大小是固定的,且都由CRC保護)。
最后,以block為單位的傳輸,大體上也分為兩類:
在傳輸開始的時候(Start command),沒有指定需要傳輸的block數目,直到發送Stop command為止。這種方法在spec中稱作“Open-ended”;
在傳輸開始的時候(Start command),指定需要傳輸的block數據,當達到數據之后,Card會自動停止傳輸,這樣可以省略Stop command。這種方法在spec中稱作pre-defined block count。
3.7.2 struct mmc_request
了解MMC bus protocol之后,再來看一次MMC傳輸請求(struct mmc_request )所包含的內容:
cmd,Start command,為struct mmc_command類型(具體請參考3.7.3中的介紹)的指針,在一次傳輸的過程中是必須的;
data,傳輸的數據,為struct mmc_data類型(具體請參考3.7.4中的介紹)的指針,不是必須要的;
stop、sbc,如果需要進行數據傳輸,根據數據傳輸的方式(參考3.7.1中的介紹):如果是“Open-ended”,則需要stop命令(stop指針,或者data->stop指針);如果是pre-defined block count,則需要sbc指針(用于發送SET_BLOCK_COUNT--CMD23命令);
completion,一個struct completion變量,用于等待此次傳輸完成,host controller driver可以根據需要使用;
done,傳輸完成時的回調,用于通知傳輸請求的發起者;
host,對應的mmc host controller指針。
3.7.3 struct mmc_command
struct mmc_command結構抽象了一個MMC command,包括如下內容:
/* include/linux/mmc/core.h */
opcode,Command的操作碼,用于標識該命令是哪一個命令,具體可參考相應的spec(例如[2]);
arg,一個Command可能會攜帶參數,具體可參考相應的spec(例如[2]);
resp[4],Command發出后,如果需要應答,結果保存在resp數組中,該數組是32-bit的,因此最多可以保存128bits的應答;
flags,是一個bitmap,保存該命令所期望的應答類型,例如:?
MMC_RSP_PRESENT(1 << 0),是否需要應答,如果該bit為0,則表示該命令不需要應答,否則,需要應答;?
MMC_RSP_136(1 << 1),如果為1,表示需要136bits的應答;?
MMC_RSP_CRC(1 << 2),如果為1,表示需要對該命令進行CRC校驗;?
等等,具體可參考include/linux/mmc/core.h中“MMC_RSP_”開頭的定義;
retries,如果命令發送出錯,可以重新發送,該字段向host driver指明最多可重發的次數;
error,如果最終還是出錯,host driver需要通過該字段返回錯誤的原因,kernel定義了一些標準錯誤,例如ETIMEDOUT、EILSEQ、EINVAL、ENOMEDIUM等,具體含義可參考include/linux/mmc/core.h中注釋;
busy_timeout,如果card具有busy檢測的功能,該字段指定等待card返回busy狀態的超時時間,單位為ms;
data,和該命令對應的struct mmc_data指針;
mrq,和該命令對應的struct mmc_request指針。
3.7.4 struct mmc_data
struct mmc_data結構包含了數據傳輸有關的內容:
/* include/linux/mmc/core.h */
timeout_ns、timeout_clks,這一筆數據傳輸的超時時間(單位分別為ns和clks),如果超過這個時間host driver還無法成功發送,則要將狀態返回給mmc core;
blksz、blocks,該筆數據包含多少block(blocks),每個block的size多大(blksz),這兩個值不會大于struct mmc_host中上報的max_blk_size和max_blk_count;
error,如果數據傳輸出錯,錯誤值保存在該字段,具體意義和struct mmc_command中的一致;
flags,一個bitmap,指明該筆傳說的方向(MMC_DATA_WRITE或者MMC_DATA_READ);
sg,一個struct scatterlist類型的數組,保存了需要傳輸的數據(可以通過dma_相關的接口,獲得相應的物理地址);
sg_len,sg數組的size;?
sg_count,通過sg map出來的實際的entry的個數(可能由于物理地址的連續、IOMMU的干涉等,map出來的entry的個數,可能會小于sg的size);
注5:有關scatterlist的介紹,可參考本站另外的文章(TODO)。有關struct mmc_data的使用場景,可參考5.2小節的介紹;
host_cookie,host driver的私有數據,怎么用由host driver自行決定。
4. 主要API
第3章花了很大的篇幅介紹了用于抽象MMC host的數據結構----struct mmc_host,并詳細說明了和mmc_host相關的mmc request、mmc command、mmc data等結構?;谶@些知識,本章將介紹MMC core提供的和struct mmc_host有關的操作函數,主要包括如下幾類。
4.1 向MMC host controller driver提供的用于操作struct mmc_host的API
包括:
struct mmc_host *mmc_alloc_host(int extra, struct device *);
int mmc_add_host(struct mmc_host *);?
void mmc_remove_host(struct mmc_host *);?
void mmc_free_host(struct mmc_host *);?
int mmc_of_parse(struct mmc_host *host);?
static inline void *mmc_priv(struct mmc_host *host) {?
??????? return (void *)host->private;?
}
mmc_alloc_host,動態分配一個struct mmc_host變量。extra是私有數據的大小,可通過host->private指針訪問(也可通過mmc_priv接口直接獲取)。mmc_free_host執行相反動作。
mmc_add_host,將已初始化好的host變量注冊到kernel中。mmc_remove_host執行相反動作。
為了方便,host controller driver可以在dts中定義host的各種特性,然后在代碼中調用mmc_of_parse解析并填充到struct mmc_host變量中。dts屬性關鍵字可參考mmc_of_parse的source code(drivers/mmc/core/host.c),并結合第三章的內容自行理解。
int mmc_power_save_host(struct mmc_host *host);?
int mmc_power_restore_host(struct mmc_host *host);
從mmc host的角度進行電源管理,進入/退出power save狀態。
void mmc_detect_change(struct mmc_host *, unsigned long delay);
當host driver檢測到總線上的設備有變動的話(例如卡的插入和拔出等),需要調用這個接口,讓MMC core幫忙做后續的工作,例如檢測新插入的卡到底是個什么東東……
另外,可以通過delay參數告訴MMC core延時多久(單位為jiffies)開始處理,通常可以用來對卡的拔插進行去抖動。
void mmc_request_done(struct mmc_host *, struct mmc_request *);
當host driver處理完成一個mmc request之后,需要調用該函數通知MMC core,MMC core會進行一些善后的操作,例如校驗結果、調用mmc request的.done回調等等。
static inline void mmc_signal_sdio_irq(struct mmc_host *host)
void sdio_run_irqs(struct mmc_host *host);
對于SDIO類型的總線,這兩個函數用于操作SDIO irqs,后面用到的時候再分析。
int mmc_regulator_get_ocrmask(struct regulator *supply);
int mmc_regulator_set_ocr(struct mmc_host *mmc,?
??????????????????????? struct regulator *supply,?
??????????????????????? unsigned short vdd_bit);?
int mmc_regulator_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios);?
int mmc_regulator_get_supply(struct mmc_host *mmc);
regulator有關的輔助函數:
mmc_regulator_get_ocrmask可根據傳入的regulator指針,獲取該regulator支持的所有電壓值,并以此推導出對應的ocr mask(可參考3.1中的介紹)。
mmc_regulator_set_ocr用于設置host controller為某一個操作電壓(vdd_bit),該接口會調用regulator framework的API,進行具體的電壓切換。
mmc_regulator_set_vqmmc可根據struct mmc_ios信息,自行調用regulator framework的接口,設置vqmmc的電壓。
最后,mmc_regulator_get_supply可以幫忙從dts的vmmc、vqmmc屬性值中,解析出對應的regulator指針,以便后面使用。
4.2 用于判斷MMC host controller所具備的能力的API
比較簡單,可結合第3章的介紹理解:
#define mmc_host_is_spi(host)?? ((host)->caps & MMC_CAP_SPI)
static inline int mmc_card_is_removable(struct mmc_host *host)?
static inline int mmc_card_keep_power(struct mmc_host *host)?
static inline int mmc_card_wake_sdio_irq(struct mmc_host *host)?
static inline int mmc_host_cmd23(struct mmc_host *host)?
static inline int mmc_boot_partition_access(struct mmc_host *host)?
static inline int mmc_host_uhs(struct mmc_host *host)?
static inline int mmc_host_packed_wr(struct mmc_host *host)?
static inline int mmc_card_hs(struct mmc_card *card)?
static inline int mmc_card_uhs(struct mmc_card *card)?
static inline bool mmc_card_hs200(struct mmc_card *card)?
static inline bool mmc_card_ddr52(struct mmc_card *card)?
static inline bool mmc_card_hs400(struct mmc_card *card)?
static inline void mmc_retune_needed(struct mmc_host *host)?
static inline void mmc_retune_recheck(struct mmc_host *host)
5. MMC host驅動的編寫步驟
經過上面章節的描述,相信大家對MMC controller driver有了比較深的理解,接下來驅動的編寫就是一件水到渠成的事情了。這里簡要描述一下驅動編寫步驟,也順便為本文做一個總結。
5.1 struct mmc_host的填充和注冊
編寫MMC host驅動的所有工作,都是圍繞struct mmc_host結構展開的。在對應的platform driver的probe函數中,通過mmc_alloc_host分配一個mmc host后,我們需要根據controller的實際情況,填充對應的字段。
mmc host中大部分和controller能力/特性有關的字段,可以通過dts配置(然后在代碼中調用mmc_of_parse自動解析并填充),舉例如下(注意其中紅色的部分,都是MMC framework的標準字段):
/* arch/arm/boot/dts/exynos5420-peach-pit.dts */
&mmc_1 {?
??????? status = "okay";?
??????? num-slots = <1>;?
????????non-removable;?
????????cap-sdio-irq;?
??????? keep-power-in-suspend;?
????????clock-frequency = <400000000>;
??????? samsung,dw-mshc-ciu-div = <1>;?
??????? samsung,dw-mshc-sdr-timing = <0 1>;?
??????? samsung,dw-mshc-ddr-timing = <0 2>;?
??????? pinctrl-names = "default";?
??????? pinctrl-0 = <&sd1_clk>, <&sd1_cmd>, <&sd1_int>, <&sd1_bus1>,?
??????????????????? <&sd1_bus4>, <&sd1_bus8>, <&wifi_en>;?
????????bus-width = <4>;?
????????cap-sd-highspeed;?
????????mmc-pwrseq = <&mmc1_pwrseq>;
????????vqmmc-supply = <&buck10_reg>;?
};
5.2 數據傳輸的實現
填充struct mmc_host變量的過程中,工作量最大的,就是對struct mmc_host_ops的實現(毫無疑問!所有MMC host的操作邏輯都封在這里呢!!)。這里簡單介紹一下相關的概念,具體的驅動編寫步驟,后面文章會結合“X Project”詳細描述。
5.2.1 Sectors(扇區)、Blocks(塊)以及Segments(段)的理解
我們在3.1小節介紹struct mmc_host的時候,提到了max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count等參數。這和磁盤設備(塊設備)中Sectors、Blocks、Segments等概念有關,下面簡單介紹一下:
1)Sectors
Sectors是存儲設備訪問的基本單位。
對磁盤、NAND等塊設備來說,Sector的size是固定的,例如512、2048等。
對存儲類的MMC設備來說,按理說也應有固定size的sector。但因為有MMC協議的封裝,host驅動以及上面的塊設備驅動,不需要關注物理的size。它們需要關注的就是bus上的數據傳輸單位(具體可參考MMC protocol的介紹[7])。
最后,對那些非存儲類的MMC設備來說,完全沒有sector的概念了。
2) Blocks
Blocks是數據傳輸的基本單位,是VFS(虛擬文件系統)抽象出來的概念,是純軟件的概念,和硬件無關。它必須是2的整數倍、不能大于Sectors的單位、不能大于page的長度,一般為512B、2048B或者4096B。
對MMC設備來說,由于協議的封裝,淡化了Sector的概念,或者說,MMC設備可以支持一定范圍內的任意的Block size。Block size的范圍,由兩個因素決定:
a)host controller的能力,這反映在struct mmc_host結構的max_blk_size字段上。?
b)卡的能力,這可以通過MMC command從卡的CSD(Card-Specific Data)寄存器中讀出。
3)Segments[8]
塊設備的數據傳輸,本質上是設備上相鄰扇區與內存之間的數據傳輸。通常情況下,為了提升性能,數據傳輸通過DMA方式。
在磁盤控制器的舊時代,DMA操作都比較簡單,每次傳輸,數據在內存中必須是連續的?,F在則不同,很多SOC都支持“分散/聚合”(scatter-gather)DMA操作,這種操作模式下,數據傳輸可以在多個非連續的內存區域中進行。
對于每個“分散/聚合”DMA操作,塊設備驅動需要向控制器發送:?
a)初始扇區號和傳輸的總共扇區數?
b)內存區域的描述鏈表,每個描述都包含一個地址和長度。不同的描述之間,可以在物理上連續,也可以不連續。
控制器來管理整個數據傳輸,例如:在讀操作中,控制器從塊設備相鄰的扇區上讀取數據,然后將數據分散存儲在內存的不同區域。
這里的每個內存區域描述(物理連續的一段內存,可以是一個page,也可以是page的一部分),就稱作Segment。一個Segment包含多個相鄰扇區。
最后,利用“分散/聚合”的DMA操作,一次數據傳輸可以會涉及多個segments。
理解了Segment的概念之后,max_seg_size和max_segs兩個字段就好理解了:
雖然控制器支持“分散/聚合”的DMA操作,但物理硬件總有限制,例如最大的Segment size(也即一個內存描述的最大長度),最多支持的segment個數(max_segs)等。
5.2.2 struct mmc_data中的sg
我們在3.7.4小節介紹struct mmc_data時,提到了scatterlist的概念。結合上面Segment的解釋,就很好理解了:
MMC core提交給MMC host driver的數據傳輸請求,是一個struct scatterlist鏈表(也即內存區域的描述鏈表),也可以理解為是一個個的Segment(Segment的個數保存在sg_len變量中了)。
每個Segment是一段物理地址連續的內存區域,所有的Segments對應了MMC設備中連續的Sector(或者說Block,初始扇區號和傳輸的總共扇區數已經在之前的MMC command中指定了。
host driver在接收到這樣的數據傳輸請求之后,需要調用dma_map_sg將這些Segment映射出來(獲得相應的物理地址),以便交給MMC controller傳輸。
當然,相鄰兩個Segment的物理地址可能是連續(或者其它原因),map的時候可能會將兩個Segment合成一個。因此可供MMC controller傳輸的內存片可能少于sg_len(具體要看dma_map_sg的返回值,可將結果保存在sg_count中)。
最后,如何實施傳輸,則要看具體的MMC controller的硬件實現(可能涉及DMA操作),后面文章再詳細介紹。
?
評論
查看更多