/*	$OpenBSD: asc7621.c,v 1.4 2007/10/31 20:46:17 cnst Exp $	*/

/*
 * Copyright (c) 2007 Mike Belopuhov
 * Copyright (c) 2007 Theo de Raadt
 *
 * 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/i2c/i2cvar.h>

/* ASC7621 registers */

#define ASC7621_PECI		0x40	/* Check for PECI monitoring */
#define ASC7621_PECI_MASK	0x10	/* 00010000 */

#define ASC7621_LEGACY		0x36	/* Check for legacy mode */
#define ASC7621_LEGACY_MASK	0x10	/* 00010000 */

#define ASC7621_TEMP1H		0x25	/* Zone 1 Temperature (MS Byte) */
#define ASC7621_TEMP1L		0x10	/* Zone 1 Temperature (LS Byte) */
#define ASC7621_TEMP2H		0x26	/* Zone 2 Temperature (MS Byte) */
#define ASC7621_TEMP2L		0x15	/* Zone 2 Temperature (LS Byte) */
#define ASC7621_TEMP3H		0x27	/* Zone 3 Temperature (MS Byte) */
#define ASC7621_TEMP3L		0x16	/* Zone 3 Temperature (LS Byte) */
#define ASC7621_TEMP4H		0x33	/* Zone 4 Temperature (MS Byte) */
#define ASC7621_TEMP4L		0x17	/* Zone 4 Temperature (LS Byte) */
#define ASC7621_TEMP_NA		0x80	/* Not plugged */

#define ASC7621_IN1_VH		0x20	/* 2.5V (MS Byte) */
#define ASC7621_IN1_VL		0x13	/* 2.5V (LS Byte) */
#define ASC7621_IN2_VH		0x21	/* VCCP (MS Byte) */
#define ASC7621_IN2_VL		0x18	/* VCCP (LS Byte) */
#define ASC7621_IN3_VH		0x22	/* 3.3V (MS Byte) */
#define ASC7621_IN3_VL		0x11	/* 2.3V (LS Byte) */
#define ASC7621_IN4_VH		0x23	/* 5V   (MS Byte) */
#define ASC7621_IN4_VL		0x12	/* 5V   (LS Byte) */
#define ASC7621_IN5_VH		0x24	/* 12V  (MS Byte) */
#define ASC7621_IN5_VL		0x14	/* 12V  (LS Byte) */

#define ASC7621_TACH1H		0x29	/* Tachometer 1 (MS Byte) */
#define ASC7621_TACH1L		0x28	/* Tachometer 1 (LS Byte) */
#define ASC7621_TACH2H		0x2b	/* Tachometer 2 (MS Byte) */
#define ASC7621_TACH2L		0x2a	/* Tachometer 2 (LS Byte) */
#define ASC7621_TACH3H		0x2d	/* Tachometer 3 (MS Byte) */
#define ASC7621_TACH3L		0x2c	/* Tachometer 3 (LS Byte) */
#define ASC7621_TACH4H		0x2f	/* Tachometer 4 (MS Byte) */
#define ASC7621_TACH4L		0x2e	/* Tachometer 4 (LS Byte) */

/* Sensors */
#define ADL_TEMP1		0
#define ADL_TEMP2		1
#define ADL_TEMP3		2
#define ADL_TEMP4		3
#define ADL_IN1_V		4
#define ADL_IN2_V		5
#define ADL_IN3_V		6
#define ADL_IN4_V		7
#define ADL_IN5_V		8
#define ADL_TACH1		9
#define ADL_TACH2		10
#define ADL_TACH3		11
#define ADL_TACH4		12
#define ADL_NUM_SENSORS		13

struct {
	char		sensor;
	u_int8_t	hreg;			/* MS-byte register */
	u_int8_t	lreg;			/* LS-byte register */
	char		*name;
	u_short		mVscale;
	u_short		tempscale;		/* else a fan */
} adl_worklist[] = {
	{ ADL_TEMP1, ASC7621_TEMP1H, ASC7621_TEMP1L, "CPU", 0, 1 },
	{ ADL_TEMP2, ASC7621_TEMP2H, ASC7621_TEMP2L, "CPU", 0, 1 },
	{ ADL_TEMP3, ASC7621_TEMP3H, ASC7621_TEMP3L, "Internal", 0, 1 },
	{ ADL_TEMP4, ASC7621_TEMP4H, ASC7621_TEMP4L, "External", 0, 1 },

	{ ADL_IN1_V, ASC7621_IN1_VH, ASC7621_IN1_VL, "+1.5V", 2500, 0 },
	{ ADL_IN2_V, ASC7621_IN2_VH, ASC7621_IN2_VL, "Vccp",  2250, 0 },
	{ ADL_IN3_V, ASC7621_IN3_VH, ASC7621_IN3_VL, "+3.3V", 3300, 0 },
	{ ADL_IN4_V, ASC7621_IN4_VH, ASC7621_IN4_VL, "+5V",   5000, 0 },
	{ ADL_IN5_V, ASC7621_IN5_VH, ASC7621_IN5_VL, "+12V", 12000, 0 },

	{ ADL_TACH1, ASC7621_TACH1L, ASC7621_TACH1H, "", 0, 0 },
	{ ADL_TACH2, ASC7621_TACH2L, ASC7621_TACH2H, "", 0, 0 },
	{ ADL_TACH3, ASC7621_TACH3L, ASC7621_TACH3H, "", 0, 0 },
	{ ADL_TACH4, ASC7621_TACH4L, ASC7621_TACH4H, "", 0, 0 }
};

