调试的目标效果
在uboot阶段实现从固化程序加载nor flash里的uboot的镜像后,在从EMMC加载boot和文件系统。
调试的思路
1、先熟悉mmc子系统的加载驱动流程。
2、确认emmc硬件电路的接法以及连接逻辑性。
3、配置emmc相应的设备树、驱动、寄存器等。
4、在uboot阶段通过日志分析emmc是否初始化成功,以及挂载设备。
调试的过程
步骤一 查看芯片手册,获取固件加载流程:
步骤二 EMMC硬件连接电路:
步骤三 查看设备树的配置,特别需要注意时钟的配置和引脚的配置:
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 配置
-
max-frequency = <150000000>;
eMMC 普通模式 50M,eMMC HS200 最大支持 150M; -
supports-emmc;
此配置标识此插槽为 emmc 功能,为必须添加项。否则无法初始化 emmc 外设。 -
bus-width = <4>;
此配置同 SD 卡功能。 -
mmc-ddr-1_8v;
此配置表示支持 50MDDR 模式; -
mmc-hs200-1_8v;
此配置表示支持 HS200 模式; -
mmc-hs400-1_8v; mmc-hs400-enhanced-strobe
此两项配置表示支持 HS400 模式以及 HS400ES 模式,仅 RK3399 芯片支持。 -
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 的数据;如下图:
绿色:EMMC_CLK
黄色:EMMC_CMD: EMMC_CMD 空闲时一直处于高电平;
主控发出的波形:当最开始的两个电平有一高一低时,是主控发出去的命令;
EMMC卡响应的波形: 当最开始的两个电平有连续的两个低电平是表示卡端有响应;
其次主控和响应一般包含 48 个 bit 的数据,所以 48 个 clk 为一个完整的包。要确认的就是:主控发出
去命令包后,EMMC 卡端是否有响应。
LOG 分析
-
正确识别 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 卡里面的文件 -
开机不读卡,运行时拔插 OK:大概率时电源问题
例如:拔掉所有电源,发现查着 HDMI 发现有漏电到 VCC_SD 卡里面;或者使用外接电源进行测试。 -
挂载失败:
如果已经看到(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 文件系统的支持即可; -
概率性不识别:
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>;
} -
TF 卡已经 mount,但不能访问 TF 卡目录,看起来是卡文件系统问题,但卡在 Windows 下可以
访问。
请尝试使用 fsck 对 TF 卡做修复。 -
硬件问题,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等等的部分即可。