xref: /plan9-contrib/sys/src/9/bcm/devrtc3231.c (revision 5c47fe09a0cc86dfb02c0ea4a2b6aec7eda2361f)
1 /*
2  * Maxim DS3231 realtime clock (accessed via rtc)
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 enum {
13 	/* DS3231 registers */
14 	Seconds=	0,
15 	Minutes=	1,
16 	Hours=		2,
17 	Weekday=	3,
18 	Mday=		4,
19 	Month=		5,
20 	Year=		6,
21 	Nbcd=		7,
22 
23 	/* Hours register may be in 12-hour or 24-hour mode */
24 	Twelvehr=	1<<6,
25 	Pm=		1<<5,
26 
27 	I2Caddr=	0x68,
28 
29 };
30 
31 typedef struct Rtc	Rtc;
32 
33 struct Rtc
34 {
35 	int	sec;
36 	int	min;
37 	int	hour;
38 	int	mday;
39 	int	mon;
40 	int	year;
41 };
42 
43 enum{
44 	Qdir = 0,
45 	Qrtc,
46 };
47 
48 Dirtab rtcdir[]={
49 	".",	{Qdir, 0, QTDIR},	0,	0555,
50 	"rtc",		{Qrtc, 0},	0,	0664,
51 };
52 
53 static ulong rtc2sec(Rtc*);
54 static void sec2rtc(ulong, Rtc*);
55 
56 static void
i2cread(uint addr,void * buf,int len)57 i2cread(uint addr, void *buf, int len)
58 {
59 	I2Cdev d;
60 
61 	d.addr = addr;
62 	d.tenbit = 0;
63 	d.salen = 0;
64 	i2crecv(&d, buf, len, 0);
65 }
66 
67 static void
i2cwrite(uint addr,void * buf,int len)68 i2cwrite(uint addr, void *buf, int len)
69 {
70 	I2Cdev d;
71 
72 	d.addr = addr;
73 	d.tenbit = 0;
74 	d.salen = 0;
75 	i2csend(&d, buf, len, 0);
76 }
77 
78 static void
rtcinit()79 rtcinit()
80 {
81 	i2csetup(0);
82 }
83 
84 static Chan*
rtcattach(char * spec)85 rtcattach(char* spec)
86 {
87 	return devattach('r', spec);
88 }
89 
90 static Walkqid*
rtcwalk(Chan * c,Chan * nc,char ** name,int nname)91 rtcwalk(Chan* c, Chan *nc, char** name, int nname)
92 {
93 	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
94 }
95 
96 static int
rtcstat(Chan * c,uchar * dp,int n)97 rtcstat(Chan* c, uchar* dp, int n)
98 {
99 	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
100 }
101 
102 static Chan*
rtcopen(Chan * c,int omode)103 rtcopen(Chan* c, int omode)
104 {
105 	char dummy;
106 
107 	omode = openmode(omode);
108 	switch((ulong)c->qid.path){
109 	case Qrtc:
110 		if(strcmp(up->user, eve)!=0 && omode!=OREAD)
111 			error(Eperm);
112 		/* if it's not there, this will throw an error */
113 		i2cread(I2Caddr, &dummy, 1);
114 		break;
115 	}
116 	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
117 }
118 
119 static void
rtcclose(Chan *)120 rtcclose(Chan*)
121 {
122 }
123 
124 static int
bcd(int n)125 bcd(int n)
126 {
127 	return (n & 0xF) + (10 * (n >> 4));
128 }
129 
130 long
rtctime(void)131 rtctime(void)
132 {
133 	uchar clk[Nbcd];
134 	Rtc rtc;
135 
136 	clk[0] = 0;
137 	i2cwrite(I2Caddr, clk, 1);
138 	i2cread(I2Caddr, clk, Nbcd);
139 
140 	/*
141 	 *  convert from BCD
142 	 */
143 	rtc.sec = bcd(clk[Seconds]);
144 	rtc.min = bcd(clk[Minutes]);
145 	rtc.hour = bcd(clk[Hours]);
146 	if(clk[Hours] & Twelvehr){
147 		rtc.hour = bcd(clk[Hours] & 0x1F);
148 		if(clk[Hours] & Pm)
149 			rtc.hour += 12;
150 	}
151 	rtc.mday = bcd(clk[Mday]);
152 	rtc.mon = bcd(clk[Month] & 0x1F);
153 	rtc.year = bcd(clk[Year]);
154 
155 	/*
156 	 *  the world starts jan 1 1970
157 	 */
158 	if(rtc.year < 70)
159 		rtc.year += 2000;
160 	else
161 		rtc.year += 1900;
162 	return rtc2sec(&rtc);
163 }
164 
165 
166 static long
rtcread(Chan * c,void * buf,long n,vlong off)167 rtcread(Chan* c, void* buf, long n, vlong off)
168 {
169 	ulong t;
170 	ulong offset = off;
171 
172 	if(c->qid.type & QTDIR)
173 		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
174 
175 	switch((ulong)c->qid.path){
176 	case Qrtc:
177 		t = rtctime();
178 		n = readnum(offset, buf, n, t, 12);
179 		return n;
180 	}
181 	error(Ebadarg);
182 	return 0;
183 }
184 
185 #define PUTBCD(n,o) bcdclock[1+o] = (n % 10) | (((n / 10) % 10)<<4)
186 
187 static long
rtcwrite(Chan * c,void * buf,long n,vlong off)188 rtcwrite(Chan* c, void* buf, long n, vlong off)
189 {
190 	Rtc rtc;
191 	ulong secs;
192 	uchar bcdclock[1+Nbcd];
193 	char *cp, *ep;
194 	ulong offset = off;
195 
196 	if(offset!=0)
197 		error(Ebadarg);
198 
199 
200 	switch((ulong)c->qid.path){
201 	case Qrtc:
202 		/*
203 		 *  read the time
204 		 */
205 		cp = ep = buf;
206 		ep += n;
207 		while(cp < ep){
208 			if(*cp>='0' && *cp<='9')
209 				break;
210 			cp++;
211 		}
212 		secs = strtoul(cp, 0, 0);
213 
214 		/*
215 		 *  convert to bcd
216 		 */
217 		sec2rtc(secs, &rtc);
218 		PUTBCD(rtc.sec, Seconds);
219 		PUTBCD(rtc.min, Minutes);	/* forces 24 hour mode */
220 		PUTBCD(rtc.hour, Hours);
221 		PUTBCD(0, Weekday);		/* hope no other OS uses this */
222 		PUTBCD(rtc.mday, Mday);
223 		PUTBCD(rtc.mon, Month);
224 		PUTBCD(rtc.year, Year);
225 
226 		/*
227 		 *  write the clock
228 		 */
229 		bcdclock[0] = 0;
230 		i2cwrite(I2Caddr, bcdclock, 1+Nbcd);
231 		return n;
232 	}
233 	error(Ebadarg);
234 	return 0;
235 }
236 
237 Dev rtc3231devtab = {
238 	'r',
239 	"rtc",
240 
241 	devreset,
242 	rtcinit,
243 	devshutdown,
244 	rtcattach,
245 	rtcwalk,
246 	rtcstat,
247 	rtcopen,
248 	devcreate,
249 	rtcclose,
250 	rtcread,
251 	devbread,
252 	rtcwrite,
253 	devbwrite,
254 	devremove,
255 	devwstat,
256 };
257 
258 #define SEC2MIN 60L
259 #define SEC2HOUR (60L*SEC2MIN)
260 #define SEC2DAY (24L*SEC2HOUR)
261 
262 /*
263  *  days per month plus days/year
264  */
265 static	int	dmsize[] =
266 {
267 	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
268 };
269 static	int	ldmsize[] =
270 {
271 	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
272 };
273 
274 /*
275  *  return the days/month for the given year
276  */
277 static int*
yrsize(int y)278 yrsize(int y)
279 {
280 	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
281 		return ldmsize;
282 	else
283 		return dmsize;
284 }
285 
286 /*
287  *  compute seconds since Jan 1 1970
288  */
289 static ulong
rtc2sec(Rtc * rtc)290 rtc2sec(Rtc *rtc)
291 {
292 	ulong secs;
293 	int i;
294 	int *d2m;
295 
296 	secs = 0;
297 
298 	/*
299 	 *  seconds per year
300 	 */
301 	for(i = 1970; i < rtc->year; i++){
302 		d2m = yrsize(i);
303 		secs += d2m[0] * SEC2DAY;
304 	}
305 
306 	/*
307 	 *  seconds per month
308 	 */
309 	d2m = yrsize(rtc->year);
310 	for(i = 1; i < rtc->mon; i++)
311 		secs += d2m[i] * SEC2DAY;
312 
313 	secs += (rtc->mday-1) * SEC2DAY;
314 	secs += rtc->hour * SEC2HOUR;
315 	secs += rtc->min * SEC2MIN;
316 	secs += rtc->sec;
317 
318 	return secs;
319 }
320 
321 /*
322  *  compute rtc from seconds since Jan 1 1970
323  */
324 static void
sec2rtc(ulong secs,Rtc * rtc)325 sec2rtc(ulong secs, Rtc *rtc)
326 {
327 	int d;
328 	long hms, day;
329 	int *d2m;
330 
331 	/*
332 	 * break initial number into days
333 	 */
334 	hms = secs % SEC2DAY;
335 	day = secs / SEC2DAY;
336 	if(hms < 0) {
337 		hms += SEC2DAY;
338 		day -= 1;
339 	}
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 	return;
371 }
372