API GUIDE / MSC / LL

MSC LL 接口

msc ll api

本文面向 BSP、SoC 驱动、MSC HAL 维护者和高级调试开发者,说明 G32S10 MSC 的 LL(Low Level)寄存器访问接口。ll_msc.h 只提供 static inline 的寄存器读写、位域打包和状态查询 helper,不是可直接承担 SD/eMMC 协议、命令序列、DMA descriptor、cache、超时或并发管理的事务驱动。

API 头文件:

  • code/include/drivers/g32s10/msc/ll_msc.h
  • code/include/drivers/g32s10/msc/msc_regs.h(寄存器布局、MSC0/MSC1 实例、位域掩码和公开枚举)

ll_msc.h 没有对应 .c 文件,所有接口在包含头文件后内联展开。寄存器地址、字段宽度和控制序列必须与 G32S10 MSC 规格一致;本文不展开 SD/eMMC 协议状态机、卡初始化、命令参数语义、ADMA descriptor 格式、cache 维护、GPIO pinmux、时钟树和板级电气参数。

LL 层不负责参数校验、错误码、软件状态机、互斥、超时、时钟使能、pinmux、卡上下电/复位、DMA 地址转换、cache 操作或中断注册。普通应用应优先使用 hal_msc.h 或 MSC 中间件;直接调用 LL 接口时,调用方承担全部正确性责任。


1. 模块概览

ll_msc.h 是 MSC HAL 和中间件的寄存器级基础层,支持 MSC0 和 MSC1 两个控制器实例:

#define MSC0 ((msc_regs_t *)(uintptr_t)MSC0_IOBASE) /* 0x13300000 */
#define MSC1 ((msc_regs_t *)(uintptr_t)MSC1_IOBASE) /* 0x13310000 */

ll_msc_is_instance() 仅接受这两个实例。其它寄存器块指针不被 LL 层支持;控制器是否在板级可用、时钟和 pinmux 是否已配置,需要调用方保证。

组件 作用
ll_msc 提供 296 个 static inline 寄存器 helper、位域 set/get、状态检查和 PIO 数据端口访问
msc_regs 定义 msc_regs_t、MSC0/MSC1、寄存器偏移、字段 mask/shift 和配置枚举
hal_msc 在 LL 层之上提供控制器发现、IRQ 分发、总线时序、DMA 模式和调谐控制
MSC 中间件 在 HAL 之上完成 SD/eMMC/SDIO 协议、卡初始化、DMA/cache、分区和文件系统集成

LL 层的使用边界如下:

  • 除 ll_msc_is_instance() 外,所有接口的第一个参数都是 msc_regs_t *mscx,且必须指向 MSC0 或 MSC1。LL 层不检查空指针,也不验证实例。
  • 接口不维护软件句柄、初始化状态或请求队列。每次调用都直接访问当前寄存器;硬件是否允许下发新命令要通过 PSTATE_REG 和中断状态判断。
  • 返回值只有 void、bool、uint8_t、uint16_t、uint32_t、uint64_t 和少量枚举,没有 error_code_t。写接口成功与否无法通过返回值判断。
  • 字段 setter 使用读改写,只修改目标位域;全寄存器 setter 会覆盖整个寄存器,包括保留位。ll_msc_host_control2_set() 会额外屏蔽保留位 RSVD_KEEP_0,但仍要求调用方提供完整目标值。
  • 读改写不是原子操作。任务与 ISR 共享同一寄存器时,必须由调用方提供临界区、统一所有者或其它串行化措施。
  • CMD_R 是写后触发命令的寄存器。LL 不提供单字段写 helper,必须先用 ll_msc_command_make() 构造成完整 word,再通过 ll_msc_command_set() 一次写入。
  • SW_RST_R 的位由硬件在复位完成后自动清零;FORCE_* 是 W1S,NORMAL_INT_STAT_R/ERROR_INT_STAT_R 中的可清事件是 W1C。写 0 通常不会清除或置位对应事件。
  • NORMAL_INT_STAT_R.CARD_INTERRUPT 是 DAT1 的只读电平状态,不能用 W1C 清除;停止上报必须关闭对应 signal enable。
  • LL 层不等待命令完成、数据完成、DMA 边界、调谐完成或软件复位完成,也不提供重试和超时。所有完成与错误处理由调用方完成。
  • 控制器正在执行命令或数据传输时,除读取状态、响应和数据端口外,不应修改命令、传输、时钟、总线宽度、DMA、调谐或低功耗配置。
  • LL 层不负责 CPM 时钟、GPIO pinmux、卡供电/复位、外部 1.8 V 稳压器、ADMA descriptor 构造、cache clean/invalidate 或 IRQ 注册。

本文覆盖 ll_msc.h 的 296 个公开内联接口,按其访问的寄存器分为 16 个功能组:

功能组 数量 主要寄存器 用途
实例检查和原始访问 7 — 判断实例,按字节偏移读写 8/16/32-bit 寄存器
传输参数和 XFER_MODE 37 SDMASA、BLOCKSIZE、BLOCKCOUNT、ARGUMENT、XFER_MODE 块参数、命令参数、方向、自动命令、DMA 和响应行为
命令和响应 15 CMD、RESP01/23/45/67 编码并触发命令,读取响应寄存器
数据端口和 PSTATE 16 BUF_DATA、PSTATE PIO word 访问,命令/DAT/缓冲区状态查询
HOST_CTRL1 17 HOST_CTRL1 卡检测、总线宽度、DMA 选择、高速模式
电源控制 12 PWR_CTRL VDD1/VDD2 电压选择和供电使能
Block gap 和唤醒 23 BGAP_CTRL、WUP_CTRL block gap、read wait、继续/停止请求和唤醒事件
时钟、超时和复位 20 CLK_CTRL、TOUT_CTRL、SW_RST 内部/卡时钟、PLL、低功耗门控、DAT 超时和软件复位
中断控制 15 NORMAL/ERROR_INT_STAT、status/signal enable 状态读取、W1C 清除和中断两级使能
HOST_CTRL2 28 HOST_CTRL2 异步中断、寻址模式、Host v4、ADMA3、采样时钟、调谐和 UHS 模式
能力寄存器 24 CAPABILITIES1/2、CURR_CAPABILITIES1/2 DMA、速率、总线宽度、调谐和供电能力查询
Force event 和 ADMA 9 FORCE_*、ADMA_ERR_STAT、ADMA_SA、ADMA_ID 强制错误事件、ADMA 错误状态和 64-bit 地址
Preset、Slot 和版本 13 PRESET_*、SLOT_INTR_STATUS、HOST_CNTRL_VERS、MSHC_VER_* 预设时序、slot 中断和版本识别
MSHC 和 eMMC 控制 28 MSHC_CTRL、EMMC_CTRL 时钟门控、命令冲突检查、CQE、eMMC reset/CRC/卡类型
调谐控制 23 HOST_CTRL2、AT_CTRL、AT_STAT 执行调谐、软件调谐、窗口选择和采样相位
低功耗控制 9 LP_CTRL 写等待阈值和空闲卡时钟门控延时

2. 典型流程

2.1 前置条件与包含方式

LL 头文件没有独立的 Kconfig 选项和源文件。通过 MSC HAL 使用时,hal_msc.h 已经包含 ll_msc.h;直接使用寄存器接口时,可显式包含:

#include "ll_msc.h"

调用任何会访问硬件的 LL 接口前,调用方必须完成以下准备:

  • 确认使用 MSC0 还是 MSC1,并完成 CLK、CMD、DAT0..DAT3 及可选卡检测信号的 pinmux。
  • 使能 MSC 总线时钟和门控时钟,选择正确的时钟父源、频率和 RX/TX 相位。LL 层只操作控制器内部的 CLK_CTRL_R,不配置 CPM。
  • 完成 SD/eMMC 供电、复位和可选 1.8 V 切换。PWR_CTRL_R 的供电位只反映控制器请求,不代表外部稳压器已经稳定。
  • 确认控制器不在传输中,且 CMD/DAT inhibit 条件允许修改配置。
  • 如使用中断,完成平台 IRQ 注册,并在控制器内同时配置 status latch enable 和 signal enable。

2.2 控制器初始化顺序

LL 层没有 init 函数。下面的顺序展示寄存器层的依赖关系;具体上电、时钟、卡检测和电压切换顺序必须结合板级设计和控制器规格:

static bool app_msc_low_level_init(msc_regs_t *mscx)
{
    if (!ll_msc_is_instance(mscx)) {
        return false;
    }

    /* 此时应已使能 CPM 时钟、完成 pinmux,并关闭卡时钟。 */
    ll_msc_sd_clock_disable(mscx);
    ll_msc_internal_clock_disable(mscx);

    ll_msc_software_reset(mscx, MSC_SOFTWARE_RESET_ALL);
    while (ll_msc_is_software_reset_pending(mscx, MSC_SOFTWARE_RESET_ALL)) {
        /* 调用方必须增加超时,避免硬件异常时永久等待。 */
    }

    ll_msc_interrupt_status_clear(mscx, 0xFFFFU, 0xFFFFU);
    ll_msc_timeout_count_set(mscx, 0xEU);
    ll_msc_host_control1_set(mscx, 0U);
    ll_msc_host_control2_set(mscx, 0U);

    ll_msc_internal_clock_enable(mscx);
    while (!ll_msc_is_internal_clock_stable(mscx)) {
        /* 同上,必须由调用方设置超时。 */
    }

    /* 电压、总线宽度、UHS 模式、DMA 选择和卡时钟按板级/协议顺序配置。 */
    return true;
}

注意:

  • 软复位会清除控制器配置。复位完成后需要重新建立时钟、电源、总线宽度、中断和 DMA 配置。
  • 内部时钟稳定前不能把 SD_CLK_EN 作为有效卡时钟使用。卡时钟的开关时机还应满足 SD/eMMC 初始化要求。
  • ll_msc_software_reset_get() 返回当前仍挂起的复位请求,不是“最后一次复位状态”。某一位清零表示对应域已回到 working 状态。
  • 初始化代码只展示 LL 调用关系,不包含 CPM、GPIO、稳压器、卡检测消抖或协议层命令。

2.3 命令与 PIO 数据流程

一次非 DMA 命令的基本顺序如下:

  1. 通过 ll_msc_is_command_inhibited() 或 ll_msc_is_data_command_inhibited() 确认允许下发命令。
  2. 写入 ARGUMENT_R。参数由协议层按 SD/eMMC 命令定义生成。
  3. 带数据时先配置 BLOCKSIZE_R、BLOCKCOUNT_R 和 XFER_MODE_R;不带数据时也要明确清除上一次残留的 DMA、多块和自动命令位。
  4. 使用 ll_msc_command_make() 生成完整的 CMD_R word,再调用 ll_msc_command_set() 写入。
  5. 等待 NORMAL_INT_STAT_R.CMD_CMPLT。需要响应时,在命令完成之后读取 RESP01/23/45/67。
  6. 读数据时,在 PSTATE_REG.BUF_RD_EN 置位后从 BUF_DATA_R 读取;写数据时,在 BUF_WR_EN 置位后写入。必须搬运完整的 block_size × block_count 数据量。
  7. 等待 XFER_CMPLT,检查 ERROR_INT_STAT_R,最后清除已处理的 W1C 状态。

PIO 端口按 32-bit word 访问,但 word 中字节顺序和 block 内偏移由卡协议和命令定义决定。LL 不判断 FIFO 水位、剩余字节数或传输是否已越界。

2.4 DMA 配置顺序

DMA 数据通路需要同时配置传输模式、控制器 DMA 选择、地址寄存器和 descriptor(若使用 ADMA):

  1. 确认控制器空闲,并根据协议选择 PIO 或 DMA。
  2. 使用 ll_msc_dma_select_set() 选择 SDMA、32/64-bit ADMA2 或 ADMA2/ADMA3。选择值受 HOST_CTRL2_R.HOST_V4_EN 影响。
  3. SDMA:写 SDMASA_R 和 SDMA_BUF_BDARY,并在边界事件后更新地址。
  4. ADMA2:在系统内存中构造合法 descriptor,满足对齐要求,把物理地址写入 ADMA_SA_LOW/HIGH,并完成 cache clean。
  5. ADMA3:把 integrated descriptor 地址写入 ADMA_ID_LOW/HIGH,并按 Host v4/CQE 要求配置。
  6. 在 XFER_MODE_R 中置 DMA_EN 后写 CMD_R 触发传输。

LL 层只写寄存器,不构造 descriptor,不转换虚拟地址,不维护 cache,也不在 DMA boundary 自动续写 SDMA 地址。Host v4 下 SDMASA_R 还可表示 32-bit block count,使用前必须确认当前模式,避免把计数写到地址字段。

2.5 中断处理

轮询模式可以只读取状态寄存器;中断模式还需要正确区分 status latch 和 IRQ signal:

  • NORMAL_INT_STAT_EN_R / ERROR_INT_STAT_EN_R 决定哪些事件形成锁存状态。
  • NORMAL_INT_SIGNAL_EN_R / ERROR_INT_SIGNAL_EN_R 决定哪些锁存状态路由到控制器 IRQ 输出。
  • 中断服务程序应先读取 ll_msc_interrupt_status_get(),再处理命令完成、传输完成、DMA boundary、buffer ready 或错误。
  • 对可写 1 清除的位,使用 ll_msc_normal_interrupt_status_clear()、ll_msc_error_interrupt_status_clear() 或 ll_msc_interrupt_status_clear() 清除。
  • CARD_INTERRUPT 不能通过 W1C 清除。若要停止 SDIO 卡中断,应关闭对应 signal enable,并在卡侧处理中断源。

2.6 软件复位、调谐和低功耗

软件复位通过 SW_RST_R 请求,可分别复位 DAT、CMD 或整个控制器。请求后调用方必须轮询 ll_msc_is_software_reset_pending() 并设置超时;复位完成后重新配置受影响寄存器。

