Rockchip RK3399 - GPIO&PWM风扇调试

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板eMMC16GBLPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏u-boot2023.04linux6.3
----------------------------------------------------------------------------------------------------------------------------

一、电路原理图

我所使用的NanoPC-T4开发板可以外接一个散热风扇,我们首先来介绍一下散热风扇硬件相关的内容。

1.1 电路原理图

下图是我们使用的NanoPC-T4开发板散热风扇的接线图:

Rockchip RK3399 - GPIO&PWM风扇调试_第1张图片

VCC_12V0_SYS为系统电压,电压级别为12V。我们可以通过GPIOPWM实现对风扇的控制;

上图中:

  • GPIO4_C6/PWM1 输出高电平时,三极管Q41导通,N MOSQ1导通,BM03B-GHS-TBT 2号引脚输入12V电鱼,风扇转动;
  • GPIO4_C6/PWM1 输出低电平时,三极管Q41截止,N MOSQ1截止,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资源进行了抽象,抽象出来的概念就是gpiolibgpiolib汇总了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.8v3.3v2.5v5.0v等,有些IO同时支持多种电压,io-domain就是配置IO电源域的寄存器,依据真实的硬件电压范围来配置对应的电压寄存器,否则无法正常工作。

RK3399datasheet中我们搜索GRF_IO_VSEL寄存器,位于GRF偏移地址offset(0x0e640):

Rockchip RK3399 - GPIO&PWM风扇调试_第2张图片

支持两种电压配置:

  • 寄存器配置成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的电源域需要配置包含bt565audiosdmmcgpio1830,以及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输出的;

Rockchip RK3399 - GPIO&PWM风扇调试_第3张图片

得到了配置的名称和供电源头,在设备树里面找到对应的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命令将内核镜像烧录到eMMC0x8000个扇区处:

=> 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文件夹下面都有baselabelngpio文件;

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控制器标签;
  • ngpioGPIO控制器包含的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,包含32GPIO口(GPIO4_A0~A7GPIO4_B0~B7GPIO4_C0~B7GPIO4_D0~D7),第一个GPIO口在内核的编号是128,所以该端口包含128-159GPIO口。

2.4 操作GPIO

GPIO资源对上层应用是以文件的形式呈现的,应用操作GPIO口就是读写相应的文件。GPIO的操作接口包括directionvalue等;

  • direction控制GPIO方向;
  • value控制GPIO输出或获得GPIO输入;

