/*	$NetBSD$	*/

/*-
 * Copyright (c) 2026 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Julian Coleman.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <machine/autoconf.h>

#include <dev/ebus/ebusreg.h>
#include <dev/ebus/ebusvar.h>

#include <dev/led.h>
#include <dev/sysmon/sysmonvar.h>

#include <arch/sparc64/dev/envmon_ebusreg.h>

#define ENVMON_MACHINE_V245	0xf5
#define ENVMON_MACHINE_V215	0xd7

#define ENVMON_PIC_MAX_TEMPS	1

struct envmon_epic_fans {
	const uint8_t	reg;
	const char	*desc;
};
static struct envmon_epic_fans envmon_epic_fans_table[] = {
	{ ENVMON_EPC_FAN_FT0_F0, "FT0.F0" },
	{ ENVMON_EPC_FAN_FT1_F0, "FT1.F0" },
	{ ENVMON_EPC_FAN_FT2_F0, "FT2.F0" },
	{ ENVMON_EPC_FAN_FT3_F0, "FT3.F0" },
	{ ENVMON_EPC_FAN_FT4_F0, "FT4.F0" },
	{ ENVMON_EPC_FAN_FT5_F0, "FT5.F0" },
	{ ENVMON_EPC_FAN_FT0_F1, "FT0.F1" },
	{ ENVMON_EPC_FAN_FT1_F1, "FT1.F1" },
	{ ENVMON_EPC_FAN_FT2_F1, "FT2.F1" },
	{ ENVMON_EPC_FAN_FT3_F1, "FT3.F1" },
	{ ENVMON_EPC_FAN_FT4_F1, "FT4.F1" },
	{ ENVMON_EPC_FAN_FT5_F1, "FT5.F1" },
};
#define ENVMON_EPC_MAX_FANS	(sizeof(envmon_epic_fans_table) / \
				    sizeof(envmon_epic_fans_table[0]))
#define ENVMON_EPC_DIV_V245		5625
#define ENVMON_EPC_DIV_V215		11250
#define ENVMON_EPC_CRIT_SPEED_V245	2022	/* From ALOM */
#define ENVMON_EPC_CRIT_SPEED_V215	5012	/* From ALOM */
#define ENVMON_EPC_VAL_TO_SPEED(div, val)	((div * 60) / val)

struct envmon_epic_ind {
	uint8_t		reg, mask, set;
	const char	*desc;
};
static struct envmon_epic_ind envmon_epic_inds_table[] = {
	{ ENVMON_EPC_KEYSW, 0x04, 0x00, "keyswitch normal/diag" },
	{ ENVMON_EPC_KEYSW, 0x04, 0x04, "keyswitch locked" },
};

#define ENVMON_EPC_MAX_INDS	(sizeof(envmon_epic_inds_table) / \
				    sizeof(envmon_epic_inds_table[0]))

struct envmon_epic_led {
	uint8_t		reg, v_on;
	const char	*desc;
};
static struct envmon_epic_led envmon_epic_led_table[] = {
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_LOC, "locator" },
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_FLT, "fault" },
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_FLT_FSH, "fault_flash" },
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_PWR, "power" },
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_TF_FLT, "top_fan_fault" },
	{ ENVMON_EPC_LED6, ENVMON_EPC_LED6_TF_FLT_FSH, "top_fan_fault_flash" },
};
#define ENVMON_EPC_MAX_LEDS	(sizeof(envmon_epic_led_table) / \
				    sizeof(envmon_epic_led_table[0]))

#define ENVMON_EPC_MAX_SENSORS	(ENVMON_EPC_MAX_FANS + ENVMON_EPC_MAX_INDS)

#define ENVMON_MAX_SENSORS	MAX(ENVMON_PIC_MAX_TEMPS, \
    ENVMON_EPC_MAX_SENSORS)