高频 SD/eMMC 模式需要调谐时,通常先配置 HOST_CTRL2_R、AT_CTRL_R 和调谐数据块,再置 EXEC_TUNING,并由协议层发送调谐命令。LL 只提供启动、清除和状态查询,不发送调谐命令,也不等待完成。硬件自动调谐之外,还可以启用软件调谐并通过 PH_SEL_CODE 选择采样相位。

低功耗配置由 CLK_CTRL_R.CARD_LP_EN、LP_CTRL_R 和 MSHC_CTRL_R.SW_CG_DIS 共同决定。修改门控阈值、关闭时钟或进入低功耗状态前,必须确认没有进行中的命令、PIO 访问、DMA 或调谐。


3. 配置和关键类型

3.1 实例和寄存器布局

msc_regs_t 由 struct msc_regs 定义,两个公开实例为 MSC0 和 MSC1。寄存器块按 SDHCI/厂商扩展布局组织,主要寄存器如下:

寄存器 偏移 访问属性 说明
SDMASA_R 0x000 R/W v3 SDMA 系统地址;Host v4 下可表示 32-bit block count
BLOCKSIZE_R 0x004 R/W block size 和 SDMA boundary
BLOCKCOUNT_R 0x006 R/W 16-bit block count
ARGUMENT_R 0x008 R/W 下一条命令的 32-bit 参数
XFER_MODE_R 0x00C R/W 数据方向、多块、自动命令、DMA 和响应检查
CMD_R 0x00E R/W,写触发 命令索引、类型、数据/响应选择和检查位
RESP01_R~RESP67_R 0x010~0x01C RO 最多 136-bit 响应
BUF_DATA_R 0x020 R/W 非 DMA PIO 数据端口
PSTATE_REG 0x024 RO 命令、DAT、缓冲区和采样线状态
HOST_CTRL1_R 0x028 R/W 卡检测、位宽、DMA 选择和高速模式
PWR_CTRL_R 0x029 R/W VDD1/VDD2 电压和供电控制
BGAP_CTRL_R 0x02A R/W block gap、read wait、continue 和 stop
WUP_CTRL_R 0x02B R/W 卡移除、插入和卡中断唤醒
CLK_CTRL_R 0x02C R/W 内部时钟、SD clock、PLL 和卡时钟低功耗
TOUT_CTRL_R 0x02E R/W DAT timeout 编码
SW_RST_R 0x02F W1S/RO DAT、CMD 或控制器软复位,完成后自动清零
NORMAL_INT_STAT_R 0x030 RO/W1C 正常事件状态,混合只读和 W1C 位
ERROR_INT_STAT_R 0x032 W1C 错误事件状态
NORMAL/ERROR_INT_STAT_EN_R 0x034/0x036 R/W 事件状态锁存使能
NORMAL/ERROR_INT_SIGNAL_EN_R 0x038/0x03A R/W 事件到 IRQ 输出的路由使能
AUTO_CMD_STAT_R 0x03C RO 自动命令错误详情
HOST_CTRL2_R 0x03E R/W 寻址、Host v4、采样时钟、调谐和 UHS 模式
CAPABILITIES1/2_R 0x040/0x044 RO 固定控制器能力
CURR_CAPABILITIES1/2_R 0x048/0x04C RO 当前电压条件下能力
FORCE_AUTO_CMD_STAT_R 0x050 WO/W1S 强制自动命令错误事件
FORCE_ERROR_INT_STAT_R 0x052 WO/W1S 强制错误事件
ADMA_ERR_STAT_R 0x054 RO ADMA 错误状态机快照和长度错误
ADMA_SA_LOW/HIGH_R 0x058/0x05C R/W 64-bit ADMA 系统地址
PRESET_*_R 0x060~0x06E、0x074 RO 各速度模式预设频率、时钟源和驱动强度
ADMA_ID_LOW/HIGH_R 0x078/0x07C R/W 64-bit ADMA3 integrated descriptor 地址
SLOT_INTR_STATUS_R 0x0FC RO slot 中断状态
HOST_CNTRL_VERS_R 0x0FE RO Host 规范版本和厂商版本
MSHC_VER_ID/TYPE_R 0x500/0x504 RO 厂商 MSHC 版本信息
MSHC_CTRL_R 0x508 R/W 软件时钟门控和命令冲突检查
EMMC_CTRL_R 0x52C R/W eMMC 卡选择、复位、CRC 和 CQE 控制
AT_CTRL_R 0x540 R/W 自动/软件调谐、窗口阈值和相位切换延时
AT_STAT_R 0x544 R/W 调谐状态和软件选择的采样相位
LP_CTRL_R 0x550 R/W 低功耗阈值和空闲时钟门控计数

ll_msc_reg8_write()、ll_msc_reg16_write() 和 ll_msc_reg32_write() 的 offset 是相对于 mscx 的字节偏移,不是字段编号或 word index。访问宽度和地址对齐必须与目标寄存器匹配。

3.2 传输参数和 XFER_MODE

ll_msc_transfer_mode_make() 负责把常用控制项打包为 XFER_MODE_R。字段如下:

位 字段 作用
0 DMA_EN 允许本次带数据命令启动所选 DMA
1 BLK_CNT_EN 使用 BLOCKCOUNT_R 限制传输块数
3:2 AUTO_CMD_EN 禁用、CMD12、CMD23 或由硬件自动选择
4 DATA_XFER_DIR 1 为卡到主机读,0 为主机到卡写或无数据
5 MULTI_BLK_SEL 多块选择;单块传输清零
6 RESP_TYPE 响应错误检查选择 R1 或 R5
7 RESP_ERR_CHK_EN 使能 R1/R5 响应错误位检查
8 RESP_INT_DIS 禁止 response/command complete 中断

ll_msc_transfer_mode_set() 覆盖整个 16-bit word,不会保留原值。若只想修改一个字段,可使用对应的 enable/disable/select/set 位域 helper;这些 helper 是读改写,仍需调用方串行化。

参数/接口 约束
BLOCKSIZE_R.XFER_BLOCK_SIZE 0 表示无数据阶段;HAL 当前把数据请求限制在 1~2048 字节,memory card 常用 512 字节
BLOCKCOUNT_R.BLOCK_CNT 16-bit 块数;Host v4 可把 0 与 SDMASA_R 的 32-bit count 组合使用
SDMA_BUF_BDARY 4 KiB~512 KiB,到达边界时硬件置 DMA_INT
RESP_ERR_CHK_EN 只对 R1/R5 有意义,不应用于调谐命令
AUTO_CMD_EN SDIO 传输应使用 MSC_AUTO_COMMAND_DISABLED
DMA_EN 选择 SDMA/ADMA 时置位;PIO 模式清零

3.3 命令编码和响应

ll_msc_command_make() 打包 CMD_R:

位 字段 作用
13:8 CMD_INDEX 命令号,0~63
7:6 CMD_TYPE normal、suspend、resume 或 abort
5 DATA_PRES_SEL 命令包含 DAT 数据阶段
4 CMD_IDX_CHK_EN 检查响应中的命令索引
3 CMD_CRC_CHK_EN 检查响应 CRC
2 SUB_CMD_FLAG 标记 sub-command
1:0 RESP_TYPE_SEL 无响应、136-bit、48-bit 或 48-bit busy

ll_msc_command_set() 写入 CMD_R 会产生命令下发副作用。LL 不检查 PSTATE_REG.CMD_INH/CMD_INH_DAT,也不保证前一条命令已经完成。读取 CMD_R 只能得到当前控制字段,不能作为完成状态。

响应寄存器映射如下:

API 寄存器 正常命令响应位 自动命令说明
ll_msc_response01_get() RESP01_R [39:8] —
ll_msc_response23_get() RESP23_R [71:40] —
ll_msc_response45_get() RESP45_R [103:72] —
ll_msc_response67_get() RESP67_R [135:104] 自动命令的 32-bit 响应在此寄存器
ll_msc_response_get() 由枚举选择 按索引返回对应 word 越界索引回退到 RESP67_R

3.4 PSTATE 和中断状态

PSTATE_REG 是实时快照,常用状态位包括:

状态 API 说明
CMD_INH ll_msc_is_command_inhibited() CMD_R 下发被禁止
CMD_INH_DAT ll_msc_is_data_command_inhibited() 使用 DAT 线的命令被禁止
DAT_LINE_ACT ll_msc_is_data_line_active() DAT 线正在传输或 busy
RD_XFER_ACT/WR_XFER_ACT ll_msc_is_read_transfer_active() / ll_msc_is_write_transfer_active() 当前读/写传输活动
BUF_RD_EN/BUF_WR_EN ll_msc_is_read_buffer_ready() / ll_msc_is_write_buffer_ready() PIO 端口可读/可写
RE_TUNE_REQ ll_msc_is_retune_requested() 硬件请求重新调谐
SUB_CMD_STAT ll_msc_is_sub_command_active() 当前为 sub-command 状态
CMD_ISSU_ERR ll_msc_is_command_issue_error() 命令下发错误
HOST_REG_VOL ll_msc_is_host_regulated_voltage_stable() 主机稳压电压状态

中断状态由 normal 和 error 两个 16-bit 寄存器组成;ll_msc_interrupt_status_get() 组合为 ERROR[31:16] | NORMAL[15:0]。使用中断时必须同时配置 status enable 和 signal enable:只配置 signal 而事件状态没有锁存,或只锁存而没有 signal,都得不到预期 IRQ。

NORMAL_INT_STAT_R 的 CMD_CMPLT、XFER_CMPLT、DMA_INT、BGAP_EVNT、BUF_RD_RDY、BUF_WR_RDY 等是可清除事件;ERR_INT、CARD_INTERRUPT 是状态位。清除接口应只写入需要清除的 W1C 位,避免把只读状态误当作可清事件处理。

3.5 主机控制、电源和时钟

HOST_CTRL1_R 主要字段:

  • CARD_DETECT_SIG_LVL 选择卡检测测试电平或输入引脚。
  • CARD_DETECT_TEST_LVL 在测试模式下模拟插入/移除。
  • EXT_DAT_XFER 与 DAT_XFER_W 组合 1/4/8-bit 总线宽度。G32S10 板级通常只提供 DAT0..DAT3;不要仅因为 LL 提供 8-bit helper 就选择 8-bit。
  • DMA_SEL 选择 SDMA/ADMA2/ADMA3;值 1 保留。
  • HS_EN 选择 high-speed timing。

HOST_CTRL2_R 主要字段:

  • UHS_MODE_SEL 选择 SD 或 eMMC 速度模式;同一编码在 SD/eMMC 下名称不同。
  • SIGNALING_1V8_EN 是电压 signaling 位,不等于采样时钟选择。
  • SMP_CLK_SEL 选择 tuned 或 fixed sampling clock。
  • EXEC_TUNING 启动执行调谐,硬件完成后清零。
  • ADDRESSING 选择 32/64-bit 系统寻址,需与 Host v4 和硬件能力一致。
  • HOST_V4_EN 改变 SDMA、ADMA 选择、系统寻址和 32-bit block count 语义。
  • CMD23_EN 选择 ADMA3 使用自动 CMD23 或自动 CMD12。
  • ADMA2_LEN_MD 选择 26-bit 或 16-bit descriptor length。

PWR_CTRL_R 只提供控制器侧的电压选择和供电请求。VDD1 编码随 SD/eMMC 模式不同而不同,VDD2 仅用于 UHS-II 1.8 V。LL 不检查外部电源是否已经稳定,也不保证先关时钟再切换电压。

CLK_CTRL_R 同时包含控制位和状态位:INTERNAL_CLK_EN 请求内部时钟,INTERNAL_CLK_STABLE 是只读稳定状态,SD_CLK_EN 驱动卡时钟,PLL_ENABLE 请求时钟 PLL,CARD_LP_EN 允许空闲卡时钟门控。

3.6 DMA 和地址

msc_dma_select_t 的编码受 Host v4 模式影响:

编码 v3 兼容模式 Host v4 模式
0 SDMA SDMA
2 32-bit ADMA2 ADMA2
3 64-bit ADMA2 ADMA2 或 ADMA3

常用接口和约束:

  • ll_msc_sdma_address_set()/get() 访问 SDMASA_R。Host v4 置位后,同一寄存器表示 32-bit block count,不要同时把它当 SDMA 地址。
  • ll_msc_32bit_block_count_set()/get() 仅在 Host v4 语义下使用;选择该路径时通常还需要让 BLOCKCOUNT_R 为 0。
  • ll_msc_sdma_buffer_boundary_set() 设置 SDMA 边界;边界事件后需要更新系统地址。
  • ll_msc_adma_address_set()/get() 组合 ADMA_SA_LOW/HIGH,地址必须是可供控制器访问的物理地址并满足 descriptor 对齐要求。
  • ll_msc_adma_id_address_set()/get() 组合 ADMA_ID_LOW/HIGH,用于 ADMA3 integrated descriptor。
  • ll_msc_adma_error_status_get()、ll_msc_is_adma_length_error() 和 ll_msc_adma_error_state_get() 用于错误诊断;LL 不自动停止或恢复 DMA。

使用 DMA 前必须完成 descriptor 构造、地址转换、对齐检查和 cache 维护。传输结束后如果 CPU 要读取 DMA 目标缓冲区,还必须按平台 cache 规则执行 invalidate。

3.7 能力、预设和版本

CAPABILITIES1_R 报告固定能力,CAPABILITIES2_R 报告 SDR/DDR、调谐和 ADMA3 能力。CURR_CAPABILITIES1/2_R 可能随当前电压条件变化,不能简单用固定能力代替当前能力。

