/*	$OpenBSD: lom.c,v 1.25 2014/12/10 12:27:57 mikeb Exp $	*/
/*
 * Copyright (c) 2009 Mark Kettenis
 *
 * 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/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/sensors.h>
#include <sys/systm.h>
#include <sys/timeout.h>

#include <machine/autoconf.h>
#include <machine/openfirm.h>

#include <sparc64/dev/ebusreg.h>
#include <sparc64/dev/ebusvar.h>

/*
 * LOMlite is a so far unidentified microcontroller.
 */
#define LOM1_STATUS		0x00	/* R */
#define  LOM1_STATUS_BUSY	0x80
#define LOM1_CMD		0x00	/* W */
#define LOM1_DATA		0x01	/* R/W */

/*
 * LOMlite2 is implemented as a H8/3437 microcontroller which has its
 * on-chip host interface hooked up to EBus.
 */
#define LOM2_DATA		0x00	/* R/W */
#define LOM2_CMD		0x01	/* W */
#define LOM2_STATUS		0x01	/* R */
#define  LOM2_STATUS_OBF	0x01	/* Output Buffer Full */
#define  LOM2_STATUS_IBF	0x02	/* Input Buffer Full  */

#define LOM_IDX_CMD		0x00
#define  LOM_IDX_CMD_GENERIC	0x00
#define  LOM_IDX_CMD_TEMP	0x04
#define  LOM_IDX_CMD_FAN	0x05

#define LOM_IDX_FW_REV		0x01	/* Firmware revision  */

#define LOM_IDX_FAN1		0x04	/* Fan speed */
#define LOM_IDX_FAN2		0x05
#define LOM_IDX_FAN3		0x06
#define LOM_IDX_FAN4		0x07
#define LOM_IDX_PSU1		0x08	/* PSU status */
#define LOM_IDX_PSU2		0x09
#define LOM_IDX_PSU3		0x0a
#define  LOM_PSU_INPUTA		0x01
#define  LOM_PSU_INPUTB		0x02
#define  LOM_PSU_OUTPUT		0x04
#define  LOM_PSU_PRESENT	0x08
#define  LOM_PSU_STANDBY	0x10

#define LOM_IDX_TEMP1		0x18	/* Temperature */
#define LOM_IDX_TEMP2		0x19
#define LOM_IDX_TEMP3		0x1a
#define LOM_IDX_TEMP4		0x1b
#define LOM_IDX_TEMP5		0x1c
#define LOM_IDX_TEMP6		0x1d
#define LOM_IDX_TEMP7		0x1e
#define LOM_IDX_TEMP8		0x1f

#define LOM_IDX_LED1		0x25

#define LOM_IDX_ALARM		0x30
#define LOM_IDX_WDOG_CTL	0x31
#define  LOM_WDOG_ENABLE	0x01
#define  LOM_WDOG_RESET		0x02
#define  LOM_WDOG_AL3_WDOG	0x04
#define  LOM_WDOG_AL3_FANPSU	0x08
#define LOM_IDX_WDOG_TIME	0x32
#define  LOM_WDOG_TIME_MAX	126

#define LOM1_IDX_HOSTNAME1	0x33
#define LOM1_IDX_HOSTNAME2	0x34
#define LOM1_IDX_HOSTNAME3	0x35
#define LOM1_IDX_HOSTNAME4	0x36
#define LOM1_IDX_HOSTNAME5	0x37
#define LOM1_IDX_HOSTNAME6	0x38
#define LOM1_IDX_HOSTNAME7	0x39
#define LOM1_IDX_HOSTNAME8	0x3a
#define LOM1_IDX_HOSTNAME9	0x3b
#define LOM1_IDX_HOSTNAME10	0x3c
#define LOM1_IDX_HOSTNAME11	0x3d
#define LOM1_IDX_HOSTNAME12	0x3e

#define LOM2_IDX_HOSTNAMELEN	0x38
#define LOM2_IDX_HOSTNAME	0x39

