Controlling PWM fans with the Raspberry Pi CM4 IO Board's EMC2301

Noctua 120mm PWM fan connected to Raspberry Pi CM4 IO Board

When I initially reviewed the Compute Module 4 IO Board, I briefly mentioned there's a 4-pin fan connector. It's connected to the Pi's I2C bus using a little PWM chip, the EMC2301.

But wait... what's I2C, what's PWM, and what's so special about a 4-pin fan connector? I'm glad you asked—this post will answer that and show you how you can control a fan connected to the IO Board, like the quiet Noctua NF-P12 pictured above with my IO Board.

If you plug a fan like that into the CM4 IO Board, it will start running full blast, 24x7. If you need that much cooling, that's great, but a lot of times, I don't mind my Pi's CPU getting warmer if it means I can run the fan silent most of the time.

2022-05 Update: Recently, a driver for the EMC2301 fan controller was merged into Raspberry Pi's Linux fork, so it will appear in the next release of Raspberry Pi OS.

Once that's done, all you'd need to do (if running Pi OS) is add the following line to your /boot/config.txt file:

dtoverlay=i2c-fan,emc2301,i2c_csi_dsi

See this comment on GitHub for more details, and how to control PWM speeds and trigger temperatures.

So what are my options? First of all, I could just buy an inline PWM controller, like a Noctua NA-FC1. It lets me turn up and down the fan speed with a little dial. But it doesn't know the temperature of my Pi, so it can't increase airflow for higher temperatures or turn off the fan when it's under a certain temperature.

EMC2301 Fan controller on Raspberry Pi CM4 IO Board

The better option is to use the built-in PWM fan controller on the IO Board (pictured above). And to do that, we're going to need to use the Raspberry Pi's I2C bus!

What is I2C?

I2C—or more correctly, I2C—stands for "Inter-Integrated Circuit" and is a two-wire serial communication interface used by many electronic devices for control and communications.

I'm not going to cover it in detail here, but if you get into any more advanced electronics projects with Arduino, Raspberry Pi, or other microcontrollers or PCs, you'll probably encounter it. To learn the basics of the protocol, I recommend Analog Device's I2C Primer.

Controlling the fan over I2C

You have to edit your /boot/config.txt file to enable the i2c_vc bus, which is bus #1. The Pi Device Tree Documentation actually recommends against touching i2c_vc unless you need to, because you could mess up CSI camera or DSI display functionality.

Make sure the following lines exist and are uncommented in /boot/config.txt and reboot the Pi:

# Enable I2C.
dtparam=i2c_arm=on
# Enable I2C bus 1.
dtparam=i2c_vc=on

Note: If you just enable I2C under the 'Interfaces' option of raspi-config, it will only enable i2c_arm. To see the fan controller, you need to enable i2c_vc as well.

Make sure the i2c-tools package is installed on your system; if it is, the following commands should work straightaway. If not, you will need to install the package with sudo apt-get install -y i2c-tools.

Now, check if you can see the fan controller chip on the bus, using i2cdetect -y 10:

$ i2cdetect -y 10
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- 0c -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 2f
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The fan is the 2f device. Test if you can turn off the fan using:

$ i2cset -y 10 0x2f 0x30 0x00

The fan should now be off. And to turn it back on:

$ i2cset -y 10 0x2f 0x30 0xff

To get the value of the fan setting, you can use:

$ i2cget -y 10 0x2f 0x30
0xff

What about setting the fan to a value between 0% (off) and 100% (full on) though? The value is hexadecimal, so 0xFF stands for 255, while 0x00 is 0. Using a high-ish number should be safe, right, to set the fan to a lower speed? Well, let's try it out.

First, set the fan speed to 'off':

$ i2cset -y 10 0x2f 0x30 0x00

Wait for the fan to spin down entirely, then set the fan to 100 (or 0x64 in hex):

$ i2cset -y 10 0x2f 0x30 0x64

If you do this, you'll notice the fan comes back on, but hopefully at a much more pleasant speed. On one of my Noctua fans, 100/255 equates to about 40% speed, or 1200 rpm, and it's nearly silent.

Now that we can control the fan over I2C, we could write up a script to set fan speeds based on CPU temperatures manually, but there are a few other ways to control the fan speeds.

Note: The 'Fan' performance option inside raspi-config currently has no effect on the operation of a fan through the EMC2301 chip.

Can I use Linux lm-sensors and fancontrol?

Unfortunately, the standard way of controlling fan speeds based on sensor data on most common PC hardware doesn't seem to be supported on the CM4 IO Board's I2C chip.

If I install lm-sensors and fancontrol (following this guide), then I run sudo sensors-detect, I get back the message:

Sorry, no sensors were detected.
Either your system has no sensors, or they are not supported, or
they are connected to an I2C or SMBus adapter that is not
supported. If you find out what chips are on your board, check
https://hwmon.wiki.kernel.org/device_support_status for driver status.