类型/接口 含义
msc_speed_mode_t SD/eMMC 共用 UHS_MODE_SEL 编码
msc_preset_index_t 选择 PRESET_INIT~PRESET_UHS2_HS400 寄存器
ll_msc_max_block_length_get() 返回 0/1/2,对应 512/1024/2048 字节;3 保留
ll_msc_max_current_1v8_get()、ll_msc_max_current_3v0_get()、ll_msc_max_current_3v3_get() 返回 SDHCI current 原始编码,不是毫安值
ll_msc_preset_value_get() 返回 16-bit 预设寄存器原值
ll_msc_preset_driver_strength_get() 0 TypeB、1 TypeA、2 TypeC、3 TypeD
ll_msc_preset_clock_generator_select_get() 选择 programmable 或 fixed clock generator
ll_msc_preset_frequency_select_get() 返回 10-bit frequency select
ll_msc_host_vendor_version_get()、ll_msc_host_spec_version_get() 读取 Host 厂商/规范版本字段
ll_msc_mshc_version_id_get()、ll_msc_mshc_version_id_value_get()、ll_msc_mshc_version_type_get()、ll_msc_mshc_version_type_value_get() 读取厂商 MSHC 版本寄存器

预设寄存器是只读值,LL 没有预设寄存器写接口。是否使用 preset 以及如何把 preset 编码应用到 CPM/控制器,由 HAL 或控制器层决定。

3.8 eMMC、CQE、调谐和低功耗

MSHC_CTRL_R:

  • SW_CG_DIS=1 禁止软件时钟门控,因此 ll_msc_software_clock_gating_disable() 是置位,enable() 是清零。
  • CMD_CONFLICT_CHECK 控制命令冲突检查。

EMMC_CTRL_R:

  • CARD_IS_EMMC 选择 eMMC 或非 eMMC 行为。
  • EMMC_RST_N_OE 控制 reset pad 是否由控制器驱动,EMMC_RST_N 为低有效复位。
  • DISABLE_DATA_CRC_CHK=1 关闭数据 CRC 检查。
  • CQE_PREFETCH_DISABLE=1 关闭 CQE prefetch,CQE_ALGO_SEL 选择 CQE task arbitration 算法。修改 CQE 配置应在 CQE 关闭时进行。

AT_CTRL_R/AT_STAT_R:

  • HOST_CTRL2_R.EXEC_TUNING 由 ll_msc_execute_tuning_start() 置位,硬件完成后自动清零。
  • SW_TUNE_EN 选择软件调谐;PH_SEL_CODE 在软件调谐下选择 0~7 的采样相位。
  • SWIN_TH_VAL、SWIN_TH_EN 控制采样窗口阈值和“首个匹配/最宽窗口”策略。
  • PRE_CHANGE_DLY、POST_CHANGE_DLY 是相位切换前后的 cclk 延时,范围 0~3。
  • TUNE_CLK_STOP_EN 允许在调谐相位切换时停止卡时钟。
  • RPT_TUNE_ERR 控制调谐错误上报。

LP_CTRL_R 与 CLK_CTRL_R.CARD_LP_EN 配合:

  • LP_TH_VAL 与 buffered write 的 XFER_BLOCK_SIZE - LP_TH_VAL 等待阈值比较,LP_TH_EN=1 时生效。
  • LP_CNT 是空闲时钟门控延时,CARD_LP_EN=1 时生效。
  • LL 不检查是否还有未完成传输;进入低功耗门控前必须确认控制器空闲。

3.9 公开枚举和常量

msc_regs.h 公开以下配置枚举。字段范围仍由调用方保证,LL 不会阻止把保留值写入寄存器。

枚举 用途
msc_sdma_boundary_t SDMA 4 KiB~512 KiB boundary
msc_response_check_type_t R1/R5 响应错误检查选择
msc_auto_command_t 禁用、CMD12、CMD23 或自动选择
msc_command_type_t normal/suspend/resume/abort
msc_response_type_t none/136-bit/48-bit/48-bit busy
msc_response_word_t 选择 RESP01/23/45/67_R
msc_dma_select_t SDMA/ADMA2/ADMA3 编码,受 Host v4 影响
msc_bus_width_t 1/4/8-bit DAT 总线
msc_vdd1_voltage_t SD/eMMC VDD1 电压编码
msc_vdd2_voltage_t UHS-II VDD2 电压编码
msc_software_reset_t DAT/CMD/ALL 软复位掩码,可组合
msc_speed_mode_t SD/eMMC UHS 速度模式
msc_preset_index_t preset 寄存器选择

寄存器位域 mask/shift、中断位和状态位定义在 msc_regs.h。组合寄存器值时,优先使用这些宏,不要手写裸 bit 位置。


4. API 参考

4.1 API 总览

ll_msc.h

以下列出全部 296 个公开接口,按功能组归类。后续小节给出参数、返回值和寄存器副作用。

实例检查和原始访问

API 用途
ll_msc_is_instance() 判断指针是否等于 MSC0 或 MSC1
ll_msc_reg8_write() 按字节偏移写 8-bit 寄存器
ll_msc_reg8_read() 按字节偏移读 8-bit 寄存器
ll_msc_reg16_write() 按半字偏移写 16-bit 寄存器
ll_msc_reg16_read() 按半字偏移读 16-bit 寄存器
ll_msc_reg32_write() 按字偏移写 32-bit 寄存器
ll_msc_reg32_read() 按字偏移读 32-bit 寄存器

传输参数和 XFER_MODE

API 用途
ll_msc_sdma_address_set() 写 SDMA 系统地址
ll_msc_sdma_address_get() 读 SDMA 系统地址
ll_msc_32bit_block_count_set() 在 Host v4 下写 32-bit block count
ll_msc_32bit_block_count_get() 在 Host v4 下读 32-bit block count
ll_msc_sdma_buffer_boundary_set() 设置 SDMA buffer boundary
ll_msc_sdma_buffer_boundary_get() 读取 SDMA buffer boundary
ll_msc_block_size_set() 设置 block size
ll_msc_block_size_get() 读取 block size
ll_msc_block_count_set() 设置 16-bit block count
ll_msc_block_count_get() 读取 16-bit block count
ll_msc_argument_set() 设置下一条命令参数
ll_msc_argument_get() 读取命令参数
ll_msc_transfer_mode_set() 覆盖写完整 XFER_MODE_R
ll_msc_transfer_mode_get() 读取完整 XFER_MODE_R
ll_msc_transfer_mode_make() 打包常用 XFER_MODE_R 位
ll_msc_response_interrupt_disable() 禁止响应完成中断
ll_msc_response_interrupt_enable() 允许响应完成中断
ll_msc_is_response_interrupt_disabled() 查询响应完成中断是否禁止
ll_msc_response_error_check_enable() 开启 R1/R5 响应错误检查
ll_msc_response_error_check_disable() 关闭 R1/R5 响应错误检查
ll_msc_is_response_error_check_enabled() 查询响应错误检查是否开启
ll_msc_response_check_type_set() 选择 R1 或 R5 响应错误检查
ll_msc_response_check_type_get() 读取响应错误检查类型
ll_msc_multi_block_select() 选择多块传输
ll_msc_single_block_select() 选择单块传输
ll_msc_is_multi_block_selected() 查询是否多块传输
ll_msc_card_to_host_direction_select() 选择卡到主机读方向
ll_msc_host_to_card_direction_select() 选择主机到卡写方向
ll_msc_is_card_to_host_direction_selected() 查询是否为卡到主机方向
ll_msc_auto_command_set() 设置自动 CMD12/CMD23
ll_msc_auto_command_get() 读取自动命令选择
ll_msc_block_count_enable() 启用 block count 限制
ll_msc_block_count_disable() 禁用 block count 限制
ll_msc_is_block_count_enabled() 查询 block count 限制
ll_msc_dma_enable() 在传输模式中开启 DMA
ll_msc_dma_disable() 在传输模式中关闭 DMA
ll_msc_is_dma_enabled() 查询本次传输是否启用 DMA

命令和响应

API 用途
ll_msc_command_set() 写入完整 CMD_R 并触发命令
ll_msc_command_get() 读取 CMD_R 控制字段
ll_msc_command_make() 打包 CMD_R 命令、检查位和响应类型
ll_msc_command_index_get() 读取命令索引
ll_msc_command_type_get() 读取命令类型
ll_msc_is_command_data_present() 查询命令是否包含数据阶段
ll_msc_is_command_index_check_enabled() 查询响应索引检查
ll_msc_is_command_crc_check_enabled() 查询响应 CRC 检查
ll_msc_is_sub_command() 查询 sub-command 标志
ll_msc_command_response_type_get() 读取期望响应类型
ll_msc_response01_get() 读取 RESP01_R
ll_msc_response23_get() 读取 RESP23_R
ll_msc_response45_get() 读取 RESP45_R
ll_msc_response67_get() 读取 RESP67_R
ll_msc_response_get() 按枚举读取一个响应 word

数据端口和 PSTATE

API 用途
ll_msc_buffer_data_write() 向 PIO 数据端口写一个 word
ll_msc_buffer_data_read() 从 PIO 数据端口读一个 word
ll_msc_present_state_get() 读取完整 PSTATE_REG
ll_msc_is_sub_command_active() 查询 sub-command 状态
ll_msc_is_command_issue_error() 查询命令下发错误
ll_msc_is_host_regulated_voltage_stable() 查询主机稳压电压状态
ll_msc_command_line_level_get() 读取 CMD 线采样电平
ll_msc_data_line_level_get() 读取 DAT0..DAT7 采样电平
ll_msc_is_read_buffer_ready() 查询 PIO 读缓冲区就绪
ll_msc_is_write_buffer_ready() 查询 PIO 写缓冲区就绪
ll_msc_is_read_transfer_active() 查询读传输是否活动
ll_msc_is_write_transfer_active() 查询写传输是否活动
ll_msc_is_retune_requested() 查询硬件重调谐请求
ll_msc_is_data_line_active() 查询 DAT 线是否活动
ll_msc_is_data_command_inhibited() 查询使用 DAT 的命令是否被禁止
ll_msc_is_command_inhibited() 查询命令下发是否被禁止

HOST_CTRL1

API 用途
ll_msc_host_control1_set() 覆盖写完整 HOST_CTRL1_R
ll_msc_host_control1_get() 读取完整 HOST_CTRL1_R
ll_msc_card_detect_test_level_select() 选择卡检测测试电平
ll_msc_card_detect_pin_select() 选择卡检测输入引脚
ll_msc_is_card_detect_test_level_selected() 查询卡检测源
ll_msc_card_detect_test_level_inserted_set() 测试模式报告卡已插入
ll_msc_card_detect_test_level_removed_set() 测试模式报告卡已移除
ll_msc_is_card_detect_test_level_inserted() 读取测试插入电平
ll_msc_8bit_bus_select() 选择 8-bit DAT 总线编码
ll_msc_bus_width_get() 读取当前 DAT 总线宽度
ll_msc_dma_select_set() 选择 SDMA/ADMA2/ADMA3
ll_msc_dma_select_get() 读取 DMA 选择
ll_msc_high_speed_select() 选择 high-speed timing
ll_msc_normal_speed_select() 选择 normal-speed timing
ll_msc_is_high_speed_selected() 查询是否 high-speed timing
ll_msc_4bit_bus_select() 选择 4-bit DAT 总线
ll_msc_1bit_bus_select() 选择 1-bit DAT 总线

电源控制

API 用途
ll_msc_power_control_set() 覆盖写完整 PWR_CTRL_R
ll_msc_power_control_get() 读取完整 PWR_CTRL_R
ll_msc_vdd2_voltage_set() 设置 VDD2 电压编码
ll_msc_vdd2_voltage_get() 读取 VDD2 电压编码
ll_msc_vdd2_power_enable() 请求 VDD2 供电
ll_msc_vdd2_power_disable() 关闭 VDD2 供电请求
ll_msc_is_vdd2_power_enabled() 查询 VDD2 供电请求
ll_msc_vdd1_voltage_set() 设置 VDD1 电压编码
ll_msc_vdd1_voltage_get() 读取 VDD1 电压编码
ll_msc_vdd1_power_enable() 请求 VDD1 供电
ll_msc_vdd1_power_disable() 关闭 VDD1 供电请求
ll_msc_is_vdd1_power_enabled() 查询 VDD1 供电请求

Block gap 和唤醒

API 用途
ll_msc_block_gap_control_set() 覆盖写完整 BGAP_CTRL_R
ll_msc_block_gap_control_get() 读取完整 BGAP_CTRL_R
ll_msc_block_gap_interrupt_enable() 开启 block-gap 中断
ll_msc_block_gap_interrupt_disable() 关闭 block-gap 中断
ll_msc_is_block_gap_interrupt_enabled() 查询 block-gap 中断
ll_msc_read_wait_enable() 开启 SD read wait
ll_msc_read_wait_disable() 关闭 SD read wait
ll_msc_is_read_wait_enabled() 查询 SD read wait
ll_msc_continue_request() 请求跨越 block gap 继续
ll_msc_is_continue_request_pending() 查询 continue 请求是否挂起
ll_msc_stop_at_block_gap_request() 请求在下一 block gap 停止
ll_msc_is_stop_at_block_gap_request_pending() 查询停止请求是否挂起
ll_msc_wakeup_control_set() 覆盖写完整 WUP_CTRL_R
ll_msc_wakeup_control_get() 读取完整 WUP_CTRL_R
ll_msc_card_removal_wakeup_enable() 开启卡移除唤醒
ll_msc_card_removal_wakeup_disable() 关闭卡移除唤醒
ll_msc_is_card_removal_wakeup_enabled() 查询卡移除唤醒
ll_msc_card_insert_wakeup_enable() 开启卡插入唤醒
ll_msc_card_insert_wakeup_disable() 关闭卡插入唤醒
ll_msc_is_card_insert_wakeup_enabled() 查询卡插入唤醒
ll_msc_card_init_wakeup_enable() 开启卡中断唤醒
ll_msc_card_init_wakeup_disable() 关闭卡中断唤醒
ll_msc_is_card_init_wakeup_enabled() 查询卡中断唤醒

时钟、超时和复位