#define LOM_IDX_CONFIG		0x5d
#define LOM_IDX_FAN1_CAL	0x5e
#define LOM_IDX_FAN2_CAL	0x5f
#define LOM_IDX_FAN3_CAL	0x60
#define LOM_IDX_FAN4_CAL	0x61
#define LOM_IDX_FAN1_LOW	0x62
#define LOM_IDX_FAN2_LOW	0x63
#define LOM_IDX_FAN3_LOW	0x64
#define LOM_IDX_FAN4_LOW	0x65

#define LOM_IDX_CONFIG2		0x66
#define LOM_IDX_CONFIG3		0x67

#define LOM_IDX_PROBE55		0x7e	/* Always returns 0x55 */
#define LOM_IDX_PROBEAA		0x7f	/* Always returns 0xaa */

#define LOM_IDX_WRITE		0x80

#define LOM_IDX4_TEMP_NAME_START	0x40
#define LOM_IDX4_TEMP_NAME_END		0xff

#define LOM_IDX5_FAN_NAME_START		0x40
#define LOM_IDX5_FAN_NAME_END		0xff

#define LOM_MAX_FAN	4
#define LOM_MAX_PSU	3
#define LOM_MAX_TEMP	8

struct lom_cmd {
	uint8_t			lc_cmd;
	uint8_t			lc_data;

	TAILQ_ENTRY(lom_cmd)	lc_next;
};

struct lom_softc {
	struct device		sc_dev;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	int			sc_type;
#define LOM_LOMLITE		0
#define LOM_LOMLITE2		2
	int			sc_space;

	struct ksensor		sc_fan[LOM_MAX_FAN];
	struct ksensor		sc_psu[LOM_MAX_PSU];
	struct ksensor		sc_temp[LOM_MAX_TEMP];
	struct ksensordev	sc_sensordev;

	int			sc_num_fan;
	int			sc_num_psu;
	int			sc_num_temp;

	uint8_t			sc_fan_cal[LOM_MAX_FAN];
	uint8_t			sc_fan_low[LOM_MAX_FAN];

	char			sc_hostname[MAXHOSTNAMELEN];

	struct timeout		sc_wdog_to;
	int			sc_wdog_period;
	uint8_t			sc_wdog_ctl;
	struct lom_cmd		sc_wdog_pat;

	TAILQ_HEAD(, lom_cmd)	sc_queue;
	struct mutex		sc_queue_mtx;
	struct timeout		sc_state_to;
	int			sc_state;
#define LOM_STATE_IDLE		0
#define LOM_STATE_CMD		1
#define LOM_STATE_DATA		2
	int			sc_retry;
};

int	lom_match(struct device *, void *, void *);
void	lom_attach(struct device *, struct device *, void *);
int	lom_activate(struct device *, int);

struct cfattach lom_ca = {
	sizeof(struct lom_softc), lom_match, lom_attach,
	NULL, lom_activate
};

struct cfdriver lom_cd = {
	NULL, "lom", DV_DULL
};

int	lom_read(struct lom_softc *, uint8_t, uint8_t *);
int	lom_write(struct lom_softc *, uint8_t, uint8_t);
void	lom_queue_cmd(struct lom_softc *, struct lom_cmd *);
void	lom_dequeue_cmd(struct lom_softc *, struct lom_cmd *);
int	lom1_read(struct lom_softc *, uint8_t, uint8_t *);
int	lom1_write(struct lom_softc *, uint8_t, uint8_t);
int	lom1_read_polled(struct lom_softc *, uint8_t, uint8_t *);
int	lom1_write_polled(struct lom_softc *, uint8_t, uint8_t);
void	lom1_queue_cmd(struct lom_softc *, struct lom_cmd *);
void	lom1_process_queue(void *);
void	lom1_process_queue_locked(struct lom_softc *);
int	lom2_read(struct lom_softc *, uint8_t, uint8_t *);
int	lom2_write(struct lom_softc *, uint8_t, uint8_t);
int	lom2_read_polled(struct lom_softc *, uint8_t, uint8_t *);
int	lom2_write_polled(struct lom_softc *, uint8_t, uint8_t);
void	lom2_queue_cmd(struct lom_softc *, struct lom_cmd *);
int	lom2_intr(void *);

int	lom_init_desc(struct lom_softc *sc);
void	lom_refresh(void *);
void	lom1_write_hostname(struct lom_softc *);
void	lom2_write_hostname(struct lom_softc *);

