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