API 用途
ll_msc_clock_control_set() 覆盖写完整 CLK_CTRL_R
ll_msc_clock_control_get() 读取完整 CLK_CTRL_R
ll_msc_card_clock_low_power_enable() 开启空闲卡时钟门控
ll_msc_card_clock_low_power_disable() 关闭空闲卡时钟门控
ll_msc_is_card_clock_low_power_enabled() 查询空闲卡时钟门控
ll_msc_pll_enable() 请求时钟 PLL
ll_msc_pll_disable() 关闭时钟 PLL 请求
ll_msc_is_pll_enabled() 查询时钟 PLL 请求
ll_msc_sd_clock_enable() 驱动 SDCLK/RCLK
ll_msc_sd_clock_disable() 停止 SDCLK/RCLK
ll_msc_is_sd_clock_enabled() 查询卡时钟使能
ll_msc_is_internal_clock_stable() 查询内部时钟稳定状态
ll_msc_internal_clock_enable() 请求内部时钟
ll_msc_internal_clock_disable() 关闭内部时钟
ll_msc_is_internal_clock_enabled() 查询内部时钟请求
ll_msc_timeout_count_set() 设置 DAT timeout 编码
ll_msc_timeout_count_get() 读取 DAT timeout 编码
ll_msc_software_reset_get() 读取仍挂起的软复位请求
ll_msc_software_reset() 请求 DAT/CMD/ALL 软复位
ll_msc_is_software_reset_pending() 查询指定软复位是否仍未完成

中断控制

API 用途
ll_msc_normal_interrupt_status_get() 读取 normal 中断状态
ll_msc_normal_interrupt_status_clear() W1C 清除 normal 事件
ll_msc_error_interrupt_status_get() 读取 error 中断状态
ll_msc_error_interrupt_status_clear() W1C 清除 error 事件
ll_msc_interrupt_status_get() 组合读取 normal 和 error 状态
ll_msc_interrupt_status_clear() 同时清除 normal 和 error 事件
ll_msc_normal_interrupt_status_enable_set() 覆盖写 normal status enable
ll_msc_normal_interrupt_status_enable_get() 读取 normal status enable
ll_msc_error_interrupt_status_enable_set() 覆盖写 error status enable
ll_msc_error_interrupt_status_enable_get() 读取 error status enable
ll_msc_normal_interrupt_signal_enable_set() 覆盖写 normal signal enable
ll_msc_normal_interrupt_signal_enable_get() 读取 normal signal enable
ll_msc_error_interrupt_signal_enable_set() 覆盖写 error signal enable
ll_msc_error_interrupt_signal_enable_get() 读取 error signal enable
ll_msc_auto_command_status_get() 读取自动命令错误详情

HOST_CTRL2

API 用途
ll_msc_host_control2_set() 覆盖写完整 HOST_CTRL2_R
ll_msc_host_control2_get() 读取完整 HOST_CTRL2_R
ll_msc_async_interrupt_enable() 开启异步中断
ll_msc_async_interrupt_disable() 关闭异步中断
ll_msc_is_async_interrupt_enabled() 查询异步中断
ll_msc_64bit_addressing_select() 选择 64-bit 系统寻址
ll_msc_32bit_addressing_select() 选择 32-bit 系统寻址
ll_msc_is_64bit_addressing_selected() 查询 64-bit 系统寻址
ll_msc_host_v4_mode_select() 选择 Host v4 模式
ll_msc_host_v3_compatible_mode_select() 选择 Host v3 兼容模式
ll_msc_is_host_v4_mode_selected() 查询 Host v4 模式
ll_msc_adma3_auto_cmd23_select() ADMA3 使用自动 CMD23
ll_msc_adma3_auto_cmd12_select() ADMA3 使用自动 CMD12
ll_msc_is_adma3_auto_cmd23_selected() 查询 ADMA3 自动命令
ll_msc_adma2_length_26bit_select() 选择 26-bit ADMA2 length
ll_msc_adma2_length_16bit_select() 选择 16-bit ADMA2 length
ll_msc_is_adma2_length_26bit_selected() 查询 ADMA2 length 模式
ll_msc_tuned_sample_clock_select() 选择 tuned sampling clock
ll_msc_fixed_sample_clock_select() 选择 fixed sampling clock
ll_msc_is_tuned_sample_clock_selected() 查询采样时钟选择
ll_msc_execute_tuning_start() 置位 EXEC_TUNING 启动调谐
ll_msc_execute_tuning_clear() 清除 EXEC_TUNING
ll_msc_is_execute_tuning_active() 查询调谐是否仍在进行
ll_msc_1v8_signaling_enable() 开启 1.8 V signaling
ll_msc_1v8_signaling_disable() 关闭 1.8 V signaling
ll_msc_is_1v8_signaling_enabled() 查询 1.8 V signaling
ll_msc_uhs_mode_set() 设置 SD/eMMC 速度模式
ll_msc_uhs_mode_get() 读取 SD/eMMC 速度模式

能力寄存器

API 用途
ll_msc_capabilities1_get() 读取固定 CAPABILITIES1_R
ll_msc_is_async_interrupt_supported() 查询异步中断能力
ll_msc_is_64bit_system_address_v3_supported() 查询 v3 64-bit 地址能力
ll_msc_is_64bit_system_address_v4_supported() 查询 v4 64-bit 地址能力
ll_msc_is_suspend_resume_supported() 查询 suspend/resume 能力
ll_msc_is_sdma_supported() 查询 SDMA 能力
ll_msc_is_high_speed_supported() 查询 high-speed 能力
ll_msc_is_adma2_supported() 查询 ADMA2 能力
ll_msc_is_embedded_8bit_supported() 查询 embedded 8-bit 能力
ll_msc_max_block_length_get() 读取最大 block length 编码
ll_msc_capabilities2_get() 读取扩展 CAPABILITIES2_R
ll_msc_is_adma3_supported() 查询 ADMA3 能力
ll_msc_retuning_mode_get() 读取重调谐模式
ll_msc_is_sdr50_tuning_required() 查询 SDR50 是否要求调谐
ll_msc_retune_count_get() 读取 timer retuning count
ll_msc_is_ddr50_supported() 查询 DDR50 能力
ll_msc_is_sdr104_supported() 查询 SDR104 能力
ll_msc_is_sdr50_supported() 查询 SDR50 能力
ll_msc_current_capabilities1_get() 读取当前 VDD1 能力
ll_msc_max_current_1v8_get() 读取 VDD1 1.8 V 最大电流编码
ll_msc_max_current_3v0_get() 读取 VDD1 3.0 V 最大电流编码
ll_msc_max_current_3v3_get() 读取 VDD1 3.3 V 最大电流编码
ll_msc_current_capabilities2_get() 读取当前 VDD2 能力
ll_msc_max_current_vdd2_1v8_get() 读取 VDD2 1.8 V 最大电流编码

Force event 和 ADMA

API 用途
ll_msc_force_auto_command_status() W1S 强制自动命令错误状态
ll_msc_force_error_interrupt_status() W1S 强制错误中断事件
ll_msc_adma_error_status_get() 读取 ADMA_ERR_STAT_R
ll_msc_is_adma_length_error() 查询 ADMA length mismatch
ll_msc_adma_error_state_get() 读取 ADMA 错误状态机编码
ll_msc_adma_address_set() 写 64-bit ADMA 系统地址
ll_msc_adma_address_get() 读 64-bit ADMA 系统地址
ll_msc_adma_id_address_set() 写 64-bit ADMA3 descriptor 地址
ll_msc_adma_id_address_get() 读 64-bit ADMA3 descriptor 地址

Preset、Slot 和版本

API 用途
ll_msc_preset_value_get() 按模式读取 preset 寄存器
ll_msc_preset_driver_strength_get() 读取 preset 驱动强度
ll_msc_preset_clock_generator_select_get() 读取 preset 时钟源
ll_msc_preset_frequency_select_get() 读取 preset frequency select
ll_msc_slot_interrupt_status_get() 读取 slot 中断状态
ll_msc_interrupting_slot_get() 读取正在中断的 slot bitmap
ll_msc_host_version_get() 读取 Host 控制器版本
ll_msc_host_vendor_version_get() 读取 Host 厂商版本字段
ll_msc_host_spec_version_get() 读取 Host 规范版本字段
ll_msc_mshc_version_id_get() 读取 MSHC version ID
ll_msc_mshc_version_id_value_get() 读取 MSHC version ID 字段
ll_msc_mshc_version_type_get() 读取 MSHC version type
ll_msc_mshc_version_type_value_get() 读取 MSHC version type 字段

MSHC 和 eMMC 控制

API 用途
ll_msc_mshc_control_set() 覆盖写完整 MSHC_CTRL_R
ll_msc_mshc_control_get() 读取完整 MSHC_CTRL_R
ll_msc_software_clock_gating_disable() 禁止软件时钟门控
ll_msc_software_clock_gating_enable() 允许软件时钟门控
ll_msc_is_software_clock_gating_disabled() 查询软件时钟门控
ll_msc_command_conflict_check_enable() 开启命令冲突检查
ll_msc_command_conflict_check_disable() 关闭命令冲突检查
ll_msc_is_command_conflict_check_enabled() 查询命令冲突检查
ll_msc_emmc_control_set() 覆盖写完整 EMMC_CTRL_R
ll_msc_emmc_control_get() 读取完整 EMMC_CTRL_R
ll_msc_cqe_prefetch_disable() 禁止 CQE prefetch
ll_msc_cqe_prefetch_enable() 允许 CQE prefetch
ll_msc_is_cqe_prefetch_disabled() 查询 CQE prefetch
ll_msc_cqe_alternate_algorithm_select() 选择 alternate CQE 算法
ll_msc_cqe_default_algorithm_select() 选择 default CQE 算法
ll_msc_is_cqe_alternate_algorithm_selected() 查询 CQE 算法
ll_msc_emmc_reset_output_enable() 驱动 eMMC reset pad
ll_msc_emmc_reset_output_disable() 停止驱动 eMMC reset pad
ll_msc_is_emmc_reset_output_enabled() 查询 reset pad 驱动
ll_msc_emmc_reset_release() 释放低有效 eMMC reset
ll_msc_emmc_reset_assert() 置位低有效 eMMC reset
ll_msc_is_emmc_reset_released() 查询 eMMC reset 状态
ll_msc_data_crc_check_disable() 关闭 eMMC 数据 CRC 检查
ll_msc_data_crc_check_enable() 开启 eMMC 数据 CRC 检查
ll_msc_is_data_crc_check_disabled() 查询数据 CRC 检查
ll_msc_emmc_card_select() 选择 eMMC 卡行为
ll_msc_non_emmc_card_select() 选择非 eMMC 卡行为
ll_msc_is_emmc_card_selected() 查询是否 eMMC 模式

调谐控制

API 用途
ll_msc_auto_tuning_control_set() 覆盖写完整 AT_CTRL_R
ll_msc_auto_tuning_control_get() 读取完整 AT_CTRL_R
ll_msc_tuning_window_threshold_set() 设置采样窗口阈值
ll_msc_tuning_window_threshold_get() 读取采样窗口阈值
ll_msc_tuning_post_change_delay_set() 设置相位切换后延时
ll_msc_tuning_post_change_delay_get() 读取相位切换后延时
ll_msc_tuning_pre_change_delay_set() 设置相位切换前延时
ll_msc_tuning_pre_change_delay_get() 读取相位切换前延时
ll_msc_tuning_clock_stop_enable() 调谐相位切换时停止卡时钟
ll_msc_tuning_clock_stop_disable() 调谐相位切换时保持卡时钟
ll_msc_is_tuning_clock_stop_enabled() 查询调谐时钟停止
ll_msc_software_tuning_enable() 开启软件调谐
ll_msc_software_tuning_disable() 关闭软件调谐
ll_msc_is_software_tuning_enabled() 查询软件调谐
ll_msc_tuning_error_report_enable() 开启调谐错误上报
ll_msc_tuning_error_report_disable() 关闭调谐错误上报
ll_msc_is_tuning_error_report_enabled() 查询调谐错误上报
ll_msc_first_matching_tuning_window_select() 选择首个匹配窗口
ll_msc_widest_tuning_window_select() 选择最宽窗口
ll_msc_is_first_matching_tuning_window_selected() 查询窗口选择策略
ll_msc_auto_tuning_status_get() 读取 AT_STAT_R
ll_msc_phase_select_code_set() 设置软件调谐采样相位
ll_msc_phase_select_code_get() 读取当前采样相位

低功耗控制

API 用途
ll_msc_low_power_control_set() 覆盖写完整 LP_CTRL_R
ll_msc_low_power_control_get() 读取完整 LP_CTRL_R
ll_msc_low_power_threshold_set() 设置 buffered-write 低功耗阈值
ll_msc_low_power_threshold_get() 读取低功耗阈值
ll_msc_low_power_threshold_enable() 开启低功耗阈值
ll_msc_low_power_threshold_disable() 关闭低功耗阈值
ll_msc_is_low_power_threshold_enabled() 查询低功耗阈值
ll_msc_low_power_count_set() 设置空闲时钟门控计数
ll_msc_low_power_count_get() 读取空闲时钟门控计数

4.2 通用约定

  • 除 ll_msc_is_instance() 外,所有接口的第一个参数都是 msc_regs_t *mscx,必须指向 MSC0 或 MSC1。LL 层不解引用前检查空指针。
  • 字段 setter 只修改目标位域;全寄存器 setter 覆盖整个寄存器。调用方必须决定哪些字段需要先读回、哪些保留位必须保持为 0。
  • 参数类型是 uint8_t、uint16_t 或 uint32_t 不代表字段范围已校验。例如 timeout count 的 0xF、DMA select 的 1、UHS mode 的 5~7 都是保留值。
  • 所有 is_* 接口只读取当前状态,不清除状态;get 接口同样没有清状态副作用。
  • 所有写接口只表示 MMIO 写已经发出,不表示硬件完成。命令完成、传输完成、错误和复位完成必须通过状态/中断判断。
  • LL 层没有错误码、等待、重试、超时和互斥。需要这些能力时,应在 HAL 或控制器层实现。

4.3 实例检查和原始访问 API

