5. [mmc subsystem] mmc core(第五章)——card相關模塊(mmc type card)

来源:https://www.cnblogs.com/linhaostudy/archive/2019/05/05/10811290.html
-Advertisement-
Play Games

零、說明(重要,需要先搞清楚概念有助於後面的理解) 1、mmc core card相關模塊為對應card實現相應的操作,包括初始化操作、以及對應的匯流排操作集合。負責和對應card協議層相關的東西。 這裡先學習mmc type card。後續再學習sd type card。 對應代碼: 2、另外,這裡 ...


零、說明(重要,需要先搞清楚概念有助於後面的理解)

1、mmc core

card相關模塊為對應card實現相應的操作,包括初始化操作、以及對應的匯流排操作集合。負責和對應card協議層相關的東西。

這裡先學習mmc type card。後續再學習sd type card。

對應代碼:

drivers/mmc/core/mmc.c(提供介面), 
drivers/mmc/core/mmc-ops.c(提供和mmc type card協議相關的操作), 
drivers/mmc/core/mmc-ops.h

2、另外,這裡繼續強調一下mmc的概念

mmc core是指mmc subsystem的核心實現,這裡的mmc是表示mmc匯流排、介面、設備相關的一種統稱,可以理解為一種軟體架構。

而mmc type card則是指mmc卡或者emmc。

總之,這裡的mmc是兩種概念概念,需要自己先消化一下。

3、mmc匯流排和mmc_bus

在本文裡面這兩個是不同的概念。

mmc_bus是指mmc core抽象出來的虛擬匯流排,和mmc設備對應的硬體匯流排無關,是一種軟體概念。

而本文的mmc匯流排是一種物理概念,是實際的匯流排,是和host controller直接相關聯的。

一、API總覽

1、mmc type card匹配相關

  • mmc_attach_mmc

提供給mmc core主模塊使用,用於綁定card到host bus上(也就是card和host的綁定)。

通過mmc_host獲取mmc type card信息,初始化mmc_card,併進行部分驅動,最後將其註冊到mmc_bus上。

    原型:int mmc_attach_mmc(struct mmc_host *host)

2、mmc type card協議相關操作

  • mmc_ops提供了部分和mmc type

card協議相關操作,這些操作會在mmc.c中mmc的初始化過程中被使用到。

建議先簡單瞭解一下mmc協議的內容。後續會進行總結。

  • mmc_go_idle

發送CMD0指令,GO_IDLE_STATE

使mmc card進入idle state。

雖然進入到了Idle State,但是上電覆位過程並不一定完成了,這主要靠讀取OCR的busy位來判斷,而流程歸結為下一步。

  • mmc_send_op_cond

發送CMD1指令,SEND_OP_COND

這裡會設置card的工作電壓寄存器OCR,並且通過busy位(bit31)來判斷card的上電覆位過程是否完成,如果沒有完成的話需要重覆發送。
完成之後,mmc card進入ready state。

  • mmc_all_send_cid

這裡會發送CMD2指令,ALL_SEND_CID

廣播指令,使card回覆對應的CID寄存器的值。在這裡就相應獲得了CID寄存器的值了,存儲在cid中。

完成之後,MMC card會進入Identification State。

  • mmc_set_relative_addr

發送CMD3指令,SET_RELATIVE_ADDR

設置該mmc card的關聯地址為card->rca,也就是0x0001

完成之後,該MMC card進入standby模式。

  • mmc_send_csd

發送CMD9指令,MMC_SEND_CSD

要求mmc card發送csd寄存器,存儲到card->raw_csd中,也就是原始的csd寄存器的值。

此時mmc card還是處於standby state

  • mmc_select_card & mmc_deselect_cards

發送CMD7指令,SELECT/DESELECT CARD

選擇或者斷開指定的card

這時卡進入transfer state。後續可以通過各種指令進入到receive-data state或者sending-data state依次來進行數據的傳輸

  • mmc_get_ext_csd

發送CMD8指令,SEND_EXT_CSD

這裡要求處於transfer state的card發送ext_csd寄存器,這裡獲取之後存放在ext_csd寄存器中

這裡會使card進入sending-data state,完成之後又退出到transfer state。

  • mmc_switch

發送CMD6命令,MMC_SWITCH

用於設置ext_csd寄存器的某些bit

  • mmc_send_status

發送CMD13命令,MMC_SEND_STATUS

