mcbinfand

mcbinfand.git
git clone git://git.lenczewski.org/mcbinfand.git
Log | Files | Refs | README | LICENSE

fand.c (3728B)


      1 /*
      2  * mcbinfand - fan daemon for the MACCHIATObin board
      3  * Copyright (C) 2020 Matteo Croce <mcroce@microsoft.com>
      4  * Copyright (C) 2025 Mikolaj Lenczewski <mikolaj@lenczewski.org>
      5  *
      6  * This program is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation, either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18  */
     19 
     20 #include <assert.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <signal.h>
     24 #include <setjmp.h>
     25 #include <time.h>
     26 #include <unistd.h>
     27 
     28 #include <gpiod.h>
     29 
     30 #define GPIO_DEV "/dev/gpiochip1"
     31 #define GPIO_DEV_OFFSET 16
     32 
     33 #define THERM_PATH "/sys/class/thermal/thermal_zone0/temp"
     34 
     35 #define MIN_TEMP 40000
     36 #define MAX_TEMP 55000
     37 
     38 /* 1 kHz consumes around 0.7% of a CPU */
     39 #define PWM_USECS 1000
     40 
     41 // #define DEBUG
     42 
     43 static jmp_buf loopjmp;
     44 
     45 static void
     46 sighandler(int _unused)
     47 {
     48 	longjmp(loopjmp, 1);
     49 }
     50 
     51 static long
     52 read_temp()
     53 {
     54 	char buf[16] = { 0 };
     55 	static FILE *ftemp;
     56 	long temp;
     57 
     58 	if (!ftemp)
     59 		ftemp = fopen(THERM_PATH, "r");
     60 
     61 	/* Protect from burning if temp can't be read */
     62 	if (!ftemp)
     63 		return MAX_TEMP;
     64 
     65 	fseek(ftemp, 0, SEEK_SET);
     66 	fread(buf, 1, sizeof(buf), ftemp);
     67 
     68 	/* Same here */
     69 	if (ferror(ftemp)) {
     70 		perror("fread");
     71 		fclose(ftemp);
     72 		ftemp = NULL;
     73 		return MAX_TEMP;
     74 	}
     75 
     76 	temp = strtol(buf, NULL, 10);
     77 
     78 	return temp;
     79 }
     80 
     81 static int
     82 duty_cycle(int temp)
     83 {
     84 	if (temp < MIN_TEMP)
     85 		return 0;
     86 
     87 	if (temp >= MAX_TEMP)
     88 		return PWM_USECS;
     89 
     90 	/* Scale the duty cycle from MIN_TEMP to MAX_TEMP */
     91 	return (temp - MIN_TEMP) * PWM_USECS / (MAX_TEMP - MIN_TEMP);
     92 }
     93 
     94 int
     95 main(int argc, char *argv[])
     96 {
     97 	struct gpiod_chip *chip;
     98 	struct gpiod_line_config *cfg;
     99 	struct gpiod_line_settings *settings;
    100 	struct gpiod_line_request *line;
    101 	struct timespec oldtp;
    102 	int on = PWM_USECS;
    103 	int off = 0;
    104 
    105 	chip = gpiod_chip_open(GPIO_DEV);
    106 	assert(chip);
    107 
    108 	cfg = gpiod_line_config_new();
    109 	assert(cfg);
    110 
    111 	settings = gpiod_line_settings_new();
    112 	assert(settings);
    113 
    114 	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT);
    115 	gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE);
    116 
    117 	unsigned offset = GPIO_DEV_OFFSET;
    118 	gpiod_line_config_add_line_settings(cfg, &offset, 1, settings);
    119 
    120 	line = gpiod_chip_request_lines(chip, NULL, cfg);
    121 	assert(line);
    122 
    123 	gpiod_line_request_set_value(line, offset, GPIOD_LINE_VALUE_INACTIVE);
    124 
    125 	signal(SIGINT, sighandler);
    126 	signal(SIGQUIT, sighandler);
    127 	signal(SIGTERM, sighandler);
    128 
    129 	clock_gettime(CLOCK_MONOTONIC, &oldtp);
    130 
    131 	while (!setjmp(loopjmp)) {
    132 		struct timespec now;
    133 
    134 		clock_gettime(CLOCK_MONOTONIC, &now);
    135 		if (now.tv_sec > oldtp.tv_sec) {
    136 			int temp = read_temp();
    137 
    138 			oldtp = now;
    139 			on = duty_cycle(temp);
    140 			off = PWM_USECS - on;
    141 
    142 #ifdef DEBUG
    143 			printf("temp: %0.1f, duty cycle: %d%%: (%d on, %d off)\n",
    144 			      temp / 1000.0, on * 100 / PWM_USECS, on, off);
    145 #endif
    146 		}
    147 
    148 		if (on) {
    149 			gpiod_line_request_set_value(line, offset, GPIOD_LINE_VALUE_ACTIVE);
    150 			usleep(on);
    151 		}
    152 
    153 		if (off) {
    154 			gpiod_line_request_set_value(line, offset, GPIOD_LINE_VALUE_INACTIVE);
    155 			usleep(off);
    156 		}
    157 	}
    158 
    159 	puts("exiting...");
    160 
    161 	/* Don't leave the fan off and burn the CPU */
    162 	usleep(100000);
    163 	gpiod_line_request_set_value(line, offset, GPIOD_LINE_VALUE_ACTIVE);
    164 	gpiod_line_request_release(line);
    165 	gpiod_chip_close(chip);
    166 
    167 	return 0;
    168 }