API 参数 返回 说明
ll_msc_is_instance(mscx) const msc_regs_t *mscx bool 仅当指针等于 MSC0 或 MSC1 时返回 true;不访问寄存器
ll_msc_reg8_write(mscx, offset, value) uint32_t offset,uint8_t value void 在 mscx + offset 处写 8-bit;调用方负责访问属性、保留位和 W1C/W1S 语义
ll_msc_reg8_read(mscx, offset) uint32_t offset uint8_t 从 mscx + offset 读 8-bit
ll_msc_reg16_write(mscx, offset, value) uint32_t offset,uint16_t value void 写 16-bit;地址应满足半字对齐要求
ll_msc_reg16_read(mscx, offset) uint32_t offset uint16_t 读 16-bit;地址应满足半字对齐要求
ll_msc_reg32_write(mscx, offset, value) uint32_t offset,uint32_t value void 写 32-bit;地址应满足字对齐要求
ll_msc_reg32_read(mscx, offset) uint32_t offset uint32_t 读 32-bit;地址应满足字对齐要求

原始访问接口只适合 bring-up、寄存器表和调试。正常驱动应优先使用命名位域 helper,避免直接覆盖保留字段或把只读寄存器当成可写寄存器。

4.4 传输参数和 XFER_MODE API

本节接口访问 SDMASA_R、BLOCKSIZE_R、BLOCKCOUNT_R、ARGUMENT_R 和 XFER_MODE_R。ll_msc_transfer_mode_set() 覆盖整个寄存器,其余 setter 只修改目标字段。

API 参数 返回 说明
ll_msc_sdma_address_set(mscx, address) uint32_t address void 写 v3 SDMA 系统地址;Host v4 下该寄存器含义变为 block count
ll_msc_sdma_address_get(mscx) — uint32_t 读取 SDMASA_R 当前值
ll_msc_32bit_block_count_set(mscx, block_count) uint32_t block_count void Host v4 下写 32-bit block count;通常让 BLOCKCOUNT_R=0 选择该路径
ll_msc_32bit_block_count_get(mscx) — uint32_t Host v4 下读取 32-bit block count
ll_msc_sdma_buffer_boundary_set(mscx, boundary) msc_sdma_boundary_t boundary void 写 BLOCKSIZE_R.SDMA_BUF_BDARY,范围 4~512 KiB
ll_msc_sdma_buffer_boundary_get(mscx) — msc_sdma_boundary_t 读取 SDMA boundary
ll_msc_block_size_set(mscx, block_size) uint16_t block_size void 写 SHIFT 0 的 12-bit block size 字段;0 表示无数据,HAL 数据路径限制 1~2048 字节
ll_msc_block_size_get(mscx) — uint16_t 读取块大小字段
ll_msc_block_count_set(mscx, block_count) uint16_t block_count void 覆盖写 BLOCKCOUNT_R;0 的含义取决于传输模式和 Host 版本
ll_msc_block_count_get(mscx) — uint16_t 读取 16-bit block count
ll_msc_argument_set(mscx, argument) uint32_t argument void 写下一条命令的 32-bit 参数
ll_msc_argument_get(mscx) — uint32_t 读取当前已写入的命令参数
ll_msc_transfer_mode_set(mscx, mode) uint16_t mode void 覆盖写 XFER_MODE_R;必须自行保留未修改字段
ll_msc_transfer_mode_get(mscx) — uint16_t 读取完整 XFER_MODE_R
ll_msc_transfer_mode_make(...) 8 个布尔/枚举参数,见下 uint16_t 不访问硬件,生成完整 XFER_MODE_R 候选值
ll_msc_response_interrupt_disable(mscx) — void 置 RESP_INT_DIS
ll_msc_response_interrupt_enable(mscx) — void 清 RESP_INT_DIS
ll_msc_is_response_interrupt_disabled(mscx) — bool 查询 RESP_INT_DIS
ll_msc_response_error_check_enable(mscx) — void 置 RESP_ERR_CHK_EN,只适用于 R1/R5
ll_msc_response_error_check_disable(mscx) — void 清 RESP_ERR_CHK_EN
ll_msc_is_response_error_check_enabled(mscx) — bool 查询响应错误检查
ll_msc_response_check_type_set(mscx, type) msc_response_check_type_t type void 写 RESP_TYPE:R1 或 R5
ll_msc_response_check_type_get(mscx) — msc_response_check_type_t 读取响应错误检查类型
ll_msc_multi_block_select(mscx) — void 置 MULTI_BLK_SEL
ll_msc_single_block_select(mscx) — void 清 MULTI_BLK_SEL
ll_msc_is_multi_block_selected(mscx) — bool 查询是否为多块传输
ll_msc_card_to_host_direction_select(mscx) — void 置 DATA_XFER_DIR,选择卡到主机
ll_msc_host_to_card_direction_select(mscx) — void 清 DATA_XFER_DIR,选择主机到卡或无数据
ll_msc_is_card_to_host_direction_selected(mscx) — bool 查询是否为卡到主机方向
ll_msc_auto_command_set(mscx, auto_command) msc_auto_command_t auto_command void 写 AUTO_CMD_EN;SDIO 传输应禁用自动命令
ll_msc_auto_command_get(mscx) — msc_auto_command_t 读取自动命令选择
ll_msc_block_count_enable(mscx) — void 置 BLK_CNT_EN
ll_msc_block_count_disable(mscx) — void 清 BLK_CNT_EN
ll_msc_is_block_count_enabled(mscx) — bool 查询是否使用 BLOCKCOUNT_R 停止传输
ll_msc_dma_enable(mscx) — void 置 DMA_EN
ll_msc_dma_disable(mscx) — void 清 DMA_EN
ll_msc_is_dma_enabled(mscx) — bool 查询本次传输是否启用 DMA

ll_msc_transfer_mode_make() 的参数顺序为:

uint16_t ll_msc_transfer_mode_make(bool response_interrupt_disable,
                                   bool response_error_check,
                                   msc_response_check_type_t response_check_type,
                                   bool multi_block,
                                   bool read_direction,
                                   msc_auto_command_t auto_command,
                                   bool block_count_enable,
                                   bool dma_enable);

该函数只打包上面列出的字段,不检查 R1/R5 与命令响应类型是否匹配,也不检查 SDIO/direction/auto command 的组合是否合法。

4.5 命令和响应 API

本节接口访问 CMD_R 和 RESP01_R~RESP67_R。写 CMD_R 会触发命令,读取响应前必须确认命令已经完成。

API 参数 返回 说明
ll_msc_command_set(mscx, command) uint16_t command void 写入完整 CMD_R 并触发命令;不检查 inhibit 状态
ll_msc_command_get(mscx) — uint16_t 读取当前 CMD_R 控制 word,不代表命令完成
ll_msc_command_make(...) 7 个参数,见下 uint16_t 不访问硬件,打包命令索引、类型、数据和响应选择
ll_msc_command_index_get(mscx) — uint8_t 读取 CMD_INDEX[5:0]
ll_msc_command_type_get(mscx) — msc_command_type_t 读取 CMD_TYPE
ll_msc_is_command_data_present(mscx) — bool 查询 DATA_PRES_SEL
ll_msc_is_command_index_check_enabled(mscx) — bool 查询响应索引检查
ll_msc_is_command_crc_check_enabled(mscx) — bool 查询响应 CRC 检查
ll_msc_is_sub_command(mscx) — bool 查询 SUB_CMD_FLAG
ll_msc_command_response_type_get(mscx) — msc_response_type_t 读取 RESP_TYPE_SEL
ll_msc_response01_get(mscx) — uint32_t 读取响应位 [39:8]
ll_msc_response23_get(mscx) — uint32_t 读取响应位 [71:40]
ll_msc_response45_get(mscx) — uint32_t 读取响应位 [103:72]
ll_msc_response67_get(mscx) — uint32_t 读取响应位 [135:104];自动命令的 32-bit 响应也在此
ll_msc_response_get(mscx, index) msc_response_word_t index uint32_t 按枚举读取响应 word;越界值回退到 RESP67_R

ll_msc_command_make() 的签名如下;command_index 的有效命令号范围是 0~63,协议层必须自行保证命令类型、响应类型、索引/CRC 检查位一致:

uint16_t ll_msc_command_make(uint8_t command_index,
                             msc_command_type_t command_type,
                             bool data_present,
                             bool index_check,
                             bool crc_check,
                             bool sub_command,
                             msc_response_type_t response_type);

4.6 数据端口和 PSTATE API

API 参数 返回 说明
ll_msc_buffer_data_write(mscx, data) uint32_t data void 向 BUF_DATA_R 写一个 PIO word;不检查 BUF_WR_EN
ll_msc_buffer_data_read(mscx) — uint32_t 从 BUF_DATA_R 读一个 word;不检查 BUF_RD_EN
ll_msc_present_state_get(mscx) — uint32_t 读取完整 PSTATE_REG 快照
ll_msc_is_sub_command_active(mscx) — bool 查询 SUB_CMD_STAT
ll_msc_is_command_issue_error(mscx) — bool 查询 CMD_ISSU_ERR
ll_msc_is_host_regulated_voltage_stable(mscx) — bool 查询 HOST_REG_VOL
ll_msc_command_line_level_get(mscx) — bool 返回 CMD 线采样电平;UHS-II 场景不适用
ll_msc_data_line_level_get(mscx) — uint8_t 返回 DAT[7:0] 电平,bit0 对应 DAT0
ll_msc_is_read_buffer_ready(mscx) — bool 查询 BUF_RD_EN,使用 PIO 读前必须检查
ll_msc_is_write_buffer_ready(mscx) — bool 查询 BUF_WR_EN,使用 PIO 写前必须检查
ll_msc_is_read_transfer_active(mscx) — bool 查询 RD_XFER_ACT
ll_msc_is_write_transfer_active(mscx) — bool 查询 WR_XFER_ACT
ll_msc_is_retune_requested(mscx) — bool 查询 RE_TUNE_REQ
ll_msc_is_data_line_active(mscx) — bool 查询 DAT_LINE_ACT
ll_msc_is_data_command_inhibited(mscx) — bool 查询 CMD_INH_DAT
ll_msc_is_command_inhibited(mscx) — bool 查询 CMD_INH

PIO 接口不做 FIFO 水位管理。应用必须按 block_size × block_count 计算总 word 数,并在读/写就绪时访问数据端口;不能仅凭一次 BUF_RD_EN 判断整个 block 已可读。

4.7 HOST_CTRL1 API

API 参数 返回 说明
ll_msc_host_control1_set(mscx, control) uint8_t control void 覆盖写完整 HOST_CTRL1_R
ll_msc_host_control1_get(mscx) — uint8_t 读取完整 HOST_CTRL1_R
ll_msc_card_detect_test_level_select(mscx) — void 置 CARD_DETECT_SIG_LVL,使用测试电平
ll_msc_card_detect_pin_select(mscx) — void 清 CARD_DETECT_SIG_LVL,使用卡检测引脚
ll_msc_is_card_detect_test_level_selected(mscx) — bool 查询卡检测源
ll_msc_card_detect_test_level_inserted_set(mscx) — void 测试模式报告卡插入
ll_msc_card_detect_test_level_removed_set(mscx) — void 测试模式报告卡移除
ll_msc_is_card_detect_test_level_inserted(mscx) — bool 查询测试插入电平
ll_msc_8bit_bus_select(mscx) — void 置 EXT_DAT_XFER,选择 8-bit 编码
ll_msc_bus_width_get(mscx) — msc_bus_width_t 按 EXT_DAT_XFER 和 DAT_XFER_W 返回 1/4/8-bit
ll_msc_dma_select_set(mscx, dma_select) msc_dma_select_t dma_select void 写 DMA_SEL;值 1 保留
ll_msc_dma_select_get(mscx) — msc_dma_select_t 读 DMA_SEL 编码
ll_msc_high_speed_select(mscx) — void 置 HS_EN
ll_msc_normal_speed_select(mscx) — void 清 HS_EN
ll_msc_is_high_speed_selected(mscx) — bool 查询高速 timing
ll_msc_4bit_bus_select(mscx) — void 清 EXT_DAT_XFER,置 DAT_XFER_W
ll_msc_1bit_bus_select(mscx) — void 清 EXT_DAT_XFER 和 DAT_XFER_W

8-bit helper 只生成寄存器编码。G32S10 板级通常只连接 DAT0..DAT3,HAL 也把 8-bit 配置视为不支持;除非确认 SoC 和板级均具备 DAT[7:0],否则不要调用 ll_msc_8bit_bus_select()。

4.8 电源控制 API

API 参数 返回 说明
ll_msc_power_control_set(mscx, control) uint8_t control void 覆盖写完整 PWR_CTRL_R
ll_msc_power_control_get(mscx) — uint8_t 读取完整 PWR_CTRL_R
ll_msc_vdd2_voltage_set(mscx, voltage) msc_vdd2_voltage_t voltage void 写 VDD2 电压字段;当前仅定义 1.8 V 编码
ll_msc_vdd2_voltage_get(mscx) — msc_vdd2_voltage_t 读取 VDD2 电压编码
ll_msc_vdd2_power_enable(mscx) — void 置 SD_BUS_PWR_VDD2
ll_msc_vdd2_power_disable(mscx) — void 清 SD_BUS_PWR_VDD2
ll_msc_is_vdd2_power_enabled(mscx) — bool 查询 VDD2 供电请求
ll_msc_vdd1_voltage_set(mscx, voltage) msc_vdd1_voltage_t voltage void 写 VDD1 电压字段;同一编码在 SD/eMMC 下含义不同
ll_msc_vdd1_voltage_get(mscx) — msc_vdd1_voltage_t 读取 VDD1 电压编码
ll_msc_vdd1_power_enable(mscx) — void 置 SD_BUS_PWR_VDD1
ll_msc_vdd1_power_disable(mscx) — void 清 SD_BUS_PWR_VDD1
ll_msc_is_vdd1_power_enabled(mscx) — bool 查询 VDD1 供电请求

这些接口不检查 CAPABILITIES1_R 是否支持目标电压,也不等待外部电源稳定。切换电压时应先按协议停止时钟和传输,并由板级代码控制稳压器。

4.9 Block gap 和唤醒 API