操作GPIO口步骤;

  • 通过数据手册和gpiochipXX文件夹查询到GPIOGPIO子系统的编号;
  • 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/PWM1GPIO子系统的编号为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接受的参数可以是:inouthighlow。其中参数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,这里指定为pwmphandle,后面有三个参数:

    • 参数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描述的是低电平的占空比;`
  • 属性cooling-levelsPWM占空比的取值范围是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

Rockchip RK3399 - GPIO&PWM风扇调试_第4张图片
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

配置完内核之后记得保存配置:

Rockchip RK3399 - GPIO&PWM风扇调试_第5张图片

存档:

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命令将内核镜像烧录到eMMC0x8000个扇区处:

=> 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:有normalinversed两个参数选择,表示输出引脚电平翻转;
  • 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框架由governorcorecooling devicesensor driver组成;

Rockchip RK3399 - GPIO&PWM风扇调试_第6张图片

其中:

  • thermal governor:温控策略,用于决定cooling device是否需要降频,降到什么程度;
  • thermal core:对thermal governorsthermal driver进行了封装和抽象,并定义了清晰的接口;
  • thermal cooling device:发热源或者可以降温的设备,比如CPUGPUDDR等;
  • thermal sensor driversensor驱动,可以获取温度的设备,比如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模块,让它重置整个芯片,或者通过GPIOPMIC信号;需要特别注意的是如果配置成GPIO复位,硬件上需要把tsadc_int输出引脚连到PMIC的复位脚,否则只能配置成CRU复位。

我们使用的开发板是有过温保护电路设计的,如下图所示;

Rockchip RK3399 - GPIO&PWM风扇调试_第7张图片

硬件引脚接线如下:

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";
};

其中:

  • regtsadc控制器寄存器基地址和寄存器地址总长度;
  • interrupts :中断号及中断触发方式;
  • resets:复位信号;
  • rockchip ,hw -tshut -temp:硬件控制的关机温度值,95摄氏度;
  • rockchip,hw-tshut-mode:硬件控制的关机模式,0代表CRU,1代表GPIO
  • rockchip,hw-tshut-polarity:硬件控制的活动极性,0代表低电平(LOW),1代表高电平(HIGH);
  • tsadc输出引脚配置,支持三种模式:initdefaultsleep

引脚配置节点otp_pinotp_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

Rockchip RK3399 - GPIO&PWM风扇调试_第8张图片
4.2.2 cpu&gpu(cooling device)

cooling device设备节点的配置参考文档 Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml

cpu在温控中作为cooling device,节点中需要包含#cooling-cellsdynamic-power-coefficient属性;

Power allocator温控策略引入PID控制,根据当前温度,动态给各cooling device分配power,温度低的时候可分配的power比较大,即可以运行的频率高,随着温度上升,可分配的power逐渐减小,可运行的频率也逐渐降低,从而达到根据温度限制频率。

arch/arm64/boot/dts/rockchip/rk3399.dtsi中配置有设备节点cpu_l0cpu_l1cpu_l2cpu_l3cpu_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 pointtrip point包含以下信息:
    • temperature:触发温度,当温度到达触发温度则该trip point被触发,单位为毫摄氏度;
    • hysteresis:当热区温度达到trip温度属性时,触发冷却动作,并且该冷却动作会一直持续,直到温度降至(temperature - hysteresis)以下。这个滞后差值的作用是防止在冷却动作被移除后不久就频繁触发trip point的情况发生,更通俗的说就是防抖;
    • typetrip point类型,沿袭PC散热方式,分为四种类型:passiveactivehotcritical
      • active表示启用主动冷却,例如通过控制风扇进行冷却;
      • passive表示启用被动冷却,例如通过限制CPU性能来进行降温;
      • hot表示将通知发送给驱动程序,具体采取的操作由驱动程序自行决定;
      • critical表示将通知发送给驱动程序,并触发系统关机。critical类型用于设置超过最大温度阈值后硬件变得不稳定,底层固件甚至可能触发重新启动,达到临界阈值将会触发系统关机;
  • 每个thermal zone可以维护多个cooling mapping,描述trip pointcooling device的绑定关系,即当trip point触发后由那个cooling device去实施冷却措施。每个cooling mapping包含一下信息;
    • trip:是一个指向thermal zone内的trip point节点的phandle(句柄);trip point节点定义了温度触发的点,用于确定何时采取相应的冷却措施;
    • cooling-device:是一个指向冷却设备列表的phandle数组。每个冷却设备都带有最小和最大冷却状态的限定符。使用特定的THERMAL_NO_LIMIT (-1UL)常量可以让框架自动使用该冷却设备的最小和最大冷却状态;
    • contribution:表示被引用冷却设备在所引用trip pointthermal 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 zonethermal sensorphandle
  • 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_lvdd_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配置项,可以选择温控策略,可以根据实际产品需求进⾏修改。

配置完内核之后记得保存配置:

Rockchip RK3399 - GPIO&PWM风扇调试_第9张图片

存档:

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命令将内核镜像烧录到eMMC0x8000个扇区处:

=> 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_zone0thermal_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_allocatoruser_spacestep_wise
  • emul_temp:模拟设置thermal的温度,单位毫摄氏度,设置后update_temperature中可获取到这个温度,和实际达到这个温度同效果。只有其为0,读取的才是真正的温度值,否则就是echo的模拟值;
  • temp:存放tsadc采集到的温度,测试时往emul_temp中写的值也会体现在这里;
  • trip_point_xxx:这一系列文件保存的就是设备节点cpu_thermal子节点trips的内容;
  • modeenabled :自带定时获取温度,判断是否需要降频;disabled关闭该功能;
  • policy:保存是当前的温控策略;
  • integral_cutoff PID算法中I的触发条件:当前温度-期望的最⾼温度<integral_cutoff
  • k_d k_i k_po k_pu PID算法中使用的参数;
  • cdev0 :对应cooling-maps第一个子设备节点cpu_warm;
    • cur_statecooling device当前的档位;
    • max_statecooling device最多有几个档位;
    • typecooling device的名称;
  • cdev0_trip_point:保存设备节点cpu_warm关联的trip pointtrips中的的索引;
  • cdev0_weight:该cooling device在计算power时扩⼤的倍数;
  • typethermal 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

你可能感兴趣的:(单片机,嵌入式硬件)