要求card發送自己當前的狀態寄存器

  • mmc_send_cid

發送CMD10命令,MMC_SEND_CID

要求mmc card回覆cid寄存器

  • mmc_card_sleepawake

發送CMD5命令,MMC_SLEEP_AWAKE

使card進入或者退出sleep state,由參數決定。關於sleep state是指card的一種狀態,具體參考emmc 5.1協議。

先結合協議理解上述幾個mmc type card的操作函數有助於理解後續mmc card的初始化代碼。具體參考第五節。

二、數據結構

1、mmc_ops & mmc_ops_unsafe

struct mmc_bus_ops表示mmc host在匯流排上的操作集合,由host的card 設備來決定,mmc type card、sd type card相應的操作集合是不一樣的。

mmc_ops和mmc_ops_unsafe則表示mmc type card所屬的host對於匯流排的操作集合。

static const struct mmc_bus_ops mmc_ops = {
    .awake = mmc_awake,  
    .sleep = mmc_sleep,     
    .remove = mmc_remove,   
    .detect = mmc_detect,
    .suspend = NULL,
    .resume = NULL,
    .power_restore = mmc_power_restore,
    .alive = mmc_alive,
    .change_bus_speed = mmc_change_bus_speed,
};

static const struct mmc_bus_ops mmc_ops_unsafe = {
    .awake = mmc_awake,    // 使mmc匯流排上的mmc type card退出sleep state
    .sleep = mmc_sleep,       // 使mmc匯流排的mmc type card進入sleep state
    .remove = mmc_remove,   // 釋放mmc type card
    .detect = mmc_detect,   // 檢測mmc匯流排的mmc type card是否拔出
    .suspend = mmc_suspend,   // suspend掉mmc匯流排上的mmc type card,註意不僅僅會使card進入sleep state,還會對clock以及mmc cache進行操作
    .resume = mmc_resume,   // resume上mmc匯流排上的mmc type card
    .power_restore = mmc_power_restore,   // 恢復mmc匯流排上的mmc type card的電源狀態
    .alive = mmc_alive,   // 檢測mmc匯流排上的mmc type card狀態是否正常
    .change_bus_speed = mmc_change_bus_speed,   // 修改mmc匯流排時鐘頻率
};

mmc_ops_unsafe和mmc_ops的區別在於是否實現suspend和resume方法。

對於card不可移除的host來說,需要使用mmc_ops_unsafe這個mmc_bus_ops來支持suspend和resume。

之所以在上述註釋中不斷說明mmc匯流排,是為了強調應該和mmc_bus虛擬匯流排區分開來,這裡的mmc匯流排是物理概念、是和host controller直接相關聯的。

2、mmc_type

struct device_type mmc_type中為mmc_card定義了很多屬性,可以在sysfs中進行查看。

/sys/class/mmc_host/mmc0/mmc0:0001
或者/sys/bus/mmc/devices/mmc0:0001下可以查看到如下屬性
block    cid   csd   date   driver   enhanced_area_offset   enhanced_area_size   erase_size   fwrev   hwrev
manfid    name   oemid   power   preferred_erase_size   prv   raw_rpmb_size_mult   rel_sectors
runtime_pm_timeout    serial   subsystem   type   uevent

mmc_type對應實現如下:

static struct device_type mmc_type = {
    .groups = mmc_attr_groups,
};

static const struct attribute_group *mmc_attr_groups[] = {
    &mmc_std_attr_group,
    NULL,
};

static struct attribute_group mmc_std_attr_group = {
    .attrs = mmc_std_attrs,
};

MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
    card->raw_cid[2], card->raw_cid[3]);
MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
    card->raw_csd[2], card->raw_csd[3]);
//...................略過一些
MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult);
MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors);

static struct attribute *mmc_std_attrs[] = {
    &dev_attr_cid.attr,
    &dev_attr_csd.attr,
    &dev_attr_date.attr,
    &dev_attr_erase_size.attr,
    &dev_attr_preferred_erase_size.attr,
    &dev_attr_fwrev.attr,
    &dev_attr_hwrev.attr,
//.....................略過一些
    &dev_attr_rel_sectors.attr,
    NULL,
};

補充說明,可以發現這些信息都是從mmc_card的cid寄存器和ext_csd寄存器中讀取的。

三、介面代碼說明

1、mmc_attach_mmc實現

