linux kernel struct clk_init_data结构浅解

在Linux内核的时钟子系统中,struct clk_init_data是连接时钟硬件描述与框架管理的核心数据结构。

它用于初始化时钟提供者(Clock Provider)的基本信息,是时钟控制器驱动(如PLL、分频器、门控时钟等)向内核时钟框架注册时钟的关键载体。

一、struct clk_init_data 结构定义

struct clk_init_data定义在 include/linux/clk-provider.h中,其核心作用是向时钟框架描述一个时钟的基本属性和行为。典型定义如下(不同内核版本可能略有差异):

复制代码
struct clk_init_data {
    const char          *name;          // 时钟名称(全局唯一)
    const char          **parent_names;// 父时钟名称数组(NULL表示无父时钟)
    u8                  num_parents;    // 父时钟数量
    const struct clk_ops *ops;          // 时钟操作函数集(必须实现核心操作)
    unsigned long       flags;          // 时钟属性标志(如关键时钟、不可睡眠等)
    void                (*hw_init_cb)(struct clk_hw *); // 硬件初始化回调(可选)
};

注:部分内核版本可能包含扩展成员(如 clk_hw指针),但核心成员保持稳定。

二、成员详细解析

1. name:时钟名称
  • 作用 ​:时钟在系统中的唯一标识符,其他驱动通过此名称调用 clk_get()获取时钟句柄。

  • 约束 ​:必须全局唯一,否则注册时会报错(-EEXIST)。

2. parent_namesnum_parents:父时钟信息
  • 作用 ​:定义该时钟的父时钟(时钟树中的上游时钟)。parent_names是父时钟名称的字符串数组,num_parents是数组长度。

  • 特殊场景​:

    • 若时钟无父时钟(如晶振源),parent_names设为 NULLnum_parents设为0。

    • 若父时钟是动态生成的(如通过 of_clk_add_provider注册的分层时钟),需确保父时钟名称已在系统中注册。

  • 示例 ​:一个分频器时钟的父时钟是 "pll0",则 parent_names = "pll0"num_parents = 1

3. ops:时钟操作函数集
  • 作用 ​:指向 struct clk_ops的指针,定义时钟的核心操作(如设置频率、使能/禁用、获取父时钟等)。​必须根据时钟类型实现必要函数

  • 常见操作函数​:

    • prepare/unprepare:硬件初始化/去初始化(如开启时钟门控的电源)。

    • enable/disable:运行时使能/禁用时钟(低功耗场景常用)。

    • recalc_rate:重新计算当前时钟频率(基于父时钟和分频系数)。

    • round_rate:计算最接近目标频率的可设置频率。

    • set_rate:设置时钟到目标频率(可能触发硬件寄存器修改)。

    • get_parent/set_parent:获取/设置父时钟索引(多父时钟场景)。

  • 注意 ​:若未实现某些函数,需将对应指针设为 NULL(但 recalc_rate通常是必需的)。

4. flags:时钟属性标志
  • 作用​:通过位掩码定义时钟的特殊属性,影响框架对时钟的管理行为。

  • 常见标志​:

    • CLK_SET_RATE_GATE:设置频率时必须先禁用时钟(避免动态调整导致异常)。

    • CLK_SET_PARENT_GATE:设置父时钟时必须先禁用时钟。

    • CLK_SET_RATE_NO_REPARENT:设置频率时不改变父时钟(仅调整内部参数)。

    • CLK_IS_BASIC:标记为基础时钟(如晶振、PLL,不依赖其他时钟)。

    • CLK_IGNORE_UNUSED:即使未被使用也保留时钟(防止被框架自动注销)。

    • CLK_IS_CRITICAL:关键时钟(系统启动时必须启用,不能被禁用)。

  • 示例 ​:关键时钟需设置 CLK_IS_CRITICAL,避免被错误关闭。

三、使用方法:从定义到注册

struct clk_init_data通常与 struct clk_hw配合使用,最终通过时钟注册函数(如 clk_hw_register)将时钟信息传递给框架。以下是典型步骤:

步骤1:定义时钟硬件结构(struct clk_hw)

struct clk_hw是时钟硬件的抽象,包含 clk_init_data和指向具体硬件操作的指针(如 clk_hw->init指向 clk_init_data)。

复制代码
struct my_clk_hw {
    struct clk_hw hw;  // 必须作为第一个成员(强制转换用)
    void __iomem *base; // 硬件寄存器基地址
    u32 reg_offset;     // 时钟控制寄存器偏移
};
步骤2:填充 struct clk_init_data

根据时钟类型(如门控时钟、分频器)填充 init_data,重点关注名称、父时钟、操作函数和标志。

