MCUX on RT1050 - MMC stack won't initialize

cancel
Showing results for 
Search instead for 
Did you mean: 

MCUX on RT1050 - MMC stack won't initialize

Jump to solution
1,985 Views
Senior Contributor I

TL;DR -- When starting the MMC stack by calling mmc_disk_initialize() which calls MMC_Init(), a possibly incorrect switch() statement causes a subroutine to generate an assert().  (EDIT 9/16: The assert() occurs when the extended CSD data comes back all zeroes, and this occurs when a cacheable memory area, i.e. not SRAM_DTC, is used to store global data.  See discussion below.)

So I'm trying to get an eMMC device (ISSI IS21ES04G) to work on an RT1050-based board.  We have the device attached to GPIO_SD_B1_00-06 (USDHC2) in 4-bit mode.  Here's the schematic:

emmc_schematic.png

Since there's no SDK 2.6.1 example for an eMMC device, I instead took some files from the "fatfs_usdcard" example, cut out the SD-specific files, and imported MMC-specific source files directly from the SDK.  Here's what I have:

sdk_mmc_files.png

Of note, I deleted fsl_sd_disk.* and replaced them with fsl_mmc_disk.*.  I also customized my SDHC-specific board.h information:

/* SD/MMC configuration. */
#define BOARD_USDHC1_BASEADDR USDHC1
#define BOARD_USDHC2_BASEADDR USDHC2
/* EVKB uses PLL2_PFD2 (166 MHz) to run USDHC, so its divider is 1. On the
 * MCU, we run PLL2_PFD0 at 396 MHz, so its divider is 2, yielding 198 MHz. */
#define BOARD_USDHC1_CLK_FREQ (CLOCK_GetSysPfdFreq(kCLOCK_Pfd0) / (CLOCK_GetDiv(kCLOCK_Usdhc1Div) + 1U))
#define BOARD_USDHC2_CLK_FREQ (CLOCK_GetSysPfdFreq(kCLOCK_Pfd0) / (CLOCK_GetDiv(kCLOCK_Usdhc2Div) + 1U))
#define BOARD_USDHC1_IRQ USDHC1_IRQn
#define BOARD_USDHC2_IRQ USDHC2_IRQn

/* eMMC is always present. */
#define BOARD_USDHC_CARD_INSERT_CD_LEVEL (1U)
#define BOARD_USDHC_CD_STATUS() (BOARD_USDHC_CARD_INSERT_CD_LEVEL)
#define BOARD_USDHC_CD_GPIO_INIT() do { } while (0)

/* eMMC is always powered. */
#define BOARD_USDHC_SDCARD_POWER_CONTROL_INIT() do { } while (0)
#define BOARD_USDHC_SDCARD_POWER_CONTROL(state) do { } while (0)
#define BOARD_USDHC_MMCCARD_POWER_CONTROL_INIT() do { } while (0)
#define BOARD_USDHC_MMCCARD_POWER_CONTROL(state) do { } while (0)

/* Our device is on USDHC2 for Rev. A/B, and on USDHC1 for Rev. C. */
#define BOARD_MMC_HOST_BASEADDR BOARD_USDHC2_BASEADDR
#define BOARD_MMC_HOST_CLK_FREQ BOARD_USDHC2_CLK_FREQ
#define BOARD_MMC_HOST_IRQ BOARD_USDHC2_IRQ

#define BOARD_SD_HOST_BASEADDR BOARD_USDHC2_BASEADDR
#define BOARD_SD_HOST_CLK_FREQ BOARD_USDHC2_CLK_FREQ
#define BOARD_SD_HOST_IRQ BOARD_USDHC2_IRQ

#define BOARD_MMC_VCCQ_SUPPLY kMMC_VoltageWindow170to195
#define BOARD_MMC_VCC_SUPPLY kMMC_VoltageWindows270to360

/* Define these to indicate we don't support 1.8V or 8-bit data bus. */
#define BOARD_SD_SUPPORT_180V SDMMCHOST_NOT_SUPPORT
#define BOARD_MMC_SUPPORT_8BIT_BUS SDMMCHOST_NOT_SUPPORT‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In my test function, I initialize the MMC stack:

SDMMCHOST_SET_IRQ_PRIORITY(BOARD_MMC_HOST_IRQ, 11);
 DSTATUS dstatus = mmc_disk_initialize(MMCDISK);
 if (RES_OK != dstatus) {
 printf("mmc_disk_initialize() failed\n");
 return -1;
 }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

