API GUIDE / AON / HAL

AON HAL 接口

1. HAL 接口

1.1 初始化

error_code_t hal_cpm_init(void);

功能:

  • 清零 HAL clock enable 引用计数
  • 从当前硬件寄存器刷新 clock parent 和 rate 缓存

返回值:

返回值 说明
IM_OK 初始化成功。
IM_ERR_INVALID_PARAM 内部 clock 表或硬件读取结果异常。
其它错误码 刷新某个 clock parent/rate 时返回的底层错误。

1.2 Enable / Disable / Is Enabled

error_code_t hal_cpm_clk_enable(hal_cpm_id_t id);
error_code_t hal_cpm_clk_disable(hal_cpm_id_t id);
error_code_t hal_cpm_clk_is_enabled(hal_cpm_id_t id, bool *is_enabled_out);

clock id 语义:

  • hal_cpm_clk_enable() 会维护 clock enable 引用计数。
  • 引用计数从 0 变成 1 时,才调用对应 clock 类型的硬件 enable。
  • 多次 enable 需要对应多次 disable;引用计数大于 1 时 disable 只递减计数,不关闭硬件。
  • 对当前无硬件 disable 控制的 base/PLL clock,最后一次 disable 只清软件引用计数。
  • HAL_CPM_ID_CLK_CGU_UART 与 HAL_CPM_ID_CLK_CGU_LP_UART 共用一个物理 STOP 位和引用计数;对任一 ID 调用 enable、disable 或 is_enabled 都按该物理时钟处理。

gate id 语义:

  • 不维护引用计数。
  • enable 直接打开 gate,disable 直接关闭 gate。

返回值:

返回值 说明
IM_OK 操作成功。
IM_ERR_INVALID_PARAM id 非法或输出指针为空。
IM_ERR_BUSY clock enable 引用计数达到上限。
IM_ERR_TIMEOUT 等待 PLL on 状态或 CDR busy 清除超时。
IM_ERR_UNSUPPORTED 指定 clock 类型不支持该操作。

1.3 Rate

error_code_t hal_cpm_clk_get_rate_hz(hal_cpm_id_t id, uint32_t *rate_hz_out);
error_code_t hal_cpm_clk_set_rate_hz(hal_cpm_id_t id, uint32_t rate_hz);

说明:

  • rate 单位固定为 Hz。
  • get_rate_hz 读取 HAL 缓存值;hal_cpm_clk_get_info() 每次调用会先全量刷新 parent/rate 缓存。
  • set_rate_hz、set_parent 成功后刷新被操作 clock 自身的 parent/rate 缓存; UART 与 LP_UART 共享 uartcdr,会同时刷新两者。其它依赖该 clock 的子节点不会递归刷新。
  • set_rate_hz 计算出的 CDR divider 字段与当前硬件值相同时不写寄存器、不触发 CE,因此重复设置同一频率不会产生额外的共享时钟重配窗口。
  • gate id 不支持 rate 接口。

返回值:

返回值 说明
IM_OK 操作成功。
IM_ERR_INVALID_PARAM id 非法、输出指针为空或 rate 参数非法。
IM_ERR_NOT_READY 当前 parent 频率为 0,无法计算目标分频。
IM_ERR_UNSUPPORTED 资源不支持设置频率,或目标模式与该 clock 的当前配置冲突。
IM_ERR_TIMEOUT 等待 CDR busy 清除或 PLL on 状态超时。

1.4 Parent

error_code_t hal_cpm_clk_get_parent(hal_cpm_id_t id,
                                    hal_cpm_id_t *parent_id_out);
error_code_t hal_cpm_clk_set_parent(hal_cpm_id_t id,
                                    hal_cpm_id_t parent_id);

说明:

  • parent 参数必须是可用作 parent 的 clock id 或 HAL_CPM_ID_CLK_NONE;具体目标 clock 还须支持该 parent,否则返回 IM_ERR_UNSUPPORTED。
  • gate id 不支持 parent 接口。
  • root clock 没有 parent,调用 get_parent 返回 IM_ERR_UNSUPPORTED。
  • get_parent 读到硬件无时钟输出时返回 HAL_CPM_ID_CLK_NONE;此时对应 clock rate 为 0。
  • set_parent 在有效 parent 及所需 EXCLK 使能状态均未变化时不写 CDR、不触发 CE;LP clock 已经选择 LPCLK 时也不会重复切换 AON LPCLKSEL。
  • set_parent 成功后刷新当前节点自身的 parent/rate 缓存;UART 与 LP_UART 会同时刷新。其它子节点缓存不会自动重算,依赖该节点的子 clock 需通过 hal_cpm_clk_get_info() 或 hal_cpm_init() 重新同步。