struct envmon_sc_ind {
	uint8_t		reg, mask, set;
};
struct envmon_sc_led {
	void			*cookie;
	struct led_device	*led;
	uint8_t			reg, v_on;
};

struct envmon_softc {
	device_t		sc_dev;

	bus_space_tag_t		sc_bst;	
	bus_space_handle_t	sc_bsh;

	int			sc_machine_type, sc_num_fans;
	uint8_t			sc_fan_reg[ENVMON_EPC_MAX_FANS];
	struct sysmon_envsys	*sc_sme;
	envsys_data_t		sc_sensor[ENVMON_MAX_SENSORS];
	struct envmon_sc_ind	sc_ind[ENVMON_EPC_MAX_INDS];
	struct envmon_sc_led	sc_led[ENVMON_EPC_MAX_LEDS];
};

static int envmon_ebus_match(device_t, cfdata_t, void *);
static void envmon_ebus_attach(device_t, device_t, void *);
static void envmon_pic_attach(struct envmon_softc *, int);
static void envmon_epic_attach(struct envmon_softc *, int);
void envmon_pic_refresh(struct sysmon_envsys *, envsys_data_t *);
void envmon_epic_refresh(struct sysmon_envsys *, envsys_data_t *);
void envmon_epic_refresh_fan(struct envmon_softc *, envsys_data_t *);
void envmon_epic_refresh_ind(struct envmon_softc *, envsys_data_t *);
int envmon_epic_get_led(void *);
void envmon_epic_set_led(void *, int);

CFATTACH_DECL_NEW(envmon_ebus, sizeof(struct envmon_softc),
    envmon_ebus_match, envmon_ebus_attach, NULL, NULL);

#define	envmon_read(sc, reg) \
    bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg)
#define envmon_write(sc, reg, val) \
    bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg, val)

static int
envmon_ebus_match(device_t parent, cfdata_t cf, void *aux)
{
	struct ebus_attach_args *ea = aux;
	char *compat;

	if (strcmp("env-monitor", ea->ea_name) != 0)
		return 0;

	compat = prom_getpropstring(ea->ea_node, "compatible");
	if (compat != NULL && (!strcmp(compat, "SUNW,ebus-pic16f747-env") ||
	    !strcmp(compat, "epic")))
		return 1;

	return 0;
}

static void
envmon_ebus_attach(device_t parent, device_t self, void *aux)
{
	struct envmon_softc *sc = device_private(self);
	struct ebus_attach_args *ea = aux;
	char compat[32];
	int sz;

	sc->sc_dev = self;
	sc->sc_bst = ea->ea_bustag;

	sz = ea->ea_reg[0].size;

	if (bus_space_map(sc->sc_bst,
			 EBUS_ADDR_FROM_REG(&ea->ea_reg[0]),
			 sz, 0,
			 &sc->sc_bsh) != 0) {
		aprint_error(": can't map register\n");
		return;
	}

	sc->sc_sme = sysmon_envsys_create();
	sc->sc_sme->sme_name = device_xname(self);
	sc->sc_sme->sme_cookie = sc;

	OF_getprop(ea->ea_node, "compatible", compat, sizeof(compat));

	if (!strcmp(compat, "SUNW,ebus-pic16f747-env")) {
		envmon_pic_attach(sc, ea->ea_node);
	}
	if (!strcmp(compat, "epic")) {
		envmon_epic_attach(sc, ea->ea_node);
	}
}

/*
 * We only add the front panel temperature here.
 * Other U45 sensors are handled by the ADT7462 driver.
 */