API 参数 返回 说明
ll_msc_block_gap_control_set(mscx, control) uint8_t control void 覆盖写完整 BGAP_CTRL_R
ll_msc_block_gap_control_get(mscx) — uint8_t 读取 BGAP_CTRL_R,包括请求位
ll_msc_block_gap_interrupt_enable(mscx) — void 置 INT_AT_BGAP
ll_msc_block_gap_interrupt_disable(mscx) — void 清 INT_AT_BGAP
ll_msc_is_block_gap_interrupt_enabled(mscx) — bool 查询 block-gap 中断
ll_msc_read_wait_enable(mscx) — void 置 RD_WAIT_CTRL,启用 SD read wait
ll_msc_read_wait_disable(mscx) — void 清 RD_WAIT_CTRL
ll_msc_is_read_wait_enabled(mscx) — bool 查询 read wait
ll_msc_continue_request(mscx) — void 置 CONT_REQ,请求跨越 block gap 继续
ll_msc_is_continue_request_pending(mscx) — bool 查询 continue 请求是否仍挂起
ll_msc_stop_at_block_gap_request(mscx) — void 置 STOP_BG_REQ
ll_msc_is_stop_at_block_gap_request_pending(mscx) — bool 查询停止请求是否仍挂起
ll_msc_wakeup_control_set(mscx, control) uint8_t control void 覆盖写完整 WUP_CTRL_R
ll_msc_wakeup_control_get(mscx) — uint8_t 读取唤醒使能 bitmap
ll_msc_card_removal_wakeup_enable(mscx) — void 开启卡移除唤醒
ll_msc_card_removal_wakeup_disable(mscx) — void 关闭卡移除唤醒
ll_msc_is_card_removal_wakeup_enabled(mscx) — bool 查询卡移除唤醒
ll_msc_card_insert_wakeup_enable(mscx) — void 开启卡插入唤醒
ll_msc_card_insert_wakeup_disable(mscx) — void 关闭卡插入唤醒
ll_msc_is_card_insert_wakeup_enabled(mscx) — bool 查询卡插入唤醒
ll_msc_card_init_wakeup_enable(mscx) — void 置 CARD_INIT,开启卡中断唤醒
ll_msc_card_init_wakeup_disable(mscx) — void 清 CARD_INIT
ll_msc_is_card_init_wakeup_enabled(mscx) — bool 查询卡中断唤醒

CONT_REQ 和 STOP_BG_REQ 由硬件在请求被接受或完成后更新;LL 只负责置位和轮询。WUP_CTRL_R.CARD_INIT 这个字段名容易误解,它实际控制由 NORMAL_INT_STAT_R.CARD_INTERRUPT 触发唤醒。

4.10 时钟、超时和软件复位 API

API 参数 返回 说明
ll_msc_clock_control_set(mscx, control) uint16_t control void 覆盖写完整 CLK_CTRL_R
ll_msc_clock_control_get(mscx) — uint16_t 读取控制位和 INTERNAL_CLK_STABLE 状态
ll_msc_card_clock_low_power_enable(mscx) — void 置 CARD_LP_EN,允许空闲卡时钟门控
ll_msc_card_clock_low_power_disable(mscx) — void 清 CARD_LP_EN
ll_msc_is_card_clock_low_power_enabled(mscx) — bool 查询空闲卡时钟门控
ll_msc_pll_enable(mscx) — void 置 PLL_ENABLE
ll_msc_pll_disable(mscx) — void 清 PLL_ENABLE
ll_msc_is_pll_enabled(mscx) — bool 查询 PLL 请求
ll_msc_sd_clock_enable(mscx) — void 置 SD_CLK_EN,驱动卡时钟
ll_msc_sd_clock_disable(mscx) — void 清 SD_CLK_EN,停止卡时钟
ll_msc_is_sd_clock_enabled(mscx) — bool 查询卡时钟输出使能
ll_msc_is_internal_clock_stable(mscx) — bool 查询 INTERNAL_CLK_STABLE
ll_msc_internal_clock_enable(mscx) — void 置 INTERNAL_CLK_EN
ll_msc_internal_clock_disable(mscx) — void 清 INTERNAL_CLK_EN
ll_msc_is_internal_clock_enabled(mscx) — bool 查询内部时钟请求
ll_msc_timeout_count_set(mscx, timeout_count) uint8_t timeout_count void 写 TOUT_CNT,有效 0~14;0 对应 TMCLK × 2^13,14 对应 TMCLK × 2^27
ll_msc_timeout_count_get(mscx) — uint8_t 返回 timeout 编码 0~14;0xF 保留
ll_msc_software_reset_get(mscx) — uint8_t 返回仍挂起的 DAT/CMD/ALL 复位位
ll_msc_software_reset(mscx, reset_mask) uint8_t reset_mask void 只写尚未挂起的复位请求位;可 OR msc_software_reset_t
ll_msc_is_software_reset_pending(mscx, reset_mask) uint8_t reset_mask bool 查询任一指定复位域是否仍未完成

CLK_CTRL_R 中同时有控制位和只读状态位。使用全寄存器 setter 前应先读取当前值,避免把状态位或其它控制字段误写。ll_msc_software_reset() 不等待完成,也不返回超时。

4.11 中断控制 API

API 参数 返回 说明
ll_msc_normal_interrupt_status_get(mscx) — uint16_t 读取 NORMAL_INT_STAT_R;包含只读状态和 W1C 事件
ll_msc_normal_interrupt_status_clear(mscx, mask) uint16_t interrupt_mask void 对 normal W1C 位写 1 清除;只读位不受影响
ll_msc_error_interrupt_status_get(mscx) — uint16_t 读取 error 中断状态
ll_msc_error_interrupt_status_clear(mscx, mask) uint16_t interrupt_mask void W1C 清除 error 事件
ll_msc_interrupt_status_get(mscx) — uint32_t 返回 ERROR[31:16] 与 NORMAL[15:0] 的组合
ll_msc_interrupt_status_clear(mscx, normal_mask, error_mask) 两个 uint16_t void 同时清除 normal 和 error 事件
ll_msc_normal_interrupt_status_enable_set(mscx, value) uint16_t value void 覆盖写 normal status latch enable
ll_msc_normal_interrupt_status_enable_get(mscx) — uint16_t 读取 normal status latch enable
ll_msc_error_interrupt_status_enable_set(mscx, value) uint16_t value void 覆盖写 error status latch enable
ll_msc_error_interrupt_status_enable_get(mscx) — uint16_t 读取 error status latch enable
ll_msc_normal_interrupt_signal_enable_set(mscx, value) uint16_t value void 覆盖写 normal IRQ signal enable
ll_msc_normal_interrupt_signal_enable_get(mscx) — uint16_t 读取 normal IRQ signal enable
ll_msc_error_interrupt_signal_enable_set(mscx, value) uint16_t value void 覆盖写 error IRQ signal enable
ll_msc_error_interrupt_signal_enable_get(mscx) — uint16_t 读取 error IRQ signal enable
ll_msc_auto_command_status_get(mscx) — uint16_t 读取 AUTO_CMD_STAT_R 的 CMD12/CMD23 错误详情

normal/error status enable 和 signal enable 都是完整寄存器写接口,不是按位 OR helper。修改单个事件时,应先读取旧值、修改目标位,再写回。

4.12 HOST_CTRL2 API

API 参数 返回 说明
ll_msc_host_control2_set(mscx, control) uint16_t control void 写完整 HOST_CTRL2_R,写入前屏蔽 RSVD_KEEP_0
ll_msc_host_control2_get(mscx) — uint16_t 读取完整 HOST_CTRL2_R
ll_msc_async_interrupt_enable(mscx) — void 置 ASYNC_INT_EN
ll_msc_async_interrupt_disable(mscx) — void 清 ASYNC_INT_EN
ll_msc_is_async_interrupt_enabled(mscx) — bool 查询异步中断
ll_msc_64bit_addressing_select(mscx) — void 置 ADDRESSING,使用 64-bit 系统寻址
ll_msc_32bit_addressing_select(mscx) — void 清 ADDRESSING
ll_msc_is_64bit_addressing_selected(mscx) — bool 查询系统寻址宽度
ll_msc_host_v4_mode_select(mscx) — void 置 HOST_V4_EN
ll_msc_host_v3_compatible_mode_select(mscx) — void 清 HOST_V4_EN
ll_msc_is_host_v4_mode_selected(mscx) — bool 查询 Host v4
ll_msc_adma3_auto_cmd23_select(mscx) — void 置 CMD23_EN
ll_msc_adma3_auto_cmd12_select(mscx) — void 清 CMD23_EN
ll_msc_is_adma3_auto_cmd23_selected(mscx) — bool 查询 ADMA3 自动命令
ll_msc_adma2_length_26bit_select(mscx) — void 置 ADMA2_LEN_MD
ll_msc_adma2_length_16bit_select(mscx) — void 清 ADMA2_LEN_MD
ll_msc_is_adma2_length_26bit_selected(mscx) — bool 查询 ADMA2 length 格式
ll_msc_tuned_sample_clock_select(mscx) — void 置 SMP_CLK_SEL
ll_msc_fixed_sample_clock_select(mscx) — void 清 SMP_CLK_SEL
ll_msc_is_tuned_sample_clock_selected(mscx) — bool 查询采样时钟选择
ll_msc_execute_tuning_start(mscx) — void 置 EXEC_TUNING;不发送调谐命令
ll_msc_execute_tuning_clear(mscx) — void 清 EXEC_TUNING;不等待调谐
ll_msc_is_execute_tuning_active(mscx) — bool 查询调谐是否仍在执行
ll_msc_1v8_signaling_enable(mscx) — void 置 legacy 1.8 V signaling 位
ll_msc_1v8_signaling_disable(mscx) — void 清 1.8 V signaling 位
ll_msc_is_1v8_signaling_enabled(mscx) — bool 查询 1.8 V signaling
ll_msc_uhs_mode_set(mscx, mode) msc_speed_mode_t mode void 写 SD/eMMC 速度模式;5~7 保留
ll_msc_uhs_mode_get(mscx) — msc_speed_mode_t 读取速度模式编码

Host v4、64-bit addressing、ADMA3 和 64-bit ADMA2 之间存在组合约束。启用前应同时检查 CAPABILITIES1/2_R、descriptor 格式和所选卡协议;LL 不验证这些组合。

4.13 能力寄存器 API

能力接口只读。固定能力来自 CAPABILITIES1/2_R,当前能力来自 CURR_CAPABILITIES1/2_R;电压切换后应重新读取当前能力。

API 参数 返回 说明
ll_msc_capabilities1_get(mscx) — uint32_t 读取固定 CAPABILITIES1_R
ll_msc_is_async_interrupt_supported(mscx) — bool 查询 SD 模式异步中断能力
ll_msc_is_64bit_system_address_v3_supported(mscx) — bool 查询 v3 64-bit 系统地址能力
ll_msc_is_64bit_system_address_v4_supported(mscx) — bool 查询 v4 64-bit 系统地址能力
ll_msc_is_suspend_resume_supported(mscx) — bool 查询 suspend/resume 命令能力
ll_msc_is_sdma_supported(mscx) — bool 查询 SDMA 能力
ll_msc_is_high_speed_supported(mscx) — bool 查询 high-speed timing 能力
ll_msc_is_adma2_supported(mscx) — bool 查询 ADMA2 能力
ll_msc_is_embedded_8bit_supported(mscx) — bool 查询 embedded 8-bit 能力
ll_msc_max_block_length_get(mscx) — uint8_t 返回编码 0/1/2,对应 512/1024/2048 字节;3 保留
ll_msc_capabilities2_get(mscx) — uint32_t 读取固定 CAPABILITIES2_R
ll_msc_is_adma3_supported(mscx) — bool 查询 ADMA3 能力
ll_msc_retuning_mode_get(mscx) — uint8_t 0 timer,1 timer+request,2 传输中自动调谐;3 保留
ll_msc_is_sdr50_tuning_required(mscx) — bool 查询 SDR50 是否要求调谐
ll_msc_retune_count_get(mscx) — uint8_t 读取 timer retuning count 字段
ll_msc_is_ddr50_supported(mscx) — bool 查询 DDR50 能力
ll_msc_is_sdr104_supported(mscx) — bool 查询 SDR104 能力
ll_msc_is_sdr50_supported(mscx) — bool 查询 SDR50 能力
ll_msc_current_capabilities1_get(mscx) — uint32_t 读取当前 VDD1 能力 bitmap
ll_msc_max_current_1v8_get(mscx) — uint8_t 读取 VDD1 1.8 V 最大电流原始编码
ll_msc_max_current_3v0_get(mscx) — uint8_t 读取 VDD1 3.0 V 最大电流原始编码
ll_msc_max_current_3v3_get(mscx) — uint8_t 读取 VDD1 3.3 V 最大电流原始编码
ll_msc_current_capabilities2_get(mscx) — uint32_t 读取当前 VDD2 能力 bitmap
ll_msc_max_current_vdd2_1v8_get(mscx) — uint8_t 读取 VDD2 1.8 V 最大电流原始编码

最大电流字段使用 SDHCI 编码:0 表示其它来源,1 表示 4 mA,2 表示 8 mA,3 表示 13 mA,255 表示 1020 mA。LL 不把编码换算为 mA。

4.14 Force event 和 ADMA API

API 参数 返回 说明
ll_msc_force_auto_command_status(mscx, force_mask) uint16_t force_mask void W1S 强制 AUTO_CMD_STAT_R 事件;写 0 无效果
ll_msc_force_error_interrupt_status(mscx, force_mask) uint16_t force_mask void W1S 强制 ERROR_INT_STAT_R 事件;用于错误注入测试
ll_msc_adma_error_status_get(mscx) — uint32_t 读取 ADMA_ERR_STAT_R
ll_msc_is_adma_length_error(mscx) — bool 查询 ADMA length mismatch
ll_msc_adma_error_state_get(mscx) — uint8_t 返回 0 停止、1 取 descriptor、3 数据搬运;2 未用
ll_msc_adma_address_set(mscx, address) uint64_t address void 写 ADMA_SA_LOW/HIGH 两个寄存器
ll_msc_adma_address_get(mscx) — uint64_t 组合读取 ADMA_SA_LOW/HIGH
ll_msc_adma_id_address_set(mscx, address) uint64_t address void 写 ADMA_ID_LOW/HIGH 两个寄存器
ll_msc_adma_id_address_get(mscx) — uint64_t 组合读取 ADMA_ID_LOW/HIGH

