winb/winb.c

687 lines
16 KiB
C

/*
* winb kernel module
* (C)2006 Dan Ponte. Under the 3-clause BSD license.
* Portions taken from mbmon. (C)1999-2004 Yoshifumi R. Shimizu.
* Builds under FreeBSD 6.1 and others
*/
#include <sys/types.h>
#include <sys/module.h>
#include <sys/systm.h> /* uprintf */
#include <sys/errno.h>
#include <sys/param.h> /* defines used in kernel.h */
#include <sys/kernel.h> /* types used in module initialization */
#include <sys/bus.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <sys/time.h>
#include <sys/sysctl.h>
#include <machine/cpufunc.h>
#include <isa/isavar.h>
#include "isa_if.h"
/*
* Load handler that deals with the loading and unloading of a KLD.
*/
#define IOP_ADDR 0x290
#define INDEX_REG_PORT (IOP_ADDR + 0x05)
#define DATA_PORT (IOP_ADDR + 0x06)
#define WAIT outb(0xEB,0x00)
#define LM75_ADDR_START 0x90 /* 0x90-0x9E (0x48-0x4F) */
#define LM75_ADDR_END 0x9E
#define WINBD_ADDR_START 0x50 /* 0x50-0x5E (0x28-0x2F) */
#define WINBD_ADDR_END 0x5E
#define ASUSM_ADDR_FIX 0xEE /* ASUS Mozart-2, 0xEE (0x77) only */
#define WINBD_CONFIG 0x40
#define WINBD_SMBADDR 0x48
#define WINBD_DEVCID 0x49
#define WINBD_CHIPID 0x58
#define WINBD_VENDEX 0x4E
#define WINBD_VENDID 0x4F
#define ANADM_VENDID 0x3E
/* temp nr=0,1,2; volt nr=0,...6; fan nr=0,1,2 */
#define WINBD_TEMP0 0x27
#define ASUSB_TEMP4 0x17
#define ASUSM_TEMP2 0x13
#define WINBD_TEMPADDR 0x4A
#define WINBD_VOLT(nr) (0x20 + (nr))
#define WINBD_FAN(nr) (0x28 + (nr))
#define WINBD_FANDIV 0x47
#define WINBD_REGPIN 0x4B
#define ASUSM_FANDIV 0xA1
#define ANADM_TEMPCFG 0x4B
#define WINB_INT_STAT2 0x42
#define WINBD_DIOSEL 0x59
#define WINBD_VMCTRL 0x5D
#define WINBD_chkRegNum 8
static int probed = 0;
/* Register checked for probing */
static int chkReg[] = {
0x40, 0x41, 0x42, 0x43,
0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4A, 0x4B,
0x4C, 0x4D, 0x4E, 0x4F,
0x56, 0x58, 0x59, 0x5D,
0x3E, 0x13, 0x17, 0xA1,
0x20, 0x22, 0x23, 0x24,
0x27, 0x29, 0x2A, 0x2B,
-1 };
enum winbond_chips {
NOSENSER,
W83781D,
W83782D,
W83783S,
W83791D,
W83627HF,
W83697HF,
WBUNKNOWN,
AS99127F,
ASB100,
ASM58,
LM78,
LM79,
ADM9240,
UNKNOWN
};
struct winb_softc {
enum winbond_chips wbdchipid;
enum winbond_chips wbdlmid;
int temp1_flag;
int temp2_flag;
struct sysctl_ctx_list sysctl_ctx;
struct sysctl_oid *winbroot;
struct {
struct sysctl_oid *temp1, *temp2, *temp3;
struct sysctl_oid *fan1, *fan2, *fan3;
struct sysctl_oid *vc0, *vc1, *v33;
struct sysctl_oid *v50p, *v50n, *v12p, *v12n;
struct sysctl_oid *chassis;
struct sysctl_oid *f1duty, *f2duty;
struct sysctl_oid *pwrled;
} oids;
};
static int Read(int addr)
{
int ret;
outb(INDEX_REG_PORT, addr); WAIT;
ret = inb(DATA_PORT); WAIT;
return (ret & 0xFF);
}
static void Write(int addr, int value)
{
outb(INDEX_REG_PORT, addr); WAIT;
outb(DATA_PORT, value); WAIT;
}
static int ReadTemp1(void)
{
int ret;
Write(0x4E, 0x01); /* changing to bank 1 */
ret = Read(0x50) | (Read(0x51) << 8);
Write(0x4E, 0x00); /* returning to bank 0 */
return ret;
}
static int ReadTemp2(void)
{
int ret;
Write(0x4E, 0x02); /* changing to bank 2 */
ret = Read(0x50) | (Read(0x51) << 8);
Write(0x4E, 0x00); /* returning to bank 0 */
return ret;
}
static int chkReg_Probe(int Reg[])
{
int i, n, r, ret = 0;
for(i = 0; (r = Reg[i]) != -1; i++) {
n = Read(r);
if(n != 0xFF)
++ret;
}
return ret;
}
/*
* \retval 0xFFFF no sensor
* \retval other temperature
* no = 0,1,2
*/
#define FLOATIT
#ifdef FLOATIT
static int winbond_temp(int no, struct winb_softc *wbc)
{
int n = 0;
if (no < 0 || 2 < no)
return 0xFFFF;
if (no == 2 &&
(wbc->wbdchipid == W83783S || wbc->wbdchipid == W83697HF || wbc->wbdchipid == ASM58))
return 0xFFFF;
if (no == 0) {
n = Read(WINBD_TEMP0);
return n;
} else if (no == 1) {
if (!wbc->temp1_flag)
n = ReadTemp1();
#ifdef SYRS
} else if (no == 2 && !wbc->temp2_flag) {
#else
} else if (no == 2) {
if (wbc->wbdchipid == ASB100) {
if (!wbc->temp1_flag)
n = ReadTemp1();
} else if (!wbc->temp2_flag)
#endif
n = ReadTemp2();
}
if ((n & 0xFF) >= 0x80)
n = 0;
return n;
}
static int winbond_chassis(device_t dev)
{
int n;
Write(0x4E, 0x04); /* changing to bank 4 */
n = Read(0x5A); /* RT hardware status II */
if(n & 0x10 /* chassis bit */) {
return 1;
} else {
return 0;
}
/* NOTREACHED */
}
static void winbond_setduty(device_t dev, int fan2 /* if true, fan2 set and not fan1 */, int duty_cycle /* byte */)
{
Write(0x4E, 0x00); /* bank 0 */
Write(0x5A + (fan2 ? 1 : 0), duty_cycle);
}
static int winbond_getduty(device_t dev, int fan2)
{
Write(0x4E, 0x00); /* bank 0 */
return Read(0x5A + (fan2 ? 1 : 0));
}
static void winbond_clearchas(void)
{
int oldval;
outb(0x2E, 0x87);
outb(0x2E, 0x87); /* double write to enter "extended function mode" */
outb(0x2E, 0x07); /* point to LD# reg */
outb(0x2F, 0xA); /* select device number A */
outb(0x2E, 0xE6); /* select CRE6 */
oldval = inb(0x2F); /* get old value */
outb(0x2E, 0xE6); /* select CRE6 */
outb(0x2F, oldval | 0x40 /* status clear */); /* update CRE6 with whatever */
outb(0x2E, 0xE6); /* select CRE6 */
outb(0x2F, oldval); /* reclear that bit */
outb(0x2E, 0xAA); /* write AAh to exit extended mode */
}
static int winbond_getpwrled(device_t dev)
{
int val;
outb(0x2E, 0x87);
outb(0x2E, 0x87); /* double write to enter "extended function mode" */
outb(0x2E, 0x07); /* point to LD# reg */
outb(0x2F, 0x8); /* select device number 8 */
outb(0x2E, 0xF5); /* select CRE6 */
val = inb(0x2F); /* get old value */
outb(0x2E, 0xAA); /* write AAh to exit extended mode */
val >>= 6;
return val;
}
static void winbond_setpwrled(device_t dev, int nv)
{
int val, tor;
tor = nv & 0x3; /* grab first two bits */
tor <<= 6;
outb(0x2E, 0x87);
outb(0x2E, 0x87); /* double write to enter "extended function mode" */
outb(0x2E, 0x07); /* point to LD# reg */
outb(0x2F, 0x8); /* select device number 8 */
outb(0x2E, 0xF5); /* select CRF5 */
val = inb(0x2F); /* get old value */
outb(0x2E, 0xF5); /* select CRF5 */
outb(0x2F, (val & ~(0x3 << 6)) | tor); /* update CRE6 with whatever */
outb(0x2E, 0xAA); /* write AAh to exit extended mode */
}
/*
* \retval 0x0000FFFF no sensor
* no = 0,1,2,...,6
*/
static int winbond_volt(int no, struct winb_softc *wbc)
{
int n;
if (no < 0 || 6 < no)
return 0xFFFF;
if (wbc->wbdchipid == ASM58 && (no == 1 || no > 4))
return 0xFFFF;
if (wbc->wbdchipid == ADM9240 && (no > 5))
return 0xFFFF;
n = Read(WINBD_VOLT(no));
return n;
}
#endif
/*
Controlling Fan Divisor for 1st/2nd fans: CR = 0x47.
1st two bits for fan1, 2nd two bits for fan2
7 4 3 0
+-+-+-+-+-+-+-+-+ xx = 00,01,10,11 div1fac = 1,2,4,8
|y y|x x| VolID | yy = 00,01,10,11 div2fac = 1,2,4,8
+-+-+-+-+-+-+-+-+ initial values: xx=01, yy=01
Controlling Fan Divisor for 3rd fan: CR = 0x4B.
1st two bits for fan3
7 6 5 0
+-+-+-+-+-+-+-+-+
|z z| | zz = 00,01,10,11 div3fac = 1,2,4,8
+-+-+-+-+-+-+-+-+ initial values: zz=01
3rd fan divisor available for Winbond (not for LM78/79).
Bit 2 of Fan Divisor: CR = 0x5D (Winbond chips except 781D).
7 6 5 0
+-+-+-+-+-+-+-+-+
|z|y|x| | x, y, z for bit 2 of fan 1, 2, 3
+-+-+-+-+-+-+-+-+
*/
/*
* \retval 0xFFFF no sensor
* no = 0,1,2
*
* Clock is 22.5kHz (22,500 x 60 = 1350000 counts/minute)
*/
static int winbond_fanrpm(int no, struct winb_softc *wbc)
{
int r, n1 = 0x50, n2 = 0x40, n3 = 0x00;
static int div[3] = {1,1,1};
if (no < 0 || 2 < no)
return 0xFFFF;
if (no == 2
&& (wbc->wbdchipid == W83697HF || wbc->wbdchipid == ASM58
|| wbc->wbdchipid == ADM9240))
return 0xFFFF;
if (W83782D <= wbc->wbdchipid && wbc->wbdchipid <= W83697HF) {
n3 = Read(WINBD_VMCTRL); /* bit 2 */
}
if (no != 2) {
n1 = Read(WINBD_FANDIV); /* bit 0,1 */
div[0] = ((n1 >> 4) & 0x03) | ((n3 & 0x20) >> 3);
div[1] = (n1 >> 6) | ((n3 & 0x40) >> 4);
} else if (wbc->wbdchipid < LM78) {
n2 = Read(WINBD_REGPIN); /* bit 0,1 */
div[2] = (n2 >> 6) | ((n3 & 0x80) >> 5);
}
r = Read(WINBD_FAN(no));
if (r == 0xFF) {
/* change divisor for the sake of next call ! */
if (no != 2) {
if (div[no] < 3)
++(div[no]);
else
div[no] = 0;
r = (n1 & 0x0F) | ((div[0] & 0x03) << 4) | ((div[1] & 0x03) << 6);
Write(WINBD_FANDIV, r);
} else if (wbc->wbdchipid < LM78) {
if (div[no] < 3)
++(div[no]);
else
div[no] = 0;
r = (n2 & 0x3F) | ((div[2] & 0x03) << 6);
Write(WINBD_REGPIN, r);
}
if (W83782D <= wbc->wbdchipid && wbc->wbdchipid <= W83697HF) {
r = (n3 & 0x1F) | ((div[0] & 0x04) << 3) |
((div[1] & 0x04) << 4) | ((div[2] & 0x04) << 5);
Write(WINBD_VMCTRL, r);
}
return 0xFFFF;
} else if (r == 0) {
return 0xFFFF;
}
return 1350000 / (r * (1 << div[no]));
}
static int
winb_loader(struct module *m, int what, void *arg)
{
int err = 0;
switch (what) {
case MOD_LOAD: /* kldload */
uprintf("winb loaded.\n");
break;
case MOD_UNLOAD:
uprintf("winb unloaded.\n");
break;
default:
err = EINVAL;
break;
}
return(err);
}
static void winb_isa_identify(driver_t *dr, device_t dev)
{
struct winb_softc *wbc;
wbc = device_get_softc(dev);
}
static int winb_isa_probe (device_t dev)
{
int n, nd, nc, nvl, nvu, nvx, nva;
struct winb_softc *wbc;
wbc = device_get_softc(dev);
if(probed)
return ENXIO;
/* probe it */
if (chkReg_Probe(chkReg) < WINBD_chkRegNum)
return ENXIO;
nd = Read(WINBD_DEVCID) & 0xFE;
nc = Read(WINBD_CHIPID);
nvx = Read(WINBD_VENDEX);
Write(WINBD_VENDEX, 0x00);
nvl = Read(WINBD_VENDID);
Write(WINBD_VENDEX, 0x80);
nvu = Read(WINBD_VENDID);
nva = Read(ANADM_VENDID);
if (nvl == 0xA3 && nvu == 0x5C) { /* Winbond Chip */
switch (nc & 0xFE) {
case 0x10: /* 0x10 (or 0x11) */
wbc->wbdchipid = W83781D;
break;
case 0x20: /* 0x20 (or 0x21) 627HF */
case 0x90: /* 0x90 (or 0x91?) 627THF */
case 0x1A: /* 0x1A (??) 627THF-A */
wbc->wbdchipid = W83627HF;
break;
case 0x30: /* 0x30 (or 0x31) */
wbc->wbdchipid = W83782D;
if (nc == 0x31)
wbc->wbdchipid = AS99127F; /* very special, but ... */
break;
case 0x40: /* 0x40 (or 0x41) */
wbc->wbdchipid = W83783S;
break;
case 0x60: /* 0x60 (or 0x61) */
wbc->wbdchipid = W83697HF;
break;
case 0x70: /* 0x70 (or 0x71) */
wbc->wbdchipid = W83791D;
break;
default:
return ENXIO;
}
}
wbc->wbdlmid = wbc->wbdchipid;
n = Read(WINBD_TEMPADDR);
if (!(wbc->temp1_flag = (n & 0x08) >> 3)) {
if (ReadTemp1() == 0xFFFF) {
wbc->temp1_flag = 1; /* disable! */
}
}
if (!(wbc->temp2_flag = (n & 0x80) >> 7)) {
if (ReadTemp2() == 0xFFFF) {
wbc->temp2_flag = 1; /* disable! */
}
}
probed = 1;
/* return wbc->wbdchipid or something */
return 0;
}
#define WB_FAN_FNC(id, num) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err, val; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
val = winbond_fanrpm(num, wbc); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if(err) \
return err; \
return 0; \
}
#define WB_CHAS_FNC(id) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err, val, oval; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
oval = val = winbond_chassis(dev); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if((val == 2) && (oval != 2)) { \
winbond_clearchas(); \
device_printf(dev, "supposedly cleared chassis bit\n"); \
} \
if(err) \
return err; \
return 0; \
}
#define WB_PWRLED_FNC(id) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err, val, oval; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
oval = val = winbond_getpwrled(dev); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if(val <= 3 && val >= 0 && (val != oval)) { \
winbond_setpwrled(dev, val); \
} else if(val != oval) { \
return EPERM; \
} \
if(err) \
return err; \
return 0; \
}
#define WB_FANDUTY_FNC(id, n) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err, val, oval; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
oval = val = winbond_getduty(dev, n); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if(val != oval) { \
winbond_setduty(dev, n, val); \
} \
if(err) \
return err; \
return 0; \
}
#ifdef FLOATIT
#define WB_TEMP_FNC(id, num) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err; \
int val; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
val = winbond_temp(num, wbc); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if(err) \
return err; \
return 0; \
}
#define WB_VOLT_FNC(id, num) static int winb_ ## id ## _sysc(SYSCTL_HANDLER_ARGS) \
{ \
struct winb_softc *wbc; \
device_t dev; \
int err; \
int val; \
dev = oidp->oid_arg1; \
wbc = device_get_softc(dev); \
val = winbond_volt(num, wbc); \
err = sysctl_handle_int(oidp, &val, sizeof(val), req); \
if(err) \
return err; \
return 0; \
}
/* *vc0 = v[0], *vc1 = v[1];
* *v33 = v[2], *v50p = v[3], *v12p = v[4];
* *v12n = v[5], *v50n = v[6];
*/
#endif
WB_FAN_FNC(fan1, 0)
WB_FAN_FNC(fan2, 1)
WB_FAN_FNC(fan3, 2)
WB_TEMP_FNC(temp1, 0)
WB_TEMP_FNC(temp2, 1)
WB_TEMP_FNC(temp3, 2)
WB_VOLT_FNC(vc0, 0)
WB_VOLT_FNC(vc1, 1)
WB_VOLT_FNC(v33, 2)
WB_VOLT_FNC(v50p, 3)
WB_VOLT_FNC(v12p, 4)
WB_VOLT_FNC(v12n, 5)
WB_VOLT_FNC(v50n, 6)
WB_CHAS_FNC(chassis)
WB_FANDUTY_FNC(f1duty, 0)
WB_FANDUTY_FNC(f2duty, 1)
WB_PWRLED_FNC(pwrled)
static int winb_isa_attach (device_t dev)
{
int rc;
struct winb_softc *wbc;
wbc = device_get_softc(dev);
rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, WINBD_ADDR_START, WINBD_ADDR_END - WINBD_ADDR_START);
if(rc != 0) {
device_printf(dev, "set res error! rc %d\n", rc);
return ENXIO;
}
sysctl_ctx_init(&wbc->sysctl_ctx);
wbc->winbroot = SYSCTL_ADD_NODE(&wbc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_hw),
OID_AUTO, "winbond", CTLFLAG_RD, 0, "winbond monitoring");
#define ADDPR(oid,hand,type, t, desc) wbc->oids.oid = SYSCTL_ADD_PROC(&wbc->sysctl_ctx, SYSCTL_CHILDREN(wbc->winbroot), OID_AUTO, #oid, CTLTYPE_ ## type | \
CTLFLAG_RD, dev, sizeof(dev), hand, t, desc)
#define ADDPRW(oid,hand,type, t, desc) wbc->oids.oid = SYSCTL_ADD_PROC(&wbc->sysctl_ctx, SYSCTL_CHILDREN(wbc->winbroot), OID_AUTO, #oid, CTLTYPE_ ## type | \
CTLFLAG_RW, dev, sizeof(dev), hand, t, desc)
#define ADDUPR(oid,hand,type, t, desc) wbc->oids.oid = SYSCTL_ADD_PROC(&wbc->sysctl_ctx, SYSCTL_CHILDREN(wbc->winbroot), OID_AUTO, #oid "_up", CTLTYPE_ ## type | \
CTLFLAG_RD, dev, sizeof(dev), hand, t, desc)
ADDPR(fan1, winb_fan1_sysc, INT, "I", "Fan1");
ADDPR(fan2, winb_fan2_sysc, INT, "I", "Fan2");
ADDPR(fan3, winb_fan3_sysc, INT, "I", "Fan3");
ADDPRW(pwrled, winb_pwrled_sysc, INT, "I", "PWRLED");
ADDPRW(chassis, winb_chassis_sysc, INT, "I", "Chassis");
ADDPRW(f1duty, winb_f1duty_sysc, INT, "I", "Fan1Duty");
ADDPRW(f2duty, winb_f2duty_sysc, INT, "I", "Fan2Duty");
ADDUPR(temp1, winb_temp1_sysc, INT, "I", "Temp1");
ADDUPR(temp2, winb_temp2_sysc, INT, "I", "Temp2");
ADDUPR(temp3, winb_temp3_sysc, INT, "I", "Temp3");
ADDUPR(vc0, winb_vc0_sysc, INT, "I", "VC0");
ADDUPR(vc1, winb_vc1_sysc, INT, "I", "VC1");
ADDUPR(v33, winb_v33_sysc, INT, "I", "V3.3");
ADDUPR(v50p, winb_v50p_sysc, INT, "I", "V5.0+");
ADDUPR(v50n, winb_v50n_sysc, INT, "I", "V5.0-");
ADDUPR(v12p, winb_v12p_sysc, INT, "I", "V12+");
ADDUPR(v12n, winb_v12n_sysc, INT, "I", "V12-");
#undef ADDPR
return 0;
}
static int winb_isa_detach (device_t dev)
{
struct winb_softc *wbc;
wbc = device_get_softc(dev);
if(sysctl_ctx_free(&wbc->sysctl_ctx)) {
uprintf("can't free context");
return ENOTEMPTY;
} else {
bus_delete_resource(dev, SYS_RES_IOPORT, 0);
return 0;
}
/* NOTREACHED */
return 0;
}
/* Declare this module to the rest of the kernel */
/* table of supported bus methods */
static device_method_t winb_isa_methods[] = {
/* list all the bus method functions supported by the driver */
/* omit the unsupported methods */
DEVMETHOD(device_identify, winb_isa_identify),
DEVMETHOD(device_probe, winb_isa_probe),
DEVMETHOD(device_attach, winb_isa_attach),
DEVMETHOD(device_detach, winb_isa_detach),
{ 0, 0 }
};
static driver_t winb_isa_driver = {
"winb",
winb_isa_methods,
sizeof(struct winb_softc),
};
static devclass_t winb_devclass;
DRIVER_MODULE(winb, acpi, winb_isa_driver, winb_devclass, winb_loader, NULL);