/*	$NetBSD: emcfanctloutputs.c,v 1.2 2025/03/12 14:01:49 brad Exp $	*/

/*
 * Copyright (c) 2025 Brad Spencer <brad@anduin.eldar.org>
 *
 * 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);
}

