/*	$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;
}
