Module Name:    src
Committed By:   jdc
Date:           Sat Oct 24 15:16:39 UTC 2020

Modified Files:
        src/sys/arch/sparc64/dev: pcf8591_envctrl.c

Log Message:
Add support for automatically changing the CPU fan speed on the E250 in a
similar way to the SB1000/SB2000.
The fan control information was determined by experiment, as it's only
partially available in OFW.
Hardcode the missing information for E250 fan control into the driver
(it should be possible to support the E450 in future too).


To generate a diff of this commit:
cvs rdiff -u -r1.9 -r1.10 src/sys/arch/sparc64/dev/pcf8591_envctrl.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/sparc64/dev/pcf8591_envctrl.c
diff -u src/sys/arch/sparc64/dev/pcf8591_envctrl.c:1.9 src/sys/arch/sparc64/dev/pcf8591_envctrl.c:1.10
--- src/sys/arch/sparc64/dev/pcf8591_envctrl.c:1.9	Tue Jun 26 06:03:57 2018
+++ src/sys/arch/sparc64/dev/pcf8591_envctrl.c	Sat Oct 24 15:16:39 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: pcf8591_envctrl.c,v 1.9 2018/06/26 06:03:57 thorpej Exp $	*/
+/*	$NetBSD: pcf8591_envctrl.c,v 1.10 2020/10/24 15:16:39 jdc Exp $	*/
 /*	$OpenBSD: pcf8591_envctrl.c,v 1.6 2007/10/25 21:17:20 kettenis Exp $ */
 
 /*
@@ -19,17 +19,25 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: pcf8591_envctrl.c,v 1.9 2018/06/26 06:03:57 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: pcf8591_envctrl.c,v 1.10 2020/10/24 15:16:39 jdc Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/kernel.h>
 #include <sys/device.h>
 
 #include <dev/sysmon/sysmonvar.h>
+#include <dev/sysmon/sysmon_taskq.h>
+
+#include <machine/autoconf.h>
 
 #include <dev/ofw/openfirm.h>
 #include <dev/i2c/i2cvar.h>
 
+/* Translation tables contain 254 entries */
+#define XLATE_SIZE		256
+#define XLATE_MAX		(XLATE_SIZE - 2)
+
 #define PCF8591_CHANNELS	4
 
 #define PCF8591_CTRL_CH0	0x00