用於通過mmc_host獲取mmc type card信息,初始化mmc_card,併進行部分驅動,最後將其註冊到mmc_bus上。

  • 主要工作:

    • 設置匯流排模式

    • 選擇一個card和host都支持的最低工作電壓

    • 對於不同type的card,相應mmc匯流排上的操作協議也可能有所不同。所以需要設置相應的匯流排操作集合(mmc_host->bus_ops)

    • 初始化card使其進入工作狀態(mmc_init_card)

    • 為card構造對應的mmc_card並且註冊到mmc_bus中(mmc_add_card,具體參考《mmc core——bus模塊說明》)

代碼如下:

int mmc_attach_mmc(struct mmc_host *host)
{
    int err;
    u32 ocr;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    /* Set correct bus mode for MMC before attempting attach */
/* 在嘗試匹配之前,先設置正確的匯流排模式 */
    if (!mmc_host_is_spi(host))
        mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);

/* 獲取card的ocr寄存器 */
    err = mmc_send_op_cond(host, 0, &ocr);
        // 發送CMD1命令(MMC_SEND_OP_COND),並且參數為0
        // 這裡獲取OCR(Operation condition register)32位的OCR包含卡設備支持的工作電壓表,存儲到ocr變數中
        // 如果Host的IO電壓可調整,那調整前需要讀取OCR。為了不使卡誤進入Inactive State,可以給MMC卡發送不帶參數的CMD1,這樣可以僅獲取OCR寄存器,而不會改變卡的狀態。

/* 對於不同type的card,相應mmc匯流排上的操作協議也可能有所不同 */
/* 所以這裡設置mmc_host的匯流排操作集合,為mmc_ops_unsafe或者mmc_ops,上述已經說明 */
    mmc_attach_bus_ops(host);
        // 設置host->bus_ops,也就是會為host的bus選擇一個操作集,對於non-removable的host來說,這裡對應應該為mmc_ops_unsafe

/* 為card選擇一個HOST和card都支持的最低電壓 */
    if (host->ocr_avail_mmc)
        host->ocr_avail = host->ocr_avail_mmc; // 選擇mmc的可用ocr值作為host的ocr_avail值

    if (ocr & 0x7F) {
        ocr &= ~0x7F; // 在標準MMC協議中,OCR寄存器的bit6-0位是屬於保留位,並不會使用,所以這裡對應將其清零
    }
    host->ocr = mmc_select_voltage(host, ocr); // 通過OCR寄存器選擇一個HOST和card都支持的最低電壓

/* 調用mmc_init_card初始化該mmc type card,這裡是核心函數,後續會繼續說明 */
    err = mmc_init_card(host, host->ocr, NULL);   // 初始化該mmc type card,併為其分配和初始化一個對應的mmc_card
    if (err)
        goto err;

/* 將分配到的mmc_card註冊到mmc_bus中 */
    mmc_release_host(host);   // 先釋放掉host,可能是在mmc_add_card中會獲取這個host
    err = mmc_add_card(host->card);   
        // 調用到mmc_add_card,將card註冊到設備驅動模型中。
        // 這時候該mmc_card就掛在了mmc_bus上,會和mmc_bus上的block這類mmc driver匹配起來。具體再學習mmc card driver的時候再說明。
    mmc_claim_host(host);   // 再次申請host
    if (err)
        goto remove_card;

/* clock scaling相關的東西,這裡暫時先不關心 */
    mmc_init_clk_scaling(host);

    register_reboot_notifier(&host->card->reboot_notify);

    return 0;

remove_card:
    mmc_release_host(host);
    mmc_remove_card(host->card);
    mmc_claim_host(host);
    host->card = NULL;
err:
    mmc_detach_bus(host);

    pr_err("%s: error %d whilst initialising MMC card\n",
        mmc_hostname(host), err);

    return err;
}

重點說明

(1)在attach過程中,有一個很重要的函數mmc_init_card,第四節就要圍繞這個函數進行展開。

(2)調用了mmc_add_card之後mmc_card就掛在了mmc_bus上,會和mmc_bus上的block(mmc_driver)匹配起來。相應block(mmc_driver)就會進行probe,驅動card,實現card的實際功能(也就是存儲設備的功能)。會對接到塊設備子系統中。具體在學習mmc card driver的時候再說明。

四、mmc type card內部核心代碼說明

1、mmc_init_card

