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_names和 num_parents:父时钟信息
作用:定义该时钟的父时钟(时钟树中的上游时钟)。
parent_names是父时钟名称的字符串数组,num_parents是数组长度。特殊场景:
若时钟无父时钟(如晶振源),
parent_names设为NULL,num_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_register或 devm_clk_hw_register注册时钟,框架会根据 init_data初始化时钟核心(struct clk_core)并加入时钟树。
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_hwmy_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);
}五、注意事项
名称唯一性:
name必须全局唯一,否则clk_get()会失败。父时钟依赖:父时钟需先于当前时钟注册(通过设备树顺序或显式依赖声明)。
操作函数完整性:至少实现
recalc_rate,否则无法获取时钟频率。错误处理:注册时检查返回值(如
-EEXIST表示名称冲突,-EINVAL表示父时钟无效)。
惠州西湖

