API GUIDE / I2D / HAL

G32S10 I2D HAL 接口

HAL 层接口:90°/270° 时输出宽高互换,HAL 用这一点校验目标 stride 并计算布局。

1. 分层关系与依赖

1.1 HAL 中每类操作调用的 LL 接口

HAL 操作用到的 LL 接口
hal_i2d_init()enable_clock_gate、safe_reset、set_outstanding_limit、set_timeout_value、enable/disable_timeout_restart、set_irq_mask、clear_irq
hal_i2d_start()set_mode、set_image_size、set_source_addr、set_destination_addr、set_source_stride、set_destination_stride、update_shadow、clear_irq、safe_reset、start
hal_i2d_stop() / hal_i2d_deinit()stop、safe_reset、disable_irq、clear_irq
hal_i2d_irq_handler()get_irq_status、get_irq_mask、clear_irq
hal_i2d_irq_config()set_irq_mask
hal_i2d_layout_get()无(纯软件计算,不访问硬件)

2. 构建配置与文件组织

2.1 Kconfig

menuconfig DRIVER_I2D
    bool "I2D driver"
    default n
    help
      Build the G32S10 I2D image rotation driver.
  • 打开 CONFIG_DRIVER_I2D=y 才会编译 hal_i2d.c。

  • HAL 依赖 CPM(CONFIG_DRIVER_CPM=y);回调示例还依赖 IRQ 框架 (CONFIG_DRIVER_IRQ=y)。

  • LL 只依赖头文件,但直接操作寄存器前必须先打开时钟(见 §6.8)。

2.2 CMake

if(CONFIG_DRIVER_I2D)
    imde_component_register(NAME g32s10_i2d SRCS
        "${CMAKE_CURRENT_LIST_DIR}/hal_i2d.c")
endif()

3. 资源模型与公开类型

3.1 公开类型

3.1.1 hal_i2d_format_t

值寄存器值说明bpp宽高对齐
HAL_I2D_FORMAT_NV120NV12 半平面 YUV 4:2:018
HAL_I2D_FORMAT_RAW81RAW8/Y8 单平面18
HAL_I2D_FORMAT_RGB5652RGB565 packed24
HAL_I2D_FORMAT_ARGB88883ARGB8888 packed42
HAL_I2D_FORMAT_YUV4224YUV422 packed(YUYV)24

3.1.2 hal_i2d_rotate_t

值寄存器值说明
HAL_I2D_ROTATE_00不旋转
HAL_I2D_ROTATE_901顺时针 90°
HAL_I2D_ROTATE_1802180°
HAL_I2D_ROTATE_2703顺时针 270°

90°/270° 时输出宽高互换,HAL 用这一点校验目标 stride 并计算布局。

3.1.3 hal_i2d_state_t

值说明
HAL_I2D_STATE_RESET句柄未初始化
HAL_I2D_STATE_READY已初始化且空闲;hal_i2d_start() 只在该状态接受调用
HAL_I2D_STATE_BUSY正在处理一帧
HAL_I2D_STATE_TIMEOUT记录到硬件超时
HAL_I2D_STATE_ERROR记录到 outstanding 错误

3.1.4 hal_i2d_irq_t / hal_i2d_event_t

HAL 位值硬件位对应 event
HAL_I2D_IRQ_FRAME_DONE1 << 0IRQ[0]HAL_I2D_EVENT_FRAME_DONE
HAL_I2D_IRQ_TIMEOUT1 << 1IRQ[1]HAL_I2D_EVENT_TIMEOUT
HAL_I2D_IRQ_OUTSTANDING1 << 2IRQ[2]HAL_I2D_EVENT_OUTSTANDING

HAL 的 IRQ 位与 event 值刻意保持一致,便于寄存器 mask 直接映射。

3.1.5 hal_i2d_error_t

标志值说明
HAL_I2D_ERROR_NONE0无错误
HAL_I2D_ERROR_INVALID_PARAM1 << 0启动时布局/参数校验失败
HAL_I2D_ERROR_TIMEOUT1 << 1硬件超时
HAL_I2D_ERROR_OUTSTANDING1 << 2outstanding 上限中断

错误标志是锁存的或运算结果,只能通过 hal_i2d_error_clear() 清除。

3.1.6 hal_i2d_init_config_t

字段类型说明
instancei2d_regs_t *必须为 I2D
outstanding_limituint32_tAXI 读 outstanding 上限,1..255
timeout_valueuint32_t硬件超时上限;0 表示关闭硬件超时
timeout_restartbooltrue 时超时后硬件自动重启
frame_done_irq_enablebool是否放行 frame-done 中断
timeout_irq_enablebool是否放行 timeout 中断
outstanding_irq_enablebool是否放行 outstanding 中断

3.1.7 hal_i2d_config_t

