API GUIDE / I2D / LL

G32S10 I2D LL 接口

LL 层接口:ll_i2d.h 全部是 static inline,第一个参数是寄存器实例 I2D。

1. 接口规范

1.1 LL 接口

ll_i2d.h 全部是 static inline,第一个参数是寄存器实例 I2D。LL 不做参数 合法性检查,只做字段截位;调用时序、时钟与 buffer 都由调用方负责。

1.1.1 实例与版本

函数说明
ll_i2d_is_instance(i2dx)指针是否为唯一实例 I2D
ll_i2d_get_version(i2dx)读 version 寄存器

1.1.2 Shadow、复位、启动与停止

函数说明
ll_i2d_update_shadow(i2dx)写 SHD_CTRL = I2D_SHD_CTRL_UPDATE_ALL(0x3)
ll_i2d_set_shadow_control(i2dx, value)写 SHD_CTRL 原始值
ll_i2d_reset(i2dx)置 CTRL.RST(bit16,模块复位)
ll_i2d_safe_reset(i2dx)置 CTRL.SAFE_RST(bit4,不清配置)
ll_i2d_start(i2dx)置 CTRL.START(bit0)
ll_i2d_stop(i2dx)清 CTRL.START
ll_i2d_get_control(i2dx)读 CTRL

1.1.3 尺寸、模式与 outstanding

函数说明
ll_i2d_set_image_size(i2dx, width, height)宽 [31:16]、高 [15:0],各自按 16 位截位
ll_i2d_get_image_size(i2dx)读 IMG_SIZE
ll_i2d_set_mode(i2dx, data_type, rotate_mode, mirror, flip)整写 MODE
ll_i2d_write_mode(i2dx, value) / ll_i2d_get_mode(i2dx)写/读 MODE 原始值
ll_i2d_set_outstanding_limit(i2dx, limit)按 8 位截位写入;不拒绝 0
ll_i2d_get_outstanding(i2dx)读 OUTSTANDING

1.1.4 地址与 stride

函数说明
ll_i2d_set_source_addr(i2dx, y_addr, uv_addr)写源 Y/UV 地址
ll_i2d_set_destination_addr(i2dx, y_addr, uv_addr)写目标 Y/UV 地址
ll_i2d_set_source_stride(i2dx, y_stride, uv_stride)两个 stride 都按 16 位截位
ll_i2d_set_destination_stride(i2dx, y_stride, uv_stride)同上
ll_i2d_get_source_y_addr() / ll_i2d_get_source_uv_addr()读源地址
ll_i2d_get_destination_y_addr() / ll_i2d_get_destination_uv_addr()读目标地址

1.1.5 中断

函数说明
ll_i2d_get_irq_status(i2dx)读 irq_state(锁存,读不清除)
ll_i2d_clear_irq(i2dx, irq_mask)写 irq_clear,写 1 清对应状态位
ll_i2d_set_irq_mask(i2dx, irq_mask)写 irq_mask 原始值(1 = 屏蔽)
ll_i2d_enable_irq(i2dx, irq_mask)清对应 mask 位(放行)
ll_i2d_disable_irq(i2dx, irq_mask)置对应 mask 位(屏蔽)
ll_i2d_get_irq_mask(i2dx)读 irq_mask

1.1.6 硬件超时与时钟门控

函数说明
ll_i2d_set_timeout_value(i2dx, value) / ll_i2d_get_timeout_value(i2dx)写/读硬件超时上限
ll_i2d_enable_timeout_restart(i2dx) / ll_i2d_disable_timeout_restart(i2dx)置/清 TIMEOUT_MODE.RST
ll_i2d_get_timeout_mode(i2dx)读 TIMEOUT_MODE
ll_i2d_enable_clock_gate(i2dx) / ll_i2d_disable_clock_gate(i2dx)置/清 CLK_GATE.VALUE
ll_i2d_get_clock_gate(i2dx)读 CLK_GATE

2. 典型用法:从零开始

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

2.1 LL 五步模型

LL 没有布局助手、没有状态机、没有等待,全部自己做:

步骤做什么
1打开 AON HP1 总线门与模块内部时钟
2一次性配置:安全复位、outstanding、硬件超时、中断掩码、清中断
3自己算布局:stride、平面大小、输出尺寸、NV12 UV 地址
4每帧:写 MODE/IMG_SIZE/地址/stride → 提交 shadow → 清中断 → 安全复位 → 启动
5轮询 irq_state(或注册 SoC 中断)→ 完成后 invalidate 目标 → 收尾关时钟

时钟的两级开关见 §6.8。下面的序列取自 code/examples/ll/i2d_ll_example.c:

/* 三个事件位;示例里叫 EXAMPLE_I2D_IRQ_ALL_MASK */
#define I2D_IRQ_ALL  (I2D_IRQ_FRAME_DONE_MASK | I2D_IRQ_TIMEOUT_MASK | \
                      I2D_IRQ_OUTSTANDING_MASK)
