xref: /inferno-os/os/cerf405/devrtc.c (revision d0e1d143ef6f03c75c008c7ec648859dd260cbab)
1 /*
2  *	DS1339 Timekeeper (on I2C)
3  */
4 
5 #include	"u.h"
6 #include	"../port/lib.h"
7 #include	"mem.h"
8 #include	"dat.h"
9 #include	"fns.h"
10 #include	"../port/error.h"
11 
12 #include	"io.h"
13 
14 typedef struct Rtc	Rtc;
15 typedef struct Rtcreg	Rtcreg;
16 
17 struct Rtc
18 {
19 	int	sec;
20 	int	min;
21 	int	hour;
22 	int	wday;
23 	int	mday;
24 	int	mon;
25 	int	year;
26 };
27 
28 struct Rtcreg
29 {
30 	uchar	sec;
31 	uchar	min;
32 	uchar	hour;
33 	uchar	wday;	/* 1=Sun */
34 	uchar	mday;	/* 00-31 */
35 	uchar	mon;	/* 1-12 */
36 	uchar	year;
37 };
38 
39 enum{
40 	Qdir = 0,
41 	Qrtc,
42 
43 	Rtclen=	7,		/* bytes read and written to timekeeper */
44 };
45 
46 static QLock	rtclock;		/* mutex on nvram operations */
47 static I2Cdev	rtdev;
48 
49 static Dirtab rtcdir[]={
50 	".",		{Qdir, 0, QTDIR},	0,	DMDIR|0555,
51 	"rtc",		{Qrtc, 0},	0,	0664,
52 };
53 
54 static ulong	rtc2sec(Rtc*);
55 static void	sec2rtc(ulong, Rtc*);
56 static void	setrtc(Rtc*);
57 
58 static void
59 rtcreset(void)
60 {
61 	rtdev.addr = 0x68;
62 	rtdev.salen = 1;
63 	i2csetup(1);
64 }
65 
66 static Chan*
67 rtcattach(char *spec)
68 {
69 	return devattach('r', spec);
70 }
71 
72 static Walkqid*
73 rtcwalk(Chan *c, Chan *nc, char **name, int nname)
74 {
75 	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
76 }
77 
78 static int
79 rtcstat(Chan *c, uchar *dp, int n)
80 {
81 	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
82 }
83 
84 static Chan*
85 rtcopen(Chan *c, int omode)
86 {
87 	omode = openmode(omode);
88 	switch((ulong)c->qid.path){
89 	case Qrtc:
90 		if(strcmp(up->env->user, eve)!=0 && omode!=OREAD)
91 			error(Eperm);
92 		break;
93 	}
94 	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
95 }
96 
97 static void
98 rtcclose(Chan*)
99 {
100 }
101 
102 static long
103 rtcread(Chan *c, void *buf, long n, vlong offset)
104 {
105 	ulong t, ot;
106 
107 	if(c->qid.type & QTDIR)
108 		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
109 
110 	switch((ulong)c->qid.path){
111 	case Qrtc:
112 		qlock(&rtclock);
113 		t = rtctime();
114 		do{
115 			ot = t;
116 			t = rtctime();	/* make sure there's no skew */
117 		}while(t != ot);
118 		qunlock(&rtclock);
119 		return readnum(offset, buf, n, t, 12);
120 	}
121 	error(Egreg);
122 	return -1;		/* never reached */
123 }
124 
125 static long
126 rtcwrite(Chan *c, void *buf, long n, vlong off)
127 {
128 	Rtc rtc;
129 	ulong secs;
130 	char *cp, sbuf[32];
131 	ulong offset = off;
132 
133 	switch((ulong)c->qid.path){
134 	case Qrtc:
135 		if(offset!=0 || n >= sizeof(sbuf)-1)
136 			error(Ebadarg);
137 		memmove(sbuf, buf, n);
138 		sbuf[n] = '\0';
139 		/*
140 		 *  read the time
141 		 */
142 		cp = sbuf;
143 		while(*cp){
144 			if(*cp>='0' && *cp<='9')
145 				break;
146 			cp++;
147 		}
148 		secs = strtoul(cp, 0, 0);
149 		/*
150 		 *  convert to bcd
151 		 */
152 		sec2rtc(secs, &rtc);
153 		/*
154 		 * write it
155 		 */
156 		setrtc(&rtc);
157 		return n;
158 	}
159 	error(Egreg);
160 	return -1;		/* never reached */
161 }
162 
163 Dev rtcdevtab = {
164 	'r',
165 	"rtc",
166 
167 	rtcreset,
168 	devinit,
169 	devshutdown,
170 	rtcattach,
171 	rtcwalk,
172 	rtcstat,
173 	rtcopen,
174 	devcreate,
175 	rtcclose,
176 	rtcread,
177 	devbread,
178 	rtcwrite,
179 	devbwrite,
180 	devremove,
181 	devwstat,
182 };
183 
184 static int
185 getbcd(int bcd)
186 {
187 	return (bcd&0x0f) + 10 * (bcd>>4);
188 }
189 
190 static int
191 putbcd(int val)
192 {
193 	return (val % 10) | (((val/10) % 10) << 4);
194 }
195 
196 long
197 rtctime(void)
198 {
199 	Rtc rtc;
200 	Rtcreg d;
201 	int h;
202 
203 	if(waserror()){
204 		iprint("rtc: err %s\n", up->env->errstr);
205 		return 0;
206 	}
207 	if(i2crecv(&rtdev, &d, Rtclen, 0) != Rtclen)
208 		return 0;
209 	poperror();
210 	rtc.sec = getbcd(d.sec);
211 	rtc.min = getbcd(d.min);
212 	if(d.hour & (1<<6)){	/* 12 hour clock */
213 		h = d.hour & 0x1F;
214 		if(d.hour & (1<<5))
215 			h += 0x12;
216 		rtc.hour = getbcd(h);
217 	}else
218 		rtc.hour = getbcd(d.hour);
219 	rtc.mday = getbcd(d.mday);
220 	rtc.mon = getbcd(d.mon & 0x7f);
221 	rtc.year = getbcd(d.year);
222 	if(rtc.mon < 1 || rtc.mon > 12)
223 		return 0;
224 	if(d.mon & (1<<7))
225 		rtc.year += 2000;
226 	else
227 		rtc.year += 1900;
228 	return rtc2sec(&rtc);
229 }
230 
231 static void
232 setrtc(Rtc *rtc)
233 {
234 	Rtcreg d;
235 
236 	memset(&d, 0, sizeof(d));
237 	d.year = putbcd(rtc->year % 100);
238 	d.mon = putbcd(rtc->mon);
239 	if(rtc->year >= 2000)
240 		d.mon |= 1<<7;
241 	d.wday = rtc->wday+1;
242 	d.mday = putbcd(rtc->mday);
243 	d.hour = putbcd(rtc->hour);
244 	d.min = putbcd(rtc->min);
245 	d.sec = putbcd(rtc->sec);
246 	i2csend(&rtdev, &d, Rtclen, 0);
247 }
248 
249 #define SEC2MIN 60L
250 #define SEC2HOUR (60L*SEC2MIN)
251 #define SEC2DAY (24L*SEC2HOUR)
252 
253 /*
254  *  days per month plus days/year
255  */
256 static	int	dmsize[] =
257 {
258 	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
259 };
260 static	int	ldmsize[] =
261 {
262 	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
263 };
264 
265 /*
266  *  return the days/month for the given year
267  */
268 static int *
269 yrsize(int y)
270 {
271 
272 	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
273 		return ldmsize;
274 	else
275 		return dmsize;
276 }
277 
278 /*
279  *  compute seconds since Jan 1 1970
280  */
281 static ulong
282 rtc2sec(Rtc *rtc)
283 {
284 	ulong secs;
285 	int i;
286 	int *d2m;
287 
288 	secs = 0;
289 
290 	/*
291 	 *  seconds per year
292 	 */
293 	for(i = 1970; i < rtc->year; i++){
294 		d2m = yrsize(i);
295 		secs += d2m[0] * SEC2DAY;
296 	}
297 
298 	/*
299 	 *  seconds per month
300 	 */
301 	d2m = yrsize(rtc->year);
302 	for(i = 1; i < rtc->mon; i++)
303 		secs += d2m[i] * SEC2DAY;
304 
305 	secs += (rtc->mday-1) * SEC2DAY;
306 	secs += rtc->hour * SEC2HOUR;
307 	secs += rtc->min * SEC2MIN;
308 	secs += rtc->sec;
309 
310 	return secs;
311 }
312 
313 /*
314  *  compute rtc from seconds since Jan 1 1970
315  */
316 static void
317 sec2rtc(ulong secs, Rtc *rtc)
318 {
319 	int d;
320 	long hms, day;
321 	int *d2m;
322 
323 	/*
324 	 * break initial number into days
325 	 */
326 	hms = secs % SEC2DAY;
327 	day = secs / SEC2DAY;
328 	if(hms < 0) {
329 		hms += SEC2DAY;
330 		day -= 1;
331 	}
332 
333 	/*
334 	 * day is the day number.
335 	 * generate day of the week.
336 	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
337 	 */
338 
339 	rtc->wday = (day + 7340036L) % 7;
340 
341 	/*
342 	 * generate hours:minutes:seconds
343 	 */
344 	rtc->sec = hms % 60;
345 	d = hms / 60;
346 	rtc->min = d % 60;
347 	d /= 60;
348 	rtc->hour = d;
349 
350 	/*
351 	 * year number
352 	 */
353 	if(day >= 0)
354 		for(d = 1970; day >= *yrsize(d); d++)
355 			day -= *yrsize(d);
356 	else
357 		for (d = 1970; day < 0; d--)
358 			day += *yrsize(d-1);
359 	rtc->year = d;
360 
361 	/*
362 	 * generate month
363 	 */
364 	d2m = yrsize(rtc->year);
365 	for(d = 1; day >= d2m[d]; d++)
366 		day -= d2m[d];
367 	rtc->mday = day + 1;
368 	rtc->mon = d;
369 }
370