API GUIDE / MSC / HAL

MSC HAL 接口

msc hal api

本文面向 BSP、驱动和中间件开发者,说明如何通过 G32S10 MSC HAL 访问 SD/eMMC 主机控制器,并提供公开 API 参考。SD/MMC 协议状态机、命令语义、卡初始化流程、DMA descriptor 格式、cache 维护和板级 pinmux 实现不在本文范围内。

MSC HAL 是控制器执行层,负责控制器寄存器配置、中断分发、命令与数据配置、PIO 数据端口、SDMA/ADMA2 地址、总线时序和采样时钟调谐。它不直接提供 SD/eMMC 读写擦除 API;普通应用应优先使用 MSC 中间件,只有实现控制器层、协议层或进行底层诊断时才直接使用本 HAL。

API 头文件:

  • code/include/drivers/g32s10/msc/hal_msc.h

公开实现位于 code/drivers/g32s10/msc/hal_msc.c。ll_msc.h、msc_regs.h 提供寄存器级 helper、MSC 实例和位域常量,属于 HAL 的内部依赖;msc_pinmux.h 提供板级总线引脚描述,也不属于本文公开 API。code/middleware/msc/ 中的 MSC 中间件在 HAL 之上完成协议、超时、DMA/cache、分区和文件系统集成。

MSC HAL 不负责 CPM 时钟源选择、频率设置和门控使能,不配置 GPIO pinmux,不控制卡供电、复位或 1.8 V 外部稳压器,不构造 ADMA descriptor,不维护 cache,也不等待命令或数据传输完成。调用方必须完成这些准备,并自行串行化同一控制器上的并发访问。


1. 模块概览

G32S10 MSC HAL 为 MSC0 和 MSC1 两个主机控制器提供统一的寄存器操作接口。控制器、IRQ 号和 CPM 时钟 ID 通过静态控制器表公开;HAL 驱动上下文由控制器层持有,不需要调用方分配或释放。

  • hal_msc_ctrl_count() 固定返回 2;hal_msc_ctrl_get() 按 0、1 返回 MSC0、MSC1 的控制器描述。
  • hal_msc_drv_t 是不透明类型。通过 hal_msc_ctrl_get(ctrl_id)->drv 取得,生命周期由 HAL 静态管理,不能 free()、复制或手工构造。
  • HAL 没有 init/deinit 软件状态机,也不维护 READY/BUSY 状态。寄存器可立即访问,硬件是否可下发新请求必须通过 hal_msc_present_state_get() 和中断状态判断。
  • 所有配置、启动和读写函数都只完成寄存器写入或读取,不等待硬件完成。命令完成、传输完成、DMA 边界和错误需要调用方通过中断或轮询获知。
  • 默认 DMA 模式为 HAL_MSC_DMA_PIO_POLL,每个控制器独立保存。DMA 模式只在 hal_msc_data_configure() 处理带数据请求时生效。
  • G32S10 只支持 1-bit 和 4-bit 数据总线。HAL_MSC_BUS_WIDTH_8 保留在枚举中,但 hal_msc_bus_config_apply() 会返回 IM_ERR_UNSUPPORTED。
  • hal_msc_command_data_reset() 只复位 CMD/DAT 线逻辑,不等同于整个控制器复位,也不恢复时钟、电源、总线宽度或工作模式。
  • 中断回调运行在硬中断上下文。hal_msc_set_cb() 只保存函数指针,不提供锁;注册、替换和清空回调时调用方必须保证与 IRQ 不并发。
  • HAL 内部不提供互斥保护。生命周期、时钟/电源切换、总线时序、DMA 和调谐操作必须在控制器空闲时串行执行。
  • 本文按 CONFIG_SDK_PARAM_CHECK=y 且 CONFIG_SDK_HAL_PARAM_CHECK=y 描述参数检查行为。关闭参数检查后,空 drv、空输出指针和非法实例不保证被拦截。
  • 除 hal_msc_set_cb() 的非法 event 和 hal_msc_bus_config_apply() 的 8-bit 宽度返回 IM_ERR_UNSUPPORTED 外,其余带 error_code_t 返回值的公开接口只报告参数类错误;硬件超时、CRC、命令错误和 DMA 错误必须由调用方从状态寄存器判断。

2. 典型流程

2.1 Kconfig 配置

MSC 驱动配置位于 code/drivers/g32s10/msc/Kconfig,菜单路径为 Drivers -> G32S10 Drivers -> MSC driver。

配置项 默认值 说明
CONFIG_DRIVER_MSC n 编译 MSC HAL,即 hal_msc.c
CONFIG_SDK_PARAM_CHECK 由 SDK 工程决定 SDK 参数检查总开关
CONFIG_SDK_HAL_PARAM_CHECK 由 SDK 工程决定 HAL 参数检查开关;总开关也必须启用

仅使用 HAL 时启用:

CONFIG_DRIVER_MSC=y
CONFIG_SDK_PARAM_CHECK=y
CONFIG_SDK_HAL_PARAM_CHECK=y

需要使用完整的 SD/eMMC/SDIO 设备、块设备和文件系统能力时,应启用 MSC 中间件。中间件会自动选择 CONFIG_DRIVER_MSC,但仍需按业务选择 DFS、ELM FatFs 和 Shell 等组件。参考配置位于 code/middleware/msc/example/。

2.2 板级准备

在访问 MSC 寄存器前完成以下准备:

  • 确认使用 MSC0 还是 MSC1,并按原理图配置 CLK、CMD 和 DAT0..DAT3 的 pinmux。G32S10 不提供 DAT4..DAT7。
  • 使用控制器描述中的 clk_id 和 clk_gate_id 使能总线时钟和门控时钟。HAL 不负责时钟父源和频率。
  • 完成 SD/eMMC 供电、复位和可选 1.8 V 电压切换 GPIO 的板级控制。HAL 的 hal_msc_1v8_signaling_set() 只切换控制器 signaling 位。
  • 在改变总线宽度、时序或供电前停止卡时钟,并确认 CMD/DAT 没有活动传输。
  • 为控制器 IRQ 准备平台中断资源。如果只使用 PIO 轮询,可以不注册中断,但仍需通过状态寄存器判断完成和错误。

典型的时钟使能顺序如下;具体错误处理应回滚已经完成的操作:

#include "hal_msc.h"

const hal_msc_ctrl_t *ctrl = hal_msc_ctrl_get(0U);
if (ctrl != NULL) {
    (void)hal_cpm_clk_enable(ctrl->clk_id);
    (void)hal_cpm_clk_enable(ctrl->clk_gate_id);
}

2.3 中断注册

如果使用中断模式,控制器层需要为事件 0~5 注册回调,并把 ctrl->drv 作为 hal_msc_irq_handler() 的 context 注册到平台 IRQ:

static void app_msc_irq_cb(hal_msc_drv_t *drv, int8_t event,
                           uint16_t normal_status, uint16_t error_status)
{
    (void)drv;
    (void)normal_status;
    (void)error_status;

    /* 仅做 ISR 安全的记录、唤醒或边界处理。 */
    (void)event;
}

const hal_msc_ctrl_t *ctrl = hal_msc_ctrl_get(0U);
if (ctrl != NULL) {
    for (int8_t event = 0; event <= 5; ++event)
        (void)hal_msc_set_cb(ctrl->drv, event, app_msc_irq_cb);

    (void)sys_irq_request(ctrl->irq, hal_msc_irq_handler,
                         ctrl->irq_name, ctrl->drv);
    (void)sys_irq_enable(ctrl->irq);
}

hal_msc_irq_handler() 只分发已同时使能 status latch 和 signal enable 的事件。注册回调后还必须调用 hal_msc_irq_status_enable_set() 和 hal_msc_irq_signal_enable_set(),否则中断不会产生或不会被处理。事件编号与含义见 3.5 节。

2.4 时钟、供电和总线配置

控制器层通常按以下顺序切换工作条件。每一步都应检查返回值,并为硬件状态轮询设置超时:

  1. 关闭卡时钟,必要时通过 hal_msc_command_data_reset() 清理残留的 CMD/DAT 状态,并轮询复位完成。
  2. 调用 hal_msc_initial_clock_set(true),再轮询 hal_msc_initial_clock_stable_get(),确保控制器内部时钟稳定。
  3. 根据板级电压和卡状态调用 hal_msc_1v8_signaling_set(),再通过 hal_msc_power_control_set() 写入完整的 PWR_CTRL_R 控制字节。
  4. 设置 CPM 时钟父源和频率,并通过 hal_msc_clock_configure() 配置 MSC 时钟相位、tuning 和 EXCLK。
  5. 调用 hal_msc_bus_config_apply() 应用总线宽度和时序。
  6. 调用 hal_msc_card_clock_set(true) 同时打开内部时钟和卡时钟。

