/*
 *  w83627hf.c       --  w83627hf.c program module
 *
 *  Copyright 2004 by Floyd Davidson, floyd@barrow.com
 *  File created:  Wed Sep  2 04:53:43 2004
 *  Last updated:  Wed Oct 20 05:40:32 2004
 *
 *  This program is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation;
 *  either version 2, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for details.
 *
 *  $Id: w83627hf.c,v 1.1.0.5 2004/10/20 13:45:08 floyd Exp floyd $
 *
 ************************************************************************/
/*
 * A program to initialize Tyan Thunder K7 S2462 motherboard
 * W83627HF sensors for use by lm_sensors under Linux.
 *
 * See:
 * http://www2.lm-sensors.nu/~lm78/readticket.cgi?ticket=861
 *
 * The Tyan S2462 and similar Tyan motherboards have both a
 * Winbond W83782D and a Winbond W83627HF chip.  The W83782D is
 * a dedicated hardware status monitoring chip which can measure
 * voltages, temperatures, fan speeds, and provide fan speed
 * control.  The W83627HF is a "Super IO" chip that in addition
 * to providing various IO interfaces for floppy drives, serial
 * ports, parallel ports and so on, also includes essentially
 * the entire functionality of a W83782D.  Each chip can monitor
 * 9 voltages, 3 fans and 3 temperature probes (plus a few other
 * odd things).
 *
 * Unfortunately the Hardware Monitor in the W83627HF chip is
 * not enabled during a normal boot process, and therefore only
 * the W83782D is available to lm_sensors.
 *
 * With the W83627HF Hardware Monitor enabled and initialized
 * the following status measurements are all available:
 *
 *   FUNCTION        W83782D            W83627HF
 *
 *   IN0             *                  VCore 1
 *   IN1             *                  VCore 2
 *   IN2             AGP V              + 3.3 V
 *   IN3             + 5.0 V            *
 *   IN4             DDR V              +12.0 V
 *   IN5             *                  -12.0 V
 *   IN6             3 VSB              *
 *   IN7             *                  *
 *   IN8             VBAT               *
 *
 *   FAN1            CPU1 Fan           Chassis2 Fan
 *   FAN2            CPU2 Fan           Chassis5 Fan
 *   FAN3            Chassis1 Fan       Chassis6 Fan
 *
 *   TEMP1           VRM2               VRM1
 *   TEMP2           CPU1               AGP
 *   TEMP3           CPU2               DDR
 *
 * Note that voltage inputs marked with a '*' do appear to have
 * voltages attached, and they might be the same as some on the
 * other chip, but the above are the ones that Tyan has
 * documented (go to www.tyan.com for a sensors.conf file).
 *
 * It is clear that the W83627HF chip monitors several voltages
 * and temperature that are significant, plus the extra fan
 * monitors may be optionally useful.
 *
 * However, none of the W83627HF monitor functions are made
 * available unless the chip is initialized properly, and that
 * is done during the boot process only if the system is taken
 * to the BIOS SETUP menu and Hardware Monitor screen is
 * selected!  That initializes the W83627HF, and if the system
 * is then warm booted (by exiting the SETUP and not powering
 * down), the W83627HF will be available to lm_sensors.
 *
 * This program initializes (and attempts to provide a useful
 * default configuration for) the W83627HF to make it available
 * to lm_sensors when the system is booted without engaging the
 * SETUP program.
 *
 * This program can be executed by the system startup scripts,
 * either before or after the modules are loaded, and then
 * followed by running "sensors -s" for local configuration
 * changes.
 *
 * WARNING  ** WARNING  ** WARNING  ** WARNING  ** WARNING
 *
 * It is not advisable to have this program or the "sensors -s"
 * command executed prior to the system going to multi-user
 * mode.  Misconfiguration can freeze the system, and access via
 * another means is necessary to allow a full system boot.  If
 * these commands are run by the multiuser mode startup scripts
 * a misconfiguration can be corrected by booting to single user
 * mode.  Running the commands in the single user mode init
 * scripts would require a rescue floppy or CDROM to recover
 * from an error!
 *
 * And of course, you run this program at your own risk!
 */

#define _POSIX_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <getopt.h>
#include <sys/io.h>

/* defines specific to the S2462 motherboard */