When I trace into mmc_disk_initialize(), everything seems OK at first.  It correctly sets the host base address (USDHC2) and source clock rate (198 MHz... I use PLL2_PFD0 @ 396 MHz divided by 2 to drive the USDHC modules), then calls MMC_Init().  MMC_HostInit() passes fine, then MMC_PowerOnCard(), then MMC_PowerOffCard().  Then it calls MMC_CardInit(), and that goes a fair distance.  It sets the 400 kHz clock OK, gets the host capabilities, tells the card to go idle, gets the card CID, sets the relative address, sets max frequency, puts the card in transfer state, gets extended CSD register content, and sets the block size.  Then it calls this:

/* switch to host support speed mode, then switch MMC data bus width and select power class */
 if (kStatus_Success != MMC_SelectBusTiming(card))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The guts of the function:

static status_t MMC_SelectBusTiming(mmc_card_t *card)
{
 assert(card);
 mmc_high_speed_timing_t targetTiming = card->busTiming;
 switch (targetTiming)
 {
 case kMMC_HighSpeedTimingNone:
 case kMMC_HighSpeed400Timing:
 if ((card->flags & (kMMC_SupportHS400DDR200MHZ180VFlag | kMMC_SupportHS400DDR200MHZ120VFlag)) && ((kSDMMCHOST_SupportHS400 != SDMMCHOST_NOT_SUPPORT)))
 {
 /* switch to HS200 perform tuning */
 if (kStatus_Success != MMC_SwitchToHS200(card, SDMMCHOST_SUPPORT_HS400_FREQ / 2U))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }
 /* switch to HS400 */
 if (kStatus_Success != MMC_SwitchToHS400(card))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }
 break;
 }
 case kMMC_HighSpeed200Timing:
 if ((card->flags & (kMMC_SupportHS200200MHZ180VFlag | kMMC_SupportHS200200MHZ120VFlag)) && ((kSDMMCHOST_SupportHS200 != SDMMCHOST_NOT_SUPPORT)))
 {
 if (kStatus_Success != MMC_SwitchToHS200(card, SDMMCHOST_SUPPORT_HS200_FREQ))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }
 break;
 }
 case kMMC_HighSpeedTiming:
 if (kStatus_Success != MMC_SwitchToHighSpeed(card))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }
 break;

 default:
 card->busTiming = kMMC_HighSpeedTimingNone;
 }
 return kStatus_Success;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

When I step into this function, card->busTiming is 0 (kMMC_HighSpeedTimingNone) and card->flags is 0x100 (kMMC_SupportHighCapacityFlag).  According to the logic of this function, because our timing is None, we go through all of the switch() cases.  In this application, kSDMMCHOST_SupportHS400 is set to SDMMCHOST_NOT_SUPPORT, so the first branch (HS400) is optimized out.  The second branch (HS200) is evaluated, but because neither of the HS200 flags are set, we skip it.  Then we fall through into the kMMC_HighSpeedTiming branch, and we call MMC_SwitchToHighSpeed().  This is where things go wrong.

Here is the relevant code in MMC_SwitchToHighSpeed():