在第三節中,可以看出mmc_attach_mmc中的一個核心函數就是mmc_init_card,用於對mmc type card進行實質性的初始化,併為其分配和初始化一個對應的mmc_card。這部分和協議相關,需要先學習一下mmc協議。

  • 主要工作
    • 根據協議初始化mmc type card,使其進入相應狀態(standby state)
    • 為mmc type card構造對應mmc_card併進行設置
    • 從card的csd寄存器以及ext_csd寄存器獲取card信息並設置到mmc_card的相應成員中
    • 根據host屬性以及一些需求修改ext_csd寄存器的值
    • 設置mmc匯流排時鐘頻率以及位寬
  • 代碼如下
static int mmc_init_card(struct mmc_host *host, u32 ocr,
    struct mmc_card *oldcard)
{
// struct mmc_host *host:該mmc card使用的host
// ocr:表示了host要使用的電壓,在mmc_attach_mmc中,已經得到了一個HOST和card都支持的最低電壓  struct mmc_card *card;
    int err = 0;
    u32 cid[4];
    u32 rocr;
    u8 *ext_csd = NULL;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    /* Set correct bus mode for MMC before attempting init */
    if (!mmc_host_is_spi(host))
        mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);  // 設置匯流排模式為開漏模式

/* 根據mmc協議從mmc匯流排上選中一張card(協議的初始化流程) */
    mmc_go_idle(host);
        // 發送CMD0指令,GO_IDLE_STATE
        // 使mmc card進入idle state。
        // 雖然進入到了Idle State,但是上電覆位過程並不一定完成了,這主要靠讀取OCR的busy位來判斷,而流程歸結為下一步。

    /* The extra bit indicates that we support high capacity */
    err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
        // 發送CMD1指令,SEND_OP_COND
        // 這裡會設置card的工作電壓寄存器OCR,並且通過busy位(bit31)來判斷card的上電覆位過程是否完成,如果沒有完成的話需要重覆發送。
        // 完成之後,mmc card進入ready state。

    /*
     * Fetch CID from card.
     */
    if (mmc_host_is_spi(host))
        err = mmc_send_cid(host, cid);
    else
        err = mmc_all_send_cid(host, cid);
                // 這裡會發送CMD2指令,ALL_SEND_CID
                // 廣播指令,使card回覆對應的CID寄存器的值。在這裡就相應獲得了CID寄存器的值了,存儲在cid中。
                // 完成之後,MMC card會進入Identification State。

    if (oldcard) {
。。。
    } else {
/* 調用mmc_alloc_card分配一個mmc_card併進行部分設置 */
        card = mmc_alloc_card(host, &mmc_type); 
                // 為card配分一個struct mmc_card結構體併進行初始化,在mmc_type中為mmc定義了大量的屬性。
                // 具體參考“《mmc core——bus模塊說明》——》mmc_alloc_card”
        card->type = MMC_TYPE_MMC; // 設置card的type為MMC_TYPE_MMC
        card->rca = 1;  // 設置card的RCA地址為1
        memcpy(card->raw_cid, cid, sizeof(card->raw_cid));      // 將讀到的CID存儲到card->raw_cid,也就是原始CID值中
        card->reboot_notify.notifier_call = mmc_reboot_notify;
        host->card = card;      // 將mmc_card和mmc_host 進行關聯
    }


/* 設置card RCA地址 */
    if (!mmc_host_is_spi(host)) {
        err = mmc_set_relative_addr(card);
                // 發送CMD3指令,SET_RELATIVE_ADDR
                // 設置該mmc card的關聯地址為card->rca,也就是0x0001
                // 完成之後,該MMC card進入standby模式。

        mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
                // 設置匯流排模式為MMC_BUSMODE_PUSHPULL
    }