@@ -39,35 +47,48 @@ __KERNEL_RCSID(0, "$NetBSD: pcf8591_envc
 #define PCF8591_CTRL_AUTOINC	0x04
 #define PCF8591_CTRL_OSCILLATOR	0x40
 
+#define PCF8591_TEMP_SENS	0x00
+#define PCF8591_CPU_FAN_CTRL	0x01
+#define PCF8591_PS_FAN_CTRL	0x02
+
 struct ecadc_channel {
 	u_int		chan_num;
+	u_int		chan_type;
 	envsys_data_t	chan_sensor;
 	u_char		*chan_xlate;
 	int64_t		chan_factor;
 	int64_t		chan_min;
 	int64_t		chan_warn;
 	int64_t		chan_crit;
+	u_int8_t	chan_speed;
 };
 
 struct ecadc_softc {
 	device_t		sc_dev;
 	i2c_tag_t		sc_tag;
 	i2c_addr_t		sc_addr;
-	u_char			sc_ps_xlate[256];
-	u_char			sc_cpu_xlate[256];
+	u_char			sc_ps_xlate[XLATE_SIZE];
+	u_char			sc_cpu_xlate[XLATE_SIZE];
+	u_char			sc_cpu_fan_spd[XLATE_SIZE];
 	u_int			sc_nchan;
 	struct ecadc_channel	sc_channels[PCF8591_CHANNELS];
 	struct sysmon_envsys	*sc_sme;
+	int			sc_hastimer;
+	callout_t		sc_timer;
 };
 
 static int	ecadc_match(device_t, cfdata_t, void *);
 static void	ecadc_attach(device_t, device_t, void *);
+static int	ecadc_detach(device_t, int);
 static void	ecadc_refresh(struct sysmon_envsys *, envsys_data_t *);
 static void	ecadc_get_limits(struct sysmon_envsys *, envsys_data_t *,
-			sysmon_envsys_lim_t *, uint32_t *);
-
-CFATTACH_DECL_NEW(ecadc, sizeof(struct ecadc_softc),
-	ecadc_match, ecadc_attach, NULL, NULL);
+			sysmon_envsys_lim_t *, u_int32_t *);
+static void	ecadc_timeout(void *);
+static void	ecadc_fan_adjust(void *);
+
+CFATTACH_DECL3_NEW(ecadc, sizeof(struct ecadc_softc),
+	ecadc_match, ecadc_attach, ecadc_detach, NULL, NULL, NULL,
+	DVF_DETACH_SHUTDOWN);
 
 static const struct device_compatible_entry compat_data[] = {
 	{ "ecadc",		0 },
@@ -102,6 +123,9 @@ ecadc_attach(device_t parent, device_t s
 	u_int i;
 
 	sc->sc_dev = self;
+	sc->sc_nchan = 0;
+	sc->sc_hastimer = 0;
+
 	if ((len = OF_getprop(node, "thermisters", term,
 	    sizeof(term))) < 0) {
 		aprint_error(": couldn't find \"thermisters\" property\n");
@@ -109,15 +133,14 @@ ecadc_attach(device_t parent, device_t s
 	}
 
 	if (OF_getprop(node, "cpu-temp-factors", &sc->sc_cpu_xlate[2],
-	    sizeof(sc->sc_cpu_xlate) - 2) < 0) {
+	    XLATE_MAX) < 0) {
 		aprint_error(": couldn't find \"cpu-temp-factors\" property\n");
 		return;
 	}
 	sc->sc_cpu_xlate[0] = sc->sc_cpu_xlate[1] = sc->sc_cpu_xlate[2];
 
 	/* Only the Sun Enterprise 450 has these. */
-	OF_getprop(node, "ps-temp-factors", &sc->sc_ps_xlate[2],
-	    sizeof(sc->sc_ps_xlate) - 2);
+	OF_getprop(node, "ps-temp-factors", &sc->sc_ps_xlate[2], XLATE_MAX);
 	sc->sc_ps_xlate[0] = sc->sc_ps_xlate[1] = sc->sc_ps_xlate[2];
 
 	cp = term;
@@ -139,6 +162,7 @@ ecadc_attach(device_t parent, device_t s
 			num = den = 1;
 
 		sc->sc_channels[sc->sc_nchan].chan_num = chan;
+		sc->sc_channels[sc->sc_nchan].chan_type = PCF8591_TEMP_SENS;
 
 		sensor = &sc->sc_channels[sc->sc_nchan].chan_sensor;
 		sensor->units = ENVSYS_STEMP;
@@ -164,6 +188,27 @@ ecadc_attach(device_t parent, device_t s
 		sc->sc_nchan++;
 	}
 
+	/*
+	 * Fan speed changing information is missing from OFW
+	 * The E250 CPU fan is connected to the sensor at addr 0x4a, channel 1
+	 */
+	if (ia->ia_addr == 0x4a && !strcmp(machine_model, "SUNW,Ultra-250") &&
+	    OF_getprop(node, "cpu-fan-speeds", &sc->sc_cpu_fan_spd,
+	    XLATE_MAX) > 0) {
+		sc->sc_channels[sc->sc_nchan].chan_num = 1;
+		sc->sc_channels[sc->sc_nchan].chan_type = PCF8591_CPU_FAN_CTRL;
+		sc->sc_channels[sc->sc_nchan].chan_speed = 0;
+		sensor = &sc->sc_channels[sc->sc_nchan].chan_sensor;
+		sensor->units = ENVSYS_INTEGER;
+		sensor->flags = ENVSYS_FMONNOTSUPP;
+		sensor->state = ENVSYS_SINVALID;
+		strlcpy(sensor->desc, "CPUFAN", sizeof(sensor->desc));
+		sc->sc_channels[sc->sc_nchan].chan_xlate = sc->sc_cpu_fan_spd;
+		sc->sc_nchan++;
+
+		sc->sc_hastimer = 1;
+	}
+
 	sc->sc_tag = ia->ia_tag;
 	sc->sc_addr = ia->ia_addr;
 
@@ -198,9 +243,30 @@ ecadc_attach(device_t parent, device_t s
 		sysmon_envsys_destroy(sc->sc_sme);
 		return;
 	}
+	
+	if (sc->sc_hastimer) {
+		callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
+		callout_reset(&sc->sc_timer, hz*20, ecadc_timeout, sc);
+	}
 
 	aprint_naive(": Temp Sensors\n");
-	aprint_normal(": %s Temp Sensors\n", ia->ia_name);
+	aprint_normal(": %s Temp Sensors (%d channels)\n", ia->ia_name,
+	    sc->sc_nchan);
+}
+
+static int
+ecadc_detach(device_t self, int flags)
+{
+	struct ecadc_softc *sc = device_private(self);
+	if (sc->sc_hastimer) {
+		callout_halt(&sc->sc_timer, NULL);
+		callout_destroy(&sc->sc_timer);
+	}
+
+	if (sc->sc_sme != NULL)
+		sysmon_envsys_destroy(sc->sc_sme);
+
+	return 0;
 }
 
 static void
@@ -218,7 +284,10 @@ ecadc_refresh(struct sysmon_envsys *sme,
 		iic_release_bus(sc->sc_tag, 0);
 		return;
 	}
-	/* NB: first byte out is stale, so read num_channels + 1 */
+	/*
+	 * Each data byte that we read is the result of the previous request,
+	 * so read num_channels + 1 and update envsys values from chan + 1.
+	 */
 	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
 	    NULL, 0, data, PCF8591_CHANNELS + 1, 0)) {
 		iic_release_bus(sc->sc_tag, 0);
@@ -226,26 +295,38 @@ ecadc_refresh(struct sysmon_envsys *sme,
 	}
 	iic_release_bus(sc->sc_tag, 0);
 
-	/* We only support temperature channels. */
+	/* Temperature with/without translation or relative (ADC value) */
 	for (i = 0; i < sc->sc_nchan; i++) {
 		struct ecadc_channel *chp = &sc->sc_channels[i];
 
-		if (chp->chan_xlate)
-			chp->chan_sensor.value_cur = 273150000 + 1000000 *
-			    chp->chan_xlate[data[1 + chp->chan_num]];
-		else
-			chp->chan_sensor.value_cur = 273150000 +
-			    chp->chan_factor * data[1 + chp->chan_num];
+		if (chp->chan_type == PCF8591_TEMP_SENS) {
+
+			/* Encode the raw value to use for the fan control */
+			if (chp->chan_xlate) {
+				int32_t temp;
+
+				temp = 273150000 + 1000000 *
+				    chp->chan_xlate[data[1 + chp->chan_num]];
+				temp &= ~0xff;
+				temp += data[1 + chp->chan_num];
+				chp->chan_sensor.value_cur = temp;
+			} else
+				chp->chan_sensor.value_cur = 273150000 +
+				    chp->chan_factor * data[1 + chp->chan_num];
+			chp->chan_sensor.flags |= ENVSYS_FMONLIMITS;
+		}
+		if (chp->chan_type == PCF8591_CPU_FAN_CTRL ||
+		    chp->chan_type == PCF8591_PS_FAN_CTRL)
+			chp->chan_sensor.value_cur = data[1 + chp->chan_num];
 
 		chp->chan_sensor.state = ENVSYS_SVALID;
 		chp->chan_sensor.flags &= ~ENVSYS_FNEED_REFRESH;
-		chp->chan_sensor.flags |= ENVSYS_FMONLIMITS;
 	}
 }
 
 static void
 ecadc_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