hal_msc_initial_clock_set()、hal_msc_power_control_set() 和 hal_msc_bus_config_apply() 都是寄存器写操作,不检查传输是否正在进行。调用方必须先确认控制器空闲。

2.5 PIO 命令和数据流程

PIO 模式使用 hal_msc_buffer_read() / hal_msc_buffer_write() 搬运 32-bit word。一次典型的数据请求如下:

  1. 通过 hal_msc_dma_mode_set() 选择 HAL_MSC_DMA_PIO_POLL。
  2. 构造 hal_msc_data_config_t,填写 block_size、block_count 和原始 transfer_mode 位域,再调用 hal_msc_data_configure()。
  3. 调用 hal_msc_command_start() 下发命令;带数据时 has_data 必须为 true。
  4. 轮询 hal_msc_present_state_get() 的 BUF_RD_EN 或 BUF_WR_EN,在就绪时读写 BUF_DATA_R。
  5. 通过 hal_msc_irq_state_get() 或回调等待 CMD_CMPLT、XFER_CMPLT 和错误状态;确认完成后用 hal_msc_irq_status_ack() 清除 W1C 位。
  6. 需要响应时调用 hal_msc_response_get();发生协议错误时按控制器层策略复位 CMD/DAT 线。

PIO word 不保证与协议字节序一致,字节拆装由命令和 phase 定义决定。必须按 block_size × block_count 搬运完整数据量,不能只依赖 XFER_CMPLT 判断缓冲区已经取空。

2.6 DMA 和调谐边界

  • HAL_MSC_DMA_SDMA_IRQ 使用 32-bit SDMA 地址,hal_msc_data_configure() 固定 512 KiB 边界。收到 DMA 边界事件后,调用方必须更新 SDMA 地址。
  • HAL_MSC_DMA_ADMA2_IRQ 使用 16-bit ADMA2 descriptor 长度模式。hal_msc_adma2_address_set() 只写入 descriptor 物理地址,descriptor 构造、对齐和 cache 维护由调用方负责。
  • DMA 模式切换只修改 HAL 软件选择,必须在控制器空闲时进行,并在下一次 hal_msc_data_configure() 时生效。
  • HS200、SDR104 等模式需要调谐时,先通过 hal_msc_tuning_start() 启动执行调谐,再下发对应的调谐命令。hal_msc_tuning_start() 不发送命令,也不等待调谐完成;调用方轮询 hal_msc_tuning_active_get()。
  • DDR50 调谐结束后通常需要调用 hal_msc_timing_recover(drv, true, NULL) 恢复固定采样时钟;是否恢复以及使用哪种时序应以控制器层状态和卡协议要求为准。

3. 配置和关键类型

3.1 控制器描述和驱动上下文

typedef struct hal_msc_drv hal_msc_drv_t;

typedef struct {
    uint32_t index;
    hal_msc_drv_t *drv;
    uint32_t irq;
    const char *irq_name;
    hal_cpm_id_t clk_id;
    hal_cpm_id_t clk_gate_id;
    const char *thread_irq_name;
    uint32_t max_freq_hz;
} hal_msc_ctrl_t;
字段 说明
index 控制器编号,MSC0 为 0,MSC1 为 1
drv HAL 静态驱动上下文,传给所有 hal_msc_*() 接口
irq 控制器硬中断号
irq_name 注册平台 IRQ 时使用的名称
clk_id MSC 总线时钟的 CPM ID
clk_gate_id MSC 门控时钟的 CPM ID
thread_irq_name 控制器层线程中断名称,不属于 HAL 寄存器接口
max_freq_hz 控制器最大频率,当前为 200 MHz

控制器描述表和驱动上下文都是静态对象。hal_msc_ctrl_get() 返回借用指针,不能释放或修改。hal_msc_drv_t 的内部字段不对外公开。

3.2 DMA 执行模式

typedef enum {
    HAL_MSC_DMA_PIO_POLL = 0U,
    HAL_MSC_DMA_SDMA_IRQ,
    HAL_MSC_DMA_ADMA2_IRQ,
    HAL_MSC_DMA_MODE_COUNT,
} hal_msc_dma_mode_t;
模式 数据通路 完成/边界通知 地址参数
HAL_MSC_DMA_PIO_POLL BUF_DATA_R 32-bit 端口 软件轮询状态 不使用
HAL_MSC_DMA_SDMA_IRQ 内部 SDMA DMA/transfer 中断 hal_msc_sdma_address_set()
HAL_MSC_DMA_ADMA2_IRQ ADMA2 descriptor DMA/transfer 中断 hal_msc_adma2_address_set()

默认模式为 HAL_MSC_DMA_PIO_POLL。模式值按控制器保存在 HAL 上下文中,不会立即写硬件。

3.3 总线宽度和时序

typedef enum {
    HAL_MSC_BUS_WIDTH_1 = 1,
    HAL_MSC_BUS_WIDTH_4 = 4,
    HAL_MSC_BUS_WIDTH_8 = 8,
} hal_msc_bus_width_t;

typedef enum {
    HAL_MSC_TIMING_LEGACY = 0,
    HAL_MSC_TIMING_HS_SDR,
    HAL_MSC_TIMING_HS_DDR,
    HAL_MSC_TIMING_HS200,
    HAL_MSC_TIMING_SDR12,
    HAL_MSC_TIMING_SDR25,
    HAL_MSC_TIMING_SDR50,
    HAL_MSC_TIMING_SDR104,
    HAL_MSC_TIMING_DDR50,
} hal_msc_timing_t;
时序值 适用介质 UHS mode 寄存器编码 sampling clock
HAL_MSC_TIMING_LEGACY eMMC legacy eMMC legacy 固定
HAL_MSC_TIMING_HS_SDR eMMC High Speed SDR eMMC HS SDR 固定
HAL_MSC_TIMING_HS_DDR eMMC DDR52 eMMC HS DDR 固定
HAL_MSC_TIMING_HS200 eMMC HS200 eMMC HS200 调谐时钟
HAL_MSC_TIMING_SDR12 SD SDR12 SD SDR12 固定
HAL_MSC_TIMING_SDR25 SD SDR25 SD SDR25 固定
HAL_MSC_TIMING_SDR50 SD SDR50 SD SDR50 固定
HAL_MSC_TIMING_SDR104 SD SDR104 SD SDR104 固定(调谐流程另控)
HAL_MSC_TIMING_DDR50 SD DDR50 SD DDR50 固定(调谐后可恢复)

HAL_MSC_BUS_WIDTH_8 只是寄存器接口保留值。G32S10 没有 DAT4..DAT7 通路,该值在总线配置接口中返回不支持。

3.4 调谐状态和控制器能力

typedef struct {
    hal_msc_uhs_mode_t uhs_mode;
    bool tuned_sample_clock_selected;
    uint16_t host_ctrl2;
} hal_msc_timing_state_t;

typedef struct {
    uint32_t max_block_size;
    uint8_t max_current_1v8;
    uint8_t max_current_3v0;
    uint8_t max_current_3v3;
    uint8_t host_spec_version;
} hal_msc_capabilities_t;

typedef enum {
    HAL_MSC_UHS_MODE_SDR12 = 0,
    HAL_MSC_UHS_MODE_SDR25,
    HAL_MSC_UHS_MODE_SDR50,
    HAL_MSC_UHS_MODE_SDR104,
    HAL_MSC_UHS_MODE_DDR50,
    HAL_MSC_UHS_MODE_UNKNOWN,
} hal_msc_uhs_mode_t;
能力字段 说明
max_block_size 控制器支持的最大块大小,由 512 << MAX_BLK_LEN 计算,单位为字节
max_current_1v8 1.8 V 最大电流能力寄存器字段,保持硬件原始编码
max_current_3v0 3.0 V 最大电流能力寄存器字段,保持硬件原始编码
max_current_3v3 3.3 V 最大电流能力寄存器字段,保持硬件原始编码
host_spec_version SD Host Controller 规范版本字段,保持硬件原始编码

