----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏u-boot
:2023.04
linux
:6.3
----------------------------------------------------------------------------------------------------------------------------
一、电路原理图
我所使用的NanoPC-T4
开发板可以外接一个散热风扇,我们首先来介绍一下散热风扇硬件相关的内容。
1.1 电路原理图
下图是我们使用的NanoPC-T4
开发板散热风扇的接线图:
VCC_12V0_SYS
为系统电压,电压级别为12V。我们可以通过GPIO
和PWM
实现对风扇的控制;
上图中:
- 当
GPIO4_C6/PWM1
输出高电平时,三极管Q41
导通,N MOS
管Q1
导通,BM03B-GHS-TBT
2号引脚输入12V电鱼,风扇转动; - 当
GPIO4_C6/PWM1
输出低电平时,三极管Q41
截止,N MOS
管Q1
截止,BM03B-GHS-TBT
2号引脚没有电压输入,风扇停止;
至于GPIO2_A6_FAN_TACH
引脚是用来测量转速的。
1.2 散热风扇接口引脚定义
连接器型号: JST GH
系列连接器,3Pin,BM03B-GHS-TBT
:
Pin# | Assignment | Description |
---|---|---|
1 | GND | 0V |
2 | 12V | 12V输出,由GPIO4_C6/PWM1控制 |
3 | GPIO2_A6_FAN_TACH | 用来测量转速 |
二、GPIO
控制
这一节我们将介绍如何通过控制GPIO
口来实现控制散热风扇的运行和停止。
2.1 GPIO
子系统简介
既然是控制GPIO
口,那自然少不了GPIO
子系统。我们在文章《linux
驱动移植-GPIO
子系统》中介绍过GPIO
子系统相关的内容,但是有一块内容却遗漏掉了,那就是有关通过sysfs
来将控制GPIO
。
linux
内核对GPIO
资源进行了抽象,抽象出来的概念就是gpiolib
;gpiolib
汇总了GPIO
的通用操作,根据GPIO
的特性:
- 对上
gpiolib
提供的一套统一通用的操作GPIO
的软件接口,屏蔽了不同芯片的具体实现; - 对下
gpiolib
提供了针对不同芯片操作的一套framework
,针对不同芯片,只需要实现gpio controller driver
,然后使用gpiolib
提供的注册函数,将其挂接到gpiolib
上,这样就完成了这一套东西;
此外,为了方便应用层控制GPIO
口,GPIO
子系统提供了通过sysfs
控制GPIO
就的方式,应用层通过sysfs
操作GPIO
的前提是内核中已经向GPIO
子系统注册GPIO
资源,并且在/sys/class/
目录下可以看到gpio
类。
2.2 内核配置
这里我们需要进行如下配置:
General setup --->
[*] Configure standard kernel features (expert users) // CONFIG_EXPERT
Device Drivers --->
-*- GPIO Support ---> // CONFIG_GPIOLIB
[*] /sys/class/gpio/... (sysfs interface) // CONFIG_GPIO_SYSFS
其中:
CONFIG_GPIO_SYSFS
:决定sysfs
是否支持GPIO
子系统,也就是能否在/sys/class/
目录下看到gpio
类;CONFIG_GPIOLIB
:决定是否将gpiolib
编译进内核,如果选择否则在内核和驱动中不能使用GPIO
子系统相关的函数接口;
CONFIG_GPIOLIB
一般都是选择y
,因为其它驱动会用到GPIO
子系统;CONFIG_GPIO_SYSFS
根据自己的需求来进行选择,如果不需要通过/sys/class/gpio
目录下的文件来操作GPIO
口,就不需要开启。
2.2.1 配置电源域
一般IO
电源的电压有1.8v
,3.3v
,2.5v
,5.0v
等,有些IO
同时支持多种电压,io-domain
就是配置IO
电源域的寄存器,依据真实的硬件电压范围来配置对应的电压寄存器,否则无法正常工作。
在RK3399
的datasheet
中我们搜索GRF_IO_VSEL
寄存器,位于GRF
偏移地址offset
(0x0e640
):
支持两种电压配置:
- 寄存器配置成1,一般对应的电压范围是1.62v ~ 1.98v,
typical
电压 1.8v; - 寄存器配置成0,一般对应的电压范围是3.00v ~ 3.60v,
typical
电压 3.3v。
在 Documentation/devicetree/bindings/power/rockchip-io-domain.yaml文中有关于RK3399 IO
电源域的配置描述描述:
rk3399:
if:
properties:
compatible:
contains:
const: rockchip,rk3399-io-voltage-domain
then:
properties:
audio-supply:
description: The supply connected to APIO5_VDD.
bt656-supply:
description: The supply connected to APIO2_VDD.
gpio1830-supply:
description: The supply connected to APIO4_VDD.
sdmmc-supply:
description: The supply connected to SDMMC0_VDD.
rk3399-pmu:
if:
properties:
compatible:
contains:
const: rockchip,rk3399-pmu-io-voltage-domain
then:
properties:
pmu1830-supply:
description: The supply connected to PMUIO2_VDD.
通过查看rockchip-io-domain.yaml
文中文档, 我们知道了RK3399
的电源域需要配置包含bt565
,audio
,sdmmc
,gpio1830
,以及PMUGRF
下面的pmu1830
这几个supply
,后面的The supplyconnected to ***_VDD
表示在硬件原理图上对应的名称。
我们在rockchip-io-domain.yml
中找到了gpio1830-supply
对应的硬件原理图上表示为APIO4_VDD
。所以通过搜索APIO4_VDD
得到NanoPC-T4
开发板 硬件原理图上的APIO4_VDD
电源VCC_3V0
是由rk808下
的15号引脚VLDO8
输出的;
得到了配置的名称和供电源头,在设备树里面找到对应的regulator: vcc_3v0
,就可以在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中追加配置;
&io_domains {
bt656-supply = <&vcc_1v8>;
audio-supply = <&vcca1v8_codec>;
sdmmc-supply = <&vcc_sdio>;
gpio1830-supply = <&vcc_3v0>;
status = "okay";
};
&pmu_io_domains {
pmu1830-supply = <&vcc_3v0>;
status = "okay";
};
注意:这里为了方便我直接把所有可能用到的电源域都配置上了。
2.2.2 编译内核
在linux
内核根目录下执行如下命令进行编译内核:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make -j8
u-boot-2023.04
路径下的mkimage
工具拷贝过来,然后在命令行使用mkimage
工具编译即可:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../u-boot-2023.04/tools/mkimage ./
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ./mkimage -f kernel.its kernel.itb
2.2.3 通过tftp
烧录内核
给开发板上电,同时连接上网线,进入uboot
命令行。我们将内核拷贝到tftp
文件目录:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp kernel.itb /work/tftpboot/
接着给开发板上电。通过uboot
命令行将kernel.itb
下到内存地址0x10000000
处:
=> tftp 0x10000000 kernel.itb
通过mmc write
命令将内核镜像烧录到eMMC
第0x8000
个扇区处:
=> mmc erase 0x8000 0xA000
=> mmc write 0x10000000 0x8000 0xA000
2.3 /sys/class/gpio
开发板上电后,查看/sys/class/gpio
目录;
root@rk3399:~# ls /sys/class/gpio
export gpiochip0 gpiochip32 gpiochip96
gpio156 gpiochip128 gpiochip64 unexport
gpiochipXX
:每个文件夹对应一个GPIO
控制器,名字的末尾是GPIO
控制器中第一个GPIO
口在内核中的编号;
每个gpiochipXX
文件夹下面都有base
、label
、ngpio
文件;
root@rk3399:~# ll /sys/class/gpio/gpiochip128
lrwxrwxrwx 1 root root 0 Mar 15 2023 /sys/class/gpio/gpiochip128 -> ../../devices/platform/pinctrl/ff790000.gpio/gpio/gpiochip128/
root@rk3399:~# ls /sys/class/gpio/gpiochip128
base device label ngpio power subsystem uevent
其中:
base
:保存的是GPIO
控制器的第一个GPIO
编号;label
:保存GPIO
控制器标签;ngpio
:GPIO
控制器包含的GPIO
数量;
这些数据是和内核中用来表示GPIO
控制器的struct gpio_chip
结构体对应的,以gpiochip128
为例;
root@rk3399:~# cat /sys/class/gpio/gpiochip128/base
128
root@rk3399:~# cat /sys/class/gpio/gpiochip128/label
gpio4
root@rk3399:~# cat /sys/class/gpio/gpiochip128/ngpio
32
GPIO
控制器名字是gpio4
,包含32
个GPIO
口(GPIO4_A0~A7
、GPIO4_B0~B7
、GPIO4_C0~B7
、GPIO4_D0~D7
),第一个GPIO
口在内核的编号是128,所以该端口包含128-159
号GPIO
口。
2.4 操作GPIO
口
GPIO
资源对上层应用是以文件的形式呈现的,应用操作GPIO
口就是读写相应的文件。GPIO
的操作接口包括direction
和value
等;
direction
控制GPIO
方向;value
控制GPIO
输出或获得GPIO
输入;
操作GPIO
口步骤;
- 通过数据手册和
gpiochipXX
文件夹查询到GPIO
在GPIO
子系统的编号; - 向
export
文件写入GPIO
口编号,导出GPIO
口; - 通过读写
GPIO
口导出的文件,操作GPIO
口; - 向
unexport
文件写入GPIO
口编号,撤销GPIO
口的导出;
更多内容可以参考:Documentation/admin-guide/gpio/sysfs.rst
;
2.4.1 查找GPIO
口
当前控制风扇通过控制GPIO
高低电平来实现,查找GPIO
口编号;
root@rk3399:~# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-31, parent: platform/ff720000.gpio, gpio0:
gpio-4 ( |bt_default_wake_host) in lo IRQ
gpio-9 ( |bt_default_reset ) out lo
gpio-10 ( |reset ) out hi ACTIVE LOW
gpiochip1: GPIOs 32-63, parent: platform/ff730000.gpio, gpio1:
gpiochip2: GPIOs 64-95, parent: platform/ff780000.gpio, gpio2:
gpio-83 ( |bt_default_rts ) in hi
gpio-90 ( |bt_default_wake ) in hi
gpiochip3: GPIOs 96-127, parent: platform/ff788000.gpio, gpio3:
gpio-111 ( |snps,reset ) out hi ACTIVE LOW
gpiochip4: GPIOs 128-159, parent: platform/ff790000.gpio, gpio4:
gpio-154 ( |vbus-typec ) out hi
gpio-156 ( |Headphone detection ) in lo IRQ
GPIO4_C6/PWM1
在GPIO
子系统的编号为150。
2.4.2 导出GPIO
口
导出GPIO
口:
root@rk3399:~# cd /sys/class/gpio
root@rk3399:/sys/class/gpio# echo 150 > export
查看GPIO
文件夹下会多出gpio150
文件夹:
root@rk3399:/sys/class/gpio# ls
export gpio156 gpiochip128 gpiochip64 unexport
gpio150 gpiochip0 gpiochip32 gpiochip96
2.4.3 操作GPIO
口
进入文件夹gpio150
;
root@rk3399:/sys/class/gpio# cd gpio150
root@rk3399:/sys/class/gpio/gpio150# ll
-rw-r--r-- 1 root root 4096 Sep 20 20:34 active_low
lrwxrwxrwx 1 root root 0 Sep 20 20:34 device -> ../../../gpiochip4/
-rw-r--r-- 1 root root 4096 Sep 20 20:34 direction
-rw-r--r-- 1 root root 4096 Sep 20 20:34 edge
drwxr-xr-x 2 root root 0 Sep 20 20:34 power/
lrwxrwxrwx 1 root root 0 Sep 20 20:34 subsystem -> ../../../../../../../class/gpio/
-rw-r--r-- 1 root root 4096 Sep 20 20:32 uevent
-rw-r--r-- 1 root root 4096 Sep 20 20:34 value
设置为输出:
root@rk3399:/sys/class/gpio/gpio150# echo out > direction
direction
接受的参数可以是:in
、out
、high
、low
。其中参数high
/ low
在设置方向为输出的同时将value
设置为相应的1/0
。
修改GPIO
口状态,输出高电平,此时散热风扇会工作:
root@rk3399:/sys/class/gpio/gpio150# echo 1 > value
输出低电平,此时散热风扇会停止工作
root@rk3399:/sys/class/gpio/gpio150# echo 0 > value
查看输出值:
root@rk3399:/sys/class/gpio/gpio150# cat value
0
注意:需要先配置电源域设备节点gpio1830-supply
:不然的话GPIO4_C6
口的输出不会生效。
2.4.4 取消导出
root@rk3399:/sys/class/gpio/gpio150# cd /
root@rk3399:/# echo 150 > /sys/class/gpio/unexport
三、PWM
控制
PWM
全称是脉宽调制技术,它通过在一个固定周期内改变信号的占空比来模拟不同的电平或功率输出,因此PWM
有两个很重要的参数:
- 频率:指每秒钟发生的
PWM
周期的数量,通常以赫兹(Hz
)为单位表示。较高的频率意味着更快的周期重复率,可以提供更精细的控制和响应速度; - 占空比:是指在每个
PWM
周期中,信号处于高电平状态(通常为逻辑高)的时间与整个周期的比例。它范围从0到1,也可以表示为百分比。例如,50%的占空比意味着信号在周期的一半时间内处于高电平状态;
通过改变PWM
信号的频率和占空比,可以实现不同的控制效果。例如,在电机控制中,可以通过调整PWM
信号的频率和占空比来控制电机的转速和方向。
3.1 配置设备节点
3.1.1 配置风扇驱动
风扇驱动位于drivers/hwmon/pwm-fan.c
,设备节点的配置参考文档 Documentation/devicetree/bindings/hwmon/pwm-fan.txt
。
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中追加配置;
fan: pwm-fan {
compatible = "pwm-fan";
/*
* With 20KHz PWM and an EVERCOOL EC4007H12SA fan
*/
cooling-levels = <0 12 30 100 200 255>;
#cooling-cells = <2>;
fan-supply = <&vcc12v0_sys>;
pwms = <&pwm1 0 50000 0>;
};
vcc12v0_sys: vcc12v0-sys {
compatible = "regulator-fixed";
regulator-always-on;
regulator-boot-on;
regulator-max-microvolt = <12000000>;
regulator-min-microvolt = <12000000>;
regulator-name = "vcc12v0_sys";
};
其中:
-
节点属性
pwms
用于控制风扇的PWM
,这里指定为pwm
的phandle
,后面有三个参数:-
参数1:表示
index
(per-chip index of the PWM to request
),一般是 0,因为Rockchip PWM
每个chip
只有一个; -
参数 2:表示
PWM
输出波形的时间周期,单位是ns
;上面我们配置的50000就是表示想要得到的PWM
输出周期是20KHz
; -
参数3:表示极性,为可选参数;
- 配置为0,那么
cooling-levels
描述的是高电平的占空比; - 配置为
PWM_POLARITY_INVERTED
,那么cooling-levels
描述的是低电平的占空比;`
- 配置为0,那么
-
-
属性
cooling-levels
:PWM
占空比的取值范围是0到255(注意这里不是0~100),对应着热量冷却状态。这些值通常被映射到特定的占空比,用于控制风扇或泵等冷却设备的速度;档位数量和大小可以自己定义,这里配置6个档位(档位对应到内核专业术语为cooling state
);-
配置占空比的取值为0时,即引脚在一个时钟周期全部输出低电平,风扇停止转动,转速最慢;
-
配置占空比的取值为12时,即引脚在一个时钟周期输出高定平占比为12/255,风扇开始转动,转速较慢;
-
......
-
配置占空比的取值为255时,即引脚在一个时钟周期输出高定平占比为1,风扇全速运转,转速最快;
-
-
属性
fan-supply
:提供给风扇电源的电压调节器所使用的phandle
;
3.1.2 配置PWM
驱动
PWM
驱动位于drivers/pwm/pwm-rockchip.c
,设备节点的配置参考文档 :
Documentation/devicetree/bindings/pwm/pwm.yaml
:Documentation/devicetree/bindings/pwm/pwm.txt
:
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中追加配置;
&pwm1 {
status = "okay";
};
pwm1
定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi
文件;
pwm1 {
pwm1_pin: pwm1-pin {
rockchip,pins =
<4 RK_PC6 1 &pcfg_pull_none>;
};
pwm1_pin_pull_down: pwm1-pin-pull-down {
rockchip,pins =
<4 RK_PC6 1 &pcfg_pull_down>;
};
};
由于控制引脚接GPIO4_C6/PWM1
,这里配置GPIO4_C6
引脚功能复用为pwm_1
;
3.2 配置内核
配置内核:
Device Drivers --->
[*] Pulse-Width Modulation (PWM) Support --->
<*> Rockchip PWM support // CONFIG_PWM_ROCKCHIP
<*> Hardware Monitoring support ---> // CONFIG_HWMON
<*> PWM fan // CONFIG_SENSORS_PWM_FAN
配置完内核之后记得保存配置:
存档:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# mv rk3399_defconfig ./arch/arm64/configs/
重新配置内核(如果不想重新编译内核,可以存档一份到.config
):
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make rk3399_defconfig
3.2.1 编译内核
在linux
内核根目录下执行如下命令进行编译内核:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make -j8
u-boot-2023.04
路径下的mkimage
工具拷贝过来,然后在命令行使用mkimage
工具编译即可:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../u-boot-2023.04/tools/mkimage ./
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ./mkimage -f kernel.its kernel.itb
3.2.2 通过tftp
烧录内核
给开发板上电,同时连接上网线,进入uboot
命令行。我们将内核拷贝到tftp
文件目录:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp kernel.itb /work/tftpboot/
接着给开发板上电。通过uboot
命令行将kernel.itb
下到内存地址0x10000000
处:
=> tftp 0x10000000 kernel.itb
通过mmc write
命令将内核镜像烧录到eMMC
第0x8000
个扇区处:
=> mmc erase 0x8000 0xA000
=> mmc write 0x10000000 0x8000 0xA000
3.3 控制PWM
占空比
开发板重新上电后,我们发现散热风扇并没有转动,这是因为我们缺少了Thermal
(温度控制)相关的配置,这个我们放在后面介绍。
但是我们可以通过用户空间接口直接去修改pwm
占空比达到控制散热风扇转速的目的。
3.3.1 方式一
pwm-fan
提供了用户层的接口,通过修改占空比达到控制风扇转动,占空比的有效操作在0-255之间,数据值越大,散热风扇转速越快;
root@rk3399:/# echo 100 > /sys/devices/platform/pwm-fan/hwmon/hwmon1/pwm1
3.3.1 方式二
PWM
提供了用户层的接口,在 /sys/class/pwm/
节点下面,PWM
驱动加载成功后,会在/sys/class/pwm/
目录下产生pwmchipX
目录,这里咱们以pwmchip0
(非散热风扇使用的PWM
)为例;
root@rk3399:/sys/class/pwm# ll /sys/class/pwm/
lrwxrwxrwx 1 root root 0 Mar 15 2023 pwmchip0 -> ../../devices/platform/ff420000.pwm/pwm/pwmchip0/
lrwxrwxrwx 1 root root 0 Mar 15 2023 pwmchip1 -> ../../devices/platform/ff420010.pwm/pwm/pwmchip1/
lrwxrwxrwx 1 root root 0 Mar 15 2023 pwmchip2 -> ../../devices/platform/ff420020.pwm/pwm/pwmchip2/
lrwxrwxrwx 1 root root 0 Mar 15 2023 pwmchip3 -> ../../devices/platform/ff420030.pwm/pwm/pwmchip3/
root@rk3399:/sys/class/pwm# cd /sys/class/pwm/pwmchip0/
root@rk3399:/sys/class/pwm/pwmchip0# ll
lrwxrwxrwx 1 root root 0 Sep 21 00:36 device -> ../../../ff420000.pwm/
--w------- 1 root root 4096 Sep 21 00:36 export
-r--r--r-- 1 root root 4096 Sep 21 00:36 npwm
drwxr-xr-x 2 root root 0 Sep 21 00:36 power/
lrwxrwxrwx 1 root root 0 Mar 15 2023 subsystem -> ../../../../../class/pwm/
-rw-r--r-- 1 root root 4096 Mar 15 2023 uevent
--w------- 1 root root 4096 Sep 21 00:36 unexport
向export
文件写入1,就是打开pwm
定时器1,会产生一个pwm1
目录,相反的往unexport
写入1就会关闭pwm
定时器了,同时pwm0
目录会被删除。
root@rk3399:/sys/class/pwm/pwmchip0# echo 0 > export
root@rk3399:/sys/class/pwm/pwmchip0# ll pwm0/
-r--r--r-- 1 root root 4096 Sep 20 23:49 capture
-rw-r--r-- 1 root root 4096 Sep 20 23:49 duty_cycle
-rw-r--r-- 1 root root 4096 Sep 20 23:49 enable
-rw-r--r-- 1 root root 4096 Sep 20 23:49 period
-rw-r--r-- 1 root root 4096 Sep 20 23:49 polarity
drwxr-xr-x 2 root root 0 Sep 20 23:49 power/
-rw-r--r-- 1 root root 4096 Sep 20 23:49 uevent
/sys/class/pwm/pwmchip0/pwm0
目录下有以下几个文件:
enable
:写入1使能pwm
,写入0关闭pwm
;polarity
:有normal
或inversed
两个参数选择,表示输出引脚电平翻转;duty_cycle
:在normal
模式下,表示一个周期内高电平持续的时间(单位:纳秒),在reversed
模式下,表示一个周期中低电平持续的时间(单位:纳秒);period
:表示pwm
波的周期(单位:纳秒);
设置pwm0
输出频率100KHz
,占空比50%
, 极性为正极性:
root@rk3399:/sys/class/pwm/pwmchip0# cd pwm0
root@rk3399:/sys/class/pwm/pwmchip0/pwm0# echo 100000 > period
root@rk3399:/sys/class/pwm/pwmchip0/pwm0# echo 50000 > duty_cycle
root@rk3399:/sys/class/pwm/pwmchip0/pwm0# echo normal > polarity
root@rk3399:/sys/class/pwm/pwmchip0/pwm0# cat enable
0
root@rk3399:/sys/class/pwm/pwmchip0/pwm0# echo 1 > enable
四、Thermal
4.1 简介
Thermal
是内核开发者定义的一套支持根据指定governor
控制系统温度,以防止芯片过热的框架模型。Thermal
框架由governor
、core
、cooling device
、sensor driver
组成;
其中:
thermal governor
:温控策略,用于决定cooling device
是否需要降频,降到什么程度;thermal core
:对thermal governors
和thermal driver
进行了封装和抽象,并定义了清晰的接口;thermal cooling device
:发热源或者可以降温的设备,比如CPU
、GPU
、DDR
等;thermal sensor driver
:sensor
驱动,可以获取温度的设备,比如tsadc
;
目前内核支持的温控策略较多,具体如下:
power_allocator
:引入PID
控制算法,根据当前温度,动态给各cooling device
分配power
,并将power
转换为频率,从而达到根据温度限制频率的效果;step_wise
:根据当前温度,cooling device
逐级降频;fair share
:频率档位比较多的cooling device
优先降频;userspace
:不限制频率。
thermal cooling device
对应系统实施冷却措施的驱动,是温控的执行者。cooling device
维护一个cooling
等级,即state
,一般state
越高即系统的冷却需求越高;cooling device
根据不同等级的冷却需求进行冷却行为。
cooling device
只根据state
进行冷却操作,是实施者,而state
的计算由thermal governor
完成。
4.2 配置设备节点
4.2.1 tsadc
(thermal sensor
)
tsadc
控制器模块支持用户定义模式和自动模式;
- 用户定义模式下,
tsadc
的所有控制信号完全由软件通过写入寄存器进行直接控制; - 自动模式下,模块会自动轮询
tsadc
的输出,并检查结果。如果在一段时间内发现温度过高,将会生成中断来通知处理器采取降温措施;如果温度持续超过一段时间,将会触发TSHUT
信号给CRU
模块,让它重置整个芯片,或者通过GPIO
给PMIC
信号;需要特别注意的是如果配置成GPIO
复位,硬件上需要把tsadc_int
输出引脚连到PMIC
的复位脚,否则只能配置成CRU
复位。
我们使用的开发板是有过温保护电路设计的,如下图所示;
硬件引脚接线如下:
RK3399 | RK808 | 描述 | |
---|---|---|---|
GPIO1_A6/TSADC_INT | OTP_OUT_H | tsadc的过温中断信号,高电平 当温度超过设定阈值时,触发tsadc中断,三极管Q34导通,Q15截止,RESET_IN_H输出低电平,rk808电源芯片被复位,触发RK3399复位 |
|
RESET_IN_H | RESET/VPPOTP(6号引脚,输入,模拟电源输入) | rk808电源芯片的复位引脚 |
tsadc
(Temperature Sensor ADC
)在温控中作为thermal sensor
,用于获取温度,tsadc
驱动位于drivers/thermal/rockchip_thermal.c
,设备节点的配置参考文档 Documentation/devicetree/bindings/thermal/rockchip-thermal.yaml
。
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中追加配置;
&tsadc {
/* tshut mode 0:CRU 1:GPIO */
rockchip,hw-tshut-mode = <1>;
/* tshut polarity 0:LOW 1:HIGH */
rockchip,hw-tshut-polarity = <1>;
status = "okay";
};
在arch/arm64/boot/dts/rockchip/rk3399.dtsi
中配置有设备节点tsadc
:
tsadc: tsadc@ff260000 {
compatible = "rockchip,rk3399-tsadc";
reg = <0x0 0xff260000 0x0 0x100>;
interrupts = ;
assigned-clocks = <&cru SCLK_TSADC>;
assigned-clock-rates = <750000>;
clocks = <&cru SCLK_TSADC>, <&cru PCLK_TSADC>;
clock-names = "tsadc", "apb_pclk";
resets = <&cru SRST_TSADC>;
reset-names = "tsadc-apb";
rockchip,grf = <&grf>;
rockchip,hw-tshut-temp = <95000>;
pinctrl-names = "init", "default", "sleep";
pinctrl-0 = <&otp_pin>;
pinctrl-1 = <&otp_out>;
pinctrl-2 = <&otp_pin>;
#thermal-sensor-cells = <1>;
status = "disabled";
};
其中:
reg
:tsadc
控制器寄存器基地址和寄存器地址总长度;interrupts
:中断号及中断触发方式;resets
:复位信号;rockchip ,hw -tshut -temp
:硬件控制的关机温度值,95摄氏度;rockchip,hw-tshut-mode
:硬件控制的关机模式,0代表CRU
,1代表GPIO
;rockchip,hw-tshut-polarity
:硬件控制的活动极性,0代表低电平(LOW
),1代表高电平(HIGH
);tsadc
输出引脚配置,支持三种模式:init
和default
、sleep
;
引脚配置节点otp_pin
和otp_out
定义如下;
tsadc {
otp_pin: otp-pin {
rockchip,pins = <1 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>;
};
otp_out: otp-out {
rockchip,pins = <1 RK_PA6 1 &pcfg_pull_none>;
};
};
otp_out
节点就是配置GPIO1_PA6
引脚功能复用为tsadc_int
;
4.2.2 cpu&gpu
(cooling device
)
cooling device
设备节点的配置参考文档 Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml
;
cpu
在温控中作为cooling device
,节点中需要包含#cooling-cells
、dynamic-power-coefficient
属性;
Power allocator
温控策略引入PID
控制,根据当前温度,动态给各cooling device
分配power
,温度低的时候可分配的power
比较大,即可以运行的频率高,随着温度上升,可分配的power
逐渐减小,可运行的频率也逐渐降低,从而达到根据温度限制频率。
在arch/arm64/boot/dts/rockchip/rk3399.dtsi
中配置有设备节点cpu_l0
、cpu_l1
、cpu_l2
、cpu_l3
、cpu_b0
、cpu_b1
:对应四Cortex-A53
小核结构(up to 1.5GHz
)+双Cortex-A72
大核(up to 2.0GHz
) ;
以cpu_l0
、cpu_b0
为例,配置如下:
cpu_l0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x0 0x0>;
enable-method = "psci";
capacity-dmips-mhz = <485>;
clocks = <&cru ARMCLKL>;
#cooling-cells = <2>; /* min followed by max */
dynamic-power-coefficient = <100>; /* 动态功耗常数C,动态功耗公式为Pdyn=C*V^2*F */
cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>;
};
cpu_b0: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a72";
reg = <0x0 0x100>;
enable-method = "psci";
capacity-dmips-mhz = <1024>;
clocks = <&cru ARMCLKB>;
#cooling-cells = <2>; /* min followed by max */
dynamic-power-coefficient = <436>; /* ⽤于计算动态功耗的参数 */
cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>;
thermal-idle {
#cooling-cells = <2>;
duration-us = <10000>;
exit-latency-us = <500>;
};
};
gpu
在温控中作为cooling device
,节点定义如下;
gpu: gpu@ff9a0000 {
compatible = "rockchip,rk3399-mali", "arm,mali-t860";
reg = <0x0 0xff9a0000 0x0 0x10000>;
interrupts = ,
,
;
interrupt-names = "job", "mmu", "gpu";
clocks = <&cru ACLK_GPU>;
#cooling-cells = <2>;
power-domains = <&power RK3399_PD_GPU>;
status = "disabled";
};
4.2.3 thermal_zones
thermal zone
代表一个温控管理区间,可以将其看做一个虚拟意义上的温度thermal sensor
, 需要有对应的物理thermal sensor
与其关联再能发挥作用;
- 一个
thermal zone
可以关联一个thermal sensor
; - 每个
thermal zone
可以维护多个trip point
。trip point
包含以下信息:temperature
:触发温度,当温度到达触发温度则该trip point
被触发,单位为毫摄氏度;hysteresis
:当热区温度达到trip温度属性时,触发冷却动作,并且该冷却动作会一直持续,直到温度降至(temperature
-hysteresis
)以下。这个滞后差值的作用是防止在冷却动作被移除后不久就频繁触发trip point
的情况发生,更通俗的说就是防抖;type
:trip point
类型,沿袭PC
散热方式,分为四种类型:passive
、active
、hot
、critical
;active
表示启用主动冷却,例如通过控制风扇进行冷却;passive
表示启用被动冷却,例如通过限制CPU
性能来进行降温;hot
表示将通知发送给驱动程序,具体采取的操作由驱动程序自行决定;critical
表示将通知发送给驱动程序,并触发系统关机。critical
类型用于设置超过最大温度阈值后硬件变得不稳定,底层固件甚至可能触发重新启动,达到临界阈值将会触发系统关机;
- 每个
thermal zone
可以维护多个cooling mapping
,描述trip point
与cooling device
的绑定关系,即当trip point
触发后由那个cooling device
去实施冷却措施。每个cooling mapping
包含一下信息;trip
:是一个指向thermal zone
内的trip point
节点的phandle
(句柄);trip point
节点定义了温度触发的点,用于确定何时采取相应的冷却措施;cooling-device
:是一个指向冷却设备列表的phandle
数组。每个冷却设备都带有最小和最大冷却状态的限定符。使用特定的THERMAL_NO_LIMIT
(-1UL
)常量可以让框架自动使用该冷却设备的最小和最大冷却状态;contribution
:表示被引用冷却设备在所引用trip point
的thermal zone
中的冷却贡献度。这个贡献度是在整个thermal zone
中所有冷却设备贡献度之和的比例;
thermal_zones
设备节点的配置参考文档 Documentation/devicetree/bindings/thermal/thermal-zones.yaml
;
在arch/arm64/boot/dts/rockchip/rk3399.dtsi
中配置有设备节点thermal_zones
;
thermal_zones: thermal-zones {
/* 1个节点对应1个thermal zone ,并包含温控策略相关参数 */
cpu_thermal: cpu-thermal {
/* 温度超过阀值时,每隔100ms查询温度 */
polling-delay-passive = <100>;
/* 温度未超过阀值时,每隔1000ms查询温度 */
polling-delay = <1000>;
/* 当前thermal zone通过tsadc0获取温度 */
thermal-sensors = <&tsadc 0>;
/* trips 包含不同温度阀值,不同的温控策略,配置不一定相同 */
trips {
cpu_alert0: cpu_alert0 {
/* 超过70摄氏度,温控策略开始工作 */
temperature = <70000>;
/* 设置滞后温度为68摄氏度,表示当下降到68摄氏度时解除温控 */
hysteresis = <2000>;
/* 表示超过该温度值时,使⽤polling-delay-passive */
type = "passive";
};
cpu_alert1: cpu_alert1 {
temperature = <75000>;
hysteresis = <2000>;
type = "passive";
};
/* 过温保护阀值,如果降频后温度仍然上升,那么超过该值后,让系统重启 */
cpu_crit: cpu_crit {
temperature = <95000>;
hysteresis = <2000>;
type = "critical";
};
};
cooling-maps {
map0 {
trip = <&cpu_alert0>;
cooling-device =
/* 对应的真正执行冷却操作的设备及最大/最小状态,格式为 */
<&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
map1 {
trip = <&cpu_alert1>;
cooling-device =
<&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_l1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_l2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_l3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
};
};
gpu_thermal: gpu-thermal {
polling-delay-passive = <100>;
polling-delay = <1000>;
thermal-sensors = <&tsadc 1>;
trips {
gpu_alert0: gpu_alert0 {
temperature = <75000>;
hysteresis = <2000>;
type = "passive";
};
gpu_crit: gpu_crit {
temperature = <95000>;
hysteresis = <2000>;
type = "critical";
};
};
cooling-maps {
map0 {
trip = <&gpu_alert0>;
cooling-device =
<&gpu THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
};
};
};
其中:
polling-delay
:在检测当前thermal zone
时,每次轮询之间等待的最大毫秒数。将其设置为0会禁用热框架设置的轮询定时器,并假设该区域中的热传感器支持中断;polling-delay-passive
:在进行被动冷却时(即温度超过trip point
中配置的触发温度时),检查当前thermal zone
时每次轮询之间等待的最大毫秒数。将其设置为0会禁用热框架设置的轮询定时器,并假设该区域中的热传感器支持中断;thermal-sensors
:用于监视当前thermal zone
的thermal sensor
的phandle
;trips
:每个子节点用于定义温度区间,在此区间内当温度达到某个点时,热框架需要采取相应的行动。这些行动可以是调整风扇速度、降低处理器频率或其他类似的操作;cooling-maps
:每个子节点描述了一个映射规则,每个规则描述在温度越过trip point
节点中描述的温度阈值时目标冷却设备需要采取的行动;
在arch/arm64/boot/dts/rockchip/rk3399-evb.dts
中追加配置;
&cpu_thermal {
trips {
cpu_warm: cpu_warm {
temperature = <55000>;
hysteresis = <2000>;
type = "active";
};
cpu_hot: cpu_hot {
temperature = <65000>;
hysteresis = <2000>;
type = "active";
};
};
cooling-maps {
map2 {
trip = <&cpu_warm>;
cooling-device = <&fan THERMAL_NO_LIMIT 1>;
};
map3 {
trip = <&cpu_hot>;
cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
};
};
};
&cpu_b0 {
cpu-supply = <&vdd_cpu_b>;
};
&cpu_b1 {
cpu-supply = <&vdd_cpu_b>;
};
&cpu_l0 {
cpu-supply = <&vdd_cpu_l>;
};
&cpu_l1 {
cpu-supply = <&vdd_cpu_l>;
};
&cpu_l2 {
cpu-supply = <&vdd_cpu_l>;
};
&cpu_l3 {
cpu-supply = <&vdd_cpu_l>;
};
这里我们配置了温度超过一定阈值时,比如上面cpu_warm
配置的触发温度为55摄氏度,采用散热风扇主动冷却。
其中vdd_cpu_l
、vdd_cpu_l
定义在i2c0
设备节点下:
&i2c0 {
......
vdd_cpu_b: regulator@40 {
compatible = "silergy,syr827";
reg = <0x40>;
fcs,suspend-voltage-selector = <1>;
pinctrl-names = "default";
pinctrl-0 = <&cpu_b_sleep>;
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <712500>;
regulator-max-microvolt = <1500000>;
regulator-name = "vdd_cpu_b";
regulator-ramp-delay = <1000>;
vin-supply = <&vcc3v3_sys>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
vdd_gpu: regulator@41 {
compatible = "silergy,syr828";
reg = <0x41>;
fcs,suspend-voltage-selector = <1>;
pinctrl-names = "default";
pinctrl-0 = <&gpu_sleep>;
regulator-always-on;
regulator-boot-on;
regulator-min-microvolt = <712500>;
regulator-max-microvolt = <1500000>;
regulator-name = "vdd_gpu";
regulator-ramp-delay = <1000>;
vin-supply = <&vcc3v3_sys>;
regulator-state-mem {
regulator-off-in-suspend;
};
};
......
};
4.3 配置内核
配置内核:
Device Drivers --->
-*- Thermal drivers --->
[*] Expose thermal sensors as hwmon device
[*] APIs to parse thermal data out of device tree
[*] Enable writable trip points
Default Thermal governor (step_wise) ---> /* default thermal governor */
[ ] Fair-share thermal governor
[*] Step_wise thermal governor /* step_wise governor */
[ ] Bang Bang thermal governor
[ ] User_space thermal governor /* user_space governor */
[*] Power allocator thermal governor
[*] Generic cpu cooling support /* cooling device */
[*] CPU frequency cooling device
[*] Generic device cooling support /* cooling device */
[*] Thermal emulation mode support
<*> Rockchip thermal driver
通过Default Thermal governor
配置项,可以选择温控策略,可以根据实际产品需求进⾏修改。
配置完内核之后记得保存配置:
存档:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# mv rk3399_defconfig ./arch/arm64/configs/
重新配置内核(如果不想重新编译内核,可以存档一份到.config
):
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make rk3399_defconfig
4.3.1 编译内核
在linux
内核根目录下执行如下命令进行编译内核:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make -j8
u-boot-2023.04
路径下的mkimage
工具拷贝过来,然后在命令行使用mkimage
工具编译即可:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../u-boot-2023.04/tools/mkimage ./
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ./mkimage -f kernel.its kernel.itb
4.3.2 通过tftp
烧录内核
给开发板上电,同时连接上网线,进入uboot
命令行。我们将内核拷贝到tftp
文件目录:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp kernel.itb /work/tftpboot/
接着给开发板上电。通过uboot
命令行将kernel.itb
下到内存地址0x10000000
处:
=> tftp 0x10000000 kernel.itb
通过mmc write
命令将内核镜像烧录到eMMC
第0x8000
个扇区处:
=> mmc erase 0x8000 0xA000
=> mmc write 0x10000000 0x8000 0xA000
4.4 测试
thermal
提供了用户层的接口,在 /sys/class/thermal/
节点下面;
zhengyang@rk3399:~$ ll /sys/class/thermal/
lrwxrwxrwx 1 root root 0 Mar 15 2023 cooling_device0 -> ../../devices/virtual/thermal/cooling_device0/
lrwxrwxrwx 1 root root 0 Mar 15 2023 thermal_zone0 -> ../../devices/virtual/thermal/thermal_zone0/
lrwxrwxrwx 1 root root 0 Mar 15 2023 thermal_zone1 -> ../../devices/virtual/thermal/thermal_zone1/
4.4.1 控制风扇
可以通过下面的节点查看风扇这个cooling device
的信息:
root@rk3399:~# ll /sys/class/thermal/cooling_device0/
-rw-r--r-- 1 root root 4096 Sep 21 00:51 cur_state
-r--r--r-- 1 root root 4096 Sep 21 00:51 max_state
drwxr-xr-x 2 root root 0 Sep 21 00:51 power/
lrwxrwxrwx 1 root root 0 Mar 15 2023 subsystem -> ../../../../class/thermal/
-r--r--r-- 1 root root 4096 Sep 21 00:51 type
-rw-r--r-- 1 root root 4096 Mar 15 2023 uevent
获取cooling device
当前的档位为0,即风扇转速最小,处于停止状态:
root@rk3399:~# cat /sys/class/thermal/cooling_device0/cur_state
0
获取cooling device
最大档位:
root@rk3399:~# cat /sys/class/thermal/cooling_device0/max_state
5
可以echo 0-5的值到
/sys/class/thermal/cooling_device0/cur_state`进行手动调试转速;
root@rk3399:~# echo 2 > /sys/class/thermal/cooling_device0/cur_state
4.4.2 thermal_zones
有的平台thermal_zones
节点下只有1个子节点,对应/sys/class/thermal/
目录下也只有thermal_zone0
一个子目录;有的平台有两个子节点,对应/sys/class/thermal/
目录下就会有thermal_zone0
和thermal_zone1
子目录。
通过用户态接口可以切换温控策略,查看当前温度等。:
root@rk3399:/# ls /sys/class/thermal/thermal_zone0
available_policies emul_temp mode temp trip_point_2_hyst trip_point_4_temp
cdev0 hwmon2 offset trip_point_0_hyst trip_point_2_temp trip_point_4_type
cdev0_trip_point integral_cutoff policy trip_point_0_temp trip_point_2_type type
cdev0_weight k_d power trip_point_0_type trip_point_3_hyst uevent
cdev1 k_i slope trip_point_1_hyst trip_point_3_temp
cdev1_trip_point k_po subsystem trip_point_1_temp trip_point_3_type
cdev1_weight k_pu sustainable_power tr
root@rk3399:/sys/class/thermal/thermal_zone0# cat temp # 获取CPU温度,温度未达到55摄氏度,风扇不会转动
48750
root@rk3399:/sys/class/thermal/thermal_zone0# cat policy
step_wise
root@rk3399:/sys/class/thermal/thermal_zone0# cat available_policies
power_allocator step_wise
root@rk3399:/sys/class/thermal/thermal_zone0# ls cdev0/
cur_state max_state power/ subsystem/ type uevent
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev0/cur_state # 获取当前档位
0
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev0/max_state # 最大档位
5
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev0/type
pwm-fan
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev1/cur_state # cdev0对应cpu_hot设备节点
0
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev1/max_state
5
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev1/type
pwm-fan
其中:
available_policies
:可选的温控governor
,如power_allocator
、user_space
、step_wise
;emul_temp
:模拟设置thermal
的温度,单位毫摄氏度,设置后update_temperature
中可获取到这个温度,和实际达到这个温度同效果。只有其为0,读取的才是真正的温度值,否则就是echo
的模拟值;temp
:存放tsadc
采集到的温度,测试时往emul_temp
中写的值也会体现在这里;trip_point_xxx
:这一系列文件保存的就是设备节点cpu_thermal
子节点trips
的内容;mode
:enabled
:自带定时获取温度,判断是否需要降频;disabled
关闭该功能;policy
:保存是当前的温控策略;integral_cutoff
:PID
算法中I
的触发条件:当前温度-期望的最⾼温度<integral_cutoff
;k_d
、k_i
、k_po
、k_pu
:PID
算法中使用的参数;cdev0
:对应cooling-maps
第一个子设备节点cpu_warm
;cur_state
:cooling device
当前的档位;max_state
:cooling device
最多有几个档位;type
:cooling device
的名称;
cdev0_trip_point
:保存设备节点cpu_warm
关联的trip point
在trips
中的的索引;cdev0_weight
:该cooling device
在计算power
时扩⼤的倍数;type
:thermal zone
的名称,对应于设备树thermal_zones
下子节点的名字,此例中为cpu-thermal
;sustainable_power
:期望的最高温度下对应的power
值;
比如我想测试温度达到55摄氏度时散热风扇是否运行在1档,可以输入如下命令:
root@rk3399:/sys/class/thermal/thermal_zone0# echo 55000 > emul_temp
root@rk3399:/sys/class/thermal/thermal_zone0# cat cdev0/cur_state
1
由于采用了step_wise
温控策略,对于cooling state
选择的策略;
- 当
throttle
发生且温升趋势为上升,使用更高一级的cooling state
; - 当
throttle
发生且温升趋势为下降,不改变cooling state
; - 当
throttle
解除且温升趋势为下降,不改变cooling state
; - 当
throttle
解除且温升趋势为上升,使用更低一级的cooling state
;
其中:throttle
=当前温度 > trip point
设备节点中设置的触发温度temperature
/滞后温度;
step_wise
是每个轮询周期逐级提高冷却状态,是一种相对温和的温控策略。
我想测试温度为120摄氏度时是否会强制关机,可以输入如下命令:
root@rk3399:/sys/class/thermal/thermal_zone0# echo 120000 > emul_temp
参考文章
[1] rk3399 linux
风扇调试
[2] PWM
控制
[3] 2.6.35内核的gpio子
系统详解
[4] 控制rk3399
的某个GPIO
[5] Rockchip_Developer_Guide_Linux_IO_DOMAIN_CN.pdf
[6] RK3399 Thermal
(温度控制)
[7] Linux Thermal
学习笔记
[8] Rockchip_Developer_Guide_Thermal_CN.pdf