/* $NetBSD: emcfanctloutputs.c,v 1.2 2025/03/12 14:01:49 brad Exp $ */
/*
* Copyright (c) 2025 Brad Spencer <
[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef __RCSID
__RCSID("$NetBSD: emcfanctloutputs.c,v 1.2 2025/03/12 14:01:49 brad Exp $");
#endif
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <mj.h>
#include <dev/i2c/emcfanreg.h>
#include <dev/i2c/emcfaninfo.h>
#define EXTERN
#include "emcfanctl.h"
#include "emcfanctlconst.h"
#include "emcfanctlutil.h"
#include "emcfanctloutputs.h"
int
output_emcfan_info(int fd, uint8_t product_id, int product_family, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
mj_t obj;
char *s = NULL;
char *pn;
char fn[8];
err = emcfan_read_register(fd, EMCFAN_REVISION, &res, debug);
if (err != 0)
goto out;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "revision", "integer", (int64_t)res);
mj_append_field(&obj, "product_id", "integer", (int64_t)product_id);
mj_append_field(&obj, "product_family", "integer", (int64_t)product_family);
pn = emcfan_product_to_name(product_id);
mj_append_field(&obj, "chip_name", "string", pn, strlen(pn));
emcfan_family_to_name(product_family, fn, sizeof(fn));
mj_append_field(&obj, "family_name", "string", fn, strlen(fn));
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
emcfan_family_to_name(product_family, fn, sizeof(fn));
printf("Product Family: %s\n", fn);
printf("Chip name: %s\n", emcfan_product_to_name(product_id));
printf("Revision: %d\n", res);
}
out:
return(err);
}
static void
output_emcfan_generic_reg_list(uint8_t product_id, const struct emcfan_registers the_registers[], long unsigned int the_registers_size, bool jsonify, bool debug)
{
mj_t array;
mj_t obj;
char *s = NULL;
int iindex;
iindex = emcfan_find_info(product_id);
if (iindex == -1) {
printf("Unknown info for product_id: %d\n",product_id);
exit(2);
}
if (debug)
fprintf(stderr, "output_emcfan_generic_reg_list: iindex=%d\n",iindex);
if (jsonify) {
memset(&array, 0x0, sizeof(array));
mj_create(&array, "array");
}
for(long unsigned int i = 0;i < the_registers_size;i++) {
if (emcfan_reg_is_real(iindex, the_registers[i].reg)) {
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "register_name", "string", the_registers[i].name, strlen(the_registers[i].name));
mj_append_field(&obj, "register", "integer", (int64_t)the_registers[i].reg);
mj_append(&array, "object", &obj);
mj_delete(&obj);
} else {
printf("%s\t%d\t0x%02X\n",the_registers[i].name,the_registers[i].reg,the_registers[i].reg);
}
}
}
if (jsonify) {
mj_asprint(&s, &array, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
mj_delete(&array);
}
}
void
output_emcfan_register_list(uint8_t product_id, int product_family, bool jsonify, bool debug)
{
if (debug)
fprintf(stderr,"output_emcfan_list: product_id=%d, product_family=%d\n",product_id, product_family);
switch(product_family) {
case EMCFAN_FAMILY_210X:
switch(product_id) {
case EMCFAN_PRODUCT_2101:
case EMCFAN_PRODUCT_2101R:
output_emcfan_generic_reg_list(product_id, emcfanctl_2101_registers, __arraycount(emcfanctl_2101_registers), jsonify, debug);
break;
case EMCFAN_PRODUCT_2103_1:
output_emcfan_generic_reg_list(product_id, emcfanctl_2103_1_registers, __arraycount(emcfanctl_2103_1_registers), jsonify, debug);
break;
case EMCFAN_PRODUCT_2103_24:
output_emcfan_generic_reg_list(product_id, emcfanctl_2103_24_registers, __arraycount(emcfanctl_2103_24_registers), jsonify, debug);
break;
case EMCFAN_PRODUCT_2104:
output_emcfan_generic_reg_list(product_id, emcfanctl_2104_registers, __arraycount(emcfanctl_2104_registers), jsonify, debug);
break;
case EMCFAN_PRODUCT_2106:
output_emcfan_generic_reg_list(product_id, emcfanctl_2106_registers, __arraycount(emcfanctl_2106_registers), jsonify, debug);
break;
default:
printf("UNSUPPORTED YET %d\n",product_id);
exit(99);
break;
};
break;
case EMCFAN_FAMILY_230X:
output_emcfan_generic_reg_list(product_id, emcfanctl_230x_registers, __arraycount(emcfanctl_230x_registers), jsonify, debug);
break;
};
}
static int
output_emcfan_230x_read_reg(int fd, uint8_t product_id, int product_family, uint8_t start, uint8_t end, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
mj_t array;
mj_t obj;
char *s = NULL;
int iindex;
const char *rn;
iindex = emcfan_find_info(product_id);
if (iindex == -1) {
printf("Unknown info for product_id: %d\n",product_id);
exit(2);
}
if (debug)
fprintf(stderr, "output_emcfan_230x_read_reg: product_id=%d, product_family=%d, iindex=%d\n",product_id, product_family, iindex);
if (jsonify) {
memset(&array, 0x0, sizeof(array));
mj_create(&array, "array");
}
for(int i = start; i <= end; i++) {
if (emcfan_reg_is_real(iindex, i)) {
err = emcfan_read_register(fd, i, &res, debug);
if (err != 0)
break;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
rn = emcfan_regname_by_reg(product_id, product_family, i);
mj_append_field(&obj, "register_name", "string", rn, strlen(rn));
mj_append_field(&obj, "register", "integer", (int64_t)i);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_append(&array, "object", &obj);
mj_delete(&obj);
} else {
printf("%s;%d (0x%02X);%d (0x%02X)\n",emcfan_regname_by_reg(product_id, product_family, i),i,i,res,res);
}
}
}
if (jsonify) {
mj_asprint(&s, &array, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
mj_delete(&array);
}
return(err);
}
int
output_emcfan_register_read(int fd, uint8_t product_id, int product_family, uint8_t start, uint8_t end, bool jsonify, bool debug)
{
int err = 0;
if (debug)
fprintf(stderr,"output_emcfan_register_read: start=%d 0x%02X, end=%d 0x%02X\n",start, start, end, end);
switch(product_family) {
case EMCFAN_FAMILY_210X:
err = output_emcfan_230x_read_reg(fd, product_id, product_family, start, end, jsonify, debug);
break;
case EMCFAN_FAMILY_230X:
err = output_emcfan_230x_read_reg(fd, product_id, product_family, start, end, jsonify, debug);
break;
};
return(err);
}
int
output_emcfan_minexpected_rpm(int fd, uint8_t product_id, int product_family, uint8_t config_reg, bool jsonify, bool debug)
{
int err = 0;
uint8_t raw_res, res;
uint8_t clear_mask;
int human_value;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, config_reg, &raw_res, debug);
if (err != 0)
goto out;
clear_mask = fan_minexpectedrpm[0].clear_mask;
res = raw_res & clear_mask;
if (debug)
fprintf(stderr,"%s: clear_mask=0x%02X 0x%02X, raw_res=%d (0x%02X), res=%d (0x%02X)\n",__func__,clear_mask,(uint8_t)~clear_mask,raw_res,raw_res,res,res);
human_value = find_human_int(fan_minexpectedrpm, __arraycount(fan_minexpectedrpm), res);
if (human_value == -10191)
return(EINVAL);
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "minimum_expected_rpm", "integer", (int64_t)human_value);
mj_append_field(&obj, "register", "integer", (int64_t)config_reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)raw_res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("Minumum expected rpm:%d\n",human_value);
}
out:
return(err);
}
int
output_emcfan_edges(int fd, uint8_t product_id, int product_family, uint8_t config_reg, bool jsonify, bool debug)
{
int err = 0;
uint8_t raw_res, res;
uint8_t clear_mask;
int human_value;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, config_reg, &raw_res, debug);
if (err != 0)
goto out;
clear_mask = fan_numedges[0].clear_mask;
res = raw_res & clear_mask;
if (debug)
fprintf(stderr,"%s: clear_mask=0x%02X 0x%02X, raw_res=%d (0x%02X), res=%d (0x%02X)\n",__func__,clear_mask,(uint8_t)~clear_mask,raw_res,raw_res,res,res);
human_value = find_human_int(fan_numedges, __arraycount(fan_numedges), res);
if (human_value == -10191)
return(EINVAL);
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "num_edges", "integer", (int64_t)human_value);
mj_append_field(&obj, "register", "integer", (int64_t)config_reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)raw_res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("Number of edges:%d\n",human_value);
}
out:
return(err);
}
static int
output_emcfan_simple_int(int fd, uint8_t product_id, int product_family, uint8_t reg, const char *what, const char *whatj, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, whatj, "integer", (int64_t)res);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer",(int64_t) res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("%s:%d\n",what, res);
}
out:
return(err);
}
int
output_emcfan_drive(int fd, uint8_t product_id, int product_family, uint8_t reg, bool jsonify, bool debug)
{
return(output_emcfan_simple_int(fd, product_id, product_family, reg, "Drive", "drive_level", jsonify, debug));
}
int
output_emcfan_divider(int fd, uint8_t product_id, int product_family, uint8_t reg, bool jsonify, bool debug)
{
return(output_emcfan_simple_int(fd, product_id, product_family, reg, "Divider", "frequency_divider", jsonify, debug));
}
int
output_emcfan_pwm_basefreq(int fd, uint8_t product_id, int product_family, uint8_t reg, int the_fan, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
int tindex;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
tindex = find_translated_blob_by_bits_instance(fan_pwm_basefreq, __arraycount(fan_pwm_basefreq), res, the_fan);
if (debug)
fprintf(stderr,"%s: reg=%d 0x%02X, res=0x%02x, tindex=%d, the_fan=%d\n",__func__,reg,reg,res,tindex,the_fan);
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "fan", "integer", (int64_t)the_fan+1);
mj_append_field(&obj, "pwm_base_frequency", "integer", (int64_t)fan_pwm_basefreq[tindex].human_int);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("PWM Base Frequency:%d\n",fan_pwm_basefreq[tindex].human_int);
}
out:
return(err);
}
int
output_emcfan_polarity(int fd, uint8_t product_id, int product_family, uint8_t reg, int the_fan, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
int mask;
bool inverted = false;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
if (product_id == EMCFAN_PRODUCT_2101 ||
product_id == EMCFAN_PRODUCT_2101R) {
mask = 0x10;
} else {
mask = 1 << the_fan;
}
if (res & mask)
inverted = true;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "fan", "integer", (int64_t)the_fan+1);
mj_append_field(&obj, "inverted", "integer", (int64_t)inverted);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("Inverted:%s\n",(inverted ? "Yes" : "No"));
}
out:
return(err);
}
int
output_emcfan_pwm_output_type(int fd, uint8_t product_id, int product_family, uint8_t reg, int the_fan, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
int mask;
bool pushpull = false;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
mask = 1 << the_fan;
if (res & mask)
pushpull= true;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "fan", "integer", (int64_t)the_fan+1);
mj_append_field(&obj, "pwm_output_type", "integer", (int64_t)pushpull);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("PWM Output Type:%s\n",(pushpull ? "push-pull" : "open drain"));
}
out:
return(err);
}
int
output_emcfan_fan_status(int fd, uint8_t product_id, int product_family, uint8_t start_reg, uint8_t end_reg, int the_fan, bool jsonify, bool debug)
{
int err = 0;
uint8_t res[4];
bool stalled = false;
bool spin_up_fail = false;
bool drive_fail = false;
uint8_t stall_mask = 0;
uint8_t spin_mask = 0;
uint8_t drive_mask = 0;
char *s = NULL;
mj_t obj;
res[0] = res[1] = res[2] = res[3] = 0;
if (product_family == EMCFAN_FAMILY_210X) {
err = emcfan_read_register(fd, start_reg, &res[0], debug);
if (err != 0)
goto out;
err = emcfan_read_register(fd, start_reg, &res[0], debug);
if (err != 0)
goto out;
switch(the_fan) {
case 0:
stall_mask = 0b00000001;
spin_mask = 0b00000010;
drive_mask = 0b00100000;
break;
case 1:
stall_mask = 0b00000100;
spin_mask = 0b00001000;
drive_mask = 0b01000000;
break;
default:
fprintf(stderr,"No status for fan: %d\n", the_fan + 1);
err = EINVAL;
};
if (debug)
fprintf(stderr,"%s: product_family=%d, stall_mask=0x%02X, spin_mask=0x%02X, drive_mask=0x%02X, res=0x%02X\n",__func__,
product_family, stall_mask, spin_mask, drive_mask, res[0]);
stalled = (res[0] & stall_mask);
spin_up_fail = (res[0] & spin_mask);
drive_fail = (res[0] & drive_mask);
} else {
int j = 0;
for(uint8_t i = start_reg; i <= end_reg;i++,j++) {
err = emcfan_read_register(fd, i, &res[j], debug);
if (err != 0)
goto out;
}
j = 0;
for(uint8_t i = start_reg; i <= end_reg;i++,j++) {
err = emcfan_read_register(fd, i, &res[j], debug);
if (err != 0)
goto out;
}
if (debug)
fprintf(stderr,"%s: product_family=%d, res[0]=0x%02X, res[1]=0x%02X, res[2]=0x%02X, res[3]=0x%02X\n",
__func__, product_family, res[0], res[1], res[2], res[3]);
stalled = (res[1] & (1 << the_fan));
spin_up_fail = (res[2] & (1 << the_fan));
drive_fail = (res[3] & (1 << the_fan));
}
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "fan", "integer", (int64_t)the_fan+1);
mj_append_field(&obj, "stalled", "integer", (int64_t)stalled);
mj_append_field(&obj, "spin_up_fail", "integer", (int64_t)spin_up_fail);
mj_append_field(&obj, "drive_fail", "integer", (int64_t)drive_fail);
mj_append_field(&obj, "register1", "integer", (int64_t)start_reg);
mj_append_field(&obj, "register1_value", "integer", (int64_t)res[0]);
mj_append_field(&obj, "register2", "integer", (int64_t)start_reg+1);
mj_append_field(&obj, "register2_value", "integer", (int64_t)res[1]);
mj_append_field(&obj, "register3", "integer", (int64_t)start_reg+2);
mj_append_field(&obj, "register3_value", "integer", (int64_t)res[2]);
mj_append_field(&obj, "register4", "integer", (int64_t)start_reg+3);
mj_append_field(&obj, "register4_value", "integer", (int64_t)res[3]);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("Stalled: %s\n",stalled ? "Yes" : "No");
printf("Spin up failed: %s\n",spin_up_fail ? "Yes" : "No");
printf("Drive failed: %s\n",drive_fail ? "Yes" : "No");
}
out:
return(err);
}
int
output_emcfan_apd(int fd, uint8_t product_id, int product_family, uint8_t reg, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
bool antiparalleldiode = false;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
if (res & 0x01)
antiparalleldiode = true;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "apd", "integer", (int64_t)antiparalleldiode);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("APD:%s\n",(antiparalleldiode ? "On" : "Off"));
}
out:
return(err);
}
int
output_emcfan_smbusto(int fd, uint8_t product_id, int product_family, uint8_t reg, int instance, bool jsonify, bool debug)
{
int err = 0;
uint8_t res;
int tindex;
bool smbusto = false;
char *s = NULL;
mj_t obj;
err = emcfan_read_register(fd, reg, &res, debug);
if (err != 0)
goto out;
tindex = find_translated_blob_by_bits_instance(smbus_timeout, __arraycount(smbus_timeout), res, instance);
if (debug)
fprintf(stderr,"%s: reg=%d 0x%02X, res=0x%02x, tindex=%d, instance=%d\n",__func__,reg,reg,res,tindex,instance);
/* The logic is inverted for the timeout */
smbusto = (res & smbus_timeout[tindex].clear_mask) ? false : true;
if (jsonify) {
memset(&obj, 0x0, sizeof(obj));
mj_create(&obj, "object");
mj_append_field(&obj, "smbus_timeout", "integer", (int64_t)smbusto);
mj_append_field(&obj, "register", "integer", (int64_t)reg);
mj_append_field(&obj, "register_value", "integer", (int64_t)res);
mj_asprint(&s, &obj, MJ_JSON_ENCODE);
printf("%s",s);
if (s != NULL)
free(s);
} else {
printf("SMBUS timeout:%s\n",(smbusto ? "On" : "Off"));
}
out:
return(err);
}