#define I2D_IRQ_WANT (I2D_IRQ_FRAME_DONE_MASK | I2D_IRQ_TIMEOUT_MASK)

/* 1. 时钟 */
ll_aon_hpclkgr1_clear_stop_mask(AON_CLKGR_STOP_MASK(AON_HPCLKGR1_I2D_BIT));
ll_i2d_enable_clock_gate(I2D);

/* 2. 一次性配置 */
ll_i2d_safe_reset(I2D);
ll_i2d_set_outstanding_limit(I2D, I2D_OUTSTANDING_LIMIT_DEFAULT); /* 0x40 */
ll_i2d_set_timeout_value(I2D, UINT32_MAX);
ll_i2d_enable_timeout_restart(I2D);
ll_i2d_set_irq_mask(I2D, I2D_IRQ_ALL & ~I2D_IRQ_WANT);  /* 1 = 屏蔽 */
ll_i2d_clear_irq(I2D, I2D_IRQ_ALL);

/* 4. 每帧(src_y/src_uv/dst_y/dst_uv 与各 stride 由下面的 layout 计算得到) */
ll_i2d_set_mode(I2D, I2D_MODE_DATA_TYPE_ARGB8888, I2D_MODE_ROTATE_90, false, false);
ll_i2d_set_image_size(I2D, width, height);       /* 源尺寸 */
ll_i2d_set_source_addr(I2D, src_y, src_uv);
ll_i2d_set_destination_addr(I2D, dst_y, dst_uv);
ll_i2d_set_source_stride(I2D, src_stride_y, src_stride_uv);
ll_i2d_set_destination_stride(I2D, dst_stride_y, dst_stride_uv);
ll_i2d_update_shadow(I2D);                       /* 必须提交 */
ll_i2d_clear_irq(I2D, I2D_IRQ_ALL);              /* 清掉上一帧的锁存 */
ll_i2d_safe_reset(I2D);
ll_i2d_start(I2D);

布局要自己算。LL 示例的 example_i2d_layout_build() 就是一个完整实现:按格式取 bpp、按旋转换输出宽高、按 16 行对齐算 NV12 的 Y 平面、UV 地址紧随 Y 之后、stride 超 0xFFFF 直接报错。核心几行:

bpp      = example_i2d_bytes_per_pixel(data_type);
out_w    = is_rotated ? height : width;
out_h    = is_rotated ? width  : height;
src_rows = is_nv12 ? align_up(height, 16U) : height;
dst_rows = is_nv12 ? align_up(out_h,  16U) : out_h;

src_y_stride = width * bpp;
dst_y_stride = out_w * bpp;
src_uv_stride = is_nv12 ? width : 0U;
dst_uv_stride = is_nv12 ? out_w : 0U;

src_buffer_size = src_y_stride * src_rows;
dst_buffer_size = dst_y_stride * dst_rows;
if (is_nv12) {
    src_uv_addr = src_y_addr + src_buffer_size;
    dst_uv_addr = dst_y_addr + dst_buffer_size;
    src_buffer_size += src_uv_stride * (height / 2U);
    dst_buffer_size += dst_uv_stride * (out_h / 2U);
}

buffer 的分配、64 字节对齐与 cache 维护和 HAL 完全一样(§7.5、§6.7),区别只在 layout 要自己算、常量要自己定义。注意 stride 超过 0xFFFF 时 LL 会静默截位,自己 的 layout 函数必须显式报错。

3. 示例程序解读

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

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

3.1 改参数做练习

只改各文件开头的 Tunables:

宏可选值
EXAMPLE_I2D_WIDTH / _HEIGHT8x8 ~ 3840x2160,受格式对齐与内存限制
EXAMPLE_I2D_FORMAT / _DATA_TYPENV12 / RAW8 / RGB565 / ARGB8888 / YUV422
EXAMPLE_I2D_ROTATEROTATE_0/90/180/270(LL 用 I2D_MODE_ROTATE_*)
EXAMPLE_I2D_HORIZONTAL_MIRROR / _VERTICAL_FLIPtrue / false
EXAMPLE_I2D_TIMEOUT_MS软件等待上限(毫秒)

建议练习顺序:

  1. 原样跑一次,确认 verify=OK;

  2. 改成 ROTATE_270,确认仍然 verify=OK(校验公式跟着变);

  3. 改成 640x480 + YUV422,观察日志里的尺寸与 stride;

  4. 改成 1920x1080 + NV12 + ROTATE_90,观察分配的帧大小是 3,125,760(源、目标 取较大者),而目标实际只有 3,110,400;

  5. 打开 horizontal_mirror,确认校验仍然通过;

  6. LL 示例里把 outstanding 改成 0(仅练习):ll_i2d_set_outstanding_limit 不拦, 硬件会按 0 配置,用来体会「LL 不校验参数」。