/* 從card的csd寄存器以及ext_csd寄存器獲取信息並設置到mmc_card的相應成員中 */
    if (!oldcard) {
        /*
         * Fetch CSD from card.
         */
        err = mmc_send_csd(card, card->raw_csd);
                // 發送CMD9指令,MMC_SEND_CSD
                // 要求mmc card發送csd寄存器,存儲到card->raw_csd中,也就是原始的csd寄存器的值。
                // 此時mmc card還是處於standby state

        err = mmc_decode_csd(card);
                // 解析raw_csd,獲取到各個bit的值並設置到card->csd中的相應成員上

        err = mmc_decode_cid(card);
                // 解析raw_cid,獲取到各個bit的值並設置到card->cid中的相應成員上
    }

    /*
     * Select card, as all following commands rely on that.
     */
    if (!mmc_host_is_spi(host)) {
        err = mmc_select_card(card);
                // 發送CMD7指令,SELECT/DESELECT CARD
                // 選擇或者斷開指定的card
                // 這時卡進入transfer state。後續可以通過各種指令進入到receive-data state或者sending-data state依次來進行數據的傳輸
    }

    if (!oldcard) {
        err = mmc_get_ext_csd(card, &ext_csd);
                // 發送CMD8指令,SEND_EXT_CSD
                // 這裡要求處於transfer state的card發送ext_csd寄存器,這裡獲取之後存放在ext_csd寄存器中
                // 這裡會使card進入sending-data state,完成之後又退出到transfer state。

        card->cached_ext_csd = ext_csd;    // 將ext_csd原始值存儲到card->cached_ext_csd,表示用來保存ext_csd的一塊緩存,可能還沒有和card的ext_csd同步
        err = mmc_read_ext_csd(card, ext_csd);  // 解析ext_csd的值,獲取到各個bit的值並設置到card->ext_csd中的相應成員上

        if (!(mmc_card_blockaddr(card)) && (rocr & (1<<30)))
            mmc_card_set_blockaddr(card);

        /* Erase size depends on CSD and Extended CSD */
        mmc_set_erase_size(card);  // 設置card的erase_size,扇區裡面的擦除位元組數,讀出來是512K

        if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR))
            mmc_card_set_blockaddr(card);
    }

/* 根據host屬性以及一些需求修改ext_csd寄存器的值 */
    /*
     * If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF
     * bit.  This bit will be lost every time after a reset or power off.
     */
    if (card->ext_csd.enhanced_area_en ||
        (card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                 EXT_CSD_ERASE_GROUP_DEF, 1,
                 card->ext_csd.generic_cmd6_time);
                // 發送CMD6命令,MMC_SWITCH
                // 用於設置ext_csd寄存器的某些bit
                // 當enhanced_area_en 被設置的時候,host需要去設置ext_csd寄存器中的EXT_CSD_ERASE_GROUP_DEF位為1
    }

    if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
        card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
                 card->ext_csd.part_config,
                 card->ext_csd.part_time);
                // 發送CMD6命令,MMC_SWITCH
                // 用於設置ext_csd寄存器的某些bit
                // 設置ext_csd寄存器中的EXT_CSD_CMD_SET_NORMAL位為EXT_CSD_PART_CONFIG
        card->part_curr = card->ext_csd.part_config &
                  EXT_CSD_PART_CONFIG_ACC_MASK;
    }

    if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&
        (card->ext_csd.rev >= 6)) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                 EXT_CSD_POWER_OFF_NOTIFICATION,
                 EXT_CSD_POWER_ON,
                 card->ext_csd.generic_cmd6_time);
                 // 發送CMD6命令,MMC_SWITCH
                // 用於設置ext_csd寄存器的某些bit
                // 設置ext_csd寄存器中的EXT_CSD_POWER_OFF_NOTIFICATION位為EXT_CSD_POWER_ON
    }

/* 設置mmc匯流排時鐘頻率以及位寬 */
    err = mmc_select_bus_speed(card, ext_csd); // 激活host和card都支持的最大匯流排速度
        //.........這裡過濾掉一些設置ext_csd的代碼
    if (!oldcard) {

        if (card->ext_csd.bkops_en) {
            INIT_DELAYED_WORK(&card->bkops_info.dw,
                      mmc_start_idle_time_bkops);
                        // 如果emmc支持bkops的話,就初始化card->bkops_info.dw工作為mmc_start_idle_time_bkops
        }
    }

    return 0;
}

後續會有一篇文章《結合log分析emmc初始化過程中的命令流程》來說明上述mmc_init_card的實際流程。

2、mmc_ops_unsafe相關函數實現

選擇幾個重點的進行說明:

static const struct mmc_bus_ops mmc_ops_unsafe = {
    .awake = mmc_awake,    // 使mmc匯流排上的mmc type card退出sleep state
    .sleep = mmc_sleep,       // 使mmc匯流排的mmc type card進入sleep state
    .remove = mmc_remove,   // 釋放mmc type card
    .detect = mmc_detect,   // 檢測mmc匯流排的mmc type card是否拔出
    .suspend = mmc_suspend,   // suspend掉mmc匯流排上的mmc type card,註意不僅僅會使card進入sleep state,還會對clock以及mmc cache進行操作
    .resume = mmc_resume,   // resume上mmc匯流排上的mmc type card
    .power_restore = mmc_power_restore,   // 恢復mmc匯流排上的mmc type card的電源狀態
    .alive = mmc_alive,   // 檢測mmc匯流排上的mmc type card狀態是否正常
    .change_bus_speed = mmc_change_bus_speed,   // 修改mmc匯流排時鐘頻率
};


/**********************使mmc匯流排上的mmc type card退出sleep state************************/
static int mmc_awake(struct mmc_host *host)
{
        //...
    if (card && card->ext_csd.rev >= 3) {   // 判斷版本是否大於3
        err = mmc_card_sleepawake(host, 0);   
                // 發送CMD5指令,MMC_SLEEP_AWAKE,參數為0,表示退出sleep state.(如果參數為1就是進入sleep state)
                // 完成之後,該MMC card從sleep state進入standby模式。
    }
        //...
}

/**********************檢測mmc匯流排的mmc type card是否拔出************************/
static void mmc_detect(struct mmc_host *host)
{
    int err;
    mmc_rpm_hold(host, &host->card->dev);
    mmc_claim_host(host);

/* 檢測card是否被拔出 */
    err = _mmc_detect_card_removed(host);

    mmc_release_host(host);

    /*
     * if detect fails, the device would be removed anyway;
     * the rpm framework would mark the device state suspended.
     */
/* card並沒有被拔出,說明出現異常了,標記card的rpm狀態為suspend */
    if (!err)
        mmc_rpm_release(host, &host->card->dev);

/* card確實被拔出,正常釋放card */
    if (err) {
        mmc_remove(host);

        mmc_claim_host(host);
        mmc_detach_bus(host);
        mmc_power_off(host);
        mmc_release_host(host);
    }
}

/********************** 修改mmc匯流排時鐘頻率************************/
/**
 * mmc_change_bus_speed() - Change MMC card bus frequency at runtime
 * @host: pointer to mmc host structure
 * @freq: pointer to desired frequency to be set
 *
 * Change the MMC card bus frequency at runtime after the card is
 * initialized. Callers are expected to make sure of the card's
 * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime.
 */
static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq)
{
    int err = 0;
    struct mmc_card *card;

    mmc_claim_host(host);
    /*
     * Assign card pointer after claiming host to avoid race
     * conditions that may arise during removal of the card.
     */
    card = host->card;

    if (!card || !freq) {
        err = -EINVAL;
        goto out;
    }

/* 確定出一個可用頻率 */
    if (mmc_card_highspeed(card) || mmc_card_hs200(card)
            || mmc_card_ddr_mode(card)
            || mmc_card_hs400(card)) {
        if (*freq > card->ext_csd.hs_max_dtr)
            *freq = card->ext_csd.hs_max_dtr;
    } else if (*freq > card->csd.max_dtr) {
        *freq = card->csd.max_dtr;
    }

    if (*freq < host->f_min)
        *freq = host->f_min;

/* 根據實際要設置的頻率值來設置時鐘 */
    if (mmc_card_hs400(card)) {
        err = mmc_set_clock_bus_speed(card, *freq);
        if (err)
            goto out;
    } else {
        mmc_set_clock(host, (unsigned int) (*freq));
    }

  /* 對於hs200來說,修改完頻率之後需要執行execute_tuning來選擇一個合適的採樣點 */
    if (mmc_card_hs200(card) && card->host->ops->execute_tuning) {
        /*
         * We try to probe host driver for tuning for any
         * frequency, it is host driver responsibility to
         * perform actual tuning only when required.
         */
        mmc_host_clk_hold(card->host);
        err = card->host->ops->execute_tuning(card->host,
                MMC_SEND_TUNING_BLOCK_HS200);
        mmc_host_clk_release(card->host);

        if (err) {
            pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n",
                   mmc_hostname(card->host), __func__, err,
                   host->clk_scaling.curr_freq);
            mmc_set_clock(host, host->clk_scaling.curr_freq);   // 採樣失敗,設置回原來的時鐘頻率
        }
    }
out:
    mmc_release_host(host);
    return err;
}

五、mmc ops介面說明

