(思考実験) Raspi4 Case Fanを"きちんとPWM制御"するには？

#TL;DR  Raspberry Pi 4 Case Fanはソフト的にもPWM制御できていない（かもしれない）。

なお、この記事はRaspi買ってない人がソースコードベースで調べた結果なので、間違っているかもしれないし、その通りにやるともしかするとソフトやハードにダメージがあるかもしれません。その前提でご確認ください。

- gpio-fan だと、onとoffとか、レベル制御しかできない。
- HWスペックとしてはPWM制御できると書いている。
- もったいない！もったいない！！

ここまでの調査メモ。


#はじめに

@nekokurono7 さんがなかなか興味深い記事を書いていた。

- [Raspberry Pi OSに搭載されたファン制御機能が怪しいので実験してみた1](https://qiita.com/nekokurono7/items/a0f634a71e6aaaaccfa8)
- [Raspberry Pi OSに搭載されたファン制御機能が怪しいので実験してみた2](https://qiita.com/nekokurono7/items/c947e781fae85855d280)

これについて、もう少しソフトウェア方面で追加検証をしてみるよう。

なお、私自身はRaspberry pi 4持っていないので、「多分こうなるんじゃないのかなー」レベルです。


#公式ファンとはどういう物なのか？ どういう商品なのか？

Raspberry Piの記事が[こちら](https://www.raspberrypi.org/products/raspberry-pi-4-case-fan/)。 

ここにもしっかりと"ユーザーが選択したGPIO pinを介して、PWM制御ができる"と書いてある。

>Fan speed control: Pulse width modulation control via user-selectable GPIO pin

# PWMでファン制御するとは？

@sh_o さんの[Raspberry Pi でPWM信号を使って5Vファンを制御する](https://qiita.com/sh_o/items/30252850d8353f0f4158)が非常にわかりやすい。

簡単に言うと以下となる。概念を雑に説明すると、PWMの信号を高速にHigh/Lowすると、これに合わせて電源-FAN-GNDが通電する。

- 電源(5V) 
- GND 
- PWM

# Raspi公式の設定方法では？

[raspberry-pi-4-case-fan 公式サイト](https://www.raspberrypi.org/products/raspberry-pi-4-case-fan/) に書いてある通り、Raspberry Pi Configuration toolから設定する。

> Then open the Raspberry Pi Configuration tool:
> 1. Click on the Raspberry Pi icon in the top left corner and select Preferences then Raspberry Pi Configuration.
> 2. Select the Performance tab.
> 3. Next to Fan, click Enabled.
> 4. If you have connected your fan as shown above, the default of 14 for Fan GPIO does not need to be changed.
> 5. Select the Fan Temperature at which you want your fan to turn on. The default is 80°C, which will stop the Raspberry Pi throttling on difficult tasks without having the fan on all the time.

# Raspberry Pi Configuration toolのソースコードでは？

[raspi-config#L1278](https://github.com/RPi-Distro/raspi-config/blob/master/raspi-config#L1278)

ここの設定を見ると、要するにkernel のcommand line引数に以下を追加するに見える。むむむ...

> sed \$CONFIG -i -e "\$adtoverlay=gpio-fan,gpiopin=\$GPIO,temp=\$TEMP"

# Linux Kernelのソースコードでは？

Raspberry pi 4のlinux kernelツリーは https://github.com/raspberrypi/linux

gpio-fanのソースコードは https://github.com/raspberrypi/linux/blob/rpi-5.10.y/drivers/hwmon/gpio-fan.c

```c:drivers/hwmon/gpio-fan.c#L482
static const struct of_device_id of_gpio_fan_match[] = {
	{ .compatible = "gpio-fan", },
	{},
};
MODULE_DEVICE_TABLE(of, of_gpio_fan_match);
```

このコードを追うと、疑問が出てくる...

## PWM制御方法が若干違う・・・かも？

起動時や設定変更時でファン速度が変わる場合、set_fan_speed()関数をcallする。
ここから、__set_fan_ctrl()が呼ばれる。

```c:drivers/hwmon/gpio-fan.c#L128
/* Must be called with fan_data->lock held, except during initialization. */
static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
{
	if (fan_data->speed_index == speed_index)
		return;

	__set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val);
	fan_data->speed_index = speed_index;
}
```

```c:drivers/hwmon/gpio-fan.c#L100
/*
 * Control GPIOs.
 */

/* Must be called with fan_data->lock held, except during initialization. */
static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val)
{
	int i;

	for (i = 0; i < fan_data->num_gpios; i++)
		gpiod_set_value_cansleep(fan_data->gpios[i],
					 (ctrl_val >> i) & 1);
}
```

あれ……　これ、指定されたindexに合わせて、GPIOでレベル指定して制御しているだけに見える・・・　ちょっと待ってあれ？

## gpio-fanのdocumentを読んでみる

最近のデバイスドライバは、Documentation/devicetreeの下にも簡易的にだがマニュアルがある。

https://github.com/raspberrypi/linux/blob/rpi-5.10.y/Documentation/devicetree/bindings/hwmon/gpio-fan.txt

```plaintext
	gpio_fan {
		compatible = "gpio-fan";
		gpios = <&gpio1 14 1
			 &gpio1 13 1>;
		gpio-fan,speed-map = <0    0
				      3000 1
				      6000 2>;
		alarm-gpios = <&gpio1 15 1>;
	};
```

このドライバは今回のような1本の信号線上でパルス信号を送るのではなく、複数のGPIO信号線でレベル指定してそれをICでPWM化/DC-ACするパターンなのでは…？　例えば、[buffaloのGPIO fan制御](https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/mvebu-linkstation-fan.dtsi)とかはこのパターンに見える... （名前は、リニア制御方式、でいいのかな？）

## つまりどうやって動いていたように見えるのか？

簡単にいうと「PWM制御できるハードを、On/Offのレベル制御でしかつかっていない」ですね…。  　もったいない、もったいない。

|   | GPIO |
|:-:|:-:|
| 停止  | LOW |
| 全力  | HIGH  |


# どうやったらPWM制御になりそうか（予想）

## 使うべきドライバは"pwm-fan"

gpio-fanではなく、pwm-fanを使うと制御できそう。

[pwm-fan.txt](https://github.com/raspberrypi/linux/blob/rpi-5.10.y/Documentation/devicetree/bindings/hwmon/pwm-fan.txt)

## PWM制御はHW?SW?

SoftでPWM制御もできる話にはなっているけど、そんなことlinux動作中にやると普通に厳しいので、HW制御を前提にする。

BCM2711のdata sheetを見る限り、Channel0/1は分離していないので、片方で設定をすればよさそう。

[bcm2711.dtsi#L274](https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2711.dtsi#L274)

```
		pwm1: pwm@7e20c800 {
			compatible = "brcm,bcm2835-pwm";
			reg = <0x7e20c800 0x28>;
			clocks = <&clocks BCM2835_CLOCK_PWM>;
			assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
			assigned-clock-rates = <10000000>;
			#pwm-cells = <2>;
			status = "disabled";
		};
```


## 接続先はGPIOのどこにする？　ー＞　18とかかなあ…

まず、linux kernel側のdevice tree設定で、GPIO40/41はPWM定義されている。これを真似して、PWM0を定義しないとダメっぽい。

[bcm2711.dtsi#L799](https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2711.dtsi#L799)

```
	pwm1_0_gpio40: pwm1_0_gpio40 {
		pin-pwm {
			pins = "gpio40";
			function = "alt0";
			bias-disable;
		};
	};
```




[bcm2711-rpi-4-b.dts#L213](https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2711-rpi-4-b.dts#L213)

```
&pwm1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;
	status = "okay";
};
```

## device treeに付け足すべきは？

多分、こんな感じになると思うんだけど、、、　ここからは現物がないので妄想ですね。

```
	pwm-fan {
		compatible = "pwm-fan";
		#cooling-cells = <2>;
		pwms = <&pwm1 1 1250000>;
		cooling-levels = <16 128 192 240>;
	};
```
pwmsの引数
-　https://www.kernel.org/doc/Documentation/devicetree/bindings/pwm/pwm.txt


- 1 : た、たぶんこちらは1chに設定でいいはず。
- 1250000: nano sec。PWMのperiod(1サイクルの長さ)。
- (設定なし): PWM_POLARITY_INVERTEDが指定できるらしい
 
数字はかなり適当に、3000 rpm中に16分割で制御で、1.3ミリ秒間隔。もーっとおそくても（時間が長くても）いいかもしれない。

60 * 1,000,000,000 ns / 3000 rpm / 16 = 1,250,000

# まとめ

実ハードウェアが無い状況で調べたことなので、間違っているかもしれません。興味のある方はこのあたり、夏休みの課題でチャレンジしてもいいかもしれませんね！

以上になります。