void	lom_wdog_pat(void *);
int	lom_wdog_cb(void *, int);

void	lom_shutdown(void *);

int
lom_match(struct device *parent, void *match, void *aux)
{
	struct ebus_attach_args *ea = aux;

	if (strcmp(ea->ea_name, "SUNW,lom") == 0 ||
	    strcmp(ea->ea_name, "SUNW,lomh") == 0)
		return (1);

	return (0);
}

void
lom_attach(struct device *parent, struct device *self, void *aux)
{
	struct lom_softc *sc = (void *)self;
	struct ebus_attach_args *ea = aux;
	uint8_t reg, fw_rev, config, config2, config3;
	uint8_t cal, low;
	int i;

	if (strcmp(ea->ea_name, "SUNW,lomh") == 0) {
		if (ea->ea_nintrs < 1) {
			printf(": no interrupt\n");
			return;
		}
		sc->sc_type = LOM_LOMLITE2;
	}

	if (ebus_bus_map(ea->ea_iotag, 0,
	    EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
	    ea->ea_regs[0].size, 0, 0, &sc->sc_ioh) == 0) {
		sc->sc_iot = ea->ea_iotag;
	} else if (ebus_bus_map(ea->ea_memtag, 0,
	    EBUS_PADDR_FROM_REG(&ea->ea_regs[0]),
	    ea->ea_regs[0].size, 0, 0, &sc->sc_ioh) == 0) {
		sc->sc_iot = ea->ea_memtag;
	} else {
		printf(": can't map register space\n");
                return;
	}

	if (sc->sc_type < LOM_LOMLITE2) {
		/* XXX Magic */
		bus_space_read_1(sc->sc_iot, sc->sc_ioh, 0);
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, 3, 0xca);
	}

	if (lom_read(sc, LOM_IDX_PROBE55, &reg) || reg != 0x55 ||
	    lom_read(sc, LOM_IDX_PROBEAA, &reg) || reg != 0xaa ||
	    lom_read(sc, LOM_IDX_FW_REV, &fw_rev) ||
	    lom_read(sc, LOM_IDX_CONFIG, &config))
	{
		printf(": not responding\n");
		return;
	}

	TAILQ_INIT(&sc->sc_queue);
	mtx_init(&sc->sc_queue_mtx, IPL_BIO);

	config2 = config3 = 0;
	if (sc->sc_type < LOM_LOMLITE2) {
		/*
		 * LOMlite doesn't do interrupts so we limp along on
		 * timeouts.
		 */
		timeout_set(&sc->sc_state_to, lom1_process_queue, sc);
	} else {
		lom_read(sc, LOM_IDX_CONFIG2, &config2);
		lom_read(sc, LOM_IDX_CONFIG3, &config3);

		bus_intr_establish(sc->sc_iot, ea->ea_intrs[0],
		    IPL_BIO, 0, lom2_intr, sc, self->dv_xname);
	}

	sc->sc_num_fan = min((config >> 5) & 0x7, LOM_MAX_FAN);
	sc->sc_num_psu = min((config >> 3) & 0x3, LOM_MAX_PSU);
	sc->sc_num_temp = min((config2 >> 4) & 0xf, LOM_MAX_TEMP);

	for (i = 0; i < sc->sc_num_fan; i++) {
		if (lom_read(sc, LOM_IDX_FAN1_CAL + i, &cal) ||
		    lom_read(sc, LOM_IDX_FAN1_LOW + i, &low)) {
			printf(": can't read fan information\n");
			return;
		}
		sc->sc_fan_cal[i] = cal;
		sc->sc_fan_low[i] = low;
	}

	/* Initialize sensor data. */
	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
	    sizeof(sc->sc_sensordev.xname));
	for (i = 0; i < sc->sc_num_fan; i++) {
		sc->sc_fan[i].type = SENSOR_FANRPM;
		sensor_attach(&sc->sc_sensordev, &sc->sc_fan[i]);
		snprintf(sc->sc_fan[i].desc, sizeof(sc->sc_fan[i].desc),
		    "fan%d", i + 1);
	}
	for (i = 0; i < sc->sc_num_psu; i++) {
		sc->sc_psu[i].type = SENSOR_INDICATOR;
		sensor_attach(&sc->sc_sensordev, &sc->sc_psu[i]);
		snprintf(sc->sc_psu[i].desc, sizeof(sc->sc_psu[i].desc),
		    "PSU%d", i + 1);
	}
	for (i = 0; i < sc->sc_num_temp; i++) {
		sc->sc_temp[i].type = SENSOR_TEMP;
		sensor_attach(&sc->sc_sensordev, &sc->sc_temp[i]);
	}
	if (lom_init_desc(sc)) {
		printf(": can't read sensor names\n");
		return;
	}

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

	sensordev_install(&sc->sc_sensordev);

	/*
	 * We configure the watchdog to turn on the fault LED when the
	 * watchdog timer expires.  We run our own timeout to pat it
	 * such that this won't happen unless the kernel hangs.  When
	 * the watchdog is explicitly configured using sysctl(8), we
	 * reconfigure it to reset the machine and let the standard
	 * watchdog(4) machinery take over.
	 */
	lom_write(sc, LOM_IDX_WDOG_TIME, LOM_WDOG_TIME_MAX);
	lom_read(sc, LOM_IDX_WDOG_CTL, &sc->sc_wdog_ctl);
	sc->sc_wdog_ctl &= ~LOM_WDOG_RESET;
	sc->sc_wdog_ctl |= LOM_WDOG_ENABLE;
	lom_write(sc, LOM_IDX_WDOG_CTL, sc->sc_wdog_ctl);
	timeout_set(&sc->sc_wdog_to, lom_wdog_pat, sc);
	timeout_add_sec(&sc->sc_wdog_to, LOM_WDOG_TIME_MAX / 2);

	wdog_register(lom_wdog_cb, sc);

	printf(": %s rev %d.%d\n",
	    sc->sc_type < LOM_LOMLITE2 ? "LOMlite" : "LOMlite2",
	    fw_rev >> 4, fw_rev & 0x0f);
}