hal_msc_timing_state_t::host_ctrl2 是恢复前读取的完整 HOST_CTRL2_R 快照,可用于控制器层记录或诊断。

3.5 中断状态和事件

typedef struct {
    uint16_t normal_status;
    uint16_t error_status;
    uint16_t normal_enable;
    uint16_t error_enable;
    uint16_t normal_signal_enable;
    uint16_t error_signal_enable;
} hal_msc_irq_state_t;

typedef void (*hal_msc_irq_cb_t)(hal_msc_drv_t *drv, int8_t event,
                                 uint16_t normal_status,
                                 uint16_t error_status);

hal_msc_irq_handler() 使用的事件编号如下。事件枚举定义在 HAL 实现内部,hal_msc.h 只公开 int8_t event 参数;控制器层按下列固定编号注册回调。

event 事件 触发条件 典型处理
0 Command Complete NORMAL_INT_STAT_R.CMD_CMPLT 置位 记录命令完成并读取响应
1 Transfer Complete NORMAL_INT_STAT_R.XFER_CMPLT 置位 结束数据请求
2 DMA Boundary NORMAL_INT_STAT_R.DMA_INT 置位 更新 SDMA 地址或推进 ADMA 状态
3 Buffer Read Ready NORMAL_INT_STAT_R.BUF_RD_RDY 置位 读取 PIO 或调谐数据
4 Card Interrupt NORMAL_INT_STAT_R.CARD_INTERRUPT 置位 处理 SDIO DAT1 中断
5 Error 任一使能的错误状态置位 记录错误并终止当前请求

normal_status 和 error_status 是处理本次事件时的 pending 位图。错误事件优先于命令/传输完成事件处理;同一 IRQ 中若同时存在错误和完成位,hal_msc_irq_handler() 会先回调 Error 并返回,不再派发普通完成事件。

CARD_INTERRUPT 是 DAT1 电平的只读状态,W1C 写操作不能清除。它通过 normal signal enable 控制 IRQ 上报;同时 HAL 使用 normal status enable 掩码过滤 pending 事件,因此控制器层还需把 MSC_NORMAL_INT_CARD_INTERRUPT_MASK 包含在 normal status enable 位图中。收到该事件后,调用方应先禁用对应 signal enable,再处理 SDIO 源,避免中断风暴。

3.6 数据配置和请求标志

typedef struct {
    bool has_data;
    uint16_t block_size;
    uint16_t block_count;
    uint16_t transfer_mode;
} hal_msc_data_config_t;
字段 说明
has_data 是否配置数据阶段;为 false 时只写 transfer_mode
block_size 块大小,带数据时范围为 1~2048 字节
block_count 块数,写入 BLOCK_COUNT_R
transfer_mode XFER_MODE_R 原始位域,DMA_EN 由 HAL 根据 DMA 模式覆盖

hal_msc_data_config_t::transfer_mode 不经过 HAL 封装。调用方需要按本次请求填写块计数使能、多块、方向、自动命令和响应检查等位;可使用 msc_regs.h 中的 MSC_XFER_MODE_R_* 位域宏。HAL 只清除并重新生成 DMA_EN 位。

hal_msc_command_start() 的 response_flags 使用以下公开宏:

宏 作用
HAL_MSC_RESPONSE_PRESENT 期待卡返回响应
HAL_MSC_RESPONSE_136 期待 136-bit 响应;不能与 HAL_MSC_RESPONSE_BUSY 同时使用
HAL_MSC_RESPONSE_BUSY 期待带 DAT0 busy 的 48-bit 响应
HAL_MSC_RESPONSE_CRC 启用响应 CRC 检查
HAL_MSC_RESPONSE_INDEX 启用响应命令索引检查
HAL_MSC_REQUEST_DATA 控制器层标记:该请求包含数据阶段
HAL_MSC_REQUEST_TUNING 控制器层标记:该请求用于执行调谐

后两个宏属于控制器层请求描述标记,hal_msc_command_start() 本身不使用它们;是否带数据仍由独立的 has_data 参数决定。

3.7 公开类型和常用常量索引

类型或常量 用途
hal_msc_ctrl_t 控制器编号、IRQ、CPM ID、驱动上下文和最大频率
hal_msc_drv_t 不透明 HAL 驱动上下文
hal_msc_dma_mode_t PIO、SDMA 或 ADMA2 请求模式
hal_msc_bus_width_t 1-bit、4-bit 和保留的 8-bit 总线宽度
hal_msc_timing_t SD/eMMC 总线时序
hal_msc_uhs_mode_t HOST_CTRL2_R.UHS_MODE_SEL 的软件视图
hal_msc_irq_state_t 中断状态、status enable 和 signal enable 快照
hal_msc_irq_cb_t 硬中断事件回调
hal_msc_capabilities_t 最大块大小、电流能力和规范版本
hal_msc_timing_state_t UHS 模式和采样时钟恢复快照
hal_msc_data_config_t 数据阶段块大小、块数和传输模式
HAL_MSC_RESPONSE_* 命令响应类型和检查标志
HAL_MSC_REQUEST_* 控制器层数据/调谐请求标记

3.8 常见错误码

HAL 公开接口只产生以下错误码。命令超时、响应 CRC、数据 CRC、DMA 错误和调谐失败来自中断/状态寄存器,不由这些函数直接返回。

错误码 触发条件 处理建议
IM_OK 寄存器写入或状态读取成功 继续等待硬件事件或进入下一步
IM_ERR_INVALID_PARAM drv、输出指针、控制器 ID、IRQ event、宽度/时序、相位、opcode、块大小或响应标志非法 修正调用参数;关闭参数检查时需由调用方自行校验
IM_ERR_UNSUPPORTED 请求 8-bit 总线,或 hal_msc_set_cb() 的 event 超出范围 使用 1-bit/4-bit 总线;检查事件编号

hal_msc_ctrl_get() 在控制器 ID 非法时返回 NULL,不返回错误码。

3.8.1 常见问题定位

现象 检查项
没有 SDCLK 检查 CPM 父源、频率和门控,确认 hal_msc_initial_clock_set() 后内部时钟稳定,再调用 hal_msc_card_clock_set(true)
Command Complete 一直不来 检查 IRQ 注册、status enable、signal enable、命令参数和 PSTATE inhibit 位;必要时执行 CMD/DAT 复位
中断持续重复 清除对应 W1C 状态;CARD_INTERRUPT 不能 W1C,必须关闭 normal signal enable
4-bit 传输数据错误 检查 DAT0..DAT3 pinmux、卡的总线宽度切换命令、采样相位和时序模式
高速模式不稳定 检查 1.8 V signaling、CPM RX/TX phase、tuning 流程和 PCB 信号完整性
ADMA2 失败 检查 descriptor 物理地址、16-bit length 模式、对齐、cache 和 DMA 能力位
SDMA 到边界后停止 在 DMA Boundary 事件中更新 SDMA 地址;确认 buffer 满足 512 KiB boundary
只要配置 8-bit 就失败 G32S10 只支持 1-bit/4-bit;HAL_MSC_BUS_WIDTH_8 不属于可用配置
电源切换后无法识别卡 确认卡时钟已关闭、外部稳压器和 GPIO 已按板级顺序切换,再恢复时钟
CMD/DAT 复位一直 pending 检查控制器时钟/供电和硬件状态,确认没有持续总线活动;复位不是全局控制器复位

4. API 参考

4.1 API 总览

hal_msc.h

