EMMC功能调试记录

调试的目标效果

在uboot阶段实现从固化程序加载nor flash里的uboot的镜像后,在从EMMC加载boot和文件系统。

调试的思路

1、先熟悉mmc子系统的加载驱动流程。
2、确认emmc硬件电路的接法以及连接逻辑性。
3、配置emmc相应的设备树、驱动、寄存器等。
4、在uboot阶段通过日志分析emmc是否初始化成功,以及挂载设备。

调试的过程

步骤一 查看芯片手册,获取固件加载流程:

在这里插入图片描述

步骤二 EMMC硬件连接电路:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJPLqhZV-1647412174411)(http://192.168.16.46:4999/server/index.php?s=/api/attachment/visitFile/sign/00766993b93b5f4eab0a15bd9e20517e)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cO6ERTnn-1647412174412)(http://192.168.16.46:4999/server/index.php?s=/api/attachment/visitFile/sign/e53faf6bb68e4ec50e4da8b23edc3c75)]

步骤三 查看设备树的配置,特别需要注意时钟的配置和引脚的配置:

sdhci: sdhci@fe310000 {
	compatible = "rockchip,dwcmshc-sdhci", "snps,dwcmshc-sdhci";
	reg = <0x0 0xfe310000 0x0 0x10000>;
	max-frequency = <200000000>;
	interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
	assigned-clocks = <&cru BCLK_EMMC>, <&cru TCLK_EMMC>;
	assigned-clock-rates = <200000000>, <24000000>;
	clocks = <&cru CCLK_EMMC>, <&cru HCLK_EMMC>,
		 <&cru ACLK_EMMC>, <&cru BCLK_EMMC>,
		 <&cru TCLK_EMMC>;
	clock-names = "core", "bus", "axi", "block", "timer";
	status = "disabled";
};

&sdhci {
bus-width = <8>;
u-boot,dm-spl;
/delete-property/ pinctrl-names;
/delete-property/ pinctrl-0;
mmc-hs200-1_8v;
status = "okay";
};

eMMC 的 DTS 配置

  1. max-frequency = <150000000>;
    eMMC 普通模式 50M,eMMC HS200 最大支持 150M;

  2. supports-emmc;
    此配置标识此插槽为 emmc 功能,为必须添加项。否则无法初始化 emmc 外设。

  3. bus-width = <4>;
    此配置同 SD 卡功能。

  4. mmc-ddr-1_8v;
    此配置表示支持 50MDDR 模式;

  5. mmc-hs200-1_8v;
    此配置表示支持 HS200 模式;

  6. mmc-hs400-1_8v; mmc-hs400-enhanced-strobe
    此两项配置表示支持 HS400 模式以及 HS400ES 模式,仅 RK3399 芯片支持。

  7. non-removable;
    此项表示该插槽为不可移动设备。 此项为必须添加项。

    步骤四 uboot通过mmc查看设备挂载情况:
    mmc list
    dwmmc@fe2b0000: 1
    dwmmc@fe2c0000: 2
    sdhci@fe310000: 0 (eMMC)

    mmc dev 0
    switch to partitions #0, OK
    mmc0(part 0) is current device

方法:

硬件问题分析

eMMC 有效电压的组合:
在这里插入图片描述

VCC_FLASH 对应 VCC;
VCC_IO 对应 VCCQ;
确保 eMMC_CMD/DATA0~7/VCC_IO 电压都一致(1.8 或 3.3v);
确保 VCC_FLAHS/VCC_IO 的电压在开机和运行时或者休眠唤醒时必须保持稳定、不能有塌陷或者纹波
过大的情况;
有条件的话,测下 clk 和 cmd 以及 data 的波形质量,确保波形正常;

波形分析

下图是 emmc 卡识别模式时的波形时序图(sdio、SD 一样)
简单说一下识别 emmc 卡的方式:主控发出 48clk 并携带 48bit 的数据发给 emmc 卡,而 emmc 卡要回应给主控
48clk 加 48bit 的数据;如下图:

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvSna2Fb-1647412174414)(http://192.168.16.46:4999/server/index.php?s=/api/attachment/visitFile/sign/4bdf4afb8e62d2771eaf4b89c61aefef)]

绿色:EMMC_CLK
黄色:EMMC_CMD: EMMC_CMD 空闲时一直处于高电平;
主控发出的波形:当最开始的两个电平有一高一低时,是主控发出去的命令;
EMMC卡响应的波形: 当最开始的两个电平有连续的两个低电平是表示卡端有响应;
其次主控和响应一般包含 48 个 bit 的数据,所以 48 个 clk 为一个完整的包。要确认的就是:主控发出
去命令包后,EMMC 卡端是否有响应。

LOG 分析

  1. 正确识别 emmc的 LOG
    [ 293.194013] mmc1: new high speed SDXC card at address 59b4
    [ 293.198185] mmcblk1: mmc1:59b4 00000 59.6 GiB
    [ 293.204351]
    mmcblk1: p1
    如果在内核看到这样的打印,说明 SD 卡已经被正确识别,并且已经有一个可用的分区 p1。
    如果在用户界面看不到 SD 卡设备或者设备不可使用,请排查用户态磁盘守护进程,如 vold。
    另外可手动验证分区是否可以使用
    mount -t vfat /dev/block/mmcblk1p1 /mnt
    或者
    mount -t vfat /dev/block/mmcblk1 /mnt
    然后到 mnt 目录下看下是否有 SD 卡里面的文件

  2. 开机不读卡,运行时拔插 OK:大概率时电源问题
    例如:拔掉所有电源,发现查着 HDMI 发现有漏电到 VCC_SD 卡里面;或者使用外接电源进行测试。

  3. 挂载失败:
    如果已经看到(1)中的 LOG,但是看到如下挂载失败的 LOG
    [ 2229.405694] FAT-fs (mmcblk1p1): bogus number of reserved sectors
    [ 2229.405751] FAT-fs (mmcblk1p1): Can’t find a valid FAT filesystem
    请格式化 SD 卡为 FAT32 文件系统;
    或者 NTFS: make menuconfig 选择 NTFS 文件系统的支持即可;

  4. 概率性不识别:
    mmc1: new high speed SD card at address b368
    mmcblk1: mmc1:b368 SMI
    486 MiB
    [mmc1] Data transmission error !!!
    MINTSTS: [0x00002000]
    dwmmc_rockchip ff0c0000.rksdmmc: data FIFO error (status=00002000)
    mmcblk1: error -110 sending status command, retrying
    need_retune:0,brq->retune_retry_done:0.
    降频和增加卡检测延时增强电源稳定性,如果降频 OK 的话,请检查硬件 layout;
    &sdmmc {
    card-detect-delay = <1200>;
    }

  5. TF 卡已经 mount,但不能访问 TF 卡目录,看起来是卡文件系统问题,但卡在 Windows 下可以
    访问。
    请尝试使用 fsck 对 TF 卡做修复。

  6. 硬件问题,io 电压异常

    `Workqueue: kmmcd mmc_rescan
     [<c0013e24>] (unwind_backtrace+0x0/0xe0) from [<c001172c>]
     (show_stack+0x10/0x14)
     [<c001172c>] (show_stack+0x10/0x14) from [<c04fa444>]
     (dw_mci_set_ios+0x9c/0x21c)
     [<c04fa444>] (dw_mci_set_ios+0x9c/0x21c) from [<c04e7748>]
     (mmc_set_chip_select+0x18/0x1c)
     [<c04e7748>] (mmc_set_chip_select+0x18/0x1c) from [<c04ebd5c>]
     (mmc_go_idle+0x94/0xc4)
     [<c04ebd5c>] (mmc_go_idle+0x94/0xc4) from [<c0748d80>]
     (mmc_rescan_try_freq+0x54/0xd0)
     [<c0748d80>] (mmc_rescan_try_freq+0x54/0xd0) from [<c04e85d0>]
     (mmc_rescan+0x2c4/0x390)
     [<c04e85d0>] (mmc_rescan+0x2c4/0x390) from [<c004d738>]
     (process_one_work+0x29c/0x458)
     [<c004d738>] (process_one_work+0x29c/0x458) from [<c004da88>]
     (worker_thread+0x194/0x2d4)
     [<c004da88>] (worker_thread+0x194/0x2d4) from [<c0052fb4>] (kthread+0xa0/0xac)
     [<c0052fb4>] (kthread+0xa0/0xac) from [<c000da98>] (ret_from_fork+0x14/0x3c)
     1409..dw_mci_set_ios:
     wait for unbusy timeout....... STATUS = 0x306 [mmc1]`
    

请检查 CMD 线与 DATA 的电压是否在空载状态下为高电平。并且检测 IO 电压是否过低,以及 IO 电压
与电源域的配置是否一致。如果是 SDIO 接口,建议排查 VCCIO_WL 电压,VBAT_WL 和 WIFI_REG_ON
以及晶振是否正常。另可以尝试排查走线太长导致波形质量很差,降频进行测试。

遇到的问题:

emmc初始化过程中,返回超时:
Trying to boot from MMC2
MMC error: The cmd index is 1, ret is -110
mmc_init: -110, time 16

解决方法:

     步骤一 通过跟踪驱动,查到返回异常的点

/* sdhci_send_command */
 static int sdhci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
 {
struct sdhci_host *host = mmc->priv;
  ...

sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND);
start = get_timer(0);
do {
	stat = sdhci_readl(host, SDHCI_INT_STATUS);
	if (stat & SDHCI_INT_ERROR)
		break;

	if (get_timer(start) >= SDHCI_READ_STATUS_TIMEOUT) {
		if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) {
			return 0;
		} else {
			printf("%s: Timeout for status update!\n",
			       __func__);
			return -ETIMEDOUT;
		}
	}
} while ((stat & mask) != mask);
...
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
if (stat & SDHCI_INT_TIMEOUT)
	return -ETIMEDOUT;
