Index: src/sys/arch/sparc64/dev/tda.c
===================================================================
RCS file: /cvsroot/src/sys/arch/sparc64/dev/tda.c,v
retrieving revision 1.14
diff -u -r1.14 tda.c
--- src/sys/arch/sparc64/dev/tda.c	31 Oct 2020 13:17:34 -0000	1.14
+++ src/sys/arch/sparc64/dev/tda.c	7 Sep 2026 08:04:36 -0000
@@ -33,10 +33,24 @@
 
 #include <dev/i2c/i2cvar.h>
 
-/* fan control registers */
-#define TDA_SYSFAN_REG		0xf0
-#define TDA_CPUFAN_REG		0xf2
-#define TDA_PSFAN_REG		0xf4
+#ifdef TDA_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF if (0) printf
+#endif
+
+/* fan control registers: instruction bits 4-7, subaddress (port) bits 0-3 */
+#define TDA_REG_AUTOINC		0x00
+#define TDA_REG_NO_INC		0xf0
+
+/* SB1000/2000 */
+#define TDA_B_SYSFAN_REG	0x0
+#define TDA_B_CPUFAN_REG	0x2
+#define TDA_B_PSFAN_REG		0x4
+/* E450 */
+#define TDA_E_CPUFAN_REG	0x0
+#define TDA_E_PSUFAN_REG	0x1
+#define TDA_E_AFBFAN_REG	0x2
 
 #define TDA_FANSPEED_MIN        0x0c
 #define TDA_FANSPEED_MAX        0x3f
@@ -44,41 +58,59 @@
 #define TDA_PSFAN_ON            0x1f
 #define TDA_PSFAN_OFF           0x00
 
-/* Internal and External temperature sensor numbers */
-#define SENSOR_TEMP_EXT		0
-#define SENSOR_TEMP_INT		1
-
+#define TDA_NUM_REG		3	/* There are 8, but we use max 3 */
 /* Fan sensor numbers */
 #define SENSOR_FAN_CPU		0
 #define SENSOR_FAN_SYS		1
+#define SENSOR_FAN_AFB		2
 
 #define DEGC_TO_mK(c)		(((c) * 1000000) + 273150000)
+#define mK_TO_DEGC(k)		((k - 273150000) / 1000000)
 
 #define CPU_TEMP_MAX		DEGC_TO_mK(67)
 #define CPU_TEMP_MIN		DEGC_TO_mK(57)
 #define SYS_TEMP_MAX		DEGC_TO_mK(30)
 #define SYS_TEMP_MIN		DEGC_TO_mK(20)
 
+#define XLATE_SIZE		0x6f	/* Translation tables: 112 entries */
+
+/* E450 temperature difference constants */
+#define CPU_TEMP_OFFSET		-20
+#define PS_TEMP_OFFSET		-30
+
+struct tda_channel {
+	u_int8_t		chan_fan_type;
+	u_int8_t		chan_fan_speed;
+	u_int8_t		chan_reg_num;
+	u_char			*chan_xlate;
+	int			chan_xlate_off;
+	u_int64_t		chan_temp_max;
+	u_int64_t		chan_temp_min;
+	envsys_data_t		chan_sensor;
+};
+
 struct tda_softc {
 	device_t		sc_dev;
 	i2c_tag_t		sc_tag;
 	i2c_addr_t		sc_addr;
 
-	u_int16_t		sc_cfan_speed;	/* current CPU fan speed */
-	u_int16_t		sc_sfan_speed;	/* current SYS fan speed */
+	u_char			sc_cpu_fan_spd[XLATE_SIZE];
+	u_char			sc_ps_fan_spd[XLATE_SIZE];
+
+	u_int8_t		sc_num_fans;
+	struct tda_channel	sc_channels[TDA_NUM_REG];
 
 	struct sysmon_envsys	*sc_sme;
-	envsys_data_t		sc_sensor[2];
 
 	callout_t		sc_timer;
 };
 
 int	tda_match(device_t, cfdata_t, void *);
 void	tda_attach(device_t, device_t, void *);
-static int	tda_detach(device_t, int);
+int	tda_detach(device_t, int);
 void	tda_refresh(struct sysmon_envsys *, envsys_data_t *);
 
