可锐资源网

技术资源分享平台,提供编程学习、网站建设、脚本开发教程

linux内核虚拟时钟驱动_linux时钟管理

问题描述:

编写虚拟时钟控制器驱动
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
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言