☰
RT-Thread `fixed-clock` 驱动详解:恒定频率时钟提供器的设备树配置、探针流程与消费方用法
2026/10/4 1:40:39 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/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

判断要点可归结为三条:

  1. 频率是否固定不变——需要 DVFS/动态调频的就交给 CCM;
  2. 是否只是被动输入——晶振、RC 振荡器、外部时钟输入是“源”,没有可编程性;
  3. 是否有 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)

文档列举了四类高频问题,这里逐条展开:

  1. 缺少clock-frequency:probe 必读该属性失败即返回错误(fixed_clk_probe中goto _fail),provider 未注册,消费方 phandle 解析得到 NULL——rt_clk_get_*返回 NULL,后续使能直接静默失败或空指针。
  2. DT 中频率填错:固定时钟是所有派生 PLL 数学计算的基准,写错 Hz 会导致整条时钟链全部算错,且是“静默”错误——UART 波特率偏差、SDIO 时序错乱等表现都是下游症状,排查时非常隐蔽。建议对晶振频率值做注释、与硬件原理图核对。
  3. 把需要门控的外设时钟建模成fixed-clock:gate 类时钟应由专门的 gate 驱动 cell 提供(配合RT_CLK_F_SET_RATE_GATE等 flag,见 clk.md 的rt_clk_cellflags 表);否则外设将永远“开着”,功耗与 DVFS 完全失控。
  4. 名字冲突: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/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

相关推荐

上一篇:探索数据算法的瑰宝:《Data Algorithms》开源项目指南
下一篇:【亲测免费】 推荐:Android 图片裁剪库 —— Android Image Cropper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询