-void	tda_setspeed(struct tda_softc *);
+static int	tda_setspeed(struct tda_softc *);
 static void	tda_adjust(void *);
 static void	tda_timeout(void *);
 
@@ -92,8 +124,9 @@
 {
 	struct i2c_attach_args *ia = aux;
 
-	/* Only attach on the Sun Blade 1000/2000. */
-	if (strcmp(machine_model, "SUNW,Sun-Blade-1000") != 0)
+	/* Only attach on the Sun Blade 1000/2000 and E450. */
+	if (strcmp(machine_model, "SUNW,Sun-Blade-1000") != 0 &&
+	    strcmp(machine_model, "SUNW,Ultra-4") != 0)
 		return (0);
 
 	/*
@@ -112,7 +145,7 @@
 {
 	struct tda_softc *sc = device_private(self);
 	struct i2c_attach_args *ia = aux;
-	int rc;
+	int i, node, rc;
 
 	sc->sc_dev = self;
 	sc->sc_tag = ia->ia_tag;
@@ -121,43 +154,104 @@
 	aprint_normal(": %s\n", ia->ia_name);
 	aprint_naive(": Environment sensor\n");
 
+	/* Set the fan parameters according to the machine model */
+	if (!strcmp(machine_model, "SUNW,Sun-Blade-1000")) {
+		sc->sc_num_fans = 2;
+		sc->sc_channels[0].chan_fan_type = SENSOR_FAN_CPU;
+		sc->sc_channels[0].chan_reg_num = TDA_B_CPUFAN_REG;
+		sc->sc_channels[0].chan_xlate = NULL;
+		sc->sc_channels[0].chan_temp_max = CPU_TEMP_MAX;
+		sc->sc_channels[0].chan_temp_min = CPU_TEMP_MIN;
+		strlcpy(sc->sc_channels[0].chan_sensor.desc,
+		    "fan.cpu",sizeof("fan.cpu"));
+		sc->sc_channels[1].chan_fan_type = SENSOR_FAN_SYS;
+		sc->sc_channels[1].chan_reg_num = TDA_B_SYSFAN_REG;
+		sc->sc_channels[1].chan_xlate = NULL;
+		sc->sc_channels[1].chan_temp_max = SYS_TEMP_MAX;
+		sc->sc_channels[1].chan_temp_min = SYS_TEMP_MIN;
+		strlcpy(sc->sc_channels[1].chan_sensor.desc,
+		    "fan.sys",sizeof("fan.sys"));
+	} else if (!strcmp(machine_model, "SUNW,Ultra-4")) {
+		node = devhandle_to_of(device_handle(self));
+		sc->sc_num_fans = 3;
+		sc->sc_channels[0].chan_fan_type = SENSOR_FAN_CPU;
+		sc->sc_channels[0].chan_reg_num = TDA_E_CPUFAN_REG;
+		if (OF_getprop(node, "cpu-fan-speeds", &sc->sc_cpu_fan_spd,
+		    XLATE_SIZE) > 0) {
+			sc->sc_channels[0].chan_xlate = sc->sc_cpu_fan_spd;
+			sc->sc_channels[0].chan_xlate_off = CPU_TEMP_OFFSET;
+		} else {
+			aprint_error_dev(self,
+			    "couldn't find \"cpu-fan-speeds\" property\n");
+			return;
+		}
+		strlcpy(sc->sc_channels[0].chan_sensor.desc,
+		    "CPUFAN",sizeof("CPUFAN"));
+		sc->sc_channels[1].chan_fan_type = SENSOR_FAN_SYS;
+		sc->sc_channels[1].chan_reg_num = TDA_E_PSUFAN_REG;
+		if (OF_getprop(node, "ps-fan-speeds", &sc->sc_ps_fan_spd,
+		    XLATE_SIZE) > 0) {
+			sc->sc_channels[1].chan_xlate = sc->sc_ps_fan_spd;
+			sc->sc_channels[1].chan_xlate_off = PS_TEMP_OFFSET;
+		} else {
+			aprint_error_dev(self,
+			    "couldn't find \"ps-fan-speeds\" property\n");
+			return;
+		}
+		strlcpy(sc->sc_channels[1].chan_sensor.desc,
+		    "PSUFAN",sizeof("PSUFAN"));
+		sc->sc_channels[2].chan_fan_type = SENSOR_FAN_AFB;
+		sc->sc_channels[2].chan_reg_num = TDA_E_AFBFAN_REG;
+		sc->sc_channels[2].chan_xlate = NULL;
+		/* AFB fan always runs at full speed */
+		sc->sc_channels[2].chan_temp_max = 0;
+		sc->sc_channels[2].chan_temp_min = 0;
+		strlcpy(sc->sc_channels[2].chan_sensor.desc,
+		    "AFBFAN",sizeof("AFBFAN"));
+	} else {
+		aprint_error_dev(self,
+		    "unsupported: %s\n", machine_model);
+		return;
+	}
 	/*
-	 * Set the fans to maximum speed and save the power levels;
+	 * Set the fans to medium speed and save the power levels;
 	 * the controller is write-only.
 	 */
-	sc->sc_cfan_speed = sc->sc_sfan_speed = (TDA_FANSPEED_MAX+TDA_FANSPEED_MIN)/2;
+	for (i = 0; i < sc->sc_num_fans; i++)
+		switch (sc->sc_channels[i].chan_fan_type) {
+		case SENSOR_FAN_CPU:
+			/* Fallthrough */
+		case SENSOR_FAN_SYS:
+			sc->sc_channels[i].chan_fan_speed =
+			    (TDA_FANSPEED_MAX+TDA_FANSPEED_MIN)/2;
+			break;
+		default:
+			sc->sc_channels[i].chan_fan_speed = TDA_FANSPEED_MAX;
+			break;
+		}
 	tda_setspeed(sc);
-	
+
 	callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
 	callout_reset(&sc->sc_timer, hz*20, tda_timeout, sc);
 
 	/* Initialise sensor data */
-	sc->sc_sensor[SENSOR_FAN_CPU].state = ENVSYS_SINVALID;
-	sc->sc_sensor[SENSOR_FAN_CPU].units = ENVSYS_INTEGER;
-	sc->sc_sensor[SENSOR_FAN_CPU].flags = ENVSYS_FMONNOTSUPP;
-	strlcpy(sc->sc_sensor[SENSOR_FAN_CPU].desc,
-	    "fan.cpu",sizeof("fan.cpu"));
-	sc->sc_sensor[SENSOR_FAN_SYS].state = ENVSYS_SINVALID;
-	sc->sc_sensor[SENSOR_FAN_SYS].units = ENVSYS_INTEGER;
-	sc->sc_sensor[SENSOR_FAN_SYS].flags = ENVSYS_FMONNOTSUPP;
-	strlcpy(sc->sc_sensor[SENSOR_FAN_SYS].desc,
-	    "fan.sys",sizeof("fan.sys"));
-	sc->sc_sme = sysmon_envsys_create();
-	rc = sysmon_envsys_sensor_attach(
-	    sc->sc_sme, &sc->sc_sensor[SENSOR_FAN_CPU]);
-	if (rc) {
-		sysmon_envsys_destroy(sc->sc_sme);
-		aprint_error_dev(self,
-		    "unable to attach cpu fan at sysmon, error %d\n", rc);
-		return;
+	for (i = 0; i < sc->sc_num_fans; i++) {
+		sc->sc_channels[i].chan_sensor.state = ENVSYS_SINVALID;
+		sc->sc_channels[i].chan_sensor.units = ENVSYS_INTEGER;
+		sc->sc_channels[i].chan_sensor.flags = ENVSYS_FMONNOTSUPP;
 	}
-	rc = sysmon_envsys_sensor_attach(
-	    sc->sc_sme, &sc->sc_sensor[SENSOR_FAN_SYS]);
-	if (rc) {
-		sysmon_envsys_destroy(sc->sc_sme);
-		aprint_error_dev(self,
-		    "unable to attach sys fan at sysmon, error %d\n", rc);
-		return;
+	sc->sc_sme = sysmon_envsys_create();
+	for (i = 0; i < sc->sc_num_fans; i++) {
+		rc = sysmon_envsys_sensor_attach(
+		    sc->sc_sme, &sc->sc_channels[i].chan_sensor);
+		if (rc) {
+			sysmon_envsys_destroy(sc->sc_sme);
+			sc->sc_sme = NULL;
+			aprint_error_dev(self,
+			    "unable to attach %s fan at sysmon, error %d\n",
+			    sc->sc_channels[i].chan_sensor.desc, rc);
+			return;
+		}
 	}
         sc->sc_sme->sme_name = device_xname(self);
         sc->sc_sme->sme_cookie = sc;
@@ -167,6 +261,7 @@
 		aprint_error_dev(self,
 		    "unable to register with sysmon, error %d\n", rc);
 		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
 		return;
 	}
 }
@@ -175,15 +270,26 @@
 tda_detach(device_t self, int flags)
 {
 	struct tda_softc *sc = device_private(self);
+	int i;
 
-	if (sc->sc_sme != NULL)
+	if (sc->sc_sme != NULL) {
 		sysmon_envsys_unregister(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
 
 	callout_halt(&sc->sc_timer, NULL);
 	callout_destroy(&sc->sc_timer);
 
-	sc->sc_cfan_speed = sc->sc_sfan_speed = TDA_FANSPEED_MAX;
-	tda_setspeed(sc);
+	for (i = 0; i < sc->sc_num_fans; i++)
+		sc->sc_channels[i].chan_fan_speed = TDA_FANSPEED_MAX;
+
+	for (i = 0; i < 5; i++) {	/* Loop in case the bus is busy */
+		if (!tda_setspeed(sc))
+			return 0;
+		delay(10000);
+	}
+
+	aprint_error_dev(sc->sc_dev, "cannot set fan speeds\n");
 	return 0;
 }
 
@@ -196,108 +302,162 @@
 	callout_reset(&sc->sc_timer, hz*60, tda_timeout, sc);
 }
 
-void
+static int
 tda_setspeed(struct tda_softc *sc)
 {
 	u_int8_t cmd[2];
+	int i, rc;
 
-	if (sc->sc_cfan_speed < TDA_FANSPEED_MIN)
-		sc->sc_cfan_speed = TDA_FANSPEED_MIN;
-	if (sc->sc_sfan_speed < TDA_FANSPEED_MIN)
-		sc->sc_sfan_speed = TDA_FANSPEED_MIN;
-	if (sc->sc_cfan_speed > TDA_FANSPEED_MAX)
-		sc->sc_cfan_speed = TDA_FANSPEED_MAX;
-	if (sc->sc_sfan_speed > TDA_FANSPEED_MAX)
-		sc->sc_sfan_speed = TDA_FANSPEED_MAX;
-
-	iic_acquire_bus(sc->sc_tag, 0);
-
-	cmd[0] = TDA_CPUFAN_REG;
-	cmd[1] = sc->sc_cfan_speed;
-	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
-	    sc->sc_addr, &cmd, sizeof(cmd), NULL, 0, 0)) {
-		aprint_error_dev(sc->sc_dev, "cannot write cpu-fan register\n");
+	for (i = 0; i < sc->sc_num_fans; i++) {
+		if (sc->sc_channels[i].chan_fan_speed < TDA_FANSPEED_MIN)
+			sc->sc_channels[i].chan_fan_speed = TDA_FANSPEED_MIN;
+		if (sc->sc_channels[i].chan_fan_speed > TDA_FANSPEED_MAX)
+			sc->sc_channels[i].chan_fan_speed = TDA_FANSPEED_MAX;
+
+		rc = iic_acquire_bus(sc->sc_tag, 0);
+		if (rc) {
+			aprint_error_dev(sc->sc_dev,
+			    "cannot acquire i2c bus for %s\n",
+			    sc->sc_channels[i].chan_sensor.desc);
+			return rc;
+		}
+		cmd[0] = sc->sc_channels[i].chan_reg_num | TDA_REG_NO_INC;
+		cmd[1] = sc->sc_channels[i].chan_fan_speed;
+		rc = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_addr, &cmd, sizeof(cmd), NULL, 0, 0);
+		if (rc) {
+			aprint_error_dev(sc->sc_dev,
+			    "cannot write register for %s\n",
+			    sc->sc_channels[i].chan_sensor.desc);
+			iic_release_bus(sc->sc_tag, 0);
+			return rc;
+		}
 		iic_release_bus(sc->sc_tag, 0);
-		return;
-        }
 
-	cmd[0] = TDA_SYSFAN_REG;
-	cmd[1] = sc->sc_sfan_speed;
-	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
-	    sc->sc_addr, &cmd, sizeof(cmd), NULL, 0, 0)) {
-		aprint_error_dev(sc->sc_dev, "cannot write system-fan register\n");
-		iic_release_bus(sc->sc_tag, 0);
-		return;
+		DPRINTF("%s: changed %s fan speed to %d\n",
+		    device_xname(sc->sc_dev),
+		    sc->sc_channels[i].chan_sensor.desc,
+		    sc->sc_channels[i].chan_fan_speed);
         }