API 用途
hal_msc_ctrl_count() 返回 MSC 控制器数量
hal_msc_ctrl_get() 按控制器编号取得静态控制器描述
hal_msc_set_cb() 注册或清除中断事件回调
hal_msc_irq_handler() 处理控制器中断并派发 HAL 事件
hal_msc_irq_state_get() 读取中断状态、状态使能和信号使能快照
hal_msc_irq_status_ack() 以 W1C 方式清除中断状态
hal_msc_irq_status_enable_set() 写入完整的 normal/error 状态锁存使能位图
hal_msc_irq_signal_enable_set() 写入完整的 normal/error IRQ 信号使能位图
hal_msc_card_clock_set() 打开或关闭 SD 卡时钟
hal_msc_card_low_power_configure() 配置空闲卡时钟门控和缓冲写低功耗阈值
hal_msc_1v8_signaling_set() 设置 1.8 V signaling 使能位
hal_msc_1v8_signaling_get() 读取 1.8 V signaling 使能位
hal_msc_initial_clock_set() 打开或关闭控制器内部时钟
hal_msc_initial_clock_stable_get() 查询内部时钟是否稳定
hal_msc_power_control_set() 写入完整的控制器电源控制字节
hal_msc_bus_config_apply() 应用总线宽度和时序模式
hal_msc_timing_recover() 读取时序/采样时钟快照,并可选恢复 fixed sampling clock
hal_msc_clock_configure() 配置 MSC CPM 时钟相位、调谐和 EXCLK
hal_msc_present_state_get() 读取控制器原始实时状态
hal_msc_capabilities_get() 读取控制器能力信息和规范版本
hal_msc_command_data_reset() 请求 CMD/DAT 线逻辑复位
hal_msc_command_data_reset_pending_get() 查询 CMD/DAT 线复位是否仍在进行
hal_msc_command_start() 写入参数并启动一次普通 SD/MMC 命令
hal_msc_data_configure() 配置数据阶段和 DMA 模式
hal_msc_response_get() 读取四个响应寄存器
hal_msc_buffer_read() 从 PIO 数据端口读取一个 32-bit word
hal_msc_buffer_write() 向 PIO 数据端口写入一个 32-bit word
hal_msc_dma_mode_set() 设置请求使用的 PIO/DMA 模式
hal_msc_dma_mode_get() 读取当前 PIO/DMA 软件模式
hal_msc_sdma_address_set() 设置 SDMA 系统地址
hal_msc_adma2_address_set() 设置 ADMA2 descriptor 地址
hal_msc_tuning_start() 配置调谐块大小并启动执行调谐
hal_msc_tuning_active_get() 查询执行调谐是否仍在进行

4.2 通用约定

  • 除 hal_msc_ctrl_count()、hal_msc_ctrl_get() 和 hal_msc_irq_handler() 外,公开接口都接收 hal_msc_drv_t *drv。drv 必须来自 hal_msc_ctrl_get() 返回的控制器描述,不能为空,也不能是调用方自行构造的对象。
  • 返回 error_code_t 的接口以 IM_OK 表示寄存器操作已提交或状态已读取;它不表示命令、DMA 或调谐已经在硬件上完成。
  • HAL 不返回 IM_ERR_TIMEOUT、IM_ERR_BUSY 或协议错误。调用方需要检查 hal_msc_present_state_get()、hal_msc_irq_state_get() 和错误回调,并自行实现超时。
  • 输出参数必须指向可写存储。HAL 不会为调用方分配缓冲区,也不会保留输出指针。
  • 所有配置函数都直接访问控制器寄存器。若同一控制器可能被多个任务或 IRQ 路径访问,调用方必须自行提供互斥或禁用中断。
  • 参数检查关闭时,接口不会拦截空指针、非法枚举或越界 event。此时必须由调用方保证参数有效,否则可能访问非法寄存器或数组。

4.3 控制器发现 API

hal_msc_ctrl_count()

uint32_t hal_msc_ctrl_count(void);

返回 MSC 控制器数量。G32S10 当前固定为 2,对应 MSC0 和 MSC1。

返回:控制器数量,不会失败。

hal_msc_ctrl_get()

const hal_msc_ctrl_t *hal_msc_ctrl_get(uint32_t ctrl_id);

返回指定控制器的静态描述,包含 HAL 驱动上下文、IRQ 号、IRQ 名称和 CPM 时钟 ID。

参数 说明
ctrl_id 控制器编号,有效值为 0(MSC0)或 1(MSC1)

返回:

  • 有效控制器:指向静态 hal_msc_ctrl_t 的借用指针。
  • 非法 ctrl_id:NULL。

说明:

  • 返回指针和其中的 drv 都不能释放、复制或长期保存到控制器之外。
  • 启用 HAL 参数检查时,非法编号会记录一次参数检查失败,然后返回 NULL。
  • 本接口不使能时钟、不注册 IRQ,也不访问控制器寄存器。

4.4 中断 API

hal_msc_set_cb()

error_code_t hal_msc_set_cb(hal_msc_drv_t *drv, int8_t event,
                            hal_msc_irq_cb_t cb);

为一个 HAL 中断事件注册或清除回调。事件编号为 0~5,含义见 3.5 节。

参数 说明
drv HAL 驱动上下文
event 事件编号 0~5
cb 回调函数;传 NULL 表示清除该事件回调

返回:

  • IM_OK:回调已保存。
  • IM_ERR_INVALID_PARAM:drv 无效。
  • IM_ERR_UNSUPPORTED:event 不在 0~5 范围内。

说明:

  • 本接口只更新 HAL 内存中的回调表,不使能控制器中断,也不注册平台 IRQ。
  • 回调可能在硬中断上下文并发执行;替换回调时调用方必须保证与当前 IRQ 不冲突。
  • 回调内只能执行 ISR 安全操作,不能阻塞、睡眠或执行耗时协议流程。

hal_msc_irq_handler()

void hal_msc_irq_handler(uint32_t irq, void *context);

处理 MSC 控制器硬中断并派发 HAL 事件。该函数适合直接作为 sys_irq_request() 的 handler 使用。

参数 说明
irq 平台 IRQ 号;HAL 当前不使用该值
context 必须是 hal_msc_ctrl_t::drv,即 hal_msc_drv_t *

返回:无。

处理顺序:

  1. 读取 hal_msc_irq_state_t,计算 status & enable 得到 pending 位。
  2. 先处理 CARD_INTERRUPT 事件,但不清除该只读状态位。
  3. 若错误 pending 非零,先清除错误 W1C 位,回调 Error 事件并返回;本次 IRQ 不再派发普通完成事件。
  4. 否则按 Command Complete、Transfer Complete、DMA Boundary、Buffer Read Ready 的顺序清除状态并派发事件。

说明:

  • context 为空或不是有效 HAL 驱动时,函数无法执行有效中断处理;关闭参数检查时可能发生非法地址访问。
  • 函数不清除未使能的状态位,也不修改 status/signal enable。
  • 函数不处理 BUF_WR_RDY 事件;写 PIO 仍由调用方根据 PSTATE 轮询。
  • CARD_INTERRUPT 是 DAT1 电平状态,W1C 不能清除。需要停止 SDIO 中断时,调用方必须修改 normal signal enable。

hal_msc_irq_state_get()

error_code_t hal_msc_irq_state_get(const hal_msc_drv_t *drv,
                                   hal_msc_irq_state_t *state_out);

一次性读取 normal/error 状态、status enable 和 signal enable。

参数 说明
drv HAL 驱动上下文
state_out 输出快照,不能为 NULL

返回:

  • IM_OK:快照已读取。
  • IM_ERR_INVALID_PARAM:drv 或 state_out 无效。

说明:

  • 本接口不修改任何中断寄存器。
  • *_status 与 *_enable 需要调用方按位与后才能得到实际 pending 事件。
  • normal_enable 表示 status latch enable,同时被 hal_msc_irq_handler() 用作 pending 过滤掩码;normal_signal_enable 才表示 IRQ 输出使能。SDIO 卡中断也应把 CARD_INTERRUPT 位包含在 normal status enable 中。

hal_msc_irq_status_ack()

error_code_t hal_msc_irq_status_ack(hal_msc_drv_t *drv,
                                    uint16_t normal_mask,
                                    uint16_t error_mask);

以 W1C 方式清除 normal 和 error 中断状态。

参数 说明
drv HAL 驱动上下文
normal_mask 要清除的 normal 状态位;为 0 时不清除
error_mask 要清除的 error 状态位;为 0 时不清除

返回:

  • IM_OK:写操作已完成。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 只修改状态寄存器,不修改 status/signal enable。
  • 运行中清零不会停止硬件继续产生新的状态。
  • 只读位(例如 CARD_INTERRUPT)不会被本接口清除。
  • 完成回调前应先清除对应状态,避免同一事件重复上报。

hal_msc_irq_status_enable_set()

error_code_t hal_msc_irq_status_enable_set(hal_msc_drv_t *drv,
                                           uint16_t normal_mask,
                                           uint16_t error_mask);

写入完整的 normal/error status latch enable 位图。

