信号电压、时钟频率、总线宽度很大程度上都取决于总线速度模式。以下来看一下他们的关系。
从《emmc 5.1》协议上看,有如下几种总线速度模式
#define MMC_HIGH_26_MAX_DTR 26000000
#define MMC_HIGH_52_MAX_DTR 52000000
#define MMC_HIGH_DDR_MAX_DTR 52000000
#define MMC_HS200_MAX_DTR 200000000
#define MMC_HS400_MAX_DTR 200000000
mmc_set_clock(host, MMC_HIGH_52_MAX_DTR);
kernel中定义如下
#define EXT_CSD_CARD_TYPE_26 (1<<0) /* Card can run at 26MHz */
#define EXT_CSD_CARD_TYPE_52 (1<<1) /* Card can run at 52MHz */
#define EXT_CSD_CARD_TYPE_MASK 0xFF /* Mask out reserved bits */
#define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */ /* DDR mode @1.8V or 3V I/O */
#define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */ /* DDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V | EXT_CSD_CARD_TYPE_DDR_1_2V)
#define EXT_CSD_CARD_TYPE_SDR_1_8V (1<<4) /* Card can run at 200MHz */
#define EXT_CSD_CARD_TYPE_SDR_1_2V (1<<5) /* Card can run at 200MHz */ /* SDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_SDR_1_8V | EXT_CSD_CARD_TYPE_SDR_1_2V)
#define EXT_CSD_CARD_TYPE_HS400_1_8V (1<<6) /* Card can run at 200MHz */ /* DDR mode @1.8V I/O */
#define EXT_CSD_CARD_TYPE_HS400_1_2V (1<<7) /* Card can run at 200MHz */ /* DDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_HS400 (EXT_CSD_CARD_TYPE_HS400_1_8V | EXT_CSD_CARD_TYPE_HS400_1_2V)
使用示例:
card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V
使用示例
//HS SDR(mmc_select_hs):
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,EXT_CSD_HS_TIMING, 1,card->ext_csd.generic_cmd6_time);
//HS DDR(mmc_select_hsddr->mmc_select_hs):
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,EXT_CSD_HS_TIMING, 1,card->ext_csd.generic_cmd6_time);
其ddr和总线宽度模式一起设置。
//HS 200(mmc_select_hs200)
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 2, 0);
//HS 400(mmc_select_hs400)
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 3, 0);
#define MMC_CAP2_HS400_1_8V (1 << 22) /* can support */
#define MMC_CAP2_HS400_1_2V (1 << 23) /* can support */
#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP_1_8V_DDR (1 << 11) /* can support */ /* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 12) /* can support */ /* DDR mode at 1.2V */
#define MMC_CAP_HSDDR (MMC_CAP_1_8V_DDR | MMC_CAP_1_2V_DDR)
使用示例
host->caps2 & MMC_CAP2_HS200
mmc_set_timing(host, MMC_TIMING_MMC_HS);
mmc_set_timing(host, MMC_TIMING_UHS_DDR50);
mmc_set_timing(host, MMC_TIMING_MMC_HS200);
mmc_set_timing(host, MMC_TIMING_MMC_HS400);
如何获取emmc支持的总线宽度模式?
没有寄存器可以读取card支持的总线宽度模式。直接按照8bit、4bit、1bit的顺序进行尝试(假如host支持8bit的情况下)。
先以相应bit的SDR的模式进行尝试,如果成功并且需要设置成DDR模式,那么再以相应bit的DDR模式进行尝试。
如何设置emmc的总线宽度模式?
通过ext_csd寄存器进行设置
使用示例如下:
#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */
#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */
#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */
#define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */
#define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */
mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, EXT_CSD_DDR_BUS_WIDTH_8, card->ext_csd.generic_cmd6_time);
#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */
#define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */
host->caps & MMC_CAP_8_BIT_DATA
#define MMC_BUS_WIDTH_1 0
#define MMC_BUS_WIDTH_4 2
#define MMC_BUS_WIDTH_8 3
mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
mmc_set_bus_width(host, MMC_BUS_WIDTH_8);
static int mmc_select_bus_width(struct mmc_card *card, int ddr, u8 *ext_csd)
{
struct mmc_host *host;
static unsigned ext_csd_bits[][2] = {
{ EXT_CSD_BUS_WIDTH_8, EXT_CSD_DDR_BUS_WIDTH_8 }, // 前面是SDR对应的模式,后面是DDR对应的模式
{ EXT_CSD_BUS_WIDTH_4, EXT_CSD_DDR_BUS_WIDTH_4 },
{ EXT_CSD_BUS_WIDTH_1, EXT_CSD_BUS_WIDTH_1 },
};
static unsigned bus_widths[] = {
MMC_BUS_WIDTH_8, MMC_BUS_WIDTH_4, MMC_BUS_WIDTH_1
};
unsigned idx, bus_width = 0;
int err = 0;
host = card->host;
if ((card->csd.mmca_vsn < CSD_SPEC_VER_4) || !(host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA)))
// 首先判断emmc是否支持,以及host是否支持,只能往4bit或者8bit去设置
goto out;
if (host->caps & MMC_CAP_8_BIT_DATA)
idx = 0;
else
idx = 1;
for (; idx < ARRAY_SIZE(bus_widths); idx++) { // 先尝试以SDR对应的总线宽度模式去设置,从8bit-》4bit-》1bit的方式去设置
bus_width = bus_widths[idx];
if (bus_width == MMC_BUS_WIDTH_1)
ddr = 0; /* no DDR for 1-bit width */
err = mmc_select_powerclass(card, ext_csd_bits[idx][0], ext_csd);
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][0], card->ext_csd.generic_cmd6_time); // 设置emmc总线宽度
if (!err) {
mmc_set_bus_width(host, bus_width); // 设置host的总线宽度
}
}
if (!err && ddr) { // 如果是DDR模式,再设置相应的总线宽度为ddr模式
err = mmc_select_powerclass(card, ext_csd_bits[idx][1], ext_csd);
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][1], card->ext_csd.generic_cmd6_time);
// 重新设置emmc的总线宽度模式为ddr模式
}
out:
return err;
}
#define EXT_CSD_CARD_TYPE_26 (1<<0) /* Card can run at 26MHz */
#define EXT_CSD_CARD_TYPE_52 (1<<1) /* Card can run at 52MHz */
#define EXT_CSD_CARD_TYPE_MASK 0xFF /* Mask out reserved bits */
#define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */ /* DDR mode @1.8V or 3V I/O */
#define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */ /* DDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V | EXT_CSD_CARD_TYPE_DDR_1_2V)
#define EXT_CSD_CARD_TYPE_SDR_1_8V (1<<4) /* Card can run at 200MHz */
#define EXT_CSD_CARD_TYPE_SDR_1_2V (1<<5) /* Card can run at 200MHz */ /* SDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_SDR_1_8V | EXT_CSD_CARD_TYPE_SDR_1_2V)
#define EXT_CSD_CARD_TYPE_HS400_1_8V (1<<6) /* Card can run at 200MHz */ /* DDR mode @1.8V I/O */
#define EXT_CSD_CARD_TYPE_HS400_1_2V (1<<7) /* Card can run at 200MHz */ /* DDR mode @1.2V I/O */
#define EXT_CSD_CARD_TYPE_HS400 (EXT_CSD_CARD_TYPE_HS400_1_8V | EXT_CSD_CARD_TYPE_HS400_1_2V)
使用示例:
card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V
如何设置emmc的信号电压?
似乎不需要另外通知emmc信号电压要发生切换了(sd card则是需要的)
如何获取host支持的信号电压?
同样是和支持的总线速度模式定义在一起的。
根据mmc_host的caps属性和caps2属性进行判断
使用示例如下:
#define MMC_CAP2_HS400_1_8V (1 << 22) /* can support */
#define MMC_CAP2_HS400_1_2V (1 << 23) /* can support */
#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP_1_8V_DDR (1 << 11) /* can support */ /* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 12) /* can support */ /* DDR mode at 1.2V */
#define MMC_CAP_HSDDR (MMC_CAP_1_8V_DDR | MMC_CAP_1_2V_DDR)
// 补充,似乎HS SDR虽然支持1.8V和1.2V,但是代码上并没有做相应的判断
eg:host->caps2 & MMC_CAP2_HS200_1_2V_SDR
#define MMC_SIGNAL_VOLTAGE_330 0
#define MMC_SIGNAL_VOLTAGE_180 1
#define MMC_SIGNAL_VOLTAGE_120 2
__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120)
__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180)
刚上电的时候是处于legacy模式。在设置速度总线模式的过程中,去设置总线宽度、信号电压、时钟等等。
会按照hs400 > hs200 > hsddr > hs的顺序去尝试切换。其实现在mmc_select_bus_speed函数中。
static int mmc_select_bus_speed(struct mmc_card *card, u8 *ext_csd)
{
int err = 0;
BUG_ON(!card);
if (!mmc_select_hs400_strobe(card, ext_csd)) // 先尝试切换到hs400模式,并设置strobe
goto out;
if (!mmc_select_hs400(card, ext_csd)) // 尝试切换到hs400模式
goto out;
if (!mmc_select_hs200(card, ext_csd)) // 尝试切换到hs200模式
goto out;
if (!mmc_select_hsddr(card, ext_csd)) // 尝试切换到hsddr模式
goto out;
if (!mmc_select_hs(card, ext_csd)) // 尝试切换到hs模式
goto out;
// 上述都失败的情况下,还是处于legacy模式
mmc_set_clock(card->host, card->csd.max_dtr);
err = mmc_select_bus_width(card, 0, ext_csd);
out:
return err;
}
后面分别对mmc_select_hs、mmc_select_hsddr、mmc_select_hs200、mmc_select_hs400这几个函数进行说明
尝试设置总线模式为hs模式。
static int mmc_select_hs(struct mmc_card *card, u8 *ext_csd)
{
int err = 0;
struct mmc_host *host;
host = card->host;
// hs模式:host——》MMC_CAP_MMC_HIGHSPEED
// hs模式:emmc——》EXT_CSD_CARD_TYPE_52
if (!(host->caps & MMC_CAP_MMC_HIGHSPEED) || !(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_52)) {
err = -EOPNOTSUPP;
goto out;
}
// 切换emmc的总线速度模式
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1, card->ext_csd.generic_cmd6_time);
if (err && err != -EBADMSG)
goto out;
// 切换成功,后续emmc都使用hs模式
mmc_card_set_highspeed(card);
// 切换host的总线速度模式(时序)
mmc_set_timing(host, MMC_TIMING_MMC_HS);
// 设置时钟频率
mmc_set_clock(host, MMC_HIGH_52_MAX_DTR);
// 切换总线宽度
err = mmc_select_bus_width(card, 0, ext_csd);
out:
if (err && err != -EOPNOTSUPP)
pr_warning("%s: Switch to HighSpeed mode failed (err:%d)\n",
mmc_hostname(host), err);
return err;
}
注意,并没有为hs模式设置总线信号电压,所以其信号电压还是3V.
尝试设置总线模式为hsddr模式。
static int mmc_select_hsddr(struct mmc_card *card, u8 *ext_csd)
{
int ddr = 0, err = 0;
struct mmc_host *host;
host = card->host;
if (!(host->caps & MMC_CAP_HSDDR) || // 判断host是否支持hsddr模式
!(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_52)) { // 判断emmc是否支持hsddr模式
err = -EOPNOTSUPP;
goto out;
}
err = mmc_select_hs(card, ext_csd); // 先尝试设置为hs模式,对于emmc来说,hs_timing的值是一样的,都是EXT_CSD_HS_TIMING
if (err)
goto out;
mmc_card_clr_highspeed(card);
if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_8V)
&& ((host->caps & (MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50))
== (MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50)))
ddr = MMC_1_8V_DDR_MODE; // 判断是否要设置成ddr 1.8V模式
else if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_2V)
&& ((host->caps & (MMC_CAP_1_2V_DDR | MMC_CAP_UHS_DDR50))
== (MMC_CAP_1_2V_DDR | MMC_CAP_UHS_DDR50)))
ddr = MMC_1_2V_DDR_MODE; // 判断是否要设置成ddr 1.2V模式
err = mmc_select_bus_width(card, ddr, ext_csd); // 重新设置总线宽度,在这里面选择DDR模式的总线宽度模式
if (err)
goto out;
if (host->ios.bus_width == MMC_BUS_WIDTH_1) { // hsddr模式不支持1bit的总线宽度
pr_err("%s: failed to switch to wide bus\n",
mmc_hostname(host));
goto out;
}
if (ddr == MMC_1_2V_DDR_MODE) {
err = __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120); // 切换信号电压为1.2V
if (err)
goto out;
}
mmc_card_set_ddr_mode(card);
mmc_set_timing(host, MMC_TIMING_UHS_DDR50); // 设置host的总线速度模式为MMC_TIMING_UHS_DDR50
mmc_set_bus_width(host, host->ios.bus_width); // 设置host的总线宽度,其实在mmc_select_bus_width已经设置过一次了。
out:
if (err && err != -EOPNOTSUPP)
pr_warning("%s: Switch to HighSpeed DDR mode failed (err:%d)\n",
mmc_hostname(host), err);
return err;
}
尝试设置总线模式为hs200模式。
static int mmc_select_hs200(struct mmc_card *card, u8 *ext_csd)
{
int err = 0;
struct mmc_host *host;
host = card->host;
if (!(host->caps2 & MMC_CAP2_HS200) || // 判断host是否支持hs200模式
!(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_HS200)) { // 判断emmc是否支持hs200模式
err = -EOPNOTSUPP;
goto out;
}
// HS200只支持1.2V或者1.8V的信号电压
if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V && // 判断emmc是否支持hs200_1.2V模式
host->caps2 & MMC_CAP2_HS200_1_2V_SDR) // 判断host是否支持hs200_1.2V模式
if (__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120)) // 如果是的话切换信号电压到1.2V
err = __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); // 否则,切换信号电压到1.8V
// 这里很不能理解,如果不支持1.2V的话,连1.8V的信号电压都不用设置了???
// 根据实际结果来看,确实hs200模式下如果没有设置1.2V的话,确实也就没有设置成1.8V了,而是直接使用3V的信号电压
// 注意如下顺序:
/*
* For devices supporting HS200 mode, the bus width has
* to be set before executing the tuning function. If
* set before tuning, then device will respond with CRC
* errors for responses on CMD line. So for HS200 the
* sequence will be
* 1. set bus width 4bit / 8 bit (1 bit not supported) // 先设置总线宽度,hs200不支持1bit的总线宽度
* 2. switch to HS200 mode // 切换emmc和host的总线速度模式为hs200模式
* 3. set the clock to > 52Mhz <=200MHz and // 设置时钟,200MHZ以内
* 4. execute tuning for HS200 // hs200需要进行tuning操作获取一个合适的采样点
*/
err = mmc_select_bus_width(card, 0, ext_csd); // 设置总线宽度
/* switch to HS200 mode if bus width set successfully */
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 2, 0); // 切换emmc的总线速度模式
/*
* When HS200 activation is performed as part of HS400 selection
* set the timing appropriately
*/
if (mmc_card_hs400(card))
mmc_set_timing(host, MMC_TIMING_MMC_HS400);
else
mmc_set_timing(host, MMC_TIMING_MMC_HS200); // 设置host的总线速度模式(时序)
mmc_set_clock(host, MMC_HS200_MAX_DTR); // 设置时钟
if (host->ops->execute_tuning) { // 进行tuning操作
mmc_host_clk_hold(host);
err = host->ops->execute_tuning(host,
MMC_SEND_TUNING_BLOCK_HS200);
mmc_host_clk_release(host);
}
if (err) {
pr_warning("%s: tuning execution failed\n",
mmc_hostname(host));
goto out;
}
mmc_card_set_hs200(card); // 设置mmc_card状态为MMC_STATE_HIGHSPEED_200
out:
if (err && err != -EOPNOTSUPP)
pr_warning("%s: Switch to HS200 mode failed (err:%d)\n",
mmc_hostname(host), err);
return err;
}
尝试设置总线模式为hs400模式。
static int mmc_select_hs400(struct mmc_card *card, u8 *ext_csd)
{
int err = 0;
struct mmc_host *host;
host = card->host;
if (!(host->caps2 & MMC_CAP2_HS400) || // 判断host是否支持hs400模式
!(card->ext_csd.card_type & EXT_CSD_CARD_TYPE_HS400)) { // 判断emmc是否支持hs400模式
err = -EOPNOTSUPP;
goto out;
}
// 根据协议emmc 5.0,不能直接切换到hs400模式,而是需要结果如下步骤
/*
* eMMC5.0 spec doesn't allow switching to HS400 mode from
* HS200 mode directly. Hence follow these steps to switch
* to HS400 mode:
* Enable HS200 mode
* Enable HighSpeed mode (The clk should be low enough
* to enable HighSpeed mode) - HS_TIMING is 0x1
* Enable DDR mode (Set bus width to 8-bit DDR)
* Enable HS400 mode (Set HS_TIMING to 0x3 and change
* frequency to <= 200MHz)
* Perform tuning if required
*/
mmc_card_set_hs400(card);
err = mmc_select_hs200(card, ext_csd); // 先设置总线速度模式为hs200模式
mmc_card_clr_hs200(card);
if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_HS400_1_2V) // 判断emmc是否支持hs400_1.2v模式
&& (host->caps2 & MMC_CAP2_HS400_1_2V)) // 判断host是否支持hs400_1.2v模式
if (__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120)) // 如果是的话切换信号电压到1.2V
err = __mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180); // 否则,切换信号电压到1.8V
// 这里的疑问和前面说的一样
/*
* Lower the clock and adjust the timing to be able
* to switch to HighSpeed mode
*/
mmc_set_timing(host, MMC_TIMING_LEGACY); // 设置host总线速度模式为legacy模式
mmc_set_clock(host, MMC_HIGH_26_MAX_DTR); // 设置时钟为legacy的最大频率
/* Switch to 8-bit HighSpeed DDR mode */
err = mmc_select_hsddr(card, ext_csd); // 设置总线速度模式为hsddr模式
mmc_card_clr_ddr_mode(card);
/* Switch to HS400 mode if bus width set successfully */
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_HS_TIMING, 3, 0); // 设置card的总线速度模式为hs400模式
mmc_set_timing(host, MMC_TIMING_MMC_HS400); // 设置host的总线速度模式为hs400模式
mmc_set_clock(host, MMC_HS400_MAX_DTR); // 设置时钟频率
if (host->ops->execute_tuning) {
mmc_host_clk_hold(host);
err = host->ops->execute_tuning(host, MMC_SEND_TUNING_BLOCK_HS400); // 执行tuning操作
mmc_host_clk_release(host);
}
mmc_card_set_hs400(card); // 设置mmc_card状态为MMC_STATE_HIGHSPEED_400
out:
return err;
}