title: clk
categories:
- linux
- drivers
- clk
tags: - linux
- drivers
- clk
abbrlink: a6f83010
date: 2025-10-03 09:01:49
文章目录
- [drivers/clk 时钟管理框架(Common Clock Framework) SoC时钟资源的统一抽象](#drivers/clk 时钟管理框架(Common Clock Framework) SoC时钟资源的统一抽象)
- include/linux/clk-provider.h
-
- [CLK_OF_DECLARE CLK_OF_DECLARE_DRIVER](#CLK_OF_DECLARE CLK_OF_DECLARE_DRIVER)
- include/linux/clk.h
-
- clk_disable_unprepare
- [clk_bulk_prepare_enable: 批量准备并使能时钟](#clk_bulk_prepare_enable: 批量准备并使能时钟)
- drivers/clk/clk.c
-
- [of_clk_init 的 clk init](#of_clk_init 的 clk init)
- [clk_core_lookup 查找时钟](#clk_core_lookup 查找时钟)
- [clk_core_get 查找 clk 的 clk_core 父级](#clk_core_get 查找 clk 的 clk_core 父级)
- [clk_core_fill_parent_index Clk 核心填充父索引](#clk_core_fill_parent_index Clk 核心填充父索引)
- [clk_core_get_parent_by_index Clk core 按索引获取父级](#clk_core_get_parent_by_index Clk core 按索引获取父级)
- [__clk_init_parent Clk init 父级](#__clk_init_parent Clk init 父级)
- [clk_core_get_phase 获取时钟的 phase](#clk_core_get_phase 获取时钟的 phase)
- [clk_core_update_duty_cycle_nolock 更新时钟的占空比](#clk_core_update_duty_cycle_nolock 更新时钟的占空比)
- [clk_core_get_rate_nolock Clk core 获取速率 nolock](#clk_core_get_rate_nolock Clk core 获取速率 nolock)
- [__clk_core_init 在 struct clk_core 中初始化数据结构](#__clk_core_init 在 struct clk_core 中初始化数据结构)
- [clk_prepare_lock clk_prepare_unlock](#clk_prepare_lock clk_prepare_unlock)
- [clk_core_link_consumer 将 clk 使用者添加到clk_core中的使用者列表中](#clk_core_link_consumer 将 clk 使用者添加到clk_core中的使用者列表中)
- [clk_core_populate_parent_map Clk 核心填充父映射](#clk_core_populate_parent_map Clk 核心填充父映射)
- [__clk_register 注册时钟](#__clk_register 注册时钟)
- [dev_or_parent_of_node 获取 @dev 或 @dev 的父节点的设备节点](#dev_or_parent_of_node 获取 @dev 或 @dev 的父节点的设备节点)
- [clk_hw_register 注册 clk_hw 并返回错误代码](#clk_hw_register 注册 clk_hw 并返回错误代码)
- [clk_core_update_orphan_status 更新 orphan 状态](#clk_core_update_orphan_status 更新 orphan 状态)
- [clk_reparent 重新父级](#clk_reparent 重新父级)
- [__clk_set_parent_before __clk_set_parent_after](#__clk_set_parent_before __clk_set_parent_after)
- [__clk_recalc_accuracies Clk 重新计算精度](#__clk_recalc_accuracies Clk 重新计算精度)
- [clk_recalc 重新计算速率](#clk_recalc 重新计算速率)
- [__clk_recalc_rates 重新计算数率](#__clk_recalc_rates 重新计算数率)
- [clk_core_reparent_orphans 重新为孤立的时钟(orphan clocks)分配父级时钟](#clk_core_reparent_orphans 重新为孤立的时钟(orphan clocks)分配父级时钟)
- [of_clk_add_hw_provider 为节点注册时钟提供程序](#of_clk_add_hw_provider 为节点注册时钟提供程序)
- [of_clk_hw_onecell_get 获取时钟](#of_clk_hw_onecell_get 获取时钟)
- [of_clk_get_hw_from_clkspec of clk 从提供商处获取 hw](#of_clk_get_hw_from_clkspec of clk 从提供商处获取 hw)
- [of_parse_clkspec() 解析给定设备节点的 DT 时钟说明符](#of_parse_clkspec() 解析给定设备节点的 DT 时钟说明符)
- [alloc_clk 分配一个 clk 消费者,但将其取消链接到 clk_core](#alloc_clk 分配一个 clk 消费者,但将其取消链接到 clk_core)
- [clk_hw_create_clk 分配 clk consumer 并将其链接到给定 clk_hw 的 clk_core](#clk_hw_create_clk 分配 clk consumer 并将其链接到给定 clk_hw 的 clk_core)
- [of_clk_get_hw 从hw获取时钟](#of_clk_get_hw 从hw获取时钟)
- [clk_prepare clk 准备时钟](#clk_prepare clk 准备时钟)
- [clk_enable_lock clk_enable_unlock](#clk_enable_lock clk_enable_unlock)
- [clk_core_enable 启用时钟](#clk_core_enable 启用时钟)
- [clk_enable 启用时钟](#clk_enable 启用时钟)
- [clk_disable 禁用时钟](#clk_disable 禁用时钟)
- [clk_prepare_enable 准备并启用时钟](#clk_prepare_enable 准备并启用时钟)
- [of_clk_get_parent_name 获取时钟的父节点名称](#of_clk_get_parent_name 获取时钟的父节点名称)
- [clk_get_rate 获取时钟频率](#clk_get_rate 获取时钟频率)
- drivers/clk/clk-composite.c
-
- [__clk_hw_register_composite 注册复合时钟](#__clk_hw_register_composite 注册复合时钟)
- drivers/clk/clk-conf.c
-
- [__set_clk_parents Set clk 父项](#__set_clk_parents Set clk 父项)
- [__set_clk_rates 设置 clk 速率](#__set_clk_rates 设置 clk 速率)
- [of_clk_set_defaults 解析和设置 assigned clocks 配置](#of_clk_set_defaults 解析和设置 assigned clocks 配置)
- drivers/clk/clk-devres.c
- drivers/clk/clk-divider.c
-
- [clk_div_readl 读取寄存器的数据](#clk_div_readl 读取寄存器的数据)
- [_get_table_div 获取表格中的分频系数](#_get_table_div 获取表格中的分频系数)
- [_get_div 获取分频系数](#_get_div 获取分频系数)
- [DIV_ROUND_UP_ULL 执行分频并向上取整](#DIV_ROUND_UP_ULL 执行分频并向上取整)
- [divider_recalc_rate 重新计算数率](#divider_recalc_rate 重新计算数率)
- [clk_divider_recalc_rate 重新计算数率](#clk_divider_recalc_rate 重新计算数率)
- [clk_divider_set_rate Clk 分频器设置速率](#clk_divider_set_rate Clk 分频器设置速率)
- [clk_divider_ops clk_divider_ro_ops](#clk_divider_ops clk_divider_ro_ops)
- [clk_hw_register_divider Clk hw 注册分频器](#clk_hw_register_divider Clk hw 注册分频器)
- drivers/clk/clk-fixed-rate.c
-
- [clk_hw_register_fixed_rate_with_accuracy 硬件时钟注册 固定速率和精度](#clk_hw_register_fixed_rate_with_accuracy 硬件时钟注册 固定速率和精度)
- _of_fixed_clk_setup
- [clk_hw_register_fixed_rate 向 clock 注册固定速率 clock](#clk_hw_register_fixed_rate 向 clock 注册固定速率 clock)
- [clk_hw_register_fixed_factor Clk hw 寄存器固定系数](#clk_hw_register_fixed_factor Clk hw 寄存器固定系数)
- stm32
- 时钟驱动:of_fixed_clk_driver
- drivers/clk/clk-fixed-factor.c
- drivers/clk/clk-gpio.c
- drivers/clk/clk-gate.c
-
- [clk_gate_readl 读取寄存器](#clk_gate_readl 读取寄存器)
- [clk_gate_writel 写入寄存器](#clk_gate_writel 写入寄存器)
- clk_gate_endisable
- clk_gate_ops
- [clk_hw_register_gate 注册门控时钟](#clk_hw_register_gate 注册门控时钟)
- drivers/clk/clk-mux.c
-
- [clk_mux_readl 返回mux的寄存器地址内的数据](#clk_mux_readl 返回mux的寄存器地址内的数据)
- [clk_mux_val_to_index 根据读取值返回索引](#clk_mux_val_to_index 根据读取值返回索引)
- [clk_mux_get_parent 复用时钟获取父设备](#clk_mux_get_parent 复用时钟获取父设备)
- [clk_mux_ops clk_mux_ro_ops](#clk_mux_ops clk_mux_ro_ops)
- [clk_hw_register_mux 注册多路复用器时钟](#clk_hw_register_mux 注册多路复用器时钟)
- drivers/clk/clkdev.c
- drivers/clk/clk-stm32h7.c
-
- [stm32_mclk stm32 mux clk 复合时钟](#stm32_mclk stm32 mux clk 复合时钟)
- [clk_register_stm32_timer_ker 注册STM32定时器KER](#clk_register_stm32_timer_ker 注册STM32定时器KER)
- [register_core_and_bus_clocks 寄存器 core 和 bus clocks](#register_core_and_bus_clocks 寄存器 core 和 bus clocks)
- [stm32_oclk stm32 晶振时钟](#stm32_oclk stm32 晶振时钟)
- [clk_register_ready_gate Clk 注册门控时钟](#clk_register_ready_gate Clk 注册门控时钟)
- [get_cfg_composite_div 获取 cfg 复合 div](#get_cfg_composite_div 获取 cfg 复合 div)
- clk_register_stm32_pll
- pclk
- stm32h7_rcc_init
- sys_ck的时钟源不是默认值的分析

drivers/clk 时钟管理框架(Common Clock Framework) SoC时钟资源的统一抽象
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/clk 目录中的代码实现了Linux内核的"通用时钟框架"(Common Clock Framework, CCF)。这项技术的诞生是为了解决在现代片上系统(SoC)中一个极其复杂且普遍存在的问题:时钟资源的管理混乱、缺乏统一抽象和代码不可移植。
一个现代SoC内部包含着一个庞大而复杂的时钟树,里面有成百上千个独立的时钟源,用于驱动不同的硬件模块(CPU核、GPU、内存控制器、UART、I2C、SPI、显示引擎等)。在CCF出现之前:
- ** vendor特定实现**:每个SoC厂商(如TI, NXP, Samsung, Rockchip)都在其BSP(板级支持包)中实现一套自己独有的、非标准的API来管理时钟。
- 驱动不可移植:一个为TI芯片编写的UART驱动,如果想移植到NXP的芯片上,其所有与时钟相关的代码(如何使能时钟、如何设置波特率所需的时钟频率)都必须完全重写。
- 电源管理困难:精确的电源管理依赖于在硬件模块不使用时能够关闭(gate)其时钟,以节省功耗。在没有统一框架的情况下,这种操作难以系统化地实现和验证。
- 缺乏依赖关系管理:时钟之间存在父子依赖关系(例如,一个UART的时钟可能来自于一个PLL的分频)。手动管理这些依赖关系非常容易出错,比如在父时钟未开启时就尝试使能子时钟。
CCF的诞生就是为了提供一个统一的、与具体SoC硬件无关的API,将时钟消费者(Consumer) (即设备驱动)与时钟提供者(Provider)(即SoC特定的时钟控制器驱动)彻底分离开来。
它的发展经历了哪些重要的里程碑或版本迭代?
CCF是内核演进中解决硬件碎片化问题的典范。
- 概念的形成与引入 :该框架被引入内核,旨在取代各种vendor特定的时钟代码。其核心是定义了一套标准的时钟操作API(
clk_prepare_enable,clk_set_rate等)。 - 与设备树(Device Tree)的深度融合:这是CCF发展中最关键的里程碑。整个SoC的时钟树拓扑结构------哪个时钟是哪个时钟的父节点,时钟的类型(门控、分频器、复用器、PLL)及其参数------被完全从C代码中剥离出来,用设备树进行描述。内核的CCF核心在启动时解析设备树,动态地在内存中重建整个时钟依赖关系图。
- 时钟提供者模型的丰富 :框架内部不断增加对各种新型、复杂时钟硬件的支持,提供了丰富的标准时钟类型(
clk_gate,clk_divider,clk_mux,clk_pll,clk_fixed_rate等),使得为新SoC编写时钟提供者驱动变得更加模块化和简单。 - 成为事实标准 :如今,任何向Linux主线提交的新的SoC平台支持,都必须使用Common Clock Framework来管理其时钟。
目前该技术的社区活跃度和主流应用情况如何?
CCF是Linux内核在嵌入式和移动领域最核心、最基础的框架之一,极其稳定和成熟。它是所有现代SoC平台(ARM, ARM64, RISC-V等)正常工作的基石。几乎所有SoC上的设备驱动都是CCF的消费者,包括:
- 核心系统:CPUfreq驱动(用于调节CPU频率)、内存控制器。
- 外设驱动:UART, I2C, SPI, SD/MMC, Ethernet, USB等。
- 多媒体驱动:GPU, 显示控制器, VPU (视频处理单元)。
核心原理与设计
它的核心工作原理是什么?
CCF的核心是提供者/消费者模型,并通过设备树作为中间的"数据粘合剂"。
-
提供者(Provider):
- 这是SoC特定的时钟控制器驱动,例如
drivers/clk/rockchip/clk-rk3399.c。 - 它负责解析设备树中描述时钟硬件的节点。
- 它根据设备树的描述,将硬件时钟源(如一个PLL、一个分频器)实例化为内核中的
struct clk_hw对象,并向CCF核心注册这些时钟,同时提供操作这些硬件的回调函数(.enable,.set_rate等)。 - CCF核心根据设备树中的父子关系,将所有
clk_hw对象链接起来,形成一个完整的时钟树图。
- 这是SoC特定的时钟控制器驱动,例如
-
消费者(Consumer):
- 这是标准的设备驱动,例如一个I2C控制器驱动。
- 在其设备树节点中,会通过
clocks和clock-names属性来声明它需要哪些时钟。例如:clocks = <&cru SCLK_I2C1>; clock-names = "i2c";。 - 驱动在
.probe函数中,调用统一的、硬件无关的APIdevm_clk_get(dev, "i2c")来获取一个时钟句柄(struct clk *)。CCF核心会根据设备树中的链接,找到并返回正确的时钟句柄。 - 在需要访问硬件之前,驱动调用
clk_prepare_enable(clk)。CCF核心会自动处理所有依赖关系(例如,递归地使能所有父时钟),并最终调用提供者驱动注册的回调函数来打开时钟门控。 - 如果需要,驱动可以调用
clk_set_rate(clk, rate)来改变时钟频率。 - 当不再需要硬件时,调用
clk_disable_unprepare(clk)。
它的主要优势体现在哪些方面?
- 完全的抽象和可移植性:消费者驱动完全不知道时钟的来源和物理实现,其代码可以在任何支持CCF的平台上不经修改地运行。
- 数据驱动的配置:时钟树的配置完全由设备树数据决定,硬件工程师或系统集成者可以修改时钟配置而无需修改任何C代码。
- 集中的电源管理:CCF提供了对时钟门控的统一管理,是实现精细化电源管理、降低系统功耗的关键。
- 健壮的依赖管理:框架自动处理复杂的时钟依赖关系,防止了因操作顺序错误导致的系统崩溃。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 初始实现的复杂性:为一个全新的SoC家族编写时钟提供者驱动并正确地描述整个设备树是一项复杂且细致的工作,需要深入理解硬件手册。
- 对设备树的强依赖:CCF的正确工作极度依赖于设备树的准确性。设备树中的任何一个错误(如父子关系错误、分频/倍频系数错误)都可能导致整个系统无法启动或工作异常。
- 调试难度 :当时钟出现问题时,调试可能比较困难。需要借助debugfs(
/sys/kernel/debug/clk)来检查时钟树的状态、频率和使能计数,这需要一定的专业知识。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请例举说明。
CCF是任何需要在SoC上运行的设备驱动程序 管理其时钟资源的唯一且标准的解决方案。
- 场景一:UART驱动
为了能以正确的波特率(如115200 bps)进行通信,UART控制器需要一个精确的输入时钟频率。UART驱动会通过clk_set_rate()请求一个合适的频率(例如,115200的整数倍),然后通过clk_prepare_enable()打开时钟,之后才能配置波特率分频器并开始通信。 - 场景二:CPU动态变频(CPUfreq)
CPUfreq驱动是CCF的一个主要消费者。当系统负载变化时,它会调用clk_set_rate()来动态地升高或降低CPU核心的时钟频率,以在性能和功耗之间取得平衡。 - 场景三:显示控制器
显示控制器需要一个精确的像素时钟(pixel clock)来匹配显示屏的分辨率和刷新率。当显示模式改变时(例如从1080p切换到4K),显示驱动会调用clk_set_rate()来设置新的像素时钟频率。
是否有不推荐使用该技术的场景?为什么?
- 非SoC内部时钟:CCF主要用于管理SoC内部的时钟资源。对于通过标准总线(如PCIe, USB)连接的外设,它们通常有自己的时钟生成和管理机制,不受SoC时钟树的直接控制。
- 固定时钟晶振 :如果一个简单的外设直接连接到一个固定的晶体振荡器,并且这个时钟不能被软件控制(开关或变频),那么它可能只需要一个
clk_fixed_rate的简单描述,但复杂的CCF功能(变频、父子切换)对其并不适用。
对比分析
请将其 与 其他相似技术 进行详细对比。
CCF的主要对比对象就是它所取代的、前CCF时代的各种vendor特定的时钟管理实现。
| 特性 | Common Clock Framework (CCF) | Vendor-Specific 实现 (旧方式) |
|---|---|---|
| 抽象级别 | 高。提供了统一的、硬件无关的API。 | 低。驱动直接调用与特定SoC寄存器绑定的私有函数。 |
| 驱动可移植性 | 非常高。消费者驱动代码可在不同平台间直接复用。 | 几乎为零。驱动与特定SoC的BSP紧密耦合。 |
| 硬件配置方式 | 数据驱动。通过设备树描述硬件拓扑和配置。 | 代码驱动。通常在C代码(板级文件)中硬编码时钟配置。 |
| 电源管理 | 标准化、集中化 。统一的clk_enable/disable接口,易于实现系统级功耗优化。 |
分散、临时。每个驱动自行实现时钟开关,难以协同和验证。 |
| 依赖管理 | 自动化、健壮。框架根据设备树自动处理父子依赖。 | 手动、易错。开发者必须在代码中手动保证正确的时钟操作顺序。 |
| 维护成本 | 低。API稳定,硬件改动只需修改设备树。 | 高。任何时钟相关的硬件改动都可能需要修改大量驱动C代码。 |
include/linux/clk-provider.h
CLK_OF_DECLARE CLK_OF_DECLARE_DRIVER
c
#define CLK_OF_DECLARE(name, compat, fn) \
static void __init __##name##_of_clk_init_declare(struct device_node *np) \
{ \
fn(np); \
fwnode_dev_initialized(of_fwnode_handle(np), true); \
} \
OF_DECLARE_1(clk, name, compat, __##name##_of_clk_init_declare)
/*
* 如果您的驱动程序需要两个初始化例程,一个在 of_clk_init() 处,另一个在平台设备探测处,请使用此宏
*/
#define CLK_OF_DECLARE_DRIVER(name, compat, fn) \
static void __init name##_of_clk_init_driver(struct device_node *np) \
{ \
of_node_clear_flag(np, OF_POPULATED); \
fn(np); \
} \
OF_DECLARE_1(clk, name, compat, name##_of_clk_init_driver)
include/linux/clk.h
clk_disable_unprepare
c
/* clk_disable_unprepare 有助于在非原子上下文中使用 clk_disable 的情况。 */
static inline void clk_disable_unprepare(struct clk *clk)
{
clk_disable(clk);
clk_unprepare(clk);
}
clk_bulk_prepare_enable: 批量准备并使能时钟
这是一个定义在内核头文件中的静态内联函数, 它的核心作用是提供一个方便且安全的封装, 用于一次性地"准备"(prepare)并"使能"(enable)一组时钟。它将一个典型的两阶段时钟启动过程合并为一个单一的API调用, 并内置了关键的错误恢复逻辑。
该函数的原理基于Linux通用时钟框架(Common Clock Framework)中的两个核心概念:
- 准备 (Prepare) : 这是一个预备步骤。调用
clk_bulk_prepare会递归地确保该组时钟所需的所有上游父时钟都已经被"准备"并"使能"。它还会执行一些必要的硬件预操作, 但通常不会真正地打开通往最终外设的时钟门控。这就像在打开水龙头之前, 确保总水管和分水管的阀门都已打开。 - 使能 (Enable) : 这是实际开启时钟的步骤。调用
clk_bulk_enable会打开直连外设的最后一个时钟门控, 让时钟信号可以真正地驱动外设工作。这相当于拧开水龙头本身。
此函数最重要的价值在于其原子性保证和错误处理:
- 它首先尝试"准备"所有时钟。如果这一步就失败了, 它会立即返回错误, 系统状态保持不变。
- 如果"准备"成功, 它会接着尝试"使能"所有时钟。如果"使能"失败了, 它不会 直接返回, 而是会自动调用
clk_bulk_unprepare来撤销第一步成功的"准备"操作, 将系统恢复到调用此函数之前的状态。只有在完成这个"回滚"操作之后, 它才会返回错误码。
对于STM32H750这样的系统, 这个函数极大地简化了设备驱动程序的编写。例如, 一个需要AHB总线时钟和自身内核时钟的复杂外设驱动, 在其probe函数中只需调用一次clk_bulk_prepare_enable即可。驱动开发者无需手动编写"如果第二步失败则撤销第一步"的繁琐逻辑, 从而使代码更简洁、更健壮, 有效地防止了因资源状态不一致而导致的系统故障。__must_check属性会提醒编译器, 如果调用者忽略了此函数的返回值(即不检查操作是否成功), 就产生一个编译警告, 从而强制实施了良好的编程实践。
c
/*
* static inline: 定义一个静态内联函数.
* "static" 意味着此函数的作用域仅限于定义它的文件, 有助于避免命名冲突.
* "inline" 是对编译器的建议, 将此函数的代码直接嵌入到调用它的地方, 以减少函数调用的开销.
* __must_check: 这是一个GCC属性, 它会告诉编译器, 任何调用此函数的代码都必须检查其返回值.
* 如果调用者忽略了返回值, 编译器会发出警告. 这对于强制检查可能失败的操作非常重要.
*/
static inline int __must_check
clk_bulk_prepare_enable(int num_clks, const struct clk_bulk_data *clks)
{
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值.
*/
int ret;
/*
* 第一步: 调用 clk_bulk_prepare 来"准备"所有时钟.
* 这会确保所有时钟的父时钟都已就绪, 但通常不直接打开最后的门控.
*/
ret = clk_bulk_prepare(num_clks, clks);
/*
* 检查 clk_bulk_prepare 是否返回了错误 (非零值).
*/
if (ret)
/*
* 如果准备失败, 立即将错误码返回给调用者. 无需做任何清理, 因为什么都还没准备好.
*/
return ret;
/*
* 第二步: 如果准备成功, 调用 clk_bulk_enable 来"使能"所有时钟.
* 这会打开通向外设的时钟门控, 使其开始工作.
*/
ret = clk_bulk_enable(num_clks, clks);
/*
* 检查 clk_bulk_enable 是否返回了错误.
*/
if (ret)
/*
* 如果使能失败, 必须执行清理操作.
* 调用 clk_bulk_unprepare 来撤销第一步成功的"准备"操作,
* 将时钟系统恢复到调用本函数之前的状态.
* 这是此封装函数提供的核心安全保证.
*/
clk_bulk_unprepare(num_clks, clks);
/*
* 返回 clk_bulk_enable 的结果.
* 如果成功, ret 为 0.
* 如果失败, ret 是一个负的错误码, 并且清理操作已经完成.
*/
return ret;
}
drivers/clk/clk.c
of_clk_init 的 clk init
c
0x00000000c02a0e10 __clk_of_table = .
*(__clk_of_table)
__clk_of_table
0x00000000c02a0e10 0xc4 drivers/clk/clk-fixed-factor.o
__clk_of_table
0x00000000c02a0ed4 0xc4 drivers/clk/clk-fixed-rate.o
__clk_of_table
0x00000000c02a0f98 0x310 drivers/clk/clk-stm32f4.o
__clk_of_table
0x00000000c02a12a8 0xc4 drivers/clk/clk-stm32h7.o
*(__clk_of_table_end)
__clk_of_table_end
CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock",
of_fixed_factor_clk_setup);
c
extern struct of_device_id __clk_of_table;
static const struct of_device_id __clk_of_table_sentinel
__used __section("__clk_of_table_end");
/**
* of_clk_init() - 来自 DT 的 Scan 和 init clock providers
* @matches: 提供程序的兼容值和 init 函数数组.
*
* 此函数扫描 device tree 以查找匹配的 clock providers 并调用其 initialization functions。它还通过尝试遵循依赖项来实现这一点。
*/
void __init of_clk_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np;
struct clock_provider *clk_provider, *next;
bool is_init_done;
bool force = false;
LIST_HEAD(clk_provider_list);
if (!matches)
matches = &__clk_of_table;
/* 首先准备 clocks providers 列表 */
for_each_matching_node_and_match(np, matches, &match) {
struct clock_provider *parent;
if (!of_device_is_available(np))
continue;
parent = kzalloc(sizeof(*parent), GFP_KERNEL);
if (!parent) {
list_for_each_entry_safe(clk_provider, next,
&clk_provider_list, node) {
list_del(&clk_provider->node);
of_node_put(clk_provider->np);
kfree(clk_provider);
}
of_node_put(np);
return;
}
parent->clk_init_cb = match->data;
parent->np = of_node_get(np);
list_add_tail(&parent->node, &clk_provider_list);
}
while (!list_empty(&clk_provider_list)) {
is_init_done = false;
list_for_each_entry_safe(clk_provider, next,
&clk_provider_list, node) {
if (force || parent_ready(clk_provider->np)) {
/* Don't populate platform devices */
of_node_set_flag(clk_provider->np,
OF_POPULATED);
clk_provider->clk_init_cb(clk_provider->np);
of_clk_set_defaults(clk_provider->np, true);
list_del(&clk_provider->node);
of_node_put(clk_provider->np);
kfree(clk_provider);
is_init_done = true;
}
}
/*
* We didn't manage to initialize any of the
* remaining providers during the last loop, so now we
* initialize all the remaining ones unconditionally
* in case the clock parent was not mandatory
*/
if (!is_init_done)
force = true;
}
}
clk_core_lookup 查找时钟
c
static struct clk_core *clk_core_lookup(const char *name)
{
struct clk_core *root_clk;
struct clk_core *ret;
if (!name)
return NULL;
/* 首先搜索 'proper' CLK 树 */
hlist_for_each_entry(root_clk, &clk_root_list, child_node) {
ret = __clk_lookup_subtree(name, root_clk);
if (ret)
return ret;
}
/* 如果未找到,则搜索孤立树 */
hlist_for_each_entry(root_clk, &clk_orphan_list, child_node) {
ret = __clk_lookup_subtree(name, root_clk);
if (ret)
return ret;
}
return NULL;
}
clk_core_get 查找 clk 的 clk_core 父级
c
/**
* clk_core_get - 查找 clk 的 clk_core 父级
* @core: clk to find parent of
* @p_index: parent index to search for
*
* 这是 clk providers 查找 clk 的 parent 级的首选方法,
* 当该父级位于 clk 控制器外部时。parent_names数组被索引并被视为与设备节点的 'clock-names' 属性中的字符串匹配的本地名称,
* 或被视为与设备clk_lookup的 dev_name() 匹配的 'con_id'。这允许 clk 提供程序使用自己的命名空间,而不是寻找全局唯一的父字符串。
*
* 例如,以下 DT 代码段将允许 clock-controller@c001 注册的 clock 具有 clk_init_data::p arent_data 数组,
* 其中 'name' 成员中带有 'xtal' ,可以找到 clock-controller@f00abcd 提供的 clock ,而无需获取 xtal clk 的全局唯一名称。
*
* parent: clock-controller@f00abcd {
* reg = <0xf00abcd 0xabcd>;
* #clock-cells = <0>;
* };
*
* clock-controller@c001 {
* reg = <0xc001 0xf00d>;
* clocks = <&parent>;
* clock-names = "xtal";
* #clock-cells = <1>;
* };
*
* 返回: -ENOENT 当找不到提供程序,或者 clk 在提供程序中不存在,或者在 DT 节点或 clkdev 查找中找不到名称时。
* NULL 当提供程序知道 clk 但未在此系统上提供时。当 clk 可以在提供程序中找到时,有效的 clk_core 指针。
*/
static struct clk_core *clk_core_get(struct clk_core *core, u8 p_index)
{
const char *name = core->parents[p_index].fw_name;
int index = core->parents[p_index].index;
struct clk_hw *hw = ERR_PTR(-ENOENT);
struct device *dev = core->dev;
const char *dev_id = dev ? dev_name(dev) : NULL;
struct device_node *np = core->of_node;
struct of_phandle_args clkspec;
if (np && (name || index >= 0) &&
/* 解析给定设备节点的 DT 时钟说明符 */
!of_parse_clkspec(np, index, name, &clkspec)) {
hw = of_clk_get_hw_from_clkspec(&clkspec);
of_node_put(clkspec.np);
} else if (name) {
/*
* 如果上面的 DT 搜索找不到提供程序,则回退到通过基于 clkdev 的 clk_lookups 进行查找。
*/
hw = clk_find_hw(dev_id, name);
}
if (IS_ERR(hw))
return ERR_CAST(hw);
if (!hw)
return NULL;
return hw->core;
}
clk_core_fill_parent_index Clk 核心填充父索引
c
static void clk_core_fill_parent_index(struct clk_core *core, u8 index)
{
struct clk_parent_map *entry = &core->parents[index];
struct clk_core *parent;
if (entry->hw) {
parent = entry->hw->core;
} else {
parent = clk_core_get(core, index);
if (PTR_ERR(parent) == -ENOENT && entry->name)
parent = clk_core_lookup(entry->name);
}
/*
*我们有一个直接引用,但还没有注册?
* 孤立它,并让 clk_reparent() 在父级注册时更新孤立状态。
*/
if (!parent)
parent = ERR_PTR(-EPROBE_DEFER);
/* 仅在不是错误的情况下缓存它 */
if (!IS_ERR(parent))
entry->core = parent;
}
clk_core_get_parent_by_index Clk core 按索引获取父级
c
static struct clk_core *clk_core_get_parent_by_index(struct clk_core *core,
u8 index)
{
if (!core || index >= core->num_parents || !core->parents)
return NULL;
if (!core->parents[index].core)
clk_core_fill_parent_index(core, index);
return core->parents[index].core;
}
__clk_init_parent Clk init 父级
c
static struct clk_core *__clk_init_parent(struct clk_core *core)
{
u8 index = 0;
if (core->num_parents > 1 && core->ops->get_parent)
index = core->ops->get_parent(core->hw);
return clk_core_get_parent_by_index(core, index);
}
clk_core_get_phase 获取时钟的 phase
c
static int clk_core_get_phase(struct clk_core *core)
{
int ret;
lockdep_assert_held(&prepare_lock);
if (!core->ops->get_phase)
return 0;
/* Always try to update cached phase if possible */
ret = core->ops->get_phase(core->hw);
if (ret >= 0)
core->phase = ret;
return ret;
}
clk_core_update_duty_cycle_nolock 更新时钟的占空比
c
static void clk_core_reset_duty_cycle_nolock(struct clk_core *core)
{
/*假设默认值为 50%*/
core->duty.num = 1;
core->duty.den = 2;
}
static int clk_core_update_duty_cycle_parent_nolock(struct clk_core *core)
{
int ret = 0;
if (core->parent &&
core->flags & CLK_DUTY_CYCLE_PARENT) {
ret = clk_core_update_duty_cycle_nolock(core->parent);
memcpy(&core->duty, &core->parent->duty, sizeof(core->duty));
} else {
clk_core_reset_duty_cycle_nolock(core);
}
return ret;
}
static int clk_core_update_duty_cycle_nolock(struct clk_core *core)
{
struct clk_duty *duty = &core->duty;
int ret = 0;
if (!core->ops->get_duty_cycle)
return clk_core_update_duty_cycle_parent_nolock(core);
ret = core->ops->get_duty_cycle(core->hw, duty);
if (ret)
goto reset;
/* Don't trust the clock provider too much */
if (duty->den == 0 || duty->num > duty->den) {
ret = -EINVAL;
goto reset;
}
return 0;
reset:
clk_core_reset_duty_cycle_nolock(core);
return ret;
}
clk_core_get_rate_nolock Clk core 获取速率 nolock
c
static unsigned long clk_core_get_rate_nolock(struct clk_core *core)
{
if (!core)
return 0;
if (!core->num_parents || core->parent)
return core->rate;
/*
* Clk 必须有一个父级,因为num_parents > 0,但父级尚未知。
* 最好返回 0 作为此 clk 的 rate,直到我们可以根据 parent's rate 正确地重新计算 rate。
*/
return 0;
}
__clk_core_init 在 struct clk_core 中初始化数据结构
c
/**
* __clk_core_init - 在 struct clk_core 中初始化数据结构
* @core:正在初始化clk_core
*
* 在 struct clk_core 中初始化列表,查询 parent 和 rate 的硬件,并设置它们。
*/
static int __clk_core_init(struct clk_core *core)
{
int ret;
struct clk_core *parent;
unsigned long rate;
int phase;
clk_prepare_lock();
/*
* 在抓取prepare_lock后设置 hw->core 与 clk_core_fill_parent_index() 的调用者同步,
* 我们将 hw->core 视为 NULL 为尚未注册的 clk。
* 这一点至关重要,这样 clks 在它们的父级完全注册之前不会成为父级。
*/
core->hw->core = core;
ret = clk_pm_runtime_get(core);
if (ret)
goto unlock;
/* 检查重复注册 检查是否已经注册了具有此名称的 Clock */
if (clk_core_lookup(core->name)) {
pr_debug("%s: clk %s already initialized\n",
__func__, core->name);
ret = -EEXIST;
goto out;
}
/* 验证 clk_ops 的完整性和一致性。 参见 Documentation/driver-api/clk.rst */
if (core->ops->set_rate &&
!((core->ops->round_rate || core->ops->determine_rate) &&
core->ops->recalc_rate)) {
pr_err("%s: %s must implement .round_rate or .determine_rate in addition to .recalc_rate\n",
__func__, core->name);
ret = -EINVAL;
goto out;
}
if (core->ops->set_parent && !core->ops->get_parent) {
pr_err("%s: %s must implement .get_parent & .set_parent\n",
__func__, core->name);
ret = -EINVAL;
goto out;
}
if (core->ops->set_parent && !core->ops->determine_rate) {
pr_err("%s: %s must implement .set_parent & .determine_rate\n",
__func__, core->name);
ret = -EINVAL;
goto out;
}
if (core->num_parents > 1 && !core->ops->get_parent) {
pr_err("%s: %s must implement .get_parent as it has multi parents\n",
__func__, core->name);
ret = -EINVAL;
goto out;
}
if (core->ops->set_rate_and_parent &&
!(core->ops->set_parent && core->ops->set_rate)) {
pr_err("%s: %s must implement .set_parent & .set_rate\n",
__func__, core->name);
ret = -EINVAL;
goto out;
}
/*
* 可选的平台特定魔法
*
* .init 回调不被任何基本的 clock 类型使用,但存在于奇怪的硬件中,
* 这些硬件必须为 CCF 执行初始化魔术,以获得任何其他回调的 clock 的准确视图。
* 它也可以用于执行动态分配。必须在 terminate() 回调中释放此类分配。
* 该回调不用于初始化 rate、parent 等参数 state...
*
* 如果存在,则应在时钟的任何其他回调之前调用此回调
*/
if (core->ops->init) {
ret = core->ops->init(core->hw);
if (ret)
goto out;
}
parent = core->parent = __clk_init_parent(core);
/*
* 如果 parent 已被clk_core_init,则填充 core->parent。
* 如果尚未clk_core_init parent,则将 clk 放在 orphan 列表中。
* 如果 clk 没有任何父项,则将其放在根 clk 列表中。
*
* 每次clk_init一个新的 clk 时,我们都会遍历 orphan clocks 列表,并重新父级任何是当前正在clk_init的 clock 的子 clock。
*/
if (parent) {
hlist_add_head(&core->child_node, &parent->children);
core->orphan = parent->orphan;
} else if (!core->num_parents) {
hlist_add_head(&core->child_node, &clk_root_list);
core->orphan = false;
} else {
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
}
/*
* 设置 clk 的精度。 首选方法是使用 .recalc_accuracy。
* 对于简单的 clocks 和懒惰的开发人员,默认的回退是使用 parent的 accuracy。
* 如果 clock 没有父级(或孤立),则 accuracy 设置为零(完美 clock)。
*/
if (core->ops->recalc_accuracy)
core->accuracy = core->ops->recalc_accuracy(core->hw,
clk_core_get_accuracy_no_lock(parent));
else if (parent)
core->accuracy = parent->accuracy;
else
core->accuracy = 0;
/*
* 通过 clk_core_get_phase() 缓存 phase 来设置 clk 的 phase。
* 由于根据定义,一个 phase 是相对于其 parent的,
* 因此只需查询当前 clock phase,或者只是假设它在 phase 中。
*/
phase = clk_core_get_phase(core);
if (phase < 0) {
ret = phase;
pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
core->name);
goto out;
}
/*
* 设置 clk 的 duty cycle。
*/
clk_core_update_duty_cycle_nolock(core);
/*
* 设置 clk 的 rate。 首选方法是使用 .recalc_rate。
* 对于简单的 clocks 和懒惰的开发人员,默认的回退是使用 parent's rate。
* 如果 clock 没有 parent (或孤立),则 rate 设置为零。
*/
if (core->ops->recalc_rate)
rate = core->ops->recalc_rate(core->hw,
clk_core_get_rate_nolock(parent));
else if (parent)
rate = parent->rate;
else
rate = 0;
core->rate = core->req_rate = rate;
/*
* 启用 CLK_IS_CRITICAL clocks,以便在遍历 orphan tree 和重新设置 clocks 父级时不会意外禁用新添加的 critical clocks
*/
if (core->flags & CLK_IS_CRITICAL) {
ret = clk_core_prepare(core);
if (ret) {
pr_warn("%s: critical clk '%s' failed to prepare\n",
__func__, core->name);
goto out;
}
ret = clk_core_enable_lock(core);
if (ret) {
pr_warn("%s: critical clk '%s' failed to enable\n",
__func__, core->name);
clk_core_unprepare(core);
goto out;
}
}
clk_core_reparent_orphans_nolock();
out:
clk_pm_runtime_put(core);
unlock:
if (ret) {
hlist_del_init(&core->child_node);
core->hw->core = NULL;
}
clk_prepare_unlock();
if (!ret)
clk_debug_register(core);
return ret;
}
clk_prepare_lock clk_prepare_unlock
c
/*** locking ***/
static void clk_prepare_lock(void)
{
if (!mutex_trylock(&prepare_lock)) {
if (prepare_owner == current) {
prepare_refcnt++;
return;
}
mutex_lock(&prepare_lock);
}
WARN_ON_ONCE(prepare_owner != NULL);
WARN_ON_ONCE(prepare_refcnt != 0);
prepare_owner = current;
prepare_refcnt = 1;
}
static void clk_prepare_unlock(void)
{
WARN_ON_ONCE(prepare_owner != current);
WARN_ON_ONCE(prepare_refcnt == 0);
if (--prepare_refcnt)
return;
prepare_owner = NULL;
mutex_unlock(&prepare_lock);
}
clk_core_link_consumer 将 clk 使用者添加到clk_core中的使用者列表中
c
/**
* clk_core_link_consumer -将 clk 使用者添加到clk_core中的使用者列表中
* @core: clk to add consumer to
* @clk: consumer to link to a clk
*/
static void clk_core_link_consumer(struct clk_core *core, struct clk *clk)
{
clk_prepare_lock();
hlist_add_head(&clk->clks_node, &core->clks);
clk_prepare_unlock();
}
clk_core_populate_parent_map Clk 核心填充父映射
c
static int clk_core_populate_parent_map(struct clk_core *core,
const struct clk_init_data *init)
{
u8 num_parents = init->num_parents;
const char * const *parent_names = init->parent_names;
const struct clk_hw **parent_hws = init->parent_hws;
const struct clk_parent_data *parent_data = init->parent_data;
int i, ret = 0;
struct clk_parent_map *parents, *parent;
if (!num_parents)
return 0;
/*
* Avoid unnecessary string look-ups of clk_core's possible parents by
* having a cache of names/clk_hw pointers to clk_core pointers.
*/
parents = kcalloc(num_parents, sizeof(*parents), GFP_KERNEL);
core->parents = parents;
if (!parents)
return -ENOMEM;
/* 复制所有内容,因为它可能会__initdata */
for (i = 0, parent = parents; i < num_parents; i++, parent++) {
parent->index = -1;
if (parent_names) {
/* 如果任何条目为 NULL,则抛出 WARN */
WARN(!parent_names[i],
"%s: invalid NULL in %s's .parent_names\n",
__func__, core->name);
ret = clk_cpy_name(&parent->name, parent_names[i],
true);
} else if (parent_data) {
parent->hw = parent_data[i].hw;
parent->index = parent_data[i].index;
ret = clk_cpy_name(&parent->fw_name,
parent_data[i].fw_name, false);
if (!ret)
ret = clk_cpy_name(&parent->name,
parent_data[i].name,
false);
} else if (parent_hws) {
parent->hw = parent_hws[i];
} else {
ret = -EINVAL;
WARN(1, "Must specify parents if num_parents > 0\n");
}
if (ret) {
do {
kfree_const(parents[i].name);
kfree_const(parents[i].fw_name);
} while (--i >= 0);
kfree(parents);
return ret;
}
}
return 0;
}
__clk_register 注册时钟
c
static struct clk *
__clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
{
int ret;
struct clk_core *core;
const struct clk_init_data *init = hw->init;
/*
* init 数据不应在注册路径之外使用。
* 将其设置为 NULL,以便提供程序驱动程序也无法使用它,
* 以便我们在核心的早期捕获 hw->init 的使用。
*/
hw->init = NULL;
core = kzalloc(sizeof(*core), GFP_KERNEL);
if (!core) {
ret = -ENOMEM;
goto fail_out;
}
kref_init(&core->ref);
core->name = kstrdup_const(init->name, GFP_KERNEL);
if (!core->name) {
ret = -ENOMEM;
goto fail_name;
}
if (WARN_ON(!init->ops)) {
ret = -EINVAL;
goto fail_ops;
}
core->ops = init->ops;
core->dev = dev;
clk_pm_runtime_init(core);
core->of_node = np;
if (dev && dev->driver)
core->owner = dev->driver->owner;
core->hw = hw;
core->flags = init->flags;
core->num_parents = init->num_parents;
core->min_rate = 0;
core->max_rate = ULONG_MAX;
/* 填充时钟的父时钟映射 */
ret = clk_core_populate_parent_map(core, init);
if (ret)
goto fail_parents;
INIT_HLIST_HEAD(&core->clks);
/*
* 不要在这里调用 clk_hw_create_clk(),因为这会将 provider 模块固定到自身并防止它被删除。
*/
hw->clk = alloc_clk(core, NULL, NULL);
if (IS_ERR(hw->clk)) {
ret = PTR_ERR(hw->clk);
goto fail_create_clk;
}
/* 调用 clk_core_link_consumer 将时钟与消费者链接 */
clk_core_link_consumer(core, hw->clk);
ret = __clk_core_init(core);
if (!ret)
return hw->clk;
clk_prepare_lock();
clk_core_unlink_consumer(hw->clk);
clk_prepare_unlock();
free_clk(hw->clk);
hw->clk = NULL;
fail_create_clk:
fail_parents:
fail_ops:
fail_name:
kref_put(&core->ref, __clk_release);
fail_out:
if (dev) {
dev_err_probe(dev, ret, "failed to register clk '%s' (%pS)\n",
init->name, hw);
} else {
pr_err("%pOF: error %pe: failed to register clk '%s' (%pS)\n",
np, ERR_PTR(ret), init->name, hw);
}
return ERR_PTR(ret);
}
dev_or_parent_of_node 获取 @dev 或 @dev 的父节点的设备节点
c
/**
* dev_or_parent_of_node() - 获取 @dev 或 @dev 的父节点的设备节点
* @dev:获取 设备节点的设备
*
* 返回:@dev 的设备节点指针,如果 dev 没有设备节点,则返回
* @dev->parent 的设备节点指针,如果 @dev 或 @dev->parent 都没有设备节点,则返回 NULL。
*/
static struct device_node *dev_or_parent_of_node(struct device *dev)
{
struct device_node *np;
if (!dev)
return NULL;
np = dev_of_node(dev);
if (!np)
np = dev_of_node(dev->parent);
return np;
}
clk_hw_register 注册 clk_hw 并返回错误代码
c
/**
* clk_hw_register - 注册 clk_hw 并返回错误代码
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* clk_hw_register 是用新的 clock nodes 填充 clock tree 的主要接口。
* 它返回一个等于 0 的整数(表示成功)或小于 0(表示失败)。
* 驱动程序必须在调用 clk_hw_register() 后测试错误代码。
*/
int clk_hw_register(struct device *dev, struct clk_hw *hw)
{
return PTR_ERR_OR_ZERO(__clk_register(dev, dev_or_parent_of_node(dev),
hw));
}
EXPORT_SYMBOL_GPL(clk_hw_register);
clk_core_update_orphan_status 更新 orphan 状态
c
/*
* 更新 @core 及其所有子项的孤立状态。
*/
static void clk_core_update_orphan_status(struct clk_core *core, bool is_orphan)
{
struct clk_core *child;
core->orphan = is_orphan;
hlist_for_each_entry(child, &core->children, child_node)
clk_core_update_orphan_status(child, is_orphan);
}
clk_reparent 重新父级
c
static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
{
bool was_orphan = core->orphan;
hlist_del(&core->child_node);
if (new_parent) {
bool becomes_orphan = new_parent->orphan;
/* 避免重复POST_RATE_CHANGE通知 */
if (new_parent->new_child == core)
new_parent->new_child = NULL;
hlist_add_head(&core->child_node, &new_parent->children);
if (was_orphan != becomes_orphan)
clk_core_update_orphan_status(core, becomes_orphan);
} else {
hlist_add_head(&core->child_node, &clk_orphan_list);
if (!was_orphan)
clk_core_update_orphan_status(core, true);
}
core->parent = new_parent;
}
__clk_set_parent_before __clk_set_parent_after
c
static struct clk_core *__clk_set_parent_before(struct clk_core *core,
struct clk_core *parent)
{
unsigned long flags;
struct clk_core *old_parent = core->parent;
/*
* 1.启用 CLK_OPS_PARENT_ENABLE 时钟的父项
*
* 2.迁移父级之间的 prepare 状态,并使用 clk_enable() 防止 race race 。
*
* 如果 clock 没有准备好,那么 clk_enable/disable() 的 race 是不可能的,
* 因为我们已经有了 prepare 锁(将来对 clk_enable() 的调用需要在 clk_prepare() 之前)。
*
* 如果 clock 已准备好,则将 prepared 状态迁移到新的 parent,
* 并通过强制 clock 和新的 parent 打开来防止与 clk_enable() 的竞争。
* 这确保了所有将来对 clk_enable() 的调用实际上都是硬件和软件状态的 NOP。
*
* 另请参阅:下面的 clk_set_parent() 评论。
*/
/* 如果设置了CLK_OPS_PARENT_ENABLE,请启用old_parent父项 */
if (core->flags & CLK_OPS_PARENT_ENABLE) {
clk_core_prepare_enable(old_parent);
clk_core_prepare_enable(parent);
}
/* 如果 > 0,则迁移准备计数*/
if (core->prepare_count) {
clk_core_prepare_enable(parent);
clk_core_enable_lock(core);
}
/* 更新 CLK 树拓扑 */
flags = clk_enable_lock();
clk_reparent(core, parent);
clk_enable_unlock(flags);
return old_parent;
}
static void __clk_set_parent_after(struct clk_core *core,
struct clk_core *parent,
struct clk_core *old_parent)
{
/*
* 完成 prepare 状态的迁移,并使用 clk_enable() 撤消为防止竞争而进行的更改。
*/
if (core->prepare_count) {
clk_core_disable_lock(core);
clk_core_disable_unprepare(old_parent);
}
/* 如果设置了 CLK_OPS_PARENT_ENABLE,则重新平衡 ref 计数 */
if (core->flags & CLK_OPS_PARENT_ENABLE) {
clk_core_disable_unprepare(parent);
clk_core_disable_unprepare(old_parent);
}
}
__clk_recalc_accuracies Clk 重新计算精度
c
/**
* __clk_recalc_accuracies
* @core:子树中的第一个 CLK
*
* 遍历以 clk 开头的 clks 子树,并在此过程中重新计算精度。
* 请注意,如果 clk 没有实现 .recalc_accuracy callback,则假定 clock 将采用其 parent.
*/
static void __clk_recalc_accuracies(struct clk_core *core)
{
unsigned long parent_accuracy = 0;
struct clk_core *child;
lockdep_assert_held(&prepare_lock);
if (core->parent)
parent_accuracy = core->parent->accuracy;
if (core->ops->recalc_accuracy)
core->accuracy = core->ops->recalc_accuracy(core->hw,
parent_accuracy);
else
core->accuracy = parent_accuracy;
hlist_for_each_entry(child, &core->children, child_node)
__clk_recalc_accuracies(child);
}
clk_recalc 重新计算速率
c
static unsigned long clk_recalc(struct clk_core *core,
unsigned long parent_rate)
{
unsigned long rate = parent_rate;
if (core->ops->recalc_rate && !clk_pm_runtime_get(core)) {
rate = core->ops->recalc_rate(core->hw, parent_rate);
clk_pm_runtime_put(core);
}
return rate;
}
__clk_recalc_rates 重新计算数率
c
/**
* __clk_recalc_rates
* @core:子树中的第一个 CLK
* @update_req:是否应使用新汇率更新 req_rate
* @msg:通知类型(参见 include/linux/clk.h)
*
* 遍历以 clk 开头的 clks 子树,并在此过程中重新计算 rates。
* 请注意,如果 clk 没有实现 .recalc_rate 回调,则假定 clock 将采用其 parent的速率。
*
* 如有必要,clk_recalc_rates 还会传播 POST_RATE_CHANGE 通知。
*/
static void __clk_recalc_rates(struct clk_core *core, bool update_req,
unsigned long msg)
{
unsigned long old_rate;
unsigned long parent_rate = 0;
struct clk_core *child;
lockdep_assert_held(&prepare_lock);
old_rate = core->rate;
if (core->parent)
parent_rate = core->parent->rate;
core->rate = clk_recalc(core, parent_rate);
if (update_req)
core->req_rate = core->rate;
/*
* ignore NOTIFY_STOP and NOTIFY_BAD return values for POST_RATE_CHANGE
* & ABORT_RATE_CHANGE notifiers
*/
if (core->notifier_count && msg)
__clk_notify(core, msg, old_rate, core->rate);
hlist_for_each_entry(child, &core->children, child_node)
__clk_recalc_rates(child, update_req, msg);
}
clk_core_reparent_orphans 重新为孤立的时钟(orphan clocks)分配父级时钟
- 孤立时钟是指在注册时没有父级时钟的时钟节点
- 该函数的主要目的是遍历孤立时钟列表(clk_orphan_list),为每个找到新父级的孤立时钟更新其时钟树拓扑结构,并重新计算相关的时钟参数。
c
static void clk_core_reparent_orphans_nolock(void)
{
struct clk_core *orphan;
struct hlist_node *tmp2;
/*
* 遍历全局的孤立时钟列表(clk_orphan_list)
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
/*
* 我们需要使用 __clk_set_parent_before() 和 _after() 来正确迁移 orphan clock 的任何 prepare/enable 计数。
* 这对于 CLK_IS_CRITICAL clocks 很重要,因为 clocks 在 init 期间启用,但可能还没有 parent。
*/
if (parent) {
/* 更新 CLK 树拓扑 */
__clk_set_parent_before(orphan, parent);
__clk_set_parent_after(orphan, parent, NULL);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, true, 0);
/*
* __clk_init_parent() 如果 clock 没有 clk_ops::recalc_rate并且在注册时是孤立的,则将初始 req_rate 设置为 0。
*
* clk_set_rate_range() 和 clk_put() 使用 'req_rate' 在边界被修改时触发 clk_set_rate() 调用。
* 让我们确保 'req_rate' 设置为非零值,以便 clk_set_rate_range() 不会降低频率。
*/
orphan->req_rate = orphan->rate;
}
}
}
static void clk_core_reparent_orphans(void)
{
clk_prepare_lock();
clk_core_reparent_orphans_nolock();
clk_prepare_unlock();
}
of_clk_add_hw_provider 为节点注册时钟提供程序
c
/**
* of_clk_add_hw_provider() - 为节点注册时钟提供程序
* @np: Device node pointer associated with clock provider
* @get: callback for decoding clk_hw
* @data: context pointer for @get callback.
*/
int of_clk_add_hw_provider(struct device_node *np,
struct clk_hw *(*get)(struct of_phandle_args *clkspec,
void *data),
void *data)
{
struct of_clk_provider *cp;
int ret;
if (!np)
return 0;
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
cp->node = of_node_get(np);
cp->data = data;
cp->get_hw = get;
mutex_lock(&of_clk_mutex);
list_add(&cp->link, &of_clk_providers);
mutex_unlock(&of_clk_mutex);
pr_debug("Added clk_hw provider from %pOF\n", np);
clk_core_reparent_orphans();
ret = of_clk_set_defaults(np, true);/* 通过FDT设置父节点与速率 */
if (ret < 0)
of_clk_del_provider(np);
fwnode_dev_initialized(&np->fwnode, true);
return ret;
}
EXPORT_SYMBOL_GPL(of_clk_add_hw_provider);
of_clk_hw_onecell_get 获取时钟
c
struct clk_hw *
of_clk_hw_onecell_get(struct of_phandle_args *clkspec, void *data)
{
struct clk_hw_onecell_data *hw_data = data;
unsigned int idx = clkspec->args[0];
if (idx >= hw_data->num) {
pr_err("%s: invalid index %u\n", __func__, idx);
return ERR_PTR(-EINVAL);
}
return hw_data->hws[idx];
}
EXPORT_SYMBOL_GPL(of_clk_hw_onecell_get);
of_clk_get_hw_from_clkspec of clk 从提供商处获取 hw
c
static struct clk_hw *
__of_clk_get_hw_from_provider(struct of_clk_provider *provider,
struct of_phandle_args *clkspec)
{
struct clk *clk;
if (provider->get_hw)
return provider->get_hw(clkspec, provider->data);
clk = provider->get(clkspec, provider->data);
if (IS_ERR(clk))
return ERR_CAST(clk);
return __clk_get_hw(clk);
}
static struct clk_hw *
of_clk_get_hw_from_clkspec(struct of_phandle_args *clkspec)
{
struct of_clk_provider *provider;
struct clk_hw *hw = ERR_PTR(-EPROBE_DEFER);
if (!clkspec)
return ERR_PTR(-EINVAL);
/* 检查 clkspec 中的节点是否处于禁用/失败状态 */
if (!of_device_is_available(clkspec->np))
return ERR_PTR(-ENOENT);
mutex_lock(&of_clk_mutex);
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np) {
hw = __of_clk_get_hw_from_provider(provider, clkspec);
if (!IS_ERR(hw))
break;
}
}
mutex_unlock(&of_clk_mutex);
return hw;
}
of_parse_clkspec() 解析给定设备节点的 DT 时钟说明符
c
/**
* of_parse_clkspec() - 解析给定设备节点的 DT 时钟说明符
* @np:要从中解析 clock 说明符的设备节点
* @index:要从中解析时钟的 phandle 的索引。如果索引< 0,则使用 @name
* @name:要查找和解析的 clock name。如果 name 为 NULL,则使用索引
* @out_args:解析 clock 说明符的结果
*
* 解析设备节点的 "clocks" 和 "clock-names" 属性,以查找所需索引或名称的 phandle 和 cells。
* 生成的 clock 说明符被放入 @out_args 中,或者在出现解析错误时返回 errno。
* 如果 @index 参数为非 NULL,则忽略@name 参数。
*
*例:
*
* phandle1: clock-controller@1 {
* #clock-cells = <2>;
* }
*
* phandle2: clock-controller@2 {
* #clock-cells = <1>;
* }
*
* clock-consumer@3 {
* clocks = <&phandle1 1 2 &phandle2 3>;
* clock-names = "name1", "name2";
* }
*
* 要获取 'clock-controller@2' 节点的device_node,你可以用几种不同的方式调用这个函数:
*
* of_parse_clkspec(clock-consumer@3, -1, "name2", &args);
* of_parse_clkspec(clock-consumer@3, 1, NULL, &args);
* of_parse_clkspec(clock-consumer@3, 1, "name2", &args);
*
* 返回:成功解析 clock 说明符后为 0。否则,如果 @name 为 NULL,则为 -ENOENT,如果 @name 为非 NULL,并且无法在 @np 的 "clock-names" 属性中找到,则为 -EINVAL。
*/
static int of_parse_clkspec(const struct device_node *np, int index,
const char *name, struct of_phandle_args *out_args)
{
int ret = -ENOENT;
/* 在设备树中查找匹配的 clock 属性 */
while (np) {
/*
* 对于命名 clocks,首先在 "clock-names" 属性中查找 name。
* 如果找不到,则 index 将是一个错误代码,of_parse_phandle_with_args() 将返回 -EINVAL。
*/
if (name)
index = of_property_match_string(np, "clock-names", name);
ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells",
index, out_args);
if (!ret)
break;
if (name && index >= 0)
break;
/*
*在此节点上未找到匹配的 clock。 如果父节点具有 "clock-ranges" 属性,那么我们可以尝试它的一个 clocks。
*/
np = np->parent;
if (np && !of_property_present(np, "clock-ranges"))
break;
index = 0;
}
return ret;
}
alloc_clk 分配一个 clk 消费者,但将其取消链接到 clk_core
c
/**
* alloc_clk - 分配一个 clk 消费者,但将其取消链接到 clk_core
* @core:用于为其分配 consumer 的 clk
* @dev_id:描述设备名称的字符串
* @con_id:设备上的连接 ID 字符串
*
* 返回: clk consumer 未从消费者列表中链接
*/
static struct clk *alloc_clk(struct clk_core *core, const char *dev_id,
const char *con_id)
{
struct clk *clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->core = core;
clk->dev_id = dev_id;
clk->con_id = kstrdup_const(con_id, GFP_KERNEL);
clk->max_rate = ULONG_MAX;
return clk;
}
clk_hw_create_clk 分配 clk consumer 并将其链接到给定 clk_hw 的 clk_core
c
/**
* clk_hw_create_clk:分配 clk consumer 并将其链接到给定 clk_hw 的 clk_core
* @dev:CLK 消费类设备
* @hw:与正在使用的 CLK 关联的 clk_hw
* @dev_id:描述设备名称的字符串
* @con_id:设备上的连接 ID 字符串
*
* 这是用于创建 clk 指针供 clk 消费者使用的主要函数。它将使用者分别连接到 framework 和 clk 提供程序使用的 clk_core 和 clk_hw 结构。
*/
struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw,
const char *dev_id, const char *con_id)
{
struct clk *clk;
struct clk_core *core;
/* 这是为了允许将此函数链接到其他函数 */
if (IS_ERR_OR_NULL(hw))
return ERR_CAST(hw);
core = hw->core;
clk = alloc_clk(core, dev_id, con_id);
if (IS_ERR(clk))
return clk;
clk->dev = dev;
if (!try_module_get(core->owner)) {
free_clk(clk);
return ERR_PTR(-ENOENT);
}
kref_get(&core->ref);
clk_core_link_consumer(core, clk);
return clk;
}
of_clk_get_hw 从hw获取时钟
c
struct clk_hw *of_clk_get_hw(struct device_node *np, int index,
const char *con_id)
{
int ret;
struct clk_hw *hw;
struct of_phandle_args clkspec;
ret = of_parse_clkspec(np, index, con_id, &clkspec);
if (ret)
return ERR_PTR(ret);
hw = of_clk_get_hw_from_clkspec(&clkspec);
of_node_put(clkspec.np);
return hw;
}
static struct clk *__of_clk_get(struct device_node *np,
int index, const char *dev_id,
const char *con_id)
{
struct clk_hw *hw = of_clk_get_hw(np, index, con_id);
return clk_hw_create_clk(NULL, hw, dev_id, con_id);
}
struct clk *of_clk_get(struct device_node *np, int index)
{
return __of_clk_get(np, index, np->full_name, NULL);
}
EXPORT_SYMBOL(of_clk_get);
clk_prepare clk 准备时钟
c
static int clk_core_prepare(struct clk_core *core)
{
int ret = 0;
lockdep_assert_held(&prepare_lock);
if (!core)
return 0;
if (core->prepare_count == 0) {
ret = clk_pm_runtime_get(core);
if (ret)
return ret;
ret = clk_core_prepare(core->parent);
if (ret)
goto runtime_put;
trace_clk_prepare(core);
if (core->ops->prepare)
ret = core->ops->prepare(core->hw);
trace_clk_prepare_complete(core);
if (ret)
goto unprepare;
}
core->prepare_count++;
/*
* CLK_SET_RATE_GATE 是 clock protection 的一种特殊情况 不是消费者声称独占速率控制,
* 而是提供者阻止任何消费者在准备 clock 时进行任何可能导致 rate 变化或 rate glitch 的作。
*/
if (core->flags & CLK_SET_RATE_GATE)
clk_core_rate_protect(core);
return 0;
unprepare:
clk_core_unprepare(core->parent);
runtime_put:
clk_pm_runtime_put(core);
return ret;
}
static int clk_core_prepare_lock(struct clk_core *core)
{
int ret;
clk_prepare_lock();
ret = clk_core_prepare(core);
clk_prepare_unlock();
return ret;
}
/**
* clk_prepare - 准备一个 clock source
* @clk:正在准备的 CLK
*
* clk_prepare 可能会睡觉,这与 clk_enable 区分开来。
* 在简单的情况下,如果操作可能休眠,则可以使用 clk_prepare 代替 clk_enable 来解锁 clk 。
* 一个例子是通过 I2c 访问的 clk。 在复杂情况下, clk ungate作可能需要 fast 和 slow 部分。
* 正是这个原因,clk_prepare 和 clk_enable 并不相互排斥。
* 事实上,必须在 clk_enable 之前调用 clk_prepare。成功时返回 0,否则返回 -EERROR。
*/
int clk_prepare(struct clk *clk)
{
if (!clk)
return 0;
return clk_core_prepare_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_prepare);
static int clk_core_prepare_enable(struct clk_core *core)
{
int ret;
ret = clk_core_prepare_lock(core);
if (ret)
return ret;
ret = clk_core_enable_lock(core);
if (ret)
clk_core_unprepare_lock(core);
return ret;
}
clk_enable_lock clk_enable_unlock
c
static unsigned long clk_enable_lock(void)
__acquires(enable_lock)
{
unsigned long flags;
/*
* 在 UP 系统上,spin_trylock_irqsave() 总是返回 true,即使我们已经持有锁。
* 因此,在这种情况下,我们只依赖于引用计数。
*/
if (!IS_ENABLED(CONFIG_SMP) ||
!spin_trylock_irqsave(&enable_lock, flags)) {
if (enable_owner == current) {
enable_refcnt++;
__acquire(enable_lock);
if (!IS_ENABLED(CONFIG_SMP))
local_save_flags(flags);
return flags;
}
spin_lock_irqsave(&enable_lock, flags);
}
WARN_ON_ONCE(enable_owner != NULL);
WARN_ON_ONCE(enable_refcnt != 0);
enable_owner = current;
enable_refcnt = 1;
return flags;
}
static void clk_enable_unlock(unsigned long flags)
__releases(enable_lock)
{
WARN_ON_ONCE(enable_owner != current);
WARN_ON_ONCE(enable_refcnt == 0);
if (--enable_refcnt) {
__release(enable_lock);
return;
}
enable_owner = NULL;
spin_unlock_irqrestore(&enable_lock, flags);
}
clk_core_enable 启用时钟
c
static int clk_core_enable(struct clk_core *core)
{
int ret = 0;
lockdep_assert_held(&enable_lock);
if (!core)
return 0;
if (WARN(core->prepare_count == 0,
"Enabling unprepared %s\n", core->name))
return -ESHUTDOWN;
if (core->enable_count == 0) {
ret = clk_core_enable(core->parent);
if (ret)
return ret;
trace_clk_enable(core);
if (core->ops->enable)
ret = core->ops->enable(core->hw);
trace_clk_enable_complete(core);
if (ret) {
clk_core_disable(core->parent);
return ret;
}
}
core->enable_count++;
return 0;
}
static int clk_core_enable_lock(struct clk_core *core)
{
unsigned long flags;
int ret;
flags = clk_enable_lock();
ret = clk_core_enable(core);
clk_enable_unlock(flags);
return ret;
}
clk_enable 启用时钟
c
/**
* clk_enable - 解锁时钟
* @clk:未被 UNGATED 的 CLK
*
* clk_enable 不得睡觉,这与 clk_prepare 不同。
* 在简单的情况下,如果操作作永远不会休眠,则可以使用 clk_enable 代替 clk_prepare 来解锁 clk。
* 一个例子是 SoC 内部的 clk,它通过简单的 register writes进行控制。
* 在复杂情况下, clk ungate作可能需要 fast 和 slow 部分。
* 正是这个原因,clk_enable 和 clk_prepare 并不相互排斥。
* 事实上,必须在 clk_enable 之前调用 clk_prepare。
* 成功时返回 0,否则返回 -EERROR。
*/
int clk_enable(struct clk *clk)
{
if (!clk)
return 0;
return clk_core_enable_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_enable);
clk_disable 禁用时钟
c
static void clk_core_disable(struct clk_core *core)
{
lockdep_assert_held(&enable_lock);
if (!core)
return;
if (WARN(core->enable_count == 0, "%s already disabled\n", core->name))
return;
if (WARN(core->enable_count == 1 && core->flags & CLK_IS_CRITICAL,
"Disabling critical %s\n", core->name))
return;
if (--core->enable_count > 0)
return;
trace_clk_disable(core);
if (core->ops->disable)
core->ops->disable(core->hw);
trace_clk_disable_complete(core);
clk_core_disable(core->parent);
}
static void clk_core_disable_lock(struct clk_core *core)
{
unsigned long flags;
flags = clk_enable_lock();
clk_core_disable(core);
clk_enable_unlock(flags);
}
/**
* clk_disable - 禁用时钟
* @clk:被门控的 CLK
*
* clk_disable不得睡觉,这与 clk_unprepare 不同。 在简单的情况下,如果作速度很快并且永远不会休眠,则可以使用 clk_disable 代替 clk_unprepare 来门控 clk。 一个例子是 SoC 内部的 clk,它通过简单的 register writes进行控制。 在复杂情况下,clk 门作可能需要快速和慢速部分。 正是这个原因,clk_unprepare 和 clk_disable 并不相互排斥。事实上,必须在 clk_unprepare 之前调用 clk_disable。
*/
void clk_disable(struct clk *clk)
{
if (IS_ERR_OR_NULL(clk))
return;
clk_core_disable_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_disable);
clk_prepare_enable 准备并启用时钟
c
/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
static inline int clk_prepare_enable(struct clk *clk)
{
int ret;
ret = clk_prepare(clk);
if (ret)
return ret;
ret = clk_enable(clk);
if (ret)
clk_unprepare(clk);
return ret;
}
of_clk_get_parent_name 获取时钟的父节点名称
c
const char *of_clk_get_parent_name(const struct device_node *np, int index)
{
struct of_phandle_args clkspec;
const char *clk_name;
bool found = false;
u32 pv;
int rc;
int count;
struct clk *clk;
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
&clkspec);
if (rc)
return NULL;
index = clkspec.args_count ? clkspec.args[0] : 0;
count = 0;
/* if there is an indices property, use it to transfer the index
* specified into an array offset for the clock-output-names property.
*/
of_property_for_each_u32(clkspec.np, "clock-indices", pv) {
if (index == pv) {
index = count;
found = true;
break;
}
count++;
}
/* We went off the end of 'clock-indices' without finding it */
if (of_property_present(clkspec.np, "clock-indices") && !found) {
of_node_put(clkspec.np);
return NULL;
}
if (of_property_read_string_index(clkspec.np, "clock-output-names",
index,
&clk_name) < 0) {
/*
* Best effort to get the name if the clock has been
* registered with the framework. If the clock isn't
* registered, we return the node name as the name of
* the clock as long as #clock-cells = 0.
*/
clk = of_clk_get_from_provider(&clkspec);
if (IS_ERR(clk)) {
if (clkspec.args_count == 0)
clk_name = clkspec.np->name;
else
clk_name = NULL;
} else {
clk_name = __clk_get_name(clk);
clk_put(clk);
}
}
of_node_put(clkspec.np);
return clk_name;
}
EXPORT_SYMBOL_GPL(of_clk_get_parent_name);
clk_get_rate 获取时钟频率
c
static unsigned long clk_core_get_rate_recalc(struct clk_core *core)
{
if (core && (core->flags & CLK_GET_RATE_NOCACHE))
__clk_recalc_rates(core, false, 0);
return clk_core_get_rate_nolock(core);
}
/**
* clk_get_rate - 返回 CLK 的速率
* @clk:返回 rate 的 CLK
*
* 仅返回 clk 的缓存速率,除非设置了 CLK_GET_RATE_NOCACHE 标志,这意味着将发出 recalc_rate。
* 无论 clock 启用性如何,都可以调用。如果 clk 为 NULL,或者发生错误,则返回 0。
*/
unsigned long clk_get_rate(struct clk *clk)
{
unsigned long rate;
if (!clk)
return 0;
clk_prepare_lock();
rate = clk_core_get_rate_recalc(clk->core);
clk_prepare_unlock();
return rate;
}
EXPORT_SYMBOL_GPL(clk_get_rate);
drivers/clk/clk-composite.c
__clk_hw_register_composite 注册复合时钟
c
static struct clk_hw *__clk_hw_register_composite(struct device *dev,
const char *name, const char * const *parent_names,
const struct clk_parent_data *pdata, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags)
{
struct clk_hw *hw;
struct clk_init_data init = {};
struct clk_composite *composite;
struct clk_ops *clk_composite_ops;
int ret;
composite = kzalloc(sizeof(*composite), GFP_KERNEL);
if (!composite)
return ERR_PTR(-ENOMEM);
init.name = name;
init.flags = flags;
if (parent_names)
init.parent_names = parent_names;
else
init.parent_data = pdata;
init.num_parents = num_parents;
hw = &composite->hw;
clk_composite_ops = &composite->ops;
if (mux_hw && mux_ops) {
if (!mux_ops->get_parent) {
hw = ERR_PTR(-EINVAL);
goto err;
}
composite->mux_hw = mux_hw;
composite->mux_ops = mux_ops;
clk_composite_ops->get_parent = clk_composite_get_parent;
if (mux_ops->set_parent)
clk_composite_ops->set_parent = clk_composite_set_parent;
if (mux_ops->determine_rate)
clk_composite_ops->determine_rate = clk_composite_determine_rate;
}
if (rate_hw && rate_ops) {
if (!rate_ops->recalc_rate) {
hw = ERR_PTR(-EINVAL);
goto err;
}
clk_composite_ops->recalc_rate = clk_composite_recalc_rate;
if (rate_ops->determine_rate)
clk_composite_ops->determine_rate =
clk_composite_determine_rate;
else if (rate_ops->round_rate)
clk_composite_ops->round_rate =
clk_composite_round_rate;
/* .set_rate requires either .round_rate or .determine_rate */
if (rate_ops->set_rate) {
if (rate_ops->determine_rate || rate_ops->round_rate)
clk_composite_ops->set_rate =
clk_composite_set_rate;
else
WARN(1, "%s: missing round_rate op is required\n",
__func__);
}
composite->rate_hw = rate_hw;
composite->rate_ops = rate_ops;
}
if (mux_hw && mux_ops && rate_hw && rate_ops) {
if (mux_ops->set_parent && rate_ops->set_rate)
clk_composite_ops->set_rate_and_parent =
clk_composite_set_rate_and_parent;
}
if (gate_hw && gate_ops) {
if (!gate_ops->is_enabled || !gate_ops->enable ||
!gate_ops->disable) {
hw = ERR_PTR(-EINVAL);
goto err;
}
composite->gate_hw = gate_hw;
composite->gate_ops = gate_ops;
clk_composite_ops->is_enabled = clk_composite_is_enabled;
clk_composite_ops->enable = clk_composite_enable;
clk_composite_ops->disable = clk_composite_disable;
}
init.ops = clk_composite_ops;
composite->hw.init = &init;
ret = clk_hw_register(dev, hw);
if (ret) {
hw = ERR_PTR(ret);
goto err;
}
if (composite->mux_hw)
composite->mux_hw->clk = hw->clk;
if (composite->rate_hw)
composite->rate_hw->clk = hw->clk;
if (composite->gate_hw)
composite->gate_hw->clk = hw->clk;
return hw;
err:
kfree(composite);
return hw;
}
struct clk_hw *clk_hw_register_composite(struct device *dev, const char *name,
const char * const *parent_names, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags)
{
return __clk_hw_register_composite(dev, name, parent_names, NULL,
num_parents, mux_hw, mux_ops,
rate_hw, rate_ops, gate_hw,
gate_ops, flags);
}
EXPORT_SYMBOL_GPL(clk_hw_register_composite);
drivers/clk/clk-conf.c
__set_clk_parents Set clk 父项
c
static int __set_clk_parents(struct device_node *node, bool clk_supplier)
{
struct of_phandle_args clkspec;
int index, rc, num_parents;
struct clk *clk, *pclk;
num_parents = of_count_phandle_with_args(node, "assigned-clock-parents",
"#clock-cells");
if (num_parents == -EINVAL)
pr_err("clk: invalid value of clock-parents property at %pOF\n",
node);
for (index = 0; index < num_parents; index++) {
rc = of_parse_phandle_with_args(node, "assigned-clock-parents",
"#clock-cells", index, &clkspec);
if (rc < 0) {
/* skip empty (null) phandles */
if (rc == -ENOENT)
continue;
else
return rc;
}
if (clkspec.np == node && !clk_supplier) {
of_node_put(clkspec.np);
return 0;
}
pclk = of_clk_get_from_provider(&clkspec);
of_node_put(clkspec.np);
if (IS_ERR(pclk)) {
if (PTR_ERR(pclk) != -EPROBE_DEFER)
pr_warn("clk: couldn't get parent clock %d for %pOF\n",
index, node);
return PTR_ERR(pclk);
}
rc = of_parse_phandle_with_args(node, "assigned-clocks",
"#clock-cells", index, &clkspec);
if (rc < 0)
goto err;
if (clkspec.np == node && !clk_supplier) {
of_node_put(clkspec.np);
rc = 0;
goto err;
}
clk = of_clk_get_from_provider(&clkspec);
of_node_put(clkspec.np);
if (IS_ERR(clk)) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
pr_warn("clk: couldn't get assigned clock %d for %pOF\n",
index, node);
rc = PTR_ERR(clk);
goto err;
}
rc = clk_set_parent(clk, pclk);
if (rc < 0)
pr_err("clk: failed to reparent %s to %s: %d\n",
__clk_get_name(clk), __clk_get_name(pclk), rc);
clk_put(clk);
clk_put(pclk);
}
return 0;
err:
clk_put(pclk);
return rc;
}
__set_clk_rates 设置 clk 速率
c
static int __set_clk_rates(struct device_node *node, bool clk_supplier)
{
struct of_phandle_args clkspec;
int rc, count, count_64, index;
struct clk *clk;
u64 *rates_64 __free(kfree) = NULL;
u32 *rates __free(kfree) = NULL;
count = of_property_count_u32_elems(node, "assigned-clock-rates");
count_64 = of_property_count_u64_elems(node, "assigned-clock-rates-u64");
if (count_64 > 0) {
count = count_64;
rates_64 = kcalloc(count, sizeof(*rates_64), GFP_KERNEL);
if (!rates_64)
return -ENOMEM;
rc = of_property_read_u64_array(node,
"assigned-clock-rates-u64",
rates_64, count);
} else if (count > 0) {
rates = kcalloc(count, sizeof(*rates), GFP_KERNEL);
if (!rates)
return -ENOMEM;
rc = of_property_read_u32_array(node, "assigned-clock-rates",
rates, count);
} else {
return 0;
}
if (rc)
return rc;
for (index = 0; index < count; index++) {
unsigned long rate;
if (rates_64)
rate = rates_64[index];
else
rate = rates[index];
if (rate) {
rc = of_parse_phandle_with_args(node, "assigned-clocks",
"#clock-cells", index, &clkspec);
if (rc < 0) {
/* skip empty (null) phandles */
if (rc == -ENOENT)
continue;
else
return rc;
}
if (clkspec.np == node && !clk_supplier) {
of_node_put(clkspec.np);
return 0;
}
clk = of_clk_get_from_provider(&clkspec);
of_node_put(clkspec.np);
if (IS_ERR(clk)) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
pr_warn("clk: couldn't get clock %d for %pOF\n",
index, node);
return PTR_ERR(clk);
}
rc = clk_set_rate(clk, rate);
if (rc < 0)
pr_err("clk: couldn't set %s clk rate to %lu (%d), current rate: %lu\n",
__clk_get_name(clk), rate, rc,
clk_get_rate(clk));
clk_put(clk);
}
}
return 0;
}
of_clk_set_defaults 解析和设置 assigned clocks 配置
c
/**
* of_clk_set_defaults() - 解析和设置 assigned clocks 配置
* @node: device 节点来应用时钟设置
* @clk_supplier: true,如果还应考虑 @node 提供的 clocks
*
* 此函数解析 'assigned-{clocks/clock-parents/clock-rates}' 属性并设置任何指定的 clock parents 和 rates。
* 如果 @node 也可以是其 'assigned-clocks' 或 'assigned-clock-parents' 属性中列出的任何 clock 的 clock supplier ,
* 则应将 @clk_supplier 参数设置为 true。如果 @clk_supplier 为 false,则函数在确定 @node 也是任何 clocks 的供应商后立即退出,返回 0。
*/
int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
{
int rc;
if (!node)
return 0;
rc = __set_clk_parents(node, clk_supplier);
if (rc < 0)
return rc;
return __set_clk_rates(node, clk_supplier);
}
EXPORT_SYMBOL_GPL(of_clk_set_defaults);
drivers/clk/clk-devres.c
devm框架下的时钟获取函数
此代码片段展示了Linux内核通用时钟框架(Common Clock Framework)中, 用于获取时钟句柄的devm(设备资源管理)系列接口 。这套API的核心作用是为设备驱动程序提供一个绝对安全、高度健壮且极其便利 的方式来获取其所需的时钟资源, 并将该资源的释放操作与驱动的生命周期完全自动化地绑定。
devm框架的根本原理是解决了驱动probe函数中一个最常见也最棘手的编程难题: 错误处理和资源清理 。一个典型的probe函数会按顺序获取多个资源(如时钟、内存、中断等)。如果在获取第N个资源时失败, 代码必须手动地、按相反的顺序释放前N-1个已成功获取的资源。这通常会导致复杂的、容易出错的goto链。devm框架通过将资源的获取和释放函数的注册绑定在一起, 彻底消除了这种手动清理的需要。
数据结构与释放函数 (devm_clk_state, devm_clk_release)
这是devm_clk_get机制的基础。devm_clk_state是一个小型的"管理票据", 用于记录一次成功的时钟获取; devm_clk_release则是这张"票据"对应的清理函数。
c
/*
* devm_clk_state: 一个用于devm框架内部的管理结构体.
* 它代表了一次被devm管理的成功的时钟获取操作.
*/
struct devm_clk_state {
struct clk *clk; // 指向通过 clk_get() 获取到的实际时钟对象的指针.
void (*exit)(struct clk *clk); // 一个可选的、由调用者提供的额外清理函数指针.
};
/*
* devm_clk_release: 这是注册到devm框架的"释放"回调函数.
* 当设备被卸载或驱动与设备解绑时, devm框架会自动为每一个
* devm_clk_get 成功的调用执行此函数.
* @dev: 设备指针.
* @res: 指向 devm_clk_state 管理结构体的指针.
*/
static void devm_clk_release(struct device *dev, void *res)
{
/* 将 void* 类型的 res 转换回其原始的 devm_clk_state* 类型. */
struct devm_clk_state *state = res;
/* 如果调用者在获取时钟时提供了一个额外的退出函数, 则在此处调用它. */
if (state->exit)
state->exit(state->clk);
/*
* 调用 clk_put(), 这是与 clk_get() 配对使用的标准API.
* 它会减少该时钟对象的引用计数. 当引用计数降为0时,
* 时钟框架就可以安全地禁用(disable)此时钟(如果没有任何其他使用者).
* 这是devm框架实现自动资源释放的核心所在.
*/
clk_put(state->clk);
}
核心与封装函数 (__devm_clk_get, devm_clk_get)
__devm_clk_get是实现了所有核心逻辑的内部函数, 而devm_clk_get是提供给大多数驱动使用的、简洁的API封装。
c
/*
* __devm_clk_get: devm获取时钟的通用核心实现.
* @dev: 请求时钟的设备.
* @id: 时钟的名称标识符 (在设备树的 "clock-names" 中定义).
* @get: 一个函数指针, 指向实际获取时钟的函数 (例如 clk_get).
* @init: 一个可选的初始化函数指针 (例如 clk_prepare_enable).
* @exit: 一个可选的退出/清理函数指针, 将保存在 devm_clk_state 中.
*/
static struct clk *__devm_clk_get(struct device *dev, const char *id,
struct clk *(*get)(struct device *dev, const char *id),
int (*init)(struct clk *clk),
void (*exit)(struct clk *clk))
{
struct devm_clk_state *state;
struct clk *clk;
int ret;
/* 步骤1: 分配devres管理记录. 将 devm_clk_release 作为其清理函数. */
state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL);
if (!state)
return ERR_PTR(-ENOMEM);
/* 步骤2: 调用传入的 get 函数, 执行实际的时钟获取操作. */
clk = get(dev, id);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
goto err_clk_get; /* 获取失败, 跳转到错误处理. */
}
/* 步骤3: 如果提供了可选的 init 函数, 则调用它. */
if (init) {
ret = init(clk);
if (ret)
goto err_clk_init; /* 初始化失败, 跳转到错误处理. */
}
/* 步骤4: 所有操作成功. 填充管理记录. */
state->clk = clk;
state->exit = exit;
/* 步骤5: 将管理记录正式添加到设备的资源列表中, "激活"自动清理机制. */
devres_add(dev, state);
/* 返回成功的时钟句柄. */
return clk;
err_clk_init:
/* 错误处理路径2: init失败, 必须撤销成功的get操作. */
clk_put(clk);
err_clk_get:
/* 错误处理路径1: get失败(或init失败后), 释放已分配但未使用的管理记录. */
devres_free(state);
return ERR_PTR(ret); /* 返回标准的错误指针. */
}
/*
* devm_clk_get: 供驱动程序使用的标准、便捷的API.
* @dev: 请求时钟的设备.
* @id: 时钟的名称标识符.
*/
struct clk *devm_clk_get(struct device *dev, const char *id)
{
/*
* 这是对 __devm_clk_get 的一个简单封装.
* - 它将 clk_get 作为实际的获取函数.
* - 它不提供可选的 init 和 exit 函数 (传入 NULL).
* 这覆盖了99%的驱动使用场景: 驱动只想获取一个时钟句柄, 后续会自己调用
* clk_prepare_enable/disable_unprepare.
*/
return __devm_clk_get(dev, id, clk_get, NULL, NULL);
}
/* 将此函数导出, 使其对其他内核模块可用. */
EXPORT_SYMBOL(devm_clk_get);
drivers/clk/clk-divider.c
c
/*
* DOC:基本可调分频时钟,不能选门
*
* 这个时钟的特点:
* 准备 - clk_prepare 只确保父母做好准备
* 启用 - clk_enable 仅确保启用父项
* rate - rate 是可调的。 clk->rate = ceiling(parent->rate / 除数)
* parent - 固定父级。 不支持 clk_set_parent
*/
clk_div_readl 读取寄存器的数据
c
static inline u32 clk_div_readl(struct clk_divider *divider)
{
if (divider->flags & CLK_DIVIDER_BIG_ENDIAN)
return ioread32be(divider->reg);
return readl(divider->reg);
}
_get_table_div 获取表格中的分频系数
c
struct clk_div_table {
unsigned int val;
unsigned int div;
};
static unsigned int _get_table_div(const struct clk_div_table *table,
unsigned int val)
{
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->val == val)
return clkt->div;
return 0;
}
_get_div 获取分频系数
c
static unsigned int _get_div(const struct clk_div_table *table,
unsigned int val, unsigned long flags, u8 width)
{
if (flags & CLK_DIVIDER_ONE_BASED)
return val;
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << val;
if (flags & CLK_DIVIDER_MAX_AT_ZERO)
return val ? val : clk_div_mask(width) + 1;
if (flags & CLK_DIVIDER_EVEN_INTEGERS)
return 2 * (val + 1);
if (table)
return _get_table_div(table, val);
return val + 1;
}
DIV_ROUND_UP_ULL 执行分频并向上取整
c
#define DIV_ROUND_DOWN_ULL(ll, d) \
({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; })
#define DIV_ROUND_UP_ULL(ll, d) \
DIV_ROUND_DOWN_ULL((unsigned long long)(ll) + (d) - 1, (d))
divider_recalc_rate 重新计算数率
c
unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
unsigned int val,
const struct clk_div_table *table,
unsigned long flags, unsigned long width)
{
unsigned int div;
div = _get_div(table, val, flags, width);
if (!div) {
WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
"%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
clk_hw_get_name(hw));
return parent_rate;
}
return DIV_ROUND_UP_ULL((u64)parent_rate, div);
}
EXPORT_SYMBOL_GPL(divider_recalc_rate);
clk_divider_recalc_rate 重新计算数率
c
static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
/* #define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) */
struct clk_divider *divider = to_clk_divider(hw);
unsigned int val;
val = clk_div_readl(divider) >> divider->shift;
val &= clk_div_mask(divider->width);
return divider_recalc_rate(hw, parent_rate, val, divider->table,
divider->flags, divider->width);
}
clk_divider_set_rate Clk 分频器设置速率
c
static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider *divider = to_clk_divider(hw);
int value;
unsigned long flags = 0;
u32 val;
value = divider_get_val(rate, parent_rate, divider->table,
divider->width, divider->flags);
if (value < 0)
return value;
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
else
__acquire(divider->lock);
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
val = clk_div_mask(divider->width) << (divider->shift + 16);
} else {
val = clk_div_readl(divider);
val &= ~(clk_div_mask(divider->width) << divider->shift);
}
val |= (u32)value << divider->shift;
clk_div_writel(divider, val);
if (divider->lock)
spin_unlock_irqrestore(divider->lock, flags);
else
__release(divider->lock);
return 0;
}
clk_divider_ops clk_divider_ro_ops
c
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
.determine_rate = clk_divider_determine_rate,
.set_rate = clk_divider_set_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ops);
const struct clk_ops clk_divider_ro_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
.determine_rate = clk_divider_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ro_ops);
clk_hw_register_divider Clk hw 注册分频器
c
struct clk_hw *__clk_hw_register_divider(struct device *dev,
struct device_node *np, const char *name,
const char *parent_name, const struct clk_hw *parent_hw,
const struct clk_parent_data *parent_data, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
unsigned long clk_divider_flags,
const struct clk_div_table *table, spinlock_t *lock)
{
struct clk_divider *div;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret;
if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
if (width + shift > 16) {
pr_warn("divider value exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the divider */
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
init.name = name;
if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
init.ops = &clk_divider_ro_ops;
else
init.ops = &clk_divider_ops;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.parent_hws = parent_hw ? &parent_hw : NULL;
init.parent_data = parent_data;
if (parent_name || parent_hw || parent_data)
init.num_parents = 1;
else
init.num_parents = 0;
/* struct clk_divider assignments */
div->reg = reg;
div->shift = shift;
div->width = width;
div->flags = clk_divider_flags;
div->lock = lock;
div->hw.init = &init;
div->table = table;
/* register the clock */
hw = &div->hw;
ret = clk_hw_register(dev, hw);
if (ret) {
kfree(div);
hw = ERR_PTR(ret);
}
return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_divider);
/**
* clk_hw_register_divider - 向 clock framework 注册一个 divider clock
* @dev:设备注册此时钟
* @name:此时钟的名称
* @parent_name:时钟父级的名称
* @flags:特定于框架的标志
* @reg:寄存器地址调整分频器
* @shift:移位位域的位数
* @width:位域的宽度
* @clk_divider_flags:此 clock 的 divider 特定标志
* @lock:此时钟的共享寄存器锁
*/
#define clk_hw_register_divider(dev, name, parent_name, flags, reg, shift, \
width, clk_divider_flags, lock) \
__clk_hw_register_divider((dev), NULL, (name), (parent_name), NULL, \
NULL, (flags), (reg), (shift), (width), \
(clk_divider_flags), NULL, (lock))
/**
* clk_hw_register_divider_table - 注册一个基于表的分频器 clock
* 时钟框架
* @dev:设备注册此时钟
* @name:此时钟的名称
* @parent_name:时钟父级的名称
* @flags:特定于框架的标志
* @reg:寄存器地址调整分频器
* @shift:移位位域的位数
* @width:位域的宽度
* @clk_divider_flags:此 clock 的 divider 特定标志
* @table:以 div 设置为 0 结尾的除法器/值对数组
* @lock:此时钟的共享寄存器锁
*/
#define clk_hw_register_divider_table(dev, name, parent_name, flags, reg, \
shift, width, clk_divider_flags, table, \
lock) \
__clk_hw_register_divider((dev), NULL, (name), (parent_name), NULL, \
NULL, (flags), (reg), (shift), (width), \
(clk_divider_flags), (table), (lock))
drivers/clk/clk-fixed-rate.c
clk_hw_register_fixed_rate_with_accuracy 硬件时钟注册 固定速率和精度
c
const struct clk_ops clk_fixed_rate_ops = {
.recalc_rate = clk_fixed_rate_recalc_rate,
.recalc_accuracy = clk_fixed_rate_recalc_accuracy,
};
EXPORT_SYMBOL_GPL(clk_fixed_rate_ops);
struct clk_hw *__clk_hw_register_fixed_rate(struct device *dev,
struct device_node *np, const char *name,
const char *parent_name, const struct clk_hw *parent_hw,
const struct clk_parent_data *parent_data, unsigned long flags,
unsigned long fixed_rate, unsigned long fixed_accuracy,
unsigned long clk_fixed_flags, bool devm)
{
struct clk_fixed_rate *fixed;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret = -EINVAL;
/* allocate fixed-rate clock */
if (devm)
fixed = devres_alloc(devm_clk_hw_register_fixed_rate_release,
sizeof(*fixed), GFP_KERNEL);
else
fixed = kzalloc(sizeof(*fixed), GFP_KERNEL);
if (!fixed)
return ERR_PTR(-ENOMEM);
init.name = name;1
init.ops = &clk_fixed_rate_ops;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.parent_hws = parent_hw ? &parent_hw : NULL;
init.parent_data = parent_data;
if (parent_name || parent_hw || parent_data)
init.num_parents = 1;
else
init.num_parents = 0;
/* struct clk_fixed_rate assignments */
fixed->flags = clk_fixed_flags;
fixed->fixed_rate = fixed_rate;
fixed->fixed_accuracy = fixed_accuracy;
fixed->hw.init = &init;
/* register the clock */
hw = &fixed->hw;
if (dev || !np)
ret = clk_hw_register(dev, hw);
else
ret = of_clk_hw_register(np, hw);
if (ret) {
if (devm)
devres_free(fixed);
else
kfree(fixed);
hw = ERR_PTR(ret);
} else if (devm)
devres_add(dev, fixed);
return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_fixed_rate);
/**
* clk_hw_register_fixed_rate_with_accuracy - register fixed-rate clock with
* the clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @fixed_rate: non-adjustable clock rate
* @fixed_accuracy: non-adjustable clock accuracy
*/
#define clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name, \
flags, fixed_rate, \
fixed_accuracy) \
__clk_hw_register_fixed_rate((dev), NULL, (name), (parent_name), \
NULL, NULL, (flags), (fixed_rate), \
(fixed_accuracy), 0, false)
_of_fixed_clk_setup
c
static struct clk_hw *_of_fixed_clk_setup(struct device_node *node)
{
struct clk_hw *hw;
const char *clk_name = node->name;
u32 rate;
u32 accuracy = 0;
int ret;
if (of_property_read_u32(node, "clock-frequency", &rate))
return ERR_PTR(-EIO);
of_property_read_u32(node, "clock-accuracy", &accuracy);
of_property_read_string(node, "clock-output-names", &clk_name);
hw = clk_hw_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,
0, rate, accuracy);
if (IS_ERR(hw))
return hw;
ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
if (ret) {
clk_hw_unregister_fixed_rate(hw);
return ERR_PTR(ret);
}
return hw;
}
/**
* of_fixed_clk_setup() - 简单固定速率时钟的设置功能
* @node:时钟的设备节点
*/
void __init of_fixed_clk_setup(struct device_node *node)
{
_of_fixed_clk_setup(node);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
clk_hw_register_fixed_rate 向 clock 注册固定速率 clock
c
struct clk_hw *__clk_hw_register_fixed_rate(struct device *dev,
struct device_node *np, const char *name,
const char *parent_name, const struct clk_hw *parent_hw,
const struct clk_parent_data *parent_data, unsigned long flags,
unsigned long fixed_rate, unsigned long fixed_accuracy,
unsigned long clk_fixed_flags, bool devm)
{
struct clk_fixed_rate *fixed;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret = -EINVAL;
/* 分配固定速率时钟 */
if (devm)
fixed = devres_alloc(devm_clk_hw_register_fixed_rate_release,
sizeof(*fixed), GFP_KERNEL);
else
fixed = kzalloc(sizeof(*fixed), GFP_KERNEL);
if (!fixed)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_fixed_rate_ops;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.parent_hws = parent_hw ? &parent_hw : NULL;
init.parent_data = parent_data;
if (parent_name || parent_hw || parent_data)
init.num_parents = 1;
else
init.num_parents = 0;
/* struct clk_fixed_rate assignments */
fixed->flags = clk_fixed_flags;
fixed->fixed_rate = fixed_rate;
fixed->fixed_accuracy = fixed_accuracy;
fixed->hw.init = &init;
/* register the clock */
hw = &fixed->hw;
if (dev || !np)
ret = clk_hw_register(dev, hw);
else
ret = of_clk_hw_register(np, hw);
if (ret) {
if (devm)
devres_free(fixed);
else
kfree(fixed);
hw = ERR_PTR(ret);
} else if (devm)
devres_add(dev, fixed);
return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_fixed_rate);
/**
* clk_hw_register_fixed_rate - 向 clock 注册固定速率 clock
*框架
* @dev:正在注册此时钟的设备
* @name:此时钟的名称
* @parent_name:时钟父级的名称
* @flags:特定于框架的标志
* @fixed_rate:不可调的时钟频率
*/
#define clk_hw_register_fixed_rate(dev, name, parent_name, flags, fixed_rate) \
__clk_hw_register_fixed_rate((dev), NULL, (name), (parent_name), NULL, \
NULL, (flags), (fixed_rate), 0, 0, false)
clk_hw_register_fixed_factor Clk hw 寄存器固定系数
c
static struct clk_hw *
__clk_hw_register_fixed_factor(struct device *dev, struct device_node *np,
const char *name, const char *parent_name,
const struct clk_hw *parent_hw, const struct clk_parent_data *pdata,
unsigned long flags, unsigned int mult, unsigned int div,
unsigned long acc, unsigned int fixflags, bool devm)
{
struct clk_fixed_factor *fix;
struct clk_init_data init = { };
struct clk_hw *hw;
int ret;
/* You can't use devm without a dev */
if (devm && !dev)
return ERR_PTR(-EINVAL);
if (devm)
fix = devres_alloc(devm_clk_hw_register_fixed_factor_release,
sizeof(*fix), GFP_KERNEL);
else
fix = kmalloc(sizeof(*fix), GFP_KERNEL);
if (!fix)
return ERR_PTR(-ENOMEM);
/* struct clk_fixed_factor assignments */
fix->mult = mult;
fix->div = div;
fix->hw.init = &init;
fix->acc = acc;
fix->flags = fixflags;
init.name = name;
init.ops = &clk_fixed_factor_ops;
init.flags = flags;
if (parent_name)
init.parent_names = &parent_name;
else if (parent_hw)
init.parent_hws = &parent_hw;
else
init.parent_data = pdata;
init.num_parents = 1;
hw = &fix->hw;
if (dev)
ret = clk_hw_register(dev, hw);
else
ret = of_clk_hw_register(np, hw);
if (ret) {
if (devm)
devres_free(fix);
else
kfree(fix);
hw = ERR_PTR(ret);
} else if (devm)
devres_add(dev, fix);
return hw;
}
struct clk_hw *clk_hw_register_fixed_factor(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div)
{
const struct clk_parent_data pdata = { .index = -1 };
return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, NULL,
&pdata, flags, mult, div, 0, 0, false);
}
EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor);
stm32
- clk-hse clk-lse i2s_ckin
c
clocks {
bootph-all;
clk-hse {
#clock-cells = <0x00>;
compatible = "fixed-clock";
clock-frequency = <0x17d7840>;
bootph-all;
phandle = <0x18>;
};
clk-lse {
#clock-cells = <0x00>;
compatible = "fixed-clock";
clock-frequency = <0x8000>;
bootph-all;
phandle = <0x19>;
};
i2s_ckin {
#clock-cells = <0x00>;
compatible = "fixed-clock";
clock-frequency = <0x00>;
bootph-all;
phandle = <0x1a>;
};
};
时钟驱动:of_fixed_clk_driver
c
#ifdef CONFIG_OF
static struct clk_hw *_of_fixed_clk_setup(struct device_node *node)
{
struct clk_hw *hw;
const char *clk_name = node->name;
u32 rate;
u32 accuracy = 0;
int ret;
if (of_property_read_u32(node, "clock-frequency", &rate))
return ERR_PTR(-EIO);
of_property_read_u32(node, "clock-accuracy", &accuracy);
of_property_read_string(node, "clock-output-names", &clk_name);
hw = clk_hw_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,
0, rate, accuracy);
if (IS_ERR(hw))
return hw;
ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
if (ret) {
clk_hw_unregister_fixed_rate(hw);
return ERR_PTR(ret);
}
return hw;
}
/**
* of_fixed_clk_setup() - Setup function for simple fixed rate clock
* @node: device node for the clock
*/
void __init of_fixed_clk_setup(struct device_node *node)
{
_of_fixed_clk_setup(node);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
static void of_fixed_clk_remove(struct platform_device *pdev)
{
struct clk_hw *hw = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
clk_hw_unregister_fixed_rate(hw);
}
static int of_fixed_clk_probe(struct platform_device *pdev)
{
struct clk_hw *hw;
/*
* This function is not executed when of_fixed_clk_setup
* succeeded.
*/
hw = _of_fixed_clk_setup(pdev->dev.of_node);
if (IS_ERR(hw))
return PTR_ERR(hw);
platform_set_drvdata(pdev, hw);
return 0;
}
static const struct of_device_id of_fixed_clk_ids[] = {
{ .compatible = "fixed-clock" },
{ }
};
static struct platform_driver of_fixed_clk_driver = {
.driver = {
.name = "of_fixed_clk",
.of_match_table = of_fixed_clk_ids,
},
.probe = of_fixed_clk_probe,
.remove = of_fixed_clk_remove,
};
builtin_platform_driver(of_fixed_clk_driver);
#endif
drivers/clk/clk-fixed-factor.c
固定因子时钟驱动:of_fixed_factor_clk_setup
本代码片段展示了 Linux 内核通用时钟框架(Common Clock Framework, CCF)中一个简单但非常重要的时钟驱动:固定因子时钟 。其核心功能是实现一种其频率始终是其父时钟频率乘以一个固定分数(mult/div)的时钟 。该驱动通过解析设备树(Device Tree)中的 clock-mult 和 clock-div 属性来获取这个分数,并向 CCF 注册一个新的时钟源。它采用了两种注册机制:一种是早期静态注册(CLK_OF_DECLARE),另一种是标准的平台驱动 probe 模式,以提供最大的灵活性。
代码分析
c
/**
* @brief _of_fixed_factor_clk_setup - 从设备树节点设置并注册一个固定因子时钟的核心函数。
* @param node: 时钟的设备树节点。
* @return struct clk_hw*: 成功则返回 clk_hw 指针,失败返回 ERR_PTR。
*/
static struct clk_hw *_of_fixed_factor_clk_setup(struct device_node *node)
{
// ... (变量定义) ...
// 从设备树读取 "clock-div" 属性 (分母)。
if (of_property_read_u32(node, "clock-div", &div)) {
// ... (错误处理) ...
return ERR_PTR(-EIO);
}
// 从设备树读取 "clock-mult" 属性 (分子)。
if (of_property_read_u32(node, "clock-mult", &mult)) {
// ... (错误处理) ...
return ERR_PTR(-EIO);
}
// 读取可选的 "clock-output-names" 属性作为时钟名。
of_property_read_string(node, "clock-output-names", &clk_name);
// 调用 CCF 核心函数注册一个 fixed_factor 类型的时钟硬件对象。
hw = __clk_hw_register_fixed_factor(NULL, node, clk_name, NULL, NULL,
&pdata, 0, mult, div, 0, 0, false);
if (IS_ERR(hw)) {
/*
* 清除 OF_POPULATED 标志,以便可以从 probe 函数中再次尝试注册。
*/
of_node_clear_flag(node, OF_POPULATED);
return ERR_CAST(hw);
}
// 将这个 clk_hw 注册为此时钟节点的提供者。
ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
if (ret) {
// 如果注册提供者失败,则撤销 clk_hw 的注册。
clk_hw_unregister_fixed_factor(hw);
return ERR_PTR(ret);
}
return hw;
}
/**
* @brief of_fixed_factor_clk_setup - 用于 CLK_OF_DECLARE 的早期初始化函数。
* @param node: 时钟的设备树节点。
*/
void __init of_fixed_factor_clk_setup(struct device_node *node)
{
_of_fixed_factor_clk_setup(node);
}
// 声明一个早期时钟提供者:当内核发现 compatible = "fixed-factor-clock" 的节点时,
// 会自动调用 of_fixed_factor_clk_setup。
CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock",
of_fixed_factor_clk_setup);
/**
* @brief of_fixed_factor_clk_remove - 平台驱动的 remove 函数。
* @param pdev: 平台设备。
*/
static void of_fixed_factor_clk_remove(struct platform_device *pdev)
{
struct clk_hw *clk = platform_get_drvdata(pdev);
of_clk_del_provider(pdev->dev.of_node);
clk_hw_unregister_fixed_factor(clk);
}
/**
* @brief of_fixed_factor_clk_probe - 平台驱动的 probe 函数。
* @param pdev: 平台设备。
* @return int: 成功返回0,失败返回错误码。
*/
static int of_fixed_factor_clk_probe(struct platform_device *pdev)
{
struct clk_hw *clk;
/*
* 如果 of_fixed_factor_clk_setup 早期设置成功,此函数不会被执行。
* 它只在早期设置失败后,作为后备方案被调用。
*/
clk = _of_fixed_factor_clk_setup(pdev->dev.of_node);
if (IS_ERR(clk))
return PTR_ERR(clk);
platform_set_drvdata(pdev, clk);
return 0;
}
// 定义驱动的 OF (设备树) 匹配表。
static const struct of_device_id of_fixed_factor_clk_ids[] = {
{ .compatible = "fixed-factor-clock" },
{ }
};
MODULE_DEVICE_TABLE(of, of_fixed_factor_clk_ids);
// 定义平台驱动结构体。
static struct platform_driver of_fixed_factor_clk_driver = {
.driver = {
.name = "of_fixed_factor_clk",
.of_match_table = of_fixed_factor_clk_ids,
},
.probe = of_fixed_factor_clk_probe,
.remove = of_fixed_factor_clk_remove,
};
// 将此驱动注册为内置的平台驱动。
builtin_platform_driver(of_fixed_factor_clk_driver);
drivers/clk/clk-gpio.c
门控固定时钟驱动:clk_gated_fixed_probe
本代码片段展示了 Linux 内核通用时钟框架(CCF)中一个用于GPIO 控制的固定频率时钟源 的平台设备驱动。其核心功能是实现一种其频率是固定的、但其使能/禁用状态由一个 GPIO 引脚控制的时钟 。该驱动通过解析设备树来获取固定的频率值、可选的使能 GPIO 以及可选的供电稳压器(regulator),并向 CCF 注册一个新的、带有 prepare/unprepare 操作的时钟源。它还能够智能地根据 GPIO 的类型(是否可睡眠)来选择不同的操作集,以提高性能和正确性。
实现原理分析
此驱动是 CCF 中一个典型的、将简单的板级硬件设计(例如,一个由 GPIO 控制使能端的晶振)抽象为标准内核 clk_hw 对象的范例。
-
平台驱动模型:
- 与
fixed-factor-clock不同,此驱动只使用标准的platform_driver模式进行注册。它通过of_match_table匹配设备树中compatible = "gated-fixed-clock"的节点。 - 当匹配发生时,其
clk_gated_fixed_probe函数被调用。
- 与
-
probe函数的核心逻辑:- 解析设备树属性 :
probe函数首先解析设备树节点中的一系列属性来构建时钟的行为模型:
a."clock-frequency": 一个必需的 属性,直接定义了这个时钟的固定频率。
b."clock-output-names": 可选的时钟名称。
c."vdd"supply: 通过devm_regulator_get_optional获取一个可选的稳压器。如果存在,驱动会在使能时钟前先使能这个稳压器。
d."enable"-gpios: 通过devm_gpiod_get_optional获取一个可选的 GPIO。这个 GPIO 将被用作时钟的使能/禁用开关。 - 智能选择操作集 (
ops) : 这是此驱动的一个关键优化。- 它调用
gpiod_cansleep(clk->clk_gpio.gpiod)来查询获取到的 GPIO 是否是一个"可睡眠"的 GPIO(例如,通过 I2C GPIO 扩展器连接的)。 - 如果 GPIO 可睡眠 ,它选择
clk_sleeping_gated_fixed_ops。这个操作集中的prepare函数会使用gpiod_set_value_cansleep,这是一个允许在设置 GPIO 电平时发生睡眠的函数。 - 如果 GPIO 不可睡眠 (例如,SoC 内核的内存映射 GPIO),它选择
clk_gated_fixed_ops。这个操作集中的prepare函数会使用gpiod_set_value,这是一个更快的、非睡眠的函数。 - 目的: 这种动态选择确保了驱动既能正确处理慢速总线上的 GPIO,又能在快速 GPIO 上获得最佳性能,同时避免了在原子上下文中调用可能睡眠的函数而导致的死锁。
- 它调用
- CCF 注册 :
a.CLK_HW_INIT_NO_PARENT: 这个宏用于初始化clk_hw结构。由于这是一个根时钟源(其频率是固定的,不依赖于父时钟),所以使用NO_PARENT版本。
b.devm_clk_hw_register: 将初始化好的clk_hw注册到 CCF 核心。
c.devm_of_clk_add_hw_provider: 将这个clk_hw注册为此时钟节点的提供者,使其可以被设备树中的其他设备所引用。
- 解析设备树属性 :
-
电源管理操作 (
clk_sleeping_gated_fixed_ops):- 此驱动的电源管理是通过 CCF 的
prepare/unprepare钩子实现的。 - 当一个下游设备调用
clk_prepare_enable()请求使用此时钟时,CCF 会调用.prepare回调(clk_sleeping_gated_fixed_prepare)。这个函数会先使能稳压器(如果存在),然后设置 GPIO 为高电平,从而物理上启动时钟源。 - 当最后一个使用者调用
clk_disable_unprepare()释放此时钟时,CCF 会调用.unprepare回调,该回调会以相反的顺序先设置 GPIO 为低电平,再禁用稳压器。
- 此驱动的电源管理是通过 CCF 的
代码分析
c
// ... (包含的头文件和 clk_ops 定义) ...
/**
* @brief clk_gated_fixed_probe - 门控固定时钟驱动的 probe 函数。
* @param pdev: 平台设备。
* @return int: 成功返回0,失败返回错误码。
*/
static int clk_gated_fixed_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct clk_gated_fixed *clk; /// < 驱动的私有数据结构。
const struct clk_ops *ops; /// < 指向要使用的操作集的指针。
const char *clk_name;
u32 rate;
int ret;
// 分配私有数据内存。
clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
if (!clk)
return -ENOMEM;
// 从设备树读取 "clock-frequency" 属性。
ret = device_property_read_u32(dev, "clock-frequency", &rate);
if (ret)
return dev_err_probe(dev, ret, "Failed to get clock-frequency\n");
clk->rate = rate;
// 读取可选的时钟名称。
ret = device_property_read_string(dev, "clock-output-names", &clk_name);
if (ret)
clk_name = fwnode_get_name(dev->fwnode);
// 获取可选的 "vdd" 供电稳压器。
clk->supply = devm_regulator_get_optional(dev, "vdd");
if (IS_ERR(clk->supply)) {
if (PTR_ERR(clk->supply) != -ENODEV)
return dev_err_probe(dev, PTR_ERR(clk->supply),
"Failed to get regulator\n");
clk->supply = NULL;
}
// 获取可选的 "enable" GPIO。
clk->clk_gpio.gpiod = devm_gpiod_get_optional(dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(clk->clk_gpio.gpiod))
return dev_err_probe(dev, PTR_ERR(clk->clk_gpio.gpiod),
"Failed to get gpio\n");
// 根据 GPIO 是否可以睡眠,智能选择不同的操作集。
if (gpiod_cansleep(clk->clk_gpio.gpiod))
ops = &clk_sleeping_gated_fixed_ops;
else
ops = &clk_gated_fixed_ops;
// 初始化 clk_hw 结构,指定名称、操作集和标志。这是一个根时钟,没有父时钟。
clk->clk_gpio.hw.init = CLK_HW_INIT_NO_PARENT(clk_name, ops, 0);
/* 注册时钟 */
// 将 clk_hw 注册到 CCF 核心。
ret = devm_clk_hw_register(dev, &clk->clk_gpio.hw);
if (ret)
return dev_err_probe(dev, ret,
"Failed to register clock\n");
// 将 clk_hw 注册为此时钟节点的提供者。
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
&clk->clk_gpio.hw);
if (ret)
return dev_err_probe(dev, ret,
"Failed to register clock provider\n");
return 0;
}
// 定义驱动的 OF (设备树) 匹配表。
static const struct of_device_id gated_fixed_clk_match_table[] = {
{ .compatible = "gated-fixed-clock" },
{ /* sentinel */ }
};
// 定义平台驱动结构体。
static struct platform_driver gated_fixed_clk_driver = {
.probe = clk_gated_fixed_probe,
.driver = {
.name = "gated-fixed-clk",
.of_match_table = gated_fixed_clk_match_table,
},
};
// 将此驱动注册为内置的平台驱动。
builtin_platform_driver(gated_fixed_clk_driver);
drivers/clk/clk-gate.c
c
/**
* DOC:基本的可作时钟,可以门控和解控其输出
*
* 这个时钟的特点:
* prepare - clk_(un)prepare) 仅确保父 (un) 已准备好
* 启用 - clk_enable和clk_disable功能正常并控制门控
* rate - 从父级继承 rate。 不支持 clk_set_rate
* parent - 固定父级。 不支持 clk_set_parent
*/
clk_gate_readl 读取寄存器
c
static inline u32 clk_gate_readl(struct clk_gate *gate)
{
if (gate->flags & CLK_GATE_BIG_ENDIAN)
return ioread32be(gate->reg);
return readl(gate->reg);
}
clk_gate_writel 写入寄存器
c
static inline void clk_gate_writel(struct clk_gate *gate, u32 val)
{
if (gate->flags & CLK_GATE_BIG_ENDIAN)
iowrite32be(val, gate->reg);
else
writel(val, gate->reg);
}
clk_gate_endisable
c
/*
* It works on following logic:
*
* For enabling clock, enable = 1
* set2dis = 1 -> clear bit -> set = 0
* set2dis = 0 -> set bit -> set = 1
*
* For disabling clock, enable = 0
* set2dis = 1 -> set bit -> set = 1
* set2dis = 0 -> clear bit -> set = 0
*
* So, result is always: enable xor set2dis.
*/
static void clk_gate_endisable(struct clk_hw *hw, int enable)
{
struct clk_gate *gate = to_clk_gate(hw);
int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
unsigned long flags;
u32 reg;
set ^= enable;
if (gate->lock)
spin_lock_irqsave(gate->lock, flags);
else
__acquire(gate->lock);
if (gate->flags & CLK_GATE_HIWORD_MASK) {
reg = BIT(gate->bit_idx + 16);
if (set)
reg |= BIT(gate->bit_idx);
} else {
reg = clk_gate_readl(gate);
if (set)
reg |= BIT(gate->bit_idx);
else
reg &= ~BIT(gate->bit_idx);
}
clk_gate_writel(gate, reg);
if (gate->lock)
spin_unlock_irqrestore(gate->lock, flags);
else
__release(gate->lock);
}
static int clk_gate_enable(struct clk_hw *hw)
{
clk_gate_endisable(hw, 1);
return 0;
}
static void clk_gate_disable(struct clk_hw *hw)
{
clk_gate_endisable(hw, 0);
}
int clk_gate_is_enabled(struct clk_hw *hw)
{
u32 reg;
struct clk_gate *gate = to_clk_gate(hw);
reg = clk_gate_readl(gate);
/* 如果 SET BIT 禁用了此 CLK,请在 Masking 之前将其翻转 */
if (gate->flags & CLK_GATE_SET_TO_DISABLE)
reg ^= BIT(gate->bit_idx);
reg &= BIT(gate->bit_idx);
return reg ? 1 : 0;
}
EXPORT_SYMBOL_GPL(clk_gate_is_enabled);
clk_gate_ops
c
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable,
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gate_ops);
clk_hw_register_gate 注册门控时钟
c
struct clk_hw *__clk_hw_register_gate(struct device *dev,
struct device_node *np, const char *name,
const char *parent_name, const struct clk_hw *parent_hw,
const struct clk_parent_data *parent_data,
unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
{
struct clk_gate *gate;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret = -EINVAL;
if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
if (bit_idx > 15) {
pr_err("gate bit exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the gate */
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_gate_ops;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.parent_hws = parent_hw ? &parent_hw : NULL;
init.parent_data = parent_data;
if (parent_name || parent_hw || parent_data)
init.num_parents = 1;
else
init.num_parents = 0;
/* struct clk_gate assignments */
gate->reg = reg;
gate->bit_idx = bit_idx;
gate->flags = clk_gate_flags;
gate->lock = lock;
gate->hw.init = &init;
hw = &gate->hw;
if (dev || !np)
ret = clk_hw_register(dev, hw);
else if (np)
ret = of_clk_hw_register(np, hw);
if (ret) {
kfree(gate);
hw = ERR_PTR(ret);
}
return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_gate);
/**
* clk_hw_register_gate - 向 Clock 框架注册一个门 clock
* @dev:正在注册此时钟的设备
* @name:此时钟的名称
* @parent_name : 此时钟的父级名称
* @flags:此 clock 的框架特定标志
* @reg:寄存器地址,用于控制此时钟的门控
* @bit_idx:寄存器中的哪个位控制该时钟的选通
* @clk_gate_flags:此 clock 的 gate 特定标志
* @lock:此时钟的共享寄存器锁
*/
#define clk_hw_register_gate(dev, name, parent_name, flags, reg, bit_idx, \
clk_gate_flags, lock) \
__clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \
NULL, (flags), (reg), (bit_idx), \
(clk_gate_flags), (lock))
drivers/clk/clk-mux.c
c
/*
* DOC:基本可调多路复用器时钟,不能门
*
* 这个时钟的特点:
* 准备 - clk_prepare 只确保父母做好准备
* 启用 - clk_enable 仅确保启用父项
* rate - 速率仅受父交换的影响。 不支持 clk_set_rate
* 父级 - 父级可通过clk_set_parent进行调整
*/
clk_mux_readl 返回mux的寄存器地址内的数据
c
static inline u32 clk_mux_readl(struct clk_mux *mux)
{
if (mux->flags & CLK_MUX_BIG_ENDIAN)
return ioread32be(mux->reg);
return readl(mux->reg);
}
clk_mux_val_to_index 根据读取值返回索引
c
int clk_mux_val_to_index(struct clk_hw *hw, const u32 *table, unsigned int flags,
unsigned int val)
{
/* hw->core->num_parents */
int num_parents = clk_hw_get_num_parents(hw);
if (table) {
int i;
for (i = 0; i < num_parents; i++)
if (table[i] == val)
return i;
return -EINVAL;
}
if (val && (flags & CLK_MUX_INDEX_BIT))
val = ffs(val) - 1;
if (val && (flags & CLK_MUX_INDEX_ONE))
val--;
if (val >= num_parents)
return -EINVAL;
return val;
}
EXPORT_SYMBOL_GPL(clk_mux_val_to_index);
clk_mux_get_parent 复用时钟获取父设备
c
static u8 clk_mux_get_parent(struct clk_hw *hw)
{
/* #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) */
struct clk_mux *mux = to_clk_mux(hw);
u32 val;
val = clk_mux_readl(mux) /* 通过读取reg地址获取配置 */
>> mux->shift; /* 进行偏移与mask确认是哪个配置生效 */
val &= mux->mask;
return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
}
clk_mux_ops clk_mux_ro_ops
c
const struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
.determine_rate = clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_mux_ops);
const struct clk_ops clk_mux_ro_ops = {
.get_parent = clk_mux_get_parent,
};
EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
clk_hw_register_mux 注册多路复用器时钟
c
struct clk_hw *__clk_hw_register_mux(struct device *dev, struct device_node *np,
const char *name, u8 num_parents,
const char * const *parent_names,
const struct clk_hw **parent_hws,
const struct clk_parent_data *parent_data,
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, const u32 *table, spinlock_t *lock)
{
struct clk_mux *mux;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret = -EINVAL;
if (clk_mux_flags & CLK_MUX_HIWORD_MASK) {
u8 width = fls(mask) - ffs(mask) + 1;
if (width + shift > 16) {
pr_err("mux value exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the mux */
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
init.name = name;
if (clk_mux_flags & CLK_MUX_READ_ONLY)
init.ops = &clk_mux_ro_ops;
else
init.ops = &clk_mux_ops;
init.flags = flags;
init.parent_names = parent_names;
init.parent_data = parent_data;
init.parent_hws = parent_hws;
init.num_parents = num_parents;
/* struct clk_mux assignments */
mux->reg = reg;
mux->shift = shift;
mux->mask = mask;
mux->flags = clk_mux_flags;
mux->lock = lock;
mux->table = table;
mux->hw.init = &init;
hw = &mux->hw;
if (dev || !np)
ret = clk_hw_register(dev, hw);
else if (np)
ret = of_clk_hw_register(np, hw);
if (ret) {
kfree(mux);
hw = ERR_PTR(ret);
}
return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_mux);
#define clk_hw_register_mux(dev, name, parent_names, num_parents, flags, reg, \
shift, width, clk_mux_flags, lock) \
__clk_hw_register_mux((dev), NULL, (name), (num_parents), \
(parent_names), NULL, NULL, (flags), (reg), \
(shift), BIT((width)) - 1, (clk_mux_flags), \
NULL, (lock))
drivers/clk/clkdev.c
clk_get
c
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk_hw *hw;
if (dev && dev->of_node) {
hw = of_clk_get_hw(dev->of_node, 0, con_id);
if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER)
return clk_hw_create_clk(dev, hw, dev_id, con_id);
}
return __clk_get_sys(dev, dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);
drivers/clk/clk-stm32h7.c
stm32_mclk stm32 mux clk 复合时钟
c
/* System clock parent */
static const char * const sys_src[] = {
"hsi_ck",**** "csi_ck", "hse_ck", "pll1_p" };
static const char * const tracein_src[] = {
"hsi_ck", "**csi_ck**", "hse_ck", "pll1_r" };
static const char * const per_src[] = {
"hsi_ker", "csi_ker", "hse_ck", "disabled" };
static const char * const pll_src[] = {
"hsi_ck", "csi_ck", "hse_ck", "no clock" };
/* MUX clock configuration */
struct stm32_mux_clk {
const char *name;
const char * const *parents;
u8 num_parents;
u32 offset;
u8 shift;
u8 width;
u32 flags;
};
#define M_MCLOCF(_name, _parents, _mux_offset, _mux_shift, _mux_width, _flags)\
{\
.name = _name,\
.parents = _parents,\
.num_parents = ARRAY_SIZE(_parents),\
.offset = _mux_offset,\
.shift = _mux_shift,\
.width = _mux_width,\
.flags = _flags,\
}
#define M_MCLOC(_name, _parents, _mux_offset, _mux_shift, _mux_width)\
M_MCLOCF(_name, _parents, _mux_offset, _mux_shift, _mux_width, 0)\
static const struct stm32_mux_clk stm32_mclk[] __initconst = {
M_MCLOC("per_ck", per_src, RCC_D1CCIPR, 28, 3),
M_MCLOC("pllsrc", pll_src, RCC_PLLCKSELR, 0, 3),
M_MCLOC("sys_ck", sys_src, RCC_CFGR, 0, 3),
/* 输入时钟信号,可能用于调试或跟踪功能(trace) */
M_MCLOC("tracein_ck", tracein_src, RCC_CFGR, 0, 3),
};
clk_register_stm32_timer_ker 注册STM32定时器KER
c
static unsigned long timer_ker_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct timer_ker *clk_elem = to_timer_ker(hw);
u32 timpre;
u32 dppre_shift = clk_elem->dppre_shift;
u32 prescaler;
u32 mul;
/* 定时器时钟预分频器选择 (Timers clocks prescaler selection)
此位由软件置 1 和复位,用于控制连接到 APB1 和 APB2 域的所有定时器的时钟频率。
0:如果 D2PPREx 对应于 1 或 2 分频,则定时器内核时钟为 rcc_hclk1;否则为 2 x Frcc_pclkx_d2
(复位后的默认值)
1:如果 D2PPREx 对应于 1、2 或 4 分频,则定时器内核时钟为 rcc_hclk1;否则为 4 x Frcc_pclkx_d2
请参考表 47:时钟定时器与 pclk 之比。
*/
timpre = (readl(base + RCC_CFGR) >> 15) & 0x01;
/* 由软件置 1 和复位,用于控制 D2 域 APB1 时钟分频系数。
在 D2PPRE1 写入后,时钟将由介于 1 到 16 个 rcc_hclk1 周期之间的新预分频系数进行分频。
0xx:rcc_pclk1 = rcc_hclk1(复位后的默认值)
100:rcc_pclk1 = rcc_hclk1 / 2
101:rcc_pclk1 = rcc_hclk1 / 4
110:rcc_pclk1 = rcc_hclk1 / 8
111:rcc_pclk1 = rcc_hclk1 / 16 */
prescaler = (readl(base + RCC_D2CFGR) >> dppre_shift) & 0x03;
mul = 2;
if (prescaler < 4)
mul = 1;
else if (timpre && prescaler > 4)
mul = 4;
return parent_rate * mul;
}
static const struct clk_ops timer_ker_ops = {
.recalc_rate = timer_ker_recalc_rate,
};
static struct clk_hw *clk_register_stm32_timer_ker(struct device *dev,
const char *name, const char *parent_name,
unsigned long flags,
u8 dppre_shift,
spinlock_t *lock)
{
struct timer_ker *element;
struct clk_init_data init;
struct clk_hw *hw;
int err;
element = kzalloc(sizeof(*element), GFP_KERNEL);
if (!element)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &timer_ker_ops;
init.flags = flags;
init.parent_names = &parent_name;
init.num_parents = 1;
element->hw.init = &init;
element->lock = lock;
element->dppre_shift = dppre_shift;
hw = &element->hw;
err = clk_hw_register(dev, hw);
if (err) {
kfree(element);
return ERR_PTR(err);
}
return hw;
}
register_core_and_bus_clocks 寄存器 core 和 bus clocks
c
struct clk_div_table {
unsigned int val; //寄存器读取到的匹配值
unsigned int div; //所对应的分频系数
};
static const struct clk_div_table d1cpre_div_table[] = {
{ 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1},
{ 4, 1 }, { 5, 1 }, { 6, 1 }, { 7, 1},
{ 8, 2 }, { 9, 4 }, { 10, 8 }, { 11, 16 },
{ 12, 64 }, { 13, 128 }, { 14, 256 },
{ 15, 512 },
{ 0 },
};
static const struct clk_div_table ppre_div_table[] = {
{ 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1},
{ 4, 2 }, { 5, 4 }, { 6, 8 }, { 7, 16 },
{ 0 },
};
static void register_core_and_bus_clocks(void)
{
/* 核心和总线 */
hws[SYS_D1CPRE] = clk_hw_register_divider_table(NULL, "d1cpre",
"sys_ck", CLK_IGNORE_UNUSED, base + RCC_D1CFGR, 8, 4, 0,
d1cpre_div_table, &stm32rcc_lock);
hws[HCLK] = clk_hw_register_divider_table(NULL, "hclk", "d1cpre",
CLK_IGNORE_UNUSED, base + RCC_D1CFGR, 0, 4, 0,
d1cpre_div_table, &stm32rcc_lock);
/* D1 DOMAIN */
/* CPU Systick
父设备时钟: d1cpre
分频系数: 8 */
hws[CPU_SYSTICK] = clk_hw_register_fixed_factor(NULL, "systick",
"d1cpre", 0, 1, 8);
/* * APB3 peripheral */
hws[PCLK3] = clk_hw_register_divider_table(NULL, "pclk3", "hclk", 0,
base + RCC_D1CFGR, 4, 3, 0,
ppre_div_table, &stm32rcc_lock);
/* D2 DOMAIN */
/* * APB1 peripheral */
hws[PCLK1] = clk_hw_register_divider_table(NULL, "pclk1", "hclk", 0,
base + RCC_D2CFGR, 4, 3, 0,
ppre_div_table, &stm32rcc_lock);
/* Timers prescaler clocks */
clk_register_stm32_timer_ker(NULL, "tim1_ker", "pclk1", 0,
4, &stm32rcc_lock);
/* * APB2 peripheral */
hws[PCLK2] = clk_hw_register_divider_table(NULL, "pclk2", "hclk", 0,
base + RCC_D2CFGR, 8, 3, 0, ppre_div_table,
&stm32rcc_lock);
clk_register_stm32_timer_ker(NULL, "tim2_ker", "pclk2", 0, 8,
&stm32rcc_lock);
/* D3 DOMAIN */
/* * APB4 peripheral */
hws[PCLK4] = clk_hw_register_divider_table(NULL, "pclk4", "hclk", 0,
base + RCC_D3CFGR, 4, 3, 0,
ppre_div_table, &stm32rcc_lock);
}
stm32_oclk stm32 晶振时钟
c
/* Oscillary clock configuration */
struct stm32_osc_clk {
const char *name;
const char *parent;
u32 gate_offset;
u8 bit_idx;
u8 bit_rdy;
u32 flags;
};
#define OSC_CLKF(_name, _parent, _gate_offset, _bit_idx, _bit_rdy, _flags)\
{\
.name = _name,\
.parent = _parent,\
.gate_offset = _gate_offset,\
.bit_idx = _bit_idx,\
.bit_rdy = _bit_rdy,\
.flags = _flags,\
}
#define OSC_CLK(_name, _parent, _gate_offset, _bit_idx, _bit_rdy)\
OSC_CLKF(_name, _parent, _gate_offset, _bit_idx, _bit_rdy, 0)
static const struct stm32_osc_clk stm32_oclk[] __initconst = {
OSC_CLKF("hsi_ck", "hsidiv", RCC_CR, 0, 2, CLK_IGNORE_UNUSED),
OSC_CLKF("hsi_ker", "hsidiv", RCC_CR, 1, 2, CLK_IGNORE_UNUSED),
OSC_CLKF("csi_ck", "clk-csi", RCC_CR, 7, 8, CLK_IGNORE_UNUSED),
OSC_CLKF("csi_ker", "clk-csi", RCC_CR, 9, 8, CLK_IGNORE_UNUSED),
OSC_CLKF("rc48_ck", "clk-rc48", RCC_CR, 12, 13, CLK_IGNORE_UNUSED),
OSC_CLKF("lsi_ck", "clk-lsi", RCC_CSR, 0, 1, CLK_IGNORE_UNUSED),
};
clk_register_ready_gate Clk 注册门控时钟
c
#define RGATE_TIMEOUT 10000
static int ready_gate_clk_enable(struct clk_hw *hw)
{
struct clk_gate *gate = to_clk_gate(hw);
struct stm32_ready_gate *rgate = to_ready_gate_clk(gate);
int bit_status;
unsigned int timeout = RGATE_TIMEOUT;
/* 时钟已经就绪,不在使能 */
if (clk_gate_ops.is_enabled(hw))
return 0;
clk_gate_ops.enable(hw);
/* 我们不能使用 readl_poll_timeout(),因为如果有人在 clocksource 更改之前启用这个 clock,我们可以阻止。
* 只有 jiffies 计数器可用。
* Jiffies 会因中断而增加,并且 enable op 不允许中断。
*/
do {
bit_status = !(readl(gate->regn) & BIT(rgate->bit_rdy));
if (bit_status)
udelay(100);
} while (bit_status && --timeout);
return bit_status;
}
static void ready_gate_clk_disable(struct clk_hw *hw)
{
struct clk_gate *gate = to_clk_gate(hw);
struct stm32_ready_gate *rgate = to_ready_gate_clk(gate);
int bit_status;
unsigned int timeout = RGATE_TIMEOUT;
if (!clk_gate_ops.is_enabled(hw))
return;
clk_gate_ops.disable(hw);
do {
bit_status = !!(readl(gate->reg) & BIT(rgate->bit_rdy));
if (bit_status)
udelay(100);
} while (bit_status && --timeout);
}
static const struct clk_ops ready_gate_clk_ops = {
.enable = ready_gate_clk_enable,
.disable = ready_gate_clk_disable,
.is_enabled = clk_gate_is_enabled,
};
static struct clk_hw *clk_register_ready_gate(struct device *dev,
const char *name, const char *parent_name,
void __iomem *reg, u8 bit_idx, u8 bit_rdy,
unsigned long flags, spinlock_t *lock)
{
struct stm32_ready_gate *rgate;
struct clk_init_data init = { NULL };
struct clk_hw *hw;
int ret;
rgate = kzalloc(sizeof(*rgate), GFP_KERNEL);
if (!rgate)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &ready_gate_clk_ops;
init.flags = flags;
init.parent_names = &parent_name;
init.num_parents = 1;
rgate->bit_rdy = bit_rdy;
rgate->gate.lock = lock;
rgate->gate.reg = reg;
rgate->gate.bit_idx = bit_idx;
rgate->gate.hw.init = &init;
hw = &rgate->gate.hw;
ret = clk_hw_register(dev, hw);
if (ret) {
kfree(rgate);
hw = ERR_PTR(ret);
}
return hw;
}
get_cfg_composite_div 获取 cfg 复合 div
c
/* ODF CLOCKS */
static unsigned long odf_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return clk_divider_ops.recalc_rate(hw, parent_rate);
}
static int odf_divider_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
return clk_divider_ops.determine_rate(hw, req);
}
static int odf_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_hw *hwp;
int pll_status;
int ret;
hwp = clk_hw_get_parent(hw);
pll_status = pll_is_enabled(hwp);
if (pll_status)
pll_disable(hwp);
ret = clk_divider_ops.set_rate(hw, rate, parent_rate);
if (pll_status)
pll_enable(hwp);
return ret;
}
static const struct clk_ops odf_divider_ops = {
.recalc_rate = odf_divider_recalc_rate,
.determine_rate = odf_divider_determine_rate,
.set_rate = odf_divider_set_rate,
};
static int odf_gate_enable(struct clk_hw *hw)
{
struct clk_hw *hwp;
int pll_status;
int ret;
if (clk_gate_ops.is_enabled(hw))
return 0;
hwp = clk_hw_get_parent(hw);
pll_status = pll_is_enabled(hwp);
if (pll_status)
pll_disable(hwp);
ret = clk_gate_ops.enable(hw);
if (pll_status)
pll_enable(hwp);
return ret;
}
static void odf_gate_disable(struct clk_hw *hw)
{
struct clk_hw *hwp;
int pll_status;
if (!clk_gate_ops.is_enabled(hw))
return;
hwp = clk_hw_get_parent(hw);
pll_status = pll_is_enabled(hwp);
if (pll_status)
pll_disable(hwp);
clk_gate_ops.disable(hw);
if (pll_status)
pll_enable(hwp);
}
static const struct clk_ops odf_gate_ops = {
.enable = odf_gate_enable,
.disable = odf_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
#define M_CFG_MUX(_mux_ops, _mux_flags)\
.mux = &(struct composite_clk_gcfg_t) { _mux_flags, _mux_ops}
#define M_CFG_DIV(_rate_ops, _rate_flags)\
.div = &(struct composite_clk_gcfg_t) {_rate_flags, _rate_ops}
#define M_CFG_GATE(_gate_ops, _gate_flags)\
.gate = &(struct composite_clk_gcfg_t) { _gate_flags, _gate_ops}
static struct composite_clk_gcfg odf_clk_gcfg = {
M_CFG_DIV(&odf_divider_ops, 0),
M_CFG_GATE(&odf_gate_ops, 0),
};
static struct clk_mux *_get_cmux(void __iomem *reg, u8 shift, u8 width,
u32 flags, spinlock_t *lock)
{
struct clk_mux *mux;
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
mux->reg = reg;
mux->shift = shift;
mux->mask = (1 << width) - 1;
mux->flags = flags;
mux->lock = lock;
return mux;
}
static struct clk_divider *_get_cdiv(void __iomem *reg, u8 shift, u8 width,
u32 flags, spinlock_t *lock)
{
struct clk_divider *div;
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
div->reg = reg;
div->shift = shift;
div->width = width;
div->flags = flags;
div->lock = lock;
return div;
}
static struct clk_gate *_get_cgate(void __iomem *reg, u8 bit_idx, u32 flags,
spinlock_t *lock)
{
struct clk_gate *gate;
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
gate->reg = reg;
gate->bit_idx = bit_idx;
gate->flags = flags;
gate->lock = lock;
return gate;
}
#define M_ODF_F(_name, _parent, _gate_offset, _bit_idx, _rate_offset,\
_rate_shift, _rate_width, _flags)\
{\
.mux = NULL,\
.div = &(struct muxdiv_cfg) {_rate_offset, _rate_shift, _rate_width},\
.gate = &(struct gate_cfg) {_gate_offset, _bit_idx },\
.name = _name,\
.parent_name = &(const char *) {_parent},\
.num_parents = 1,\
.flags = _flags,\
}
#define M_ODF(_name, _parent, _gate_offset, _bit_idx, _rate_offset,\
_rate_shift, _rate_width)\
M_ODF_F(_name, _parent, _gate_offset, _bit_idx, _rate_offset,\
_rate_shift, _rate_width, 0)\
static const struct composite_clk_cfg stm32_odf[3][3] = {
{
M_ODF_F("pll1_p", "vco1", RCC_PLLCFGR, 16, RCC_PLL1DIVR, 9, 7,
CLK_IGNORE_UNUSED),
M_ODF_F("pll1_q", "vco1", RCC_PLLCFGR, 17, RCC_PLL1DIVR, 16, 7,
CLK_IGNORE_UNUSED),
M_ODF_F("pll1_r", "vco1", RCC_PLLCFGR, 18, RCC_PLL1DIVR, 24, 7,
CLK_IGNORE_UNUSED),
},
{
M_ODF("pll2_p", "vco2", RCC_PLLCFGR, 19, RCC_PLL2DIVR, 9, 7),
M_ODF("pll2_q", "vco2", RCC_PLLCFGR, 20, RCC_PLL2DIVR, 16, 7),
M_ODF("pll2_r", "vco2", RCC_PLLCFGR, 21, RCC_PLL2DIVR, 24, 7),
},
{
M_ODF("pll3_p", "vco3", RCC_PLLCFGR, 22, RCC_PLL3DIVR, 9, 7),
M_ODF("pll3_q", "vco3", RCC_PLLCFGR, 23, RCC_PLL3DIVR, 16, 7),
M_ODF("pll3_r", "vco3", RCC_PLLCFGR, 24, RCC_PLL3DIVR, 24, 7),
}
};
static void get_cfg_composite_div(const struct composite_clk_gcfg *gcfg,
const struct composite_clk_cfg *cfg,
struct composite_cfg *composite, spinlock_t *lock)
{
struct clk_mux *mux = NULL;
struct clk_divider *div = NULL;
struct clk_gate *gate = NULL;
const struct clk_ops *mux_ops, *div_ops, *gate_ops;
struct clk_hw *mux_hw;
struct clk_hw *div_hw;
struct clk_hw *gate_hw;
mux_ops = div_ops = gate_ops = NULL;
mux_hw = div_hw = gate_hw = NULL;
if (gcfg->mux && cfg->mux) {
mux = _get_cmux(base + cfg->mux->offset,
cfg->mux->shift,
cfg->mux->width,
gcfg->mux->flags, lock);
if (!IS_ERR(mux)) {
mux_hw = &mux->hw;
mux_ops = gcfg->mux->ops ?
gcfg->mux->ops : &clk_mux_ops;
}
}
if (gcfg->div && cfg->div) {
div = _get_cdiv(base + cfg->div->offset,
cfg->div->shift,
cfg->div->width,
gcfg->div->flags, lock);
if (!IS_ERR(div)) {
div_hw = &div->hw;
div_ops = gcfg->div->ops ?
gcfg->div->ops : &clk_divider_ops;
}
}
if (gcfg->gate && cfg->gate) {
gate = _get_cgate(base + cfg->gate->offset,
cfg->gate->bit_idx,
gcfg->gate->flags, lock);
if (!IS_ERR(gate)) {
gate_hw = &gate->hw;
gate_ops = gcfg->gate->ops ?
gcfg->gate->ops : &clk_gate_ops;
}
}
composite->mux_hw = mux_hw;
composite->mux_ops = mux_ops;
composite->div_hw = div_hw;
composite->div_ops = div_ops;
composite->gate_hw = gate_hw;
composite->gate_ops = gate_ops;
}
clk_register_stm32_pll
c
/* PLL configuration */
struct st32h7_pll_cfg {
u8 bit_idx;
u32 offset_divr;
u8 bit_frac_en;
u32 offset_frac;
u8 divm;
};
struct stm32_pll_data {
const char *name;
const char *parent_name;
unsigned long flags;
const struct st32h7_pll_cfg *cfg;
};
static const struct st32h7_pll_cfg stm32h7_pll1 = {
.bit_idx = 24,
.offset_divr = RCC_PLL1DIVR,
.bit_frac_en = 0,
.offset_frac = RCC_PLL1FRACR,
.divm = 4,
};
static const struct st32h7_pll_cfg stm32h7_pll2 = {
.bit_idx = 26,
.offset_divr = RCC_PLL2DIVR,
.bit_frac_en = 4,
.offset_frac = RCC_PLL2FRACR,
.divm = 12,
};
static const struct st32h7_pll_cfg stm32h7_pll3 = {
.bit_idx = 28,
.offset_divr = RCC_PLL3DIVR,
.bit_frac_en = 8,
.offset_frac = RCC_PLL3FRACR,
.divm = 20,
};
static const struct stm32_pll_data stm32_pll[] = {
{ "vco1", "pllsrc", CLK_IGNORE_UNUSED, &stm32h7_pll1 },
{ "vco2", "pllsrc", 0, &stm32h7_pll2 },
{ "vco3", "pllsrc", 0, &stm32h7_pll3 },
};
static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src)
{
dst->clk = src->clk;
dst->core = src->core;
}
static int pll_is_enabled(struct clk_hw *hw)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct clk_hw *_hw = &clk_elem->rgate.gate.hw;
__clk_hw_set_clk(_hw, hw);
return ready_gate_clk_ops.is_enabled(_hw);
}
static int pll_enable(struct clk_hw *hw)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct clk_hw *_hw = &clk_elem->rgate.gate.hw;
__clk_hw_set_clk(_hw, hw);
return ready_gate_clk_ops.enable(_hw);
}
static void pll_disable(struct clk_hw *hw)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct clk_hw *_hw = &clk_elem->rgate.gate.hw;
__clk_hw_set_clk(_hw, hw);
ready_gate_clk_ops.disable(_hw);
}
static int pll_frac_is_enabled(struct clk_hw *hw)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct stm32_fractional_divider *fd = &clk_elem->div;
return (readl(fd->freg_status) >> fd->freg_bit) & 0x01;
}
static unsigned long pll_read_frac(struct clk_hw *hw)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct stm32_fractional_divider *fd = &clk_elem->div;
return (readl(fd->freg_value) >> fd->fshift) &
GENMASK(fd->fwidth - 1, 0);
}
static unsigned long pll_fd_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct stm32_pll_obj *clk_elem = to_pll(hw);
struct stm32_fractional_divider *fd = &clk_elem->div;
unsigned long m, n;
u32 val, mask;
u64 rate, rate1 = 0;
val = readl(fd->mreg);
mask = GENMASK(fd->mwidth - 1, 0) << fd->mshift;
m = (val & mask) >> fd->mshift;
val = readl(fd->nreg);
mask = GENMASK(fd->nwidth - 1, 0) << fd->nshift;
n = ((val & mask) >> fd->nshift) + 1;
if (!n || !m)
return parent_rate;
rate = (u64)parent_rate * n;
do_div(rate, m);
if (pll_frac_is_enabled(hw)) {
val = pll_read_frac(hw);
rate1 = (u64)parent_rate * (u64)val;
do_div(rate1, (m * 8191));
}
return rate + rate1;
}
static const struct clk_ops pll_ops = {
.enable = pll_enable,
.disable = pll_disable,
.is_enabled = pll_is_enabled,
.recalc_rate = pll_fd_recalc_rate,
};
static struct clk_hw *clk_register_stm32_pll(struct device *dev,
const char *name,
const char *parent,
unsigned long flags,
const struct st32h7_pll_cfg *cfg,
spinlock_t *lock)
{
struct stm32_pll_obj *pll;
struct clk_init_data init = { NULL };
struct clk_hw *hw;
int ret;
struct stm32_fractional_divider *div = NULL;
struct stm32_ready_gate *rgate;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &pll_ops;
init.flags = flags;
init.parent_names = &parent;
init.num_parents = 1;
pll->hw.init = &init;
hw = &pll->hw;
rgate = &pll->rgate;
rgate->bit_rdy = cfg->bit_idx + 1;
rgate->gate.lock = lock;
rgate->gate.reg = base + RCC_CR;
rgate->gate.bit_idx = cfg->bit_idx;
div = &pll->div;
div->flags = 0;
div->mreg = base + RCC_PLLCKSELR;
div->mshift = cfg->divm;
div->mwidth = 6;
div->nreg = base + cfg->offset_divr;
div->nshift = 0;
div->nwidth = 9;
div->freg_status = base + RCC_PLLCFGR;
div->freg_bit = cfg->bit_frac_en;
div->freg_value = base + cfg->offset_frac;
div->fshift = 3;
div->fwidth = 13;
div->lock = lock;
ret = clk_hw_register(dev, hw);
if (ret) {
kfree(pll);
hw = ERR_PTR(ret);
}
return hw;
}
pclk
c
/* 外设 时钟 */
struct pclk_t {
u32 gate_offset;
u8 bit_idx;
const char *name;
const char *parent;
u32 flags;
};
#define PER_CLKF(_gate_offset, _bit_idx, _name, _parent, _flags)\
{\
.gate_offset = _gate_offset,\
.bit_idx = _bit_idx,\
.name = _name,\
.parent = _parent,\
.flags = _flags,\
}
#define PER_CLK(_gate_offset, _bit_idx, _name, _parent)\
PER_CLKF(_gate_offset, _bit_idx, _name, _parent, 0)
static const struct pclk_t pclk[] = {
PER_CLK(RCC_AHB3ENR, 31, "d1sram1", "hclk"),
PER_CLK(RCC_AHB3ENR, 30, "itcm", "hclk"),
PER_CLK(RCC_AHB3ENR, 29, "dtcm2", "hclk"),
PER_CLK(RCC_AHB3ENR, 28, "dtcm1", "hclk"),
PER_CLK(RCC_AHB3ENR, 8, "flitf", "hclk"),
PER_CLK(RCC_AHB3ENR, 5, "jpgdec", "hclk"),
PER_CLK(RCC_AHB3ENR, 4, "dma2d", "hclk"),
PER_CLK(RCC_AHB3ENR, 0, "mdma", "hclk"),
PER_CLK(RCC_AHB1ENR, 28, "usb2ulpi", "hclk"),
PER_CLK(RCC_AHB1ENR, 26, "usb1ulpi", "hclk"),
PER_CLK(RCC_AHB1ENR, 17, "eth1rx", "hclk"),
PER_CLK(RCC_AHB1ENR, 16, "eth1tx", "hclk"),
PER_CLK(RCC_AHB1ENR, 15, "eth1mac", "hclk"),
PER_CLK(RCC_AHB1ENR, 14, "art", "hclk"),
PER_CLK(RCC_AHB1ENR, 1, "dma2", "hclk"),
PER_CLK(RCC_AHB1ENR, 0, "dma1", "hclk"),
PER_CLK(RCC_AHB2ENR, 31, "d2sram3", "hclk"),
PER_CLK(RCC_AHB2ENR, 30, "d2sram2", "hclk"),
PER_CLK(RCC_AHB2ENR, 29, "d2sram1", "hclk"),
PER_CLK(RCC_AHB2ENR, 5, "hash", "hclk"),
PER_CLK(RCC_AHB2ENR, 4, "crypt", "hclk"),
PER_CLK(RCC_AHB2ENR, 0, "camitf", "hclk"),
PER_CLK(RCC_AHB4ENR, 28, "bkpram", "hclk"),
PER_CLK(RCC_AHB4ENR, 25, "hsem", "hclk"),
PER_CLK(RCC_AHB4ENR, 21, "bdma", "hclk"),
PER_CLK(RCC_AHB4ENR, 19, "crc", "hclk"),
PER_CLK(RCC_AHB4ENR, 10, "gpiok", "hclk"),
PER_CLK(RCC_AHB4ENR, 9, "gpioj", "hclk"),
PER_CLK(RCC_AHB4ENR, 8, "gpioi", "hclk"),
PER_CLK(RCC_AHB4ENR, 7, "gpioh", "hclk"),
PER_CLK(RCC_AHB4ENR, 6, "gpiog", "hclk"),
PER_CLK(RCC_AHB4ENR, 5, "gpiof", "hclk"),
PER_CLK(RCC_AHB4ENR, 4, "gpioe", "hclk"),
PER_CLK(RCC_AHB4ENR, 3, "gpiod", "hclk"),
PER_CLK(RCC_AHB4ENR, 2, "gpioc", "hclk"),
PER_CLK(RCC_AHB4ENR, 1, "gpiob", "hclk"),
PER_CLK(RCC_AHB4ENR, 0, "gpioa", "hclk"),
PER_CLK(RCC_APB3ENR, 6, "wwdg1", "pclk3"),
PER_CLK(RCC_APB1LENR, 29, "dac12", "pclk1"),
PER_CLK(RCC_APB1LENR, 11, "wwdg2", "pclk1"),
PER_CLK(RCC_APB1LENR, 8, "tim14", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 7, "tim13", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 6, "tim12", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 5, "tim7", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 4, "tim6", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 3, "tim5", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 2, "tim4", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 1, "tim3", "tim1_ker"),
PER_CLK(RCC_APB1LENR, 0, "tim2", "tim1_ker"),
PER_CLK(RCC_APB1HENR, 5, "mdios", "pclk1"),
PER_CLK(RCC_APB1HENR, 4, "opamp", "pclk1"),
PER_CLK(RCC_APB1HENR, 1, "crs", "pclk1"),
PER_CLK(RCC_APB2ENR, 18, "tim17", "tim2_ker"),
PER_CLK(RCC_APB2ENR, 17, "tim16", "tim2_ker"),
PER_CLK(RCC_APB2ENR, 16, "tim15", "tim2_ker"),
PER_CLK(RCC_APB2ENR, 1, "tim8", "tim2_ker"),
PER_CLK(RCC_APB2ENR, 0, "tim1", "tim2_ker"),
PER_CLK(RCC_APB4ENR, 26, "tmpsens", "pclk4"),
PER_CLK(RCC_APB4ENR, 16, "rtcapb", "pclk4"),
PER_CLK(RCC_APB4ENR, 15, "vref", "pclk4"),
PER_CLK(RCC_APB4ENR, 14, "comp12", "pclk4"),
PER_CLK(RCC_APB4ENR, 1, "syscfg", "pclk4"),
};
stm32h7_rcc_init
c
static void __init stm32h7_rcc_init(struct device_node *np)
{
struct clk_hw_onecell_data *clk_data;
struct composite_cfg c_cfg;
int n;
const char *hse_clk, *lse_clk, *i2s_clk;
struct regmap *pdrm;
/* #define STM32H7_MAX_CLKS 166 */
clk_data = kzalloc(struct_size(clk_data, hws, STM32H7_MAX_CLKS),
GFP_KERNEL);
if (!clk_data)
return;
clk_data->num = STM32H7_MAX_CLKS;
hws = clk_data->hws;
for (n = 0; n < STM32H7_MAX_CLKS; n++)
hws[n] = ERR_PTR(-ENOENT);
/* get RCC base @ from DT */
base = of_iomap(np, 0);
if (!base) {
pr_err("%pOFn: unable to map resource", np);
goto err_free_clks;
}
pdrm = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
if (IS_ERR(pdrm))
pr_warn("%s: Unable to get syscfg\n", __func__);
else
/*
* 存器的 BREN 和 MOEN 位均受到写访问保护。必须将此位置 1 才能使能对这些寄存器的写访问。
* 在任何情况下,请禁用备份域写保护,并且永远不会启用。
* LSE 和 RTC 时钟需要.
*/
regmap_update_bits(pdrm, PWR_CR, PWR_CR_DBP, PWR_CR_DBP);
/* Put parent names from DT */
hse_clk = of_clk_get_parent_name(np, 0);
lse_clk = of_clk_get_parent_name(np, 1);
i2s_clk = of_clk_get_parent_name(np, 2);
sai_src[3] = i2s_clk;
spi_src1[3] = i2s_clk;
/* 注册内部振荡器 没有父设备时钟*/
clk_hw_register_fixed_rate(NULL, "clk-hsi", NULL, 0, 64000000);
clk_hw_register_fixed_rate(NULL, "clk-csi", NULL, 0, 4000000);
clk_hw_register_fixed_rate(NULL, "clk-lsi", NULL, 0, 32000);
clk_hw_register_fixed_rate(NULL, "clk-rc48", NULL, 0, 48000);
/* 这个时钟是从外面传来的。频率未知 没有父设备时钟*/
hws[CK_DSI_PHY] = clk_hw_register_fixed_rate(NULL, "ck_dsi_phy", NULL,
0, 0);
/* HSI: 父设备时钟:clk-hsi: 基础频率:64M
分频系数: 范围[1, 2, 4, 8] 通过 width = 2 < CLK_DIVIDER_POWER_OF_TWO*/
hws[HSI_DIV] = clk_hw_register_divider(NULL, "hsidiv", "clk-hsi", 0,
base + RCC_CR, 3, 2, CLK_DIVIDER_POWER_OF_TWO,
&stm32rcc_lock);
/* HSE: 父设备时钟:hse_ck: 基础1M
分频系数: 范围[1~64] 通过width = 6和CLK_DIVIDER_ONE_BASED得出
*/
hws[HSE_1M] = clk_hw_register_divider(NULL, "hse_1M", "hse_ck", 0,
base + RCC_CFGR, 8, 6, CLK_DIVIDER_ONE_BASED |
CLK_DIVIDER_ALLOW_ZERO,
&stm32rcc_lock);
/* Mux 系统时钟 */
for (n = 0; n < ARRAY_SIZE(stm32_mclk); n++)
hws[MCLK_BANK + n] = clk_hw_register_mux(NULL,
stm32_mclk[n].name,
stm32_mclk[n].parents,
stm32_mclk[n].num_parents,
stm32_mclk[n].flags,
stm32_mclk[n].offset + base,
stm32_mclk[n].shift,
stm32_mclk[n].width,
0,
&stm32rcc_lock);
register_core_and_bus_clocks();
/* Oscillary clocks */
for (n = 0; n < ARRAY_SIZE(stm32_oclk); n++)
hws[OSC_BANK + n] = clk_register_ready_gate(NULL,
stm32_oclk[n].name,
stm32_oclk[n].parent,
stm32_oclk[n].gate_offset + base,
stm32_oclk[n].bit_idx,
stm32_oclk[n].bit_rdy,
stm32_oclk[n].flags,
&stm32rcc_lock);
/* hse_ck时钟注册准备好可以使能控制 */
hws[HSE_CK] = clk_register_ready_gate(NULL,
"hse_ck",
hse_clk,
RCC_CR + base,
16, /* 时钟使能位 */
17, /* 时钟就绪位 */
0,
&stm32rcc_lock);
hws[LSE_CK] = clk_register_ready_gate(NULL,
"lse_ck",
lse_clk,
RCC_BDCR + base,
0, 1,
0,
&stm32rcc_lock);
/* 122分频 */
hws[CSI_KER_DIV122 + n] = clk_hw_register_fixed_factor(NULL,
"csi_ker_div122", "csi_ker", 0, 1, 122);
/* PLLs */
for (n = 0; n < ARRAY_SIZE(stm32_pll); n++) {
int odf;
/* 注册 VCO*/
clk_register_stm32_pll(NULL, stm32_pll[n].name,
stm32_pll[n].parent_name, stm32_pll[n].flags,
stm32_pll[n].cfg,
&stm32rcc_lock);
/* 注册 3 个输出分频器 */
for (odf = 0; odf < 3; odf++) {
int idx = n * 3 + odf;
get_cfg_composite_div(&odf_clk_gcfg, &stm32_odf[n][odf],
&c_cfg, &stm32rcc_lock);
hws[ODF_BANK + idx] = clk_hw_register_composite(NULL,
stm32_odf[n][odf].name,
stm32_odf[n][odf].parent_name,
stm32_odf[n][odf].num_parents,
c_cfg.mux_hw, c_cfg.mux_ops,
c_cfg.div_hw, c_cfg.div_ops,
c_cfg.gate_hw, c_cfg.gate_ops,
stm32_odf[n][odf].flags);
}
}
/*外设时钟 */
for (n = 0; n < ARRAY_SIZE(pclk); n++)
hws[PERIF_BANK + n] = clk_hw_register_gate(NULL, pclk[n].name,
pclk[n].parent,
pclk[n].flags, base + pclk[n].gate_offset,
pclk[n].bit_idx, pclk[n].flags, &stm32rcc_lock);
/* Kernel clocks */
for (n = 0; n < ARRAY_SIZE(kclk); n++) {
get_cfg_composite_div(&kernel_clk_cfg, &kclk[n], &c_cfg,
&stm32rcc_lock);
hws[KERN_BANK + n] = clk_hw_register_composite(NULL,
kclk[n].name,
kclk[n].parent_name,
kclk[n].num_parents,
c_cfg.mux_hw, c_cfg.mux_ops,
c_cfg.div_hw, c_cfg.div_ops,
c_cfg.gate_hw, c_cfg.gate_ops,
kclk[n].flags);
}
/* RTC clock (default state is off) */
clk_hw_register_fixed_rate(NULL, "off", NULL, 0, 0);
get_cfg_composite_div(&rtc_clk_cfg, &rtc_clk, &c_cfg, &stm32rcc_lock);
hws[RTC_CK] = clk_hw_register_composite(NULL,
rtc_clk.name,
rtc_clk.parent_name,
rtc_clk.num_parents,
c_cfg.mux_hw, c_cfg.mux_ops,
c_cfg.div_hw, c_cfg.div_ops,
c_cfg.gate_hw, c_cfg.gate_ops,
rtc_clk.flags);
/* Micro-controller clocks */
for (n = 0; n < ARRAY_SIZE(mco_clk); n++) {
get_cfg_composite_div(&mco_clk_cfg, &mco_clk[n], &c_cfg,
&stm32rcc_lock);
hws[MCO_BANK + n] = clk_hw_register_composite(NULL,
mco_clk[n].name,
mco_clk[n].parent_name,
mco_clk[n].num_parents,
c_cfg.mux_hw, c_cfg.mux_ops,
c_cfg.div_hw, c_cfg.div_ops,
c_cfg.gate_hw, c_cfg.gate_ops,
mco_clk[n].flags);
}
/* 将reset控制设备添加 */
of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
return;
err_free_clks:
kfree(clk_data);
}
/* The RCC node is a clock and reset controller, and these
* functionalities are supported by different drivers that
* matches the same compatible strings.
*/
CLK_OF_DECLARE_DRIVER(stm32h7_rcc, "st,stm32h743-rcc", stm32h7_rcc_init);
sys_ck的时钟源不是默认值的分析
- 日志可以看出sys_ck读取后选择的时钟源是索引3,即
pll1_p
log
[2025/5/24 22:06:33 042] [ 0.000000] sys_ck: get_parent = 3
[2025/5/24 22:06:33 045] [ 0.000000] sys_ck: none rate 0
- 查看手册说法,默认时钟源是HSI
c
/* Bits 2:0 SW[2:0]: System clock switch
Set and reset by software to select system clock source (sys_ck).
Set by hardware in order to:
-- force the selection of the HSI or CSI (depending on STOPWUCK selection) when leaving a
system Stop mode
-- force the selection of the HSI in case of failure of the HSE when used directly or indirectly
as system clock.
000: HSI selected as system clock (hsi_ck) (default after reset)
001: CSI selected as system clock (csi_ck)
010: HSE selected as system clock (hse_ck)
011: PLL1 selected as system clock (pll1_p_ck)
others: Reserved */
- 查看u-boot的代码,可以看到在
drivers/clk/stm32/clk-stm32h7.c中,在configure_clocks函数中,会设置时钟源为PPL1
c
/* select PLL1 as system clock source (sys_ck)*/
clrsetbits_le32(®s->cfgr, RCC_CFGR_SW_MASK, RCC_CFGR_SW_PLL1);
while ((readl(®s->cfgr) & RCC_CFGR_SW_MASK) != RCC_CFGR_SW_PLL1)
;