It did find 'clients' on the I2C bus at 0x51 and 0x2f, but it couldn't identify them. And when I ran sudo pwmconfig, I got the message:

/usr/sbin/pwmconfig: There are no pwm-capable sensor modules installed

So it looks like that's out of the running, unfortunately. There's a really old patch for Linux to add the fan controller to the Linux source tree, but for some reason it never got worked on beyond early stages. There's also an issue discussing the IO Board fan controller in the Raspberry Pi Linux kernel project, in case you want to subscribe and see the latest updates.

Dec 2021 update: It looks like there's a newer patch here and a new firmware driver that some folks at Pine64 have been working on.

The cm4io-fan driver

GitHub user neg2led is maintaining an open source CM4 IO Board Fan controller driver, which is the next best thing. This driver is based on Traverse Technologies' EMC2301 hwmon driver.

To install it, I made sure I had I2C enabled as written above, and ran the following:

# Install the Raspberry Pi kernel headers, so the source build works.
sudo apt install raspberrypi-kernel-headers

# Install DKMS, to make updating the driver easier.
sudo apt install dkms

# Get the URL (tar.gz) of the latest release: https://github.com/neg2led/cm4io-fan/releases
wget https://github.com/neg2led/cm4io-fan/archive/refs/tags/0.1.1.tar.gz

# Expand the contents of the download into your /usr/src directory.
sudo tar -xzvf 0.1.1.tar.gz -C /usr/src/

# Build/install the driver with DKMS.
sudo dkms install cm4io-fan/0.1.1

At this point, the driver should be installed and will work after a reboot, once you configure it.

Note: I've only tested the driver on 64-bit Pi OS, but other users have reported it successfully compiles and works on 32-bit Pi OS as well.

Configuring the driver

Configuration can be done in the /boot/config.txt file. Add a line like the following:

# Control fan speeds.
dtoverlay=cm4io-fan,minrpm=1000,maxrpm=3000

This would set the fan to stay on at least at 1000 rpm at all times, and it would go up to 3000 rpm once the Pi's SoC reaches the maxtemp, which by default is 5500 in millicelcius (55°C).

You can override other options such as temperature thresholds using the driver's config options.

You can also check the current fan speed with the following command:

$ cat /sys/class/hwmon/hwmon2/fan1_input
2146

Driver problems

The driver seems to work better on some CM4 boards than others, and may also have issues with certain PWM fans. One of the issues I encountered seems to be related to a potential bug in the reference design on the official IO Board.

If you encounter issues, check if the problem you hit is already documented in the cm4io-fan issue queue.

Basic Temperature-controlled fan script

As a final option you could write your own temperature-controlled fan script, which checks the current temperature, and boosts the fan speed accordingly. It's nowhere near as fully featured as the driver above, but it could work in a pinch:

Conclusion

There are a few different ways to interact with the EMC2301 fan controller on the Raspberry Pi Compute Module 4 IO Board (and a few other CM4 boards I've tested, like the Seaberry), but the cm4io-fan driver seems the most promising.

Hopefully you've learned a little about I2C in this post, too—I know I've learned a bit more about it, how PWM fans work, and even why tuning things like fan curves are important!

Comments

I can confirm that the cm4io-fan driver builds and works great under 32-bit Pi OS.

A bit of a tangent, but for the PiBox, we ended up skipping the EMC2301 and let the Pi provide the PWM signal directly (primarily because it was out of stock, but it also saves ~$1 on component cost)

The Pi has two hardware PWM channels (usually used for audio, L + R channels), and if you are ok giving up one of those, then you can drive a fan directly! Someone smarter than me figured this out and wrote the drivers for it on GitHub: https://gist.github.com/alwynallan/1c13096c4cd675f38405702e89e0c536#gis…. So after trying that, we hardwired a PWM channel to a fan header on our PCBs, and it works perfectly!

Also.. your latest video prompted me to donate a chunk to Peter (the author of that driver). The open source donation campaign is a fantastic idea :)

Your latest video prompted me to donate a chunk to Peter

That's awesome! And nice use of a feature that probably 99% of the users of the PiBox wouldn't ever even think about anyways!

I also recently found out about cm4-pwm-fan, a Python script to control the PWM fan on the CM4. Looks like a nice simple utility that's in some ways easier to comprehend if you're more used to Python, and less C. But I'm not sure how much more or less CPU the Python script running in the background would take.

I gave the cm4io-fan driver a try. It worked well for me for about a day or so but I just found it running at full speed again. Checked fan1_input and it was not at full rpm's, it was sitting around 2600. Gave the pi a reboot and it is back to running at a more quiet speed now. Guess I'll see how it goes again.

im on raspberry pi os 64 bit and it does not work for me is there a workaround ?

5500 in millicelcius (55°C)

Should be 55,000 millicelsius. 😉

