SDIO
SDIO Slave 驱动 | 无 sample
学习目标
- 理解 SDIO 从机模式——WS63 作为从设备通过 SDIO 总线与主机通信
- 掌握回调驱动架构——7 个回调覆盖读/写/消息/复位全流程
- 掌握消息传递机制——
sched_msg准备消息、sync_msg同步发送、process_msg原子替换 - 能够在 WS63 端通过 SDIO 从机向 Linux/Android 主机上报数据
基本概念
SDIO 从机做什么
SDIO(Secure Digital Input/Output)是标准化的芯片间高速通信接口,物理层包含 CLK、CMD、DATA0~DATA3 共 6 条线。WS63 在 SDIO 总线上充当从设备——所有传输由主机发起,从机响应。典型场景:WiFi/BT 模组通过 SDIO 向应用处理器上报数据,速率可达数十 MB/s。
flowchart LR
H[主机 AP] -->|CLK + CMD| S[SDIO 控制器]
H -->|DATA0~3 读写| S
S -->|回调通知| A[WS63 应用层]
A -->|sched_msg| S
S -->|消息就绪| H
通道状态机
SDIO 从机通道有明确的状态迁移:复位 → 初始化 → 睡眠 ↔ 唤醒 ↔ 工作。只有处于 SDIO_CHANNEL_WORK 状态时才能收发消息。
stateDiagram-v2
[*] --> RESET: 上电
RESET --> INIT: uapi_sdio_slave_init
INIT --> WORK: 主机 CLK 就绪
WORK --> SLEEP: 主机允许休眠
SLEEP --> WAKE: 主机唤醒
WAKE --> WORK: 通道恢复
WORK --> RESET: 软复位
回调驱动架构
从机端不主动发起传输——所有传输由主机触发,从机通过回调感知事件。sdio_callback_func_t 包含 7 个回调:
| 回调 | 触发时机 | 返回值 |
|---|---|---|
read_start_callback |
主机发起读操作 | uint32_t:待发送数据长度 |
read_over_callback |
主机读操作完成 | uint32_t |
read_err_callback |
主机读操作出错 | void |
write_start_callback |
主机发起写操作 | uint32_t:可接收数据长度 |
write_over_callback |
主机写操作完成 | uint32_t |
process_msg_callback |
主机发来消息 | void |
soft_rst_callback |
主机发起软复位 | void |
消息传递机制
WS63 SDIO 从机使用 32 位 bitmask 传递消息——bit0~bit31 对应消息 0~31。每条消息可处于三种状态:挂起(pending)、发送中(sending)、就绪(ready)。
| 接口 | 行为 | 阻塞 |
|---|---|---|
sched_msg |
扫描 pending 队列,调度一条消息发送 | 否 |
sync_msg |
将消息加入队列并同步发送 | 是 |
send_msg_ack |
覆盖当前发送中的消息 | 否 |
process_msg |
清除一条消息,发送另一条(原子操作) | 否 |
涉及 API
| API | 用途 | 头文件 |
|---|---|---|
uapi_sdio_slave_init(sdio_bus_t bus) |
初始化 SDIO 从机通道 | sdio_slave.h |
uapi_sdio_slave_deinit(sdio_bus_t bus) |
去初始化 | sdio_slave.h |
uapi_sdio_slave_register_callback(sdio_bus_t bus, const sdio_callback_func_t *fun) |
注册回调集 | sdio_slave.h |
uapi_sdio_slave_sched_msg(sdio_bus_t bus) |
调度 pending 消息发送 | sdio_slave.h |
uapi_sdio_slave_sync_msg(sdio_bus_t bus, uint32_t msg) |
将消息加入队列并发送 | sdio_slave.h |
uapi_sdio_slave_send_msg_ack(sdio_bus_t bus, uint32_t msg) |
覆盖当前发送中消息 | sdio_slave.h |
uapi_sdio_slave_process_msg(sdio_bus_t bus, uint32_t send_msg, uint32_t clear_msg) |
原子替换消息 | sdio_slave.h |
uapi_sdio_slave_is_pending_msg(sdio_bus_t bus, uint32_t msg) |
查询消息是否 pending | sdio_slave.h |
uapi_sdio_slave_is_sending_msg(sdio_bus_t bus, uint32_t msg) |
查询消息是否正在发送 | sdio_slave.h |
uapi_sdio_slave_register_notify_message_callback(notify_host_event_t event_callback) |
注册通知 Host 事件回调 | sdio_slave.h |
uapi_sdio_slave_get_status(sdio_bus_t bus, sdio_status_info_t *status_info) |
获取通道状态 | sdio_slave.h |
uapi_sdio_slave_prepare_send_data(sdio_bus_t bus, uint32_t data_len) |
启动 DMA 数据发送 | sdio_slave.h |
回调中不可阻塞、不可调用耗时操作——应通过消息队列或信号量将事件传递给任务处理。
案例说明
做什么
初始化 SDIO 从机通道,注册全套回调(读/写/消息/复位),准备消息 1 供主机读取,通过串口打印回调触发情况。
规格与功能
| 规格项 | 说明 |
|---|---|
| 总线 | SDIO_BUS0 |
| 回调集 | 7 个回调全部注册 |
| 消息 | 消息 1(bit1) |
| 模式 | 从机模式,消息被动上报 |
| 状态查询 | 定时打印通道状态 |
程序运行流程:init → register_callback → loop { sched_msg(1), get_status, delay } → 主机读取触发回调 → 回调打印事件。
案例流程
sequenceDiagram
participant A as WS63应用
participant S as SDIO从机
participant H as 主机AP
A->>S: uapi_sdio_slave_init
A->>S: register_callback 7个回调
A->>S: sync_msg msg=1
Note over S: 消息1置为pending
S->>S: sched_msg 调度发送
H->>S: 主机发起读操作
S->>A: read_start_cb 触发
S->>H: 返回消息1数据
S->>A: read_over_cb 触发
H->>S: 主机写入消息
S->>A: process_msg_cb 触发
案例操作指导
第一步:编译
第二步:烧录和验证
用 SDIO 排线连接 WS63 与主机开发板。启动主机端 SDIO 驱动后,WS63 串口输出各回调触发日志。若主机未就绪,可通过 uapi_sdio_slave_host_clk_ready() 轮询等待。
关键配置
| 参数 | 值 | 说明 |
|---|---|---|
| 消息编号范围 | 0~31 | bitmask 限制 |
| pending 消息调度 | 非阻塞 | sched_msg 不在 WORK 状态直接返回错 |
| 回调耗时 | < 50μs | 回调在中断上下文执行 |
| 最大传输速率 | 数十 MB/s | 受主机端驱动和 DMA 配置影响 |
| 初始化模式 | 阻塞 / 非阻塞 | init 等待主机 CLK;init_no_wait 立即返回 |
L 位锁定
soft_rst_callback 触发后进入复位状态——需重新初始化才能恢复通信。状态机不处于 WORK 时 sched_msg/sync_msg 均返回失败。
代码详解
概念性代码,基于 SDK 头文件
sdio_slave.h中的 API 签名。
#include "sdio_slave.h"
#include "osal_printk.h"
#include "app_init.h"
/* ---- 回调实现 ---- */
static uint32_t sdio_read_start_cb(uint32_t len, uint8_t *dma_tbl)
{
osal_printk("SDIO: HOST start read, len=%u\n", len);
return len; /* 返回可发送的数据长度 */
}
static uint32_t sdio_read_over_cb(void)
{
osal_printk("SDIO: HOST read over\n");
return 0;
}
static void sdio_read_err_cb(void)
{
osal_printk("SDIO: HOST read error\n");
}
static uint32_t sdio_write_start_cb(uint32_t len, uint8_t *dma_tbl)
{
osal_printk("SDIO: HOST start write, len=%u\n", len);
return len;
}
static uint32_t sdio_write_over_cb(void)
{
osal_printk("SDIO: HOST write over\n");
return 0;
}
static void sdio_process_msg_cb(uint32_t msg)
{
osal_printk("SDIO: HOST sent msg=0x%08X\n", msg);
}
static void sdio_soft_rst_cb(void)
{
osal_printk("SDIO: soft reset from HOST\n");
}
/* ---- 回调集 ---- */
static sdio_callback_func_t g_sdio_cb = {
.read_start_callback = sdio_read_start_cb,
.read_over_callback = sdio_read_over_cb,
.read_err_callback = sdio_read_err_cb,
.write_start_callback = sdio_write_start_cb,
.write_over_callback = sdio_write_over_cb,
.process_msg_callback = sdio_process_msg_cb,
.soft_rst_callback = sdio_soft_rst_cb,
};
/* ---- 主入口 ---- */
static void sdio_slave_entry(void)
{
errcode_t ret;
sdio_status_info_t status;
/* 初始化 SDIO 从机通道 */
ret = uapi_sdio_slave_init(SDIO_BUS0);
if (ret != ERRCODE_SUCC) {
osal_printk("SDIO init failed: %d\n", ret);
return;
}
/* 注册 7 个回调 */
ret = uapi_sdio_slave_register_callback(SDIO_BUS0, &g_sdio_cb);
if (ret != ERRCODE_SUCC) {
osal_printk("SDIO register cb failed: %d\n", ret);
return;
}
/* 循环: 准备消息 + 查询状态 */
while (1) {
/* 调度 pending 消息发送 */
uapi_sdio_slave_sched_msg(SDIO_BUS0);
/* 获取通道状态 */
if (uapi_sdio_slave_get_status(SDIO_BUS0, &status) == ERRCODE_SUCC) {
osal_printk("SDIO status: work=%d tx=%d sleep=%d\n",
status.work_status, status.tx_status, status.sleep_status);
}
osal_msleep(1000);
}
}
app_run(sdio_slave_entry);
sched_msg仅在通道处于 WORK 状态时有效。若主机 CLK 未就绪,可通过uapi_sdio_slave_host_clk_ready()轮询等待。