static void
envmon_pic_attach(struct envmon_softc *sc, int node)
{
	int32_t vers;

	vers = prom_getpropint(node, "version", 0);
	aprint_normal(": pic16f747 (ver. %d)\n", vers);

	sc->sc_sme->sme_refresh = envmon_pic_refresh;
	strlcpy(sc->sc_sensor[0].desc, "Front_panel",
	    sizeof(sc->sc_sensor[0].desc));
	sc->sc_sensor[0].units = ENVSYS_STEMP;
	sc->sc_sensor[0].state = ENVSYS_SINVALID;
	sc->sc_sensor[0].flags = ENVSYS_FHAS_ENTROPY;
	if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[0])) {
		sysmon_envsys_destroy(sc->sc_sme);
		sc->sc_sme = NULL;
		aprint_error_dev(sc->sc_dev,
		    "unable to attach sensor to sysmon\n");
		return;
	}
	if (sysmon_envsys_register(sc->sc_sme)) {
		aprint_error_dev(sc->sc_dev,
		    "unable to register with sysmon\n");
		sysmon_envsys_destroy(sc->sc_sme);
		sc->sc_sme = NULL;
		return;
	}

	return;
}

static void
envmon_epic_attach(struct envmon_softc *sc, int node)
{
	const char *vers;
	uint16_t fan_mask;
	int i, j;

	vers = prom_getpropstring(node, "version");
	aprint_normal(": epic (ver. %s)\n", vers);

	/* Fans */
	if (!strcmp(machine_model, "SUNW,Sun-Fire-V245")) {
		sc->sc_machine_type = ENVMON_MACHINE_V245;
		sc->sc_num_fans = 6;
		fan_mask = 0x003f;	/* 6 fans */
	} else {	/* V215 */
		sc->sc_machine_type = ENVMON_MACHINE_V215;
		sc->sc_num_fans = 12;
		fan_mask = 0x0fff;	/* 12 fans */
	}

	sc->sc_sme->sme_refresh = envmon_epic_refresh;
	for (i = 0; i < ENVMON_EPC_MAX_FANS; i++) {
		if (!(fan_mask & (1 << i)))
			continue;

		strlcpy(sc->sc_sensor[i].desc, envmon_epic_fans_table[i].desc,
		    sizeof(sc->sc_sensor[i].desc));
		sc->sc_sensor[i].units = ENVSYS_SFANRPM;
		sc->sc_sensor[i].state = ENVSYS_SINVALID;
		sc->sc_sensor[i].flags = ENVSYS_FMONCRITICAL;
		sc->sc_fan_reg[i] = envmon_epic_fans_table[i].reg;
		if (sysmon_envsys_sensor_attach(sc->sc_sme,
		    &sc->sc_sensor[i])) {
			sysmon_envsys_destroy(sc->sc_sme);
			sc->sc_sme = NULL;
			aprint_error_dev(sc->sc_dev,
			    "unable to attach sensor to sysmon\n");
			goto leds;
		}
	}

	/* Indicators */
	for (j = 0; j < ENVMON_EPC_MAX_INDS; j++) {
		i = j + sc->sc_num_fans;
		strlcpy(sc->sc_sensor[i].desc, envmon_epic_inds_table[j].desc,
		    sizeof(sc->sc_sensor[i].desc));
		sc->sc_sensor[i].units = ENVSYS_INDICATOR;
		sc->sc_sensor[i].state = ENVSYS_SINVALID;
		sc->sc_ind[j].reg = envmon_epic_inds_table[j].reg;
		sc->sc_ind[j].mask = envmon_epic_inds_table[j].mask;
		sc->sc_ind[j].set = envmon_epic_inds_table[j].set;
		if (sysmon_envsys_sensor_attach(sc->sc_sme,
		    &sc->sc_sensor[i])) {
			sysmon_envsys_destroy(sc->sc_sme);
			sc->sc_sme = NULL;
			aprint_error_dev(sc->sc_dev,
			    "unable to attach sensor to sysmon\n");
			goto leds;
		}
	}
	
	if (sysmon_envsys_register(sc->sc_sme)) {
		aprint_error_dev(sc->sc_dev,
		    "unable to register with sysmon\n");
		sysmon_envsys_destroy(sc->sc_sme);
		sc->sc_sme = NULL;
	}

leds:
	/* LED's */
	for (i = 0; i < ENVMON_EPC_MAX_LEDS; i++) {
		sc->sc_led[i].cookie = sc;
		sc->sc_led[i].reg = envmon_epic_led_table[i].reg;
		sc->sc_led[i].v_on = envmon_epic_led_table[i].v_on;
		led_attach(envmon_epic_led_table[i].desc,
		    &sc->sc_led[i], envmon_epic_get_led, envmon_epic_set_led);
	}
	
	return;
}

