SD卡初始化失败案例

问题描述

SD卡在uboot中能识别,在linux中初始化失败,经过分析是SD卡在3.3V切换到1.8V时存在问题,需要分析那些因素影响电压切换。

# kthread=>worker_thread=>process_one_work=>mmc_rescan=>mmc_attach_sd=>mmc_sd_init_card=>mmc_sd_get_cid
mmc0: error -110 whilst initialising SD card

SD卡DTS相关分析

# support 3.3v & 1.8v
&usdhc1 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;
	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend;
	enable-sdio-wakeup;
	status = "okay";
};

# SD1 VSELECT 用于切换电压(1.8v/3.3v)
pinctrl_usdhc1: usdhc1grp {
    fsl,pins = <
        MX6UL_PAD_SD1_CMD__USDHC1_CMD      0x17059
        MX6UL_PAD_SD1_CLK__USDHC1_CLK      0x10071
        MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
        MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
        MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
        MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
        MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
        MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
        MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */
    >;
};

# only support 3.3v
&usdhc1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_usdhc1>;
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend;
	wakeup-source;
	no-1-8-v;
	status = "okay";
};
# pinctrl_usdhc1 pinctrl_usdhc1_100mhz pinctrl_usdhc1_200mhz
# dts use the strongest driver strength fordefault(high-speed), 100MHz(SDR50/DDR50/DDR52) and 200MHz(SDR104/HS200/HS400) timing
Bus Speed Mode (using 4 parallel data lines)
(1) Default Speed mode: 3.3V signaling, Frequency up to 25MHz, up to 12.5MB/sec
(2) High Speed mode: 3.3V signaling, Frequency up to 50MHz, up to 25MB/sec
(3) SDR12: UHS-I 1.8V signaling, Frequency up to 25MHz, up to 12.5MB/sec
(4) SDR25: UHS-I 1.8V signaling, Frequency up to 50MHz, up to 25MB/sec
(5) SDR50: UHS-I 1.8V signaling, Frequency up to 100MHz, up to 50MB/sec
(6) SDR104: UHS-I 1.8V signaling, Frequency up to 208MHz, up to 104MB/sec
(7) DDR50: UHS-I 1.8V signaling, Frequency up to 50MHz, sampled on both clock edges, up to 50MB/sec
(8) UHS156: UHS-II RCLK Frequency Range 26MHz-52MHz, up to 1.56Gbps per lane

pinctrl-0 to pinctrl-3: These define the actual pin configurations for the specified states
disable-wp: Disables the write protection feature on the SD card.
keep-power-in-suspend: Ensures that the power to the SD card is maintained even during suspend modes.
no-1-8-v: This disables the use of 1.8V signaling for the SD card interface. This setting means the interface will use 3.3V signaling instead.
vqmmc-supply: Defines the regulator supply for the Vqmmc pin, which is the supply for the SD card's voltage. It references a regulator &reg_sd1_vqmmc elsewhere in the device tree.
wakeup-source:Marks the SD card interface as a wakeup source. This means the SD card interface can be used to wake up the system from a suspend state.
cd-gpios: This defines the GPIO pin used for card detection. It is connected to GPIO5, pin 0. The GPIO is active low, meaning the card is considered inserted when the GPIO pin is low.

linux kernel 切换1.8V

parse dts no-1-8-v & pins_100mhz & pins_200mhz
/**
platform_probe => sdhci_esdhc_imx_probe=>sdhci_esdhc_imx_probe_dt
 */
static int
sdhci_esdhc_imx_probe_dt(struct platform_device *pdev,
			 struct sdhci_host *host,
			 struct pltfm_imx_data *imx_data)
{
    ...
    if (of_find_property(np, "no-1-8-v", NULL))
		host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
    ...
    if (!is_s32v234_usdhc(imx_data) && esdhc_is_usdhc(imx_data)
                && !IS_ERR(imx_data->pinctrl)) {
        imx_data->pins_100mhz = pinctrl_lookup_state(imx_data->pinctrl,
                        ESDHC_PINCTRL_STATE_100MHZ);
        imx_data->pins_200mhz = pinctrl_lookup_state(imx_data->pinctrl,
                        ESDHC_PINCTRL_STATE_200MHZ);
	}
    ...
}

static int esdhc_change_pinstate(struct sdhci_host *host,
						unsigned int uhs)
{
    ....
	switch (uhs) {
	case MMC_TIMING_UHS_SDR50:
	case MMC_TIMING_UHS_DDR50:
		pinctrl = imx_data->pins_100mhz;
		break;
	case MMC_TIMING_UHS_SDR104:
	case MMC_TIMING_MMC_HS200:
	case MMC_TIMING_MMC_HS400:
		pinctrl = imx_data->pins_200mhz;
		break;
    .....
	}
	return pinctrl_select_state(imx_data->pinctrl, pinctrl);
}