参数 说明
drv HAL 驱动上下文
normal_mask 完整的 NORMAL_INT_STAT_EN_R 位图
error_mask 完整的 ERROR_INT_STAT_EN_R 位图

返回:

  • IM_OK:两个 enable 寄存器已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 这是整寄存器覆盖写入,不是按位 OR。调用方需要保留的其他使能位必须一起传入。
  • 传 0 会关闭对应组的所有 status latch。
  • status enable 只决定状态是否锁存;要产生 IRQ,还必须设置 signal enable。
  • CARD_INTERRUPT 的硬件状态是只读电平,不能通过 W1C 清除。调用方应将 MSC_NORMAL_INT_CARD_INTERRUPT_MASK 包含在 normal status enable 和 normal signal enable 中,使 HAL 能过滤并上报该事件。

hal_msc_irq_signal_enable_set()

error_code_t hal_msc_irq_signal_enable_set(hal_msc_drv_t *drv,
                                           uint16_t normal_mask,
                                           uint16_t error_mask);

写入完整的 normal/error IRQ signal enable 位图。

参数 说明
drv HAL 驱动上下文
normal_mask 完整的 NORMAL_INT_SIGNAL_EN_R 位图
error_mask 完整的 ERROR_INT_SIGNAL_EN_R 位图

返回:

  • IM_OK:两个 signal enable 寄存器已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 这是整寄存器覆盖写入。需要保留的事件必须一起传入,不能只传本次新增的位。
  • signal enable 只控制状态是否路由到 IRQ 输出,不负责锁存状态;普通事件还需要 status enable。
  • SDIO 卡中断通过 normal signal enable 的 CARD_INTERRUPT 位控制实际 IRQ 输出,并通过 normal status enable 的同一位参与 HAL pending 过滤。

4.5 时钟、低功耗、电压和电源 API

hal_msc_card_clock_set()

error_code_t hal_msc_card_clock_set(hal_msc_drv_t *drv, bool enable);

打开或关闭卡时钟输出。

参数 说明
drv HAL 驱动上下文
enable true 打开内部时钟和 SD 卡时钟;false 关闭 SD 卡时钟和内部时钟

返回:

  • IM_OK:控制位已更新。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 打开顺序为 internal clock → SD clock;关闭顺序为 SD clock → internal clock。
  • 本接口不设置 CPM 父源、频率或门控,也不等待时钟稳定;稳定判断使用 hal_msc_initial_clock_stable_get()。
  • 改变 pinmux、供电、总线宽度或时序前,应先关闭卡时钟并确认 CMD/DAT 空闲。

hal_msc_card_low_power_configure()

error_code_t hal_msc_card_low_power_configure(hal_msc_drv_t *drv,
                                              bool enable, uint8_t count,
                                              bool threshold_enable,
                                              uint8_t threshold);

配置空闲卡时钟门控和缓冲写低功耗阈值。

参数 说明
drv HAL 驱动上下文
enable 是否使能空闲卡时钟门控
count 空闲时钟计数,有效范围 0~127
threshold_enable 是否使能缓冲写低功耗阈值
threshold 阈值字段,有效范围 0~255

返回:

  • IM_OK:配置已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 函数会先写 count 和 threshold,再根据两个使能参数切换门控位。
  • 当前实现不检查 count 和 threshold 的取值;超过字段宽度时会被寄存器掩码截断,调用方必须限制在有效范围。
  • 本接口不改变 CPM 门控或卡供电,也不等待低功耗状态生效。

hal_msc_1v8_signaling_set()

error_code_t hal_msc_1v8_signaling_set(hal_msc_drv_t *drv, bool enable);

设置控制器的 1.8 V signaling enable 位。

参数 说明
drv HAL 驱动上下文
enable true 使能 1.8 V signaling,false 关闭

返回:

  • IM_OK:控制位已更新。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 本接口只修改控制器位,不改变外部稳压器、GPIO 或卡端供电。切换前必须由板级代码完成电压和电气条件。
  • 该位不同于采样时钟选择位;调谐状态请使用 hal_msc_timing_recover() 查询。

hal_msc_1v8_signaling_get()

error_code_t hal_msc_1v8_signaling_get(const hal_msc_drv_t *drv,
                                       bool *enabled_out);

读取控制器 1.8 V signaling enable 位。

参数 说明
drv HAL 驱动上下文
enabled_out 输出是否为 1.8 V signaling,不能为 NULL

返回:

  • IM_OK:状态已读取。
  • IM_ERR_INVALID_PARAM:drv 或 enabled_out 无效。

hal_msc_initial_clock_set()

error_code_t hal_msc_initial_clock_set(hal_msc_drv_t *drv, bool enable);

打开或关闭控制器内部时钟,不控制 SD 卡时钟输出。

参数 说明
drv HAL 驱动上下文
enable true 使能内部时钟,false 关闭

返回:

  • IM_OK:控制位已更新。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 控制器初始化通常在打开卡时钟前先使能内部时钟,并等待其稳定。
  • 本接口不设置频率、不打开 CPM 门控,也不等待稳定。

hal_msc_initial_clock_stable_get()

error_code_t hal_msc_initial_clock_stable_get(const hal_msc_drv_t *drv,
                                              bool *stable_out);

读取内部时钟稳定状态。

参数 说明
drv HAL 驱动上下文
stable_out 输出是否稳定,不能为 NULL

返回:

  • IM_OK:状态已读取。
  • IM_ERR_INVALID_PARAM:drv 或 stable_out 无效。

说明:本接口只返回当前状态,不提供超时或重试;调用方必须自行限制轮询时间。

hal_msc_power_control_set()

error_code_t hal_msc_power_control_set(hal_msc_drv_t *drv, uint8_t control);

写入完整的 PWR_CTRL_R 总线电源控制字节。

参数 说明
drv HAL 驱动上下文
control 完整的 PWR_CTRL_R 位图,包含总线电源和 VDD1 电压编码

返回:

  • IM_OK:电源控制字节已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • control 是整寄存器原始值,不由 HAL 解析或校验。调用方应保留无关/保留位,并按 msc_regs.h 的位域定义组合 VDD1 电压和电源使能。
  • 该接口只控制控制器电源状态,不打开外部卡电源;板级电源和复位必须在调用前后由 BSP 完成。
  • 关闭电源前应先关闭卡时钟并停止所有 CMD/DAT 活动。

4.6 总线、时序和 CPM 时钟 API

hal_msc_bus_config_apply()

error_code_t hal_msc_bus_config_apply(hal_msc_drv_t *drv,
                                      hal_msc_bus_width_t width,
                                      hal_msc_timing_t timing);

应用 G32S10 MSC 的总线宽度和时序模式。

参数 说明
drv HAL 驱动上下文
width HAL_MSC_BUS_WIDTH_1 或 HAL_MSC_BUS_WIDTH_4
timing 3.3 节列出的 hal_msc_timing_t 值

返回:

  • IM_OK:总线宽度和时序已写入。
  • IM_ERR_INVALID_PARAM:drv、宽度或时序值非法。
  • IM_ERR_UNSUPPORTED:width == HAL_MSC_BUS_WIDTH_8。

行为:

  • 1-bit 选择 DAT0;4-bit 选择 DAT0..DAT3。
  • 除 Legacy 和 SDR12 外,设置 High Speed enable;随后按 timing 写 UHS_MODE_SEL。
  • 所有模式先选择 fixed sampling clock;HS200 再切换到 tuned sampling clock。
  • HS200 的本接口只切换采样时钟选择,不执行调谐;调谐必须由 hal_msc_tuning_start() 和对应命令完成。

说明:

  • 本接口不设置 CPM 父源、频率、RX/TX phase 或卡供电,也不检查 CMD/DAT 是否空闲。
  • 操作不是整体事务。宽度检查通过后,如果后续时序值非法,寄存器可能已经被部分修改;调用方应先验证参数并只在空闲状态调用。
  • 8-bit 是寄存器预留编码,G32S10 硬件不支持,不能把该返回值当作可恢复的临时失败。

hal_msc_timing_recover()

error_code_t hal_msc_timing_recover(hal_msc_drv_t *drv,
                                    bool restore_fixed_sample_clock,
                                    hal_msc_timing_state_t *state_out);

读取时序/采样时钟快照,并可选恢复 fixed sampling clock。