/* LPC port for W83627HF (0x2e or 0x4e possible, Tyan uses 0x2e) */
#define EFER  (lpc_efer)/* Extended Functions Enable Register */
#define EFIR  EFER      /* Extended Function Index Register   */
#define EFDR  EFIR + 1  /* Extended Function Data Register    */

#define HWM_INDEX       0x0B  /* index for Hardware Monitor Select */

/* W83627HF LPC accessible configuration registers */
#define LDEVNO_REG	0x07
#define DEVID0_REG	0x20
#define DEVREV_REG	0x21
#define DEVEXT_REG	0x27
#define GBLCFG_REG	0x2b
#define ENABLE_REG	0x30
#define ADRMSB_REG	0x60
#define ADRLSB_REG	0x61
#define CREG70_REG      0x70
#define CREGF0_REG      0xf0

#define ACCESS1         0x87
#define ACCESS2         0x87
#define EXIT_CFG        0xaa

#define W83627HF	0x52
#define W83627THF	0x83

/* Hardware Monitor Addressing */
/*
 * Choice of this address is somewhat arbitrary.  It is used by
 * this program to access the W83627HF via the LPC bus.  The
 * lm_sensors programs can access the W83627HF either by loading
 * the i2c-isa and w83627hf modules and using the address set
 * here; or the lm_sensors programs can use the w837821d module
 * with the right options and access both the W83627HF Hardware
 * Monitor sensors and the W83782D sensors via the AMD756
 * (i2c-amd756 module).  (The last method is recommended.)
 *
 * I've tried both 0x0c00 and 0x0c10 successfully.
 */
#define I2C_HDWM      0x2c /* I2C addr for W83627HF Hardware Monitor */
#define LPC_EFER    0x002e /* LPC addr for W83627HF EFER register    */
#define LPC_HDWM    0x0c00 /* LPC addr for W83627HF Hardware Monitor */
#define LPC_W83782D 0x0290 /* LPC addr for W83782D  sensor chip      */
		
#define HWM_MSB	(((lpc_hdwm) & 0xff00) >> 8) /* Base MSB     */
#define HWM_LSB	(((lpc_hdwm) & 0x00ff))      /* Base LSB     */
#define HWM_CMD	(lpc_hdwm + 5)               /* Command Reg  */
#define HWM_DAT	(lpc_hdwm + 6)               /* Data Reg     */

void write_hwm(int, int);
void write_cr(int, int);
void write_lpc(int, int);
void write_lpcw(int, int);
int  read_hwm(int);
int  read_cr(int);
int  read_lpc(int);
int  read_lpcw(int);
void setiopl(int);
void sig_handler(int);
void dump_chip(void);
void parse_cmds(int, char **);
void cleanup(void);

#define CMDOPTS "DE:I:L:abdirv::"

/* option flags */
int dumpaft;
int dumpbef;
int lpczero;
int i2czero;
int initreg;
int verbose;

int lpc_enabled;           /* flag access via LPC is enabled */

/* set program defaults, which command line options can override */
int i2c_hwdm = I2C_HDWM;   /* W83267HF Hardware Monitor I2C bus address */
int lpc_hdwm = LPC_HDWM;   /* W83267HF Hardware Monitor LPC bus address */
int lpc_efer = LPC_EFER;   /* W83267HF EFER LPC address                 */

const struct option options[] = {
  { "after",      no_argument,       NULL, 'a'},
  { "before",     no_argument,       NULL, 'b'},
  { "eferaddr",   required_argument, NULL, 'E'},
  { "ipcaddr",    required_argument, NULL, 'I'},
  { "lpcaddr",    required_argument, NULL, 'L'},
  { "disablei2c", no_argument,       NULL, 'D'},
  { "disablelpc", no_argument,       NULL, 'd'},
  { "init",       no_argument,       NULL, 'i'},
  { "verbose",    required_argument, NULL, 'v'},
  { "help",       no_argument,       NULL, 'h'},
};

const struct option *optsl;