字段类型说明
src_buf / dst_bufvoid *源/目标图像起始地址,非空
src_uv_buf / dst_uv_bufvoid *NV12 源/目标 UV 平面地址;NULL 时 HAL 推导;其它格式必须为 NULL
width / heightuint32_t源图宽高
formathal_i2d_format_t源和目标共用的像素格式
rotatehal_i2d_rotate_t旋转角度
horizontal_mirror / vertical_flipbool水平镜像 / 垂直翻转
src_stride_y / dst_stride_yuint32_tY 平面 stride;0 表示由 HAL 推导
src_stride_uv / dst_stride_uvuint32_tNV12 UV 平面 stride;0 表示由 HAL 推导;其它格式必须为 0

3.1.8 hal_i2d_layout_t

hal_i2d_layout_get() 的输出,供调用方做容量检查与 cache 维护:

字段说明
src_y_addr / src_uv_addr源 Y/UV 平面地址
dst_y_addr / dst_uv_addr目标 Y/UV 平面地址
src_y_stride / src_uv_stride源 Y/UV stride
dst_y_stride / dst_uv_stride目标 Y/UV stride
src_y_size / src_uv_size源 Y/UV 平面字节数
dst_y_size / dst_uv_size目标 Y/UV 平面字节数
src_buffer_size / dst_buffer_size源/目标总字节数(Y + UV)

3.1.9 hal_i2d_handle_t

字段说明
instance绑定的寄存器基地址
inithal_i2d_init() 保存的初始化配置副本
statehal_i2d_state_t 运行时状态
error_code锁存的 HAL_I2D_ERROR_*
callback可选的中断事件回调
user_ctx回调上下文

3.2 默认值

hal_i2d_init_config_default() 不访问硬件:

字段默认值
instanceI2D
outstanding_limitHAL_I2D_DEFAULT_OUTSTANDING = 0x40
timeout_valueUINT32_MAX
timeout_restarttrue
frame_done_irq_enabletrue
timeout_irq_enabletrue
outstanding_irq_enablefalse

hal_i2d_config_default() 清零整个结构体,只把 format 设为 NV12、rotate 设为ROTATE_0。宽高、地址与 stride 必须在使用前填写。

3.3 缓冲区所有权与对齐

  • 源/目标 buffer 完全由调用方分配、填充和释放;HAL 只拿地址。

  • 容量至少是 hal_i2d_layout_get() 返回的 src_buffer_size / dst_buffer_size。 90°/270° 时两者可能不同,按各自的值准备,不要假设相等。

  • 推荐按 HAL_I2D_BUFFER_ALIGN(64 字节)对齐——它同时是 cache 行大小。三个 官方示例都申请 size + 64 再向上对齐,硬件地址取对齐后的指针。

  • 同一块目标 buffer 在 I2D 完成前不能交给 CPU 读或另一路 DMA 写;完成后再做 cache invalidate 才能读。

4. 接口规范

4.1 调用顺序速查

STEP 1  hal_i2d_init_config_default / hal_i2d_config_default
↓
STEP 2  按需覆盖 init 和每帧 config 字段
↓
STEP 3  hal_i2d_init
↓
STEP 4  hal_i2d_callback_register + SoC IRQ 申请/使能(可选)
↓
STEP 5  每一帧重复  hal_i2d_layout_get() 获取地址、stride 与缓冲尺寸
准备源/目标 buffer 并维护 cache
hal_i2d_start
轮询 handler 或等待 SoC IRQ 完成
invalidate 目标 buffer 并取结果
异常时调用 hal_i2d_stop / hal_i2d_error_clear
↓
STEP 6  hal_i2d_deinit

5. 行为约束与实现说明

5.1 布局推导公式

hal_i2d_layout_get() 的规则:

项规则
bppARGB8888 = 4;RGB565/YUV422 = 2;NV12/RAW8 = 1
输出宽 out_w90°/270° 为 height,否则 width
输出高 out_h90°/270° 为 width,否则 height
src_y_stridesrc_stride_y != 0 ? src_stride_y : width * bpp
dst_y_stridedst_stride_y != 0 ? dst_stride_y : out_w * bpp
src_uv_stride仅 NV12:src_stride_uv != 0 ? src_stride_uv : width
dst_uv_stride仅 NV12:dst_stride_uv != 0 ? dst_stride_uv : out_w
源 Y 行数NV12 为 ALIGN_UP(height, 16),其它格式为 height
目标 Y 行数NV12 为 ALIGN_UP(out_h, 16),其它格式为 out_h
NV12 源 UV 地址显式 src_uv_buf,否则 src_y_addr + src_y_stride * 源 Y 行数
NV12 目标 UV 地址显式 dst_uv_buf,否则 dst_y_addr + dst_y_stride * 目标 Y 行数
src_y_sizesrc_y_stride * 源 Y 行数
src_uv_size仅 NV12:src_uv_stride * (height / 2),否则 0
dst_y_sizedst_y_stride * 目标 Y 行数
dst_uv_size仅 NV12:dst_uv_stride * (out_h / 2),否则 0
*_buffer_size对应 Y + UV 之和(非 NV12 的 UV 为 0)