1.5 ROSC2 校准

error_code_t hal_cpm_rosc2_trim(void);

功能:

  • 把 ROSC2 切到 24 MHz 档位(AON_ROSC2_FSEL_24M),然后固定延时 100 us;该流程没有可读取的档位稳定状态位
  • 清计数,保持 TRIM_COUNT_EN 为低电平 200 us,再拉高 TRIM_COUNT_EN;固定延时 100 us 后轮询 TRIMS.TRIM_FINISH,置位后读取一次 TRIMS.TRIM_COUNT_NUM
  • 在 64 项升序 trim 表中取第一个不小于计数值的表项索引,作为ROSCC.ROSC2_TRIM 字段值并写入
  • 测量只执行一次;写入 trim 后不会再次测量,也不会反复测量直到表值与应用值一致

说明:

  • 阻塞实现:档位切换后固定延时 100 us,计数前保持 TRIM_COUNT_EN 低电平 200 us,拉高后再等待 100 us,然后轮询 TRIM_FINISH(最多 100000 次)。按约 1 ms的计数窗口估算,单次调用为 1.4 ms 量级;忙等期间不让出 CPU。
  • 必须在任务上下文调用,不要在中断上下文、关中断上下文或其它对延迟敏感的路径调用。
  • 调用结束后 ROSC2_FSEL 保持为 24 MHz,不会恢复调用前的档位;调用方需评估LPCLK/RTCLK 等下游时钟对频率变化的容忍度。
  • 计数值高于表项上限时饱和到 ROSC2_TRIM=63,不会返回“未校准”错误。
  • 计数握手失败时关闭 TRIM_COUNT_EN 并返回 IM_ERR_TIMEOUT;成功路径读完计数后同样关闭。TRIMS 中的计数保持到下一次测量开始(TRIM_COUNT_CLR)。
  • 超时返回时 ROSC2_FSEL 仍为 24 MHz,ROSCC.ROSC2_TRIM 保持原值不变。
  • 该接口直接操作 LL 位域,不刷新 HAL clock cache。若调用前 ROSC2_FSEL 不是24 MHz,hal_cpm_clk_get_rate_hz() 在刷新前仍可能返回旧档位的标称频率;调用后可执行 hal_cpm_clk_get_info() 或 hal_cpm_init() 刷新。
  • hal_cpm_clk_get_info() / hal_cpm_init() 只会按 ROSC2_FSEL 刷新 HAL使用的标称频率,不返回 trim 后的实测频率;需要实测值时必须通过 ll_aon_clear_trim_count() / ll_aon_enable_trim_count() /ll_aon_is_trim_finished() / ll_aon_get_trim_count_number() 重新执行一次 测量。

返回值:

返回值 说明
IM_OK 测量完成,trim 已写入。
IM_ERR_TIMEOUT TRIM_FINISH 在轮询上限内未置位。

1.6 Clock information

typedef struct {
    uint32_t id;
    const char *name;
    const char *parent;
    bool enabled;
    bool gate_present;
    bool gate_enabled;
    bool busy;
    uint32_t enable_count;
    uint32_t rate_hz;
    uint32_t div_num;
    uint32_t div_den;
} hal_cpm_clk_info_t;

error_code_t hal_cpm_clk_get_count(uint32_t *count_out);
error_code_t hal_cpm_clk_get_info(uint32_t id,
                                  hal_cpm_clk_info_t *info_out);

typedef struct {
    const char *name;
    uintptr_t address;
    uint32_t value;
} hal_cpm_gate_reg_info_t;

error_code_t hal_cpm_clk_get_gate_reg_count(uint32_t *count_out);
error_code_t hal_cpm_clk_get_gate_reg_info(uint32_t index,
                                           hal_cpm_gate_reg_info_t *info_out);