int
main(int argc, char **argv)
{
  int              bytedata;
  int              chipid;
  int              x;
  struct sigaction sa;

  parse_cmds(argc, argv);

  sa.sa_handler  = sig_handler;
  sa.sa_flags    = 0;
  sa.sa_restorer = NULL;
  sigemptyset(&sa.sa_mask);

  setiopl(3);

  /* Enter MB PnP Mode to access W83627HF Super IO Chip */
  outb(ACCESS1, EFER);
  outb(ACCESS2, EFER);

  /* try to arrange a graceful exit if necessary */
  for (x = 0; x < _NSIG; ++x) {
    sigaction(x, &sa, NULL);
  }

  /* identify device and revision */
  chipid = read_cr(DEVID0_REG); /* Configuration Register 20:  Device ID No. */
  switch (chipid) {
  case W83627HF:
    if (verbose > 1) {
      printf("Chip identifies as a W83627HF Rev. ");
      bytedata = read_cr(DEVREV_REG); /* Configuration Register 20:  Device Revision */
      switch (bytedata) {
      case 0x17:
	printf("G");
	break;
      case 0x3a:
	printf("J");
	break;
      default:
	printf("Unknown");
      }
      printf("\n");
    }
    break;
  case W83627THF:
    if (verbose) {
      printf("Chip identifies as a W83627THF Rev. ");
      bytedata = read_cr(DEVREV_REG); /* Configuration Register 20:  Device Revision */
      switch (bytedata) {
      case 0x83:
	printf("1");
	break;
      default:
	printf("Unknown");
	break;
      }
      printf("\n");
      fprintf(stderr, "The W83627THF detected is not supported.\n");
    }
    break;
  default:
    fprintf(stderr, "Chip does not identify as a W83627HF.\n");
    exit(1);
  }

  if (verbose > 4) {
    /* print Hardware Monitor Configuration Registers */
    printf("HW Monitor  CR30  CR60  CR61  CR70  CRF0\n");
    printf("             %2.2x", read_hwm(ENABLE_REG));
    printf("    %2.2x", read_hwm(ADRMSB_REG));
    printf("    %2.2x", read_hwm(ADRLSB_REG));
    printf("    %2.2x", read_hwm(CREG70_REG));
    printf("    %2.2x", read_hwm(CREGF0_REG));

    /* Global Configuration Register 2b */
    printf("\nGlobal Configuration Register CR2b:  %2.2x\n", read_cr(GBLCFG_REG));
  }

  /*
   * Connect to the I2C bus hardware:
   *   Serial Clock Line is connected to pin 92
   *   Serial Data Line  is connncted to pin 91
   */
  if (chipid == W83627HF) {
    /* GPIO multiplexed pin selection register 2.              */
    /* default is 0xC0, see page 78 of data sheet for W83627HF */
    /* 0x80 sets pin 92s to GP21 instead of SCL */
    /* 0x40 sets pin 91s to GP22 instead of SDA */
    write_cr(GBLCFG_REG, 00);
    if (verbose > 4) {
      bytedata = read_cr(GBLCFG_REG);
      if (bytedata & 0xc0) {
	if (verbose > 5) {
	  printf("Hardware Connection to I2C bus is disabled.\n");
	  if (bytedata & 0x80) {
	    printf("  The Serial Clock Lead is not enabled for on 92.\n");
	  }
	  if (bytedata & 0x40) {
	    printf("  The Serial Data Lead is not enabled for on 91.\n");
	  }
	}
      } else {
	if (verbose > 5) {
	  printf("Hardware Connection to I2C bus is enabled.\n");
	  if (~bytedata & 0x80) {
	    printf("  The Serial Clock Lead is enabled on pin 92.\n");
	  }
	  if (~bytedata & 0x40) {
	    printf("  The Serial Data Lead is enabled on pin 91.\n");
	  }
	}
      }
    }
  }

  /* Enable Hareware Monitor */
  write_hwm(ENABLE_REG, 0x01);    /* 1 enabled, 0 disabled */

  /* Set Hardware Monitor's Base Address to 0x0C00 for LPC Bus access. */
  /* Note that the Low Pin Count (LPC) bus replaces the ISA bus, and   */
  /* is compatible with the ISA bus for software expecting to use the  */
  /* ISA bus.  This access could be disabled after we use it, but by   */
  /* leaving it set we allow access via the w83627hf kernel module,    */
  /* which uses the "ISA bus" to access the W83627HF Super I/O chip.   */

  write_hwm(ADRMSB_REG, HWM_MSB);
  write_hwm(ADRLSB_REG, HWM_LSB);

  if (read_hwm(ADRMSB_REG) == HWM_MSB && read_hwm(ADRLSB_REG) == HWM_LSB) {
    lpc_enabled = 1;
  }
  
  if (verbose > 2) {
    printf("Hardware Monitor Base Address:  0x%2.2x%2.2x\n",
	   read_hwm(ADRMSB_REG), read_hwm(ADRLSB_REG));
  }
  
  if (verbose > 4) {
    printf("Hardware Monitor is:  %s\n",
	   (read_hwm(ENABLE_REG) & 0x01) == 1 ? "Enabled" : "Disabled");
  }
  
  /* see page 39 of W83627HF data sheet for address at HWM_CMD/HWM_DAT */
  /* Set I2C address for Hardware Monitor */
  if (i2czero) {
    i2c_hwdm = 0;
  }

  write_lpc(0x48, i2c_hwdm);
  bytedata = read_lpc(0x48);

  if (verbose) {
    if (bytedata) {
      printf("I2C bus address:  0x%2.2x\n", bytedata);
    } else {
      printf("W83627HF Hardware Monitor access via the I2C bus is disabled\n");
    }
  }

  write_lpc(0x4e, 0x81);      /* select high bit value for 0x4f and bank 1 */
  if (verbose > 4) {
    bytedata = read_lpc(0x4f);
    printf("0x4f (high bit):  0x%2.2x\n", bytedata);
  }

  /*
   * For unknown reasons, a sane value must be written to register
   * address 0x4A in Bank 0, or initialization is incomplete and
   * attempts to access the W83627HF chip via the i2c bus will
   * freeze the entire system.  Register 0x4A enables temperature
   * inputs 2 and 3, and sets their i2c bus addresses.
   * 
   * This is somewhat difficult to experiment with, because the
   * W83627HF chip must be powered down (hence the entire system
   * must be power cycled) to reset in order to test the effect
   * of any changes.
   *
   */

  /*  BITS    VALUE   DESCRIPTION
   *  =====   =====   ===========
   *  Bit 0     0 -
   *  Bit 1     1  }  t2 address is 01001 010 = 0x4A
   *  Bit 2     0 -
   *  Bit 3     0     0 == t2 enabled
   *  Bit 4     1 -
   *  Bit 5     1  }  t3 address is 01001 011 = 0x4B
   *  Bit 7     0 -
   *  Bit 8     0     0 == t3 enabled
   *
   *  See page 48 of W83627HF/F data sheet
   */

  write_lpc(0x4e, 0x0);       /* select low bit value for 0x4f and bank 0 */
  write_lpc(0x4a, 0x32);
  
  if (dumpbef || verbose > 8) {
    if (!initreg && verbose < 9) {
      printf("\nRegister Dump of Hardware Monitor.\n");
    } else {
      printf("\nRegister Dump before the Hardware Monitor is Initialized.\n");
    }

    /* don't allow two register dumps if verbose and not initreg */
    if (initreg || verbose < 9) {
      dump_chip();
    }
  }

  if (initreg) {
    /* init registers to sane defaults */
    write_lpc(0x4e, 0);        /* select Bank 0 */
    write_lpc(0x5a, 0x0);
    write_lpc(0x5b, 0x0);
    write_lpc(0x5c, 0x80);
    write_lpc(0x47, 0xf0);
    write_lpc(0x4A, 0x32);
    write_lpc(0x4B, 0xc0);
    write_lpc(0x4C, 0x18);

    write_lpc(0x4e, 1);        /* select Bank 1 */
    write_lpc(0x51, 0x80);
    write_lpc(0x53, 0x2f);
    write_lpc(0x55, 0x34);
    write_lpc(0x56, 0x00);

    write_lpc(0x4e, 2);        /* select Bank 2 */
    write_lpc(0x50, 0x4d);
    write_lpc(0x51, 0x00);
    write_lpc(0x53, 0x2f);
    write_lpc(0x55, 0x34);
    write_lpc(0x56, 0x00);

    write_lpc(0x4e, 4);        /* select Bank 4 */
    write_lpc(0x59, 0x78);
    write_lpc(0x5A, 0x2e);

#if 0
    /* Apparently there are differences in motherboards... */
    /* I have two S2462 boards, and the early version does */
    /* not control the CPU fans, but can control at least  */
    /* some of the chassis fans.  The later version of the */
    /* same motherboard can control the CPU fans. However, */
    /* note that fan2_pwm does not necessarily control the */
    /* the fan reported by fan2_input.                     */
    
    /* diddle with pwm */
    write_lpc(0x4e, 0);     /* select Bank 0               */
    write_lpc(0x5a, 0xc0);
    write_lpc(0x5b, 0xc0);  /* 3/4 speed                   */
    write_lpc(0x5c, 0x2a);  /* 12 KHz clocks, enable pwm2  */
    write_lpc(0x4e, 4);     /* select Bank 4               */
    write_lpc(0x5c, 0x22);  /* 12 KHz clocks               */
#endif

    if (verbose) {
      printf("Hardware Monitor Initialized to default values.\n");
    }
  }

  if (dumpaft || verbose > 7) {
    if (!initreg) {
      if (!dumpbef) {
	printf("\nRegister Dump of Hardware Monitor.\n");
      }
    } else {
      printf("\nRegister Dump After the Hardware Monitor was Initialized.\n");
    }
    /* if we are not doing initreg, only allow one register dump */
    if (!dumpbef || initreg) {
      dump_chip();  
    }
  }

  cleanup();  /* close ports, etc. */

  if (lpczero && verbose > 2) {
    printf("W83627HF Hardware Monitor access via the LPC bus is disabled\n");
  }

  return 0;
}


