shithub: util

ref: 54fbb1ea3332ec5ab5025569d7c25a388c642803
dir: /mcp7940x.c/

View raw version
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>

#define CTL "%s/i2c.6f.ctl"
#define DATA "%s/i2c.6f.data"

char *i2cdir = "/dev/i2c1";

enum {
	/* registers */
	Seconds=	0,
	Minutes=	1,
	Hours=		2,
	Weekday=	3,
	Mday=		4,
	Month=		5,
	Year=		6,
	Nbcd=		7,

	Control=	7,
		CrsTrim=	1<<2,
	OscTrim=	8,

	/* Hours register may be in 12-hour or 24-hour mode */
	Twelvehr=	0x40,
	Pm=			0x20,

	Start=		0x80,
	Vbaten=		0x08,
};

typedef struct Rtc	Rtc;

struct Rtc
{
	int	sec;
	int	min;
	int	hour;
	int	mday;
	int	mon;
	int	year;
};

#define SEC2MIN 60L
#define SEC2HOUR (60L*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 y)
{
	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
		return ldmsize;
	else
		return dmsize;
}

/*
 *  compute seconds since Jan 1 1970
 */
static ulong
rtc2sec(Rtc *rtc)
{
	ulong secs;
	int i;
	int *d2m;

	secs = 0;

	/*
	 *  seconds per year
	 */
	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;
	}

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

	return;
}

void
ctl(char *s)
{
	char *ctl = smprint(CTL, i2cdir);
	if (ctl == nil)
		sysfatal("smprint: %r");
	int fd = open(ctl, OWRITE);
	if (fd < 0)
		sysfatal("open: %r");
	free(ctl);
	write(fd, s, strlen(s));
	close(fd);
}

static int
bcd(int n)
{
	return (n & 0xF) + (10 * (n >> 4));
}

int
datafd(void)
{
	char *data;
	int fd;

	data = smprint(DATA, i2cdir);
	if (data == nil)
		sysfatal("smprint: %r");
	fd = open(data, ORDWR);
	if (fd < 0)
		sysfatal("open: %r");
	free(data);

	return fd;
}

void
fsread(Req *r)
{
	int fd;
	uchar clk[Nbcd];
	Rtc rtc;
	char buf[256];
	ulong t;
	int sign;
	int trim;
	int offs;
	int len;
	int i;

	clk[0] = 0;

	if ((intptr)(r->fid->file->aux) == 1) {
		fd = datafd();

		write(fd, clk, 1);
		read(fd, clk, Nbcd);
		close(fd);

		rtc.sec = bcd(clk[Seconds] & 0x7F);
		rtc.min = bcd(clk[Minutes] & 0x7F);
		rtc.hour = bcd(clk[Hours] & 0x3F);
		if(clk[Hours] & Twelvehr){
			rtc.hour = bcd(clk[Hours] & 0x1F);
			if(clk[Hours] & Pm)
				rtc.hour += 12;
		}
		rtc.mday = bcd(clk[Mday] & 0x3F);
		rtc.mon = bcd(clk[Month] & 0x1F);
		rtc.year = bcd(clk[Year]);

		if(rtc.year < 70)
			rtc.year += 2000;
		else
			rtc.year += 1900;

		t = rtc2sec(&rtc);
		snprint(buf, 256, "%11lud ", t);
		readstr(r, buf);
		respond(r, nil);
	} else if ((intptr)(r->fid->file->aux) == 2) {
		fd = datafd();

		clk[0] = OscTrim;
		write(fd, clk, 1);
		read(fd, clk, 1);
		close(fd);

		sign = 1;
		if ((clk[0] & 0x80) == 0) {
			sign = -1;
		}

		trim = clk[0] & 0x7F;
		trim <<= 1;
		trim *= sign;

		snprint(buf, 256, "%11d ", trim);
		readstr(r, buf);
		respond(r, nil);
	} else if ((intptr)(r->fid->file->aux) == 3) {
		offs = r->ifcall.offset;

		if (offs >= 64) {
			respond(r, nil);
			return;
		}

		len = r->ifcall.count;

		if (len > 64)
			len = 64;

		len -= offs;

		fd = datafd();

		for (i = 0; i < len; i++) {
			clk[0] = i + (uchar)offs + 0x20;
			write(fd, clk, 1);

			read(fd, &buf[i], 1);
		}

		close(fd);

		r->ofcall.count = len;

		readbuf(r, buf, len);
		respond(r, nil);
	} else {
		respond(r, "file not found");
	}
}