int
lom_activate(struct device *self, int act)
{
	int ret = 0;

	switch (act) {
	case DVACT_POWERDOWN:
		wdog_shutdown(self);
		lom_shutdown(self);
		break;
	}

	return (ret);
}

int
lom_read(struct lom_softc *sc, uint8_t reg, uint8_t *val)
{
	if (sc->sc_type < LOM_LOMLITE2)
		return lom1_read(sc, reg, val);
	else
		return lom2_read(sc, reg, val);
}

int
lom_write(struct lom_softc *sc, uint8_t reg, uint8_t val)
{
	if (sc->sc_type < LOM_LOMLITE2)
		return lom1_write(sc, reg, val);
	else
		return lom2_write(sc, reg, val);
}

void
lom_queue_cmd(struct lom_softc *sc, struct lom_cmd *lc)
{
	if (sc->sc_type < LOM_LOMLITE2)
		return lom1_queue_cmd(sc, lc);
	else
		return lom2_queue_cmd(sc, lc);
}

void
lom_dequeue_cmd(struct lom_softc *sc, struct lom_cmd *lc)
{
	struct lom_cmd *lcp;

	mtx_enter(&sc->sc_queue_mtx);
	TAILQ_FOREACH(lcp, &sc->sc_queue, lc_next) {
		if (lcp == lc) {
			TAILQ_REMOVE(&sc->sc_queue, lc, lc_next);
			break;
		}
	}
	mtx_leave(&sc->sc_queue_mtx);
}

int
lom1_read(struct lom_softc *sc, uint8_t reg, uint8_t *val)
{
	struct lom_cmd lc;
	int error;

	if (cold)
		return lom1_read_polled(sc, reg, val);

	lc.lc_cmd = reg;
	lc.lc_data = 0xff;
	lom1_queue_cmd(sc, &lc);

	error = tsleep(&lc, PZERO, "lomrd", hz);
	if (error)
		lom_dequeue_cmd(sc, &lc);

	*val = lc.lc_data;

	return (error);
}

