Philips I2C sensors

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Philips I2C sensors

Damien Miller
Hi,

Now that dlg's pcf_ebus and ofwiic_scan diff is in (thanks!), the two
drivers in the patch below are suddenly useful. One is for the Philips
PCF8591 ADC (only as a temperature sensor right now) and the other is
for the PCF8574 GPIO (as a bunch of inputs). These are ONLY for sparc64
(see below), do don't bother testing on any other platform.

You can try the patch below on -current (updated in the last 12 hours)
or the kernel at http://www2.mindrot.org/tmp/bsd.pcf

SHA1 (bsd.pcf) = d83eeae4c92275630dbc8239461a9667d3903e30

(obviously, you shouldn't be running random kernels that you download
from the net on systems with private data on them - including mine)

Either way, please test and report dmesg + hw.sensors output.

Both of these sensors are present on the Netra T1 105 and probably other
Sun boxes of similar vintage. I'm a little dubious as to whether the
pcfgpio works properly, as I don't have any way to twiddle the state of
its pins on my systems. If anyone has a box with fan sensors attached to
one of these, then I'd really appreciate if you could unplug a fan and
see if the indicator changes.

These chips are as dumb as can be imagined - they are impossible to
probe, so we require OFW to detect them and grok their output. For this
reason, the drivers are sparc64 only for now.

FYI, the output looks like this, the names come from openfirmware:

hw.sensors.0=pcfadc0, temp,cpu, temp, 31.00 degC / 87.80 degF
hw.sensors.1=pcfgpio0, vddcore,cpu, indicator, On
hw.sensors.2=pcfgpio0, i2c-power-fail, indicator, On
hw.sensors.3=pcfgpio1, ga0, indicator, On
hw.sensors.4=pcfgpio1, ga1, indicator, On
hw.sensors.5=pcfgpio1, ga2, indicator, On
hw.sensors.6=pcfgpio1, ga3, indicator, On
hw.sensors.7=pcfgpio1, ga4, indicator, On
hw.sensors.8=pcfgpio1, DEG#, indicator, On
hw.sensors.9=pcfgpio1, FAL#, indicator, On

-d

Index: arch/sparc64/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/conf/GENERIC,v
retrieving revision 1.140
diff -u -r1.140 GENERIC
--- arch/sparc64/conf/GENERIC 1 Feb 2006 11:03:33 -0000 1.140
+++ arch/sparc64/conf/GENERIC 9 Feb 2006 08:16:02 -0000
@@ -323,6 +323,8 @@
 lmtemp* at iic? # NS LM75/LM77 temperature sensor
 maxds* at iic? # Maxim DS1631
 maxtmp* at iic? # Maxim MAX6642/MAX6690
+pcfadc* at iic? # Philips PCF8591
+pcfgpio* at iic? # Philips PCF8574
 
 # Framebuffers
 agten* at sbus? # Fujitsu AG-10e framebuffer
Index: arch/sparc64/conf/files.sparc64
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/conf/files.sparc64,v
retrieving revision 1.64
diff -u -r1.64 files.sparc64
--- arch/sparc64/conf/files.sparc64 1 Feb 2006 11:03:33 -0000 1.64
+++ arch/sparc64/conf/files.sparc64 9 Feb 2006 08:16:02 -0000
@@ -262,3 +262,18 @@
 # Machine-independent USB drivers
 #
 include "dev/usb/files.usb"
