1*275cbf62Sderaadt /* $OpenBSD: pcf8591_ofw.c,v 1.3 2007/03/22 16:55:31 deraadt Exp $ */ 250279da8Sdjm 350279da8Sdjm /* 450279da8Sdjm * Copyright (c) 2006 Damien Miller <djm@openbsd.org> 550279da8Sdjm * 650279da8Sdjm * Permission to use, copy, modify, and distribute this software for any 750279da8Sdjm * purpose with or without fee is hereby granted, provided that the above 850279da8Sdjm * copyright notice and this permission notice appear in all copies. 950279da8Sdjm * 1050279da8Sdjm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1150279da8Sdjm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1250279da8Sdjm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1350279da8Sdjm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1450279da8Sdjm * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1550279da8Sdjm * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1650279da8Sdjm * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1750279da8Sdjm */ 1850279da8Sdjm 1950279da8Sdjm #include <sys/param.h> 2050279da8Sdjm #include <sys/systm.h> 2150279da8Sdjm #include <sys/device.h> 2250279da8Sdjm #include <sys/sensors.h> 2350279da8Sdjm 2450279da8Sdjm #include <dev/ofw/openfirm.h> 2550279da8Sdjm #include <dev/i2c/i2cvar.h> 2650279da8Sdjm 2750279da8Sdjm #define PCF8591_CHANNELS 4 2850279da8Sdjm 2950279da8Sdjm struct pcfadc_channel { 3050279da8Sdjm u_int chan_num; 31*275cbf62Sderaadt struct ksensor chan_sensor; 3250279da8Sdjm }; 3350279da8Sdjm 3450279da8Sdjm struct pcfadc_softc { 3550279da8Sdjm struct device sc_dev; 3650279da8Sdjm i2c_tag_t sc_tag; 3750279da8Sdjm i2c_addr_t sc_addr; 3850279da8Sdjm u_char sc_xlate[256]; 3950279da8Sdjm u_int sc_nchan; 4050279da8Sdjm struct pcfadc_channel sc_channels[PCF8591_CHANNELS]; 41*275cbf62Sderaadt struct ksensordev sc_sensordev; 4250279da8Sdjm }; 4350279da8Sdjm 4450279da8Sdjm int pcfadc_match(struct device *, void *, void *); 4550279da8Sdjm void pcfadc_attach(struct device *, struct device *, void *); 4650279da8Sdjm void pcfadc_refresh(void *); 4750279da8Sdjm 4850279da8Sdjm struct cfattach pcfadc_ca = { 4950279da8Sdjm sizeof(struct pcfadc_softc), pcfadc_match, pcfadc_attach 5050279da8Sdjm }; 5150279da8Sdjm 5250279da8Sdjm struct cfdriver pcfadc_cd = { 5350279da8Sdjm NULL, "pcfadc", DV_DULL 5450279da8Sdjm }; 5550279da8Sdjm 5650279da8Sdjm int 5750279da8Sdjm pcfadc_match(struct device *parent, void *match, void *aux) 5850279da8Sdjm { 5950279da8Sdjm struct i2c_attach_args *ia = aux; 6050279da8Sdjm 6150279da8Sdjm if (strcmp(ia->ia_name, "i2cpcf,8591") != 0) 6250279da8Sdjm return (0); 6350279da8Sdjm 6450279da8Sdjm return (1); 6550279da8Sdjm } 6650279da8Sdjm 6750279da8Sdjm void 6850279da8Sdjm pcfadc_attach(struct device *parent, struct device *self, void *aux) 6950279da8Sdjm { 7050279da8Sdjm struct pcfadc_softc *sc = (struct pcfadc_softc *)self; 7150279da8Sdjm u_char chanuse[PCF8591_CHANNELS * 4], desc[PCF8591_CHANNELS * 32]; 7250279da8Sdjm u_char *cp; 7350279da8Sdjm u_int8_t junk[PCF8591_CHANNELS + 1]; 7450279da8Sdjm u_int32_t transinfo[PCF8591_CHANNELS * 4]; 7550279da8Sdjm struct i2c_attach_args *ia = aux; 7650279da8Sdjm int dlen, clen, tlen, node = *(int *)ia->ia_cookie; 7750279da8Sdjm u_int i; 7850279da8Sdjm 7950279da8Sdjm if ((dlen = OF_getprop(node, "channels-description", desc, 8050279da8Sdjm sizeof(desc))) < 0) { 8150279da8Sdjm printf(": couldn't find \"channels-description\" property\n"); 8250279da8Sdjm return; 8350279da8Sdjm } 8450279da8Sdjm if (dlen > sizeof(desc) || desc[dlen - 1] != '\0') { 8550279da8Sdjm printf(": bad \"channels-description\" property\n"); 8650279da8Sdjm return; 8750279da8Sdjm } 8850279da8Sdjm if ((clen = OF_getprop(node, "channels-in-use", chanuse, 8950279da8Sdjm sizeof(chanuse))) < 0) { 9050279da8Sdjm printf(": couldn't find \"channels-in-use\" property\n"); 9150279da8Sdjm return; 9250279da8Sdjm } 9350279da8Sdjm if ((clen % 4) != 0) { 9450279da8Sdjm printf(": invalid \"channels-in-use\" length %d\n", clen); 9550279da8Sdjm return; 9650279da8Sdjm } 9750279da8Sdjm sc->sc_nchan = clen / 4; 9850279da8Sdjm if (sc->sc_nchan > PCF8591_CHANNELS) { 9950279da8Sdjm printf(": invalid number of channels (%d)\n", sc->sc_nchan); 10050279da8Sdjm return; 10150279da8Sdjm } 10250279da8Sdjm 10350279da8Sdjm if ((tlen = OF_getprop(node, "tables", sc->sc_xlate, 10450279da8Sdjm sizeof(sc->sc_xlate))) < 0) { 10550279da8Sdjm printf(": couldn't find \"tables\" property\n"); 10650279da8Sdjm return; 10750279da8Sdjm } 10850279da8Sdjm /* We only support complete, single width tables */ 10950279da8Sdjm if (tlen != 256) { 11050279da8Sdjm printf(": invalid \"tables\" length %d\n", tlen); 11150279da8Sdjm return; 11250279da8Sdjm } 11350279da8Sdjm 11450279da8Sdjm if ((tlen = OF_getprop(node, "translation", transinfo, 11550279da8Sdjm sizeof(transinfo))) < 0) { 11650279da8Sdjm printf(": couldn't find \"translation\" property\n"); 11750279da8Sdjm return; 11850279da8Sdjm } 11950279da8Sdjm if (tlen != (sc->sc_nchan * 4 * 4)) { 12050279da8Sdjm printf(": invalid \"translation\" length %d\n", tlen); 12150279da8Sdjm return; 12250279da8Sdjm } 12350279da8Sdjm 12450279da8Sdjm cp = desc; 12550279da8Sdjm for (i = 0; i < sc->sc_nchan; i++) { 12650279da8Sdjm struct pcfadc_channel *chp = &sc->sc_channels[i]; 12750279da8Sdjm 12850279da8Sdjm chp->chan_sensor.type = SENSOR_TEMP; 12950279da8Sdjm 13050279da8Sdjm if (cp >= desc + dlen) { 13150279da8Sdjm printf(": invalid \"channels-description\"\n"); 13250279da8Sdjm return; 13350279da8Sdjm } 13450279da8Sdjm strlcpy(chp->chan_sensor.desc, cp, 13550279da8Sdjm sizeof(chp->chan_sensor.desc)); 13650279da8Sdjm cp += strlen(cp) + 1; 13750279da8Sdjm 13850279da8Sdjm /* 13950279da8Sdjm * We only support input temperature channels, with 14050279da8Sdjm * valid channel numbers, and basic (unscaled) translation 14150279da8Sdjm * 14250279da8Sdjm * XXX TODO: support voltage (type 2) channels and type 4 14350279da8Sdjm * (scaled) translation tables 14450279da8Sdjm */ 14550279da8Sdjm if (chanuse[(i * 4)] > PCF8591_CHANNELS || /* channel # */ 14650279da8Sdjm chanuse[(i * 4) + 1] != 0 || /* dir == input */ 14750279da8Sdjm chanuse[(i * 4) + 2] != 1 || /* type == temp */ 14850279da8Sdjm transinfo[(i * 4)] != 3 || /* xlate == table */ 14950279da8Sdjm transinfo[(i * 4) + 2] != 0 || /* no xlate offset */ 15050279da8Sdjm transinfo[(i * 4) + 3] != 0x100) { /* xlate tbl length */ 15150279da8Sdjm printf(": unsupported sensor %d\n", i); 15250279da8Sdjm return; 15350279da8Sdjm } 15450279da8Sdjm chp->chan_num = chanuse[(i * 4)]; 15550279da8Sdjm } 15650279da8Sdjm 15750279da8Sdjm sc->sc_tag = ia->ia_tag; 15850279da8Sdjm sc->sc_addr = ia->ia_addr; 15950279da8Sdjm 16050279da8Sdjm iic_acquire_bus(sc->sc_tag, 0); 16150279da8Sdjm 16250279da8Sdjm /* Try a read now, so we can fail if it doesn't work */ 16350279da8Sdjm if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 16450279da8Sdjm NULL, 0, junk, sc->sc_nchan + 1, 0)) { 16550279da8Sdjm printf(": read failed\n"); 16650279da8Sdjm iic_release_bus(sc->sc_tag, 0); 16750279da8Sdjm return; 16850279da8Sdjm } 16950279da8Sdjm 17050279da8Sdjm iic_release_bus(sc->sc_tag, 0); 17150279da8Sdjm 17250279da8Sdjm /* Initialize sensor data. */ 17327515a6bSderaadt strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 17427515a6bSderaadt sizeof(sc->sc_sensordev.xname)); 17527515a6bSderaadt 17650279da8Sdjm for (i = 0; i < sc->sc_nchan; i++) 17750279da8Sdjm if (!(sc->sc_channels[i].chan_sensor.flags & SENSOR_FINVALID)) 17827515a6bSderaadt sensor_attach(&sc->sc_sensordev, 17927515a6bSderaadt &sc->sc_channels[i].chan_sensor); 18050279da8Sdjm 18150279da8Sdjm if (sensor_task_register(sc, pcfadc_refresh, 5)) { 18250279da8Sdjm printf(": unable to register update task\n"); 18350279da8Sdjm return; 18450279da8Sdjm } 18550279da8Sdjm 18627515a6bSderaadt sensordev_install(&sc->sc_sensordev); 18727515a6bSderaadt 18850279da8Sdjm printf("\n"); 18950279da8Sdjm } 19050279da8Sdjm 19150279da8Sdjm void 19250279da8Sdjm pcfadc_refresh(void *arg) 19350279da8Sdjm { 19450279da8Sdjm struct pcfadc_softc *sc = arg; 19550279da8Sdjm u_int i; 19650279da8Sdjm u_int8_t data[PCF8591_CHANNELS + 1]; 19750279da8Sdjm 19850279da8Sdjm iic_acquire_bus(sc->sc_tag, 0); 19950279da8Sdjm /* NB: first byte out is stale, so read num_channels + 1 */ 20050279da8Sdjm if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 20150279da8Sdjm NULL, 0, data, PCF8591_CHANNELS + 1, 0)) { 20250279da8Sdjm iic_release_bus(sc->sc_tag, 0); 20350279da8Sdjm return; 20450279da8Sdjm } 20550279da8Sdjm iic_release_bus(sc->sc_tag, 0); 20650279da8Sdjm 20750279da8Sdjm /* XXX: so far this only supports temperature channels */ 20850279da8Sdjm for (i = 0; i < sc->sc_nchan; i++) { 20950279da8Sdjm struct pcfadc_channel *chp = &sc->sc_channels[i]; 21050279da8Sdjm 21150279da8Sdjm if ((chp->chan_sensor.flags & SENSOR_FINVALID) != 0) 21250279da8Sdjm continue; 21350279da8Sdjm chp->chan_sensor.value = 273150000 + 1000000 * 21450279da8Sdjm sc->sc_xlate[data[1 + chp->chan_num]]; 21550279da8Sdjm } 21650279da8Sdjm } 21750279da8Sdjm 218