Fan control

I have been running FreeBSD on my HoneyComb and it doesn’t seem to support setting the fan speed yet. Is there a way to set the fans speed from the UEFI firmware? I looked around and didn’t find anything.

Also I read somewhere the additional fan headers aren’t PWM, only the cpu header is PWN. Is that correct? That seemed wrong given its a 4 pin header but I have’t found documents clarifying it.

Currently FreeBSD only supports 2 fan speeds, default and full because there is no Host OS ACPI support for the I2C controllers. I am working on an automatic mode for OS’s that don’t support the I2C bus but haven’t integrated that fully into a release firmware yet.

Yes only the CPU header is connected to the PWM controller.

@jnettlet Can you provide more details on how to control the fan speed?

With the raw OS image I have been using so far the fan keeps running at full speed after boot, which is fine with me (at least once I had replaced the awful fan that came with the board…). However, an interesting side effect of switching from a raw image to a u-boot one is that the fan reduces speed before control is transferred to the OS:

## Booting kernel from Legacy Image at 80200000 ...
   Image Name:   
   Created:      2022-08-03  10:40:09 UTC
   Image Type:   AArch64 Linux Kernel Image (uncompressed)
   Data Size:    16147312 Bytes = 15.4 MiB
   Load Address: 80200000
   Entry Point:  80200800
   Verifying Checksum ... OK
## Flattened Device Tree blob at 80100000
   Booting using the fdt blob at 0x80100000
   Loading Kernel Image
   Loading Device Tree to 000000009fff1000, end 000000009ffff036 ... OK
Releasing fan controller full speed gpio
fsl-mc: DPL not deployed, DPAA2 ethernet not work

While I appreciate the reduced noise, I’m a bit hesitant to do kernel development with the fan not working at full speed (can never tell when something is going to go wrong…).

–Elad

That is a choose that was made by the BSP team since they can verify that the kernel is built with the appropriate kernel module to manage the PWM controller. Since EDK2 is firmware controlled thermal management I can not guarantee that the host OS knows how to manage the fan so I leave it fully on until the host OS initializes the _TZ ACPI methods. However if you are using Linux rather than BSD then any kernel should have no issues handling the ACPI _TZ methods.

I’m running QNX (the reason I bought this board is to evaluate SMP scalability on a new kernel architecture), so neither BSD nor Linux.
I’m not looking for full control, just a way to keep the fan running at full speed (or restore it to full speed after boot). I’ll gladly RTFM if there is any documentation.

–Elad

This is all managed by the PWM controller, it is programmable over i2c, or the full fan speed gpio is available. This is all on the schematics and the documentation for the controller is freely available.

I see, thanks. Just to make sure I got it right (sorry, I’m not a hardware person), the fan controller is on I2C address 0x18?

–Elad

yes that is correct. on i2c@1 off of the i2c expander.

Works now, with full PWM control based on temperature reading.

For those interested, the temperature/fan control registers can be controlled by I2C commands to the chip on address 0x18, as described here:

Note, however, that every command needs to be preceded by selecting channel 1 on the switch first, achieved by writing 0x9 to address 0x77.

You can see how the fan adjusts to a duty cycle of 70%:

elahav@honeycomb:~$ ./honeycomb_fan -d 70
temp=53 speed=2539
temp=53 speed=2214
temp=53 speed=1995
temp=50 speed=1857
temp=53 speed=1837
temp=53 speed=1795
temp=53 speed=1767
temp=53 speed=1733
temp=53 speed=1727

–Elad

@elahav could you share your script so I wont reinvent the wheel?

Edit I managed to compile this:

Weird thing to notice my PWM is on i²C@0 not 1 I am on Debian Bookworm (UEFI+ACPI)
Now need to script some systemd service for that mentioned program and should do.

Another thing. If someone wants to try Noctula NF-A4x10 it doesnt cool the SoC enough. Its max 5000RPM and even at that speed the SoC gets 71°C IDLE. I will add some side fans and control stock one with that mentioned program.

@AreYouLoco I was about to reply but then saw that you have it working. Not sure if my code will help you further, but here it is:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <hw/i2c.h>

static int  i2c_fd;
static int  verbose;

static bool
send_recv(
    uint32_t const          addr,
    uint8_t const * const   send,
    uint32_t const          sendlen,
    uint8_t * const         recv
)
{
    struct {
        i2c_sendrecv_t  hdr;
        uint8_t         data[8];
    } msg = {
        .hdr = {
            .slave = {
                .addr = addr,
                .fmt = I2C_ADDRFMT_7BIT
            },
            .send_len = sendlen,
            .recv_len = 1,
            .stop = 1
        }
    };

    memcpy(msg.data, send, sendlen);

    int const rc = devctl(i2c_fd, DCMD_I2C_SENDRECV, &msg, sizeof(msg), NULL);
    if (rc == -1) {
        perror("devctl");
        return false;
    }

    if (verbose) {
        fprintf(stderr, "%.2x %.2x %.2x\n", addr, send[0], msg.data[0]);
    }

    if (recv) {
        *recv = msg.data[0];
    }

    return true;
}

static uint8_t
amc6821_read_reg(uint8_t const reg)
{
    // Switch to channel 1.
    uint8_t channel = 9;
    if (!send_recv(0x77, &channel, 1, NULL)) {
        return 0;
    }

    // Read register.
    uint8_t value;
    if (!send_recv(0x18, &reg, 1, &value)) {
        return 0;
    }

    return value;
}

static void
amc6821_write_reg(uint8_t const reg, uint8_t const value)
{
    // Switch to channel 1.
    uint8_t channel = 9;
    if (!send_recv(0x77, &channel, 1, NULL)) {
        return;
    }

    // Write register.
    uint8_t data[2] = { reg, value };
    if (!send_recv(0x18, data, 2, NULL)) {
        return;
    }
}

int
main(int argc, char **argv)
{
    int duty = -1;

    for (;;) {
        int const opt = getopt(argc, argv, "d:v");
        if (opt == 'd') {
            duty = strtol(optarg, NULL, 0);
            if ((duty < 0) || (duty > 100)) {
                fprintf(stderr, "Bad duty cycle value: %d\n", duty);
            }
        } else if (opt == 'v') {
            verbose++;
        } else {
            break;
        }
    }

    i2c_fd = open("/dev/i2c0", O_RDWR);
    if (i2c_fd == -1) {
        perror("open");
        return EXIT_FAILURE;
    }

    // Enable fan speed monitoring and set software DCY control.
    uint8_t conf1 = amc6821_read_reg(0x0);
    conf1 |= 1 << 0;
    conf1 &= ~((1 << 6) | (1 << 5));
    amc6821_write_reg(0x0, conf1);

    uint8_t conf2 = amc6821_read_reg(0x1);
    conf2 |= 1 << 2;
    amc6821_write_reg(0x1, conf2);

    // Write a duty cycle value.
    if (duty != -1) {
        duty = (duty * 255) / 100;
        amc6821_write_reg(0x22, duty);
    }

    for (;;) {
        uint32_t tach = amc6821_read_reg(0x8);
        tach += (uint32_t)amc6821_read_reg(0x9) << 8;

        int8_t temp = (int8_t)amc6821_read_reg(0xa);

        printf("temp=%d speed=%u\n", temp, (6000000 / tach));
        sleep(1);
    }
    return EXIT_SUCCESS;
}

You just need to implement send_recv() for your system.

–Elad

1 Like