基于RK3399分析Linux系统下的CPU时钟管理 - 第1篇

本文详细介绍了ARM处理器平台中的时钟管理机制,特别是基于设备树(dts)的时钟树结构。时钟提供者(clockproviders)通过#clock-cells定义输出时钟的数量,clock-output-names指定时钟名称,而时钟使用者(clockconsumers)通过clocks引用时钟源,并通过clock-names进行解析。此外,文章还提到了assigned-clocks和assigned-clock-rates用于多路时钟管理和频率设定,以及clock-frequency用于单一时钟频率的定义。通过对rk3399平台的实例分析,展示了时钟配置的具体细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 背景介绍

绝大多数的电子器件都是由时钟驱动其工作的。而SoC芯片或电路板中的时钟以树状结构呈现,按时钟域进行划分,按照不同的时钟需求进行管理。
出于功耗和数据传输时序控制等目的,在内核代码中对时钟进行统一注册、统一管理。kernel代码中很早就出现了时钟管理机制,甚至早于git版本管控之前。

1. 时钟定义

ARM处理器平台中基于dts描述时钟树,包括时钟结构、时钟属性等信息。由时钟框架驱动和设备驱动解析dts中时钟树信息,完成时钟系统的初始化、管理、使用。在设备树中定义了时钟的providersconsumers。前者代表时钟提供者,通常是一个固定的PLL,后者代表时钟使用者,例如处理器核心以及各种外部设备等。

1.1 Clock providers

1. #clock-cells

时钟提供者输出的时钟路数,当#clock-cells为0时,代表仅输出1路时钟,若大于等于1,则代表输出多路时钟,Clock consumers通过编号索引使用。
下面的时钟结点clock-cells为0,代表仅能够输出1路时钟,这里面只提供了一路24MHz的固定时钟。

xin24m: xin24m {
  compatible = "fixed-clock";
  clock-frequency = <24000000>;
  clock-output-names = "xin24m";
  #clock-cells = <0>;
};

下面的时钟结点中clock-cells为1,代表能够输出多路时钟,这里面我们可以看出它提供了两路时钟分别是PLL_PPLLFCLK_CM0S_SRC_PMU,时钟频率分别是67.6MHz和97MHz。

pmucru: pmu-clock-controller@ff750000 {
...
		#clock-cells = <1>;
		#reset-cells = <1>;
		assigned-clocks = <&pmucru PLL_PPLL>, <&pmucru FCLK_CM0S_SRC_PMU>;
		assigned-clock-rates = <676000000>, <97000000>;
	};

2. clock-output-names
顾名思义,它定义了输出时钟的名字。当clock consumers使用这路时钟的时候,我们可以见名知意。从下面的clock-output-names可以清楚的直到,这是外部晶振提供的24MHz时钟。

xin24m: xin24m {
		compatible = "fixed-clock";
		clock-frequency = <24000000>;
		clock-output-names = "xin24m";
		#clock-cells = <0>;
	};

3. clock-indices
这并不是一个必选项,当然也不常见。当存在多路输出时钟时,clock consumerindex引用对应的时钟,默认不指定clock-indices且没有使用assigned-clocks时,index索引是线性增长的,像下面这样,ckil的index是0,而ckih的index就是1。

oscillator { #clock-cells = <1>;
    clock-output-names = "ckil", "ckih"; };

当定义了clock-indices之后,index的值由clock-indices决定,像下面这样:

oscillator { #clock-cells = <1>;
    clock-output-names = "ckil", "ckih"; };

4. assigned-clocks
当输出多路时钟时,为没路输出时钟进行编号,以phandle+specifier组合进行管理,像下面这样:

pmucru: pmu-clock-controller@ff750000 {
...
  assigned-clocks = <&pmucru PLL_PPLL>, <&pmucru FCLK_CM0S_SRC_PMU>;
  assigned-clock-rates = <676000000>, <97000000>;
};

PLL_PPLL代表了specifier,其定义位于头文件include/dt-bindings/clock/rk3399-cru.h中。需要说明的是,这个头文件很重要,所有的specifier都可以在这里找到对应的宏。

5. assigned-clock-rates
这个定义是和assigned-clocks成对使用的。它代表了assigned-clocks所对应的时钟频率,例如PLL_PPLL所对应的时钟频率是67.6MHz。

pmucru: pmu-clock-controller@ff750000 {
...
  assigned-clocks = <&pmucru PLL_PPLL>, <&pmucru FCLK_CM0S_SRC_PMU>;
  assigned-clock-rates = <676000000>, <97000000>;
};

6. clock-frequency
当不使用assigned-clock-rates为输出时钟指定大小时,可以利用clock-frequency进行指定。像下面这样,指定了osc的时钟频率为32.678Khz。