#define PUTBCD(n,o) bcdclock[1+o] = (n % 10) | (((n / 10) % 10)<<4)

void
fswrite(Req *r)
{
	Rtc rtc;
	ulong secs;
	int sign;
	uchar bcdclock[1+Nbcd];
	char *p, *ep;
	int fd;
	long trim;
	uchar reg[2];
	int offs;
	int len;
	int i;

	if ((intptr)(r->fid->file->aux) == 1) {
		p = r->ifcall.data;
		ep = p + r->ifcall.count;

		while(p < ep)
			if (*p >= '0' && *p <= '9')
				break;
			else
				p++;

		secs = strtoul(p, 0, 0);
		sec2rtc(secs, &rtc);
		PUTBCD(rtc.sec, Seconds);
		PUTBCD(rtc.min, Minutes);	/* forces 24 hour mode */
		PUTBCD(rtc.hour, Hours);
		PUTBCD(0, Weekday);		/* hope no other OS uses this */
		PUTBCD(rtc.mday, Mday);
		PUTBCD(rtc.mon, Month);
		PUTBCD(rtc.year, Year);

		bcdclock[0] = 0;

		bcdclock[1+Seconds] |= Start;
		bcdclock[1+Weekday] |= Vbaten;

		fd = datafd();

		write(fd, bcdclock, 1+Nbcd);
		close(fd);

		respond(r, nil);
	} else if ((intptr)(r->fid->file->aux) == 2) {
		p = r->ifcall.data;
		ep = p + r->ifcall.count;

		sign = 1;
		while(p < ep) {
			if (*p == '-')
				sign = -1;
			else if (*p >= '0' && *p <= '9')
				break;
			p++;
		}

		trim = strtol(p, 0, 0);
		if (trim < 0 || trim > 254) {
			respond(r, "invalid trim value");
			return;
		}

		trim >>= 1;

		reg[0] = OscTrim;
		reg[1] = 0;
		if (sign == 1)
			reg[1] = 0x80;
		reg[1] |= (uchar)trim;

		fd = datafd();

		if (write(fd, reg, 2) != 2)
			sysfatal("write: %r");

		close(fd);

		r->ofcall.count = r->ifcall.count;

		respond(r, nil);
	} else if ((intptr)(r->fid->file->aux) == 3) {
		p = r->ifcall.data;
		len = r->ifcall.count;
		if (len > 64)
			len = 64;
		offs = r->ifcall.offset;
		if (offs > 64)
			offs = 64;
		len -= offs;

		fd = datafd();

		for (i = 0; i < len; i++) {
			reg[0] = i + offs + 0x20;
			reg[1] = p[i];
			write(fd, reg, 2);
		}

		close(fd);

		r->ofcall.count = len;

		respond(r, nil);
	} else {
		respond(r, "file not found");
	}
}

void
fsstat(Req *r)
{
	if ((intptr)(r->fid->file->aux) == 3)
		r->d.length = 64;

	respond(r, nil);
}

Srv fs = {
.read	= fsread,
.write	= fswrite,
.stat	= fsstat,
};

void
main()
{
	int fd;
	uchar reg;
	uchar buf[2];

	ctl("size 256");
	ctl("subaddress 0");

	fd = datafd();

	reg = Control;
	if (write(fd, &reg, 1) != 1)
		sysfatal("write: %r");
	if (read(fd, &reg, 1) != 1)
		sysfatal("read: %r");
	reg |= CrsTrim;
	buf[0] = Control;
	buf[1] = reg;
	if (write(fd, buf, 2) != 2)
		sysfatal("write: %r");

	close(fd);

	fs.tree = alloctree("mcp7940x", "mcp7940x", DMDIR|0555, nil);
	createfile(fs.tree->root, "rtc", "mcp7940x", 0666, (void*)1);
	createfile(fs.tree->root, "rtctrim", "mcp7940x", 0666, (void*)2);
	createfile(fs.tree->root, "rtcsram", "mcp7940x", 0666, (void*)3);
	postmountsrv(&fs, "mcp7940x", "/dev", MBEFORE);
}