+
+#
+# Machine-dependent I2C drivers
+#
+
+# PCF8591 ADC I2C Sensor (needs ofw, so sparc64 only for now)
+device pcfadc
+attach pcfadc at i2c
+file arch/sparc64/dev/pcf8591_ofw.c pcfadc
+
+# PCF8574 GPIO I2C Sensor (needs ofw, so sparc64 only for now)
+device pcfgpio
+attach pcfgpio at i2c
+file arch/sparc64/dev/pcf8574_ofw.c pcfgpio
+
Index: arch/sparc64/dev/pcf8574_ofw.c
===================================================================
RCS file: arch/sparc64/dev/pcf8574_ofw.c
diff -N arch/sparc64/dev/pcf8574_ofw.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ arch/sparc64/dev/pcf8574_ofw.c 9 Feb 2006 08:16:02 -0000
@@ -0,0 +1,178 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2006 Damien Miller <[hidden email]>
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/sensors.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/i2c/i2cvar.h>
+
+#define PCF8574_PINS 8
+
+struct pcfgpio_channel {
+ u_int chan_num;
+ struct sensor chan_sensor;
+};
+
+struct pcfgpio_softc {
+ struct device sc_dev;
+ i2c_tag_t sc_tag;
+ i2c_addr_t sc_addr;
+ u_int sc_nchan;
+ struct pcfgpio_channel sc_channels[PCF8574_PINS];
+};
+
+int pcfgpio_match(struct device *, void *, void *);
+void pcfgpio_attach(struct device *, struct device *, void *);
+void pcfgpio_refresh(void *);
+
+struct cfattach pcfgpio_ca = {
+ sizeof(struct pcfgpio_softc), pcfgpio_match, pcfgpio_attach
+};
+
+struct cfdriver pcfgpio_cd = {
+ NULL, "pcfgpio", DV_DULL
+};
+
+int
+pcfgpio_match(struct device *parent, void *match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+
+ if (strcmp(ia->ia_name, "i2cpcf,8574a") != 0)
+ return (0);
+
+ return (1);
+}
+
+void
+pcfgpio_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct pcfgpio_softc *sc = (struct pcfgpio_softc *)self;
+ u_char chanuse[PCF8574_PINS * 4], desc[PCF8574_PINS * 32], *cp;
+ u_int8_t junk;
+ struct i2c_attach_args *ia = aux;
+ int dlen, clen, node = *(int *)ia->ia_cookie;
+ u_int i;
+
+ if ((dlen = OF_getprop(node, "channels-description", desc,
+    sizeof(desc))) < 0) {
+ printf(": couldn't find \"channels-description\" property\n");
+ return;
+ }
+ if (dlen > sizeof(desc) || desc[dlen - 1] != '\0') {
+ printf(": bad \"channels-description\" property\n");
+ return;
+ }
+ if ((clen = OF_getprop(node, "channels-in-use", chanuse,
+    sizeof(chanuse))) < 0) {
+ printf(": couldn't find \"channels-in-use\" property\n");
+ return;
+ }
+ if ((clen % 4) != 0) {
+ printf(": invalid \"channels-in-use\" length %d\n", clen);
+ return;
+ }
+ sc->sc_nchan = clen / 4;
+ if (sc->sc_nchan > PCF8574_PINS) {
+ printf(": invalid number of channels (%d)\n", sc->sc_nchan);
+ return;
+ }
+
+ cp = desc;
+ for (i = 0; i < sc->sc_nchan; i++) {
+ struct pcfgpio_channel *chp = &sc->sc_channels[i];
+
+ bzero(&chp->chan_sensor, sizeof(chp->chan_sensor));
+ strlcpy(chp->chan_sensor.device, sc->sc_dev.dv_xname,
+    sizeof(chp->chan_sensor.device));
+
+ if (cp >= desc + dlen) {
+ printf(": invalid \"channels-description\"\n");
+ return;
+ }
+ strlcpy(chp->chan_sensor.desc, cp,
+    sizeof(chp->chan_sensor.desc));
+ cp += strlen(cp) + 1;
+
+ chp->chan_sensor.type = SENSOR_INDICATOR;
+
+ chp->chan_num = chanuse[i * 4];
+ if (chp->chan_num > PCF8574_PINS) {
+ printf(": invalid channel number\n");
+ return;
+ }
+ /* Only interested in input channels */
+ if (chanuse[(i * 4) + 1] != 0)
+ chp->chan_sensor.flags |= SENSOR_FINVALID;
+ }
+
+ sc->sc_tag = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+
+ iic_acquire_bus(sc->sc_tag, 0);
+
+ /* Try a read now, so we can fail if it doesn't work */
+ if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+    NULL, 0, &junk, 1, 0)) {
+ printf(": read failed\n");
+ iic_release_bus(sc->sc_tag, 0);
+ return;
+ }
+
+ iic_release_bus(sc->sc_tag, 0);
+
+ /* Initialize sensor data. */
+ for (i = 0; i < sc->sc_nchan; i++)
+ if (!(sc->sc_channels[i].chan_sensor.flags & SENSOR_FINVALID))
+ sensor_add(&sc->sc_channels[i].chan_sensor);
+
+ if (sensor_task_register(sc, pcfgpio_refresh, 5)) {
+ printf(": unable to register update task\n");
+ return;
+ }
+
+ printf("\n");
+}
+
+void
+pcfgpio_refresh(void *arg)
+{
+ struct pcfgpio_softc *sc = arg;
+ u_int i;
+ u_int8_t data;
+
+ iic_acquire_bus(sc->sc_tag, 0);
+ if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+    NULL, 0, &data, 1, 0)) {
+ iic_release_bus(sc->sc_tag, 0);
+ return;
+ }
+ iic_release_bus(sc->sc_tag, 0);
+
+ for (i = 0; i < sc->sc_nchan; i++) {
+ struct pcfgpio_channel *chp = &sc->sc_channels[i];
+
+ if ((chp->chan_sensor.flags & SENSOR_FINVALID) != 0)
+ continue;
+ chp->chan_sensor.value = (data >> i) & 1;
+ }
+}
+
Index: arch/sparc64/dev/pcf8591_ofw.c
===================================================================
RCS file: arch/sparc64/dev/pcf8591_ofw.c
diff -N arch/sparc64/dev/pcf8591_ofw.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ arch/sparc64/dev/pcf8591_ofw.c 9 Feb 2006 08:16:03 -0000
@@ -0,0 +1,213 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2006 Damien Miller <[hidden email]>
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/sensors.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/i2c/i2cvar.h>
+
+#define PCF8591_CHANNELS 4
+
+struct pcfadc_channel {
+ u_int chan_num;
+ struct sensor chan_sensor;
+};
+
+struct pcfadc_softc {
+ struct device sc_dev;
+ i2c_tag_t sc_tag;
+ i2c_addr_t sc_addr;
+ u_char sc_xlate[256];
+ u_int sc_nchan;
+ struct pcfadc_channel sc_channels[PCF8591_CHANNELS];
+};
+
+int pcfadc_match(struct device *, void *, void *);
+void pcfadc_attach(struct device *, struct device *, void *);
+void pcfadc_refresh(void *);
+
+struct cfattach pcfadc_ca = {
+ sizeof(struct pcfadc_softc), pcfadc_match, pcfadc_attach
+};
+
+struct cfdriver pcfadc_cd = {
+ NULL, "pcfadc", DV_DULL
+};
+
+int
+pcfadc_match(struct device *parent, void *match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+
+ if (strcmp(ia->ia_name, "i2cpcf,8591") != 0)
+ return (0);
+
+ return (1);
+}
+
+void
+pcfadc_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct pcfadc_softc *sc = (struct pcfadc_softc *)self;
+ u_char chanuse[PCF8591_CHANNELS * 4], desc[PCF8591_CHANNELS * 32];
+ u_char *cp;
+ u_int8_t junk[PCF8591_CHANNELS + 1];
+ u_int32_t transinfo[PCF8591_CHANNELS * 4];
+ struct i2c_attach_args *ia = aux;
+ int dlen, clen, tlen, node = *(int *)ia->ia_cookie;
+ u_int i;
+
+ if ((dlen = OF_getprop(node, "channels-description", desc,
+    sizeof(desc))) < 0) {
+ printf(": couldn't find \"channels-description\" property\n");
+ return;
+ }
+ if (dlen > sizeof(desc) || desc[dlen - 1] != '\0') {
+ printf(": bad \"channels-description\" property\n");
+ return;
+ }
+ if ((clen = OF_getprop(node, "channels-in-use", chanuse,
+    sizeof(chanuse))) < 0) {
+ printf(": couldn't find \"channels-in-use\" property\n");
+ return;
+ }
+ if ((clen % 4) != 0) {
+ printf(": invalid \"channels-in-use\" length %d\n", clen);
+ return;
+ }
+ sc->sc_nchan = clen / 4;
+ if (sc->sc_nchan > PCF8591_CHANNELS) {
+ printf(": invalid number of channels (%d)\n", sc->sc_nchan);
+ return;
+ }
+
+ if ((tlen = OF_getprop(node, "tables", sc->sc_xlate,
+    sizeof(sc->sc_xlate))) < 0) {
+ printf(": couldn't find \"tables\" property\n");
+ return;
+ }
+ /* We only support complete, single width tables */
+ if (tlen != 256) {
+ printf(": invalid \"tables\" length %d\n", tlen);
+ return;
+ }
+
+ if ((tlen = OF_getprop(node, "translation", transinfo,
+    sizeof(transinfo))) < 0) {
+ printf(": couldn't find \"translation\" property\n");
+ return;
+ }
+ if (tlen != (sc->sc_nchan * 4 * 4)) {
+ printf(": invalid \"translation\" length %d\n", tlen);
+ return;
+ }
+
+ cp = desc;
+ for (i = 0; i < sc->sc_nchan; i++) {
+ struct pcfadc_channel *chp = &sc->sc_channels[i];
+
+ bzero(&chp->chan_sensor, sizeof(chp->chan_sensor));
+ strlcpy(chp->chan_sensor.device, sc->sc_dev.dv_xname,
+    sizeof(chp->chan_sensor.device));
+ chp->chan_sensor.type = SENSOR_TEMP;
+
+ if (cp >= desc + dlen) {
+ printf(": invalid \"channels-description\"\n");
+ return;
+ }
+ strlcpy(chp->chan_sensor.desc, cp,
+    sizeof(chp->chan_sensor.desc));
+ cp += strlen(cp) + 1;
+
+ /*
+ * We only support input temperature channels, with
+ * valid channel numbers, and basic (unscaled) translation
+ *
+ * XXX TODO: support voltage (type 2) channels and type 4
+ * (scaled) translation tables
+ */
+ if (chanuse[(i * 4)] > PCF8591_CHANNELS || /* channel # */
+    chanuse[(i * 4) + 1] != 0 || /* dir == input */
+    chanuse[(i * 4) + 2] != 1 || /* type == temp */
+    transinfo[(i * 4)] != 3 || /* xlate == table */
+    transinfo[(i * 4) + 2] != 0 || /* no xlate offset */
+    transinfo[(i * 4) + 3] != 0x100) { /* xlate tbl length */
+ printf(": unsupported sensor %d\n", i);
+ return;
+ }
+ chp->chan_num = chanuse[(i * 4)];
+ }
+
+ sc->sc_tag = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+
+ iic_acquire_bus(sc->sc_tag, 0);
+
+ /* Try a read now, so we can fail if it doesn't work */
+ if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+    NULL, 0, junk, sc->sc_nchan + 1, 0)) {
+ printf(": read failed\n");
+ iic_release_bus(sc->sc_tag, 0);
+ return;
+ }
+
+ iic_release_bus(sc->sc_tag, 0);
+
+ /* Initialize sensor data. */
+ for (i = 0; i < sc->sc_nchan; i++)
+ if (!(sc->sc_channels[i].chan_sensor.flags & SENSOR_FINVALID))
+ sensor_add(&sc->sc_channels[i].chan_sensor);
+
+ if (sensor_task_register(sc, pcfadc_refresh, 5)) {
+ printf(": unable to register update task\n");
+ return;
+ }
+
+ printf("\n");
+}
+
+void
+pcfadc_refresh(void *arg)
+{
+ struct pcfadc_softc *sc = arg;
+ u_int i;
+ u_int8_t data[PCF8591_CHANNELS + 1];
+
+ iic_acquire_bus(sc->sc_tag, 0);
+ /* NB: first byte out is stale, so read num_channels + 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);
+ return;
+ }
+ iic_release_bus(sc->sc_tag, 0);
+
+ /* XXX: so far this only supports temperature channels */
+ for (i = 0; i < sc->sc_nchan; i++) {
+ struct pcfadc_channel *chp = &sc->sc_channels[i];
+
+ if ((chp->chan_sensor.flags & SENSOR_FINVALID) != 0)
+ continue;
+ chp->chan_sensor.value = 273150000 + 1000000 *
+    sc->sc_xlate[data[1 + chp->chan_num]];
+ }
+}
+