xref: /plan9/sys/src/9/pc/devlm78.c (revision 68412abfd32cf2b40bc99e2a1363ebd4a35a4480)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "io.h"
7 #include "ureg.h"
8 #include "../port/error.h"
9 
10 /* this driver doesn't implement the management interrupts.  we
11  * leave the LM78 interrupts set to whatever the BIOS did.  we do
12  * allow reading and writing the the readouts and alarm values.
13  * Read(2)ing or write(2)ing at offset 0x0-0x1f, is
14  * equivalent to reading or writing lm78 registers 0x20-0x3f.
15  */
16 enum
17 {
18 	/*  address of chip on serial interface */
19 	Serialaddr=	0x2d,
20 
21 	/*  parallel access registers */
22 	Rpaddr=		0x5,
23 	Bbusy=		 (1<<7),
24 	Rpdata=		0x6,
25 
26 	/*  internal register addresses */
27 	Rconfig=	0x40,
28 	Bstart=		 (1<<0),
29 	Bsmiena=	 (1<<1),
30 	Birqena=	 (1<<2),
31 	Bintclr=	 (1<<3),
32 	Breset=		 (1<<4),
33 	Bnmi=		 (1<<5),	/*  if set, use nmi, else irq */
34 	Bpowbypass=	 (1<<6),
35 	Binit=		 (1<<7),
36 	Ristat1=	0x41,
37 	Ristat2=	0x42,
38 	Rsmimask1=	0x43,
39 	Rsmimask2=	0x44,
40 	Rnmimask1=	0x45,
41 	Rnmimask2=	0x46,
42 	Rvidfan=	0x47,		/*  set fan counter, and read voltage level */
43 	Mvid=		 0x0f,
44 	Mfan=		 0xf0,
45 	Raddr=		0x48,		/*  address used on serial bus */
46 	Rresetid=	0x49,		/*  chip reset and ID register */
47 	Rpost=		0x00,		/*  start of post ram */
48 	Rvalue=		0x20,		/*  start of value ram */
49 
50 	VRsize=		0x20,		/*  size of value ram */
51 };
52 
53 enum
54 {
55 	Qdir,
56 	Qlm78vram,
57 };
58 
59 static Dirtab lm78dir[] = {
60 	".",			{ Qdir, 0, QTDIR},	0,	0555,
61 	"lm78vram",	{ Qlm78vram, 0 },	0,	0444,
62 };
63 
64 /*  interface type */
65 enum
66 {
67 	None=	0,
68 	Smbus,
69 	Parallel,
70 };
71 
72 static struct {
73 	QLock;
74 	int	probed;
75 	int 	ifc;	/*  which interface is connected */
76 	SMBus	*smbus;	/*  serial interface */
77 	int	port;	/*  parallel interface */
78 } lm78;
79 
80 extern SMBus*	piix4smbus(void);
81 
82 /*  wait for device to become quiescent and then set the */
83 /*  register address */
84 static void
setreg(int reg)85 setreg(int reg)
86 {
87 	int tries;
88 
89 	for(tries = 0; tries < 1000000; tries++)
90 		if((inb(lm78.port+Rpaddr) & Bbusy) == 0){
91 			outb(lm78.port+Rpaddr, reg);
92 			return;
93 		}
94 	error("lm78 broken");
95 }
96 
97 /*  routines that actually touch the device */
98 static void
lm78wrreg(int reg,uchar val)99 lm78wrreg(int reg, uchar val)
100 {
101 	if(waserror()){
102 		qunlock(&lm78);
103 		nexterror();
104 	}
105 	qlock(&lm78);
106 
107 	switch(lm78.ifc){
108 	case Smbus:
109 		lm78.smbus->transact(lm78.smbus, SMBbytewrite, Serialaddr, reg, &val);
110 		break;
111 	case Parallel:
112 		setreg(reg);
113 		outb(lm78.port+Rpdata, val);
114 		break;
115 	default:
116 		error(Enodev);
117 		break;
118 	}
119 
120 	qunlock(&lm78);
121 	poperror();
122 }
123 
124 static int
lm78rdreg(int reg)125 lm78rdreg(int reg)
126 {
127 	uchar val;
128 
129 	if(waserror()){
130 		qunlock(&lm78);
131 		nexterror();
132 	}
133 	qlock(&lm78);
134 
135 	switch(lm78.ifc){
136 	case Smbus:
137 		lm78.smbus->transact(lm78.smbus, SMBsend, Serialaddr, reg, nil);
138 		lm78.smbus->transact(lm78.smbus, SMBrecv, Serialaddr, 0, &val);
139 		break;
140 	case Parallel:
141 		setreg(reg);
142 		val = inb(lm78.port+Rpdata);
143 		break;
144 	default:
145 		error(Enodev);
146 		break;
147 	}
148 
149 	qunlock(&lm78);
150 	poperror();
151 	return val;
152 }
153 
154 /*  start the chip monitoring but don't change any smi
155  *  interrupts and/or alarms that the BIOS may have set up.
156  *  this isn't locked because it's thought to be idempotent
157  */
158 static void
lm78enable(void)159 lm78enable(void)
160 {
161 	uchar config;
162 
163 	if(lm78.ifc == None)
164 		error(Enodev);
165 
166 	if(lm78.probed == 0){
167 		/*  make sure its really there */
168 		if(lm78rdreg(Raddr) != Serialaddr){
169 			lm78.ifc = None;
170 			error(Enodev);
171 		} else {
172 			/*  start the sampling */
173 			config = lm78rdreg(Rconfig);
174 			config = (config | Bstart) & ~(Bintclr|Binit);
175 			lm78wrreg(Rconfig, config);
176 pprint("Rvidfan %2.2ux\n", lm78rdreg(Rconfig), lm78rdreg(Rvidfan));
177 		}
178 		lm78.probed = 1;
179 	}
180 }
181 
182 enum
183 {
184 	IntelVendID=	0x8086,
185 	PiixID=		0x122E,
186 	Piix3ID=	0x7000,
187 
188 	Piix4PMID=	0x7113,		/*  PIIX4 power management function */
189 
190 	PCSC=		0x78,		/*  programmable chip select control register */
191 	PCSC8bytes=	0x01,
192 };
193 
194 /*  figure out what kind of interface we could have */
195 void
lm78reset(void)196 lm78reset(void)
197 {
198 	int pcs;
199 	Pcidev *p;
200 
201 	lm78.ifc = None;
202 	p = nil;
203 	while((p = pcimatch(p, IntelVendID, 0)) != nil){
204 		switch(p->did){
205 		/*  these bridges use the PCSC to map the lm78 into port space. */
206 		/*  for this case the lm78's CS# select is connected to the PIIX's */
207 		/*  PCS# output and the bottom 3 bits of address are passed to the */
208 		/*  LM78's A0-A2 inputs. */
209 		case PiixID:
210 		case Piix3ID:
211 			pcs = pcicfgr16(p, PCSC);
212 			if(pcs & 3) {
213 				/* already enabled */
214 				lm78.port = pcs & ~3;
215 				lm78.ifc = Parallel;
216 				return;
217 			}
218 
219 			/*  enable the chip, use default address 0x50 */
220 			pcicfgw16(p, PCSC, 0x50|PCSC8bytes);
221 			pcs = pcicfgr16(p, PCSC);
222 			lm78.port = pcs & ~3;
223 			lm78.ifc = Parallel;
224 			return;
225 
226 		/*  this bridge puts the lm78's serial interface on the smbus */
227 		case Piix4PMID:
228 			lm78.smbus = piix4smbus();
229 			if(lm78.smbus == nil)
230 				continue;
231 			print("found piix4 smbus, base %lud\n", lm78.smbus->base);
232 			lm78.ifc = Smbus;
233 			return;
234 		}
235 	}
236 }
237 
238 Walkqid *
lm78walk(Chan * c,Chan * nc,char ** name,int nname)239 lm78walk(Chan* c, Chan *nc, char** name, int nname)
240 {
241 	return devwalk(c, nc, name, nname, lm78dir, nelem(lm78dir), devgen);
242 }
243 
244 static int
lm78stat(Chan * c,uchar * dp,int n)245 lm78stat(Chan* c, uchar* dp, int n)
246 {
247 	return devstat(c, dp, n, lm78dir, nelem(lm78dir), devgen);
248 }
249 
250 static Chan*
lm78open(Chan * c,int omode)251 lm78open(Chan* c, int omode)
252 {
253 	return devopen(c, omode, lm78dir, nelem(lm78dir), devgen);
254 }
255 
256 static void
lm78close(Chan *)257 lm78close(Chan*)
258 {
259 }
260 
261 enum
262 {
263 	Linelen= 25,
264 };
265 
266 static long
lm78read(Chan * c,void * a,long n,vlong offset)267 lm78read(Chan *c, void *a, long n, vlong offset)
268 {
269 	uchar *va = a;
270 	int off, e;
271 
272 	off = offset;
273 
274 	switch((ulong)c->qid.path){
275 	case Qdir:
276 		return devdirread(c, a, n, lm78dir, nelem(lm78dir), devgen);
277 
278 	case Qlm78vram:
279 		if(off >=  VRsize)
280 			return 0;
281 		e = off + n;
282 		if(e > VRsize)
283 			e = VRsize;
284 		for(; off < e; off++)
285 			*va++ = lm78rdreg(Rvalue+off);
286 		return (int)(va - (uchar*)a);
287 	}
288 	return 0;
289 }
290 
291 static long
lm78write(Chan * c,void * a,long n,vlong offset)292 lm78write(Chan *c, void *a, long n, vlong offset)
293 {
294 	uchar *va = a;
295 	int off, e;
296 
297 	off = offset;
298 
299 	switch((ulong)c->qid.path){
300 	default:
301 		error(Eperm);
302 
303 	case Qlm78vram:
304 		if(off >=  VRsize)
305 			return 0;
306 		e = off + n;
307 		if(e > VRsize)
308 			e = VRsize;
309 		for(; off < e; off++)
310 			lm78wrreg(Rvalue+off, *va++);
311 		return va - (uchar*)a;
312 	}
313 	return 0;
314 }
315 
316 extern Dev lm78devtab;
317 
318 static Chan*
lm78attach(char * spec)319 lm78attach(char* spec)
320 {
321 	lm78enable();
322 
323 	return devattach(lm78devtab.dc, spec);
324 }
325 
326 Dev lm78devtab = {
327 	'T',
328 	"lm78",
329 
330 	lm78reset,
331 	devinit,
332 	devshutdown,
333 	lm78attach,
334 	lm78walk,
335 	lm78stat,
336 	lm78open,
337 	devcreate,
338 	lm78close,
339 	lm78read,
340 	devbread,
341 	lm78write,
342 	devbwrite,
343 	devremove,
344 	devwstat,
345 };
346 
347