Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * 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,
};