功能:

  • 由具体芯片 HAL 提供当前芯片的 clock 名称、parent 关系、gate、DIV 和 BUSY 语义
  • hal_cpm_clk_get_count() 返回当前芯片 clock 数量
  • hal_cpm_clk_get_info() 查询一个 clock 的完整状态
  • enabled 表示 clock output 状态;gate_present/gate_enabled 表示关联的模块 gate 状态
  • busy 表示当前是否正在进行分频或时钟切换
  • div_num/div_den 表示分频值;普通分频使用 N/1,I2S 使用 M/N,没有分频器时为 0/0
  • 通用 clk middleware 负责遍历 HAL 信息并提供 im_clk_summary();shell 的 cat_clk 只依赖 middleware
  • hal_cpm_clk_get_gate_reg_count()/hal_cpm_clk_get_gate_reg_info() 提供诊断用的 clock gate 寄存器枚举接口
  • gate 寄存器集合是芯片相关的:数量、名称和地址都由具体芯片 HAL 决定,调用方按 [0, count) 遍历,不要假定固定是 LPCLKGR/HPCLKGR0/HPCLKGR1 三个;因此 im_clk_get_gate_reg_count()/im_clk_get_gate_reg_info() 也一样返回描述符而不写死寄存器
  • cat_clk misc 直接打印 HAL 返回的名称和地址;数量为 0 时提示该芯片不暴露 gate 寄存器

返回值:

返回值 说明
IM_OK 查询成功,info_out 已填写。
IM_ERR_INVALID_PARAM id 越界或 info_out 为空。
IM_ERR_NOT_READY 全量刷新 clock cache 失败,无法保证信息一致。

CPM stub 的 clock information 和 gate register 查询接口返回 IM_ERR_UNSUPPORTED。

2. HAL 与 LL 使用约束

  • 普通业务和驱动上层应优先使用 hal_cpm_clk_* 接口,不直接拼 CDR、CPCCR 或 AON CLKGR 字段。
  • LL 接口适用于 HAL 内部、bring-up、调试和确实需要寄存器级控制的底层模块。
  • 修改 CPCCR/CPCCR1 或 CDR source/divider/STOP 字段后,需要按对应寄存器要求触发 CE/update 并等待 busy 清除;HAL 已封装这些流程。
  • gate close 会直接关闭目标模块时钟,关闭 bus、memory、CPU、PSRAM 等关键 gate 可能导致系统挂起。
  • hal_cpm_clk_get_rate_hz() 返回 HAL 缓存值,不直接读寄存器即时计算;外部绕过 HAL 修改寄存器后,应调用 hal_cpm_clk_get_info() 触发全量刷新,或调用 hal_cpm_init()(同时会清零 enable 引用计数)重新同步。
  • 普通 main clock 和 CGU 的 hal_cpm_clk_set_rate_hz() 向上取整分频,使输出频率不大于目标值;受字段上限限制时可能明显低于目标。I2S 使用 M/N 搜索最接近目标的组合,实际频率可能高于或低于目标。调用方应再读取实际 rate 确认结果。
  • hal_cpm_clk_set_rate_hz()/hal_cpm_clk_set_parent() 只刷新被操作 clock 自身,修改 SCLK_A、MPLL 等共享 parent 后,依赖它的子 clock 缓存可能仍是旧值。
  • PLL 动态改频会修改 PLL 控制寄存器并轮询 PLL on 状态,调用方应评估下游 clock 对频率变化的容忍度。

3. 典型使用

3.1 初始化并读取 clock rate

#include "hal_cpm.h"

error_code_t ret;
uint32_t rate_hz;

ret = hal_cpm_init();
if (ret != IM_OK) {
    return ret;
}

ret = hal_cpm_clk_get_rate_hz(HAL_CPM_ID_CLK_CGU_UART, &rate_hz);
if (ret != IM_OK) {
    return ret;
}

3.2 设置 CGU 频率并打开输出

ret = hal_cpm_clk_set_parent(HAL_CPM_ID_CLK_CGU_UART,
                             HAL_CPM_ID_CLK_EXTAL);
if (ret != IM_OK) {
    return ret;
}

ret = hal_cpm_clk_set_rate_hz(HAL_CPM_ID_CLK_CGU_UART, 24000000U);
if (ret != IM_OK) {
    return ret;
}

ret = hal_cpm_clk_enable(HAL_CPM_ID_CLK_CGU_UART);
if (ret != IM_OK) {
    return ret;
}

3.3 打开模块 gate

ret = hal_cpm_clk_enable(HAL_CPM_ID_GATE_UART1);
if (ret != IM_OK) {
    return ret;
}

3.4 查询 gate 状态

bool open;

ret = hal_cpm_clk_is_enabled(HAL_CPM_ID_GATE_UART1, &open);
if (ret != IM_OK) {
    return ret;
}