/* * devrtc - real-time clock, for kirkwood */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "io.h" typedef struct RtcReg RtcReg; typedef struct Rtc Rtc; struct RtcReg { ulong time; ulong date; ulong alarmtm; ulong alarmdt; ulong intrmask; ulong intrcause; }; struct Rtc { int sec; int min; int hour; int wday; int mday; int mon; int year; }; enum { Qdir, Qrtc, }; static Dirtab rtcdir[] = { ".", {Qdir, 0, QTDIR}, 0, 0555, "rtc", {Qrtc}, NUMSIZE, 0664, }; static RtcReg *rtcreg; /* filled in by attach */ static Lock rtclock; #define SEC2MIN 60 #define SEC2HOUR (60*SEC2MIN) #define SEC2DAY (24L*SEC2HOUR) /* * days per month plus days/year */ static int dmsize[] = { 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static int ldmsize[] = { 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /* * return the days/month for the given year */ static int * yrsize(int yr) { if((yr % 4) == 0) return ldmsize; else return dmsize; } /* * compute seconds since Jan 1 1970 */ static ulong rtc2sec(Rtc *rtc) { ulong secs; int i; int *d2m; /* * seconds per year */ secs = 0; for(i = 1970; i < rtc->year; i++){ d2m = yrsize(i); secs += d2m[0] * SEC2DAY; } /* * seconds per month */ d2m = yrsize(rtc->year); for(i = 1; i < rtc->mon; i++) secs += d2m[i] * SEC2DAY; secs += (rtc->mday-1) * SEC2DAY; secs += rtc->hour * SEC2HOUR; secs += rtc->min * SEC2MIN; secs += rtc->sec; return secs; } /* * compute rtc from seconds since Jan 1 1970 */ static void sec2rtc(ulong secs, Rtc *rtc) { int d; long hms, day; int *d2m; /* * break initial number into days */ hms = secs % SEC2DAY; day = secs / SEC2DAY; if(hms < 0) { hms += SEC2DAY; day -= 1; } /* * 19700101 was thursday */ rtc->wday = (day + 7340036L) % 7; /* * generate hours:minutes:seconds */ rtc->sec = hms % 60; d = hms / 60; rtc->min = d % 60; d /= 60; rtc->hour = d; /* * year number */ if(day >= 0) for(d = 1970; day >= *yrsize(d); d++) day -= *yrsize(d); else for (d = 1970; day < 0; d--) day += *yrsize(d-1); rtc->year = d; /* * generate month */ d2m = yrsize(rtc->year); for(d = 1; day >= d2m[d]; d++) day -= d2m[d]; rtc->mday = day + 1; rtc->mon = d; } enum { Rtcsec = 0x00007f, Rtcmin = 0x007f00, Rtcms = 8, Rtchr12 = 0x1f0000, Rtchr24 = 0x3f0000, Rtchrs = 16, Rdmday = 0x00003f, Rdmon = 0x001f00, Rdms = 8, Rdyear = 0x7f0000, Rdys = 16, Rtcpm = 1<<21, /* pm bit */ Rtc12 = 1<<22, /* 12 hr clock */ }; static ulong bcd2dec(ulong bcd) { ulong d, m, i; d = 0; m = 1; for(i = 0; i < 2 * sizeof d; i++){ d += ((bcd >> (4*i)) & 0xf) * m; m *= 10; } return d; } static ulong dec2bcd(ulong d) { ulong bcd, i; bcd = 0; for(i = 0; d != 0; i++){ bcd |= (d%10) << (4*i); d /= 10; } return bcd; } static long _rtctime(void) { ulong t, d; Rtc rtc; t = rtcreg->time; d = rtcreg->date; rtc.sec = bcd2dec(t & Rtcsec); rtc.min = bcd2dec((t & Rtcmin) >> Rtcms); if(t & Rtc12){ rtc.hour = bcd2dec((t & Rtchr12) >> Rtchrs) - 1; /* 1—12 */ if(t & Rtcpm) rtc.hour += 12; }else rtc.hour = bcd2dec((t & Rtchr24) >> Rtchrs); /* 0—23 */ rtc.mday = bcd2dec(d & Rdmday); /* 1—31 */ rtc.mon = bcd2dec((d & Rdmon) >> Rdms); /* 1—12 */ rtc.year = bcd2dec((d & Rdyear) >> Rdys) + 2000; /* year%100 */ // print("%0.2d:%0.2d:%.02d %0.2d/%0.2d/%0.2d\n", /* HH:MM:SS YY/MM/DD */ // rtc.hour, rtc.min, rtc.sec, rtc.year, rtc.mon, rtc.mday); return rtc2sec(&rtc); } long rtctime(void) { int i; long t, ot; ilock(&rtclock); /* loop until we get two reads in a row the same */ t = _rtctime(); ot = ~t; for(i = 0; i < 100 && ot != t; i++){ ot = t; t = _rtctime(); } if(ot != t) print("rtctime: we are boofheads\n"); iunlock(&rtclock); return t; } static void setrtc(Rtc *rtc) { ilock(&rtclock); rtcreg->time = dec2bcd(rtc->wday) << 24 | dec2bcd(rtc->hour) << 16 | dec2bcd(rtc->min) << 8 | dec2bcd(rtc->sec); rtcreg->date = dec2bcd(rtc->year - 2000) << 16 | dec2bcd(rtc->mon) << 8 | dec2bcd(rtc->mday); iunlock(&rtclock); } static Chan* rtcattach(char *spec) { rtcreg = (RtcReg*)soc.rtc; return devattach(L'r', spec); } static Walkqid* rtcwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen); } static int rtcstat(Chan *c, uchar *dp, int n) { return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen); } static Chan* rtcopen(Chan *c, int omode) { return devopen(c, omode, rtcdir, nelem(rtcdir), devgen); } static void rtcclose(Chan*) { } static long rtcread(Chan *c, void *buf, long n, vlong off) { if(c->qid.type & QTDIR) return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen); switch((ulong)c->qid.path){ default: error(Egreg); case Qrtc: return readnum(off, buf, n, rtctime(), NUMSIZE); } } static long rtcwrite(Chan *c, void *buf, long n, vlong off) { ulong offset = off; char *cp, sbuf[32]; Rtc rtc; switch((ulong)c->qid.path){ default: error(Egreg); case Qrtc: if(offset != 0 || n >= sizeof(sbuf)-1) error(Ebadarg); memmove(sbuf, buf, n); sbuf[n] = '\0'; for(cp = sbuf; *cp != '\0'; cp++) if(*cp >= '0' && *cp <= '9') break; sec2rtc(strtoul(cp, 0, 0), &rtc); setrtc(&rtc); return n; } } Dev rtcdevtab = { L'r', "rtc", devreset, devinit, devshutdown, rtcattach, rtcwalk, rtcstat, rtcopen, devcreate, rtcclose, rtcread, devbread, rtcwrite, devbwrite, devremove, devwstat, devpower, };