xref: /plan9/sys/src/9/mtx/devrtc.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 /*
2  *	M48T59/559 Timekeeper
3  */
4 #include	"u.h"
5 #include	"../port/lib.h"
6 #include	"mem.h"
7 #include	"dat.h"
8 #include	"fns.h"
9 #include	"../port/error.h"
10 
11 #include	"io.h"
12 
13 enum{
14 	STB0 = 0x74,
15 	STB1 = 0x75,
16 	Data = 0x77,
17 
18 	NVOFF=	0,
19 	NVLEN=	0x1ff0,		/* length in bytes of NV RAM */
20 
21 	/*
22 	 *  register offsets into time of day clock
23 	 */
24 	NVflags=		0x1ff0,
25 	NVwatchdog=	0x1ff7,
26 	NVctl=		0x1ff8,
27 	NVsec,
28 	NVmin,
29 	NVhour,
30 	NVday,		/* (1 = Sun) */
31 	NVmday,		/* (1-31) */
32 	NVmon,		/* (1-12) */
33 	NVyear,		/* (0-99) */
34 
35 	/* NVctl */
36 	RTwrite = (1<<7),
37 	RTread = (1<<6),
38 	RTsign = (1<<5),
39 	RTcal = 0x1f,
40 
41 	/* NVwatchdog */
42 	WDsteer = (1<<7),		/* 0 -> intr, 1 -> reset */
43 	WDmult = (1<<2),		/* 5 bits of multiplier */
44 	WDres0 = (0<<0),		/* 1/16 sec resolution */
45 	WDres1 = (1<<0),		/* 1/4 sec resolution */
46 	WDres2 = (2<<0),		/* 1 sec resolution */
47 	WDres3 = (3<<0),		/* 4 sec resolution */
48 
49 	Qdir = 0,
50 	Qrtc,
51 	Qnvram,
52 };
53 
54 /*
55  *  broken down time
56  */
57 typedef struct
58 {
59 	int	sec;
60 	int	min;
61 	int	hour;
62 	int	mday;
63 	int	mon;
64 	int	year;
65 } Rtc;
66 
67 QLock	rtclock;		/* mutex on nvram operations */
68 
69 static Dirtab rtcdir[]={
70 	".",		{Qdir, 0, QTDIR},	0,	DMDIR|0555,
71 	"rtc",		{Qrtc, 0},	0,	0644,
72 	"nvram",	{Qnvram, 0},	0,	0600,
73 };
74 
75 static ulong	rtc2sec(Rtc*);
76 static void	sec2rtc(ulong, Rtc*);
77 static void	setrtc(Rtc*);
78 static void	nvcksum(void);
79 static void	nvput(int, uchar);
80 static uchar	nvget(int);
81 
82 static Chan*
rtcattach(char * spec)83 rtcattach(char *spec)
84 {
85 	return devattach('r', spec);
86 }
87 
88 static Walkqid*
rtcwalk(Chan * c,Chan * nc,char ** name,int nname)89 rtcwalk(Chan *c, Chan *nc, char **name, int nname)
90 {
91 	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
92 }
93 
94 static int
rtcstat(Chan * c,uchar * dp,int n)95 rtcstat(Chan *c, uchar *dp, int n)
96 {
97 	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
98 }
99 
100 static Chan*
rtcopen(Chan * c,int omode)101 rtcopen(Chan *c, int omode)
102 {
103 	omode = openmode(omode);
104 	switch((ulong)c->qid.path){
105 	case Qrtc:
106 		if(strcmp(up->user, eve)!=0 && omode!=OREAD)
107 			error(Eperm);
108 		break;
109 	case Qnvram:
110 		if(strcmp(up->user, eve)!=0 || !cpuserver)
111 			error(Eperm);
112 	}
113 	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
114 }
115 
116 static void
rtcclose(Chan *)117 rtcclose(Chan*)
118 {
119 }
120 
121 static long
rtcread(Chan * c,void * buf,long n,vlong off)122 rtcread(Chan *c, void *buf, long n, vlong off)
123 {
124 	char *p;
125 	ulong t;
126 	int i;
127 	ulong offset = off;
128 
129 	if(c->qid.type & QTDIR)
130 		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
131 
132 	switch((ulong)c->qid.path){
133 	case Qrtc:
134 		qlock(&rtclock);
135 		t = rtctime();
136 		qunlock(&rtclock);
137 		n = readnum(offset, buf, n, t, 12);
138 		return n;
139 	case Qnvram:
140 		offset += NVOFF;
141 		if(offset > NVLEN)
142 			return 0;
143 		if(n > NVLEN - offset)
144 			n = NVLEN - offset;
145 		p = buf;
146 		qlock(&rtclock);
147 		for(i = 0; i < n; i++)
148 			p[i] = nvget(i+offset);
149 		qunlock(&rtclock);
150 		return n;
151 	}
152 	error(Egreg);
153 	return -1;		/* never reached */
154 }
155 
156 static long
rtcwrite(Chan * c,void * buf,long n,vlong off)157 rtcwrite(Chan *c, void *buf, long n, vlong off)
158 {
159 	Rtc rtc;
160 	ulong secs;
161 	char *cp, *ep;
162 	int i;
163 	ulong offset = off;
164 
165 	switch((ulong)c->qid.path){
166 	case Qrtc:
167 		if(offset!=0)
168 			error(Ebadarg);
169 		/*
170 		 *  read the time
171 		 */
172 		cp = ep = buf;
173 		ep += n;
174 		while(cp < ep){
175 			if(*cp>='0' && *cp<='9')
176 				break;
177 			cp++;
178 		}
179 		secs = strtoul(cp, 0, 0);
180 		/*
181 		 *  convert to bcd
182 		 */
183 		sec2rtc(secs, &rtc);
184 		/*
185 		 * write it
186 		 */
187 		qlock(&rtclock);
188 		setrtc(&rtc);
189 		qunlock(&rtclock);
190 		return n;
191 	case Qnvram:
192 		offset += NVOFF;
193 		if(offset > NVLEN)
194 			return 0;
195 		if(n > NVLEN - offset)
196 			n = NVLEN - offset;
197 		qlock(&rtclock);
198 		for(i = 0; i < n; i++)
199 			nvput(i+offset, ((uchar*)buf)[i]);
200 		nvcksum();
201 		qunlock(&rtclock);
202 		return n;
203 	}
204 	error(Egreg);
205 	return -1;		/* never reached */
206 }
207 
208 long
rtcbwrite(Chan * c,Block * bp,ulong offset)209 rtcbwrite(Chan *c, Block *bp, ulong offset)
210 {
211 	return devbwrite(c, bp, offset);
212 }
213 
214 Dev rtcdevtab = {
215 	'r',
216 	"rtc",
217 
218 	devreset,
219 	devinit,
220 	devshutdown,
221 	rtcattach,
222 	rtcwalk,
223 	rtcstat,
224 	rtcopen,
225 	devcreate,
226 	rtcclose,
227 	rtcread,
228 	devbread,
229 	rtcwrite,
230 	devbwrite,
231 	devremove,
232 	devwstat,
233 };
234 
235 static void
nvput(int offset,uchar val)236 nvput(int offset, uchar val)
237 {
238 	outb(STB0, offset);
239 	outb(STB1, offset>>8);
240 	outb(Data, val);
241 }
242 
243 static uchar
nvget(int offset)244 nvget(int offset)
245 {
246 	outb(STB0, offset);
247 	outb(STB1, offset>>8);
248 	return inb(Data);
249 }
250 
251 static void
nvcksum(void)252 nvcksum(void)
253 {
254 }
255 
256 void
watchreset(void)257 watchreset(void)
258 {
259 	splhi();
260 	nvput(NVwatchdog, WDsteer|(1*WDmult)|WDres0);
261 	for(;;);
262 }
263 
264 static int
getbcd(int bcd)265 getbcd(int bcd)
266 {
267 	return (bcd&0x0f) + 10 * (bcd>>4);
268 }
269 
270 static int
putbcd(int val)271 putbcd(int val)
272 {
273 	return (val % 10) | (((val/10) % 10) << 4);
274 }
275 
276 long
rtctime(void)277 rtctime(void)
278 {
279 	int ctl;
280 	Rtc rtc;
281 
282 	/*
283 	 *  convert from BCD
284 	 */
285 	ctl = nvget(NVctl);
286 	ctl &= RTsign|RTcal;
287 	nvput(NVctl, ctl|RTread);
288 
289 	rtc.sec = getbcd(nvget(NVsec) & 0x7f);
290 	rtc.min = getbcd(nvget(NVmin));
291 	rtc.hour = getbcd(nvget(NVhour));
292 	rtc.mday = getbcd(nvget(NVmday));
293 	rtc.mon = getbcd(nvget(NVmon));
294 	rtc.year = getbcd(nvget(NVyear));
295 	if(rtc.year < 70)
296 		rtc.year += 2000;
297 	else
298 		rtc.year += 1900;
299 
300 	nvput(NVctl, ctl);
301 
302 	return rtc2sec(&rtc);
303 }
304 
305 static void
setrtc(Rtc * rtc)306 setrtc(Rtc *rtc)
307 {
308 	int ctl;
309 
310 	ctl = nvget(NVctl);
311 	ctl &= RTsign|RTcal;
312 	nvput(NVctl, ctl|RTwrite);
313 
314 	nvput(NVsec, putbcd(rtc->sec));
315 	nvput(NVmin, putbcd(rtc->min));
316 	nvput(NVhour, putbcd(rtc->hour));
317 	nvput(NVmday, putbcd(rtc->mday));
318 	nvput(NVmon, putbcd(rtc->mon));
319 	nvput(NVyear, putbcd(rtc->year % 100));
320 
321 	nvput(NVctl, ctl);
322 }
323 
324 #define SEC2MIN 60L
325 #define SEC2HOUR (60L*SEC2MIN)
326 #define SEC2DAY (24L*SEC2HOUR)
327 
328 /*
329  *  days per month plus days/year
330  */
331 static	int	dmsize[] =
332 {
333 	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
334 };
335 static	int	ldmsize[] =
336 {
337 	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
338 };
339 
340 /*
341  *  return the days/month for the given year
342  */
343 static int *
yrsize(int y)344 yrsize(int y)
345 {
346 
347 	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
348 		return ldmsize;
349 	else
350 		return dmsize;
351 }
352 
353 /*
354  *  compute seconds since Jan 1 1970
355  */
356 static ulong
rtc2sec(Rtc * rtc)357 rtc2sec(Rtc *rtc)
358 {
359 	ulong secs;
360 	int i;
361 	int *d2m;
362 
363 	secs = 0;
364 
365 	/*
366 	 *  seconds per year
367 	 */
368 	for(i = 1970; i < rtc->year; i++){
369 		d2m = yrsize(i);
370 		secs += d2m[0] * SEC2DAY;
371 	}
372 
373 	/*
374 	 *  seconds per month
375 	 */
376 	d2m = yrsize(rtc->year);
377 	for(i = 1; i < rtc->mon; i++)
378 		secs += d2m[i] * SEC2DAY;
379 
380 	secs += (rtc->mday-1) * SEC2DAY;
381 	secs += rtc->hour * SEC2HOUR;
382 	secs += rtc->min * SEC2MIN;
383 	secs += rtc->sec;
384 
385 	return secs;
386 }
387 
388 /*
389  *  compute rtc from seconds since Jan 1 1970
390  */
391 static void
sec2rtc(ulong secs,Rtc * rtc)392 sec2rtc(ulong secs, Rtc *rtc)
393 {
394 	int d;
395 	long hms, day;
396 	int *d2m;
397 
398 	/*
399 	 * break initial number into days
400 	 */
401 	hms = secs % SEC2DAY;
402 	day = secs / SEC2DAY;
403 	if(hms < 0) {
404 		hms += SEC2DAY;
405 		day -= 1;
406 	}
407 
408 	/*
409 	 * generate hours:minutes:seconds
410 	 */
411 	rtc->sec = hms % 60;
412 	d = hms / 60;
413 	rtc->min = d % 60;
414 	d /= 60;
415 	rtc->hour = d;
416 
417 	/*
418 	 * year number
419 	 */
420 	if(day >= 0)
421 		for(d = 1970; day >= *yrsize(d); d++)
422 			day -= *yrsize(d);
423 	else
424 		for (d = 1970; day < 0; d--)
425 			day += *yrsize(d-1);
426 	rtc->year = d;
427 
428 	/*
429 	 * generate month
430 	 */
431 	d2m = yrsize(rtc->year);
432 	for(d = 1; day >= d2m[d]; d++)
433 		day -= d2m[d];
434 	rtc->mday = day + 1;
435 	rtc->mon = d;
436 
437 	return;
438 }
439