int
lom1_write(struct lom_softc *sc, uint8_t reg, uint8_t val)
{
	struct lom_cmd lc;
	int error;

	if (cold)
		return lom1_write_polled(sc, reg, val);

	lc.lc_cmd = reg | LOM_IDX_WRITE;
	lc.lc_data = val;
	lom1_queue_cmd(sc, &lc);

	error = tsleep(&lc, PZERO, "lomwr", 2 * hz);
	if (error)
		lom_dequeue_cmd(sc, &lc);

	return (error);
}

int
lom1_read_polled(struct lom_softc *sc, uint8_t reg, uint8_t *val)
{
	uint8_t str;
	int i;

	/* Wait for input buffer to become available. */
	for (i = 30; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_STATUS);
		delay(1000);
		if ((str & LOM1_STATUS_BUSY) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_CMD, reg);

	/* Wait until the microcontroller fills output buffer. */
	for (i = 30; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_STATUS);
		delay(1000);
		if ((str & LOM1_STATUS_BUSY) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	*val = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_DATA);
	return (0);
}

int
lom1_write_polled(struct lom_softc *sc, uint8_t reg, uint8_t val)
{
	uint8_t str;
	int i;

	/* Wait for input buffer to become available. */
	for (i = 30; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_STATUS);
		delay(1000);
		if ((str & LOM1_STATUS_BUSY) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	reg |= LOM_IDX_WRITE;
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_CMD, reg);

	/* Wait until the microcontroller fills output buffer. */
	for (i = 30; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_STATUS);
		delay(1000);
		if ((str & LOM1_STATUS_BUSY) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_DATA, val);

	return (0);
}

void
lom1_queue_cmd(struct lom_softc *sc, struct lom_cmd *lc)
{
	mtx_enter(&sc->sc_queue_mtx);
	TAILQ_INSERT_TAIL(&sc->sc_queue, lc, lc_next);
	if (sc->sc_state == LOM_STATE_IDLE) {
		sc->sc_state = LOM_STATE_CMD;
		lom1_process_queue_locked(sc);
	}
	mtx_leave(&sc->sc_queue_mtx);
}

void
lom1_process_queue(void *arg)
{
	struct lom_softc *sc = arg;

	mtx_enter(&sc->sc_queue_mtx);
	lom1_process_queue_locked(sc);
	mtx_leave(&sc->sc_queue_mtx);
}

void
lom1_process_queue_locked(struct lom_softc *sc)
{
	struct lom_cmd *lc;
	uint8_t str;

	lc = TAILQ_FIRST(&sc->sc_queue);
	if (lc == NULL) {
		sc->sc_state = LOM_STATE_IDLE;
		return;
	}

	str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_STATUS);
	if (str & LOM1_STATUS_BUSY) {
		if (sc->sc_retry++ < 30) {
			timeout_add_msec(&sc->sc_state_to, 1);
			return;
		}

		/*
		 * Looks like the microcontroller got wedged.  Unwedge
		 * it by writing this magic value.  Give it some time
		 * to recover.
		 */
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_DATA, 0xac);
		timeout_add_msec(&sc->sc_state_to, 1000);
		sc->sc_state = LOM_STATE_CMD;
		return;
	}

	sc->sc_retry = 0;

	if (sc->sc_state == LOM_STATE_CMD) {
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_CMD, lc->lc_cmd);
		sc->sc_state = LOM_STATE_DATA;
		timeout_add_msec(&sc->sc_state_to, 250);
		return;
	}

	KASSERT(sc->sc_state == LOM_STATE_DATA);
	if ((lc->lc_cmd & LOM_IDX_WRITE) == 0)
		lc->lc_data = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM1_DATA);
	else
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM1_DATA, lc->lc_data);

	TAILQ_REMOVE(&sc->sc_queue, lc, lc_next);

	wakeup(lc);

	if (!TAILQ_EMPTY(&sc->sc_queue)) {
		sc->sc_state = LOM_STATE_CMD;
		timeout_add_msec(&sc->sc_state_to, 1);
		return;
	}

	sc->sc_state = LOM_STATE_IDLE;
}

int
lom2_read(struct lom_softc *sc, uint8_t reg, uint8_t *val)
{
	struct lom_cmd lc;
	int error;

	if (cold)
		return lom2_read_polled(sc, reg, val);

	lc.lc_cmd = reg;
	lc.lc_data = 0xff;
	lom2_queue_cmd(sc, &lc);

	error = tsleep(&lc, PZERO, "lom2rd", hz);
	if (error)
		lom_dequeue_cmd(sc, &lc);

	*val = lc.lc_data;

	return (error);
}