else
	return -ECOMM;

}

在stat = sdhci_readl(host, SDHCI_INT_STATUS);返回的状态值异常。

 *******zgy********sdhci_send_command  stat = 32768   ret  = 0

  步骤二 对比公版的EMMC电路设计:

Rockchip_RK3568_Hardware_Design_Guide_V1.0_CN.pdf
在这里插入图片描述

   步骤三 通过示波器量相应的EMMC的供电,CMD,clock等引脚,下图为**正常加载的clock时钟和cmd引脚**,
   此次异常的状态则表现为,clock和cmd都没有信号输出。

在这里插入图片描述

在这里插入图片描述

 步骤四  查看数据手册,查看EMMC FLASH GPIO寄存器的设置。

Rockchip RK3568 TRM Part1 V1.0-20210111.pdf
Rockchip RK3568 TRM Part2 V1.0-20210111.pdf

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

此次调式中,init阶段返回错误的中断状态值。
Trying to boot from MMC1
MMC error: The cmd index is 1, ret is -110
mmc_init: -110, time 16

在这里插入图片描述

 步骤五  查找到引脚复用的基地址和偏移地址,读出该引脚状态。

=> md 0xfe740014 4
fe740014: 00000000 00000000 00000000 00000000 …

步骤六  通过VScode中字段GRF_GPIO1C_IOMUX_H查到到代码复用的写入值:

在这里插入图片描述

通过读取寄存器的值和日志打印,发现此寄存器的状态没有设置对。

步骤七 修改代码,使之生效。

	writel((0x7777UL << 16) | (0x1111), GRF_BASE + GRF_GPIO1B_IOMUX_H);
writel((0x7777UL << 16) | (0x1111), GRF_BASE + GRF_GPIO1C_IOMUX_L);
writel((0x7777UL << 16) | (0x2111), GRF_BASE + GRF_GPIO1C_IOMUX_H);
writel((0x7777UL << 16) | (0x1111), GRF_BASE + GRF_GPIO1D_IOMUX_L);
writel(((7 << 0) << 16) | (1 << 0), GRF_BASE + GRF_GPIO1D_IOMUX_H);

参考内容

#MMC子系统#
##1.MMC分层##
mmc 3个层,1)硬件host层 2)core层 3)driver
1)硬件host,soc控制器,提供给core层接口,mmc_host、mmc_host_ops
sdhc标准化mmc_host通用,非sdhc标准需要创建mmc_host

2)core层,就是fireware层,sdio/sd/mmc一系列标准函数操作的集合,承上启下
3)driver层,host、core只是提供了上层接口,下层是什么设备还需要driver,比如sd卡,emmc,需要mtd驱动,sdio WiFi 需要sdio驱动

##2.中断##
mmc中断有2处,1处是接收中断,1处是插卡中断
emmc没有插卡中断
获取插卡,除了中断,还可以通过轮询寄存器

##3.代码框架##
1.probe函数里面通过mmc_alloc、mmc_add一个mmc结构体,给core层提供需要的接口,然后开启一个工作队列
2.热插拔设备入usb,都会开启工作队列。工作队列延时开启(或者没有插卡,插卡后中断使能工作队列)
3.工作队列是一个线程,在线程里面判断是sd/sdio/emmc设备,然后调用相应函数进行初始化sd卡/sdio/emmc设备,设置对应寄存器
4.中断采用request_thread_irq上下部

##4.设置信息##
1.设置工作频率、工作电压(3.3V/1.8V)、数据位(1/4/8bit)等信息,这里的设置是指根据sd卡设备,设置soc host寄存器端