1、說明

  • mmc_ops提供了部分和mmc type card協議相關操作,這些操作會在mmc.c中mmc的初始化過程中被使用到。
  • 這些操作都會發起mmc請求,因此會調用mmc core主模塊的mmc請求API,會在mmc core中進行說明。
  • 建議先簡單瞭解一下mmc協議的內容。後續會進行總結。

2、代碼說明

以下說明比較典型和比較特殊的介面

  • mmc_send_status(典型)

發送CMD13命令,MMC_SEND_STATUS

要求card發送自己當前的狀態寄存器

int mmc_send_status(struct mmc_card *card, u32 *status)
{
    int err;
    struct mmc_command cmd = {0};

    BUG_ON(!card);
    BUG_ON(!card->host);

/* 主要是根據對應命令構造struct mmc_command */
    cmd.opcode = MMC_SEND_STATUS;   // 設置命令操作碼opcode,這裡設置為MMC_SEND_STATUS,也就是CMD13
    if (!mmc_host_is_spi(card->host))
        cmd.arg = card->rca << 16;   // 設置命令的對應參數,這裡設置為card的RCA地址
    cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;   // 設置請求的一些標識,包括命令類型,response類型等等

/* 調用mmc_wait_for_cmd發送命令請求並且等待命令處理完成。 */
    err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    if (err)
        return err;

    /* NOTE: callers are required to understand the difference
     * between "native" and SPI format status words!
     */
/* 對response的處理 */
    if (status)
        *status = cmd.resp[0];      // 依照協議,response[0]存儲了status,因此這裡提取cmd.resp[0]

    return 0;
}
  • mmc_send_op_cond(特殊)

發送CMD1指令,SEND_OP_COND

在idle狀態時,向卡傳送Host支持的電壓範圍,卡回覆OCR的值以及上電覆位的狀態。如果發送的電壓參數為0,則卡僅傳回OCR的值,並不進行判斷。如果發送的電壓參數存在,則和卡本身的OCR對比,若不符合,則卡進入Inactive State,符合,則返回OCR寄存器的值。

其實和典型的介面類似,但是其特殊之處在於需要通過busy位(bit31)來判斷card的上電覆位過程是否完成,如果沒有完成的話需要重覆發送。

int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
    struct mmc_command cmd = {0};
    int i, err = 0;

    BUG_ON(!host);

/* 主要是根據對應命令構造struct mmc_command */
    cmd.opcode = MMC_SEND_OP_COND;
    cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
    cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;

/* 需要判斷status的busy(bit31)來判斷上電覆位是否完成,如果沒有完成的話需要重覆發送。 */
    for (i = 100; i; i--) {
        err = mmc_wait_for_cmd(host, &cmd, 0);
        if (err)
            break;

        /* if we're just probing, do a single pass */
        if (ocr == 0)   // ocr為0,說明只是讀取ocr寄存器的值,不進行判斷
            break;

        /* otherwise wait until reset completes */
        if (mmc_host_is_spi(host)) {
            if (!(cmd.resp[0] & R1_SPI_IDLE))
                break;
        } else {
            if (cmd.resp[0] & MMC_CARD_BUSY)
                break;
                        // 如果發送的電壓參數存在,則和卡本身的OCR對比,若不符合,則卡進入Inactive State,符合,則返回OCR寄存器的值。
                        // 同時,需要判斷OCR寄存器的busy位來判斷上電覆位是否完成。
        }

        err = -ETIMEDOUT;

        mmc_delay(10);
    }

    if (rocr && !mmc_host_is_spi(host))
        *rocr = cmd.resp[0];

    return err;
}
  • mmc_send_ext_csd

發送CMD8指令,SEND_EXT_CSD

這裡要求處於transfer state的card發送ext_csd寄存器,這裡獲取之後存放在ext_csd寄存器中

這裡會使card進入sending-data state,完成之後又退出到transfer state。

特殊之處在於涉及到了DATA線上的數據傳輸,會調用到內部介面mmc_send_cxd_data。

如下:

int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
        unsigned int timeout_ms)
{
    return __mmc_switch(card, set, index, value, timeout_ms, true, false);
}

