- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
导读
fixed-clock是 RT-Thread CLK(Common Clock Framework,通用时钟框架)子系统中最基础、也是几乎所有 SoC 时钟树根节点都会用到的平台驱动:它向系统注册一个频率恒定、常开(always-on)、无 gate、无父时钟、不可编程的时钟单元,通常作为晶振(crystal)、RC 振荡器或外部时钟输入在设备树中的建模方式,是 PLL 等复杂时钟控制器(CCM)的挂载底座。本文以 fixed.md 为核心骨架,结合 clk-fixed-rate.c、clk.h 及仓库内真实 DTS 片段,完整讲解该驱动的设备树描述、probe 流程、数据结构、OFW 匹配、与 SoC CCM 驱动的选型边界、消费方 API 用法及常见坑位,使读者能够正确地在 RT-Thread DM(Device Model)项目中声明并消费fixed-clock节点。
1. 驱动定位与总体概述
fixed-clock平台驱动的实现位于components/drivers/clk/clk-fixed-rate.c,驱动名为clk-fixed-rate,属于 RT-Thread CLK 子系统(总览见 clk.md 的page_device_clk文档)。
从代码看,它注册的时钟具有如下特征:
- 恒频:频率在设备树
clock-frequency属性中写死,运行时不变; - 常开:实现中没有
enable/disable/prepare/unprepare回调——在 ops 层面使能操作是空操作(no-op),因此它天然适合描述始终运行的根时钟; - 无父无 gate:没有父时钟、没有 gate 位、没有 MMIO 可编程寄存器,只有唯一的
.recalc_rate回调返回固定频率。
用一句话概括它在时钟树中的位置:fixed-clock是 SoC 时钟树里 PLL 等可编程时钟控制器(CCM)所“悬挂”的最底层叶子节点。几乎所有带晶振输入的平台,设备树根部都会出现一个compatible = "fixed-clock"的节点。
2. 设备树描述(Provider 与 Consumer)
2.1 Provider 节点:声明固定频率时钟
文档给出的标准 Provider 声明如下:
osc24m: fixed-clock { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <24000000>; clock-accuracy = <0>; /* optional, ppb */ clock-output-names = "osc24m"; /* optional; becomes cell name */ };各属性含义:
| 属性 | 必填 | 作用 |
|---|---|---|
compatible | 必填 | 固定为"fixed-clock",用于匹配fixed_clk_ofw_ids |
#clock-cells | 必填 | 必须为<0>,表示该 provider 的每个时钟说明符不携带额外整数 |
clock-frequency | 必填 | 时钟频率,单位 Hz;缺失将导致 probe 失败 |
clock-accuracy | 可选 | 时钟精度,单位 ppb(十亿分之一),默认 0 |
clock-output-names | 可选 | 输出时钟名,会成为 cell 的name,供rt_clk_get_by_name回退查找使用 |
仓库内真实的 DTS 用法可对照 bsp/qemu-virt64-aarch64/amp.dts 中的clk24m节点:
clk24m: apb-pclk { clock-output-names = "clk24mhz"; clock-frequency = <24000000>; #clock-cells = <0>; compatible = "fixed-clock"; };该节点被同文件中的pl011@9040000(UART)串口节点消费(见下文)。此外 bsp/zynqmp-a53-dfzu2eg/zynqmp.dts、bsp/raspberry-pi/dm/dts/bcm2711-rpi-4-b.dts、bsp/raspberry-pi/dm/dts/bcm283x-rpi-3-b.dts 中也都有fixed-clock节点,可作为多平台参考。
2.2 Consumer 节点:消费固定频率时钟
下游设备通过clocks属性引用 provider:
uart0: serial@40000000 { clocks = <&osc24m>; /* clock-names optional when only one clock */ };关键语义:当#clock-cells = <0>时,clocks = <&osc24m>中的说明符不再携带额外整数,内核将隐式按索引 0 解析,即rt_ofw_get_clk(np, 0)默认使用args->args[0](无参数时为 0)作为 cell 索引。
对照 amp.dts 中的真实消费方式:
pl011@9040000 { clock-names = "uartclk", "apb_pclk"; clocks = <&clk24m &clk24m>; ... };注意这里同一根clk24m被同一个节点以两个 clock-names 各引用了一次——固定时钟只有一个 cell(cells_nr = 1),多次引用解析到的都是同一个struct rt_clk_cell,框架的引用计数(prepare_count/enable_count)按 cell 共享,因此重复引用不会造成计数错乱。
3. Probe 流程源码级拆解
文档将 probe 流程概括为 6 步,下面逐一对齐源码(clk-fixed-rate.c):
3.1 早期注册:INIT_SUBSYS_EXPORT
static int fixed_clk_drv_register(void) { rt_platform_driver_register(&fixed_clk_driver); return 0; } INIT_SUBSYS_EXPORT(fixed_clk_drv_register);通过INIT_SUBSYS_EXPORT在子系统初始化阶段完成rt_platform_driver_register,保证固定时钟的 probe 早于 CCM(如rockchip,*-cru)及其消费方的 phandle 解析,让晶振节点在 CCM 消费之前就已注册完毕。
3.2 分配与解析:fixed_clk_probe
static rt_err_t fixed_clk_probe(struct rt_platform_device *pdev) { rt_err_t err; rt_uint32_t val; struct rt_device *dev = &pdev->parent; struct clk_fixed *cf = rt_calloc(1, sizeof(*cf)); ... if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val))) { goto _fail; } cf->fcell.fixed_rate = val; val = 0; rt_dm_dev_prop_read_u32(dev, "clock-accuracy", &val); cf->fcell.fixed_accuracy = val; rt_dm_dev_prop_read_string(dev, "clock-output-names", &cf->fcell.cell.name); ... if ((err = rt_clk_register(&cf->parent))) { goto _fail; } return RT_EOK; _fail: rt_free(cf); return err; }- 第 3 步:
rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val)是必填解析,返回值非RT_EOK直接goto _fail释放内存返回错误——文档明确:probe 缺少clock-frequency即失败; - 第 4 步:
clock-accuracy与clock-output-names均为可选解析;clock-output-names读取成功后赋给cf->fcell.cell.name,成为该 cell 的名字; - 第 5 步:接线
cells[0],挂载fixed_clk_ops(仅含.recalc_rate); - 第 6 步:
rt_clk_register(&cf->parent)将 provider 节点登记进全局时钟节点链表(RT_CLK_NODE_OBJ_NAME = "CLKNP"),并挂上rt_ofw_data供 phandle 查找;若该 fixed-clock 节点自身带assigned-clocks*属性(对叶子晶振而言很少见),rt_clk_register内部可能调用rt_ofw_clk_set_defaults应用默认配置。
失败路径统一rt_free(cf)并返回错误码,不留内存泄漏。
4. 数据结构:clk_fixed与rt_clk_fixed_rate
文档给出的结构定义与 clk.h 中的struct rt_clk_fixed_rate完全一致:
struct clk_fixed { struct rt_clk_node parent; struct rt_clk_fixed_rate fcell; struct rt_clk_cell *cells[1]; }; struct rt_clk_fixed_rate { struct rt_clk_cell cell; rt_ubase_t fixed_rate; rt_ubase_t fixed_accuracy; };字段角色对照:
| 成员 | 作用 |
|---|---|
fixed_rate | 频率(Hz),来自 DTclock-frequency |
fixed_accuracy | 精度(ppb),来自 DTclock-accuracy,可选 |
cell.ops->recalc_rate | 直接返回fixed_rate,忽略 parent_rate 参数 |
注意struct clk_fixed以rt_clk_node parent开头、rt_clk_fixed_rate fcell内嵌于其中,cells[1]只挂一个输出 cell——整个驱动用一次rt_calloc分配全部所需内存,结构紧凑、无二次分配。
fixed_clk_recalc_rate的实现也印证了“忽略父时钟”的说法:
static rt_ubase_t fixed_clk_recalc_rate(struct rt_clk_cell *cell, rt_ubase_t parent_rate) { struct rt_clk_fixed_rate *fr = rt_container_of(cell, struct rt_clk_fixed_rate, cell); return fr->fixed_rate; }rt_clk_fixed_rate在 clk.h 中的注释也明确其定位:"Used for constant-frequency clocks without configurable parents or dividers."
4.1 关于 enable/prepare 的语义
由于fixed_clk_ops中没有.enable/.disable/.prepare/.unprepare,使能操作在 ops 层面是空操作。但框架层的引用计数仍然对消费方生效:rt_clk_enable会增加 cell 的enable_count,rt_clk_disable会递减——list_clk调试命令里依然能看到这些计数。这意味着消费方可以安全地对固定时钟调用rt_clk_prepare_enable/rt_clk_disable_unprepare,只是底层没有硬件动作,纯粹是引用计数记账。
5. OFW 匹配与 Linux 兼容性
static const struct rt_ofw_node_id fixed_clk_ofw_ids[] = { { .compatible = "fixed-clock" }, { /* sentinel */ } }; static struct rt_platform_driver fixed_clk_driver = { .name = "clk-fixed-rate", .ids = fixed_clk_ofw_ids, .probe = fixed_clk_probe, };驱动以"fixed-clock"作为唯一 compatible 匹配项。该绑定与 Linux 内核的 fixed-clock 绑定兼容(文档明确说明 "Linux-compatible binding"),因此同一段 DTS 片段既可用于上游 dt-bindings 生态,也可直接用于 RT-Thread BSP,这是 RT-Thread CLK 框架“镜像 Linux 通用时钟模型以便复用 dt-bindings 与 DTS 片段”设计目标的具体体现(见 clk.md)。
6. 选型边界:fixed-clockvs SoC CCM 驱动
文档给出了清晰的选择对照表,这是实战中最容易混淆的决策点:
使用fixed-clock | 使用 SoC 驱动(如rockchip,*-cru) |
|---|---|
| 晶振(Crystal) | PLL |
| RC 振荡器 | mux(多路选择器) |
| 外部时钟输入 | divider(分频器) |
| 频率在启动时固定 | gate(门控) |
| 无 MMIO(或 MMIO 不由本驱动管理) | DVFS、动态 reparent(换父) |
| —— | 大型时钟控制器 IP |
判断要点可归结为三条:
- 频率是否固定不变——需要 DVFS/动态调频的就交给 CCM;
- 是否只是被动输入——晶振、RC 振荡器、外部时钟输入是“源”,没有可编程性;
- 是否有 MMIO 可编程寄存器——
fixed-clock完全没有寄存器操作,任何需要写寄存器选频、分频、门控的场景都应交给 SoC 驱动。
clk.md中的 CLK 子系统内置 provider 表也印证了这一分层:clk-fixed-rate.c 与clk-scmi.c是内核自带的两类通用 provider,而 Rockchip 等 SoC 的 CCM/PLL/mux/gate 则通过SOC_DM_CLK_DIR挂在bsp/*/dm/clk/下(如 bsp/rockchip/dm/clk/)。
7. 消费方 API 用法
文档给出的消费示例:
struct rt_clk *clk = rt_clk_get_by_name(dev, "uart_clk"); /* or index 0 if no clock-names */ rt_err_t err = rt_clk_prepare_enable(clk); rt_ubase_t hz = rt_clk_get_rate(clk);结合 clk.h 与 clk.md 的消费 API 表,要点如下:
rt_clk_get_by_name(dev, "uart_clk")按clock-names匹配;若 DT 中只有一个时钟且未写clock-names,可用rt_clk_get_by_index(dev, 0),其内部按clocks数组序号解析;- 若名字在
clock-names中未命中,rt_clk_get_by_name还会回退扫描已注册 cell 的cell->name——这正是clock-output-names的作用:让固定时钟的 cell 有一个全局唯一、可被名字查找的名字; rt_clk_prepare_enable是 probe 中最常用的组合(先 prepare 再 enable);rt_clk_get_rate读取 cell 的速率:固定时钟的.recalc_rate直接返回fixed_rate,因此读到的就是 DT 里clock-frequency写死的值;fixed-clock几乎不需要rt_clk_set_rate——速率固定不可编程;若调用,框架会走set_rate回调,而固定时钟没有实现该回调,只会得到“不支持”的语义。
7.1 NULL 时钟的便利性
rt_clk_enable(NULL)与rt_clk_prepare(NULL)均返回RT_EOK——框架对 NULL 句柄宽容处理,方便驱动把fixed-clock当作“可选时钟”使用。但文档也提醒:rt_clk_get_*的返回值仍需在启用前判空,不能依赖宽容行为掩盖 phandle 解析失败。
8. 常见陷阱(Pitfalls)
文档列举了四类高频问题,这里逐条展开:
- 缺少
clock-frequency:probe 必读该属性失败即返回错误(fixed_clk_probe中goto _fail),provider 未注册,消费方 phandle 解析得到 NULL——rt_clk_get_*返回 NULL,后续使能直接静默失败或空指针。 - DT 中频率填错:固定时钟是所有派生 PLL 数学计算的基准,写错 Hz 会导致整条时钟链全部算错,且是“静默”错误——UART 波特率偏差、SDIO 时序错乱等表现都是下游症状,排查时非常隐蔽。建议对晶振频率值做注释、与硬件原理图核对。
- 把需要门控的外设时钟建模成
fixed-clock:gate 类时钟应由专门的 gate 驱动 cell 提供(配合RT_CLK_F_SET_RATE_GATE等 flag,见 clk.md 的rt_clk_cellflags 表);否则外设将永远“开着”,功耗与 DVFS 完全失控。 - 名字冲突:
clock-output-names应保证足够唯一,因为rt_clk_get_by_name存在按cell->name的全局回退查找——重名可能导致消费方拿到错误的时钟句柄。
另可补充一条来自 CLK 框架总文档的通用注意点:ISR 上下文中禁止调用prepare/prepare_enable(RT_DEBUG_NOT_IN_INTERRUPT检查),固定时钟虽无硬件准备动作,但框架语义上仍属“可睡眠”路径,应在线程上下文调用。
9. 调试与验证
启用RT_USING_CONSOLE+RT_USING_MSH后,可在 MSH shell 中执行:
list_clk该命令会列出每个已注册 cell 的:名称、enable/prepare 计数、速率、消费方dev_id/con_id、父时钟名。对于fixed-clock,你会看到速率恒等于 DT 中的clock-frequency,enable/prepare 计数随消费方的启停增减,从而直观验证引用计数语义与注册是否成功。
10. 总结
RT-Thread 的fixed-clock驱动是 CLK 子系统中最简单却也最关键的 provider:它以约 100 行代码、一次内存分配、一个.recalc_rate回调,为整个 SoC 时钟树提供了恒频常开的根节点,并通过与 Linux 兼容的 DT 绑定直接复用上游设备树生态。使用时牢记三条主线:DT 中clock-frequency必须准确且必填;只有真正的“源时钟”才适合它,gate/PLL/mux/divider 一律交给 SoC CCM 驱动;消费方统一走rt_clk_get_*+rt_clk_prepare_enable的框架 API,让引用计数和上层 DVFS 协调逻辑保持完整。
进一步阅读:
- CLK 框架总览:clk.md
- 驱动实现:clk-fixed-rate.c
- 公共头文件与全部 API:clk.h
- 真实 DTS 参考:bsp/qemu-virt64-aarch64/amp.dts
- 操作系统
- 嵌入式
- 物联网
- 嵌入式OS
- RTOS
【免费下载链接】rt-thread
RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/
相关推荐
RT-Thread RTC 设备驱动框架详解:时间设置、date 命令与 Soft RTC 使用指南
RT Thread RTC 设备驱动框架详解:时间设置、date 命令与 Soft RTC 使用指南 RT Thread 的 RTC(实时时钟)设备为操作系统的
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread Nuvoton N9H30 BSP 外设驱动移植详解:设备模型、设备名与 Kconfig 配置全解析
RT Thread Nuvoton N9H30 BSP 外设驱动移植详解:设备模型、设备名与 Kconfig 配置全解析 在 RT Thread 的 BSP 体
操作系统嵌入式物联网嵌入式OSRTOSRT-Thread 平台下 SAM L10 同步 I2C 主机驱动(HAL)详解:API、时序与时钟配置
RT Thread 平台下 SAM L10 同步 I2C 主机驱动(HAL)详解:API、时序与时钟配置 本篇技术指南以 RT Thread 仓库中 bsp/m
操作系统嵌入式物联网嵌入式OSRTOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考