Force event 只用于测试和诊断,不应出现在正常产品命令路径。ADMA 错误状态中的地址可能指向出错 descriptor 或下一个 descriptor,具体取决于状态机阶段;恢复时应重新构造 descriptor,而不是盲目复用旧地址。

4.15 Preset、Slot 和版本 API

API 参数 返回 说明
ll_msc_preset_value_get(mscx, preset_index) msc_preset_index_t preset_index uint16_t 读取目标 preset 寄存器;越界索引回退到 PRESET_UHS2_R
ll_msc_preset_driver_strength_get(mscx, preset_index) msc_preset_index_t preset_index uint8_t 返回 0 TypeB、1 TypeA、2 TypeC、3 TypeD;1.8 V signaling 下才有意义
ll_msc_preset_clock_generator_select_get(mscx, preset_index) msc_preset_index_t preset_index bool true 为 programmable clock generator,false 为 fixed
ll_msc_preset_frequency_select_get(mscx, preset_index) msc_preset_index_t preset_index uint16_t 返回 10-bit frequency select
ll_msc_slot_interrupt_status_get(mscx) — uint16_t 读取 SLOT_INTR_STATUS_R
ll_msc_interrupting_slot_get(mscx) — uint8_t 返回 slot bitmap,bit0 对应 slot 1,bit7 对应 slot 8
ll_msc_host_version_get(mscx) — uint16_t 读取 Host vendor/spec version bitmap
ll_msc_host_vendor_version_get(mscx) — uint8_t 读取 vendor version 字段
ll_msc_host_spec_version_get(mscx) — uint8_t 读取 spec version 字段
ll_msc_mshc_version_id_get(mscx) — uint32_t 读取 MSHC_VER_ID_R
ll_msc_mshc_version_id_value_get(mscx) — uint32_t 读取 MSHC version ID 字段
ll_msc_mshc_version_type_get(mscx) — uint32_t 读取 MSHC_VER_TYPE_R
ll_msc_mshc_version_type_value_get(mscx) — uint32_t 读取 MSHC version type 字段

preset 寄存器是只读的硬件建议值,不等同于当前 CPM 时钟配置。读取 preset 后是否采用,仍需控制器层结合卡模式、时钟能力和板级约束决定。

4.16 MSHC 和 eMMC 控制 API

API 参数 返回 说明
ll_msc_mshc_control_set(mscx, control) uint32_t control void 覆盖写完整 MSHC_CTRL_R
ll_msc_mshc_control_get(mscx) — uint32_t 读取完整 MSHC_CTRL_R
ll_msc_software_clock_gating_disable(mscx) — void 置 SW_CG_DIS,禁止软件时钟门控
ll_msc_software_clock_gating_enable(mscx) — void 清 SW_CG_DIS,允许门控
ll_msc_is_software_clock_gating_disabled(mscx) — bool 查询软件时钟门控是否被禁止
ll_msc_command_conflict_check_enable(mscx) — void 置 CMD_CONFLICT_CHECK
ll_msc_command_conflict_check_disable(mscx) — void 清 CMD_CONFLICT_CHECK
ll_msc_is_command_conflict_check_enabled(mscx) — bool 查询命令冲突检查
ll_msc_emmc_control_set(mscx, control) uint32_t control void 覆盖写完整 EMMC_CTRL_R;注意 reset 和 disable 字段的反向语义
ll_msc_emmc_control_get(mscx) — uint32_t 读取完整 EMMC_CTRL_R
ll_msc_cqe_prefetch_disable(mscx) — void 置 CQE_PREFETCH_DISABLE,禁止 CQE prefetch
ll_msc_cqe_prefetch_enable(mscx) — void 清 CQE_PREFETCH_DISABLE,允许 prefetch
ll_msc_is_cqe_prefetch_disabled(mscx) — bool 查询 CQE prefetch 是否被禁止
ll_msc_cqe_alternate_algorithm_select(mscx) — void 置 CQE_ALGO_SEL,选择 alternate 算法
ll_msc_cqe_default_algorithm_select(mscx) — void 清 CQE_ALGO_SEL,选择 default 算法
ll_msc_is_cqe_alternate_algorithm_selected(mscx) — bool 查询 CQE 算法
ll_msc_emmc_reset_output_enable(mscx) — void 置 EMMC_RST_N_OE,由控制器驱动 reset pad
ll_msc_emmc_reset_output_disable(mscx) — void 清 EMMC_RST_N_OE,释放 pad 驱动
ll_msc_is_emmc_reset_output_enabled(mscx) — bool 查询 reset pad 驱动使能
ll_msc_emmc_reset_release(mscx) — void 置 EMMC_RST_N,释放低有效复位
ll_msc_emmc_reset_assert(mscx) — void 清 EMMC_RST_N,置位低有效复位
ll_msc_is_emmc_reset_released(mscx) — bool 查询 eMMC reset 是否释放
ll_msc_data_crc_check_disable(mscx) — void 置 DISABLE_DATA_CRC_CHK,关闭数据 CRC 检查
ll_msc_data_crc_check_enable(mscx) — void 清 DISABLE_DATA_CRC_CHK,开启检查
ll_msc_is_data_crc_check_disabled(mscx) — bool 查询数据 CRC 检查是否关闭
ll_msc_emmc_card_select(mscx) — void 置 CARD_IS_EMMC
ll_msc_non_emmc_card_select(mscx) — void 清 CARD_IS_EMMC
ll_msc_is_emmc_card_selected(mscx) — bool 查询是否 eMMC 模式

注意:ll_msc_software_clock_gating_disable() 是置位 SW_CG_DIS,调用后门控被禁止;ll_msc_software_clock_gating_enable() 则是清位,使门控可用。CQE 算法和 prefetch 配置应在 CQE 关闭时修改。eMMC reset 为低有效,assert() 会清零 EMMC_RST_N。

4.17 调谐控制 API

API 参数 返回 说明
ll_msc_auto_tuning_control_set(mscx, control) uint32_t control void 覆盖写完整 AT_CTRL_R
ll_msc_auto_tuning_control_get(mscx) — uint32_t 读取完整 AT_CTRL_R
ll_msc_tuning_window_threshold_set(mscx, threshold) uint8_t threshold void 写 SWIN_TH_VAL,范围 0~255
ll_msc_tuning_window_threshold_get(mscx) — uint8_t 读采样窗口阈值
ll_msc_tuning_post_change_delay_set(mscx, delay) uint8_t delay void 写 POST_CHANGE_DLY,范围 0~3,单位 cclk
ll_msc_tuning_post_change_delay_get(mscx) — uint8_t 读相位切换后延时
ll_msc_tuning_pre_change_delay_set(mscx, delay) uint8_t delay void 写 PRE_CHANGE_DLY,范围 0~3,单位 cclk
ll_msc_tuning_pre_change_delay_get(mscx) — uint8_t 读相位切换前延时
ll_msc_tuning_clock_stop_enable(mscx) — void 置 TUNE_CLK_STOP_EN
ll_msc_tuning_clock_stop_disable(mscx) — void 清 TUNE_CLK_STOP_EN
ll_msc_is_tuning_clock_stop_enabled(mscx) — bool 查询调谐相位切换时是否停时钟
ll_msc_software_tuning_enable(mscx) — void 置 SW_TUNE_EN
ll_msc_software_tuning_disable(mscx) — void 清 SW_TUNE_EN
ll_msc_is_software_tuning_enabled(mscx) — bool 查询软件调谐
ll_msc_tuning_error_report_enable(mscx) — void 置 RPT_TUNE_ERR
ll_msc_tuning_error_report_disable(mscx) — void 清 RPT_TUNE_ERR
ll_msc_is_tuning_error_report_enabled(mscx) — bool 查询调谐错误上报
ll_msc_first_matching_tuning_window_select(mscx) — void 置 SWIN_TH_EN,选择首个匹配窗口
ll_msc_widest_tuning_window_select(mscx) — void 清 SWIN_TH_EN,选择最宽窗口
ll_msc_is_first_matching_tuning_window_selected(mscx) — bool 查询窗口选择策略
ll_msc_auto_tuning_status_get(mscx) — uint32_t 读取 AT_STAT_R,包含 PH_SEL_CODE
ll_msc_phase_select_code_set(mscx, phase_code) uint8_t phase_code void 写 PH_SEL_CODE,范围 0~7;只在软件调谐时使用
ll_msc_phase_select_code_get(mscx) — uint8_t 读取当前采样相位编码

调谐流程通常需要 HAL/控制器层发送调谐命令并检查 ERROR_INT_STAT_R.TUNING_ERR。ll_msc_execute_tuning_start() 和 ll_msc_auto_tuning_control_set() 本身不会触发完整调谐事务。软件调谐时,ll_msc_phase_select_code_set() 修改的是 AT_STAT_R 中的可写控制字段;修改前应确认 SW_TUNE_EN=1,并在控制器空闲时进行。

4.18 低功耗控制 API

API 参数 返回 说明
ll_msc_low_power_control_set(mscx, control) uint32_t control void 覆盖写完整 LP_CTRL_R
ll_msc_low_power_control_get(mscx) — uint32_t 读取完整 LP_CTRL_R
ll_msc_low_power_threshold_set(mscx, threshold) uint8_t threshold void 写 LP_TH_VAL,范围 0~255
ll_msc_low_power_threshold_get(mscx) — uint8_t 读取 buffered-write 阈值
ll_msc_low_power_threshold_enable(mscx) — void 置 LP_TH_EN
ll_msc_low_power_threshold_disable(mscx) — void 清 LP_TH_EN
ll_msc_is_low_power_threshold_enabled(mscx) — bool 查询低功耗阈值使能
ll_msc_low_power_count_set(mscx, count) uint8_t count void 写 LP_CNT,范围 0~127 clocks
ll_msc_low_power_count_get(mscx) — uint8_t 读取空闲时钟门控计数

LP_TH_VAL 只在 LP_TH_EN=1 且存在 buffered write 时参与 XFER_BLOCK_SIZE - LP_TH_VAL 判断;LP_CNT 只在 CLK_CTRL_R.CARD_LP_EN=1 时影响空闲卡时钟门控。两者都不替代控制器空闲检查。

5. 典型完整示例

5.1 单数据块(CMD17)PIO 读取示例

下面的示例假设 SD/eMMC 卡已经完成协议层初始化,MSC0 的时钟、pinmux、卡供电和总线宽度已经配置,并且当前没有其它命令或数据传输。block_address 的具体单位由协议层和卡类型决定,本例不把它解释为字节地址或 LBA。

#include <stdbool.h>
#include <stdint.h>

#include "ll_msc.h"
#include "im_time.h"

#define APP_MSC_BLOCK_SIZE   512U
#define APP_MSC_BLOCK_WORDS  (APP_MSC_BLOCK_SIZE / 4U)
#define APP_MSC_TIMEOUT_MS   200U

static bool app_msc_wait_cmd_ready(msc_regs_t *mscx, uint32_t timeout_ms)
{
    uint32_t start_ms = sys_time_get_ms();

    while (ll_msc_is_command_inhibited(mscx) ||
           ll_msc_is_data_command_inhibited(mscx)) {
        if ((uint32_t)(sys_time_get_ms() - start_ms) >= timeout_ms) {
            return false;
        }
        sys_time_delay_ms(1U);
    }

    return true;
}

static bool app_msc_read_one_block(msc_regs_t *mscx,
                                   uint32_t block_address,
                                   uint32_t words[APP_MSC_BLOCK_WORDS])
{
    const uint16_t clear_normal =
        MSC_NORMAL_INT_CMD_CMPLT_MASK |
        MSC_NORMAL_INT_XFER_CMPLT_MASK |
        MSC_NORMAL_INT_BUF_RD_RDY_MASK;
    uint32_t start_ms;
    uint32_t word_count = 0U;
    uint16_t transfer_mode;
    uint16_t command;

    if (!ll_msc_is_instance(mscx)) {
        return false;
    }

    if (!app_msc_wait_cmd_ready(mscx, APP_MSC_TIMEOUT_MS)) {
        return false;
    }

    ll_msc_interrupt_status_clear(mscx, clear_normal, 0xFFFFU);

    ll_msc_argument_set(mscx, block_address);
    ll_msc_block_size_set(mscx, (uint16_t)APP_MSC_BLOCK_SIZE);
    ll_msc_block_count_set(mscx, 1U);

    transfer_mode = ll_msc_transfer_mode_make(
        false,                       /* 允许 response 完成中断 */
        true,                        /* 检查 R1 响应错误位 */
        MSC_RESPONSE_CHECK_R1,
        false,                       /* 单块 */
        true,                        /* 卡到主机 */
        MSC_AUTO_COMMAND_DISABLED,
        false,                       /* 单块不使用 BLK_CNT_EN */
        false);                      /* PIO,不使用 DMA */
    ll_msc_transfer_mode_set(mscx, transfer_mode);

    command = ll_msc_command_make(17U,
                                  MSC_COMMAND_TYPE_NORMAL,
                                  true,
                                  true,
                                  true,
                                  false,
                                  MSC_RESPONSE_48_BIT);
    ll_msc_command_set(mscx, command);

    start_ms = sys_time_get_ms();
    for (;;) {
        uint16_t normal = ll_msc_normal_interrupt_status_get(mscx);
        uint16_t error = ll_msc_error_interrupt_status_get(mscx);

        if (error != 0U) {
            ll_msc_error_interrupt_status_clear(mscx, error);
            ll_msc_normal_interrupt_status_clear(mscx, clear_normal);
            return false;
        }

        if (ll_msc_is_read_buffer_ready(mscx)) {
            if (word_count >= APP_MSC_BLOCK_WORDS) {
                ll_msc_normal_interrupt_status_clear(mscx, clear_normal);
                return false;
            }

            words[word_count++] = ll_msc_buffer_data_read(mscx);
        }

        if (((normal & MSC_NORMAL_INT_XFER_CMPLT_MASK) != 0U) &&
            !ll_msc_is_read_transfer_active(mscx) &&
            !ll_msc_is_read_buffer_ready(mscx)) {
            break;
        }

        if ((uint32_t)(sys_time_get_ms() - start_ms) >= APP_MSC_TIMEOUT_MS) {
            /* 超时后的恢复由控制器层决定,可执行 CMD/DAT 软复位。 */
            ll_msc_software_reset(mscx,
                                  MSC_SOFTWARE_RESET_CMD |
                                  MSC_SOFTWARE_RESET_DAT);
            return false;
        }
    }

    ll_msc_normal_interrupt_status_clear(mscx, clear_normal);
    return word_count == APP_MSC_BLOCK_WORDS;
}