int
lom2_read_polled(struct lom_softc *sc, uint8_t reg, uint8_t *val)
{
	uint8_t str;
	int i;

	/* Wait for input buffer to become available. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if ((str & LOM2_STATUS_IBF) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM2_CMD, reg);

	/* Wait until the microcontroller fills output buffer. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if (str & LOM2_STATUS_OBF)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	*val = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_DATA);
	return (0);
}

int
lom2_write(struct lom_softc *sc, uint8_t reg, uint8_t val)
{
	struct lom_cmd lc;
	int error;

	if (cold)
		return lom2_write_polled(sc, reg, val);

	lc.lc_cmd = reg | LOM_IDX_WRITE;
	lc.lc_data = val;
	lom2_queue_cmd(sc, &lc);

	error = tsleep(&lc, PZERO, "lom2wr", hz);
	if (error)
		lom_dequeue_cmd(sc, &lc);

	return (error);
}

int
lom2_write_polled(struct lom_softc *sc, uint8_t reg, uint8_t val)
{
	uint8_t str;
	int i;

	/* Wait for input buffer to become available. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if ((str & LOM2_STATUS_IBF) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	if (sc->sc_space == LOM_IDX_CMD_GENERIC && reg != LOM_IDX_CMD)
		reg |= LOM_IDX_WRITE;

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM2_CMD, reg);

	/* Wait until the microcontroller fills output buffer. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if (str & LOM2_STATUS_OBF)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_DATA);

	/* Wait for input buffer to become available. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if ((str & LOM2_STATUS_IBF) == 0)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, LOM2_DATA, val);

	/* Wait until the microcontroller fills output buffer. */
	for (i = 1000; i > 0; i--) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		delay(10);
		if (str & LOM2_STATUS_OBF)
			break;
	}
	if (i == 0)
		return (ETIMEDOUT);

	bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_DATA);

	/* If we switched spaces, remember the one we're in now. */
	if (reg == LOM_IDX_CMD)
		sc->sc_space = val;

	return (0);
}

void
lom2_queue_cmd(struct lom_softc *sc, struct lom_cmd *lc)
{
	uint8_t str;

	mtx_enter(&sc->sc_queue_mtx);
	TAILQ_INSERT_TAIL(&sc->sc_queue, lc, lc_next);
	if (sc->sc_state == LOM_STATE_IDLE) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		if ((str & LOM2_STATUS_IBF) == 0) {
			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
			    LOM2_CMD, lc->lc_cmd);
			sc->sc_state = LOM_STATE_DATA;
		}
	}
	mtx_leave(&sc->sc_queue_mtx);
}

int
lom2_intr(void *arg)
{
	struct lom_softc *sc = arg;
	struct lom_cmd *lc;
	uint8_t str, obr;

	mtx_enter(&sc->sc_queue_mtx);

	str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
	obr = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_DATA);

	lc = TAILQ_FIRST(&sc->sc_queue);
	if (lc == NULL) {
		mtx_leave(&sc->sc_queue_mtx);
		return (0);
	}

	if (lc->lc_cmd & LOM_IDX_WRITE) {
		bus_space_write_1(sc->sc_iot, sc->sc_ioh,
		    LOM2_DATA, lc->lc_data);
		lc->lc_cmd &= ~LOM_IDX_WRITE;
		mtx_leave(&sc->sc_queue_mtx);
		return (1);
	}

	KASSERT(sc->sc_state = LOM_STATE_DATA);
	lc->lc_data = obr;

	TAILQ_REMOVE(&sc->sc_queue, lc, lc_next);

	wakeup(lc);

	sc->sc_state = LOM_STATE_IDLE;

	if (!TAILQ_EMPTY(&sc->sc_queue)) {
		str = bus_space_read_1(sc->sc_iot, sc->sc_ioh, LOM2_STATUS);
		if ((str & LOM2_STATUS_IBF) == 0) {
			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
			    LOM2_CMD, lc->lc_cmd);
			sc->sc_state = LOM_STATE_DATA;
		}
	}

	mtx_leave(&sc->sc_queue_mtx);

	return (1);
}

