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 时钟、供电和总线配置
控制器层通常按以下顺序切换工作条件。每一步都应检查返回值,并为硬件状态轮询设置超时:
- 关闭卡时钟,必要时通过
hal_msc_command_data_reset()清理残留的 CMD/DAT 状态,并轮询复位完成。 - 调用
hal_msc_initial_clock_set(true),再轮询hal_msc_initial_clock_stable_get(),确保控制器内部时钟稳定。 - 根据板级电压和卡状态调用
hal_msc_1v8_signaling_set(),再通过hal_msc_power_control_set()写入完整的PWR_CTRL_R控制字节。 - 设置 CPM 时钟父源和频率,并通过
hal_msc_clock_configure()配置 MSC 时钟相位、tuning 和 EXCLK。 - 调用
hal_msc_bus_config_apply()应用总线宽度和时序。 - 调用
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。一次典型的数据请求如下:
- 通过
hal_msc_dma_mode_set()选择HAL_MSC_DMA_PIO_POLL。 - 构造
hal_msc_data_config_t,填写block_size、block_count和原始transfer_mode位域,再调用hal_msc_data_configure()。 - 调用
hal_msc_command_start()下发命令;带数据时has_data必须为true。 - 轮询
hal_msc_present_state_get()的BUF_RD_EN或BUF_WR_EN,在就绪时读写BUF_DATA_R。 - 通过
hal_msc_irq_state_get()或回调等待CMD_CMPLT、XFER_CMPLT和错误状态;确认完成后用hal_msc_irq_status_ack()清除 W1C 位。 - 需要响应时调用
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 * |
返回:无。
处理顺序:
- 读取
hal_msc_irq_state_t,计算status & enable得到 pending 位。 - 先处理
CARD_INTERRUPT事件,但不清除该只读状态位。 - 若错误 pending 非零,先清除错误 W1C 位,回调 Error 事件并返回;本次 IRQ 不再派发普通完成事件。
- 否则按 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 count0xE、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 板级条件。