##5.实验代码##
在这里插入图片描述

##6.结构体##

struct sdhci_ops {
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS    
 // 表示host另外提供了一套访问寄存器的方法,没有定义的话,则说明使用通用的读写寄存器的方法
u32        (*read_l)(struct sdhci_host *host, int reg);
u16        (*read_w)(struct sdhci_host *host, int reg);
u8        (*read_b)(struct sdhci_host *host, int reg);
void        (*write_l)(struct sdhci_host *host, u32 val, int reg);
void        (*write_w)(struct sdhci_host *host, u16 val, int reg);
void        (*write_b)(struct sdhci_host *host, u8 val, int reg);
#endif

void    (*set_clock)(struct sdhci_host *host, unsigned int clock);    // 设置时钟频率

int        (*enable_dma)(struct sdhci_host *host);    // 使能DMA
unsigned int    (*get_max_clock)(struct sdhci_host *host);    // 获取支持的最大时钟频率
unsigned int    (*get_min_clock)(struct sdhci_host *host);    // 获取支持的最小时钟频率
unsigned int    (*get_timeout_clock)(struct sdhci_host *host);
int        (*platform_bus_width)(struct sdhci_host *host, int width);  
void (*platform_send_init_74_clocks)(struct sdhci_host *host,
                     u8 power_mode);
unsigned int    (*get_ro)(struct sdhci_host *host);    // 获取
void    (*platform_reset_enter)(struct sdhci_host *host, u8 mask);    // 进入平台复位的方法
void    (*platform_reset_exit)(struct sdhci_host *host, u8 mask);    // 退出平台复位的方法
int    (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);    // 设置uhs方式
void    (*hw_reset)(struct sdhci_host *host);    // 硬件复位的方法
void    (*platform_suspend)(struct sdhci_host *host);    // 平台host的suspend方法
void    (*platform_resume)(struct sdhci_host *host);    // 平台host的resume方法
void    (*adma_workaround)(struct sdhci_host *host, u32 intmask);
void    (*platform_init)(struct sdhci_host *host);    // 平台host的初始化方法
void    (*check_power_status)(struct sdhci_host *host, u32 req_type);    // 检测总线的电源状态
#define REQ_BUS_OFF    (1 << 0)
#define REQ_BUS_ON    (1 << 1)
#define REQ_IO_LOW    (1 << 2)
#define REQ_IO_HIGH    (1 << 3)
int    (*execute_tuning)(struct sdhci_host *host, u32 opcode);    // 执行tuning操作的的方法
void    (*toggle_cdr)(struct sdhci_host *host, bool enable);
unsigned int    (*get_max_segments)(void);
void    (*platform_bus_voting)(struct sdhci_host *host, u32 enable);    // 平台总线投票的方法
void    (*disable_data_xfer)(struct sdhci_host *host);
void    (*dump_vendor_regs)(struct sdhci_host *host);
int    (*config_auto_tuning_cmd)(struct sdhci_host *host,
                  bool enable,
                  u32 type);
int    (*enable_controller_clock)(struct sdhci_host *host);
void    (*reset_workaround)(struct sdhci_host *host, u32 enable);

};

core层对接

static const struct mmc_host_ops sdhci_ops = {
    // post_req和pre_req是为了实现异步请求处理而设置的
    // 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
    // 具体参考《mmc core主模块》
.pre_req    = sdhci_pre_req,  
.post_req   = sdhci_post_req,
.request    = sdhci_request,    // host处理mmc请求的方法,在mmc_start_request中会调用
.set_ios    = sdhci_set_ios,   // 设置host的总线的io setting
.get_cd     = sdhci_get_cd,   // 检测host的卡槽中card的插入状态
.get_ro     = sdhci_get_ro,  // 获取host上的card的读写属性
.hw_reset   = sdhci_hw_reset,  // 硬件复位
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch    = sdhci_start_signal_voltage_switch,   // 切换信号电压的方法
.execute_tuning         = sdhci_execute_tuning,   // 执行tuning操作,为card选择一个合适的采样点
.card_event         = sdhci_card_event,
.card_busy  = sdhci_card_busy,   // 用于检测card是否处于busy状态
.enable     = sdhci_enable, // 使能host,当host被占用时(第一次调用mmc_claim_host)调用
.disable    = sdhci_disable,    // 禁用host,当host被释放时(第一次调用mmc_release_host)调用
.stop_request = sdhci_stop_request,   // 停止请求处理的方法
.get_xfer_remain = sdhci_get_xfer_remain,
.notify_load    = sdhci_notify_load,

};