int
lom_init_desc(struct lom_softc *sc)
{
	uint8_t val;
	int i, j, k;
	int error;

	/* LOMlite doesn't provide sensor descriptions. */
	if (sc->sc_type < LOM_LOMLITE2)
		return (0);

	/*
	 * Read temperature sensor names.
	 */
	error = lom_write(sc, LOM_IDX_CMD, LOM_IDX_CMD_TEMP);
	if (error)
		return (error);

	i = 0;
	j = 0;
	k = LOM_IDX4_TEMP_NAME_START;
	while (k <= LOM_IDX4_TEMP_NAME_END) {
		error = lom_read(sc, k++, &val);
		if (error)
			goto fail;

		if (val == 0xff)
			break;

		if (j < sizeof (sc->sc_temp[i].desc) - 1)
			sc->sc_temp[i].desc[j++] = val;

		if (val == '\0') {
			i++;
			j = 0;
			if (i < sc->sc_num_temp)
				continue;

			break;
		}
	}

	/*
	 * Read fan names.
	 */
	error = lom_write(sc, LOM_IDX_CMD, LOM_IDX_CMD_FAN);
	if (error)
		return (error);

	i = 0;
	j = 0;
	k = LOM_IDX5_FAN_NAME_START;
	while (k <= LOM_IDX5_FAN_NAME_END) {
		error = lom_read(sc, k++, &val);
		if (error)
			goto fail;

		if (val == 0xff)
			break;

		if (j < sizeof (sc->sc_fan[i].desc) - 1)
			sc->sc_fan[i].desc[j++] = val;

		if (val == '\0') {
			i++;
			j = 0;
			if (i < sc->sc_num_fan)
				continue;

			break;
		}
	}

fail:
	lom_write(sc, LOM_IDX_CMD, LOM_IDX_CMD_GENERIC);
	return (error);
}

void
lom_refresh(void *arg)
{
	struct lom_softc *sc = arg;
	uint8_t val;
	int i;

	for (i = 0; i < sc->sc_num_fan; i++) {
		if (lom_read(sc, LOM_IDX_FAN1 + i, &val)) {
			sc->sc_fan[i].flags |= SENSOR_FINVALID;
			continue;
		}

		sc->sc_fan[i].value = (60 * sc->sc_fan_cal[i] * val) / 100;
		if (val < sc->sc_fan_low[i])
			sc->sc_fan[i].status = SENSOR_S_CRIT;
		else
			sc->sc_fan[i].status = SENSOR_S_OK;
		sc->sc_fan[i].flags &= ~SENSOR_FINVALID;
	}

	for (i = 0; i < sc->sc_num_psu; i++) {
		if (lom_read(sc, LOM_IDX_PSU1 + i, &val) ||
		    !ISSET(val, LOM_PSU_PRESENT)) {
			sc->sc_psu[i].flags |= SENSOR_FINVALID;
			continue;
		}

		if (val & LOM_PSU_STANDBY) {
			sc->sc_psu[i].value = 0;
			sc->sc_psu[i].status = SENSOR_S_UNSPEC;
		} else {
			sc->sc_psu[i].value = 1;
			if (ISSET(val, LOM_PSU_INPUTA) &&
			    ISSET(val, LOM_PSU_INPUTB) &&
			    ISSET(val, LOM_PSU_OUTPUT))
				sc->sc_psu[i].status = SENSOR_S_OK;
			else
				sc->sc_psu[i].status = SENSOR_S_CRIT;
		}
		sc->sc_psu[i].flags &= ~SENSOR_FINVALID;
	}

	for (i = 0; i < sc->sc_num_temp; i++) {
		if (lom_read(sc, LOM_IDX_TEMP1 + i, &val)) {
			sc->sc_temp[i].flags |= SENSOR_FINVALID;
			continue;
		}

		sc->sc_temp[i].value = val * 1000000 + 273150000;
		sc->sc_temp[i].flags &= ~SENSOR_FINVALID;
	}

	/*
	 * If our hostname is set and differs from what's stored in
	 * the LOM, write the new hostname back to the LOM.  Note that
	 * we include the terminating NUL when writing the hostname
	 * back to the LOM, otherwise the LOM will print any trailing
	 * garbage.
	 */
	if (hostnamelen > 0 &&
	    strncmp(sc->sc_hostname, hostname, sizeof(hostname)) != 0) {
		if (sc->sc_type < LOM_LOMLITE2)
			lom1_write_hostname(sc);
		else
			lom2_write_hostname(sc);
		strlcpy(sc->sc_hostname, hostname, sizeof(hostname));
	}
}

