diff --git a/arch/arm64/src/imx9/hardware/imx9_usdhc.h b/arch/arm64/src/imx9/hardware/imx9_usdhc.h index 0233a0e18f1..ca49fb0191e 100644 --- a/arch/arm64/src/imx9/hardware/imx9_usdhc.h +++ b/arch/arm64/src/imx9/hardware/imx9_usdhc.h @@ -340,6 +340,7 @@ # define USDHC_SYSCTL_SDCLKFS_DIV64 (0x20 << USDHC_SYSCTL_SDCLKFS_SHIFT) /* Base clock / 64 */ # define USDHC_SYSCTL_SDCLKFS_DIV128 (0x40 << USDHC_SYSCTL_SDCLKFS_SHIFT) /* Base clock / 128 */ # define USDHC_SYSCTL_SDCLKFS_DIV256 (0x80 << USDHC_SYSCTL_SDCLKFS_SHIFT) /* Base clock / 256 */ +# define USDHC_SYSCTL_SDCLKFS_DIV(p) (((p) >> 1) << USDHC_SYSCTL_SDCLKFS_SHIFT) #define USDHC_SYSCTL_DTOCV_SHIFT (16) /* Bits 16-19: Data Timeout Counter Value */ #define USDHC_SYSCTL_DTOCV_MASK (0xf << USDHC_SYSCTL_DTOCV_SHIFT) diff --git a/arch/arm64/src/imx9/imx9_usdhc.c b/arch/arm64/src/imx9/imx9_usdhc.c index 06d0fd14a19..c63477b641d 100644 --- a/arch/arm64/src/imx9/imx9_usdhc.c +++ b/arch/arm64/src/imx9/imx9_usdhc.c @@ -221,6 +221,7 @@ struct imx9_dev_s uint32_t sw_cd_gpio; /* If a non USDHCx CD pin is used, * this is its GPIO */ uint32_t cd_invert; /* If true invert the CD pin */ + uint32_t root_clock_freq; /* Root clock frequency */ }; /* Register logging support */ @@ -326,10 +327,9 @@ static void imx9_reset(struct sdio_dev_s *dev); static sdio_capset_t imx9_capabilities(struct sdio_dev_s *dev); static sdio_statset_t imx9_status(struct sdio_dev_s *dev); static void imx9_widebus(struct sdio_dev_s *dev, bool enable); - -#ifdef CONFIG_IMX9_USDHC_ABSFREQ -static void imx9_frequency(struct sdio_dev_s *dev, uint32_t frequency); -#endif +static bool imx9_sdcard_hs_mode(struct sdio_dev_s *dev, bool set); +static uint32_t imx9_frequency(struct sdio_dev_s *dev, + unsigned long frequency); static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate); static int imx9_attach(struct sdio_dev_s *dev); @@ -1658,6 +1658,141 @@ static void imx9_widebus(struct sdio_dev_s *dev, bool wide) putreg32(regval, priv->addr + IMX9_USDHC_PROCTL_OFFSET); } +/**************************************************************************** + * Name: imx9_sdcard_hs_mode + * + * Description: + * Check if the high speed mode is supported or try to switch to that + * via CMD6 + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * set - Switch to HS mode + * + * Returned Value: + * If "set" is false, return true if card supports high speed mode. If + * the "set" is true, return true if the selected mode for group one is + * high-speed (1), that is, card is switched to high-speed. + * + ****************************************************************************/ + +static bool imx9_sdcard_hs_mode(struct sdio_dev_s *dev, bool set) +{ +#ifdef CONFIG_IMX9_USDHC_DMA + struct imx9_dev_s *priv = (struct imx9_dev_s *)dev; + uint8_t *resp = priv->rxbuffer; +#else + uint32_t resp_buf[16]; /* 32-bit aligned 64-byte buffer */ + uint8_t *resp = (uint8_t *)resp_buf; +#endif + uint32_t r1; + int ret; + + /* Construct CMD6 with full 64 byte response */ + + const uint32_t cmd6 = (MMCSD_CMDIDX6 | MMCSD_R1_RESPONSE | + MMCSD_RDDATAXFR | USDHC_XFERTYP_RSPTYP_LEN48 | + USDHC_XFERTYP_CICEN | USDHC_XFERTYP_CCCEN); + + /* CMD6 argument for switching to high-speed mode: + * Bit 31: Mode (1 = switch, 0 = check) + * Bits 30:24: Reserved (0) + * Bits 23:20: Group 6 (0xf = no change) + * Bits 19:16: Group 5 (0xf = no change) + * Bits 15:12: Group 4 (0xf = no change) + * Bits 11:8: Group 3 (0xf = no change) + * Bits 7:4: Group 2 (0xf = no change) + * Bits 3:0: Group 1 - Access Mode (0x1 = High Speed) + */ + + const uint32_t cmd6_arg = set ? 0x80fffff1u : 0x00fffff1u; + uint16_t fg1_support; + uint8_t fg1_selected; + bool hs_supported = false; + sdio_eventset_t wait_ret; + +#ifdef CONFIG_SDIO_BLOCKSETUP + imx9_blocksetup(dev, 64, 1); +#endif + imx9_waitenable(dev, SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT | + SDIOWAIT_ERROR, 100); + +#ifdef CONFIG_IMX9_USDHC_DMA + imx9_dmarecvsetup(dev, resp, 64); +#else + imx9_recvsetup(dev, resp, 64); +#endif + + ret = imx9_sendcmd(dev, cmd6, cmd6_arg); + if (ret != OK) + { + return false; + } + + ret = imx9_waitresponse(dev, cmd6); + if (ret != OK) + { + return false; + } + + ret = imx9_recvshortcrc(dev, cmd6, &r1); + if (ret != OK) + { + return false; + } + + wait_ret = imx9_eventwait(dev); + if (wait_ret & (SDIOWAIT_TIMEOUT | SDIOWAIT_ERROR)) + { + return false; + } + + /* Parse the switch status block */ + + /* FG1 support (16-bit big-endian at bytes 12..13) */ + + fg1_support = ((uint16_t)resp[12] << 8) | resp[13]; + + /* FG1 selected result at byte 16 low nibble */ + + fg1_selected = resp[16] & 0xf; + + if (set) + { + /* Selected should indicate function 1 */ + + hs_supported = fg1_selected == 1; + } + else + { + /* Rely on support bits */ + + hs_supported = (fg1_support & (1 << 1)) != 0; + } + + if (set) + { + if (hs_supported) + { + mcinfo("SD card switched to high-speed mode (fg1_selected=%02x)\n", + fg1_selected); + } + else + { + mcerr("ERROR: Failed to set SD card high-speed mode" + " (fg1_selected=%02x, fg1_support=0x%04x)\n", + fg1_selected, fg1_support); + } + } + else + { + mcinfo("High Speed is%s supported by the card (fg1_support=0x%04x)\n", + hs_supported ? "" : " not", fg1_support); + } + + return hs_supported; +} + /**************************************************************************** * Name: imx9_frequency * @@ -1669,18 +1804,17 @@ static void imx9_widebus(struct sdio_dev_s *dev, bool wide) * frequency - The frequency to use * * Returned Value: - * None + * frequency divisor bits for the SYSCTL register * ****************************************************************************/ -#ifdef CONFIG_IMX9_USDHC_ABSFREQ -static void imx9_frequency(struct sdio_dev_s *dev, uint32_t frequency) +static uint32_t imx9_frequency(struct sdio_dev_s *dev, + unsigned long frequency) { - uint32_t sdclkfs; - uint32_t prescaled; - uint32_t regval; - unsigned int prescaler; - unsigned int divisor; + struct imx9_dev_s *priv = (struct imx9_dev_s *)dev; + unsigned long root_freq = priv->root_clock_freq; + unsigned prescaler; + unsigned divisor; /* The SDCLK frequency is determined by * (1) the frequency of the base clock that was selected as the @@ -1691,109 +1825,58 @@ static void imx9_frequency(struct sdio_dev_s *dev, uint32_t frequency) * * The prescaler is available only for the values: 2, 4, 8, 16, 32, * 64, 128, and 256. Pick the smallest value of SDCLKFS that would - * result in an in-range frequency. For example, if the base clock - * frequency is 96 MHz, and the target frequency is 25 MHz, the - * following logic will select prescaler: - * - * NOTE: USDHC_SYSCTL_SDCLKFS_DIVs are for Single Data Rate mode. - * See Reference manual for further details. - * - * 96MHz / 2 <= 25MHz <= 96MHz / 2 /16 -- YES, prescaler == 2 - * - * If the target frequency is 400 kHz, the following logic will - * select prescaler: - * - * 96MHz / 2 <= 400KHz <= 96MHz / 2 / 16 -- NO - * 96MHz / 4 <= 400KHz <= 96MHz / 4 / 16 -- NO - * 96MHz / 8 <= 400KHz <= 96MHz / 8 / 16 -- NO - * 96MHz / 16 <=400KHz <= 96MHz / 16 / 16 -- YES, prescaler == 16 + * result in an in-range frequency. */ - if (/* frequency >= (BOARD_CORECLK_FREQ / 2) && */ - frequency <= (BOARD_CORECLK_FREQ / 2 / 16)) + prescaler = 1; + do { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV2; - prescaler = 2; + prescaler *= 2; + divisor = root_freq / (frequency * prescaler); + + /* In case of root_freq is not divisible by frequency * prescaler + * we need to increase divisor by one to avoid frequency higher + * than requested + */ + + if (root_freq % (frequency * prescaler)) + { + divisor++; + } } - else if (frequency >= (BOARD_CORECLK_FREQ / 4) && - frequency <= (BOARD_CORECLK_FREQ / 4 / 16)) + while (divisor > 16 && prescaler < 256); + + /* Clamp divisor to minimum of 1 */ + + if (divisor < 1) { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV4; - prescaler = 4; + divisor = 1; } - else if (frequency >= (BOARD_CORECLK_FREQ / 8) && - frequency <= (BOARD_CORECLK_FREQ / 8 / 16)) + + /* Clamp divisor to maximum 16. This would result too high frequency. + * This only triggers if prescaler reached maximum (256) but frequency + * is still too high. At 200MHz root clock this would mean that < 50kHZ + * frequency was requested, so this is not a real issue. + */ + + if (divisor > 16) { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV8; - prescaler = 8; - } - else if (frequency >= (BOARD_CORECLK_FREQ / 16) && - frequency <= (BOARD_CORECLK_FREQ / 16 / 16)) - { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV16; - prescaler = 16; - } - else if (frequency >= (BOARD_CORECLK_FREQ / 32) && - frequency <= (BOARD_CORECLK_FREQ / 32 / 16)) - { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV32; - prescaler = 32; - } - else if (frequency >= (BOARD_CORECLK_FREQ / 64) && - frequency <= (BOARD_CORECLK_FREQ / 64 / 16)) - { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV64; - prescaler = 64; - } - else if (frequency >= (BOARD_CORECLK_FREQ / 128) && - frequency <= (BOARD_CORECLK_FREQ / 128 / 16)) - { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV128; - prescaler = 128; + divisor = 16; + mcerr("ERROR: Frequency too high. Requested: %lu Hz, Actual: %lu Hz " + "(prescaler=%u, divisor=%u)\n", frequency, + root_freq / (prescaler * divisor), prescaler, divisor); } else { - sdclkfs = USDHC_SYSCTL_SDCLKFS_DIV256; - prescaler = 256; + mcinfo("Requested: %lu Hz, Actual: %lu Hz " + "(prescaler=%u, divisor=%u)\n", frequency, + root_freq / (prescaler * divisor), prescaler, divisor); } - /* The optimal divider can than be calculated. For example, if the base - * clock frequency is 96 MHz, the target frequency is 25 MHz, and the - * selected prescaler value is 2, then - * - * prescaled = 96MHz / 2 = 48MHz - * divisor = (48MHz + 12.5HMz/ 25MHz = 2 - * - * And the resulting frequency will be 24MHz. Or, for example, if the - * target frequency is 400 kHz and the selected prescaler is 16, the - * following logic will select prescaler: - * - * prescaled = 96MHz / 16 = 6MHz - * divisor = (6MHz + 200KHz) / 400KHz = 15 - * - * And the resulting frequency will be exactly 400KHz. - */ + /* Return the new divisor information */ - prescaled = frequency / prescaler; - divisor = (prescaled + (frequency >> 1)) / frequency; - - /* Set the new divisor information and enable all clocks in the SYSCTRL - * register. TODO: Investigate using the automatically gated clocks to - * reduce power consumption. - */ - - regval = getreg32(priv->addr + IMX9_USDHC_SYSCTL_OFFSET); - regval &= ~(USDHC_SYSCTL_SDCLKFS_MASK | USDHC_SYSCTL_DVS_MASK); - regval |= (sdclkfs | USDHC_SYSCTL_DVS_DIV(divisor)); - regval |= (USDHC_SYSCTL_SDCLKEN | USDHC_SYSCTL_PEREN | - USDHC_SYSCTL_HCKEN | USDHC_SYSCTL_IPGEN); - - putreg32(regval, priv->addr + IMX9_USDHC_SYSCTL_OFFSET); - - mcinfo("SYSCTRL: %08x\n", - getreg32(priv->addr + IMX9_USDHC_SYSCTL_OFFSET)); + return USDHC_SYSCTL_SDCLKFS_DIV(prescaler) | USDHC_SYSCTL_DVS_DIV(divisor); } -#endif /**************************************************************************** * Name: imx9_clock @@ -1814,12 +1897,13 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) { struct imx9_dev_s *priv = (struct imx9_dev_s *)dev; uint32_t regval; + unsigned speed; /* Clear the old prescaler and divisor values so that new ones can be * ORed in. */ - regval = getreg32(priv->addr + IMX9_USDHC_SYSCTL_OFFSET); + regval = getreg32(priv->addr + IMX9_USDHC_SYSCTL_OFFSET); regval &= ~(USDHC_SYSCTL_SDCLKFS_MASK | USDHC_SYSCTL_DVS_MASK); /* Select the new prescaler and divisor values based on the requested @@ -1830,13 +1914,12 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) switch (rate) { default: - case CLOCK_SDIO_DISABLED: /* Clock is disabled */ + case CLOCK_SDIO_DISABLED: { - /* Clear the prescaler and divisor settings */ + /* Clock is disabled */ - putreg32(regval, priv->addr + IMX9_USDHC_SYSCTL_OFFSET); - mcinfo("DISABLED, SYSCTRL: %08" PRIx32 "\n", - getreg32(priv->addr + IMX9_USDHC_SYSCTL_OFFSET)); return; + mcinfo("DISABLED, SYSCTRL: %08" PRIx32 "\n", regval); + speed = 0; } break; @@ -1845,14 +1928,13 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) /* Initial ID mode clocking (<400KHz) */ mcinfo("IDMODE\n"); + speed = BOARD_USDHC_IDMODE_SPEED; /* Put out an additional 80 clocks in case this is a power-up * sequence. */ - regval |= (BOARD_USDHC_IDMODE_PRESCALER | - BOARD_USDHC_IDMODE_DIVISOR | - USDHC_SYSCTL_INITA); + regval |= USDHC_SYSCTL_INITA; } break; @@ -1861,8 +1943,7 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) /* MMC normal operation clocking */ mcinfo("MMCTRANSFER\n"); - regval |= (BOARD_USDHC_MMCMODE_PRESCALER | - BOARD_USDHC_MMCMODE_DIVISOR); + speed = BOARD_USDHC_MMCMODE_SPEED; } break; @@ -1871,8 +1952,7 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) /* SD normal operation clocking (narrow 1-bit mode) */ mcinfo("1BITTRANSFER\n"); - regval |= (BOARD_USDHC_SD1MODE_PRESCALER | - BOARD_USDHC_SD1MODE_DIVISOR); + speed = BOARD_USDHC_SD1MODE_SPEED; } break; @@ -1880,13 +1960,34 @@ static void imx9_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) { /* SD normal operation clocking (wide 4-bit mode) */ - mcinfo("4BITTRANSFER\n"); - regval |= (BOARD_USDHC_SD4MODE_PRESCALER | - BOARD_USDHC_SD4MODE_DIVISOR); + mcinfo("SDTRANSFER\n"); + speed = BOARD_USDHC_SDMODE_SPEED; + + if (speed > 25000000) + { + /* Switch SD card to high-speed mode if configured for higher + * than 25MHz + */ + + if (imx9_sdcard_hs_mode(dev, false)) + { + if (!imx9_sdcard_hs_mode(dev, true)) + { + /* HS mode setting failed, fall back to 25 MHz */ + + speed = 25000000; + } + } + } } break; } + if (speed > 0) + { + regval |= imx9_frequency(dev, speed); + } + putreg32(regval, priv->addr + IMX9_USDHC_SYSCTL_OFFSET); /* Wait for clock to become stable */ @@ -3352,8 +3453,8 @@ struct sdio_dev_s *imx9_usdhc_initialize(int slotno) /* Enable clocks */ - imx9_ccm_configure_root_clock(CCM_CR_USDHC2, SYS_PLL1PFD1, 4); - + imx9_ccm_configure_root_clock(CCM_CR_USDHC1, SYS_PLL1PFD1, 4); + imx9_get_rootclock(CCM_CR_USDHC1, &priv->root_clock_freq); imx9_ccm_gate_on(CCM_LPCG_USDHC1, true); break; @@ -3388,7 +3489,7 @@ struct sdio_dev_s *imx9_usdhc_initialize(int slotno) /* Enable clocks */ imx9_ccm_configure_root_clock(CCM_CR_USDHC2, SYS_PLL1PFD1, 4); - + imx9_get_rootclock(CCM_CR_USDHC2, &priv->root_clock_freq); imx9_ccm_gate_on(CCM_LPCG_USDHC2, true); mcinfo("Enabled clocks\n"); diff --git a/boards/arm64/imx9/imx93-evk/include/board.h b/boards/arm64/imx9/imx93-evk/include/board.h index 92b7bb7df32..bcb6fbb6261 100644 --- a/boards/arm64/imx9/imx93-evk/include/board.h +++ b/boards/arm64/imx9/imx93-evk/include/board.h @@ -105,23 +105,21 @@ #define PIN_USDHC2_CD_MUX IOMUX_CFG(IOMUXC_PAD_SD2_DATA3_USDHC2_DATA3, IOMUXC_PAD_DSE_X4 | IOMUXC_PAD_FSEL_FAST, 0) #define PIN_USDHC2_VSELECT_MUX IOMUX_CFG(IOMUXC_PAD_SD2_VSELECT_USDHC2_VSELECT, IOMUXC_PAD_DSE_X5 | IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_PD_ON, 0) -/* 390 KHz for initial inquiry stuff */ +/* 400 KHz for initial inquiry stuff */ -#define BOARD_USDHC_IDMODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV256 -#define BOARD_USDHC_IDMODE_DIVISOR USDHC_SYSCTL_DVS_DIV(2) +#define BOARD_USDHC_IDMODE_SPEED 400000 + +/* 25MHz for MMC mode */ + +#define BOARD_USDHC_MMCMODE_SPEED 25000000 /* 25MHz for 1-bit wide bus */ -#define BOARD_USDHC_MMCMODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV8 -#define BOARD_USDHC_MMCMODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +#define BOARD_USDHC_SD1MODE_SPEED 25000000 -#define BOARD_USDHC_SD1MODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV8 -#define BOARD_USDHC_SD1MODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +/* 50MHz for SD card mode */ -/* 50MHz for 4-bit wide bus */ - -#define BOARD_USDHC_SD4MODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV4 -#define BOARD_USDHC_SD4MODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +#define BOARD_USDHC_SDMODE_SPEED 50000000 /* Set the PLL clocks as follows: * diff --git a/boards/arm64/imx9/imx95-a55-evk/include/board.h b/boards/arm64/imx9/imx95-a55-evk/include/board.h index ec24e6a0746..2b160675c6a 100644 --- a/boards/arm64/imx9/imx95-a55-evk/include/board.h +++ b/boards/arm64/imx9/imx95-a55-evk/include/board.h @@ -105,23 +105,21 @@ #define PIN_USDHC2_CD_MUX IOMUX_CFG(IOMUXC_PAD_SD2_DATA3_USDHC2_DATA3, IOMUXC_PAD_DSE_X4 | IOMUXC_PAD_FSEL_FAST, 0) #define PIN_USDHC2_VSELECT_MUX IOMUX_CFG(IOMUXC_PAD_SD2_VSELECT_USDHC2_VSELECT, IOMUXC_PAD_DSE_X5 | IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_PD_ON, 0) -/* 390 KHz for initial inquiry stuff */ +/* 400 KHz for initial inquiry stuff */ -#define BOARD_USDHC_IDMODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV256 -#define BOARD_USDHC_IDMODE_DIVISOR USDHC_SYSCTL_DVS_DIV(2) +#define BOARD_USDHC_IDMODE_SPEED 400000 + +/* 25MHz for MMC mode */ + +#define BOARD_USDHC_MMCMODE_SPEED 25000000 /* 25MHz for 1-bit wide bus */ -#define BOARD_USDHC_MMCMODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV8 -#define BOARD_USDHC_MMCMODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +#define BOARD_USDHC_SD1MODE_SPEED 25000000 -#define BOARD_USDHC_SD1MODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV8 -#define BOARD_USDHC_SD1MODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +/* 50MHz for SD card mode */ -/* 50MHz for 4-bit wide bus */ - -#define BOARD_USDHC_SD4MODE_PRESCALER USDHC_SYSCTL_SDCLKFS_DIV4 -#define BOARD_USDHC_SD4MODE_DIVISOR USDHC_SYSCTL_DVS_DIV(1) +#define BOARD_USDHC_SDMODE_SPEED 50000000 /* Set the PLL clocks as follows: *