/*
 * parse command line options
 */
void
parse_cmds(int argc, char **argv)
{
  int  ch;
  char *endptr = NULL;

  while ((ch = getopt_long(argc, argv, CMDOPTS, options, NULL)) != EOF) {

    switch (ch) {
    case 'a':
      dumpaft = 1;
      break;

    case 'b':
      dumpbef = 1;
      break;

    case 'D':
      i2czero = 1;
      break;

    case 'd':
      lpczero = 1;
      break;

    case 'E':
      if (optarg && *optarg) {
	lpc_efer = strtol(optarg, &endptr, 0);
	if (*endptr) {
	  fprintf(stderr, "FATAL ERROR:  Invalid LPC EFER Address:  %s\n", optarg);
	  exit(3);
	}
      }
      break;

    case 'L':
      if (optarg && *optarg) {
	lpc_hdwm = strtol(optarg, &endptr, 0);
	if (*endptr) {
	  fprintf(stderr, "FATAL ERROR:  Invalid LPC Hardware Monitor Address:  %s\n", optarg);
	  exit(4);
	}
      }
      break;

    case 'I':
      if (optarg && *optarg) {
	i2c_hwdm = strtol(optarg, &endptr, 0);
	if (*endptr) {
	  fprintf(stderr, "FATAL ERROR:  Invalid I2C Hardware Monitor Address:  %s\n", optarg);
	  exit(3);
	}
      }
      break;

    case 'i':
      initreg = 1;
      break;

    case 'v':
      verbose = 1;
      if (optarg && *optarg && atoi(optarg)) {
	verbose = atoi(optarg);
      }
      break;

    case '?':
    case 'h':
    default:
      fprintf(stderr, "\n");
      fprintf(stderr, "Valid Options:\n");
      fprintf(stderr, "  -E addr, --lpcefer=addr   Use LPC bus address \"addr\" for EFER\n");
      fprintf(stderr, "  -I addr, --i2caddr=addr   Set I2C bus address to \"addr\"\n");
      fprintf(stderr, "  -L addr, --lpcaddr=addr   Set LPC bus address to \"addr\"\n");
      fprintf(stderr, "  -D       --disablei2c     Disable I2C access to the W83627HF.\n");
      fprintf(stderr, "  -d       --disablelpc     Disable LPC access to the W83627HF.\n");
      fprintf(stderr, "  -a,      --after          Dump registers before initializing them.\n");
      fprintf(stderr, "  -b,      --before         Dump registers after initializing them.\n");
      fprintf(stderr, "  -h,      --help           Show this help screen\n");
      fprintf(stderr, "  -i,      --init           Init registers to sane values\n");
      fprintf(stderr, "  -v,      --verbose        Provide noise\n");
      fprintf(stderr, "  -v n,    --verbose=n      Increase the noise level, 1 to 9\n");
      fprintf(stderr, "\n\n");
      exit(EXIT_FAILURE);
    }
  }
}