参数 说明
drv HAL 驱动上下文
restore_fixed_sample_clock 为 true 且当前使用 tuned clock 时,切回 fixed clock
state_out 可选输出快照;为 NULL 时只执行恢复动作

返回:

  • IM_OK:快照读取或恢复动作已完成。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • state_out 在恢复动作前填充,包含 UHS mode、tuned clock 选择状态和完整 HOST_CTRL2_R。
  • 本接口只切回 fixed sampling clock 位,不重新执行调谐,也不修改 UHS_MODE_SEL。
  • 若当前没有选择 tuned clock,restore_fixed_sample_clock == true 不会产生额外动作。

hal_msc_clock_configure()

error_code_t hal_msc_clock_configure(hal_cpm_id_t id, uint32_t rx_phase,
                                     uint32_t tx_phase, bool tuning_enable,
                                     bool exclk_enable);

配置 MSC CPM 时钟的 RX/TX 相位、CPM tuning 和 EXCLK 输出。

参数 说明
id HAL_CPM_ID_CLK_CGU_MSC0 或 HAL_CPM_ID_CLK_CGU_MSC1
rx_phase RX 相位角,允许 0、45、90、135、180、225、270、315
tx_phase TX 相位角,允许 90、135、180、270
tuning_enable 使能或关闭 CPM MSC CDR tuning
exclk_enable 使能或关闭 MSC EXCLK

返回:

  • IM_OK:CPM 配置已写入,或当前为 CONFIG_CPM_ENABLE_STUB。
  • IM_ERR_INVALID_PARAM:控制器 ID、RX phase 或 TX phase 非法。

说明:

  • 本接口不设置时钟父源或频率;调用方仍需使用 CPM API 选择父源并设置目标频率。
  • tuning_enable 控制 CPM CDR tuning,不是主机控制器的 EXEC_TUNING。主机调谐使用 hal_msc_tuning_start()。
  • exclk_enable 作用于 MSC EXCLK 输出,不改变控制器内部时钟门控。
  • 启用 CPM stub 时,参数仍会被检查,但不会访问 CPM 寄存器。
  • 频率或相位切换应在控制器空闲、卡时钟关闭或控制器层确认可安全切换时进行。

4.7 状态和能力 API

hal_msc_present_state_get()

error_code_t hal_msc_present_state_get(const hal_msc_drv_t *drv,
                                       uint32_t *state_out);

读取 PSTATE_REG 的原始硬件状态。

参数 说明
drv HAL 驱动上下文
state_out 输出 PSTATE_REG 位图,不能为 NULL

返回:

  • IM_OK:状态已读取。
  • IM_ERR_INVALID_PARAM:drv 或 state_out 无效。

说明:

  • 返回值为原始位图,不转换为枚举。可用 msc_regs.h 中的 MSC_PSTATE_REG_* 掩码检查命令/数据 inhibit、DAT 活动、缓冲区就绪和重调谐请求。
  • 常用检查包括 MSC_PSTATE_REG_CMD_INH_MASK、MSC_PSTATE_REG_CMD_INH_DAT_MASK、MSC_PSTATE_REG_DAT_LINE_ACT_MASK、MSC_PSTATE_REG_BUF_RD_EN_MASK 和 MSC_PSTATE_REG_BUF_WR_EN_MASK。
  • 本接口只读状态,不清除任何事件。

hal_msc_capabilities_get()

error_code_t hal_msc_capabilities_get(const hal_msc_drv_t *drv,
                                      hal_msc_capabilities_t *capabilities_out);

读取控制器的固定能力和规范版本。

参数 说明
drv HAL 驱动上下文
capabilities_out 输出能力结构,不能为 NULL

返回:

  • IM_OK:能力已读取。
  • IM_ERR_INVALID_PARAM:drv 或 capabilities_out 无效。

说明:

  • max_block_size 已从寄存器编码换算为字节数。
  • 三个 max_current_* 字段保留 CURR_CAPABILITIES1_R 的硬件原始编码,不在此处换算为 mA。
  • host_spec_version 是规范版本字段的原始值,由协议层解释。
  • 本接口可在控制器未启动时调用,不依赖卡时钟或传输状态。

4.8 命令/数据线复位 API

hal_msc_command_data_reset()

error_code_t hal_msc_command_data_reset(hal_msc_drv_t *drv);

同时请求 CMD 和 DAT 线逻辑复位。

参数 说明
drv HAL 驱动上下文

返回:

  • IM_OK:复位请求已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 本接口写入 SW_RST_CMD | SW_RST_DAT,立即返回,不等待复位完成。
  • 它不执行控制器全局复位,不清除时钟/电源/总线/中断配置,也不复位外部卡。
  • 调用后应通过 hal_msc_command_data_reset_pending_get() 轮询;完成后再清理错误状态和恢复总线状态。

hal_msc_command_data_reset_pending_get()

error_code_t hal_msc_command_data_reset_pending_get(
    const hal_msc_drv_t *drv, bool *pending_out);

查询 CMD 或 DAT 线复位是否仍在进行。

参数 说明
drv HAL 驱动上下文
pending_out 输出是否存在未完成复位,不能为 NULL

返回:

  • IM_OK:状态已读取。
  • IM_ERR_INVALID_PARAM:drv 或 pending_out 无效。

说明:只要 CMD 或 DAT 任一复位位仍为 1,*pending_out 就为 true。本接口不提供超时,调用方应限制等待时间。


4.9 命令、数据和 PIO API

hal_msc_command_start()

error_code_t hal_msc_command_start(hal_msc_drv_t *drv, uint32_t opcode,
                                   uint32_t argument, uint32_t response_flags,
                                   bool has_data);

写入命令参数和命令寄存器,启动一次普通 SD/MMC 命令。

参数 说明
drv HAL 驱动上下文
opcode 命令号,有效范围 0~63
argument 32-bit 命令参数,按协议字节顺序写入
response_flags HAL_MSC_RESPONSE_* 标志组合
has_data 命令是否带数据阶段

返回:

  • IM_OK:命令已写入控制器。
  • IM_ERR_INVALID_PARAM:drv 无效、opcode > 63,或同时请求 136-bit 和 busy 响应。

说明:

  • HAL_MSC_RESPONSE_PRESENT 决定是否期待响应;没有该位时忽略 136/BUSY 类型。
  • HAL_MSC_RESPONSE_INDEX 和 HAL_MSC_RESPONSE_CRC 分别设置索引和 CRC 检查。
  • 本接口固定使用 normal command type,不提供 suspend、resume 或 abort 命令类型。
  • 带数据命令必须先调用 hal_msc_data_configure() 配好块大小、块数、方向和 DMA。has_data 只设置命令的数据存在位,不会自动写数据参数。
  • 写入命令寄存器即启动硬件操作。返回 IM_OK 不代表命令已经完成,调用方仍应等待 Command Complete 或检查错误。
  • HAL_MSC_REQUEST_DATA 和 HAL_MSC_REQUEST_TUNING 是控制器层标记,本接口不解析。

hal_msc_data_configure()

error_code_t hal_msc_data_configure(hal_msc_drv_t *drv,
                                    const hal_msc_data_config_t *config);

配置数据阶段的块大小、块数、传输模式和 DMA 选择。

参数 说明
drv HAL 驱动上下文
config 数据配置,不能为 NULL

返回:

  • IM_OK:配置已写入。
  • IM_ERR_INVALID_PARAM:drv/config 无效,或 has_data == true 时 block_size 不在 1~2048 字节范围内。

行为:

  • 从 config->transfer_mode 复制原始 XFER_MODE_R 位,并强制清除 DMA_EN。
  • has_data == true 时写入 timeout count 0xE、block_count、block_size 和 512 KiB SDMA boundary。
  • DMA 模式为 HAL_MSC_DMA_SDMA_IRQ 时选择 SDMA 并置位 DMA_EN;为 HAL_MSC_DMA_ADMA2_IRQ 时选择 32-bit ADMA2 并置位 DMA_EN。
  • DMA 模式为 HAL_MSC_DMA_PIO_POLL 时不选择 DMA,DMA_EN 保持清零。

说明:

  • HAL 不检查 block_count 是否为 0,也不检查块数、传输方向和 auto command 是否与当前卡协议一致。
  • has_data == false 时只写 transfer_mode,不会清除上一次残留的块大小、块数、boundary 或 timeout。
  • 本接口不启动命令、不重置 DMA、不维护 cache,也不检查 CMD/DAT 是否空闲。
  • 16-bit transfer_mode 的具体方向和多块位由调用方通过 msc_regs.h 宏填写;不要在此字段中重复设置 DMA_EN,因为它会被覆盖。