-		sysmon_envsys_lim_t *limits, uint32_t *props)
+		sysmon_envsys_lim_t *limits, u_int32_t *props)
 {
 	struct ecadc_softc *sc = sme->sme_cookie;
 	int i;
@@ -253,10 +334,81 @@ ecadc_get_limits(struct sysmon_envsys *s
 	for (i = 0; i < sc->sc_nchan; i++) {
 		if (edata != &sc->sc_channels[i].chan_sensor)
 			continue;
-		*props |= PROP_WARNMIN|PROP_WARNMAX|PROP_CRITMAX;
-		limits->sel_warnmin = sc->sc_channels[i].chan_min;
-		limits->sel_warnmax = sc->sc_channels[i].chan_warn;
-		limits->sel_critmax = sc->sc_channels[i].chan_crit;
+		if (sc->sc_channels[i].chan_type == PCF8591_TEMP_SENS) {
+			*props |= PROP_WARNMIN|PROP_WARNMAX|PROP_CRITMAX;
+			limits->sel_warnmin = sc->sc_channels[i].chan_min;
+			limits->sel_warnmax = sc->sc_channels[i].chan_warn;
+			limits->sel_critmax = sc->sc_channels[i].chan_crit;
+		}
 		return;
 	}
 }
+
+static void
+ecadc_timeout(void *v)
+{
+	struct ecadc_softc *sc = v;
+
+	sysmon_task_queue_sched(0, ecadc_fan_adjust, sc);
+	callout_reset(&sc->sc_timer, hz*60, ecadc_timeout, sc);
+}
+
+static bool
+is_cpu_temp(const envsys_data_t *edata)
+{
+	if (edata->units != ENVSYS_STEMP)
+		return false;
+	return strncmp(edata->desc, "CPU", 3) == 0;
+}
+
+static int
+ecadc_set_fan_speed(struct ecadc_softc *sc, u_int8_t chan, u_int8_t val)
+{
+	u_int8_t ctrl = PCF8591_CTRL_AUTOINC | PCF8591_CTRL_OSCILLATOR;
+	int ret;
+
+	ctrl |= chan;
+	iic_acquire_bus(sc->sc_tag, 0);
+	ret = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+	    &ctrl, 1, &val, 1, 0);
+	if (ret)
+		aprint_error_dev(sc->sc_dev,
+		    "error changing fan speed (ch %d)\n", chan);
+	else
+		aprint_debug_dev(sc->sc_dev,
+		    "changed fan speed (ch %d) to 0x%x\n", chan, val);
+	iic_release_bus(sc->sc_tag, 0);
+	return ret;
+}
+
+static void
+ecadc_fan_adjust(void *v)
+{
+	struct ecadc_softc *sc = v;
+	int i;
+	u_int8_t temp, speed;
+
+	for (i = 0; i < sc->sc_nchan; i++) {
+		struct ecadc_channel *chp = &sc->sc_channels[i];
+
+		if (chp->chan_type == PCF8591_CPU_FAN_CTRL) {
+			/* Extract the raw value from the max CPU temp */
+			temp = sysmon_envsys_get_max_value(is_cpu_temp, true)
+				& 0xff;
+			if (!temp) {
+				aprint_error_dev(sc->sc_dev,
+				    "skipping temp adjustment"
+				    " - no sensor values\n");
+				return;
+			}
+			if (temp > XLATE_MAX)
+				temp = XLATE_MAX;
+			speed = chp->chan_xlate[temp];
+			if (speed != chp->chan_speed) {
+				if (!ecadc_set_fan_speed(sc, chp->chan_num,
+				    speed))
+					chp->chan_speed = speed;
+			}
+		}
+	}
+}

Reply via email to