/*
 * dump all registers on the chip
 */
void
dump_chip(void)
{
  int i, j = 0, k = 0;
  int bytedata;

  printf("\nHardware Monitor Registers\n");
  for (i = 0; i < 0x50; ++i) {
    if (j == 4 || j == 8 || j == 12) {
      printf(" ");
    }

    if (j == 0) {
      printf(" 0x%2.2x: ", i);
    }

    if (j == 16) {
      printf(" :0x%2.2x\n 0x%2.2x: ", i-1, i);
      j = 0;
    }
    printf("%2.2x ", read_lpc(i));
    ++j;
  }
  printf(" :0x%2.2x\n\n", i-1);

  write_lpc(0x4e, 0x80);   
  bytedata = read_lpc(0x4f);
  printf("0x4f (high bit):  0x%2.2x\n", bytedata);
  write_lpc(0x4e, 0x0);
  
  for (k = 0; k < 5; ++k) {
    printf("\nBank %d: Hardware Monitor Registers\n", k);
    write_lpc(0x4e, k);
    j = 0;
    for (i = 0x50; i < 0x60; ++i) {
      if (j == 4 || j == 8 || j == 12) {
	printf(" ");
      }

      if (j == 0) {
	printf(" 0x%2.2x: ", i);
      }

      if (j == 16) {
	printf(" :0x%2.2x\n 0x%2.2x: ", i-1, i);
	j = 0;
      }
      printf("%2.2x ", read_lpc(i));
      ++j;
    }
    printf(" :0x%2.2x\n", i-1);
  }
}