hal_msc_response_get()

error_code_t hal_msc_response_get(const hal_msc_drv_t *drv,
                                  uint32_t response_out[4]);

读取四个响应寄存器。

参数 说明
drv HAL 驱动上下文
response_out 至少 4 个 uint32_t 的输出数组,不能为 NULL

返回:

  • IM_OK:四个响应字已读取。
  • IM_ERR_INVALID_PARAM:drv 或 response_out 无效。

数组映射:

索引 寄存器
response_out[0] RESP01_R
response_out[1] RESP23_R
response_out[2] RESP45_R
response_out[3] RESP67_R

说明:

  • HAL 不等待响应完成,也不判断 48-bit 或 136-bit 响应;调用方必须在 Command Complete 后读取并按协议解析。
  • 不进行大小端转换,也不提取 R1/R3/R5 的协议字段。

hal_msc_buffer_read()

error_code_t hal_msc_buffer_read(const hal_msc_drv_t *drv,
                                 uint32_t *word_out);

从 PIO 数据端口读取一个 32-bit word。

参数 说明
drv HAL 驱动上下文
word_out 输出 word,不能为 NULL

返回:

  • IM_OK:已读取一个 word。
  • IM_ERR_INVALID_PARAM:drv 或 word_out 无效。

说明:

  • 本接口不检查 PSTATE.BUF_RD_EN,调用方必须先用 hal_msc_present_state_get() 确认读缓冲区就绪。
  • 读端口按 32-bit word 访问;字节序拆装和 block 内偏移由协议层处理。
  • 不提供 FIFO 深度、剩余字节数或溢出来源;这些状态由 PSTATE 和中断状态判断。

hal_msc_buffer_write()

error_code_t hal_msc_buffer_write(hal_msc_drv_t *drv, uint32_t word);

向 PIO 数据端口写入一个 32-bit word。

参数 说明
drv HAL 驱动上下文
word 待写入数据

返回:

  • IM_OK:已写入一个 word。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 本接口不检查 PSTATE.BUF_WR_EN,调用方必须先确认写缓冲区就绪。
  • 没有等待、重试或 FIFO 水位管理;上层必须按硬件响应速度供给数据。
  • 本接口不处理 cache、DMA 或协议字节序。

4.10 DMA API

hal_msc_dma_mode_set()

error_code_t hal_msc_dma_mode_set(hal_msc_drv_t *drv,
                                  hal_msc_dma_mode_t mode);

设置一个控制器的请求 PIO/DMA 模式。

参数 说明
drv HAL 驱动上下文
mode HAL_MSC_DMA_PIO_POLL、HAL_MSC_DMA_SDMA_IRQ 或 HAL_MSC_DMA_ADMA2_IRQ

返回:

  • IM_OK:软件模式已更新。
  • IM_ERR_INVALID_PARAM:drv 无效,或 mode 超出有效枚举范围(包括 HAL_MSC_DMA_MODE_COUNT)。

说明:该函数只更新 HAL 上下文,不改变控制器 DMA_SEL、DMA_EN 或地址;新选择在下一次 hal_msc_data_configure() 中生效。切换前应确认当前请求已经完成。

hal_msc_dma_mode_get()

error_code_t hal_msc_dma_mode_get(const hal_msc_drv_t *drv,
                                  hal_msc_dma_mode_t *mode_out);

读取当前控制器的 PIO/DMA 软件模式。

参数 说明
drv HAL 驱动上下文
mode_out 输出模式,不能为 NULL

返回:

  • IM_OK:模式已读取。
  • IM_ERR_INVALID_PARAM:drv 或 mode_out 无效。

说明:本接口返回 HAL 保存的模式,不反读 HOST_CTRL1_R.DMA_SEL。

hal_msc_sdma_address_set()

error_code_t hal_msc_sdma_address_set(hal_msc_drv_t *drv, uint32_t address);

设置 32-bit SDMA 系统地址。

参数 说明
drv HAL 驱动上下文
address SDMA 使用的物理地址,按控制器和缓冲区要求对齐

返回:

  • IM_OK:地址已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 本接口不转换虚拟地址、不检查物理连续性、不维护 cache,也不限制传输长度。
  • hal_msc_data_configure() 固定 512 KiB SDMA boundary;收到 DMA Boundary 事件后,调用方必须更新地址。
  • 应与 HAL_MSC_DMA_SDMA_IRQ 模式配合使用。

hal_msc_adma2_address_set()

error_code_t hal_msc_adma2_address_set(hal_msc_drv_t *drv, uint32_t address);

选择 16-bit ADMA2 descriptor 长度模式,并设置 descriptor 物理地址。

参数 说明
drv HAL 驱动上下文
address ADMA2 descriptor 表的 32-bit 物理地址

返回:

  • IM_OK:地址和长度模式已写入。
  • IM_ERR_INVALID_PARAM:drv 无效。

说明:

  • 本接口只写地址并清除 ADMA2 26-bit length mode,不构造 descriptor、不设置 descriptor 有效位、不做 cache clean/invalidate。
  • 地址必须满足控制器和平台 DMA 的要求;descriptor 的内容、对齐和所有权由调用方维护。
  • 应与 HAL_MSC_DMA_ADMA2_IRQ 模式配合使用。

4.11 调谐 API

hal_msc_tuning_start()

error_code_t hal_msc_tuning_start(hal_msc_drv_t *drv, uint16_t block_size);

配置调谐块大小并启动执行调谐。

参数 说明
drv HAL 驱动上下文
block_size 调谐块大小,有效范围 1~2048 字节

返回:

  • IM_OK:调谐寄存器已配置并启动。
  • IM_ERR_INVALID_PARAM:drv 无效,或 block_size 不在有效范围内。

说明:

  • 函数写入 block_size、512 KiB SDMA boundary、数据方向位和 EXEC_TUNING,但不写命令参数和命令寄存器。
  • 调用方仍需根据卡类型下发调谐命令,并通过 Buffer Read Ready、错误状态或轮询判断硬件进度。
  • 本接口不设置 DMA 模式、传送块数或数据缓冲区,不等待调谐完成。
  • SD 和 eMMC 的调谐命令及调谐结束后的采样时钟处理不同,需由协议层决定。

hal_msc_tuning_active_get()

error_code_t hal_msc_tuning_active_get(const hal_msc_drv_t *drv,
                                       bool *active_out);

读取执行调谐状态。

参数 说明
drv HAL 驱动上下文
active_out 输出 EXEC_TUNING 是否仍有效,不能为 NULL

返回:

  • IM_OK:状态已读取。
  • IM_ERR_INVALID_PARAM:drv 或 active_out 无效。

说明:true 表示调谐仍在进行或执行位尚未被硬件清除;本接口不提供超时和重试。


5. 典型完整示例

以下代码用于说明 HAL 调用顺序,不包含板级 pinmux、卡供电/复位、完整 SD/eMMC 命令序列、DMA descriptor 和 cache 操作。实际产品应使用 MSC 中间件,或按卡协议补齐这些步骤。

5.1 控制器初始化和 IRQ 注册示例

#include "hal_msc.h"
#include "im_irq.h"

#define APP_MSC_CLOCK_HZ (50000000U)
#define APP_MSC_RETRY    (100000U)

static volatile uint16_t s_msc_normal_pending;
static volatile uint16_t s_msc_error_pending;

static void app_msc_irq_cb(hal_msc_drv_t *drv, int8_t event,
                           uint16_t normal_status, uint16_t error_status)
{
    (void)drv;
    if (event == 5)
        s_msc_error_pending |= error_status;
    else
        s_msc_normal_pending |= normal_status;
}