I wrote a script, based on yours, to read the fan RPM.

#!/bin/bash

# Equation taken from section 4.4 of the EMC2301 datasheet

# Explicitly set $PATH so i2c tools are found.
PATH=$PATH:/usr/sbin

A=$(($(($(printf '%d\n' "$((16#$(i2cget -y 10 0x2f 0x32 | sed -e 's/0x//g')))") & 96)) >> 5))
M=$(echo  "2^$A"  | bc -l)

CH=$(printf '%d\n' "$((16#$(i2cget -y 10 0x2f 0x3e | sed -e 's/0x//g')))")
CL=$(printf '%d\n' "$((16#$(i2cget -y 10 0x2f 0x3f | sed -e 's/0x//g')))")
#echo $CH $CL

COUNT=$(echo "32*$CH+ $CL" | bc -l)
echo "COUNT= $COUNT, M = $M"

MULT=3932160

RPM=$(echo "$MULT*$M/$COUNT" | bc -l)
#echo $RPM' RPM'
RPMR=$(printf %.0f $(echo $RPM))
echo $RPMR' RPM'

Awesome guide, thank you very much!
I think it works very well for me, but for some reason the controller (i guess) makes a very high pitched and relatively loud noise, epecially, when the fan is spinning with low rpm.
The fan itself is really quite.

Do you have any idea why that's the case, and if there's something i can do about it? Maybe not using the EMC2301, since it seems to be the problem?

Sadly does not work for me.
I am running a cm4 on a Waveshare IO-BASE-B which should be able to run with cm4io-fan.

I believe that the problem lies in the i2c bus, as i only have i2c-1,20 and 21 but no 10.

Running the latest 64bit version.

Does anyone know how to do control the fan in retropie specifically?

Sorry for that question, but I'm not able to get a script getting executed at boot. (for testing I just made a script containing the following line:
i2cset -y 10 0x2f 0x30 0x00
so the fan should turn off at (re)boot. But it doesn't!

I already tried calling the script via crontab and /etc/init.d but none of these worked.

Ho can I get it to work?

Sorry again for that question, but I'm quite new to scripting on linux...

This was an awesome post thank you!

Hi Jeff. Thanx so much for this post. I has kept me posted on the progress on a feature I very much wanted. :)

See this post: https://forums.raspberrypi.com/viewtopic.php?t=337918#p2023930

That "dtparam" 3rd line option seems to be working for me. (fan stopped spinning at full speed) Sorry if I missed this in your post. I actually cant find official pi documentation if this is even there.

I want to control the PWM fan I have attached to my CM4 IO board, but with Bookworm I get "UU" instead of "2F". When I try to write to that location I get...

me@mycm4:~ $ i2cset -y 10 0x2f 0x30 0x00
Error: Could not set address to 0x2f: Device or resource busy
me@mycm4:~ $ i2cdetect -y 10
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

I suspect that there is a daemon controlling the fan now with bookworm, but I can find no info on it. Any suggestions? Thanks!

Hello Jeff,
All is good now. I looked at the config.txt over and over and noticed I had...
dtoverlay=cm4io-fan,minrpm=500,maxrpm=1700,midtemp=58000,maxtemp=68000
... not commented out. Not sure when I put that in, I do not even have cm4io-fan installed. And I thought I had everything checked before commenting here. My apologies! Thanks for the info!!

It seems some of this has been added to the latest kernel provided by Bullseye and Bookworm (see the comments posted by Jeff here: https://github.com/raspberrypi/linux/issues/4632).

This means that simply adding the following to the [all] section of /boot/config.txt should allow the kernel to automatically manage the fan speed based on temperature:

[all]
dtoverlay=i2c-fan,emc2301

You can also add a couple of extra parameters to customize the config:

[all]
dtoverlay=i2c-fan,emc2301,minpwm=50,maxpwm=255,midtemp=40000,maxtemp=75000

However, there seems to be a bug in the latest kernel (6.1.21-v8+) provided through the Raspberry Pi OS Bullseye distribution. The fan just seems to be stuck at zero RPM and is not managed properly. Using rpi-update 6c2b033bf556c2a2ae109ec85d86485fa4c16050 to move the kernel to 6.1.73-v8+ fixes the issue and the fan speeds up as temperature increases.

For all those trying to make this work with Bullseye in combination with the wonderful post on ZFS provided by Jeff here: https://www.jeffgeerling.com/blog/2021/htgwa-create-zfs-raidz1-zpool-on-raspberry-pi , you will also need to update kernel headers to get the zfs-dkms package to install properly. Using rpi-source will allow you to do that. For those running 64-bit OS, you will need an updated version of rpi-source provided by this pull-request if it has not been merged yet.

I am using this with the PWM variant of the LoveRPi POE Hat. Having dynamic fan speed on my home Pi with ZFS volumes has been a huge win. Thanks Jeff!!!