linux clock driver framework
- Linux要管理电源(休眠唤醒)、动态调频(DVFS),还要处理多个外设之间的时钟依赖关系。因此,Linux 引入了 CCF (Common Clock Framework,通用时钟框架)。
时钟树
所有的时钟树,都只有 5 种基本组成,这也是 Linux CCF 框架定义的最基础的 5 种时钟类型
- Oscillator (晶振/固定时钟源)
- 产生最原始的时钟频率。K1的 32 kHz RTC 和 24 MHz 晶振 (OSC)。在 Linux 中通常用 clk-fixed-rate 表示。
- PLL (锁相环)
- 把低频的晶振“倍频”成很高频的时钟。图中的 PLL1、PLL2、PLL3。
- Divider (分频器)
- 把高频时钟除以一个系数(比如 /2, /4),降频给低速外设用。图里那些标着 DIV 或者 div 的方块。
- Multiplexer (MUX / 多路选择器)
- 有多个时钟源输入,通过寄存器选择其中一个输出(像铁轨的道岔)。手册里提到的 “无毛刺 (Glitch-Free) 时钟切换” 就是指高级的 MUX 切换时不会产生破坏系统的错误波形。图中那些梯形的符号(比如图里 APB clk 前面的选择器)。
- Gate (门控)
- 就是一个开关。关掉时钟可以省电。k1手册里提到的 “细粒度时钟门控 (Clock Gating)” 就是指这个。图中那些带有使能端(通常带个与门 & 符号或者开关符号)的模块。
- Oscillator (晶振/固定时钟源)
CLOCK 驱动,本质工作就是把手册里这些 晶振、PLL、MUX、DIV、GATE,一个一个地用 Linux 规定的 C 语言结构体描述出来,然后“注册”给内核。
k1 dts分析
- 基础时钟源
1 | |
- 时钟控制器节点
1 | |
k1的四个时钟控制器:APBS(专门管 PLL)、APBC(管 APB 总线外设)、MPMU(主电源/时钟管理)、APMU(应用电源/时钟管理)
- APBS(PLL部分):相当于“发动机”,负责升高频率(比如把 24M 晶振倍频到 1.2GHz 给 CPU,或者 300MHz 给总线)。
- APBC / MPMU / APMU:相当于“变速箱和开关”,它们接收来自 PLL 的高频时钟,然后通过各自内部的 MUX(选择器)和 DIV(分频器)分配和控制给各个具体外设的频率大小,并通过 GATE(门控)来开关。
- PMU: Power Management Unit,电源管理单元, 电源和时钟是高度绑定的。
- MPMU (Main PMU):负责管理芯片里最核心、最基础、甚至休眠时也不能断电的模块(比如看门狗、RTC、唤醒逻辑)的时钟和电源。
- APMU (Application PMU):负责管理那些极其耗电的“应用级”大模块(比如 CPU核心、GPU、VPU视频编解码、PCIe、USB)的时钟和电源。
- PMU: Power Management Unit,电源管理单元, 电源和时钟是高度绑定的。
大多数现代复杂应用处理器(如瑞芯微、全志、NXP i.MX 以及这款 K1)都是这样的架构,只有结构简单的单片机才会把时钟全塞进一个大杂烩寄存器(RCC)里。
- 简化了电源处理逻辑,例如休眠时主要切断 APMU 所在的时钟和电源。
MFD Syscon 架构 : 虽然物理上分为四个区域,但在 Linux 内核的 CCF 框架看来,它们构成了一棵完整的、互相依赖的时钟树(比如 APBC 里的 UART 时钟,父节点可能就是 APMU 里的某个时钟,源头是 APBS 里的 PLL)。为了方便在这个驱动内统一解析这些依赖、统一向内核注册,写在一个 spacemit,k1-ccu 驱动里是最高效的。
驱动代码
- ccu_common.c
1 | |
- probe 函数
1 | |
- 把数组里的几百个时钟,一个一个向 Linux 的通用时钟框架(CCF)报备。
1 | |
- data 数组:键值对
1 | |
- 驱动只有一个: k1_ccu_driver, data 数组里的每一个元素是具体的硬件资源
clk 注册完成后申请
- 绝大部分时钟就是指定使用用途的(硬件电路决定了)
- 我自己写个 LED 驱动,能申请时钟吗?
- 如果你想申请 uart0_clk 给 LED 用:技术上可以(只要你拿到它的 ID),但逻辑上很奇怪。你开了这个时钟,UART0 模块内部的逻辑电路就开始跳动了,而你的 LED 硬件并不受这个时钟线控制,所以 LED 不会亮,反而白白增加了 UART0 模块的功耗。
- 能申请“没指定给谁用”的时钟吗?:通常没有这种“闲置”时钟。不过,芯片里会有一些通用的 GPIO 时钟或辅助时钟(Dummy Clocks)。如果你的硬件(比如 LED)是通过 GPIO 控制的,你申请的其实是 GPIO 控制器的时钟。
- 我自己写个 LED 驱动,能申请时钟吗?
- 如果这时候 UART 驱动要开启 UART0 时钟(clk_enable)! 内核怎么知道去动哪个寄存器的哪个位呢?
- 内核会去调用这个时钟对应的 操作函数集(clk_ops)。而 ccu_pll.c、ccu_ddn.c、ccu_mix.c 这三个文件里写的,正是一天天具体的“底层干活指令”:
- ccu_mix.c 里面有 ccu_gate_enable() 函数,里面写的正是如何调用 regmap 往特定的寄存器写 1。
- ccu_pll.c 里面有 ccu_pll_set_rate() 函数,里面写的正是怎么根据目标频率去配置锁相环的参数。
- 内核会去调用这个时钟对应的 操作函数集(clk_ops)。而 ccu_pll.c、ccu_ddn.c、ccu_mix.c 这三个文件里写的,正是一天天具体的“底层干活指令”:
- 如果 UART0 一直不用,它的时钟硬件就一直不使用吗?
- 是的,这正是 Linux CCF 框架存在的意义。默认情况下,为了省电,内核会把不使用的时钟全关了(Gate)。只有当 UART0 的驱动程序调用 clk_prepare_enable() 时,这块硬件才会真正开始震荡输出。一旦驱动卸载或进入休眠,时钟又会被关掉。
详:
- ccu_pll.c: 锁相环操作
- PLL 驱动代码中,写完寄存器后不能立即返回。必须有一个 read 循环去检测“锁定状态位(Lock Bit)”,直到硬件确认“我稳了”,驱动才能继续往下走。
- ccu_ddn.c: 处理复杂的小数分频器
- ccu_mix.c: 杂合了门控/分频/多路复用的基本操作: 在硬件上,一个时钟寄存器可能既是多路选择器(MUX),又是分频器(DIV),还带个开关(GATE)。这种“多项全能”的硬件位,被抽象成了一种 MIX 类型。
调试
- clk_summary
- cat /sys/kernel/debug/clk/clk_summary
- 查看时钟层级
- 开关enable_cnt
- 最终频率rate :div/pll
clk 相关
一、 硬件组件类
- OSC (Oscillator / Crystal Oscillator):晶体振荡器。负责产生原始的、固定频率的脉冲信号,是整个芯片所有时钟的源头(源泉)。
- PLL (Phase Locked Loop):锁相环。负责将低频信号(如 24MHz 晶振)倍频成高频信号(如 1.6GHz),是芯片动力的“心脏”。
- MUX (Multiplexer):多路选择器。负责在多个时钟源中选择一个输出。比如串口时钟可以选择来自 24MHz 晶振,也可以选择来自某个分频器。
- DIV (Divider):分频器。负责将高频信号除以一个系数。比如将 100MHz 降频为 50MHz 给外设使用。
- GATE (Clock Gating):时钟门控。负责时钟的“开关”。关闭不使用的模块时钟可以显著降低芯片功耗(省电的关键)。
二、 K1 芯片特定模块
- APBS (Application Peripheral Bus Spare/Special):应用外设总线备用/专用单元。在 K1 中,它主要负责管理最底层的 PLL(锁相环) 控制寄存器。
- APBC (APB Bus Clock Unit):负责 APB 总线上的时钟单元(比如管 UART、PWM、IIC)。
- MPMU (Main Power Management Unit):主电源管理单元。负责芯片最核心、最基础部分(如 RTC、看门狗、系统总线)的时钟和电源控制。
- APMU (Application Power Management Unit):应用电源管理单元。负责高性能应用模块(如 CPU、GPU、USB、PCIe)的时钟和电源控制。
三、 软件架构类(驱动开发的“框架”)
- CCF (Common Clock Framework):通用时钟框架。Linux 内核提供的一套标准接口。它把时钟抽象成树状结构,自动处理父子时钟的依赖关系(比如你开 UART 时钟,CCF 会自动帮你把它的父级 PLL 也开了)。
- MFD (Multi-Function Device):多功能设备。Linux 驱动的一种架构,用于处理一个硬件块同时具有多种功能(比如一个寄存器基地址里既有时钟控制,又有复位控制,还有电源控制)。
- Syscon (System Controller):系统控制器。内核中一种特殊的 MFD 设备。它通常代表一组通用的寄存器,可以被多个不同的驱动(时钟驱动、复位驱动、电源驱动)通过 Regmap 共同访问。
- Regmap (Register Map):寄存器映射层。Linux 提供的一套抽象接口。它给读写寄存器加了一层“保险”,提供自动加锁(防止并发冲突)、缓存等功能,让不同的驱动可以安全地共享同一个硬件控制块。
四.
- CMU: 中央模块单元: APBC…
硬件上,OSC 产生信号,PLL 升频,MUX/DIV/GATE 分配频率。这些硬件被物理上安置在 APBS/APBC/MPMU/APMU 不同的房间里。
软件上,我们利用 MFD Syscon 架构找到这些房间,通过 Regmap 拿到钥匙,最后按照 CCF 框架的要求,把这些零件组装成一棵“时钟树”。
linux clock driver framework
https://goko-son626.github.io/post/linux-CLOCK-driver-framework.html