算例:

配置输出源 stride / 大小目标 stride / 大小
640x480 YUV422,90°480x6401280 / 614,400960 / 614,400
1280x1280 ARGB8888,90°1280x12805120 / 6,553,6005120 / 6,553,600
1920x1080 NV12,90°1080x19201920 / 3,125,7601080 / 3,110,400
1920x1080 RAW8,90°1080x19201920 / 2,073,6001080 / 2,073,600

两个容易错的点:

  1. NV12 的 Y 平面按 16 行对齐。1080 行会占 1088 行:1920 * 1088 = 2,088,960,UV 从这之后开始;直接用 width * height 推 UV 地址会错位。

  2. 90°/270° 后源/目标大小不同。NV12 1920x1080 转 90° 时源 3,125,760、 目标 3,110,400;按 max(src, dst) 分配即可,但 cache 维护要按各自的真实大小做。

5.2 参数校验规则

hal_i2d_start() 与 hal_i2d_layout_get() 共用同一套校验:

检查项约束
config、layout非空
src_buf、dst_buf非空
format五个枚举值之一
rotate四个枚举值之一
width / height8..3840 / 8..2160
对齐宽、高必须整除格式对齐:ARGB8888 = 2;RGB565/YUV422 = 4;NV12/RAW8 = 8
src_stride_y非 0 时 <= 0xFFFF 且 >= width * bpp
dst_stride_y非 0 时 <= 0xFFFF 且 >= out_w * bpp
src_stride_uvNV12 且非 0 时 <= 0xFFFF 且 >= width
dst_stride_uvNV12 且非 0 时 <= 0xFFFF 且 >= out_w
非 NV12 格式两个 UV 指针必须为 NULL,两个 UV stride 必须为 0

config_default() 的默认格式是 NV12,因此不填 src_uv_buf 时 HAL 会按 Y 平面推导 UV 地址。如果要处理单平面/打包格式,必须显式设置 format,否则校验和布局都会 按 NV12 处理。

5.3 中断分发、事件回调与屏蔽语义

  • irq_state 是锁存状态,读不清除;清除要写 irq_clear。

  • irq_mask 是「1 = 屏蔽」。HAL 初始化按 frame_done_irq_enable /timeout_irq_enable / outstanding_irq_enable 写一次掩码;之后可以用hal_i2d_irq_config() 改。

  • hal_i2d_irq_handler() 只处理 latched & enabled;被屏蔽的位保持锁存,留给 轮询读 irq_state 的人。

  • 回调按 TIMEOUT → OUTSTANDING → FRAME_DONE 顺序逐个触发;一次入口可能回调多次。

  • 回调里只做「置标志/计数/唤醒」;cache 维护与日志放到任务上下文。

6. 典型用法:从零开始

本节假设你手上有一块能跑 SDK 的板子,想把「内存里的图 → 旋转/镜像 → 内存里的图」 直接做在 HAL 或 LL 上。

6.1 第二步:算布局

hal_i2d_layout_get() 是纯软件计算,可以在 hal_i2d_init() 之前调用。还不知道 buffer 地址时用占位地址(函数只校验非空,不解引用):

static error_code_t i2d_probe(uint32_t width, uint32_t height,
                              hal_i2d_format_t format, hal_i2d_rotate_t rotate,
                              hal_i2d_layout_t *layout)
{
    hal_i2d_config_t probe;

    (void)hal_i2d_config_default(&probe);
    probe.src_buf  = (void *)(uintptr_t)HAL_I2D_BUFFER_ALIGN;        /* 占位 */
    probe.dst_buf  = (void *)(uintptr_t)(HAL_I2D_BUFFER_ALIGN * 2U); /* 占位 */
    probe.width    = width;
    probe.height   = height;
    probe.format   = format;
    probe.rotate   = rotate;

    return hal_i2d_layout_get(&probe, layout);
}

例:640x480 YUV422 顺时针 90°,得到 480x640、源 stride 1280 / 目标 stride 960、 两边各 614,400 字节。算例见 §6.3。

7. 示例程序解读

三个示例结构相同,只是「谁负责等待、谁负责时钟」不同:

文件层级等待方式时钟布局入口
i2d_blocking_transform_example.cHAL任务里轮询 hal_i2d_irq_handler()HALHALexample_i2d_blocking_transform_run()
i2d_callback_transform_example.cHALIRQ_I2D + HAL 回调HALHALexample_i2d_callback_transform_run()
i2d_ll_example.cLL轮询 irq_state自己开/关自己算example_i2d_ll_run()

8. 集成到你自己的工程

8.1 三种等待形态怎么选

场景形态
bring-up、单帧验证、测试项任务轮询 hal_i2d_irq_handler()
正式业务、任务需要睡眠SoC IRQ + 回调 + 信号量
调试寄存器、验证中断路径LL 轮询 irq_state

不要混用轮询与 ISR:两者都会清 IRQ_STATE,事件会被对方吃掉。