static error_code_t app_msc_init(uint32_t ctrl_id, hal_msc_drv_t **drv_out)
{
    const hal_msc_ctrl_t *ctrl;
    hal_msc_drv_t *drv;
    error_code_t status;
    bool stable = false;
    bool resetting = true;
    uint32_t retry;
    uint8_t power;

    if (drv_out == NULL)
        return IM_ERR_INVALID_PARAM;

    ctrl = hal_msc_ctrl_get(ctrl_id);
    if (ctrl == NULL)
        return IM_ERR_INVALID_PARAM;
    drv = ctrl->drv;

    status = hal_cpm_clk_enable(ctrl->clk_id);
    if (status != IM_OK)
        return status;
    status = hal_cpm_clk_enable(ctrl->clk_gate_id);
    if (status != IM_OK)
        return status;

    for (int8_t event = 0; event <= 5; ++event) {
        status = hal_msc_set_cb(drv, event, app_msc_irq_cb);
        if (status != IM_OK)
            return status;
    }

    status = sys_irq_request(ctrl->irq, hal_msc_irq_handler,
                            ctrl->irq_name, drv);
    if (status != IM_OK)
        return status;
    status = sys_irq_enable(ctrl->irq);
    if (status != IM_OK)
        return status;

    status = hal_msc_initial_clock_set(drv, true);
    if (status != IM_OK)
        return status;

    for (retry = APP_MSC_RETRY; retry > 0U; --retry) {
        status = hal_msc_initial_clock_stable_get(drv, &stable);
        if (status != IM_OK)
            return status;
        if (stable)
            break;
        /* 在此加入板级短延时,避免空循环。 */
    }
    if (!stable)
        return IM_ERR_TIMEOUT;

    status = hal_msc_command_data_reset(drv);
    if (status != IM_OK)
        return status;
    for (retry = APP_MSC_RETRY; resetting && retry > 0U; --retry) {
        status = hal_msc_command_data_reset_pending_get(drv, &resetting);
        if (status != IM_OK)
            return status;
        /* 在此加入板级短延时。 */
    }
    if (resetting)
        return IM_ERR_TIMEOUT;

    status = hal_msc_1v8_signaling_set(drv, false);
    if (status != IM_OK)
        return status;

    power = (uint8_t)(MSC_PWR_CTRL_R_SD_BUS_PWR_VDD1_MASK |
                      ((uint8_t)MSC_VDD1_SD_3V3 <<
                       MSC_PWR_CTRL_R_SD_BUS_VOL_VDD1_SHIFT));
    status = hal_msc_power_control_set(drv, power);
    if (status != IM_OK)
        return status;

    status = hal_msc_bus_config_apply(drv, HAL_MSC_BUS_WIDTH_4,
                                      HAL_MSC_TIMING_SDR12);
    if (status != IM_OK)
        return status;

    status = hal_cpm_clk_set_parent(ctrl->clk_id, HAL_CPM_ID_CLK_MPLL);
    if (status != IM_OK)
        return status;
    status = hal_cpm_clk_set_rate_hz(ctrl->clk_id, APP_MSC_CLOCK_HZ);
    if (status != IM_OK)
        return status;
    status = hal_msc_clock_configure(ctrl->clk_id, 0U, 270U, true, false);
    if (status != IM_OK)
        return status;

    status = hal_msc_card_clock_set(drv, true);
    if (status != IM_OK)
        return status;

    *drv_out = drv;
    return IM_OK;
}

示例中的电压、总线宽度、时序和频率都是占位值。必须按板级电源、卡能力和协议协商结果修改。出现错误时还应在产品代码中关闭卡时钟、释放 IRQ 并关闭 CPM 门控。

5.2 单数据块(CMD17)CPU 模式读取示例

下面的片段展示带数据命令的配置和中断掩码设置,不包含完整等待、协议校验和错误恢复:

hal_msc_data_config_t data = {
    .has_data = true,
    .block_size = 512U,
    .block_count = 1U,
    .transfer_mode = MSC_XFER_MODE_R_BLK_CNT_EN_MASK |
                     MSC_XFER_MODE_R_DATA_XFER_DIR_MASK,
};
uint16_t normal_events = MSC_NORMAL_INT_CMD_CMPLT_MASK |
                         MSC_NORMAL_INT_BUF_RD_RDY_MASK |
                         MSC_NORMAL_INT_XFER_CMPLT_MASK;
error_code_t status;
uint32_t response[4];

status = hal_msc_dma_mode_set(drv, HAL_MSC_DMA_PIO_POLL);
if (status != IM_OK)
    return status;

status = hal_msc_irq_status_ack(drv, normal_events, MSC_ERROR_INT_ALL_MASK);
if (status != IM_OK)
    return status;

status = hal_msc_irq_status_enable_set(drv, normal_events,
                                       MSC_ERROR_INT_ALL_MASK);
if (status != IM_OK)
    return status;

status = hal_msc_irq_signal_enable_set(drv, normal_events,
                                       MSC_ERROR_INT_ALL_MASK);
if (status != IM_OK)
    return status;

status = hal_msc_data_configure(drv, &data);
if (status != IM_OK)
    return status;

status = hal_msc_command_start(drv, 17U, 0U,
    HAL_MSC_RESPONSE_PRESENT | HAL_MSC_RESPONSE_CRC |
    HAL_MSC_RESPONSE_INDEX, true);
if (status != IM_OK)
    return status;

/* 等待 CMD_CMPLT、BUF_RD_RDY 和 XFER_CMPLT。
 * 每次 BUF_RD_EN 置位时读取一个 32-bit word,共 512 / 4 个 word。
 * 任一位 MSC_ERROR_INT_* 置位时立即终止并按控制器层策略复位。
 */

status = hal_msc_response_get(drv, response);
if (status != IM_OK)
    return status;

status = hal_msc_irq_status_ack(drv, normal_events, MSC_ERROR_INT_ALL_MASK);
if (status != IM_OK)
    return status;

说明:

  • CMD17 的响应类型、参数和数据方向必须按 SD 协议确认;示例仅展示 HAL 字段组合。
  • 块数、块大小和传输模式必须写入命令前的数据配置中。
  • 状态 enable 和 signal enable 都是整寄存器覆盖写入,不能只传本次新增位。

6. 使用约束和检查清单

6.1 关键约束

  • MSC HAL 没有分配、初始化和释放流程,也没有软件状态机;drv 是静态借用上下文。
  • 所有硬件操作都是同步寄存器写或读,不等待命令、传输、DMA、复位或调谐完成。
  • HAL 不提供互斥、临界区或状态检查。多任务访问、任务与 IRQ 并发、时钟/供电切换都必须由调用方串行化。
  • 1-bit 和 4-bit 是唯一支持的总线宽度;8-bit 返回 IM_ERR_UNSUPPORTED。
  • 中断 status enable 与 signal enable 都是整寄存器覆盖写入;未保留的位会被清除。
  • Error pending 优先于 normal completion;错误回调后不要继续使用本次请求的旧响应或数据。
  • CARD_INTERRUPT 是只读电平状态,必须通过 normal signal enable 停止上报,不能通过 ack 清除;normal status enable 的对应位用于 HAL pending 过滤,也应一并维护。
  • SDMA 地址和 ADMA2 descriptor 地址必须是硬件可访问的物理地址;cache 维护和 descriptor 内容由调用方负责。
  • HAL 不控制 CPM 父源/频率/门控、GPIO pinmux、卡供电、外部 1.8 V 稳压器或卡复位。
  • 参数检查是可配置的。关闭 CONFIG_SDK_PARAM_CHECK 或 CONFIG_SDK_HAL_PARAM_CHECK 后,调用方必须保证所有指针和枚举值有效。

6.2 首次集成检查

  • 已确认 MSC0/MSC1 与板级引脚、卡槽和 CPM 时钟的对应关系。
  • 已使能 CONFIG_DRIVER_MSC,并根据需要使能 SDK/HAL 参数检查。
  • 已配置 CLK/CMD/DAT pinmux、卡供电、复位和电压切换 GPIO。
  • 已通过 hal_msc_ctrl_get() 取得控制器描述和 drv,没有自行分配或释放 HAL 上下文。
  • 若使用中断,已为事件 0~5 注册回调、注册平台 IRQ,并正确写入 status enable 与 signal enable。
  • 已在时钟切换前关闭卡时钟,并确认 hal_msc_initial_clock_stable_get() 最终返回稳定。
  • 已根据卡能力选择 1-bit/4-bit 和合法时序;没有请求 8-bit。
  • 已在命令前完成 hal_msc_data_configure(),并在完成后读取响应、清除状态。
  • DMA 路径已检查物理地址、边界、descriptor 长度模式、对齐和 cache 一致性。
  • HS200/SDR104/DDR50 等模式已确认 tuning 需求、采样时钟恢复和 1.8 V 板级条件。