-
-	iic_release_bus(sc->sc_tag, 0);
-
-	aprint_debug_dev(sc->sc_dev, "changed fan speed to cpu=%d system=%d\n",
-		sc->sc_cfan_speed, sc->sc_sfan_speed);
+	return 0;
 }
 
 static bool
 is_cpu_sensor(const envsys_data_t *edata)
 {
+	int i;
+	char name[8];
+
 	if (edata->units != ENVSYS_STEMP)
 		return false;
-	return strcmp(edata->desc, "external") == 0;
+	if (!strcmp(machine_model, "SUNW,Sun-Blade-1000"))
+		return strcmp(edata->desc, "external") == 0;
+	if (!strcmp(machine_model, "SUNW,Ultra-4"))
+		for (i = 0; i < 4; i++) {
+			snprintf(name, 8, "CPU%d", i);
+			if (strcmp(edata->desc, name) == 0)
+				return true;
+		}
+	return false;
 }
 
 static bool
 is_system_sensor(const envsys_data_t *edata)
 {
+	int i;
+	char name[8];
+
 	if (edata->units != ENVSYS_STEMP)
 		return false;
-	return strcmp(edata->desc, "internal") == 0;
+	if (!strcmp(machine_model, "SUNW,Sun-Blade-1000"))
+		return strcmp(edata->desc, "internal") == 0;
+	if (!strcmp(machine_model, "SUNW,Ultra-4"))
+		for (i = 0; i < 4; i++) {
+			snprintf(name, 8, "PS%d", i);
+			if (strcmp(edata->desc, name) == 0)
+				return true;
+		}
+	return false;
 }
 
 static void
 tda_adjust(void *v)
 {
 	struct tda_softc *sc = v;
-	u_int64_t ctemp, stemp;
-	u_int16_t cspeed, sspeed;
-
-	/* Default to running the fans at maximum speed. */
-	sspeed = cspeed = TDA_FANSPEED_MAX;
-
-	/* fetch maximum current temperature */
-	ctemp = sysmon_envsys_get_max_value(is_cpu_sensor, true);
-	stemp = sysmon_envsys_get_max_value(is_system_sensor, true);
-
-	/* the predicates for selecting sensors must have gone wrong */
-	if (ctemp == 0 || stemp == 0) {
-		aprint_error_dev(sc->sc_dev, "skipping temp adjustment"
-			" - no sensor values\n");
-		return;
+	struct tda_channel *chp;
+	u_int64_t temp, temp_max, temp_min;
+	u_int8_t speed;
+	int i, idx, changed = 0;
+
+	for (i = 0; i < sc->sc_num_fans; i++) {
+		chp = &sc->sc_channels[i];
+		temp = 0;
+
+		/* Default to running the fans at maximum speed. */
+		speed = TDA_FANSPEED_MAX;
+
+		/* fetch maximum current temperature */
+		switch (chp->chan_fan_type) {
+		case SENSOR_FAN_CPU:
+			temp = sysmon_envsys_get_max_value(is_cpu_sensor,
+			    true);
+			break;
+		case SENSOR_FAN_SYS:
+			temp = sysmon_envsys_get_max_value(is_system_sensor,
+			    true);
+			break;
+		default:
+			temp = 1;	/* Any temperature > 0 */
+			break;
+		}
+
+		/* the predicates for selecting sensors must have gone wrong */
+		if (temp == 0) {
+			aprint_error_dev(sc->sc_dev, "skipping temp adjustment"
+			    " - no sensor values for %s\n",
+			    chp->chan_sensor.desc);
+			return;
+		}
+
+		DPRINTF("%s: current temperature for %s: %" PRIu64 " mK\n",
+		    device_xname(sc->sc_dev), chp->chan_sensor.desc, temp);
+
+		if (chp->chan_xlate == NULL) {
+			temp_max = chp->chan_temp_max;
+			temp_min = chp->chan_temp_min;
+			DPRINTF("%s: temperature range: %" PRIu64
+			    " - %" PRIu64 "\n", device_xname(sc->sc_dev),
+			    temp_min, temp_max);
+			if (temp < temp_min)
+				speed = TDA_FANSPEED_MIN;
+			else if (temp < temp_max)
+				speed = TDA_FANSPEED_MIN + (temp - temp_min) *
+				    (TDA_FANSPEED_MAX - TDA_FANSPEED_MIN) /
+				    (temp_max - temp_min);
+		} else {
+			/* Translation tables are in Celsius */
+			idx = mK_TO_DEGC(temp);
+			idx += chp->chan_xlate_off;
+			if (idx >= XLATE_SIZE)
+				idx = XLATE_SIZE - 1;
+			if (idx < 0)
+				idx = 0;
+			speed = chp->chan_xlate[idx];
+			DPRINTF("%s: temperature index (speed): %d (%d)\n",
+			    device_xname(sc->sc_dev), idx, speed);
+		}
+		if (speed != chp->chan_fan_speed) {
+			changed = 1;
+			DPRINTF("%s: fan speed change: %d > %d\n",
+			    device_xname(sc->sc_dev),
+			    chp->chan_fan_speed, speed);
+			chp->chan_fan_speed = speed;
+		}
 	}
 
-	aprint_debug_dev(sc->sc_dev, "current temperature: cpu %" PRIu64
-		" system %" PRIu64 "\n",
-		ctemp, stemp);
-
-	if (ctemp < CPU_TEMP_MIN)
-		cspeed = TDA_FANSPEED_MIN;
-	else if (ctemp < CPU_TEMP_MAX)
-		cspeed = TDA_FANSPEED_MIN +
-			(ctemp - CPU_TEMP_MIN) * 
-			(TDA_FANSPEED_MAX - TDA_FANSPEED_MIN) / 
-			(CPU_TEMP_MAX - CPU_TEMP_MIN);
-
-	if (stemp < SYS_TEMP_MIN)
-		sspeed = TDA_FANSPEED_MIN;
-	else if (stemp < SYS_TEMP_MAX)
-		sspeed = TDA_FANSPEED_MIN +
-			(stemp - SYS_TEMP_MIN) * 
-			(TDA_FANSPEED_MAX - TDA_FANSPEED_MIN) / 
-			(SYS_TEMP_MAX - SYS_TEMP_MIN);
-
-	if (sspeed == sc->sc_sfan_speed && cspeed == sc->sc_cfan_speed)
+	if (!changed)
 		return;
 
-	sc->sc_sfan_speed = sspeed;
-	sc->sc_cfan_speed = cspeed;
 	tda_setspeed(sc);
 }
 
@@ -305,12 +465,9 @@
 tda_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
 {
 	struct tda_softc *sc = sme->sme_cookie;
-	u_int16_t speed;
+	u_int8_t speed;
 
-	if (edata->sensor == SENSOR_FAN_CPU)
-		speed = sc->sc_cfan_speed;
-	else
-		speed = sc->sc_sfan_speed;
+	speed = sc->sc_channels[edata->sensor].chan_fan_speed;
 	if (!speed)
 		edata->state = ENVSYS_SINVALID;
 	else {
@@ -318,4 +475,3 @@
 		edata->state = ENVSYS_SVALID;
 	}
 }
-