/**
 platform_probe => sdhci_esdhc_imx_probe=>sdhci_add_host=>sdhci_setup_host
 */
int sdhci_setup_host(struct sdhci_host *host)
{
	...
	if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
				 SDHCI_SUPPORT_DDR50);
		/*
		*  SoC 中的 SDHCI 控制器可能支持 HS200/HS400(使用 mmc-hs200-1_8v/mmc-hs400-1_8v dt 属性表示),
		* 但如果主板的模型使得 IO 线未连接到 1.8v,则无法支持 HS200/HS400。
		* 如果主板没有将 1.8v 连接到IO 线,则禁用 HS200/HS400。(适用于 1.8v 中的其他模式)
		 * The SDHCI controller in a SoC might support HS200/HS400
		 * (indicated using mmc-hs200-1_8v/mmc-hs400-1_8v dt property),
		 * but if the board is modeled such that the IO lines are not
		 * connected to 1.8v then HS200/HS400 cannot be supported.
		 * Disable HS200/HS400 if the board does not have 1.8v connected
		 * to the IO lines. (Applicable for other modes in 1.8v)
		 */
		mmc->caps2 &= ~(MMC_CAP2_HSX00_1_8V | MMC_CAP2_HS400_ES);
		mmc->caps &= ~(MMC_CAP_1_8V_DDR | MMC_CAP_UHS);
	}
	...
}
no pinctrl states for 100MHz/200MHz

如果没有pinctrl_usdhc1_100mhz和pinctrl_usdhc1_200mhz,则不要声明支持UHS模式

/**
    platform_probe=>sdhci_esdhc_imx_probe==>sdhci_add_host=>sdhci_read_caps=>__sdhci_read_caps=>sdhci_readl(host, SDHCI_CAPABILITIES_1)=>host->ops->read_l(...)=>esdhc_readl_le(...)

	esdhc_readl_le 中:
	It has checks to ensure that UHS modes (Ultra High-Speed modes) are only advertised if there are valid pinctrl states for 100 MHz or 200 MHz, ensuring that only supported modes are enabled.
 */
static inline void sdhci_read_caps(struct sdhci_host *host)
{
	__sdhci_read_caps(host, NULL, NULL, NULL);
}
void __sdhci_read_caps(struct sdhci_host *host, const u16 *ver,
		       const u32 *caps, const u32 *caps1)
{
    ....

	if (host->version < SDHCI_SPEC_300)
		return;

	if (caps1) {
		host->caps1 = *caps1;
	} else {
		host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1);
		host->caps1 &= ~upper_32_bits(dt_caps_mask);
		host->caps1 |= upper_32_bits(dt_caps);
	}
}

static inline u32 sdhci_readl(struct sdhci_host *host, int reg)
{
	if (unlikely(host->ops->read_l)) /**esdhc_readl_le */
		return host->ops->read_l(host, reg);
	......
}

static const struct esdhc_soc_data usdhc_imx6ull_data = {
	.flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
			| ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
			| ESDHC_FLAG_ERR010450
			| ESDHC_FLAG_STATE_LOST_IN_LPMODE
			| ESDHC_FLAG_BUSFREQ,
};

static u32 esdhc_readl_le(struct sdhci_host *host, int reg)
{
    ......
    if (unlikely(reg == SDHCI_CAPABILITIES_1)) {
		if (esdhc_is_usdhc(imx_data)) {
			if (imx_data->socdata->flags & ESDHC_FLAG_HAVE_CAP1)
				val = readl(host->ioaddr + SDHCI_CAPABILITIES) & 0xFFFF;
        ...
        }
        /*
        * 如果没有针对100MHz/200MHz的pinctrl状态,则不要声明支持更快的UHS模式
        * Do not advertise faster UHS modes if there are no pinctrl states for 100MHz/200MHz.
        */
        if (IS_ERR_OR_NULL(imx_data->pins_100mhz))
            val &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
        if (IS_ERR_OR_NULL(imx_data->pins_200mhz))
            val &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_HS400);
    }
    ......
}

static struct sdhci_ops sdhci_esdhc_ops = {
	.read_l = esdhc_readl_le,
    ......
};
static const struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = {
	.ops = &sdhci_esdhc_ops,
};

/** platform_probe=>sdhci_esdhc_imx_probe=>sdhci_pltfm_init */
static const struct of_device_id imx_esdhc_dt_ids[] = {
	{ .compatible = "fsl,imx6ull-usdhc", .data = &usdhc_imx6ull_data, },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_esdhc_dt_ids);
static struct platform_driver sdhci_esdhc_imx_driver = {
	.driver		= {
		.name	= "sdhci-esdhc-imx",
		.of_match_table = imx_esdhc_dt_ids,
	},
	.probe		= sdhci_esdhc_imx_probe,
};