void
envmon_pic_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
	struct envmon_softc *sc = sme->sme_cookie;
	uint8_t val;
	
	envmon_write(sc, ENVMON_ADDR, ENVMON_PIC_TEMP_FP);
	val = envmon_read(sc, ENVMON_DATA);
	edata->value_cur = ENVMON_PIC_TEMP_BASE + val * 1000000;
	edata->state = ENVSYS_SVALID;
}

void
envmon_epic_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
	struct envmon_softc *sc = sme->sme_cookie;
	if (edata->sensor < sc->sc_num_fans)
		envmon_epic_refresh_fan(sc, edata);
	else
		envmon_epic_refresh_ind(sc, edata);
	return;
}

void
envmon_epic_refresh_fan(struct envmon_softc *sc, envsys_data_t *edata)
{
	uint8_t val;
	int div, crit;
	
	switch (sc->sc_machine_type) {
		case ENVMON_MACHINE_V215:
			div = ENVMON_EPC_DIV_V215;
			crit = ENVMON_EPC_CRIT_SPEED_V215;
			break;
		case ENVMON_MACHINE_V245:
		default:
			div = ENVMON_EPC_DIV_V245;
			crit = ENVMON_EPC_CRIT_SPEED_V245;
			break;
	}

	envmon_write(sc, ENVMON_ADDR, sc->sc_fan_reg[edata->sensor]);
	val = envmon_read(sc, ENVMON_DATA);
	if (val == 0xff || val == 0x0)
		edata->value_cur = 0;
	else {
		edata->value_cur = ENVMON_EPC_VAL_TO_SPEED(div, val);
	}
	if (edata->value_cur < crit)
		edata->state = ENVSYS_SCRITICAL;
	else
		edata->state = ENVSYS_SVALID;
}

void
envmon_epic_refresh_ind(struct envmon_softc *sc, envsys_data_t *edata)
{
	int ind = edata->sensor - sc->sc_num_fans;
	uint8_t val;

	envmon_write(sc, ENVMON_ADDR, sc->sc_ind[ind].reg);
	val = envmon_read(sc, ENVMON_DATA);
	
	if ((val & sc->sc_ind[ind].mask) == sc->sc_ind[ind].set)
		edata->value_cur = TRUE;
	else
		edata->value_cur = FALSE;
	edata->state = ENVSYS_SVALID;
}

int
envmon_epic_get_led(void *cookie)
{
	struct envmon_sc_led *l = cookie;
	struct envmon_softc *sc = l->cookie;
	uint8_t val;
	
	envmon_write(sc, ENVMON_ADDR, l->reg);
	val = envmon_read(sc, ENVMON_DATA);
	return ((val & l->v_on) == l->v_on);
}

void
envmon_epic_set_led(void *cookie, int val)
{
	struct envmon_sc_led *l = cookie;
	struct envmon_softc *sc = l->cookie;
	uint8_t oldval, newval;
	
	envmon_write(sc, ENVMON_ADDR, l->reg);
	oldval = envmon_read(sc, ENVMON_DATA);
	newval = oldval & ~(l->v_on);
	newval |= val ? l->v_on : 0x0;
	if (newval != oldval) {
		envmon_write(sc, ENVMON_EPC_LED_MASK, l->v_on);
		envmon_write(sc, ENVMON_ADDR, l->reg);
		delay(10000);
		envmon_write(sc, ENVMON_DATA, newval);
	}
}
