/* * 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 #include #include #include #include #include #include #include /* 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 */ }