1 /*
2 * DS1339 Timekeeper (on I2C)
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 #include "io.h"
13
14 typedef struct Rtc Rtc;
15 typedef struct Rtcreg Rtcreg;
16
17 struct Rtc
18 {
19 int sec;
20 int min;
21 int hour;
22 int wday;
23 int mday;
24 int mon;
25 int year;
26 };
27
28 struct Rtcreg
29 {
30 uchar sec;
31 uchar min;
32 uchar hour;
33 uchar wday; /* 1=Sun */
34 uchar mday; /* 00-31 */
35 uchar mon; /* 1-12 */
36 uchar year;
37 };
38
39 enum{
40 Qdir = 0,
41 Qrtc,
42
43 Rtclen= 7, /* bytes read and written to timekeeper */
44 };
45
46 static QLock rtclock; /* mutex on nvram operations */
47 static I2Cdev rtdev;
48
49 static Dirtab rtcdir[]={
50 ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
51 "rtc", {Qrtc, 0}, 0, 0664,
52 };
53
54 static ulong rtc2sec(Rtc*);
55 static void sec2rtc(ulong, Rtc*);
56 static void setrtc(Rtc*);
57
58 static void
rtcreset(void)59 rtcreset(void)
60 {
61 rtdev.addr = 0x68;
62 rtdev.salen = 1;
63 i2csetup(1);
64 }
65
66 static Chan*
rtcattach(char * spec)67 rtcattach(char *spec)
68 {
69 return devattach('r', spec);
70 }
71
72 static Walkqid*
rtcwalk(Chan * c,Chan * nc,char ** name,int nname)73 rtcwalk(Chan *c, Chan *nc, char **name, int nname)
74 {
75 return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
76 }
77
78 static int
rtcstat(Chan * c,uchar * dp,int n)79 rtcstat(Chan *c, uchar *dp, int n)
80 {
81 return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
82 }
83
84 static Chan*
rtcopen(Chan * c,int omode)85 rtcopen(Chan *c, int omode)
86 {
87 omode = openmode(omode);
88 switch((ulong)c->qid.path){
89 case Qrtc:
90 if(strcmp(up->env->user, eve)!=0 && omode!=OREAD)
91 error(Eperm);
92 break;
93 }
94 return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
95 }
96
97 static void
rtcclose(Chan *)98 rtcclose(Chan*)
99 {
100 }
101
102 static long
rtcread(Chan * c,void * buf,long n,vlong offset)103 rtcread(Chan *c, void *buf, long n, vlong offset)
104 {
105 ulong t, ot;
106
107 if(c->qid.type & QTDIR)
108 return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
109
110 switch((ulong)c->qid.path){
111 case Qrtc:
112 qlock(&rtclock);
113 t = rtctime();
114 do{
115 ot = t;
116 t = rtctime(); /* make sure there's no skew */
117 }while(t != ot);
118 qunlock(&rtclock);
119 return readnum(offset, buf, n, t, 12);
120 }
121 error(Egreg);
122 return -1; /* never reached */
123 }
124
125 static long
rtcwrite(Chan * c,void * buf,long n,vlong off)126 rtcwrite(Chan *c, void *buf, long n, vlong off)
127 {
128 Rtc rtc;
129 ulong secs;
130 char *cp, sbuf[32];
131 ulong offset = off;
132
133 switch((ulong)c->qid.path){
134 case Qrtc:
135 if(offset!=0 || n >= sizeof(sbuf)-1)
136 error(Ebadarg);
137 memmove(sbuf, buf, n);
138 sbuf[n] = '\0';
139 /*
140 * read the time
141 */
142 cp = sbuf;
143 while(*cp){
144 if(*cp>='0' && *cp<='9')
145 break;
146 cp++;
147 }
148 secs = strtoul(cp, 0, 0);
149 /*
150 * convert to bcd
151 */
152 sec2rtc(secs, &rtc);
153 /*
154 * write it
155 */
156 setrtc(&rtc);
157 return n;
158 }
159 error(Egreg);
160 return -1; /* never reached */
161 }
162
163 Dev rtcdevtab = {
164 'r',
165 "rtc",
166
167 rtcreset,
168 devinit,
169 devshutdown,
170 rtcattach,
171 rtcwalk,
172 rtcstat,
173 rtcopen,
174 devcreate,
175 rtcclose,
176 rtcread,
177 devbread,
178 rtcwrite,
179 devbwrite,
180 devremove,
181 devwstat,
182 };
183
184 static int
getbcd(int bcd)185 getbcd(int bcd)
186 {
187 return (bcd&0x0f) + 10 * (bcd>>4);
188 }
189
190 static int
putbcd(int val)191 putbcd(int val)
192 {
193 return (val % 10) | (((val/10) % 10) << 4);
194 }
195
196 long
rtctime(void)197 rtctime(void)
198 {
199 Rtc rtc;
200 Rtcreg d;
201 int h;
202
203 if(waserror()){
204 iprint("rtc: err %s\n", up->env->errstr);
205 return 0;
206 }
207 if(i2crecv(&rtdev, &d, Rtclen, 0) != Rtclen)
208 return 0;
209 poperror();
210 rtc.sec = getbcd(d.sec);
211 rtc.min = getbcd(d.min);
212 if(d.hour & (1<<6)){ /* 12 hour clock */
213 h = d.hour & 0x1F;
214 if(d.hour & (1<<5))
215 h += 0x12;
216 rtc.hour = getbcd(h);
217 }else
218 rtc.hour = getbcd(d.hour);
219 rtc.mday = getbcd(d.mday);
220 rtc.mon = getbcd(d.mon & 0x7f);
221 rtc.year = getbcd(d.year);
222 if(rtc.mon < 1 || rtc.mon > 12)
223 return 0;
224 if(d.mon & (1<<7))
225 rtc.year += 2000;
226 else
227 rtc.year += 1900;
228 return rtc2sec(&rtc);
229 }
230
231 static void
setrtc(Rtc * rtc)232 setrtc(Rtc *rtc)
233 {
234 Rtcreg d;
235
236 memset(&d, 0, sizeof(d));
237 d.year = putbcd(rtc->year % 100);
238 d.mon = putbcd(rtc->mon);
239 if(rtc->year >= 2000)
240 d.mon |= 1<<7;
241 d.wday = rtc->wday+1;
242 d.mday = putbcd(rtc->mday);
243 d.hour = putbcd(rtc->hour);
244 d.min = putbcd(rtc->min);
245 d.sec = putbcd(rtc->sec);
246 i2csend(&rtdev, &d, Rtclen, 0);
247 }
248
249 #define SEC2MIN 60L
250 #define SEC2HOUR (60L*SEC2MIN)
251 #define SEC2DAY (24L*SEC2HOUR)
252
253 /*
254 * days per month plus days/year
255 */
256 static int dmsize[] =
257 {
258 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
259 };
260 static int ldmsize[] =
261 {
262 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
263 };
264
265 /*
266 * return the days/month for the given year
267 */
268 static int *
yrsize(int y)269 yrsize(int y)
270 {
271
272 if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
273 return ldmsize;
274 else
275 return dmsize;
276 }
277
278 /*
279 * compute seconds since Jan 1 1970
280 */
281 static ulong
rtc2sec(Rtc * rtc)282 rtc2sec(Rtc *rtc)
283 {
284 ulong secs;
285 int i;
286 int *d2m;
287
288 secs = 0;
289
290 /*
291 * seconds per year
292 */
293 for(i = 1970; i < rtc->year; i++){
294 d2m = yrsize(i);
295 secs += d2m[0] * SEC2DAY;
296 }
297
298 /*
299 * seconds per month
300 */
301 d2m = yrsize(rtc->year);
302 for(i = 1; i < rtc->mon; i++)
303 secs += d2m[i] * SEC2DAY;
304
305 secs += (rtc->mday-1) * SEC2DAY;
306 secs += rtc->hour * SEC2HOUR;
307 secs += rtc->min * SEC2MIN;
308 secs += rtc->sec;
309
310 return secs;
311 }
312
313 /*
314 * compute rtc from seconds since Jan 1 1970
315 */
316 static void
sec2rtc(ulong secs,Rtc * rtc)317 sec2rtc(ulong secs, Rtc *rtc)
318 {
319 int d;
320 long hms, day;
321 int *d2m;
322
323 /*
324 * break initial number into days
325 */
326 hms = secs % SEC2DAY;
327 day = secs / SEC2DAY;
328 if(hms < 0) {
329 hms += SEC2DAY;
330 day -= 1;
331 }
332
333 /*
334 * day is the day number.
335 * generate day of the week.
336 * The addend is 4 mod 7 (1/1/1970 was Thursday)
337 */
338
339 rtc->wday = (day + 7340036L) % 7;
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