struct adl_softc {
	struct device sc_dev;
	i2c_tag_t sc_tag;
	i2c_addr_t sc_addr;
	u_int8_t sc_conf;

	struct ksensor sc_sensor[ADL_NUM_SENSORS];
	struct ksensordev sc_sensordev;
};

#if 0
static int peci_enabled;
static int legacy_mode;
#endif

int	adl_match(struct device *, void *, void *);
void	adl_attach(struct device *, struct device *, void *);

void	adl_refresh(void *);

struct cfattach adl_ca = {
	sizeof(struct adl_softc), adl_match, adl_attach
};

struct cfdriver adl_cd = {
	NULL, "adl", DV_DULL
};

int
adl_match(struct device *parent, void *match, void *aux)
{
	struct i2c_attach_args *ia = aux;

	if (strcmp(ia->ia_name, "asc7621") == 0)
		return (1);
	return (0);
}

void
adl_attach(struct device *parent, struct device *self, void *aux)
{
	struct adl_softc *sc = (struct adl_softc *)self;
	struct i2c_attach_args *ia = aux;
	u_int8_t cmd, data;
	int i;

	sc->sc_tag = ia->ia_tag;
	sc->sc_addr = ia->ia_addr;

	printf(": %s", ia->ia_name);

	/* Initialize sensor data. */
	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
	    sizeof(sc->sc_sensordev.xname));

	/* Check for PECI mode */
	cmd = ASC7621_PECI;
	(void)iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
	    &cmd, sizeof(cmd), &data, sizeof(data), 0);
	if (data & ASC7621_PECI_MASK)
		printf(", PECI enabled\n");

#if 0
	/* Check for legacy mode */
	cmd = ASC7621_LEGACY;
	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
	    &cmd, sizeof(cmd), &data, sizeof(data), 0)) {
		printf(", unable to read PECI configuration register");
	}
	if (data & ASC7621_LEGACY_MASK)
		legacy_mode = 1;
#endif

	if (sensor_task_register(sc, adl_refresh, 5) == NULL) {
		printf(", unable to register update task\n");
		return;
	}

	for (i = 0; i < ADL_NUM_SENSORS; i++) {
		if (adl_worklist[i].tempscale)
			sc->sc_sensor[i].type = SENSOR_TEMP;
		else if (adl_worklist[i].mVscale)
			sc->sc_sensor[i].type = SENSOR_VOLTS_DC;
		else
			sc->sc_sensor[i].type = SENSOR_FANRPM;
		strlcpy(sc->sc_sensor[i].desc, adl_worklist[i].name,
		    sizeof(sc->sc_sensor[i].desc));

		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
	}
	sensordev_install(&sc->sc_sensordev);

	printf("\n");
}

void
adl_refresh(void *arg)
{
	struct adl_softc *sc = arg;
	int64_t temp, volt;
	u_int8_t hdata, ldata, hreg, lreg;
	u_int16_t fan;
	int i;

	iic_acquire_bus(sc->sc_tag, 0);

	for (i = 0; i < sizeof adl_worklist / sizeof(adl_worklist[0]); i++) {
		hreg = adl_worklist[i].hreg;
		if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
		    sc->sc_addr, &hreg, sizeof hreg, &hdata, sizeof hdata, 0)) {
			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
			continue;
		}
		lreg = adl_worklist[i].lreg;
		if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
		    sc->sc_addr, &lreg, sizeof lreg, &ldata, sizeof ldata, 0)) {
			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
			continue;
		}

		sc->sc_sensor[i].flags &= ~SENSOR_FINVALID;
		if (adl_worklist[i].tempscale) {
			if (hdata == ASC7621_TEMP_NA)
				sc->sc_sensor[i].flags |= SENSOR_FINVALID;
			else {
				/*
				 * 10-bit two's complement integer in
				 * steps of 0.25
				 */
				temp = ((hdata << 8 | ldata)) >> (16 - 10);
				temp = temp * 250000 + 273150000;
				sc->sc_sensor[i].value = temp;
			}
		} else if (adl_worklist[i].mVscale) {
			volt = ((hdata << 8 | ldata)) >> (16 - 10);
			volt = volt * adl_worklist[i].mVscale / (192 << 2);
			sc->sc_sensor[i].value = volt * 1000;
		} else {
			/*
			 * Inversed to ensure that the LS byte will be read
			 * before MS byte.
			 */
			fan = hdata + (ldata << 8);
			if (fan == 0 || fan == 0xffff)
				sc->sc_sensor[i].flags |= SENSOR_FINVALID;
			else
				sc->sc_sensor[i].value = (90000 * 60) / fan;
		}
	}

	iic_release_bus(sc->sc_tag, 0);
}