Edit

IABSD.fr/src/sys/dev/i2c/lm75.c

Branch :

  • Show log

    Commit

  • Author : kettenis
    Date : 2015-05-30 08:39:05
    Hash : 338688f7
    Message : Acquire/release the i2c bus before/after reading the temperature register. Prevents concurrent access to the smu(4) microcontroller on Apple G5 machines, which would result in errors reading the RTC. ok deraadt@, mpi@

  • sys/dev/i2c/lm75.c
  • /*	$OpenBSD: lm75.c,v 1.20 2015/05/30 08:39:05 kettenis Exp $	*/
    /*	$NetBSD: lm75.c,v 1.1 2003/09/30 00:35:31 thorpej Exp $	*/
    /*
     * Copyright (c) 2006 Theo de Raadt <deraadt@openbsd.org>
     * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
     *
     * 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.
     */
    
    /*
     * National Semiconductor LM75/LM76/LM77 temperature sensor.
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/sensors.h>
    
    #include <dev/i2c/i2cvar.h>
    
    #define	LM_MODEL_LM75	1
    #define	LM_MODEL_LM77	2
    #define	LM_MODEL_DS1775	3
    #define	LM_MODEL_LM75A	4
    #define	LM_MODEL_LM76	5
    
    #define LM_POLLTIME	3	/* 3s */
    
    #define	LM75_REG_TEMP			0x00
    #define	LM75_REG_CONFIG			0x01
    #define  LM75_CONFIG_SHUTDOWN		0x01
    #define  LM75_CONFIG_CMPINT		0x02
    #define  LM75_CONFIG_OSPOLARITY		0x04
    #define  LM75_CONFIG_FAULT_QUEUE_MASK	0x18
    #define  LM75_CONFIG_FAULT_QUEUE_1	(0 << 3)
    #define  LM75_CONFIG_FAULT_QUEUE_2	(1 << 3)
    #define  LM75_CONFIG_FAULT_QUEUE_4	(2 << 3)
    #define  LM75_CONFIG_FAULT_QUEUE_6	(3 << 3)
    #define  LM77_CONFIG_INTPOLARITY	0x08
    #define  LM77_CONFIG_FAULT_QUEUE_4	0x10
    #define  DS1755_CONFIG_RESOLUTION(i)	(9 + (((i) >> 5) & 3))
    #define	LM75_REG_THYST_SET_POINT	0x02
    #define	LM75_REG_TOS_SET_POINT		0x03
    #define	LM77_REG_TLOW			0x04
    #define	LM77_REG_THIGH			0x05
    
    struct lmtemp_softc {
    	struct device sc_dev;
    	i2c_tag_t sc_tag;
    	int	sc_addr;
    	int	sc_model;
    	int	sc_bits;
    	int	sc_ratio;
    
    	struct ksensor sc_sensor;
    	struct ksensordev sc_sensordev;
    };
    
    int  lmtemp_match(struct device *, void *, void *);
    void lmtemp_attach(struct device *, struct device *, void *);
    
    struct cfattach lmtemp_ca = {
    	sizeof(struct lmtemp_softc),
    	lmtemp_match,
    	lmtemp_attach
    };
    
    struct cfdriver lmtemp_cd = {
    	NULL, "lmtemp", DV_DULL
    };
    
    /*
     * Temperature on the LM75 is represented by a 9-bit two's complement
     * integer in steps of 0.5C.  The following examples are taken from
     * the LM75 data sheet:
     *
     *	+125C	0 1111 1010	0x0fa
     *	+25C	0 0011 0010	0x032
     *	+0.5C	0 0000 0001	0x001
     *	0C	0 0000 0000	0x000
     *	-0.5C	1 1111 1111	0x1ff
     *	-25C	1 1100 1110	0x1ce
     *	-55C	1 1001 0010	0x192
     *
     * Temperature on the LM75A is represented by an 11-bit two's complement
     * integer in steps of 0.125C.  The LM75A can be treated like an LM75 if
     * the extra precision is not required.  The following examples are
     * taken from the LM75A data sheet:
     *
     *	+127.000C	011 1111 1000	0x3f8
     *	+126.875C	011 1111 0111	0x3f7
     *	+126.125C	011 1111 0001	0x3f1
     *	+125.000C	011 1110 1000	0x3e8
     *	+25.000C	000 1100 1000	0x0c8
     *	+0.125C		000 0000 0001	0x001
     *	0C		000 0000 0000	0x000
     *	-0.125C		111 1111 1111	0x7ff
     *	-25.000C	111 0011 1000	0x738
     *	-54.875C	110 0100 1001	0x649
     *	-55.000C	110 0100 1000	0x648
     *
     * Temperature on the LM77 is represented by a 13-bit two's complement
     * integer in steps of 0.5C.  The LM76 is similar, but the integer is
     * in steps of 0.065C
     *
     * LM75 temperature word:
     *
     * MSB Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 X X X X X X X
     * 15  14   13   12   11   10   9    8    7    6 5 4 3 2 1 0
     *
     *
     * LM75A temperature word:
     *
     * MSB Bit9 Bit8 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 X X X X X
     * 15  14   13   12   11   10   9    8    7    6    5    4 3 2 1 0
     *
     *
     * LM77 temperature word:
     *
     * Sign Sign Sign Sign MSB Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 Status bits
     * 15   14   13   12   11  10   9    8    7    6    5    4    3    2 1 0
     */
    
    int  lmtemp_temp_read(struct lmtemp_softc *, uint8_t, int *);
    void lmtemp_refresh_sensor_data(void *);
    
    int
    lmtemp_match(struct device *parent, void *match, void *aux)
    {
    	struct i2c_attach_args *ia = aux;
    
    	if (strcmp(ia->ia_name, "lm75") == 0 ||
    	    strcmp(ia->ia_name, "lm76") == 0 ||
    	    strcmp(ia->ia_name, "lm77") == 0 ||
    	    strcmp(ia->ia_name, "ds1775") == 0 ||
    	    strcmp(ia->ia_name, "lm75a") == 0)
    		return (1);
    	return (0);
    }
    
    void
    lmtemp_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct lmtemp_softc *sc = (struct lmtemp_softc *)self;
    	struct i2c_attach_args *ia = aux;
    	u_int8_t cmd, data;
    
    	sc->sc_tag = ia->ia_tag;
    	sc->sc_addr = ia->ia_addr;
    
    	printf(": %s", ia->ia_name);
    
    	/* If in SHUTDOWN mode, wake it up */
    	iic_acquire_bus(sc->sc_tag, 0);
    	cmd = LM75_REG_CONFIG;
    	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
    	    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0)) {
    		iic_release_bus(sc->sc_tag, 0);
    		printf(", fails to respond\n");
    		return;
    	}
    	if (data & LM75_CONFIG_SHUTDOWN) {
    		data &= ~LM75_CONFIG_SHUTDOWN;
    		if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
    		    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0)) {
    			printf(", cannot wake up\n");
    			iic_release_bus(sc->sc_tag, 0);
    			return;
    		}
    		printf(", woken up");
    	}
    	iic_release_bus(sc->sc_tag, 0);
    
    	sc->sc_model = LM_MODEL_LM75;
    	sc->sc_bits = 9;
    	sc->sc_ratio = 500000;		/* 0.5 degC for LSB */
    	if (strcmp(ia->ia_name, "lm77") == 0) {
    		sc->sc_model = LM_MODEL_LM77;
    		sc->sc_bits = 13;
    	} else if (strcmp(ia->ia_name, "lm76") == 0) {
    		sc->sc_model = LM_MODEL_LM76;
    		sc->sc_bits = 13;
    		sc->sc_ratio = 62500;	/* 0.0625 degC for LSB */
    	} else if (strcmp(ia->ia_name, "ds1775") == 0) {
    		sc->sc_model = LM_MODEL_DS1775;
    		//sc->sc_bits = DS1755_CONFIG_RESOLUTION(data);
    	} else if (strcmp(ia->ia_name, "lm75a") == 0) {
    		/* For simplicity's sake, treat the LM75A as an LM75 */
    		sc->sc_model = LM_MODEL_LM75A;
    	}
    
    	printf("\n");
    
    	/* Initialize sensor data */
    	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
    	    sizeof(sc->sc_sensordev.xname));
    	sc->sc_sensor.type = SENSOR_TEMP;
    
    	/* Hook into the hw.sensors sysctl */
    	sensor_attach(&sc->sc_sensordev, &sc->sc_sensor);
    	sensordev_install(&sc->sc_sensordev);
    
    	sensor_task_register(sc, lmtemp_refresh_sensor_data, LM_POLLTIME);
    }
    
    int
    lmtemp_temp_read(struct lmtemp_softc *sc, uint8_t which, int *valp)
    {
    	u_int8_t cmd = which;
    	u_int16_t data = 0x0000;
    	int error;
    
    	iic_acquire_bus(sc->sc_tag, 0);
    	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
    	    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0);
    	iic_release_bus(sc->sc_tag, 0);
    	if (error)
    		return (error);
    
    	/* Some chips return transient 0's.. we try next time */
    	if (data == 0x0000)
    		return (1);
    
    	/* convert to half-degrees C */
    	*valp = betoh16(data) / (1 << (16 - sc->sc_bits));
    	return (0);
    }
    
    void
    lmtemp_refresh_sensor_data(void *aux)
    {
    	struct lmtemp_softc *sc = aux;
    	int val;
    	int error;
    
    	error = lmtemp_temp_read(sc, LM75_REG_TEMP, &val);
    	if (error) {
    #if 0
    		printf("%s: unable to read temperature, error = %d\n",
    		    sc->sc_dev.dv_xname, error);
    #endif
    		sc->sc_sensor.flags |= SENSOR_FINVALID;
    		return;
    	}
    
    	sc->sc_sensor.value = val * sc->sc_ratio + 273150000;
    	sc->sc_sensor.flags &= ~SENSOR_FINVALID;
    }