void
lom1_write_hostname(struct lom_softc *sc)
{
	char name[(LOM1_IDX_HOSTNAME12 - LOM1_IDX_HOSTNAME1 + 1) + 1];
	char *p;
	int i;

	/*
	 * LOMlite generally doesn't have enough space to store the
	 * fully qualified hostname.  If the hostname is too long,
	 * strip off the domain name.
	 */
	strlcpy(name, hostname, sizeof(name));
	if (hostnamelen >= sizeof(name)) {
		p = strchr(name, '.');
		if (p)
			*p = '\0';
	}

	for (i = 0; i < strlen(name) + 1; i++)
		if (lom_write(sc, LOM1_IDX_HOSTNAME1 + i, name[i]))
			break;
}

void
lom2_write_hostname(struct lom_softc *sc)
{
	int i;

	lom_write(sc, LOM2_IDX_HOSTNAMELEN, hostnamelen + 1);
	for (i = 0; i < hostnamelen + 1; i++)
		lom_write(sc, LOM2_IDX_HOSTNAME, hostname[i]);
}

void
lom_wdog_pat(void *arg)
{
	struct lom_softc *sc = arg;

	/* Pat the dog. */
	sc->sc_wdog_pat.lc_cmd = LOM_IDX_WDOG_CTL | LOM_IDX_WRITE;
	sc->sc_wdog_pat.lc_data = sc->sc_wdog_ctl;
	lom_queue_cmd(sc, &sc->sc_wdog_pat);

	timeout_add_sec(&sc->sc_wdog_to, LOM_WDOG_TIME_MAX / 2);
}

int
lom_wdog_cb(void *arg, int period)
{
	struct lom_softc *sc = arg;

	if (period > LOM_WDOG_TIME_MAX)
		period = LOM_WDOG_TIME_MAX;
	else if (period < 0)
		period = 0;

	if (period == 0) {
		if (sc->sc_wdog_period != 0) {
			/* Stop watchdog from resetting the machine. */
			sc->sc_wdog_ctl &= ~LOM_WDOG_RESET;
			lom_write(sc, LOM_IDX_WDOG_CTL, sc->sc_wdog_ctl);

			lom_write(sc, LOM_IDX_WDOG_TIME, LOM_WDOG_TIME_MAX);
			timeout_add_sec(&sc->sc_wdog_to, LOM_WDOG_TIME_MAX / 2);
		}
	} else {
		if (sc->sc_wdog_period != period) {
			/* Set new timeout. */
			lom_write(sc, LOM_IDX_WDOG_TIME, period);
		}
		if (sc->sc_wdog_period == 0) {
			/* Make watchdog reset the machine. */
			sc->sc_wdog_ctl |= LOM_WDOG_RESET;
			lom_write(sc, LOM_IDX_WDOG_CTL, sc->sc_wdog_ctl);

			timeout_del(&sc->sc_wdog_to);
		} else {
			/* Pat the dog. */
			lom_dequeue_cmd(sc, &sc->sc_wdog_pat);
			sc->sc_wdog_pat.lc_cmd = LOM_IDX_WDOG_CTL | LOM_IDX_WRITE;
			sc->sc_wdog_pat.lc_data = sc->sc_wdog_ctl;
			lom_queue_cmd(sc, &sc->sc_wdog_pat);
		}
	}
	sc->sc_wdog_period = period;

	return (period);
}

void
lom_shutdown(void *arg)
{
	struct lom_softc *sc = arg;

	sc->sc_wdog_ctl &= ~LOM_WDOG_ENABLE;
	lom_write(sc, LOM_IDX_WDOG_CTL, sc->sc_wdog_ctl);
}
