Raspberry Pi 4のファンをCPU温度に応じてPWMで速度調整する方法

# 背景

Raspberry PiにPWM制御可能なファンを取り付けている場合、PythonでGPIOピンをPWM制御する方法などが検索にヒットするが、OSに組み込まれている仕組みを使って簡単に実現できることがわかった。

[ファンの例(Amazon)](https://www.amazon.co.jp/GeeekPi-PWM%E5%88%B6%E5%BE%A1%E5%8F%AF%E8%83%BD%E3%83%95%E3%82%A1%E3%83%B3Raspberry-PWM%E9%80%9F%E5%BA%A6%E8%AA%BF%E6%95%B4%E3%83%95%E3%82%A1%E3%83%B340x40x10mm-4010%E9%9D%99%E3%81%8B%E3%81%AA%E3%83%95%E3%82%A1%E3%83%B3Raspberry-3%EF%BC%88%E3%83%96%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%89%E3%81%AB%E9%81%A9%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99/dp/B092VRPC8H?th=1)

本記事の内容は、ほぼ以下ページに記載がある。

https://forums.raspberrypi.com/viewtopic.php?t=363655

# 環境

- Raspberry Pi 4B
- Raspberry Pi OS (Bookworm)
  - Linux rpi4b 6.6.28+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.28-1+rpt1 (2024-04-22) aarch64 GNU/Linux


# 手順
## ソースの取得と変更

上記ページ内の `pwm-fan-overlay.dts` を取得し、Raspberry Piにコピーする

<details>
  <summary>上記ページ内のpwm-fan-overlay.dts</summary>

```cpp
/*
 * Overlay for a Raspberry Pi PWM Fan
 * References: 
 *
 * Optional parameters:
 *
 * pwm-fan-overlay.dts
 */
/dts-v1/;
/plugin/;

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/thermal/thermal.h>
#include <dt-bindings/pinctrl/bcm2835.h>

/ {
	compatible = "brcm,bcm2835";

	fragment@0 {
		target = <&gpio>;
		__overlay__ {
			pwm_pins: pwm_pins {
				brcm,pins = <18>;
				brcm,function = <BCM2835_FSEL_ALT5>;
				brcm,pull = <0>;
			};
		};
	};

	fragment@1 {
		target = <&pwm>;
		frag1: __overlay__ {
			pinctrl-names = "default";
			pinctrl-0 = <&pwm_pins>;
			/* in Hz */
			assigned-clock-rates = <100000000>;
			status = "okay";
		};
	};

	fragment@2 {
		target-path = "/";
		__overlay__ {
			fan: pwm-fan {
				compatible = "pwm-fan";
				#cooling-cells = <2>;
				/* in ns */
				pwms = <&pwm 0 20000000 0>;
				cooling-min-state = <0>;
				cooling-max-state = <4>;	
				/* PWM duty cycle values in a range from 0 to 255
				 * which correspond to thermal cooling states
				 */
				cooling-levels = <0 75 125 175 250>;
			};
		};
	};

	fragment@3 {
		target = <&cpu_thermal>;
		__overlay__ {
			polling-delay = <2000>; /* milliseconds */
		};
	};

	fragment@4 {
		target = <&thermal_trips>;
		__overlay__ {
			trip0: trip0 {
				temperature = <40000>;
				hysteresis = <2000>;
				type = "active";
			};
			trip1: trip1 {
				temperature = <45000>;
				hysteresis = <2000>;
				type = "active";
			};
			trip2: trip2 {
				temperature = <50000>;
				hysteresis = <2000>;
				type = "active";
			};
			trip3: trip3 {
				temperature = <55000>;
				hysteresis = <5000>;
				type = "active";
			};
		};
	};

	fragment@5 {
		target = <&cooling_maps>;
		__overlay__ {
			map0 {
				trip = <&trip0>;
				cooling-device = <&fan 0 1>;
			};
			map1 {
				trip = <&trip1>;
				cooling-device = <&fan 1 2>;
			};
			map2 {
				trip = <&trip2>;
				cooling-device = <&fan 2 3>;
			};
			map3 {
				trip = <&trip3>;
				cooling-device = <&fan 3 4>;
			};
		};
	};

	__overrides__ {
			fan_temp0 = <&trip0>,"temperature:0";
			fan_temp0_hyst = <&trip0>,"hysteresis:0";
			fan_temp0_speed = <&fan>, "cooling-levels:4";
			fan_temp1 = <&trip1>,"temperature:0";
			fan_temp1_hyst = <&trip1>,"hysteresis:0";
			fan_temp1_speed = <&fan>, "cooling-levels:8";
			fan_temp2 = <&trip2>,"temperature:0";
			fan_temp2_hyst = <&trip2>,"hysteresis:0";
			fan_temp2_speed = <&fan>, "cooling-levels:12";
			fan_temp3 = <&trip3>,"temperature:0";
			fan_temp3_hyst = <&trip3>,"hysteresis:0";
			fan_temp3_speed = <&fan>, "cooling-levels:16";
		};
};
```
</details>

### 必要に応じて編集する場所

#### ピン番号

`brcm,pins = <18>;` の部分がPWM制御をするピン番号。
`18`はGPIO18(=12番ピン)を表す。

ここを変更した場合は、`brcm,function = <BCM2835_FSEL_ALT5>;` も変更する必要があると考えられる。（未確認）

#### PWM周波数

`fragment@2`内の`pwms = <&pwm 0 20000000 0>;`の部分のうち、`20000000`がPWMの周期（ns）なので、ファンからノイズが聞こえる場合などは変更する。
`20000000`は 50Hz であり、私のファンではノイズがあったので `40000` (=25000Hz)に変更した。


## コンパイル

### プリプロセッサを通す

`#include`となっている部分は`cpp`を使って事前に処理を行う。

```
$ cpp -nostdinc -undef -x assembler-with-cpp -I /usr/src/linux-headers-6.6.28+rpt-common-rpi/include/ -o pwm-fan-overlay.tmp.dts pwm-fan-overlay.dts
```
※ヘッダーファイルの位置は各自の環境に合わせて変更する

### コンパイル

`dtc`コマンドを使用する

```
$ dtc -O dtb -o pwm-fan.dtbo pwm-fan-overlay.tmp.dts
```

### インストール

#### ファイルを配置

```
$ sudo mv pwm-fan.dtbo /boot/firmware/overlays/
```
※警告が出るが無視して問題なかった

#### 設定

`/boot/firmware/config.txt` で　`pwm-fan` を読み込むように変更

```
...
dtoverlay=pwm-fan
```

#### 設定変更

`config.txt`内で、以下の記法でパラメータを変更できる。

```
dtoverlay=pwm-fan
dtparam=fan_temp0=55000
dtparam=fan_temp0_hyst=5000
dtparam=fan_temp0_speed=75
dtparam=fan_temp1=62500
dtparam=fan_temp1_hyst=5000
dtparam=fan_temp1_speed=128
dtparam=fan_temp2=70000
dtparam=fan_temp2_hyst=5000
dtparam=fan_temp2_speed=192
dtparam=fan_temp3=77500
dtparam=fan_temp3_hyst=5000
dtparam=fan_temp3_speed=255
```

### 再起動
```
$ sudo reboot
```