#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "ureg.h" #include "../port/error.h" /* this driver doesn't implement the management interrupts. we * leave the LM78 interrupts set to whatever the BIOS did. we do * allow reading and writing the the readouts and alarm values. * Read(2)ing or write(2)ing at offset 0x0-0x1f, is * equivalent to reading or writing lm78 registers 0x20-0x3f. */ enum { /* address of chip on serial interface */ Serialaddr= 0x2d, /* parallel access registers */ Rpaddr= 0x5, Bbusy= (1<<7), Rpdata= 0x6, /* internal register addresses */ Rconfig= 0x40, Bstart= (1<<0), Bsmiena= (1<<1), Birqena= (1<<2), Bintclr= (1<<3), Breset= (1<<4), Bnmi= (1<<5), /* if set, use nmi, else irq */ Bpowbypass= (1<<6), Binit= (1<<7), Ristat1= 0x41, Ristat2= 0x42, Rsmimask1= 0x43, Rsmimask2= 0x44, Rnmimask1= 0x45, Rnmimask2= 0x46, Rvidfan= 0x47, /* set fan counter, and read voltage level */ Mvid= 0x0f, Mfan= 0xf0, Raddr= 0x48, /* address used on serial bus */ Rresetid= 0x49, /* chip reset and ID register */ Rpost= 0x00, /* start of post ram */ Rvalue= 0x20, /* start of value ram */ VRsize= 0x20, /* size of value ram */ }; enum { Qdir, Qlm78vram, }; static Dirtab lm78dir[] = { ".", { Qdir, 0, QTDIR}, 0, 0555, "lm78vram", { Qlm78vram, 0 }, 0, 0444, }; /* interface type */ enum { None= 0, Smbus, Parallel, }; static struct { QLock; int probed; int ifc; /* which interface is connected */ SMBus *smbus; /* serial interface */ int port; /* parallel interface */ } lm78; extern SMBus* piix4smbus(void); /* wait for device to become quiescent and then set the */ /* register address */ static void setreg(int reg) { int tries; for(tries = 0; tries < 1000000; tries++) if((inb(lm78.port+Rpaddr) & Bbusy) == 0){ outb(lm78.port+Rpaddr, reg); return; } error("lm78 broken"); } /* routines that actually touch the device */ static void lm78wrreg(int reg, uchar val) { if(waserror()){ qunlock(&lm78); nexterror(); } qlock(&lm78); switch(lm78.ifc){ case Smbus: lm78.smbus->transact(lm78.smbus, SMBbytewrite, Serialaddr, reg, &val); break; case Parallel: setreg(reg); outb(lm78.port+Rpdata, val); break; default: error(Enodev); break; } qunlock(&lm78); poperror(); } static int lm78rdreg(int reg) { uchar val; if(waserror()){ qunlock(&lm78); nexterror(); } qlock(&lm78); switch(lm78.ifc){ case Smbus: lm78.smbus->transact(lm78.smbus, SMBsend, Serialaddr, reg, nil); lm78.smbus->transact(lm78.smbus, SMBrecv, Serialaddr, 0, &val); break; case Parallel: setreg(reg); val = inb(lm78.port+Rpdata); break; default: error(Enodev); break; } qunlock(&lm78); poperror(); return val; } /* start the chip monitoring but don't change any smi * interrupts and/or alarms that the BIOS may have set up. * this isn't locked because it's thought to be idempotent */ static void lm78enable(void) { uchar config; if(lm78.ifc == None) error(Enodev); if(lm78.probed == 0){ /* make sure its really there */ if(lm78rdreg(Raddr) != Serialaddr){ lm78.ifc = None; error(Enodev); } else { /* start the sampling */ config = lm78rdreg(Rconfig); config = (config | Bstart) & ~(Bintclr|Binit); lm78wrreg(Rconfig, config); pprint("Rvidfan %2.2ux\n", lm78rdreg(Rconfig), lm78rdreg(Rvidfan)); } lm78.probed = 1; } } enum { IntelVendID= 0x8086, PiixID= 0x122E, Piix3ID= 0x7000, Piix4PMID= 0x7113, /* PIIX4 power management function */ PCSC= 0x78, /* programmable chip select control register */ PCSC8bytes= 0x01, }; /* figure out what kind of interface we could have */ void lm78reset(void) { int pcs; Pcidev *p; lm78.ifc = None; p = nil; while((p = pcimatch(p, IntelVendID, 0)) != nil){ switch(p->did){ /* these bridges use the PCSC to map the lm78 into port space. */ /* for this case the lm78's CS# select is connected to the PIIX's */ /* PCS# output and the bottom 3 bits of address are passed to the */ /* LM78's A0-A2 inputs. */ case PiixID: case Piix3ID: pcs = pcicfgr16(p, PCSC); if(pcs & 3) { /* already enabled */ lm78.port = pcs & ~3; lm78.ifc = Parallel; return; } /* enable the chip, use default address 0x50 */ pcicfgw16(p, PCSC, 0x50|PCSC8bytes); pcs = pcicfgr16(p, PCSC); lm78.port = pcs & ~3; lm78.ifc = Parallel; return; /* this bridge puts the lm78's serial interface on the smbus */ case Piix4PMID: lm78.smbus = piix4smbus(); if(lm78.smbus == nil) continue; print("found piix4 smbus, base %lud\n", lm78.smbus->base); lm78.ifc = Smbus; return; } } } Walkqid * lm78walk(Chan* c, Chan *nc, char** name, int nname) { return devwalk(c, nc, name, nname, lm78dir, nelem(lm78dir), devgen); } static int lm78stat(Chan* c, uchar* dp, int n) { return devstat(c, dp, n, lm78dir, nelem(lm78dir), devgen); } static Chan* lm78open(Chan* c, int omode) { return devopen(c, omode, lm78dir, nelem(lm78dir), devgen); } static void lm78close(Chan*) { } enum { Linelen= 25, }; static long lm78read(Chan *c, void *a, long n, vlong offset) { uchar *va = a; int off, e; off = offset; switch((ulong)c->qid.path){ case Qdir: return devdirread(c, a, n, lm78dir, nelem(lm78dir), devgen); case Qlm78vram: if(off >= VRsize) return 0; e = off + n; if(e > VRsize) e = VRsize; for(; off < e; off++) *va++ = lm78rdreg(Rvalue+off); return (int)(va - (uchar*)a); } return 0; } static long lm78write(Chan *c, void *a, long n, vlong offset) { uchar *va = a; int off, e; off = offset; switch((ulong)c->qid.path){ default: error(Eperm); case Qlm78vram: if(off >= VRsize) return 0; e = off + n; if(e > VRsize) e = VRsize; for(; off < e; off++) lm78wrreg(Rvalue+off, *va++); return va - (uchar*)a; } return 0; } extern Dev lm78devtab; static Chan* lm78attach(char* spec) { lm78enable(); return devattach(lm78devtab.dc, spec); } Dev lm78devtab = { 'T', "lm78", lm78reset, devinit, devshutdown, lm78attach, lm78walk, lm78stat, lm78open, devcreate, lm78close, lm78read, devbread, lm78write, devbwrite, devremove, devwstat, };