3. RK eMMC驱动初始化流程:设备树解析、时钟与复位使能、MMC控制器寄存器初始化、中断注册

好,咱们今天来聊聊RK平台上eMMC驱动的初始化流程。说实话,这部分是驱动开发的“地基”。地基没打好,后面跑起来全是坑。我见过不少工程师,上来就调读写速度,结果发现连设备都没识别到,回头一看,初始化流程里少配了一个时钟门控。

初始化流程,说白了就四步:设备树解析 → 时钟与复位使能 → 控制器寄存器初始化 → 中断注册。每一步都有讲究,咱们一个一个拆开讲。

核心要点:eMMC初始化失败,90%的问题出在前两步——设备树配错了,或者时钟没给够。别问我怎么知道的,我调过的板子,有一半都是这种“低级错误”。

3.1 设备树解析:驱动与硬件的“翻译官”

设备树(Device Tree)是什么?说白了,它就是驱动和硬件之间的“翻译官”。驱动代码是通用的,但你的板子上eMMC接在哪个控制器上、用哪个时钟源、电压是多少——这些信息,都得靠设备树告诉驱动。

在RK平台上,eMMC的设备树节点通常长这样:

&sdhci0 {
    status = "okay";
    bus-width = <8>;
    max-frequency = <200000000>;
    non-removable;
    pinctrl-names = "default";
    pinctrl-0 = <&emmc_pins>;
    cap-mmc-highspeed;
    mmc-hs200-1_8v;
    mmc-hs400-1_8v;
    mmc-hs400-enhanced-strobe;
};

这里有几个关键字段,我重点说一下:

  • bus-width:数据总线宽度。eMMC一般用8位,SD卡用4位。配错了,性能直接砍半。
  • max-frequency:最大时钟频率。RK3588的eMMC控制器理论上支持200MHz,但实际要看PCB走线质量。我遇到过一块板子,跑到150MHz就丢数据,最后降频到100MHz才稳定。
  • non-removable:这个标志告诉驱动,eMMC是焊死在板子上的,别去检测卡插入/拔出。少了这个,驱动会多走一套热插拔流程,浪费启动时间。
  • mmc-hs400-enhanced-strobe:HS400模式需要的增强选通信号。RK3588支持这个特性,但前提是eMMC芯片本身也支持。

驱动解析设备树的入口函数是mmc_of_parse()。它会遍历这些属性,填充到struct mmc_host的对应字段里。我个人习惯在解析完成后,加一段调试打印,把解析到的参数都打出来——这样一眼就能看出设备树有没有配错。

小技巧:在mmc_of_parse()之后,加一句dev_info(dev, "bus_width: %d, max_freq: %d\n", host->bus_width, host->max_freq);。启动时看内核日志,比对着设备树文件翻来覆去快多了。

3.2 时钟与复位使能:给eMMC“通电”

设备树解析完了,接下来要干的事——给eMMC控制器和芯片供电、供时钟、释放复位。这一步没做好,后面的寄存器操作全是白搭。

RK平台的时钟树比较复杂。eMMC控制器通常需要三个时钟:

时钟名称 作用 典型频率
core_clk 控制器核心时钟 200MHz - 400MHz
bus_clk AHB/AXI总线接口时钟 100MHz - 200MHz
card_clk eMMC芯片工作时钟 0Hz - 200MHz(可调)

驱动里通过clk_get()和clk_prepare_enable()来获取和使能这些时钟。代码大概是这样:

host->core_clk = devm_clk_get(dev, "core");
if (IS_ERR(host->core_clk))
    return PTR_ERR(host->core_clk);
clk_prepare_enable(host->core_clk);

host->bus_clk = devm_clk_get(dev, "bus");
if (IS_ERR(host->bus_clk))
    return PTR_ERR(host->bus_clk);
clk_prepare_enable(host->bus_clk);

复位使能这块,RK平台用的是reset_control框架。先获取复位句柄,然后释放复位:

host->rstc = devm_reset_control_get(dev, NULL);
if (!IS_ERR(host->rstc)) {
    reset_control_deassert(host->rstc);
    usleep_range(10, 20);  // 等待复位完成
}

注意:释放复位后,一定要加一个小延时。我曾经遇到过一个问题:复位刚释放,紧接着就去写寄存器,结果写不进去。后来发现是硬件还没稳定。加个10微秒的延时,问题就解决了。

3.3 MMC控制器寄存器初始化:让硬件“就位”

时钟和复位搞定后,就可以操作控制器的寄存器了。RK平台的eMMC控制器是基于Synopsys DesignWare IP的,寄存器布局和标准DW_MMC基本一致。

初始化寄存器,核心是以下几个:

  • CTRL(控制寄存器):配置控制器的工作模式、中断使能等。
  • CLKDIV(时钟分频寄存器):设置初始时钟频率。初始化阶段一般用400kHz,等识别到eMMC后再切到高速。
  • RINTSTS(原始中断状态寄存器):初始化前先清掉所有pending的中断,防止历史残留影响判断。
  • INTMASK(中断屏蔽寄存器):使能我们需要的中断类型,比如传输完成、错误等。