static int sdhci_esdhc_imx_probe(struct platform_device *pdev)
{
...
	host = sdhci_pltfm_init(pdev, &sdhci_esdhc_imx_pdata,
				sizeof(*imx_data));
...
}

struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
				    const struct sdhci_pltfm_data *pdata,
				    size_t priv_size)
{
	...
	host->ioaddr = ioaddr;
	host->irq = irq;
	host->hw_name = dev_name(&pdev->dev);
	if (pdata && pdata->ops)
		host->ops = pdata->ops;
	else
		host->ops = &sdhci_pltfm_ops;
    ...
}
EXPORT_SYMBOL_GPL(sdhci_pltfm_init);

/**drivers/mmc/core/sd.c 
kthread=>worker_thread=>process_one_work=>mmc_rescan=>mmc_attach_sd=>mmc_sd_init_card ==> mmc_sd_get_cid=>mmc_set_uhs_voltage=>mmc_host_set_uhs_voltage
*/
int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr)
{
	/**OCR (Operation Condition Register): ocr is the Operation Condition Register read from the SD card.
		S18R : Switching to 1.8V Request: 0b: Use current signal voltage, 1b: Switch to 1.8V signal voltage
	 */
	if (!mmc_host_is_spi(host) && (ocr & SD_OCR_S18R) &&
	    rocr && (*rocr & SD_ROCR_S18A)) {
		err = mmc_set_uhs_voltage(host, pocr); /** set sdcard & host voltage */
		....
	}
}

int mmc_set_uhs_voltage(struct mmc_host *host, u32 ocr)
{
	/** SD card uses CMD11 (SD_SWITCH_VOLTAGE) command to switch to 1.8V signaling */
	cmd.opcode = SD_SWITCH_VOLTAGE;
	cmd.arg = 0;
	cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
	err = mmc_wait_for_cmd(host, &cmd, 0);

	/** set the UHS (Ultra High Speed) signaling voltage for an SD/MMC host controller */
	mmc_host_set_uhs_voltage(host);
}

/**
The function sdhci_start_signal_voltage_switch() is part of the SDHCI (SD Host Controller Interface) code in the Linux kernel and is responsible for switching the signal voltage for SD cards, particularly in the context of changing the signal voltage between 3.3V and 1.8V. This is a critical operation for SD cards that support different signaling voltages, typically to improve performance, power efficiency, or compatibility.
 */
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
				      struct mmc_ios *ios)
{
	case MMC_SIGNAL_VOLTAGE_180:
		if (!(host->flags & SDHCI_SIGNALING_180))
			return -EINVAL;

		/** dts中可以指定 vqmmc-supply = <&reg_1v8>; */
		if (!IS_ERR(mmc->supply.vqmmc)) {
			ret = mmc_regulator_set_vqmmc(mmc, ios);
			......
		}

		/* Enable 1.8V Signal Enable in the Host Control2 register

		56.8.26 Vendor Specific Register (uSDHCx_VEND_SPEC)
		Voltage Selection
		Change the value of output signal VSELECT, to control the voltage on pads for external card. There must
		be a control circuit out of uSDHC to change the voltage on pads.
		1 Change the voltage to low voltage range, around 1.8 V
		0 Change the voltage to high voltage range, around 3.0 V
		*/
		ctrl |= SDHCI_CTRL_VDD_180;
		sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); /**==>> esdhc_writew_le */
		......
}

问题结论

  1. sd卡初始化是在切换1.8v后发生的, kthread=>worker_thread=>process_one_work=>mmc_rescan=>mmc_attach_sd=>mmc_sd_init_card=>mmc_sd_get_cid
    mmc0: error -110 whilst initialising SD card
  2. SD卡要支持1.8v,除软件支持切换以外,硬件还需要支持电压切换,从原理图上看仅仅支持3.3V,没有1.8v/3.3v切换电路
  3. SD卡初始化失败,因为软件将sd卡和sd控制器切换到1.8v,但实际硬件并未切换到1.8v,个人理解SD卡切换到1.8v工作模式,不过实际管脚不是1.8v,后面再访问sd卡就无法恢复
  4. UBoot中可以,是因为没有处理dts中的pins_100mhz和pins_200mhz,实际工作仍然在3.3V
  5. 正常工作时的信息(3V3)
cat /sys/kernel/debug/mmc0/ios 
clock:          50000000 Hz
actual clock:   44000000 Hz
vdd:            21 (3.3 ~ 3.4 V)
bus mode:       2 (push-pull)
chip select:    0 (don't care)
power mode:     2 (on)
bus width:      2 (4 bits)
timing spec:    2 (sd high-speed)
signal voltage: 0 (3.30 V)
driver type:    0 (driver type B)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值