复制代码
static const char * const my_clk_parents[] = { "pll0" }; // 父时钟名称

static const struct clk_init_data my_clk_init_data = {
    .name = "my_custom_clk",       // 时钟名称
    .parent_names = my_clk_parents,// 父时钟数组
    .num_parents = 1,              // 父时钟数量
    .ops = &my_clk_ops,            // 自定义操作函数集
    .flags = CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED, // 标志
};
步骤3:实现 clk_ops 操作函数

根据时钟类型实现必要的操作函数。以门控时钟为例:

复制代码
static const struct clk_ops my_clk_ops = {
    .enable = my_clk_enable,       // 使能时钟
    .disable = my_clk_disable,     // 禁用时钟
    .recalc_rate = my_clk_recalc_rate, // 计算当前频率
};

// 使能函数:设置寄存器的使能位
static int my_clk_enable(struct clk_hw *hw)
{
    struct my_clk_hw *my_hw = to_my_clk_hw(hw);
    writel_relaxed(1 << my_hw->reg_offset, my_hw->base + REG_ENABLE);
    return 0;
}

// 禁用函数:清除使能位
static void my_clk_disable(struct clk_hw *hw)
{
    struct my_clk_hw *my_hw = to_my_clk_hw(hw);
    writel_relaxed(0 << my_hw->reg_offset, my_hw->base + REG_ENABLE);
}

// 计算频率:从寄存器读取分频系数,结合父时钟频率计算
static unsigned long my_clk_recalc_rate(struct clk_hw *hw,
                                        unsigned long parent_rate)
{
    struct my_clk_hw *my_hw = to_my_clk_hw(hw);
    u32 div = readl_relaxed(my_hw->base + my_hw->reg_offset) & 0xFF;
    return parent_rate / (div + 1); // 假设分频系数为div+1
}
步骤4:注册时钟

通过 clk_hw_registerdevm_clk_hw_register注册时钟,框架会根据 init_data初始化时钟核心(struct clk_core)并加入时钟树。

cpp 复制代码
static int my_clk_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_clk_hw *my_hw;
    struct resource *res;
    int ret;

    // 分配硬件结构内存
    my_hw = devm_kzalloc(dev, sizeof(*my_hw), GFP_KERNEL);
    if (!my_hw)
        return -ENOMEM;

    // 获取寄存器基地址
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    my_hw->base = devm_ioremap_resource(dev, res);
    if (IS_ERR(my_hw->base))
        return PTR_ERR(my_hw->base);

    // 关联 clk_init_data 到 clk_hw
    my_hw->hw.init = &my_clk_init_data;
    my_hw->reg_offset = 0x10; // 假设控制寄存器偏移为0x10

    // 注册时钟
    ret = devm_clk_hw_register(dev, &my_hw->hw);
    if (ret) {
        dev_err(dev, "Failed to register clock");
        return ret;
    }

    // 可选:将时钟暴露给设备树(通过of_clk_add_hw_provider)
    return of_clk_add_hw_provider(dev->of_node, of_clk_hw_simple_get, &my_hw->hw);
}

五、注意事项

  1. 名称唯一性 ​:name必须全局唯一,否则 clk_get()会失败。

  2. 父时钟依赖​:父时钟需先于当前时钟注册(通过设备树顺序或显式依赖声明)。

  3. 操作函数完整性 ​:至少实现 recalc_rate,否则无法获取时钟频率。

  4. 错误处理 ​:注册时检查返回值(如 -EEXIST表示名称冲突,-EINVAL表示父时钟无效)。

惠州西湖

相关推荐
LCG元3 小时前
Sed 和 Awk 的终极实战:用一行命令搞定90%的文本处理
linux
NO.10243 小时前
11.4八股
java·linux·数据库
我想吃余3 小时前
Linux信号(下):信号保存和信号处理
linux·运维·信号处理
七夜zippoe3 小时前
高性能网络编程实战:用Tokio构建自定义协议服务器
linux·服务器·网络·rust·tokio
owCode3 小时前
Linux中的管道
linux·运维·服务器
jiedaodezhuti3 小时前
服务器负载过高的多维度诊断与性能瓶颈定位指南
linux
neo_will_mvp3 小时前
服务器bmc功能
linux·运维·服务器
敲上瘾4 小时前
Elasticsearch从入门到实践:核心概念到Kibana测试与C++客户端封装
大数据·linux·c++·elasticsearch·搜索引擎·全文检索
慕慕涵雪月光白4 小时前
在Ubuntu系统上安装英伟达(NVIDIA)RTX 3070 Ti的驱动程序
linux·运维·人工智能·ubuntu