/*
 * Set I/O privilege level
 */
void setiopl(int level)
{
  if (iopl(level)) {
    switch (errno) {
    case EINVAL:
      perror("Invalid level specified");
      break;
    case EPERM:
      perror("You are not root");
      break;
    default:
      perror("iopl error");
      break;
    }
    exit(1);
  }
}

/*
 * write a data byte to a register
 *   of the W83627HF Hardware Monitor Device (0x0B)
 */

void write_hwm(int reg, int data)
{
  outb(LDEVNO_REG, EFIR);  /* Logical Device Number Register 7 */
  outb(HWM_INDEX, EFDR);   /* Select Device 0x0B (HW Monitor)  */
  outb(reg,  EFIR);        /* Select register                  */
  outb(data, EFDR);        /* Set to data value                */
}

/*
 * read a data byte from a register
 *   of the W83627HF Hardware Monitor Device (0x0B)
 */

int read_hwm(int reg)
{
  write_cr(LDEVNO_REG, HWM_INDEX);  /* Log. Dev. Reg, Device 0x0b (HW Monitor */
  return read_cr(reg);  /* get data value */
}

/*
 * write a data byte to a W83627HF register reg
 */

void write_cr(int reg, int data)
{
  outb(reg,  EFIR);  /* set register index */
  outb(data, EFDR);  /* set data value     */
}

/*
 * read a data byte from a W83627HF register reg
 */

int read_cr(int reg)
{
  outb(reg,  EFIR);  /* set register index */
  return inb(EFDR);  /* get data value     */
}

/*
 *  write a data byte to the W83627HF Hardware Monitor
 *  via the LPC bus.
 */
void write_lpc(int reg, int data)
{
  outb(reg,  HWM_CMD);  /* set register index */
  outb(data, HWM_DAT);  /* set data value     */
}

/*
 *  read a data byte from the W83782D Hardware Monitor
 *  via the LPC bus.
 */
int read_lpcw(int reg)
{
  outb(reg,  0x0295);  /* set register index */
  return inb(0x296);  /* get data value     */
}


/*
 *  write a data byte to the W83782d Hardware Monitor
 *  via the LPC bus.
 */
void write_lpcw(int reg, int data)
{
  outb(reg,  0x0295);  /* set register index */
  outb(data, 0x0296);  /* set data value     */
}

/*
 *  read a data byte from the W83627HF Hardware Monitor
 *  via the LPC bus.
 */
int read_lpc(int reg)
{
  outb(reg,  HWM_CMD);  /* set register index */
  return inb(HWM_DAT);  /* get data value     */
}


/*
 * Die gracefully, we hope
 */
void
sig_handler(int sig)
{
  sig = 0;             /* satisfy gcc               */

  cleanup();
  exit(1);             /* Get the Hell out of Dodge */
}


void
cleanup(void)
{

  if (lpczero && lpc_enabled) {
    /* disable LPC access to the W83627HF */
    write_hwm(ADRMSB_REG, 0);
    write_hwm(ADRLSB_REG, 0);
  }
  
  outb(EXIT_CFG, EFER);  /* Exit configuration mode   */
  setiopl(0);               /* restore privilege level   */
}