问题描述:
编写虚拟时钟控制器驱动
1. 固定频率时钟 (Fixed-Rate Clock)
作用 (Function): 这是最简单的时钟源。它代表一个频率恒定、不可被软件改变的时钟。它通常是时钟树的“根”或者一个主要分支的起点。它的频率是在驱动或设备树中硬编码的。
使用场景 (Use Cases):
晶体振荡器 (Crystal Oscillator, XTAL): SoC 外部通常会接一个晶振,比如 24MHz 或 25MHz,为整个系统提供一个稳定、精确的初始时钟源。
锁相环 (PLL) 的固定输出: 某些 PLL 被设计为只输出一个固定的高频时钟,软件无法调整它。
任何频率由硬件决定,软件无法干预的时钟源。
内核 API: clk_register_fixed_rate()
2. 门控 (Gate)
作用 (Function): 这是一个时钟的开关。当门控“打开”时,它会原封不动地将父时钟传递给下游;当门控“关闭”时,它会彻底阻断时钟信号,输出频率为 0。
使用场景 (Use Cases):
功耗管理: 这是门控最核心的应用。当一个外设(如 USB 控制器、显示引擎、串口)不被使用时,可以关闭它的时钟门控,从而显著降低芯片的功耗。在 clk_prepare_enable() 和 clk_disable_unprepare() 的背后,往往就是对门控寄存器的操作。几乎每个需要动态开关的外设时钟都会有一个门控。
内核 API: clk_register_gate()
4. 倍频器 (Multiplier)
作用 (Function): 这是一个升频器。它的作用与分频器相反,将一个低频的父时钟频率乘以一个整数(倍频系数 M),从而输出一个更高的频率。公式:输出频率 = 父时钟频率 * M。
使用场景 (Use Cases):
锁相环 (PLL): 这是倍频器最经典的应用场景。PLL 的核心功能就是接收一个稳定但频率较低的外部晶振时钟(如 24MHz),通过内部的倍频器将其倍频到数百 MHz 甚至数 GHz,为 SoC 的高速部件(如 CPU、GPU)提供主时钟。
在 Linux 4.4 内核中,没有 clk_register_multiplier() 辅助函数,需要使用通用的 clk_register() 配合 clk_multiplier_ops 来手动实现。
5. 选择器 (Multiplexer / MUX)
作用 (Function): 这是一个多路选择开关。它有多个父时钟输入端和一个输出端。在任意时刻,它会根据软件的配置,选择其中一个父时钟作为其输出。就像电视的信号源切换器,可以选择 HDMI1、HDMI2 或 AV 输入。
使用场景 (Use Cases):
性能与功耗的权衡: CPU 时钟的 MUX 可以在高性能 PLL(高频)和低功耗 RC 振荡器(低频)之间切换。当系统空闲时,切换到低频源以节省功耗;当需要高性能时,切换回高频源。
功能切换: 某个外设可能需要根据不同的工作模式使用不同来源的时钟。例如,一个音频编解码器(Codec)的时钟 MUX 可以在为 44.1kHz 采样率优化的时钟源和为 48kHz 采样率优化的时钟源之间切换。
内核 API: clk_register_mux()
6. 复合器 (Composite Clock)
作用 (Function): 复合时钟本身不是一个单一的硬件单元,而是 CCF 中一个极其重要的软件抽象。它将上述几种基本元素(通常是 MUX -> Divider -> Gate 这个经典组合)打包成一个逻辑上的“整体时钟”。
使用场景 (Use Cases):
现代 SoC 的标准外设时钟: 一个典型的外设时钟输入模块通常包含以下全部功能:
一个 MUX,用来选择时钟源(比如来自 PLL0 还是 PLL1)。
一个 Divider,用来将选定的时钟源分频到外设所需的确切频率。
一个 Gate,用来在不使用外设时彻底关断时钟。
通过 clk_register_composite(),驱动开发者可以将这个 MUX + Divider + Gate 的硬件组合注册成一个单一的、名为 "uart_clk" 的时钟。
**优点:**对于时钟的使用者(consumer)来说,它看到的只是一个简单的时钟。它可以调用 clk_set_parent() (实际操作 MUX),clk_set_rate() (实际操作 Divider),clk_prepare_enable() (实际操作 Gate),而无需关心背后复杂的硬件实现。CCF 框架会自动将这些高层 API 请求分发到正确的硬件操作上
日志
添加打印日志信息
分析步骤
第1步:
第2步:
...
代码片段
1.固定时钟的获取和使能
设备树
/*
* 这是一个 25MHz 的外部晶振。
* 我们给它一个标签 (label) "osc_25m",这样其他设备就可以通过 phandle 引用它。
*/
osc_25m: clock-oscillator-25m {
//内核驱动中有专门的fixed-clock驱动,并不用自己取手动编写
compatible = "fixed-clock";
#clock-cells = <0>; /* 关键点: fixed-clock 本身不接受任何参数,所以是0 */
clock-frequency = <25000000>; /* 频率: 25 MHz */
clock-output-names = "xtal_25m"; /* 时钟在 debugfs 中显示的名字 */
};
clk_virtual: clock-virtual@deadbeef {
compatible = "linux-expert,virtual-clock";
/*
* 'clocks' 属性是连接提供者和消费者的桥梁。
* <&osc_25m> 是一个 phandle,它是一个指向 'osc_25m' 节点的引用。
* 因为 osc_25m 节点的 #clock-cells = <0>,所以我们只需要 phandle,
* 后面不需要跟任何数字。
*/
clocks = <&osc_25m>;
/*
* 'clock-names' 属性为引用的每个时钟指定一个本地名称。
* 如果 'clocks' 属性中有多个时钟,这里也应该有对应数量的字符串。
* 驱动程序可以使用这个名字 ("bus_clk") 来获取特定的时钟。
*/
clock-names = "bus_clk";
};
时钟驱动
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/err.h>
/*
* 驱动的私有数据结构
* 我们需要一个成员来保存获取到的时钟的指针。
*/
struct virtual_spi_priv {
struct device *dev;
struct clk *bus_clk; // 用于保存时钟指针
// void __iomem *regs; // 真实驱动中会映射寄存器
};
static int virtual_clock_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct virtual_spi_priv *priv;
long rate;
int ret;
/* 1. 分配并初始化私有数据结构 */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
platform_set_drvdata(pdev, priv);
dev_info(dev, "Probing virtual SPI controller...\n");
/*
* 2. 获取时钟资源
*
* devm_clk_get() 是一个 "managed" 函数,它会自动处理资源的释放。
* - 第一个参数 'dev' 是设备指针。
* - 第二个参数 '"bus_clk"' 是我们在设备树 'clock-names' 属性中定义的名字。
* 如果传入 NULL,它会尝试获取 'clocks' 列表中的第一个时钟。
* 使用名字是更健壮的做法。
*/
priv->bus_clk = devm_clk_get(dev, "bus_clk");
if (IS_ERR(priv->bus_clk)) {
ret = PTR_ERR(priv->bus_clk);
if (ret == -ENOENT) {
dev_err(dev, "Clock 'bus_clk' not found in device tree\n");
} else {
dev_err(dev, "Failed to get 'bus_clk': %d\n", ret);
}
return ret;
}
/*
* 3. 准备并使能时钟 (Prepare and Enable)
* 这是使用时钟的标准流程。
* - clk_prepare(): 执行任何使能时钟前需要做的准备工作(如唤醒父时钟)。
* - clk_enable(): 真正打开时钟门控,让时钟信号流向设备。
* 对于fixed-clock,这些函数通常是空操作,但为了代码的通用性和健壮性,必须调用它们。
*
* 可以使用clk_prepare_enable()函数来同时完成prepare和enable操作
* clk_prepare_enable() = clk_prepare() + clk_enable()
* ret = clk_prepare_enable(priv->bus_clk);
*/
ret = clk_prepare(priv->bus_clk);
if (ret) {
dev_err(dev, "Failed to prepare 'bus_clk': %d\n", ret);
// devm_clk_get 会自动处理 clk_put,所以这里无需手动释放
return ret;
}
ret = clk_enable(priv->bus_clk);
if (ret) {
dev_err(dev, "Failed to enable 'bus_clk': %d\n", ret);
// devm_clk_get 会自动处理 clk_put,所以这里无需手动释放
return ret;
}
/* 4. 获取并打印时钟频率,以验证我们拿到了正确的时钟 */
rate = clk_get_rate(priv->bus_clk);
dev_info(dev, "Successfully enabled 'bus_clk', rate is %ld Hz\n", rate);
if (rate != 25000000) {
dev_warn(dev, "Warning: Expected 25MHz, but got %ldHz\n", rate);
}
// ... 在这里可以进行设备的其他初始化,比如映射寄存器、请求中断等 ...
return 0; // Probe 成功
}
static int virtual_clock_remove(struct platform_device *pdev)
{
struct virtual_spi_priv *priv = platform_get_drvdata(pdev);
dev_info(priv->dev, "Removing virtual SPI controller...\n");
/*
* 5. 在驱动卸载时,关闭并解除准备时钟
* clk_disable_unprepare() 是 clk_prepare_enable() 的逆操作。
* 因为我们使用了 devm_clk_get,内核会在驱动卸载时自动调用 clk_put,
* 但 disable/unprepare 仍然需要我们手动完成。
*/
clk_disable_unprepare(priv->bus_clk);
dev_info(priv->dev, "Clock 'bus_clk' disabled. Driver removed.\n");
return 0;
}
/* 匹配设备树中的 "compatible" 属性 */
static const struct of_device_id virtual_clock_of_match[] = {
{ .compatible = "linux-expert,virtual-clock" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, virtual_clock_of_match);
static struct platform_driver virtual_clock_driver = {
.driver = {
.name = "virtual-clock",
.of_match_table = virtual_clock_of_match,
},
.probe = virtual_clock_probe,
.remove = virtual_clock_remove,
};
module_platform_driver(virtual_clock_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A virtual SPI driver to demonstrate clock consumption");
测试方法
启动完内核后,并没有加载程序,查看时钟树
/ # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
可以看到是在设备树中定义的名称
xtal_25m 0 0 25000000 0 0
加载驱动,可以看到prepare_cnt和enable_cnt都是0
/ko # insmod virtual-fixed-clock.ko
virtual-clock clock-virtual@deadbeef: Probing virtual SPI controller...
virtual-clock clock-virtual@deadbeef: Successfully enabled 'bus_clk', rate is 25000000 Hz
可以看到加载完驱动时钟的prepare和enable计数都增加了
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------y
xtal_25m 1 1 25000000 0 0
也可以进入到下面的具体的目录中,查看时钟的信息
/sys/kernel/debug/clk # cd xtal_25m/
/sys/kernel/debug/clk/xtal_25m # ls
clk_accuracy clk_notifier_count clk_rate
clk_enable_count clk_phase
clk_flags clk_prepare_count
/sys/kernel/debug/clk/xtal_25m # cat clk_rate
25000000
/sys/kernel/debug/clk/xtal_25m # cat clk_prepare_count
1
2.固定时钟+时钟门控
设备树
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* 4KB内存用于模拟寄存器 */
syscon_mem: syscon-mem@10000000 {
reg = <0x70000000 0x400>;
no-map;
};
};
/* Clock Provider Node */
virtual_clocks: clock-provider@ff000000 {
compatible = "linux-expert,virtual-clk-provider-v2";
//参数为1,因此别的节点引用时要传递参数
#clock-cells = <1>;
//提供了两个时钟: user_xtal_24m 和 xtal_gate,在sys/kernel/debug/clk/clk_summary 可以进行查看
clock-output-names = "user_xtal_24m", "xtal_gate";
reg = <0x70000000 4>;
};
/* Clock Consumer Node */
virtual_spi: spi@ff010000 {
compatible = "linux-expert,virtual-clock";
clocks = <&virtual_clocks 1>; /* Points to the second clock (index 1), which is gated_spi_clk */
clock-names = "bus_clk";
};
时钟驱动:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
/*
* 私有数据结构。
* gate_reg 的类型必须是 void __iomem * 来存储映射后的虚拟地址。
*/
struct virtual_clk_priv {
void __iomem *gate_reg;
spinlock_t lock;
struct clk *xtal_clk;
struct clk *gate_clk;
struct clk_onecell_data *onecell_data;
};
static int virtual_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct virtual_clk_priv *priv;
const char *xtal_name, *gate_name;
struct resource *res;
int ret;
/*
* 使用 devm_kzalloc,它会自动管理内存,简化错误处理和 remove 函数
*/
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* 2. 从设备树读取时钟名称 */
ret = of_property_read_string_index(np, "clock-output-names", 0,
&xtal_name);
if (ret) {
dev_err(dev, "Failed to get user_xtal_24m name from DT\n");
return ret;
}
ret = of_property_read_string_index(np, "clock-output-names", 1,
&gate_name);
if (ret) {
dev_err(dev, "Failed to get xtal_gate name from DT\n");
return ret;
}
/* 获取并映射内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->gate_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(priv->gate_reg)) {
dev_err(dev, "Failed to map gate register\n");
return PTR_ERR(priv->gate_reg);
}
dev_info(dev, "Registering clocks: %s and %s\n", xtal_name, gate_name);
dev_info(dev, "Gate register mapped to virtual address: %p\n", priv->gate_reg);
/* 3. 注册固定频率时钟
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
*/
priv->xtal_clk = clk_register_fixed_rate(dev, xtal_name, NULL, CLK_IS_ROOT, 24000000);
if (IS_ERR(priv->xtal_clk)) {
dev_err(dev, "Failed to register fixed-rate clock %s\n",
xtal_name);
return PTR_ERR(priv->xtal_clk);
}
dev_info(dev, "Registered xtal_clk = %p\n", priv->xtal_clk);
/*
* 4. 注册门控时钟
* struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock);
struct device *dev:
值: dev
含义: 指向与此时钟关联的设备。通常是平台设备(platform_device)的 dev 成员。内核用它来进行日志记录、资源管理等。
const char *name:
值: gate_name (即 "xtal_gate")
含义: 要注册的新时钟的名称。这个名字会出现在 /sys/kernel/debug/clk/clk_summary 中。
const char *parent_name:
值: xtal_name (即 "user_xtal_24m")
含义: 父时钟的名称。CCF会根据这个名字去查找已经注册的父时钟,并建立父子关系。
unsigned long flags:
值: 0
含义: 通用时钟标志(定义在 linux/clk-provider.h)。例如 CLK_IS_ROOT、CLK_SET_RATE_PARENT 等。传 0 表示没有特殊的通用标志,这是最常见的情况。
void __iomem *reg:
值: &priv->gate_reg
含义: 这正是关键点。这个参数要求的是控制门控的硬件寄存器的地址。在真实的硬件驱动中,这会是一个通过 ioremap 映射过来的物理内存地址。在我们的虚拟驱动中,我们用私有结构体 virtual_clk_priv 中的 u32 gate_reg; 成员来模拟这个硬件寄存器。因此,&priv->gate_reg 就是我们这个模拟寄存器的内存地址。这个用法是完全正确的。
u8 bit_idx:
值: 0
含义: 在第5个参数 reg 所指向的寄存器中,控制门控的位的索引(bit index)。我们用第0位来控制时钟的开关。
u8 clk_gate_flags:
值: 0
含义: 门控时钟的特定标志。
传 0 等价于 CLK_GATE_SET_TO_DISABLE,意思是:当 bit_idx 位被设置为 0 时,时钟是关闭(gated)的;当该位被设置为 1 时,时钟是开启(ungated)的。这是最常见的硬件逻辑。
如果硬件逻辑是相反的(1表示关闭),则需要传递 CLK_GATE_INVERT 标志。
spinlock_t *lock:
值: &priv->lock
含义: 一个指向自旋锁的指针,用于在多核/多线程环境下保护对 reg 寄存器的并发访问。当使能/禁止时钟时,内核的 clk_gate_ops 会自动使用这个锁。
*/
priv->gate_clk = clk_register_gate(dev, gate_name, xtal_name, 0,
priv->gate_reg,
3, 0, &priv->lock);
if (IS_ERR(priv->gate_clk)) {
dev_err(dev, "Failed to register gate clock %s\n", gate_name);
ret = PTR_ERR(priv->gate_clk);
goto err_unreg_xtal;
}
dev_info(dev, "Registered gate_clk = %p\n", priv->gate_clk);
/* 5. 正确分配 clk_onecell_data 结构 */
priv->onecell_data = devm_kzalloc(dev, sizeof(*priv->onecell_data),
GFP_KERNEL);
if (!priv->onecell_data) {
ret = -ENOMEM;
goto err_unreg_gate;
}
/* 分配时钟数组 */
priv->onecell_data->clks = devm_kcalloc(dev, 2, sizeof(struct clk *),
GFP_KERNEL);
if (!priv->onecell_data->clks) {
ret = -ENOMEM;
goto err_unreg_gate;
}
priv->onecell_data->clk_num = 2;
priv->onecell_data->clks[0] = priv->xtal_clk;
priv->onecell_data->clks[1] = priv->gate_clk;
dev_info(dev, "onecell_data = %p, clks = %p\n",
priv->onecell_data, priv->onecell_data->clks);
/* 6. 注册时钟提供者 */
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
priv->onecell_data);
if (ret) {
dev_err(dev, "Failed to add clock provider\n");
goto err_unreg_gate;
}
dev_info(dev, "Virtual clock provider registered successfully\n");
return 0;
/* 错误处理路径 */
err_unreg_gate:
clk_unregister(priv->gate_clk);
err_unreg_xtal:
clk_unregister(priv->xtal_clk);
/* 使用了 devm_*,内存会自动清理 */
return ret;
}
static int virtual_clk_remove(struct platform_device *pdev)
{
struct virtual_clk_priv *priv = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
/* 因为使用了 devm_kzalloc,不需要手动 kfree */
clk_unregister(priv->gate_clk);
clk_unregister(priv->xtal_clk);
/* devm_* 管理的资源会自动释放 */
dev_info(&pdev->dev, "Virtual clock provider unregistered\n");
return 0;
}
static const struct of_device_id virtual_clk_of_match[] = {
{ .compatible = "linux-expert,virtual-clk-provider-v2", },
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, virtual_clk_of_match);
static struct platform_driver virtual_clk_driver = {
.driver = {
.name = "virtual-clk-provider",
.of_match_table = virtual_clk_of_match,
},
.probe = virtual_clk_probe,
.remove = virtual_clk_remove,
};
module_platform_driver(virtual_clk_driver);
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A virtual clock provider driver (FIXED for 4.4 compatible)");
MODULE_LICENSE("GPL");
测试代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
/*
* 私有数据结构。
* gate_reg 的类型必须是 void __iomem * 来存储映射后的虚拟地址。
*/
struct virtual_clk_priv {
void __iomem *gate_reg;
spinlock_t lock;
struct clk *xtal_clk;
struct clk *gate_clk;
struct clk_onecell_data *onecell_data;
};
static int virtual_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct virtual_clk_priv *priv;
const char *xtal_name, *gate_name;
struct resource *res;
int ret;
/*
* 使用 devm_kzalloc,它会自动管理内存,简化错误处理和 remove 函数
*/
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* 2. 从设备树读取时钟名称 */
ret = of_property_read_string_index(np, "clock-output-names", 0,
&xtal_name);
if (ret) {
dev_err(dev, "Failed to get user_xtal_24m name from DT\n");
return ret;
}
ret = of_property_read_string_index(np, "clock-output-names", 1,
&gate_name);
if (ret) {
dev_err(dev, "Failed to get xtal_gate name from DT\n");
return ret;
}
/* 获取并映射内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->gate_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(priv->gate_reg)) {
dev_err(dev, "Failed to map gate register\n");
return PTR_ERR(priv->gate_reg);
}
dev_info(dev, "Registering clocks: %s and %s\n", xtal_name, gate_name);
dev_info(dev, "Gate register mapped to virtual address: %p\n", priv->gate_reg);
/* 3. 注册固定频率时钟
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
*/
priv->xtal_clk = clk_register_fixed_rate(dev, xtal_name, NULL, CLK_IS_ROOT, 24000000);
if (IS_ERR(priv->xtal_clk)) {
dev_err(dev, "Failed to register fixed-rate clock %s\n",
xtal_name);
return PTR_ERR(priv->xtal_clk);
}
dev_info(dev, "Registered xtal_clk = %p\n", priv->xtal_clk);
/*
* 4. 注册门控时钟
* struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock);
struct device *dev:
值: dev
含义: 指向与此时钟关联的设备。通常是平台设备(platform_device)的 dev 成员。内核用它来进行日志记录、资源管理等。
const char *name:
值: gate_name (即 "xtal_gate")
含义: 要注册的新时钟的名称。这个名字会出现在 /sys/kernel/debug/clk/clk_summary 中。
const char *parent_name:
值: xtal_name (即 "user_xtal_24m")
含义: 父时钟的名称。CCF会根据这个名字去查找已经注册的父时钟,并建立父子关系。
unsigned long flags:
值: 0
含义: 通用时钟标志(定义在 linux/clk-provider.h)。例如 CLK_IS_ROOT、CLK_SET_RATE_PARENT 等。传 0 表示没有特殊的通用标志,这是最常见的情况。
void __iomem *reg:
值: &priv->gate_reg
含义: 这正是关键点。这个参数要求的是控制门控的硬件寄存器的地址。在真实的硬件驱动中,这会是一个通过 ioremap 映射过来的物理内存地址。在我们的虚拟驱动中,我们用私有结构体 virtual_clk_priv 中的 u32 gate_reg; 成员来模拟这个硬件寄存器。因此,&priv->gate_reg 就是我们这个模拟寄存器的内存地址。这个用法是完全正确的。
u8 bit_idx:
值: 0
含义: 在第5个参数 reg 所指向的寄存器中,控制门控的位的索引(bit index)。我们用第0位来控制时钟的开关。
u8 clk_gate_flags:
值: 0
含义: 门控时钟的特定标志。
传 0 等价于 CLK_GATE_SET_TO_DISABLE,意思是:当 bit_idx 位被设置为 0 时,时钟是关闭(gated)的;当该位被设置为 1 时,时钟是开启(ungated)的。这是最常见的硬件逻辑。
如果硬件逻辑是相反的(1表示关闭),则需要传递 CLK_GATE_INVERT 标志。
spinlock_t *lock:
值: &priv->lock
含义: 一个指向自旋锁的指针,用于在多核/多线程环境下保护对 reg 寄存器的并发访问。当使能/禁止时钟时,内核的 clk_gate_ops 会自动使用这个锁。
*/
priv->gate_clk = clk_register_gate(dev, gate_name, xtal_name, 0,
priv->gate_reg,
3, 0, &priv->lock);
if (IS_ERR(priv->gate_clk)) {
dev_err(dev, "Failed to register gate clock %s\n", gate_name);
ret = PTR_ERR(priv->gate_clk);
goto err_unreg_xtal;
}
dev_info(dev, "Registered gate_clk = %p\n", priv->gate_clk);
/* 5. 正确分配 clk_onecell_data 结构 */
priv->onecell_data = devm_kzalloc(dev, sizeof(*priv->onecell_data),
GFP_KERNEL);
if (!priv->onecell_data) {
ret = -ENOMEM;
goto err_unreg_gate;
}
/* 分配时钟数组 */
priv->onecell_data->clks = devm_kcalloc(dev, 2, sizeof(struct clk *),
GFP_KERNEL);
if (!priv->onecell_data->clks) {
ret = -ENOMEM;
goto err_unreg_gate;
}
priv->onecell_data->clk_num = 2;
priv->onecell_data->clks[0] = priv->xtal_clk;
priv->onecell_data->clks[1] = priv->gate_clk;
dev_info(dev, "onecell_data = %p, clks = %p\n",
priv->onecell_data, priv->onecell_data->clks);
/* 6. 注册时钟提供者 */
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
priv->onecell_data);
if (ret) {
dev_err(dev, "Failed to add clock provider\n");
goto err_unreg_gate;
}
dev_info(dev, "Virtual clock provider registered successfully\n");
return 0;
/* 错误处理路径 */
err_unreg_gate:
clk_unregister(priv->gate_clk);
err_unreg_xtal:
clk_unregister(priv->xtal_clk);
/* 使用了 devm_*,内存会自动清理 */
return ret;
}
static int virtual_clk_remove(struct platform_device *pdev)
{
struct virtual_clk_priv *priv = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
/* 因为使用了 devm_kzalloc,不需要手动 kfree */
clk_unregister(priv->gate_clk);
clk_unregister(priv->xtal_clk);
/* devm_* 管理的资源会自动释放 */
dev_info(&pdev->dev, "Virtual clock provider unregistered\n");
return 0;
}
static const struct of_device_id virtual_clk_of_match[] = {
{ .compatible = "linux-expert,virtual-clk-provider-v2", },
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, virtual_clk_of_match);
static struct platform_driver virtual_clk_driver = {
.driver = {
.name = "virtual-clk-provider",
.of_match_table = virtual_clk_of_match,
},
.probe = virtual_clk_probe,
.remove = virtual_clk_remove,
};
module_platform_driver(virtual_clk_driver);
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A virtual clock provider driver (FIXED for 4.4 compatible)");
MODULE_LICENSE("GPL");
测试步骤:
/ko # insmod virtual-fixed-gate-clock.ko
virtual-clk-provider 70000000.clock-provider: Registering clocks: user_xtal_24m and xtal_gate
virtual-clk-provider 70000000.clock-provider: Gate register mapped to virtual address: c08ec000
virtual-clk-provider 70000000.clock-provider: Registered xtal_clk = bb9b3e40
virtual-clk-provider 70000000.clock-provider: Registered gate_clk = bb9b3f80
virtual-clk-provider 70000000.clock-provider: onecell_data = bb9b3fd0, clks = bc03bf90
virtual-clk-provider 70000000.clock-provider: Virtual clock provider registered successfully
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 0 0 24000000 0 0
xtal_gate 0 0 24000000 0 0
/ko # devmem 0x70000000 32
0x00000000
/ko # insmod virtual-fixed-gate-clock-consumer.ko
virtual-clock spi@ff010000: Probing virtual SPI controller...
virtual-clock spi@ff010000: Successfully enabled 'bus_clk', rate is 24000000 Hz
virtual-clock spi@ff010000: Virtual SPI controller is now active and clocked.
可以看到不仅仅xtal_gate它被使能,连同user_xtal_24m也被使能了
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 1 1 24000000 0 0
xtal_gate 1 1 24000000 0 0
可以看到0x70000000的第3bit被设置为1,说明xtal_gate被使能了
/ko # devmem 0x70000000 32
0x00000008
3.固定时钟+时钟门控+分频
设备树
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* 4KB内存用于模拟寄存器 */
syscon_mem: syscon-mem@10000000 {
reg = <0x70000000 0x400>;
no-map;
};
};
/* Clock Provider Node - V3 */
virtual_clocks: clock-provider@ff000000 {
compatible = "virtual-clk-provider";
reg = <0x70000000 0x400>;
#clock-cells = <1>;
clock-output-names = "user_xtal_24m", "xtal_gate","xtal_div";
};
/* Timer Consumer Node */
virtual_timer: timer@ff020000 {
compatible = "virtual-clk-consumer";
clocks = <&virtual_clocks 2>;
clock-names = "timer_src";
};
clk时钟代码
/*
* @Author: your name
* @Date: 2025-10-15 14:30:41
* @LastEditTime: 2025-10-15 14:36:57
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.4.159\drivers\gpu\drm\test\virtual-fixed-gate-div.c
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
/*
* 私有数据结构
* 增加一个 xtal_div 的 clk 指针
* 将 reg_base 拆分为 gate_reg 和 div_reg,以模拟不同的寄存器偏移
*/
struct virtual_clk_priv {
void __iomem *gate_reg;
void __iomem *div_reg;
spinlock_t lock; // 门控和分频器可以共享一个锁
/* 保存所有注册的时钟指针,以便注销 */
struct clk *xtal_clk;
struct clk *gate_clk;
struct clk *div_clk;
/* 用于 of_clk_add_provider 的数据 */
struct clk_onecell_data *onecell_data;
};
static int virtual_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct virtual_clk_priv *priv;
const char *xtal_name, *gate_name, *div_name;
void __iomem *reg_base;
struct resource *res;
int ret;
/* 使用 devm_kzalloc,它会自动管理内存 */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* 1. 获取并映射内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
reg_base = devm_ioremap_resource(dev, res);
if (IS_ERR(reg_base)) {
dev_err(dev, "Failed to map register space\n");
return PTR_ERR(reg_base);
}
/* 假设门控寄存器在基地址偏移0处,分频寄存器在偏移4处 */
priv->gate_reg = reg_base + 0x0;
priv->div_reg = reg_base + 0x4;
/* 2. 从设备树读取所有时钟名称 */
ret = of_property_read_string_index(np, "clock-output-names", 0,
&xtal_name);
if (ret) {
dev_err(dev, "Failed to get 'user_xtal_24m' name from DT\n");
return ret;
}
ret = of_property_read_string_index(np, "clock-output-names", 1,
&gate_name);
if (ret) {
dev_err(dev, "Failed to get 'xtal_gate' name from DT\n");
return ret;
}
ret = of_property_read_string_index(np, "clock-output-names", 2,
&div_name);
if (ret) {
dev_err(dev, "Failed to get 'xtal_div' name from DT\n");
return ret;
}
dev_info(dev, "Registering clocks: %s, %s, and %s\n", xtal_name,
gate_name, div_name);
dev_info(dev, "Gate reg: %p, Div reg: %p\n", priv->gate_reg,
priv->div_reg);
/* 3. 注册固定频率时钟 (user_xtal_24m) - 作为父时钟 */
priv->xtal_clk =
clk_register_fixed_rate(dev, xtal_name, NULL, CLK_IS_ROOT, 24000000);
if (IS_ERR(priv->xtal_clk)) {
dev_err(dev, "Failed to register fixed-rate clock '%s'\n",
xtal_name);
return PTR_ERR(priv->xtal_clk);
}
/* 4. 注册门控时钟 (xtal_gate) */
priv->gate_clk = clk_register_gate(dev, gate_name, xtal_name, 0,
priv->gate_reg, 0, 0, &priv->lock);
if (IS_ERR(priv->gate_clk)) {
dev_err(dev, "Failed to register gate clock '%s'\n", gate_name);
ret = PTR_ERR(priv->gate_clk);
goto err_unreg_xtal;
}
/* 5. 注册分频时钟 (xtal_div) */
priv->div_clk = clk_register_divider(
dev, div_name, xtal_name, 0, priv->div_reg,
0, /* shift: 分频值在寄存器中的起始位 */
8, /* width: 分频值占用的位数,8位可以表示1-256的分频 */
0, /* flags: 如 CLK_DIVIDER_POWER_OF_TWO */
&priv->lock);
if (IS_ERR(priv->div_clk)) {
dev_err(dev, "Failed to register divider clock '%s'\n", div_name);
ret = PTR_ERR(priv->div_clk);
goto err_unreg_gate;
}
/* 6. 准备并注册时钟提供者 (one-cell) */
priv->onecell_data =
devm_kzalloc(dev, sizeof(*priv->onecell_data), GFP_KERNEL);
if (!priv->onecell_data) {
ret = -ENOMEM;
goto err_unreg_div;
}
/* 分配时钟数组,现在需要3个元素 */
priv->onecell_data->clks =
devm_kcalloc(dev, 3, sizeof(struct clk *), GFP_KERNEL);
if (!priv->onecell_data->clks) {
ret = -ENOMEM;
goto err_unreg_div;
}
priv->onecell_data->clk_num = 3;
priv->onecell_data->clks[0] = priv->xtal_clk;
priv->onecell_data->clks[1] = priv->gate_clk;
priv->onecell_data->clks[2] = priv->div_clk;
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
priv->onecell_data);
if (ret) {
dev_err(dev, "Failed to add clock provider\n");
goto err_unreg_div;
}
dev_info(dev, "Virtual clock provider registered successfully\n");
return 0;
/* 错误处理路径 */
err_unreg_div:
clk_unregister(priv->div_clk);
err_unreg_gate:
clk_unregister(priv->gate_clk);
err_unreg_xtal:
clk_unregister(priv->xtal_clk);
/* 使用了 devm_*,内存会自动清理,无需额外操作 */
return ret;
}
static int virtual_clk_remove(struct platform_device *pdev)
{
struct virtual_clk_priv *priv = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
/* 按照注册的反向顺序注销时钟 */
clk_unregister(priv->div_clk);
clk_unregister(priv->gate_clk);
clk_unregister(priv->xtal_clk);
/* devm_* 管理的资源会自动释放 */
dev_info(&pdev->dev, "Virtual clock provider unregistered\n");
return 0;
}
static const struct of_device_id virtual_clk_of_match[] = {
{
.compatible = "virtual-clk-provider",
},
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, virtual_clk_of_match);
static struct platform_driver virtual_clk_driver = {
.driver =
{
.name = "virtual-clk-provider",
.of_match_table = virtual_clk_of_match,
},
.probe = virtual_clk_probe,
.remove = virtual_clk_remove,
};
module_platform_driver(virtual_clk_driver);
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A virtual clock provider with fixed, gate, and divider clocks");
MODULE_LICENSE("GPL");
测试程序
/*
* @Author: your name
* @Date: 2025-10-15 14:41:07
* @LastEditTime: 2025-10-15 14:45:22
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \project\technology\sw\linux_drivers\内核时钟系统\src\virtual-fixed-gate-div-consumer.c
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/err.h>
struct virtual_timer_priv {
struct clk *clk;
};
static int virtual_timer_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct virtual_timer_priv *priv;
long rate, actual_rate, target_rate;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
platform_set_drvdata(pdev, priv);
dev_info(dev, "Probing virtual timer...\n");
priv->clk = devm_clk_get(dev, "timer_src");
if (IS_ERR(priv->clk)) {
dev_err(dev, "Failed to get clock 'timer_src': %ld\n", PTR_ERR(priv->clk));
return PTR_ERR(priv->clk);
}
ret = clk_prepare_enable(priv->clk);
if (ret) {
dev_err(dev, "Failed to enable clock: %d\n", ret);
return ret;
}
/* --- Test 1: 获取默认速率 --- */
rate = clk_get_rate(priv->clk);
dev_info(dev, "Default clock rate is %ld Hz\n", rate);
// 预期:24000000 Hz (因为我们初始化分频寄存器为0,即1分频)
/* --- Test 2: 设置一个新速率 --- */
target_rate = 6000000; // 目标 6 MHz,目标分频频率 = 频率 / (分频值 + 1)
// target_rate = 48000000; // 目标 48 MHz,此处会设置不成功,该处是分频器而不是倍频器
dev_info(dev, "Attempting to set rate to %ld Hz...\n", target_rate);
ret = clk_set_rate(priv->clk, target_rate);
if (ret) {
dev_err(dev, "Failed to set rate: %d\n", ret);
}
/* --- Test 3: 验证实际速率 --- */
actual_rate = clk_get_rate(priv->clk);
dev_info(dev, "After setting, actual clock rate is %ld Hz\n", actual_rate);
// 预期:6000000 Hz (因为 24MHz / 4 = 6MHz, 内核会计算出分频值应为4,并写入寄存器3)
dev_info(dev, "Virtual timer probe finished.\n");
return 0;
}
static int virtual_timer_remove(struct platform_device *pdev)
{
struct virtual_timer_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->clk);
dev_info(&pdev->dev, "Virtual timer removed.\n");
return 0;
}
static const struct of_device_id virtual_timer_of_match[] = {
{ .compatible = "virtual-clk-consumer" }, { }
};
MODULE_DEVICE_TABLE(of, virtual_timer_of_match);
static struct platform_driver virtual_timer_driver = {
.driver = {
.name = "virtual-clk-consumer",
.of_match_table = virtual_timer_of_match
},
.probe = virtual_timer_probe,
.remove = virtual_timer_remove,
};
module_platform_driver(virtual_timer_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A demo driver to test a variable-rate clock.");
日志
/ko # insmod virtual-fixed-gate-div.ko
virtual-clk-provider 70000000.clock-provider: Registering clocks: user_xtal_24m, xtal_gate, and xtal_div
virtual-clk-provider 70000000.clock-provider: Gate reg: c08ec000, Div reg: c08ec004
virtual-clk-provider 70000000.clock-provider: Virtual clock provider registered successfully
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 0 0 24000000 0 0
xtal_div 0 0 24000000 0 0
xtal_gate 0 0 24000000 0 0
/ko # insmod virtual-fixed-gate-div-consumer.ko
virtual-clk-consumer timer@ff020000: Probing virtual timer...
virtual-clk-consumer timer@ff020000: Default clock rate is 24000000 Hz
virtual-clk-consumer timer@ff020000: Attempting to set rate to 6000000 Hz...
virtual-clk-consumer timer@ff020000: After setting, actual clock rate is 6000000 Hz
virtual-clk-consumer timer@ff020000: Virtual timer probe finished.
//可以看到xtal_div的rate变成了6000000
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 1 1 24000000 0 0
xtal_div 1 1 6000000 0 0
xtal_gate 0 0 24000000 0 0
//可以看到寄存器的值变成了3
/ko # devmem 0x70000004 32
0x00000003
4.固定时钟+时钟门控+分频+倍频
设备树
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* 4KB内存用于模拟寄存器 */
syscon_mem: syscon-mem@10000000 {
reg = <0x70000000 0x400>;
no-map;
};
};
virtual_clocks: clock-provider@ff000000 {
compatible = "virtual-clk-provider";
reg = <0x70000000 0x400>;
#clock-cells = <1>;
clock-output-names = "user_xtal_24m", "xtal_gate","xtal_div","xtal_mult";
};
virtual_timer: timer@ff020000 {
compatible = "virtual-clk-consumer";
clocks = <&virtual_clocks 3>;
clock-names = "timer_src";
};
时钟代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk/clk-conf.h> /* 包含 clk-multiplier.h (间接) */
/*
* 私有数据结构
* 对于倍频时钟,我们需要嵌入完整的 clk_multiplier 结构,
* 因为没有高层辅助函数可用。
*/
struct virtual_clk_priv {
void __iomem *gate_reg;
void __iomem *div_reg;
spinlock_t lock;
struct clk_multiplier mult; // 手动定义的倍频时钟硬件描述
/* 保存所有注册的时钟指针,以便注销 */
struct clk *xtal_clk;
struct clk *gate_clk;
struct clk *div_clk;
struct clk *mult_clk;
/* 用于 of_clk_add_provider 的数据 */
struct clk_onecell_data *onecell_data;
};
static int virtual_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct virtual_clk_priv *priv;
const char *xtal_name, *gate_name, *div_name, *mult_name;
void __iomem *reg_base;
struct resource *res;
struct clk_init_data init; // 用于手动注册时钟
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* 1. 获取并映射内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
reg_base = devm_ioremap_resource(dev, res);
if (IS_ERR(reg_base)) {
dev_err(dev, "Failed to map register space\n");
return PTR_ERR(reg_base);
}
priv->gate_reg = reg_base + 0x0;
priv->div_reg = reg_base + 0x4;
/* 将倍频寄存器地址直接赋给 clk_multiplier 结构 */
priv->mult.reg = reg_base + 0x8;
/* 2. 从设备树读取所有时钟名称 */
of_property_read_string_index(np, "clock-output-names", 0, &xtal_name);
of_property_read_string_index(np, "clock-output-names", 1, &gate_name);
of_property_read_string_index(np, "clock-output-names", 2, &div_name);
ret = of_property_read_string_index(np, "clock-output-names", 3,
&mult_name);
if (ret) {
dev_err(dev, "Failed to get 'xtal_mult' name from DT\n");
return ret;
}
dev_info(dev, "Registering clocks: %s, %s, %s, and %s\n", xtal_name,
gate_name, div_name, mult_name);
/* 3. 注册固定频率时钟 (user_xtal_24m) */
priv->xtal_clk =
clk_register_fixed_rate(dev, xtal_name, NULL, CLK_IS_ROOT, 24000000);
if (IS_ERR(priv->xtal_clk)) {
return PTR_ERR(priv->xtal_clk);
}
/* 4. 注册门控时钟 (xtal_gate) */
priv->gate_clk = clk_register_gate(dev, gate_name, xtal_name, 0,
priv->gate_reg, 0, 0, &priv->lock);
if (IS_ERR(priv->gate_clk)) {
ret = PTR_ERR(priv->gate_clk);
goto err_unreg_xtal;
}
/* 5. 注册分频时钟 (xtal_div) */
priv->div_clk = clk_register_divider(
dev, div_name, xtal_name, 0, priv->div_reg, 0, 8, 0, &priv->lock);
if (IS_ERR(priv->div_clk)) {
ret = PTR_ERR(priv->div_clk);
goto err_unreg_gate;
}
/* 6. 手动注册倍频时钟 (xtal_mult) */
/* 6.1 填充 clk_init_data */
init.name = mult_name;
init.ops = &clk_multiplier_ops; /* <-- 使用内核提供的标准倍频器ops */
init.flags = 0;
init.parent_names = &xtal_name;
init.num_parents = 1;
/* 6.2 填充 clk_multiplier 特有字段 */
priv->mult.shift = 0;
priv->mult.width = 8;
priv->mult.flags = 0;
priv->mult.lock = &priv->lock;
priv->mult.hw.init = &init;
/* 6.3 使用通用的 clk_register 进行注册 */
priv->mult_clk = clk_register(dev, &priv->mult.hw);
if (IS_ERR(priv->mult_clk)) {
ret = PTR_ERR(priv->mult_clk);
goto err_unreg_div;
}
/* 7. 准备并注册时钟提供者 (one-cell) */
priv->onecell_data =
devm_kzalloc(dev, sizeof(*priv->onecell_data), GFP_KERNEL);
if (!priv->onecell_data) {
ret = -ENOMEM;
goto err_unreg_mult;
}
priv->onecell_data->clks =
devm_kcalloc(dev, 4, sizeof(struct clk *), GFP_KERNEL);
if (!priv->onecell_data->clks) {
ret = -ENOMEM;
goto err_unreg_mult;
}
priv->onecell_data->clk_num = 4;
priv->onecell_data->clks[0] = priv->xtal_clk;
priv->onecell_data->clks[1] = priv->gate_clk;
priv->onecell_data->clks[2] = priv->div_clk;
priv->onecell_data->clks[3] = priv->mult_clk;
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
priv->onecell_data);
if (ret) {
dev_err(dev, "Failed to add clock provider\n");
goto err_unreg_mult;
}
dev_info(dev, "Virtual clock provider registered successfully\n");
return 0;
/* 错误处理路径 */
err_unreg_mult:
clk_unregister(priv->mult_clk);
err_unreg_div:
clk_unregister(priv->div_clk);
err_unreg_gate:
clk_unregister(priv->gate_clk);
err_unreg_xtal:
clk_unregister(priv->xtal_clk);
return ret;
}
static int virtual_clk_remove(struct platform_device *pdev)
{
struct virtual_clk_priv *priv = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
clk_unregister(priv->mult_clk);
clk_unregister(priv->div_clk);
clk_unregister(priv->gate_clk);
clk_unregister(priv->xtal_clk);
dev_info(&pdev->dev, "Virtual clock provider unregistered\n");
return 0;
}
static const struct of_device_id virtual_clk_of_match[] = {
{
.compatible = "virtual-clk-provider",
},
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, virtual_clk_of_match);
static struct platform_driver virtual_clk_driver = {
.driver =
{
.name = "virtual-clk-provider",
.of_match_table = virtual_clk_of_match,
},
.probe = virtual_clk_probe,
.remove = virtual_clk_remove,
};
module_platform_driver(virtual_clk_driver);
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A 4.4-compatible clock provider with fixed, gate, divider, and multiplier");
MODULE_LICENSE("GPL");
测试代码
/*
* @Author: your name
* @Date: 2025-10-15 14:44:51
* @LastEditTime: 2025-10-15 14:55:42
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.4.159\drivers\gpu\drm\test\virtual-fixed-gate-mul-consumer.c
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/err.h>
struct virtual_timer_priv {
struct clk *clk;
};
static int virtual_timer_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct virtual_timer_priv *priv;
long rate, actual_rate, target_rate;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
platform_set_drvdata(pdev, priv);
dev_info(dev, "Probing virtual timer...\n");
priv->clk = devm_clk_get(dev, "timer_src");
if (IS_ERR(priv->clk)) {
dev_err(dev, "Failed to get clock 'timer_src': %ld\n", PTR_ERR(priv->clk));
return PTR_ERR(priv->clk);
}
ret = clk_prepare_enable(priv->clk);
if (ret) {
dev_err(dev, "Failed to enable clock: %d\n", ret);
return ret;
}
/* --- Test 1: 获取默认速率 --- */
rate = clk_get_rate(priv->clk);
dev_info(dev, "Default clock rate is %ld Hz\n", rate);
// 预期:24000000 Hz (因为我们初始化分频寄存器为0,即1分频)
/* --- Test 2: 设置一个新速率 --- */
target_rate = 48000000; // 目标 48 MHz,此处会设置不成功,该处是分频器而不是倍频器
dev_info(dev, "Attempting to set rate to %ld Hz...\n", target_rate);
ret = clk_set_rate(priv->clk, target_rate);
if (ret) {
dev_err(dev, "Failed to set rate: %d\n", ret);
}
/* --- Test 3: 验证实际速率 --- */
actual_rate = clk_get_rate(priv->clk);
dev_info(dev, "After setting, actual clock rate is %ld Hz\n", actual_rate);
// 预期:6000000 Hz (因为 24MHz / 4 = 6MHz, 内核会计算出分频值应为4,并写入寄存器3)
dev_info(dev, "Virtual timer probe finished.\n");
return 0;
}
static int virtual_timer_remove(struct platform_device *pdev)
{
struct virtual_timer_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->clk);
dev_info(&pdev->dev, "Virtual timer removed.\n");
return 0;
}
static const struct of_device_id virtual_timer_of_match[] = {
{ .compatible = "virtual-clk-consumer" }, { }
};
MODULE_DEVICE_TABLE(of, virtual_timer_of_match);
static struct platform_driver virtual_timer_driver = {
.driver = {
.name = "virtual-clk-consumer",
.of_match_table = virtual_timer_of_match
},
.probe = virtual_timer_probe,
.remove = virtual_timer_remove,
};
module_platform_driver(virtual_timer_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A demo driver to test a variable-rate clock.");
测试文档
ko # insmod virtual-fixed-gate-mul.ko
virtual-clk-provider 70000000.clock-provider: Registering clocks: user_xtal_24m, xtal_gate, xtal_div, and xtal_mult
virtual-clk-provider 70000000.clock-provider: Virtual clock provider registered successfully
/ko # cat /sys/kernel/debug/clk/clk_summary
//可以看到下面默认的xtal_mult为0,原因是由于相对应的寄存器中数据为0,如果不想为0,可以提前设置相应寄存器中的值
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 0 0 24000000 0 0
xtal_mult 0 0 0 0 0
xtal_div 0 0 24000000 0 0
xtal_gate 0 0 24000000 0 0
/ko # insmod virtual-fixed-gate-mul-consumer.ko
virtual-clk-consumer timer@ff020000: Probing virtual timer...
virtual-clk-consumer timer@ff020000: Default clock rate is 0 Hz
virtual-clk-consumer timer@ff020000: Attempting to set rate to 48000000 Hz...
virtual-clk-consumer timer@ff020000: After setting, actual clock rate is 48000000 Hz
virtual-clk-consumer timer@ff020000: Virtual timer probe finished.
可以看到倍频器频率发生了变化
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 1 1 24000000 0 0
xtal_mult 1 1 48000000 0 0
xtal_div 0 0 24000000 0 0
xtal_gate 0 0 24000000 0 0
查看相应倍频器寄存器的值
/ko # devmem 0x70000008 32
0x00000002
5.固定时钟+时钟门控+分频+倍频+MUX
设备树
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* 4KB内存用于模拟寄存器 */
syscon_mem: syscon-mem@10000000 {
reg = <0x70000000 0x400>;
no-map;
};
};
virtual_clocks: clock-provider@ff000000 {
compatible = "virtual-clk-provider";
reg = <0x70000000 0x400>;
#clock-cells = <1>;
clock-output-names = "user_xtal_24m", "xtal_gate","xtal_div","xtal_mult","xtal_mux";
};
virtual_timer: timer@ff020000 {
compatible = "virtual-clk-consumer";
clocks = <&virtual_clocks 4>, /* Mux clock */
<&virtual_clocks 2>, /* div clock source */
<&virtual_clocks 3>; /* mult clock source */
/* Give a name to each clock handle */
clock-names = "timer_src", /* Name for the mux */
"parent_src_div", /* Name for the div parent */
"parent_src_mult"; /* Name for the mult parent */
};
时钟代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk/clk-conf.h> /* 包含 clk-multiplier.h (间接) */
#define DEFAULT_MULTIPLIER_VAL 1
#define DEFAULT_DIVIDER_VAL 1
/*
* 私有数据结构
* 增加 mux 相关的时钟指针和寄存器指针
*/
struct virtual_clk_priv {
void __iomem *gate_reg;
void __iomem *div_reg;
void __iomem *mux_reg;
spinlock_t lock;
struct clk_multiplier mult;
/* 保存所有注册的时钟指针 */
struct clk *xtal_clk;
struct clk *gate_clk;
struct clk *div_clk;
struct clk *mult_clk;
struct clk *mux_clk;
/* 用于 of_clk_add_provider 的数据 */
struct clk_onecell_data *onecell_data;
};
static int virtual_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct virtual_clk_priv *priv;
const char *xtal_name, *gate_name, *div_name, *mult_name, *mux_name;
const char *mux_parent_names[2]; /* MUX 有两个父时钟 */
void __iomem *reg_base;
struct resource *res;
struct clk_init_data init;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* 1. 获取并映射内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
reg_base = devm_ioremap_resource(dev, res);
if (IS_ERR(reg_base)) {
dev_err(dev, "Failed to map register space\n");
return PTR_ERR(reg_base);
}
priv->gate_reg = reg_base + 0x0;
priv->div_reg = reg_base + 0x4;
priv->mult.reg = reg_base + 0x8;
priv->mux_reg = reg_base + 0xC; /* 为MUX分配新的寄存器空间 */
/*设置硬件寄存器的初始值 */
spin_lock(&priv->lock);
writel(DEFAULT_DIVIDER_VAL - 1, priv->div_reg);
writel(DEFAULT_MULTIPLIER_VAL, priv->mult.reg);
writel(0, priv->mux_reg); /* 默认选择第0个父时钟 (xtal_div) */
spin_unlock(&priv->lock);
/* 2. 从设备树读取所有时钟名称 */
of_property_read_string_index(np, "clock-output-names", 0, &xtal_name);
of_property_read_string_index(np, "clock-output-names", 1, &gate_name);
of_property_read_string_index(np, "clock-output-names", 2, &div_name);
of_property_read_string_index(np, "clock-output-names", 3, &mult_name);
ret = of_property_read_string_index(np, "clock-output-names", 4,
&mux_name);
if (ret) {
dev_err(dev, "Failed to get 'xtal_mux' name from DT\n");
return ret;
}
dev_info(dev, "Registering all 5 clocks...\n");
/* 3. 注册固定频率时钟 (user_xtal_24m) */
priv->xtal_clk =
clk_register_fixed_rate(dev, xtal_name, NULL, CLK_IS_ROOT, 24000000);
if (IS_ERR(priv->xtal_clk)) {
return PTR_ERR(priv->xtal_clk);
}
/* 4. 注册门控时钟 (xtal_gate) */
priv->gate_clk = clk_register_gate(dev, gate_name, xtal_name, 0,
priv->gate_reg, 0, 0, &priv->lock);
if (IS_ERR(priv->gate_clk)) {
ret = PTR_ERR(priv->gate_clk);
goto err_unreg_xtal;
}
/* 5. 注册分频时钟 (xtal_div) */
priv->div_clk = clk_register_divider(
dev, div_name, xtal_name, 0, priv->div_reg, 0, 8, 0, &priv->lock);
if (IS_ERR(priv->div_clk)) {
ret = PTR_ERR(priv->div_clk);
goto err_unreg_gate;
}
/* 6. 手动注册倍频时钟 (xtal_mult) */
init.name = mult_name;
init.ops = &clk_multiplier_ops;
init.flags = 0;
init.parent_names = &xtal_name;
init.num_parents = 1;
priv->mult.shift = 0;
priv->mult.width = 8;
priv->mult.flags = 0;
priv->mult.lock = &priv->lock;
priv->mult.hw.init = &init;
priv->mult_clk = clk_register(dev, &priv->mult.hw);
if (IS_ERR(priv->mult_clk)) {
ret = PTR_ERR(priv->mult_clk);
goto err_unreg_div;
}
/*
* +++ 新增部分:注册MUX时钟 +++
* 7. 注册 MUX 时钟 (xtal_mux)
*/
mux_parent_names[0] = div_name; /* 父时钟1: 分频器 */
mux_parent_names[1] = mult_name; /* 父时钟2: 倍频器 */
priv->mux_clk = clk_register_mux(
dev, mux_name, mux_parent_names, 2, /* 父时钟名字数组和数量 */
CLK_SET_RATE_NO_REPARENT, /* flags */
priv->mux_reg, /* MUX选择寄存器地址 */
0, /* shift: 选择位在寄存器中的偏移 */
1, /* width: 1位可以表示2个选项 */
0, /* mux_flags */
&priv->lock);
if (IS_ERR(priv->mux_clk)) {
ret = PTR_ERR(priv->mux_clk);
goto err_unreg_mult;
}
/* 9. 准备并注册时钟提供者 (one-cell) */
priv->onecell_data =
devm_kzalloc(dev, sizeof(*priv->onecell_data), GFP_KERNEL);
if (!priv->onecell_data) {
ret = -ENOMEM;
goto err_unreg_mux;
}
priv->onecell_data->clks =
devm_kcalloc(dev, 5, sizeof(struct clk *), GFP_KERNEL);
if (!priv->onecell_data->clks) {
ret = -ENOMEM;
goto err_unreg_mux;
}
priv->onecell_data->clk_num = 5;
priv->onecell_data->clks[0] = priv->xtal_clk;
priv->onecell_data->clks[1] = priv->gate_clk;
priv->onecell_data->clks[2] = priv->div_clk;
priv->onecell_data->clks[3] = priv->mult_clk;
priv->onecell_data->clks[4] = priv->mux_clk;
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
priv->onecell_data);
if (ret) {
dev_err(dev, "Failed to add clock provider\n");
goto err_unreg_mux;
}
dev_info(dev, "Virtual clock provider registered successfully\n");
return 0;
/* 错误处理路径 */
err_unreg_mux:
clk_unregister(priv->mux_clk);
err_unreg_mult:
clk_unregister(priv->mult_clk);
err_unreg_div:
clk_unregister(priv->div_clk);
err_unreg_gate:
clk_unregister(priv->gate_clk);
err_unreg_xtal:
clk_unregister(priv->xtal_clk);
return ret;
}
static int virtual_clk_remove(struct platform_device *pdev)
{
struct virtual_clk_priv *priv = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
clk_unregister(priv->mux_clk);
clk_unregister(priv->mult_clk);
clk_unregister(priv->div_clk);
clk_unregister(priv->gate_clk);
clk_unregister(priv->xtal_clk);
dev_info(&pdev->dev, "Virtual clock provider unregistered\n");
return 0;
}
static const struct of_device_id virtual_clk_of_match[] = {
{
.compatible = "virtual-clk-provider",
},
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, virtual_clk_of_match);
static struct platform_driver virtual_clk_driver = {
.driver =
{
.name = "virtual-clk-provider",
.of_match_table = virtual_clk_of_match,
},
.probe = virtual_clk_probe,
.remove = virtual_clk_remove,
};
module_platform_driver(virtual_clk_driver);
MODULE_AUTHOR("Linux Expert");
MODULE_DESCRIPTION("A 4.4-compatible provider with fixed, gate, divider, multiplier and mux");
MODULE_LICENSE("GPL");
测试程序
/*
* @Author: your name
* @Date: 2025-09-23 18:29:45
* @LastEditTime: 2025-10-15 16:09:14
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.4.159\drivers\gpu\drm\test\virtual-fixed-gate-mul-mux-consumer.c
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/clk-provider.h>
struct virtual_timer_priv {
struct clk *clk_mux; // Handle for the Mux itself
struct clk *parent_div; // Handle for the 24MHz parent
struct clk *parent_mult; // Handle for the 36MHz parent
};
static int virtual_timer_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct virtual_timer_priv *priv;
struct clk *current_parent;
long rate;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
platform_set_drvdata(pdev, priv);
dev_info(dev, "Probing Mux timer test (Final Correct Version)...\n");
/* Step 1: Get all the clock handles we need, by their names */
priv->clk_mux = devm_clk_get(dev, "timer_src");
if (IS_ERR(priv->clk_mux)) {
dev_err(dev, "Failed to get clock 'timer_src': %ld\n", PTR_ERR(priv->clk_mux));
return PTR_ERR(priv->clk_mux);
}
priv->parent_div = devm_clk_get(dev, "parent_src_div");
if (IS_ERR(priv->parent_div)) {
dev_err(dev, "Failed to get clock 'parent_src_div': %ld\n", PTR_ERR(priv->parent_div));
return PTR_ERR(priv->parent_div);
}
clk_set_rate(priv->parent_div, 12000000);
priv->parent_mult = devm_clk_get(dev, "parent_src_mult");
if (IS_ERR(priv->parent_mult)) {
dev_err(dev, "Failed to get clock 'parent_src_mult': %ld\n", PTR_ERR(priv->parent_mult));
return PTR_ERR(priv->parent_mult);
}
clk_set_rate(priv->parent_mult, 48000000);
ret = clk_prepare_enable(priv->clk_mux);
if (ret) {
dev_err(dev, "Failed to enable clock: %d\n", ret);
return ret;
}
/* Test 1: Check initial state */
rate = clk_get_rate(priv->clk_mux);
current_parent = clk_get_parent(priv->clk_mux);
dev_info(dev, "Initial rate: %ld Hz, Parent: %s\n", rate, __clk_get_name(current_parent));
/* Test 2: Switch to the 48MHz parent. Now we have the handle directly! */
dev_info(dev, "Attempting to switch to parent '%s'...\n", __clk_get_name(priv->parent_mult));
ret = clk_set_parent(priv->clk_mux, priv->parent_mult);
if (ret) {
dev_err(dev, "Failed to set new parent: %d\n", ret);
goto disable_clk;
}
dev_info(dev, "Successfully set new parent.\n");
/* Test 3: Verify the new state */
rate = clk_get_rate(priv->clk_mux);
current_parent = clk_get_parent(priv->clk_mux);
dev_info(dev, "New rate: %ld Hz, Current Parent: %s\n", rate, __clk_get_name(current_parent));
dev_info(dev, "Mux timer test finished.\n");
return 0;
disable_clk:
clk_disable_unprepare(priv->clk_mux);
return ret;
}
static int virtual_timer_remove(struct platform_device *pdev)
{
struct virtual_timer_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->clk_mux);
dev_info(&pdev->dev, "Mux timer test removed.\n");
return 0;
}
static const struct of_device_id virtual_timer_of_match[] = {
{ .compatible = "virtual-clk-consumer" }, { }
};
MODULE_DEVICE_TABLE(of, virtual_timer_of_match);
static struct platform_driver virtual_timer_driver = {
.driver = {
.name = "virtual-clk-consumer",
.of_match_table = virtual_timer_of_match
},
.probe = virtual_timer_probe,
.remove = virtual_timer_remove,
};
module_platform_driver(virtual_timer_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Linux Expert (Final Correction)");
MODULE_DESCRIPTION("The correct and robust consumer driver for a Mux clock.");
日志
/ko # insmod virtual-fixed-gate-mul-mux.ko
virtual-clk-provider 70000000.clock-provider: Registering all 5 clocks...
virtual-clk-provider 70000000.clock-provider: Virtual clock provider registered successfully
默认选择的是xtal_div
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 0 0 24000000 0 0
xtal_mult 0 0 24000000 0 0
xtal_div 0 0 24000000 0 0
xtal_mux 0 0 24000000 0 0
/ko # insmod virtual-fixed-gate-mul-mux-consumer.ko
virtual-clk-consumer timer@ff020000: Probing Mux timer test (Final Correct Version)...
virtual-clk-consumer timer@ff020000: Initial rate: 12000000 Hz, Parent: xtal_div
virtual-clk-consumer timer@ff020000: Attempting to switch to parent 'xtal_mult'...
virtual-clk-consumer timer@ff020000: Successfully set new parent.
virtual-clk-consumer timer@ff020000: New rate: 48000000 Hz, Current Parent: xtal_mult
virtual-clk-consumer timer@ff020000: Mux timer test finished.
在代码中选择了xtal_mult作为父时钟
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
user_xtal_24m 1 1 24000000 0 0
xtal_mult 1 1 48000000 0 0
xtal_mux 1 1 48000000 0 0
xtal_div 0 0 12000000 0 0
xtal_gate 0 0 24000000 0 0
查看分频系数
/ko # devmem 0x70000004 32
0x00000001
查看倍频系数
/ko # devmem 0x70000008 32
0x00000002
查看mux的选择
/ko # devmem 0x7000000c 32
0x00000001
6.复合设备
设备树
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* 4KB内存用于模拟寄存器 */
syscon_mem: syscon-mem@10000000 {
reg = <0x70000000 0x400>;
no-map;
};
};
fixed_clk_24m: clock-24m {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "fixed_24m";
};
fixed_clk_100m: clock-100m {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <100000000>;
clock-output-names = "fixed_100m";
};
my_clkc: clock-controller@10000000 {
compatible = "virtual-clk-provider";
reg = <0x70000000 0x400>;
#clock-cells = <0>;
//输出时钟的名称,会在sys下进行体现
clock-output-names = "my_composite_clk";
clocks = <&fixed_clk_24m>, <&fixed_clk_100m>;
clock-names = "parent_24m", "parent_100m";
/* 这些属性将由驱动手动解析 */
my-vendor,mux-offset = <0x04>;
my-vendor,mux-shift = <16>;
my-vendor,mux-width = <1>;
my-vendor,divider-offset = <0x04>;
my-vendor,divider-shift = <8>;
my-vendor,divider-width = <4>;
my-vendor,gate-offset = <0x04>;
my-vendor,gate-shift = <0>;
};
clk_consumer_device {
compatible = "virtual-clk-consumer";
clocks = <&my_clkc>, /* 复合时钟 */
<&fixed_clk_24m>, /* 24MHz父时钟 */
<&fixed_clk_100m>; /* 100MHz父时钟 */
clock-names = "test_clk", "parent_24m", "parent_100m";
};
时钟代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/spinlock.h>
static int my_soc_clk_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
void __iomem *base;
struct clk *clk;
struct clk_hw *hw;
struct clk_composite *composite_clk;
struct clk_init_data init;
struct clk_gate *gate;
struct clk_mux *mux;
struct clk_divider *div;
struct resource *res;
spinlock_t *clklock;
int ret;
u32 val;
dev_info(&pdev->dev, "Probing my-soc clock controller (for Linux 4.4)\n");
// 1. 映射寄存器空间 - Linux 4.4没有devm_platform_ioremap_resource
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base)) {
dev_err(&pdev->dev, "Failed to map registers\n");
return PTR_ERR(base);
}
// 2. 为复合时钟组件分配内存
composite_clk = devm_kzalloc(&pdev->dev, sizeof(*composite_clk), GFP_KERNEL);
if (!composite_clk)
return -ENOMEM;
hw = &composite_clk->hw;
// 3. 为gate、mux和div组件分配内存
gate = devm_kzalloc(&pdev->dev, sizeof(*gate), GFP_KERNEL);
if (!gate)
return -ENOMEM;
mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return -ENOMEM;
div = devm_kzalloc(&pdev->dev, sizeof(*div), GFP_KERNEL);
if (!div)
return -ENOMEM;
// 4. 为时钟锁分配内存
clklock = devm_kzalloc(&pdev->dev, sizeof(spinlock_t), GFP_KERNEL);
if (!clklock)
return -ENOMEM;
spin_lock_init(clklock);
/*
* 5. 手动解析设备树属性并填充各个组件结构
* 这是与新内核版本的主要区别
*/
// ---- Gate 部分 ----
ret = of_property_read_u32(np, "my-vendor,gate-offset", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,gate-offset\n");
return ret;
}
gate->reg = base + val;
ret = of_property_read_u32(np, "my-vendor,gate-shift", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,gate-shift\n");
return ret;
}
gate->bit_idx = val;
gate->flags = CLK_GATE_SET_TO_DISABLE;
gate->lock = clklock;
gate->hw.init = NULL;
// ---- Mux 部分 ----
ret = of_property_read_u32(np, "my-vendor,mux-offset", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,mux-offset\n");
return ret;
}
mux->reg = base + val;
ret = of_property_read_u32(np, "my-vendor,mux-shift", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,mux-shift\n");
return ret;
}
mux->shift = val;
ret = of_property_read_u32(np, "my-vendor,mux-width", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,mux-width\n");
return ret;
}
// 在Linux 4.4中,clk_mux使用mask而不是width
mux->mask = (1 << val) - 1;
mux->flags = 0;
mux->lock = clklock;
mux->hw.init = NULL;
// ---- Divider 部分 ----
ret = of_property_read_u32(np, "my-vendor,divider-offset", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,divider-offset\n");
return ret;
}
div->reg = base + val;
ret = of_property_read_u32(np, "my-vendor,divider-shift", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,divider-shift\n");
return ret;
}
div->shift = val;
ret = of_property_read_u32(np, "my-vendor,divider-width", &val);
if (ret) {
dev_err(&pdev->dev, "missing my-vendor,divider-width\n");
return ret;
}
div->width = val;
//如果标志设置为这个的话,寄存器的值reg_val=分频系数div
// div->flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO;
//根据我们的硬件设计 (div = reg_val + 1),设置标志
div->flags = 0;
div->lock = clklock;
div->hw.init = NULL;
// 6. 填充时钟的初始化数据
ret = of_property_read_string_index(np, "clock-output-names", 0, &init.name);
if (ret) {
dev_err(&pdev->dev, "Failed to read clock-output-names\n");
return ret;
}
init.num_parents = of_clk_get_parent_count(np);
if (init.num_parents == 0) {
dev_err(&pdev->dev, "No parent clocks found\n");
return -EINVAL;
}
printk("init.num_parents = %d\n",init.num_parents);
init.parent_names = devm_kzalloc(&pdev->dev, sizeof(const char *) * init.num_parents, GFP_KERNEL);
if (!init.parent_names)
return -ENOMEM;
of_clk_parent_fill(np, init.parent_names, init.num_parents);
init.flags = CLK_IS_BASIC;
// 在Linux 4.4中,clk_composite_ops不是全局变量,而是在clk_register_composite内部创建
init.ops = NULL; // 将由clk_register_composite设置
hw->init = &init;
printk(KERN_INFO "my_soc_clk_driver: clock name = %s\n", init.name);
// 7. 注册复合时钟 - 使用Linux 4.4的API
clk = clk_register_composite(&pdev->dev, init.name,
init.parent_names, init.num_parents,
&mux->hw, &clk_mux_ops,
&div->hw, &clk_divider_ops,
&gate->hw, &clk_gate_ops,
init.flags);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "Failed to register composite clock\n");
return PTR_ERR(clk);
}
// 8. 创建时钟提供者 - Linux 4.4使用不同的API
ret = of_clk_add_provider(np, of_clk_src_simple_get, clk);
if (ret) {
dev_err(&pdev->dev, "Failed to add clock provider\n");
clk_unregister(clk);
return ret;
}
// 保存clk指针以便在remove时使用
platform_set_drvdata(pdev, clk);
dev_info(&pdev->dev, "Registered composite clock '%s' successfully\n", init.name);
return 0;
}
static int my_soc_clk_remove(struct platform_device *pdev)
{
struct clk *clk = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "Removing my-soc clock controller\n");
of_clk_del_provider(pdev->dev.of_node);
clk_unregister(clk);
return 0;
}
static const struct of_device_id my_soc_clk_of_match[] = {
{ .compatible = "virtual-clk-provider" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_soc_clk_of_match);
static struct platform_driver my_soc_clk_driver = {
.driver = {
.name = "virtual-clk-provider",
.of_match_table = my_soc_clk_of_match,
},
.probe = my_soc_clk_probe,
.remove = my_soc_clk_remove,
};
module_platform_driver(my_soc_clk_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My SoC composite clock driver (for Linux 4.4)");
MODULE_LICENSE("GPL v2");
测试代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/delay.h>
struct clk_consumer_priv {
struct clk *test_clk;
struct clk *parent_24m;
struct clk *parent_100m;
};
static int clk_consumer_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct clk_consumer_priv *priv;
struct clk *parent;
long rate;
int ret;
dev_info(dev, "Probing clk consumer test device\n");
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
// 1. 获取时钟句柄
priv->test_clk = devm_clk_get(dev, "test_clk");
if (IS_ERR(priv->test_clk)) {
dev_err(dev, "Failed to get clock 'test_clk'\n");
return PTR_ERR(priv->test_clk);
}
// 获取复合时钟的父时钟
priv->parent_24m = devm_clk_get(dev, "parent_24m");
if (IS_ERR(priv->parent_24m)) {
dev_warn(dev, "Could not get parent clock 'parent_24m'\n");
priv->parent_24m = NULL;
} else {
dev_info(dev, "Got parent clock 'parent_24m', rate: %ld Hz\n",
clk_get_rate(priv->parent_24m));
}
priv->parent_100m = devm_clk_get(dev, "parent_100m");
if (IS_ERR(priv->parent_100m)) {
dev_warn(dev, "Could not get parent clock 'parent_100m'\n");
priv->parent_100m = NULL;
} else {
dev_info(dev, "Got parent clock 'parent_100m', rate: %ld Hz\n",
clk_get_rate(priv->parent_100m));
}
dev_info(dev, "======== Clock Verification Start ========\n");
// 2. 使能时钟 (测试 GATE)
ret = clk_prepare_enable(priv->test_clk);
if (ret) {
dev_err(dev, "Failed to enable clock\n");
return ret;
}
dev_info(dev, "Clock enabled successfully.\n");
// 3. 获取并打印当前速率 (默认状态)
rate = clk_get_rate(priv->test_clk);
dev_info(dev, "Initial clock rate: %ld Hz\n", rate);
// 4. 改变分频 (测试 DIVIDER)
dev_info(dev, "Setting rate to 6 MHz...\n");
ret = clk_set_rate(priv->test_clk, 6000000); // 24MHz / 4 = 6MHz
if (ret) {
dev_err(dev, "Failed to set rate\n");
} else {
rate = clk_get_rate(priv->test_clk);
dev_info(dev, "New clock rate: %ld Hz (target 6MHz)\n", rate);
}
// 5. 改变父时钟 (测试 MUX)
parent = clk_get_parent(priv->test_clk);
dev_info(dev, "Current parent clock: %s\n", __clk_get_name(parent));
// 使用获取到的父时钟100m
if (priv->parent_100m) {
dev_info(dev, "Switching parent to 100MHz...\n");
ret = clk_set_parent(priv->test_clk, priv->parent_100m);
if (ret) {
dev_err(dev, "Failed to set parent to 100MHz: %d\n", ret);
} else {
parent = clk_get_parent(priv->test_clk);
rate = clk_get_rate(priv->test_clk);
dev_info(dev, "New parent is '%s', rate is now %ld Hz\n",
__clk_get_name(parent), rate);
}
}
// 6. 在新的父时钟下再次改变分频
dev_info(dev, "Setting rate to 10 MHz...\n");
ret = clk_set_rate(priv->test_clk, 10000000); // 100MHz / 10 = 10MHz
if (ret) {
dev_err(dev, "Failed to set rate\n");
} else {
rate = clk_get_rate(priv->test_clk);
dev_info(dev, "New clock rate: %ld Hz (target 10MHz)\n", rate);
}
// 8. 禁用时钟
// clk_disable_unprepare(priv->test_clk);
// dev_info(dev, "Clock disabled.\n");
dev_info(dev, "======== Clock Verification End ========\n");
return 0;
}
static int clk_consumer_remove(struct platform_device *pdev)
{
// devm_clk_get 分配的资源会自动释放
dev_info(&pdev->dev, "Removing clk consumer test device\n");
return 0;
}
static const struct of_device_id clk_consumer_of_match[] = {
{ .compatible = "virtual-clk-consumer" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, clk_consumer_of_match);
static struct platform_driver clk_consumer_driver = {
.driver = {
.name = "virtual-clk-consumer",
.of_match_table = clk_consumer_of_match,
},
.probe = clk_consumer_probe,
.remove = clk_consumer_remove,
};
module_platform_driver(clk_consumer_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Clock consumer for verification");
MODULE_LICENSE("GPL v2");
日志
/ko # insmod virtual-clk-composite.ko
virtual-clk-provider 70000000.clock-controller: Probing my-soc clock controller (for Linux 4.4)
init.num_parents = 2
my_soc_clk_driver: clock name = my_composite_clk
virtual-clk-provider 70000000.clock-controller: Registered composite clock 'my_composite_clk' successfully
/ko # insmod virtual-clk-composite-consumer.ko
virtual-clk-consumer clk_consumer_device: Probing clk consumer test device
virtual-clk-consumer clk_consumer_device: Got parent clock 'parent_24m', rate: 24000000 Hz
virtual-clk-consumer clk_consumer_device: Got parent clock 'parent_100m', rate: 100000000 Hz
virtual-clk-consumer clk_consumer_device: ======== Clock Verification Start ========
virtual-clk-consumer clk_consumer_device: Clock enabled successfully.
virtual-clk-consumer clk_consumer_device: Initial clock rate: 24000000 Hz
virtual-clk-consumer clk_consumer_device: Setting rate to 6 MHz...
virtual-clk-consumer clk_consumer_device: New clock rate: 6000000 Hz (target 6MHz)
virtual-clk-consumer clk_consumer_device: Current parent clock: fixed_24m
virtual-clk-consumer clk_consumer_device: Switching parent to 100MHz...
virtual-clk-consumer clk_consumer_device: New parent is 'fixed_100m', rate is now 25000000 Hz
virtual-clk-consumer clk_consumer_device: Setting rate to 10 MHz...
virtual-clk-consumer clk_consumer_device: New clock rate: 10000000 Hz (target 10MHz)
virtual-clk-consumer clk_consumer_device: ======== Clock Verification End ========
/ko # cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
fixed_100m 1 1 100000000 0 0
my_composite_clk 1 1 10000000 0 0
fixed_24m 0 0 24000000 0 0
/ko # devmem 0x70000004 32
0x00010900