int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
         unsigned int timeout_ms, bool use_busy_signal,
         bool ignore_timeout)
{
    int err;
    struct mmc_command cmd = {0};
    unsigned long timeout;
    u32 status;

    BUG_ON(!card);
    BUG_ON(!card->host);

/* 主要是根據對應命令構造struct mmc_command */
    cmd.opcode = MMC_SWITCH;
    cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
          (index << 16) |
          (value << 8) |
          set;
    cmd.flags = MMC_CMD_AC;
    if (use_busy_signal)
        cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
    else
        cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
    cmd.cmd_timeout_ms = timeout_ms;
    cmd.ignore_timeout = ignore_timeout;


/* 調用mmc_wait_for_cmd發送命令請求並且等待命令處理完成。 */
    err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    if (err)
        return err;

    /* No need to check card status in case of unblocking command */
    if (!use_busy_signal)
        return 0;

/* 調用mmc_send_status發送CMD13獲取card status,等待card退出programming state。 */
    timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
    do {
        err = mmc_send_status(card, &status);
        if (err)
            return err;
        if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
            break;
        if (mmc_host_is_spi(card->host))
            break;

        /* Timeout if the device never leaves the program state. */
        if (time_after(jiffies, timeout)) {
            pr_err("%s: Card stuck in programming state! %s\n",
                mmc_hostname(card->host), __func__);
            return -ETIMEDOUT;
        }
    } while (R1_CURRENT_STATE(status) == R1_STATE_PRG);

    if (mmc_host_is_spi(card->host)) {
        if (status & R1_SPI_ILLEGAL_COMMAND)
            return -EBADMSG;
    } else {
        if (status & 0xFDFFA000)
            pr_warning("%s: unexpected status %#x after "
                   "switch", mmc_hostname(card->host), status);
        if (status & R1_SWITCH_ERROR)
            return -EBADMSG;
    }

    return 0;
}

後續會有一篇文章《結合log分析emmc初始化過程中的命令流程》來說明上述mmc_init_card的實際流程。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • @ "TOC" quickLook插件是Mac上的快速瀏覽的一個功能,現在win10系統上也能安裝插件,這個插件可以快速瀏覽txt,doc,圖片,表格等文件如下圖: 我認為最方便的地方是你需要查看某一個文件內容不需要編輯的時候,又不想用wps打開時就可以用這個簡單的插件。 安裝方式也很簡單,但是需要 ...
  • 一、echo 1.顯示普通字元串: 這裡的雙引號可以省略。 2.顯示轉義字元: 3.顯示變數: read 命令從標準輸入中讀取一行,並把輸入行的每個欄位的值指定給 shell 變數 輸出: 4.顯示換行: 輸出: 5.顯示不換行: 輸出: 6.顯示定向至文件: 輸出: 7.原樣輸出字元串,不進行轉義 ...
  • [20190505]ts 命令在哪裡.txt--//在論壇問一下ts命令在哪裡?沒人解答,自己也google看了一下:https://unix.stackexchange.com/questions/272433/piping-into-moreutils-ts-with-nanosecond-pr ...
  • 一、sdhci core說明 1、sdhci說明 具體參考《host(第一章)——概述》 SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的設計標準,其寄存器偏移以及意義都有一定的規範,並且提供了對應的驅動程式,方便vendor進行host ...
  • 由於linux最小單位為分,但是很多需求上需要按秒執行,如30秒請求一個URL地址之類的,思路很簡單就是修改計劃任務腳本用迴圈控制,代碼如下: 上述代碼中XXXXXX為你需要執行的URL地址 以上示例以寶塔下計劃任務為基礎所演示 ...
  • 一、前言 1、簡介 在上一篇UART詳解中,已經有了關於UART的詳細介紹了,也有關於如何使用STM32CubeMX來配置UART的操作了,而在該篇博客,主要會講解一下如何實現UART串口的發送功能。 2、UART簡介 嵌入式開發中,UART串口通信協議是我們常用的通信協議之一,全稱叫做通用非同步收發 ...
  • 一、host簡單說明 host,也可以理解為host controller,是指mmc匯流排上的主機端,mmc匯流排的控制器,每個host controller對應一條mmc匯流排。 host controller會控制命令線、數據線和時鐘線,從而實現mmc匯流排上的通訊。 上層發送mmc請求時,就是通過h ...
  • 一、說明 1、mmc core概述 mmc core主模塊是mmc core的實現核心。也是本章的重點內容。 對應代碼位置 。 其主要負責如下功能: mmc core初始化,包括註冊mmc bus、mm host class等等 mmc host的管理和維護,包括為其他模塊提供mmc_host的操作 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...