Rockchip RK3399 - GPIO&PWM风扇调试

发布时间 2023-09-21 00:37:20作者: 大奥特曼打小怪兽

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

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

一、电路原理图

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

1.1 电路原理图

下图是我们使用的NanoPC-T4开发板AP6356的接线图:

VCC_12V0_SYS为系统电压,电压级别为12V。风扇支持通过GPIOPWM控制;

上图中:

  • GPIO4_C6/PWM1 输出高电平时,三极管Q1导通,N MOSQ1截止,BM03B-GHS-TBT 2号引脚没有电压输入,风扇停止转动;
  • GPIO4_C6/PWM1 输出低电平时,三极管Q1截止,N MOSQ1导通,BM03B-GHS-TBT 2号引脚输入12V电压,风扇转动;

至于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):

支持两种电压配置:

  • 寄存器配置成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的电源域需要配置包含bt565audiosdmmc,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命令将内核镜像烧录到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口在内核中的编号;

每个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结构体对应的,以gpiochip32为例;

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

盱眙:需要先配置电源域:不然的话配置的输出不会生效。

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, these levels
		 * work out to 0, ~1200, ~3000, and 5000RPM respectively.
		 */
		cooling-levels = <0 12 18 255>;
		#cooling-cells = <2>;
		fan-supply = <&vcc12v0_sys>;
		pwms = <&pwm1 0 50000 PWM_POLARITY_INVERTED>;
};

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:表示极性,为可选参数;上面例子中的配置为负极性。

属性cooling-levelsPWM占空比的取值范围是0到255,对应着热量冷却状态。这些值通常被映射到特定的占空比,用于控制风扇或泵等冷却设备的速度。

属性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

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

img

存档:

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占空比

PWM提供了用户层的接口,在 /sys/class/pwm/节点下面,PWM驱动加载成功后,会在/sys/class/pwm/目录下产生pwmchip0目录;

root@rk3399:~# cd /sys/class/pwm/pwmchip0/
root@rk3399:/sys/class/pwm/pwmchip0# ll
lrwxrwxrwx 1 root root    0 Sep 20 23:45 device -> ../../../ff420000.pwm/
--w------- 1 root root 4096 Sep 20 23:45 export
-r--r--r-- 1 root root 4096 Sep 20 23:45 npwm
drwxr-xr-x 2 root root    0 Sep 20 23:45 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 20 23:45 unexport

export文件写入0,就是打开pwm定时器0,会产生一个pwm0目录,相反的往unexport写入0就会关闭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

通过修改占空比达到控制风扇转动,占空比的有效操作大致在0-255左右,数据值越大,转速越慢;

root@rk3399:/sys/class/pwm/pwmchip0#  echo 100 > /sys/devices/platform/pwm-fan/hwmon/hwmon1/pwm1

参考文章

[1] rk3399 linux风扇调试

[2] PWM控制

[3] 2.6.35内核的gpio子系统详解

[4] 控制rk3399的某个GPIO

**[5] Rockchip_Developer_Guide_Linux_IO_DOMAIN_CN.pdf **