我一般会写一个dw_mmc_init()函数,把这些寄存器的初始化逻辑封装起来。代码片段如下:

static void dw_mmc_init(struct dw_mmc_host *host)
{
    void __iomem *regs = host->regs;

    // 1. 复位控制器
    writel(SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET |
           SDMMC_CTRL_DMA_RESET, regs + SDMMC_CTRL);
    readl_poll_timeout(regs + SDMMC_CTRL, val,
                       !(val & (SDMMC_CTRL_RESET |
                                SDMMC_CTRL_FIFO_RESET |
                                SDMMC_CTRL_DMA_RESET)),
                       1, 10000);

    // 2. 设置初始时钟分频(400kHz)
    writel(SDMMC_CLKDIV_DIV(host->bus_hz / 400000 / 2),
           regs + SDMMC_CLKDIV);

    // 3. 清中断状态
    writel(0xFFFFFFFF, regs + SDMMC_RINTSTS);

    // 4. 使能必要的中断
    writel(SDMMC_INT_CD | SDMMC_INT_RXDR | SDMMC_INT_TXDR |
           SDMMC_INT_DTO | SDMMC_INT_ERROR,
           regs + SDMMC_INTMASK);
}

这里有个细节:复位控制器时,要轮询等待复位完成。DW_MMC的复位是自清零的,写1到复位位后,硬件复位完成会自动清0。如果不等待,直接往下走,可能会出问题。

3.4 中断注册:让驱动“会说话”

寄存器初始化完了,控制器已经能工作了。但它怎么通知CPU“我活干完了”或者“我出错了”?靠中断。

中断注册的接口是devm_request_irq():

ret = devm_request_irq(dev, host->irq, dw_mmc_irq_handler,
                       0, "dw_mmc", host);
if (ret) {
    dev_err(dev, "failed to request irq %d\n", host->irq);
    return ret;
}

中断处理函数dw_mmc_irq_handler()里,要读取中断状态寄存器,判断是哪种中断,然后做相应处理:

static irqreturn_t dw_mmc_irq_handler(int irq, void *dev_id)
{
    struct dw_mmc_host *host = dev_id;
    u32 status;

    status = readl(host->regs + SDMMC_RINTSTS);
    writel(status, host->regs + SDMMC_RINTSTS);  // 写1清中断

    if (status & SDMMC_INT_DTO) {
        // 数据传输完成
        dw_mmc_transfer_complete(host);
    }
    if (status & SDMMC_INT_ERROR) {
        // 错误处理
        dw_mmc_error_handler(host);
    }
    // ... 其他中断处理

    return IRQ_HANDLED;
}

关键点:中断处理函数里,一定要先读中断状态,然后写1清除。顺序不能反。如果先清再读,可能会漏掉在清除瞬间新到达的中断。这个坑我踩过,调试了整整两天才发现是中断丢失了。

3.5 初始化流程全景图

说了这么多,咱们用一张图把整个流程串起来。这张图是我自己画的一个简化版,但核心逻辑都在里面了。

RK eMMC驱动初始化流程 1. 设备树解析 mmc_of_parse() 解析 bus-width、max-frequency 等 2. 时钟与复位使能 clk_prepare_enable() + reset_control_deassert() 3. 控制器寄存器初始化 CTRL / CLKDIV / RINTSTS / INTMASK 配置 4. 中断注册 devm_request_irq() + 中断处理函数 5. 初始化完成 等待 eMMC 卡识别与配置 注意:每一步失败都应返回错误码,并释放已申请的资源,避免内存泄漏或状态残留

3.6 避坑指南:我踩过的几个坑

最后,分享几个我在实际项目中遇到的坑,希望能帮你少走弯路。

坑1:设备树里忘了配pinctrl

有一次,我移植一个BSP包,设备树里只配了时钟和频率,忘了配pinctrl。结果eMMC初始化时,数据线引脚默认被配置成了GPIO功能,读写全是乱码。查了两天才发现是引脚复用没配。所以,设备树里pinctrl-0一定要配,而且要确认引脚号和SoC的pinmux表一致。

坑2:时钟频率配得太高,初始化失败

eMMC初始化阶段,协议规定时钟不能超过400kHz。但有些工程师图省事,直接把最终的工作频率写进了设备树的max-frequency里,结果控制器一开始就用200MHz去和eMMC通信——eMMC芯片根本反应不过来,CMD0都发不出去。正确的做法是:初始化阶段用400kHz,识别到卡之后再通过mmc_set_clock()切换到高速。

坑3:中断共享导致误触发

RK平台上,有些eMMC控制器和SD卡控制器共享同一个中断号。如果中断处理函数里不判断中断状态寄存器,可能会把SD卡的中断当成eMMC的来处理。我建议在中断处理函数开头加一句if (!status) return IRQ_NONE;,避免误处理。

嗯,初始化流程就讲到这里。这四步走完,eMMC控制器就已经准备好和eMMC芯片通信了。接下来就是卡识别和模式切换——那是下一阶段的事。但记住,初始化是基础,基础不牢,地动山摇。