说明:

  • 示例没有等待 CMD_CMPLT 后再开始读;实际控制器层通常同时处理 CMD_CMPLT、BUF_RD_RDY 和 XFER_CMPLT。只要遵守 BUF_RD_EN,PIO 数据可以按硬件节奏消费。
  • 错误状态可能在传输中到达。这里把任意 error 位都视为失败;产品代码应按具体错误决定重试、CMD/DAT reset 或向上层报告。
  • 示例只读取一个 block。多块传输应选择多块模式、正确设置 block count/CMD12/CMD23 策略,并按相同的就绪/完成规则循环。
  • 响应值可在 CMD_CMPLT 后通过 ll_msc_response_get() 读取并交给协议层解析;本例没有解析 R1 字段。

5.2 中断和 ADMA2 配置示例

下面的代码展示 ADMA2 descriptor 地址、DMA 选择和中断使能的 LL 调用。descriptor 必须由控制器层构造,并保证物理地址、对齐和 cache 状态正确。

#include <stdbool.h>
#include <stdint.h>

#include "ll_msc.h"

static void app_msc_enable_adma2_irq(msc_regs_t *mscx)
{
    const uint16_t normal_events =
        MSC_NORMAL_INT_CMD_CMPLT_MASK |
        MSC_NORMAL_INT_XFER_CMPLT_MASK |
        MSC_NORMAL_INT_DMA_INT_MASK |
        MSC_NORMAL_INT_BUF_RD_RDY_MASK |
        MSC_NORMAL_INT_BUF_WR_RDY_MASK;

    ll_msc_normal_interrupt_status_clear(mscx, normal_events, 0xFFFFU);
    ll_msc_normal_interrupt_status_enable_set(mscx, normal_events);
    ll_msc_error_interrupt_status_enable_set(mscx, MSC_ERROR_INT_ALL_MASK);
    ll_msc_normal_interrupt_signal_enable_set(mscx, normal_events);
    ll_msc_error_interrupt_signal_enable_set(mscx, MSC_ERROR_INT_ALL_MASK);
}

static bool app_msc_start_adma2_read(msc_regs_t *mscx,
                                     uint32_t block_address,
                                     uint32_t block_count,
                                     uint64_t descriptor_address)
{
    uint16_t transfer_mode;
    uint16_t command;

    if (!ll_msc_is_instance(mscx) || (block_count == 0U)) {
        return false;
    }

    if (ll_msc_is_command_inhibited(mscx) ||
        ll_msc_is_data_command_inhibited(mscx)) {
        return false;
    }

    ll_msc_host_v3_compatible_mode_select(mscx);
    ll_msc_32bit_addressing_select(mscx);
    ll_msc_adma2_length_16bit_select(mscx);
    ll_msc_dma_select_set(mscx, MSC_DMA_V3_ADMA2_32BIT);
    ll_msc_adma_address_set(mscx, descriptor_address);

    ll_msc_argument_set(mscx, block_address);
    ll_msc_block_size_set(mscx, 512U);
    ll_msc_block_count_set(mscx, (uint16_t)block_count);

    transfer_mode = ll_msc_transfer_mode_make(
        false,
        true,
        MSC_RESPONSE_CHECK_R1,
        true,
        true,
        MSC_AUTO_COMMAND_CMD12,
        true,
        true);
    ll_msc_transfer_mode_set(mscx, transfer_mode);

    command = ll_msc_command_make(18U,
                                  MSC_COMMAND_TYPE_NORMAL,
                                  true,
                                  true,
                                  true,
                                  false,
                                  MSC_RESPONSE_48_BIT);
    ll_msc_command_set(mscx, command);
    return true;
}

static void app_msc_irq_handler(msc_regs_t *mscx)
{
    uint16_t normal = ll_msc_normal_interrupt_status_get(mscx);
    uint16_t error = ll_msc_error_interrupt_status_get(mscx);
    uint16_t normal_ack = normal &
        (MSC_NORMAL_INT_CMD_CMPLT_MASK |
         MSC_NORMAL_INT_XFER_CMPLT_MASK |
         MSC_NORMAL_INT_DMA_INT_MASK |
         MSC_NORMAL_INT_BUF_RD_RDY_MASK |
         MSC_NORMAL_INT_BUF_WR_RDY_MASK);

    if (error != 0U) {
        /* 记录错误并交给控制器层决定复位或重试。 */
    }

    if ((normal & MSC_NORMAL_INT_DMA_INT_MASK) != 0U) {
        /* SDMA 模式需要在此更新 SDMA 地址;ADMA2 通常只需记录边界事件。 */
    }

    if ((normal & MSC_NORMAL_INT_CMD_CMPLT_MASK) != 0U) {
        /* 响应已经有效,可读取 RESP01/23/45/67。 */
    }

    if ((normal & MSC_NORMAL_INT_XFER_CMPLT_MASK) != 0U) {
        /* 数据阶段结束;若使用 DMA,需要按平台规则 invalidate cache。 */
    }

    ll_msc_interrupt_status_clear(mscx, normal_ack, error);

    if ((normal & MSC_NORMAL_INT_CARD_INTERRUPT_MASK) != 0U) {
        /* CARD_INTERRUPT 是只读电平,调用方必须关闭 signal enable 并处理 SDIO 源。 */
    }
}

说明:

  • 示例使用 Host v3 兼容模式和 MSC_DMA_V3_ADMA2_32BIT。若硬件和 descriptor 支持 64-bit ADMA2,应同时检查能力位、选择 64-bit addressing 并更新 HOST_CTRL1_R.DMA_SEL。
  • descriptor_address 必须是控制器可访问的物理地址。LL 不会对 descriptor 或数据缓冲区执行 cache 操作。
  • app_msc_irq_handler() 只是 LL 状态处理骨架,不包含平台 IRQ 注册、中断清除顺序的并发保护或上层请求状态机。
  • 若 ADMA3 被选用,应使用 ll_msc_adma_id_address_set() 和对应 Host v4/CQE 配置,不应复用 ADMA2 descriptor 地址接口。

5.3 软件复位、能力查询和重调谐检查

#include <stdbool.h>
#include <stdint.h>

#include "ll_msc.h"
#include "im_time.h"

#define APP_MSC_RESET_TIMEOUT_MS 50U

static bool app_msc_reset_all(msc_regs_t *mscx)
{
    uint32_t start_ms;

    if (!ll_msc_is_instance(mscx)) {
        return false;
    }

    ll_msc_sd_clock_disable(mscx);
    ll_msc_software_reset(mscx, MSC_SOFTWARE_RESET_ALL);

    start_ms = sys_time_get_ms();
    while (ll_msc_is_software_reset_pending(mscx, MSC_SOFTWARE_RESET_ALL)) {
        if ((uint32_t)(sys_time_get_ms() - start_ms) >=
            APP_MSC_RESET_TIMEOUT_MS) {
            return false;
        }
        sys_time_delay_ms(1U);
    }

    /* 复位后必须按板级和协议要求重新配置时钟、电压、总线和中断。 */
    return true;
}

static void app_msc_report_capabilities(msc_regs_t *mscx)
{
    uint32_t caps1 = ll_msc_capabilities1_get(mscx);
    uint32_t caps2 = ll_msc_capabilities2_get(mscx);

    (void)caps1;
    (void)caps2;

    /* 可用 is_* 接口按功能逐项查询,避免手写能力位。 */
    if (ll_msc_is_sdr104_supported(mscx)) {
        /* SDR104 需要调谐,具体命令和窗口策略由协议层决定。 */
    }

    if (ll_msc_is_adma3_supported(mscx)) {
        /* 启用 ADMA3 前还要确认 Host v4、descriptor 和 CQE 组合。 */
    }
}

static bool app_msc_check_retune(msc_regs_t *mscx)
{
    if (!ll_msc_is_instance(mscx)) {
        return false;
    }

    if (!ll_msc_is_retune_requested(mscx)) {
        return true;
    }

    /* 先配置调谐数据阶段,再置 EXEC_TUNING,并由协议层下发 tuning 命令。 */
    ll_msc_execute_tuning_start(mscx);

    while (ll_msc_is_execute_tuning_active(mscx)) {
        /* 增加超时,并检查 ERROR_INT_STAT_R.TUNING_ERR。 */
    }

    if (ll_msc_is_tuned_sample_clock_selected(mscx)) {
        /* 调谐成功后继续使用 tuned sample clock。 */
        return true;
    }

    return false;
}

说明:

  • 软复位会清除控制器内部配置;示例在复位后没有自动重建初始化,调用方必须完成这一步。
  • ll_msc_is_retune_requested() 只反映 PSTATE_REG.RE_TUNE_REQ,不表示调谐已经开始或必须立即执行。调谐命令、窗口策略、重试次数和超时都由控制器层决定。
  • 调谐失败时,应先检查 ERROR_INT_STAT_R.TUNING_ERR、AUTO_CMD_STAT_R 和当前 UHS/采样时钟设置,再决定重新调谐或降速。

6. 使用约束和检查清单

6.1 关键约束

  • LL 层只支持 MSC0 和 MSC1,不验证板级是否实际引出、供电或完成 pinmux。
  • 所有接口直接访问寄存器,没有句柄、参数检查、错误码、超时、重试、锁或请求队列。
  • CMD_R 写入即触发命令;必须先检查 PSTATE_REG inhibit 条件,并保证前一条命令/数据传输已经结束。
  • XFER_MODE_R 会保留上一次配置。每次命令前必须显式设置方向、单/多块、块数、自动命令和 DMA 位,不能只修改本次关心的字段。
  • BLOCKSIZE_R.XFER_BLOCK_SIZE=0 表示无数据阶段;带数据命令必须设置合法非零块大小,并搬运完整数据量。
  • G32S10 板级通常只有 DAT0..DAT3。LL 的 8-bit helper 只是寄存器编码,不代表系统支持 8-bit 总线。
  • 修改总线宽度、UHS 模式、采样时钟、1.8 V signaling、DMA 选择或 Host v4 前,必须停止传输并满足卡协议要求。
  • Host v4 会改变 SDMASA_R(SDMA 地址或 32-bit block count)和 DMA select 编码。混用 v3/v4 配置会导致地址或计数错误。
  • SDMA 到达 boundary 后需要软件更新地址;ADMA 需要合法 descriptor、物理地址、对齐和 cache 维护。
  • 中断必须同时配置 status enable 和 signal enable。CARD_INTERRUPT 是只读电平,不能通过 W1C 清除。
  • 软件复位是异步的,且不会自动恢复时钟、供电、总线、DMA 或中断配置。
  • 调谐接口不发送命令;EXEC_TUNING 清零只表示执行位结束,仍需检查调谐错误和采样时钟状态。
  • LL 层不提供 CPM 时钟、GPIO、稳压器、card detect 消抖、SD/eMMC 协议或文件系统功能。
  • 同一控制器的读改写、命令下发和 IRQ 状态清除必须由调用方串行化,避免丢写和竞态。

6.2 首次集成检查

  • 已确认使用 MSC0 还是 MSC1,且 msc_regs_t * 只指向受支持实例。
  • 已使能 MSC 总线时钟和门控时钟,配置父源、频率、RX/TX phase,并完成 CLK/CMD/DAT/卡检测 pinmux。
  • 卡供电、复位和可选 1.8 V 稳压器已按板级顺序控制,控制器 PWR_CTRL_R 请求与外部硬件一致。
  • 控制器空闲,CMD_INH/CMD_INH_DAT/DAT_LINE_ACT 状态符合预期。
  • 已配置 timeout count,且没有写入保留值 0xF。
  • 内部时钟已请求且 INTERNAL_CLK_STABLE=1 后才启用 SD_CLK_EN。
  • 总线宽度与板级 DAT 线一致,没有误用 8-bit helper。
  • HOST_CTRL2_R 的 signaling、UHS mode、sampling clock、Host v4 和寻址模式与卡协议一致。
  • 每条命令前都设置了完整的 XFER_MODE_R,没有遗留上一次的 DMA、多块或自动命令位。
  • PIO 读写在 BUF_RD_EN/BUF_WR_EN 就绪后访问,且搬运字数与 block_size × block_count 一致。
  • PIO 操作有软件超时;超时或 CRC 错误时能执行 CMD/DAT reset 或上报控制器层。
  • SDMA/ADMA 地址为有效物理地址,descriptor 已满足对齐和 cache 要求。
  • 如果使用 Host v4,已确认 SDMASA_R 当前表示 block count 还是 SDMA 地址,并未混用两套语义。
  • normal/error status enable 与 signal enable 均已配置,平台 IRQ 已注册并正确分发到对应控制器。
  • 中断处理只清除已处理的 W1C 位,CARD_INTERRUPT 通过 signal enable 停止上报。
  • 软件复位流程带超时,复位完成后重新初始化控制器并经 ll_msc_is_instance() 重新确认实例。
  • 高频模式已执行调谐或在固定采样时钟下验证数据完整性,且检查了 TUNING_ERR。
  • 低功耗门控、卡时钟关闭和软件复位只发生在控制器空闲时。
  • 多任务/IRQ 共享同一控制器时,所有配置、命令、DMA 和状态清除路径都有明确互斥策略。