osc: oscillator { 
    compatible = "fixed-clock";
    #clock-cells = <1>;
    clock-frequency  = <32678>;
    clock-output-names = "osc"; };

1.2 Clock consumers

Clock consumers意为时钟使用者,通常是CPU核心部件或者其他外设。
我们以dsi为例说明Clock consumers设备树的组成。

	vopb: vop@ff900000 {
		compatible = "rockchip,rk3399-vop-big";
		reg = <0x0 0xff900000 0x0 0x600>,
			<0x0 0xff901c00 0x0 0x200>,
			<0x0 0xff902000 0x0 0x1000>;
		reg-names = "regs", "cabc_lut", "gamma_lut";
		interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
		clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>, <&cru DCLK_VOP0_DIV>;
		clock-names = "aclk_vop", "dclk_vop", "hclk_vop", "dclk_source";
  ...
};

1. clocks
它代表了设备的时钟源,通常以phandle+specifier组合进行引用。例如本例中,aclk_vop时钟使用的是cru模块提供的ACLK_VOP0,它的时钟频率在cru结点中定义,大小是400000000

2. clock-names
这代表了Clock consumers中使用的时钟名字,方便设备驱动代码进行相应的时钟解析,例如:

static int vop_bind(struct device *dev, struct device *master, void *data)
{
...
	vop->aclk = devm_clk_get(vop->dev, "aclk_vop");
	if (IS_ERR(vop->aclk)) {
		dev_err(vop->dev, "failed to get aclk source\n");
		return PTR_ERR(vop->aclk);
	}
...
}

以上是ARM平台RK3399的软件时钟树定义方式,具体可查看rk3399.dtsi文件。除此之外,可参考如下内核文档做相关了解。

Documentation/evicetree/indings/lock/lock-bindings.txt

### 配置和使用 RK3399 平台上的硬件 PWM #### 设备树中的配置 为了在 Rockchip RK3399 上启用并配置硬件 PWM 功能,需要修改设备树文件 `rk3399-evb.dts`。具体来说,在该文件中定义 PWM 控制器及其属性可以确保内核能够识别并初始化相应的外设。 对于电源管理部分,已经存在如下配置用于指定 GPIO 的供电源: ```dts &io_domains { ... gpio1830-supply = <&vcc_3v0>; }; ``` 这表明 GPIO 使用的是名为 `vcc_3v0` 的稳压器作为其工作电压[^1]。然而,针对具体的 PWM 通道,则需进一步声明节点以描述这些资源的位置以及它们的操作参数。 #### 添加 PWM 节点到设备树 假设要使能某个特定编号的 PWM 信道(比如 pwm@ff7200c0),可以在 DTS 文件里加入类似下面的内容: ```dts &pwm { compatible = "rockchip,rk3399-pwm"; reg = <0xff7200c0 0x4>; /* 假定地址 */ clocks = <&cru SCLK_PWMX>; clock-names = "pclk"; #pwm-cells = <3>; status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&pwmX_default>; }; /* 定义引脚控制状态 */ &pinctrl { pwmX_default: pwmx_default { rockchip,pins = < /* 替换为实际使用的管脚号 */ ROCKCHIP_PINCTRL_PIN(1, 1) (ROCKCHIP_PULL_NONE | ROCKCHIP_FUNC_X) >; }; } ``` 上述片段指定了与 PWM 相关联的一系列特性,包括寄存器基址、时钟源等,并设置了默认的工作模式。注意这里的 `SCLK_PWMX`, `ROCKCHIP_PINCTRL_PIN()` 和其他宏应该依据实际情况调整至匹配目标系统的设置。 #### 编写驱动程序或应用程序来操作PWM 一旦完成了设备树方面的准备工作之后,下一步就是编写用户空间的应用程序或者是内核模块形式的驱动代码来进行更细致的功能实现。通常情况下,Linux 提供了一个通用接口 `/sys/class/pwm/` 来简化对不同 SoC 上 PWM 外设的操作流程。 例如,可以通过命令行工具轻松地开启关闭 PWM 输出或是更改占空比: ```bash echo 0 > /sys/class/pwm/pwmchip0/export # 导出第一个PWM控制器下的第零个通道 echo 500000 > /sys/class/pwm/pwmchip0/pwm0/period # 设置周期为500微秒 echo 250000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle # 设置高电平持续时间为250微秒 echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable # 启动PWM输出 ``` 以上指令序列展示了如何利用标准路径去访问由底层硬件抽象出来的 PWM 接口,而无需关心内部细节[^2]。 #### 关于性能考量 值得注意的是,当采用软件定时方式生成 PWM 波形时可能会遇到一些局限性,如较高的 CPU 占用率等问题。相比之下,借助专用硬件单元产生的波形不仅效率更高而且更加稳定可靠。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux与SoC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值