From 809097c8d3709b9a62c5cd535cc3816444396e50 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Fri, 10 Feb 2006 05:28:57 +0000 Subject: driver for Philips PCF8591 I2C ADC sensor. Because of the mindlessness of this chip, it is impossible to probe for and we need to be told where it is and how to understand its output. We get this information from openfirmware, but this restricts the use of the driver to sparc64 for now. Only sensors that OFW identifies as temperature sensors are supported at present. prodded by dlg@ "get it in" deraadt@ --- sys/arch/sparc64/dev/pcf8591_ofw.c | 213 +++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 sys/arch/sparc64/dev/pcf8591_ofw.c (limited to 'sys/arch/sparc64/dev') diff --git a/sys/arch/sparc64/dev/pcf8591_ofw.c b/sys/arch/sparc64/dev/pcf8591_ofw.c new file mode 100644 index 00000000000..bc25659b96e --- /dev/null +++ b/sys/arch/sparc64/dev/pcf8591_ofw.c @@ -0,0 +1,213 @@ +/* $OpenBSD: pcf8591_ofw.c,v 1.1 2006/02/10 05:28:56 djm Exp $ */ + +/* + * Copyright (c) 2006 Damien Miller + * + * 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 +#include +#include +#include + +#include +#include + +#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]]; + } +} + -- cgit v1.2.3