static status_t MMC_SwitchToHighSpeed(mmc_card_t *card)
{
 assert(card);
 uint32_t freq = 0U;
 /* check VCCQ voltage supply */
 [...]

 if (kStatus_Success != MMC_SwitchHSTiming(card, kMMC_HighSpeedTiming, kMMC_DriverStrength0))
 {
 return kStatus_SDMMC_SwitchBusTimingFailed;
 }

 if ((card->busWidth == kMMC_DataBusWidth4bitDDR) || (card->busWidth == kMMC_DataBusWidth8bitDDR))
 {
 freq = MMC_CLOCK_DDR52;
 SDMMCHOST_ENABLE_DDR_MODE(card->host.base, true, 0U);
 }
 else if (card->flags & kMMC_SupportHighSpeed52MHZFlag)
 {
 freq = MMC_CLOCK_52MHZ;
 }
 else if (card->flags & kMMC_SupportHighSpeed26MHZFlag)
 {
 freq = MMC_CLOCK_26MHZ;
 }

 card->busClock_Hz = SDMMCHOST_SET_CARD_CLOCK(card->host.base, card->host.sourceClock_Hz, freq);
 [...]

 card->busTiming = kMMC_HighSpeedTiming;
 return kStatus_Success;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

freq is initialized to 0 at the top of the function.  We tell the MMC controller to switch to HS timing with 50 ohm drive strength, then we set freq based on one of three sets of conditions:

  • If 4-bit DDR or 8-bit DDR flag is enabled, set freq to 52000000 (and enable DDR mode).
  • If HS 52 MHz flag is enabled, set freq to 52000000.
  • If HS 26 MHz flag is enabled, set freq to 26000000.

However, if none of those three conditions are met, then freq remains 0.  Thus, the call to SDMMCHOST_SET_CARD_CLOCK() triggers an assert(), because USDHC_SetSdClock() asserts that freq cannot be 0.  And that's it, game over.

I have not yet put a scope on the data/clk/cmd lines to verify there is activity; that'll probably be my next step.  My question is... what's going wrong here, exactly?  By the time execution reaches MMC_SelectBusTiming(), card->busTiming is set to 0 (kMMC_HighSpeedTimingNone) and the only flag set in card->flags is 0x100 (kMMC_SupportHighCapacityFlag).  So why does MMC_SwitchToHighSpeed() assume that either the bus width has already been selected to be 4-bit or 8-bit, or that HS26 or HS52 are already indicated in the card flags?  Has this code actually been tested on an MMC card?  What should I be doing differently?  Thanks.

David R.

1 Solution
814 Views
Senior Contributor I

Hi Mike,

So I tried the solution of using AT_NONCACHEABLE_SECTION_ALIGN() macros to declare the Ethernet data buffers, but I found that they had no effect on the application.  After exploring what those macros do and examining the MAP files, I arrived at a complete solution.  (It's not the only possible solution, but it works for my application.)

Here is the definition for AT_NONCACHEABLE_SECTION_ALIGN() in fsl_common.h:

#define AT_NONCACHEABLE_SECTION(var) __attribute__((section("NonCacheable,\"aw\",%nobits @"))) var
#define AT_NONCACHEABLE_SECTION_ALIGN(var, alignbytes) \
    __attribute__((section("NonCacheable,\"aw\",%nobits @"))) var __attribute__((aligned(alignbytes)))‍‍‍‍‍‍

It simply declares the variable to belong to a section named "NonCacheable".  Importantly, the default linker script does absolutely nothing with this declaration; simply using this declaration will have no effect on the application.  I applied these macros to the Ethernet buffers:

err_t ethernetif0_init(struct netif *netif)
{
    static struct ethernetif ethernetif_0;
    AT_NONCACHEABLE_SECTION_ALIGN(static enet_rx_bd_struct_t rxBuffDescrip_0[ENET_RXBD_NUM], FSL_ENET_BUFF_ALIGNMENT);
    AT_NONCACHEABLE_SECTION_ALIGN(static enet_tx_bd_struct_t txBuffDescrip_0[ENET_TXBD_NUM], FSL_ENET_BUFF_ALIGNMENT);
    AT_NONCACHEABLE_SECTION_ALIGN(static rx_buffer_t rxDataBuff_0[ENET_RXBD_NUM], FSL_ENET_BUFF_ALIGNMENT);
    AT_NONCACHEABLE_SECTION_ALIGN(static tx_buffer_t txDataBuff_0[ENET_TXBD_NUM], FSL_ENET_BUFF_ALIGNMENT);
    [...]
}

But they remained allocated to the same area of memory (SRAM_OC), and the application remained non-functional (app runs, but unit fails to respond to ping).

It is necessary to tell the linker to do something with regard to the "NonCacheable" section name.  In my case, I created a custom linker script input section, like this:

noncacheable_section_definition.png

The entry there says that anything in section "NonCacheable" should be located in SRAM_DTC (by definition non-cacheable) and placed in the .bss section.  After making this change and recompiling, the application now works as expected.

This same solution can be applied to the MMC (and presumably SD) examples.  For MMC, edit fsl_mmc_host.c like so:

/*******************************************************************************
 * Variables
 ******************************************************************************/

/*! @brief Card descriptor */
AT_NONCACHEABLE_SECTION(mmc_card_t g_mmc);‍‍‍‍‍‍

Then add the "NonCacheable" definition to the linker script as above, and now "g_mmc" is located in SRAM_DTC, while the remaining global data is in SRAM_OC as previously declared.

There may well be other means of declaring where "NonCacheable" data should go, but this seems the easiest to implement -- use SRAM_DTC for all non-cacheable data, and remaining global data can reside wherever (SRAM_OC, BOARD_SDRAM, etc).

It should now be NXP's task to re-test each of its SDK examples to ensure that there are no other cache issues in the SDK.  As Jack King has already identified, a caching issue exists with the USB examples.  Thanks for your assistance in resolving this matter.

David R.

View solution in original post

25 Replies
130 Views
NXP TechSupport
NXP TechSupport

Hi David,

First of all, many thanks for your feedback.

Your voice is quite important to us and we do appreciate it.

We collect customer feedback to enhance MCUXpresso SDK software quality, make it more robust. 

Our SDK software got the message and I think they will make related improvement accordingly. Thanks.

best regards,

Mike

130 Views
NXP TechSupport
NXP TechSupport

Hi David,

Thanks for the patience.

I could regenerate your mentioned issue with attached [lwip_tcpecho] demo with IMXRT1050-EVKB board:

pastedImage_1.png

The DTCM is non-cache memory range, while OCRAM is cacheable memory range.

When ENET and core as two masters to access the same memory range in OCRAM, in fact both master will access the cache instead of access the OCRAM memory range directly. There could exists the issue when cached data is different with actual data in OCRAM. So, it need to do cache maintainance during actual application.

Customer can call L1CACHE_InvalidateDCacheByRange() API function of cache driver (fsl_cache.c & fsl_cache.h) to define memory range in OCRAM be invalidates Cortex-M7 L1 data cache.

Wish it helps.

best regards,

Mike

0 Kudos
130 Views
Senior Contributor I

UPDATE: I have a culprit, but honestly I have no idea what's required to fix it.

Just for giggles, and because it seemed a likely suspect, I made a clone of the "rt1050_emmc" project and called it "rt1050_emmc_sdram" (attached).  I did not change any of the source code; I only changed the preprocessor and linker settings.  Specifically:

  • Added XIP_BOOT_HEADER_DCD_ENABLE=1 to the C preprocessor to enable SDRAM configuration by the boot ROM. 
  • Added SKIP_SYSCLK_INIT to the C preprocessor to stop the clock initialization code from stepping on the SDRAM clocks.
  • Changed "Global Data Placement" to BOARD_SDRAM (from "Default", which was SRAM_DTC).

Enabling SDRAM does have the side effect of changing the PLL_PFD2 frequency to 327.724128 MHz instead of 396 MHz, which makes the USDHC controller run at 163 MHz instead of 198 MHz.  Presumably this is just fine.

Anyway, I built the new project, did a debug launch, traced into MMC_CardInit(), got to MMC_SendExtendedCsd(), stepped over it, then examined card->rawExtendedCsd and card->extendedCsd... they were both zero.  The EXACT same program built to use internal SRAM only ("rt1050_emmc") reads the extended CSD data just fine.  But when all the global data (including the mmc_card_t struct) is located in SDRAM, the extended CSD read malfunctions.  EDIT: I did some additional testing, and found that the global data must reside in SRAM_DTC for the extended CSD read to succeed.  If another region is used (e.g. SRAM_OC, or BOARD_SDRAM), then the extended CSD read will come back as zeros.  I managed to shift around the memory in my original program to make the global data fit in SRAM_DTC, and what do you know, the extended CSD data now reads back correctly.

Why is this happening?  Why is SRAM_DTC apparently required for the correct operation of this driver?  Is there some step during initialization that, when missing, makes SDRAM or OCRAM unsafe to use with certain driver modules?  Is there some internal bus contention issue that the USDHC driver isn't accounting for?  Is there some requirement that certain memory elements must reside in SRAM_DTC?  Why does it matter where global data is located in this example?

LATE EDIT: By disabling HS200 selection in the MMC driver, I was able to get standard 52 MHz 4-bit mode working with my eMMC chip; the SDK example can now read and write files and directories.  So now I just need to understand this SRAM_DTC memory issue, because it's quite constraining to have to restrict all global data to less than 128 KB.

David Rodgers

0 Kudos
130 Views
NXP TechSupport
NXP TechSupport

Hi David,

First of all,Sorry for the delay reply.

I am checking this thread.

I still need some time to answer this thread.

Thanks for your patience.

best regards,

Mike

130 Views
Senior Contributor I

So I tacked on a couple of wires and got a capture of the SDHC traffic... it certainly looks like there's stuff going on, but sadly my analyzer can't decode SD/MMC card protocol (and of course I can't decode the high-speed data, since I'm limited to a 16 MHz capture).  The bus frequency is configured correctly in the first part (400 kHz), so that appears correct.  I think I really just need an answer for why freq is allowed to remain 0 prior to setting the card clock.  I've attached my Saleae Logic data capture here.

0 Kudos