##7.重要函数##
//启动线程

static void _mmc_detect_change(struct mmc_host *host, unsigned long delay,bool cd_irq)
{
    #ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
     #endif

/*
 * If the device is configured as wakeup, we prevent a new sleep for
 * 5 s to give provision for user space to consume the event.
 */
if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&
	device_can_wakeup(mmc_dev(host)))
	pm_wakeup_event(mmc_dev(host), 5000);

host->detect_change = 1;
mmc_schedule_delayed_work(&host->detect, delay);
}

底层硬件发现card插入状态发生变化而调用mmc_detect_change的时候,sd card插入状态监控,
当底层硬件发现card插入状态发生变化,例如sd card的插入或者拔出可以触发某个GPIO产生中断。
此时,可以在中断处理中调用mmc_detect_change来进行扫描mmc硬件总线,并且根据总线上的card状态变化进行处理

当mmc_host的mmc_bus_ops被设置的时候
这种情况下,说明mmc_bus已经和card绑定了。也就是之前已经有card插入并且初始化成功了。
那么此时,需要调用mmc_bus_ops中的detect方法(对于sd card来说就是mmc_sd_detect)来判断card是否被移除,并且进行相应的动作

mmc_rescan是通过调用mmc_host->mmc_host_ops->get_cd来获取当前card的插入状态的
一般来说有两种方式来获取到sd card当前的插入状态
(1)GPIO获取的方法
可以通过sd card的card detect引脚来判断当前是否有sd card插入
(2)host寄存器获取的方法
某些host在硬件上有识别sd card是否插入的能力。这种情况下,可以通过读取host的寄存器来获取到当前是否有sd card插入

插拔,函数是interrupt_t,初始化时候已经插卡,不会发生此中断

中断只处理mmc_detect_change,唤醒进程mmc_schedule_delayed_work

struct mmc_host
mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器。
struct mmc_card
mmc core由mmc设备抽象出来的card设备的结构体,用于代表一个mmc设备。
struct mmc_driver
用于代表一个card drive。
struct mmc_bus_ops
mmc_bus_ops表示mmc host在总线上的操作集合,由host的card 设备来决定,mmc type card、sd type card相应的操作集合是不一样的。
mmc_command
表示一个mmc命令包
mmc_data
表示一个mmc数据包
mmc_request
表示一个mmc请求,包括了mmc命令包(mmc_command)和mmc数据包(mmc_data)。
mmc_async_req
表示一个mmc异步请求,包括了mmc_request

内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及一个host class设备。接着执行card/block.c中,申请一个块设备
mmc总线操作相关函数,由于mmc卡支持多种总数据线,如SPI、SDIO、8LineMMC,而不同的总线的操作控制方式不尽相同,所以通过此结构与相应的总线回调函数相关联

执行mmc_init()及mmc_blk_init(),以对mmc设备及mmc块模块进行初始化。
然后在挂载mmc设备驱动时,执行驱动程序中的xx_mmc_probe(),检测host设备中挂载的sd设备。此时probe函数会创建一个host设备,然后开启一个延时任务mmc_rescan()

mmc_attach_sdio()、mmc_attach_sd()、mmc_attach_mmc()这三个函数分别是装载sdio设备,sd卡和mmc卡的。
在 sd卡中,驱动循环发送ACMD41、CMD55给卡,读取OCR寄存器,成功后,依次发送CMD2(读CID)、CMD3(得到RCA)、CMD9(读 CSD)、CMD7(选择卡)

mmci_host类比于sdhci_host

clock和pinctrl是由host driver自己管理,sdhci core并不参与

sdhci_host是指host控制器,mmc_host是提取控制器和core层对接部分的集合,mmc_host是抽象层
sdhci core要求host提供标准的访问硬件的一些方法。而这些方法就被定义在了struct sdhci_ops结构体内部
sdhci core只是提供了一些接口和符合mmc core的操作集方法给对应的host driver使用。由于各个host的硬件有所差异,所以实际和硬件交互的驱动部分还是在host driver中实现

##8.SDHC##

SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的设计标准,其寄存器偏移以及意义都有一定的规范,并且提供了对应的驱动程序,方便vendor进行host controller的开发。
vendor按照这套标准设计host controller之后,可以直接使用sdhci driver来实现host controller的使用,(qcom和samsung都使用了这套标准)。而vendor只需要实现平台相关的部分、如clock、pinctrl、power等等的部分即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值