NVRAM fixes (Jocelyn Mayer)
[qemu] / hw / m48t59.c
1 /*
2  * QEMU M48T59 NVRAM emulation for PPC PREP platform
3  * 
4  * Copyright (c) 2003-2004 Jocelyn Mayer
5  * 
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include "vl.h"
25 #include "m48t59.h"
26
27 //#define DEBUG_NVRAM
28
29 #if defined(DEBUG_NVRAM)
30 #define NVRAM_PRINTF(fmt, args...) do { printf(fmt , ##args); } while (0)
31 #else
32 #define NVRAM_PRINTF(fmt, args...) do { } while (0)
33 #endif
34
35 struct m48t59_t {
36     /* Hardware parameters */
37     int      IRQ;
38     uint32_t io_base;
39     uint16_t size;
40     /* RTC management */
41     time_t   time_offset;
42     time_t   stop_time;
43     /* Alarm & watchdog */
44     time_t   alarm;
45     struct QEMUTimer *alrm_timer;
46     struct QEMUTimer *wd_timer;
47     /* NVRAM storage */
48     uint8_t  lock;
49     uint16_t addr;
50     uint8_t *buffer;
51 };
52
53 /* Fake timer functions */
54 /* Generic helpers for BCD */
55 static inline uint8_t toBCD (uint8_t value)
56 {
57     return (((value / 10) % 10) << 4) | (value % 10);
58 }
59
60 static inline uint8_t fromBCD (uint8_t BCD)
61 {
62     return ((BCD >> 4) * 10) + (BCD & 0x0F);
63 }
64
65 /* RTC management helpers */
66 static void get_time (m48t59_t *NVRAM, struct tm *tm)
67 {
68     time_t t;
69
70     t = time(NULL) + NVRAM->time_offset;
71     localtime_r(&t, tm);
72 }
73
74 static void set_time (m48t59_t *NVRAM, struct tm *tm)
75 {
76     time_t now, new_time;
77     
78     new_time = mktime(tm);
79     now = time(NULL);
80     NVRAM->time_offset = new_time - now;
81 }
82
83 /* Alarm management */
84 static void alarm_cb (void *opaque)
85 {
86     struct tm tm, tm_now;
87     uint64_t next_time;
88     m48t59_t *NVRAM = opaque;
89
90     pic_set_irq(NVRAM->IRQ, 1);
91     if ((NVRAM->buffer[0x1FF5] & 0x80) == 0 && 
92         (NVRAM->buffer[0x1FF4] & 0x80) == 0 &&
93         (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
94         (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
95         /* Repeat once a month */
96         get_time(NVRAM, &tm_now);
97         memcpy(&tm, &tm_now, sizeof(struct tm));
98         tm.tm_mon++;
99         if (tm.tm_mon == 13) {
100             tm.tm_mon = 1;
101             tm.tm_year++;
102         }
103         next_time = mktime(&tm);
104     } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
105                (NVRAM->buffer[0x1FF4] & 0x80) == 0 &&
106                (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
107                (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
108         /* Repeat once a day */
109         next_time = 24 * 60 * 60 + mktime(&tm_now);
110     } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
111                (NVRAM->buffer[0x1FF4] & 0x80) != 0 &&
112                (NVRAM->buffer[0x1FF3] & 0x80) == 0 &&
113                (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
114         /* Repeat once an hour */
115         next_time = 60 * 60 + mktime(&tm_now);
116     } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 &&
117                (NVRAM->buffer[0x1FF4] & 0x80) != 0 &&
118                (NVRAM->buffer[0x1FF3] & 0x80) != 0 &&
119                (NVRAM->buffer[0x1FF2] & 0x80) == 0) {
120         /* Repeat once a minute */
121         next_time = 60 + mktime(&tm_now);
122     } else {
123         /* Repeat once a second */
124         next_time = 1 + mktime(&tm_now);
125     }
126     qemu_mod_timer(NVRAM->alrm_timer, next_time * 1000);
127     pic_set_irq(NVRAM->IRQ, 0);
128 }
129
130
131 static void get_alarm (m48t59_t *NVRAM, struct tm *tm)
132 {
133     localtime_r(&NVRAM->alarm, tm);
134 }
135
136 static void set_alarm (m48t59_t *NVRAM, struct tm *tm)
137 {
138     NVRAM->alarm = mktime(tm);
139     if (NVRAM->alrm_timer != NULL) {
140         qemu_del_timer(NVRAM->alrm_timer);
141         NVRAM->alrm_timer = NULL;
142     }
143     if (NVRAM->alarm - time(NULL) > 0)
144         qemu_mod_timer(NVRAM->alrm_timer, NVRAM->alarm * 1000);
145 }
146
147 /* Watchdog management */
148 static void watchdog_cb (void *opaque)
149 {
150     m48t59_t *NVRAM = opaque;
151
152     NVRAM->buffer[0x1FF0] |= 0x80;
153     if (NVRAM->buffer[0x1FF7] & 0x80) {
154         NVRAM->buffer[0x1FF7] = 0x00;
155         NVRAM->buffer[0x1FFC] &= ~0x40;
156         /* May it be a hw CPU Reset instead ? */
157         reset_requested = 1;
158         printf("Watchdog reset...\n");
159         cpu_interrupt(cpu_single_env, CPU_INTERRUPT_EXIT);
160     } else {
161         pic_set_irq(NVRAM->IRQ, 1);
162         pic_set_irq(NVRAM->IRQ, 0);
163     }
164 }
165
166 static void set_up_watchdog (m48t59_t *NVRAM, uint8_t value)
167 {
168     uint64_t interval; /* in 1/16 seconds */
169
170     if (NVRAM->wd_timer != NULL) {
171         qemu_del_timer(NVRAM->wd_timer);
172         NVRAM->wd_timer = NULL;
173     }
174     NVRAM->buffer[0x1FF0] &= ~0x80;
175     if (value != 0) {
176         interval = (1 << (2 * (value & 0x03))) * ((value >> 2) & 0x1F);
177         qemu_mod_timer(NVRAM->wd_timer, ((uint64_t)time(NULL) * 1000) +
178                        ((interval * 1000) >> 4));
179     }
180 }
181
182 /* Direct access to NVRAM */
183 void m48t59_write (m48t59_t *NVRAM, uint32_t val)
184 {
185     struct tm tm;
186     int tmp;
187
188     if (NVRAM->addr > 0x1FF8 && NVRAM->addr < 0x2000)
189         NVRAM_PRINTF("%s: 0x%08x => 0x%08x\n", __func__, NVRAM->addr, val);
190     switch (NVRAM->addr) {
191     case 0x1FF0:
192         /* flags register : read-only */
193         break;
194     case 0x1FF1:
195         /* unused */
196         break;
197     case 0x1FF2:
198         /* alarm seconds */
199         tmp = fromBCD(val & 0x7F);
200         if (tmp >= 0 && tmp <= 59) {
201             get_alarm(NVRAM, &tm);
202             tm.tm_sec = tmp;
203             NVRAM->buffer[0x1FF2] = val;
204             set_alarm(NVRAM, &tm);
205         }
206         break;
207     case 0x1FF3:
208         /* alarm minutes */
209         tmp = fromBCD(val & 0x7F);
210         if (tmp >= 0 && tmp <= 59) {
211             get_alarm(NVRAM, &tm);
212             tm.tm_min = tmp;
213             NVRAM->buffer[0x1FF3] = val;
214             set_alarm(NVRAM, &tm);
215         }
216         break;
217     case 0x1FF4:
218         /* alarm hours */
219         tmp = fromBCD(val & 0x3F);
220         if (tmp >= 0 && tmp <= 23) {
221             get_alarm(NVRAM, &tm);
222             tm.tm_hour = tmp;
223             NVRAM->buffer[0x1FF4] = val;
224             set_alarm(NVRAM, &tm);
225         }
226         break;
227     case 0x1FF5:
228         /* alarm date */
229         tmp = fromBCD(val & 0x1F);
230         if (tmp != 0) {
231             get_alarm(NVRAM, &tm);
232             tm.tm_mday = tmp;
233             NVRAM->buffer[0x1FF5] = val;
234             set_alarm(NVRAM, &tm);
235         }
236         break;
237     case 0x1FF6:
238         /* interrupts */
239         NVRAM->buffer[0x1FF6] = val;
240         break;
241     case 0x1FF7:
242         /* watchdog */
243         NVRAM->buffer[0x1FF7] = val;
244         set_up_watchdog(NVRAM, val);
245         break;
246     case 0x1FF8:
247         /* control */
248         NVRAM->buffer[0x1FF8] = (val & ~0xA0) | 0x90;
249         break;
250     case 0x1FF9:
251         /* seconds (BCD) */
252         tmp = fromBCD(val & 0x7F);
253         if (tmp >= 0 && tmp <= 59) {
254             get_time(NVRAM, &tm);
255             tm.tm_sec = tmp;
256             set_time(NVRAM, &tm);
257         }
258         if ((val & 0x80) ^ (NVRAM->buffer[0x1FF9] & 0x80)) {
259             if (val & 0x80) {
260                 NVRAM->stop_time = time(NULL);
261             } else {
262                 NVRAM->time_offset += NVRAM->stop_time - time(NULL);
263                 NVRAM->stop_time = 0;
264             }
265         }
266         NVRAM->buffer[0x1FF9] = val & 0x80;
267         break;
268     case 0x1FFA:
269         /* minutes (BCD) */
270         tmp = fromBCD(val & 0x7F);
271         if (tmp >= 0 && tmp <= 59) {
272             get_time(NVRAM, &tm);
273             tm.tm_min = tmp;
274             set_time(NVRAM, &tm);
275         }
276         break;
277     case 0x1FFB:
278         /* hours (BCD) */
279         tmp = fromBCD(val & 0x3F);
280         if (tmp >= 0 && tmp <= 23) {
281             get_time(NVRAM, &tm);
282             tm.tm_hour = tmp;
283             set_time(NVRAM, &tm);
284         }
285         break;
286     case 0x1FFC:
287         /* day of the week / century */
288         tmp = fromBCD(val & 0x07);
289         get_time(NVRAM, &tm);
290         tm.tm_wday = tmp;
291         set_time(NVRAM, &tm);
292         NVRAM->buffer[0x1FFC] = val & 0x40;
293         break;
294     case 0x1FFD:
295         /* date */
296         tmp = fromBCD(val & 0x1F);
297         if (tmp != 0) {
298             get_time(NVRAM, &tm);
299             tm.tm_mday = tmp;
300             set_time(NVRAM, &tm);
301         }
302         break;
303     case 0x1FFE:
304         /* month */
305         tmp = fromBCD(val & 0x1F);
306         if (tmp >= 1 && tmp <= 12) {
307             get_time(NVRAM, &tm);
308             tm.tm_mon = tmp - 1;
309             set_time(NVRAM, &tm);
310         }
311         break;
312     case 0x1FFF:
313         /* year */
314         tmp = fromBCD(val);
315         if (tmp >= 0 && tmp <= 99) {
316             get_time(NVRAM, &tm);
317             tm.tm_year = fromBCD(val);
318             set_time(NVRAM, &tm);
319         }
320         break;
321     default:
322         /* Check lock registers state */
323         if (NVRAM->addr >= 0x20 && NVRAM->addr <= 0x2F && (NVRAM->lock & 1))
324             break;
325         if (NVRAM->addr >= 0x30 && NVRAM->addr <= 0x3F && (NVRAM->lock & 2))
326             break;
327         if (NVRAM->addr < 0x1FF0 ||
328             (NVRAM->addr > 0x1FFF && NVRAM->addr < NVRAM->size)) {
329             NVRAM->buffer[NVRAM->addr] = val & 0xFF;
330         }
331         break;
332     }
333 }
334
335 uint32_t m48t59_read (m48t59_t *NVRAM)
336 {
337     struct tm tm;
338     uint32_t retval = 0xFF;
339
340     switch (NVRAM->addr) {
341     case 0x1FF0:
342         /* flags register */
343         goto do_read;
344     case 0x1FF1:
345         /* unused */
346         retval = 0;
347         break;
348     case 0x1FF2:
349         /* alarm seconds */
350         goto do_read;
351     case 0x1FF3:
352         /* alarm minutes */
353         goto do_read;
354     case 0x1FF4:
355         /* alarm hours */
356         goto do_read;
357     case 0x1FF5:
358         /* alarm date */
359         goto do_read;
360     case 0x1FF6:
361         /* interrupts */
362         goto do_read;
363     case 0x1FF7:
364         /* A read resets the watchdog */
365         set_up_watchdog(NVRAM, NVRAM->buffer[0x1FF7]);
366         goto do_read;
367     case 0x1FF8:
368         /* control */
369         goto do_read;
370     case 0x1FF9:
371         /* seconds (BCD) */
372         get_time(NVRAM, &tm);
373         retval = (NVRAM->buffer[0x1FF9] & 0x80) | toBCD(tm.tm_sec);
374         break;
375     case 0x1FFA:
376         /* minutes (BCD) */
377         get_time(NVRAM, &tm);
378         retval = toBCD(tm.tm_min);
379         break;
380     case 0x1FFB:
381         /* hours (BCD) */
382         get_time(NVRAM, &tm);
383         retval = toBCD(tm.tm_hour);
384         break;
385     case 0x1FFC:
386         /* day of the week / century */
387         get_time(NVRAM, &tm);
388         retval = NVRAM->buffer[0x1FFC] | tm.tm_wday;
389         break;
390     case 0x1FFD:
391         /* date */
392         get_time(NVRAM, &tm);
393         retval = toBCD(tm.tm_mday);
394         break;
395     case 0x1FFE:
396         /* month */
397         get_time(NVRAM, &tm);
398         retval = toBCD(tm.tm_mon + 1);
399         break;
400     case 0x1FFF:
401         /* year */
402         get_time(NVRAM, &tm);
403         retval = toBCD(tm.tm_year);
404         break;
405     default:
406         /* Check lock registers state */
407         if (NVRAM->addr >= 0x20 && NVRAM->addr <= 0x2F && (NVRAM->lock & 1))
408             break;
409         if (NVRAM->addr >= 0x30 && NVRAM->addr <= 0x3F && (NVRAM->lock & 2))
410             break;
411         if (NVRAM->addr < 0x1FF0 ||
412             (NVRAM->addr > 0x1FFF && NVRAM->addr < NVRAM->size)) {
413         do_read:
414             retval = NVRAM->buffer[NVRAM->addr];
415         }
416         break;
417     }
418     if (NVRAM->addr > 0x1FF9 && NVRAM->addr < 0x2000)
419         NVRAM_PRINTF("0x%08x <= 0x%08x\n", NVRAM->addr, retval);
420
421     return retval;
422 }
423
424 void m48t59_set_addr (m48t59_t *NVRAM, uint32_t addr)
425 {
426     NVRAM->addr = addr;
427 }
428
429 void m48t59_toggle_lock (m48t59_t *NVRAM, int lock)
430 {
431     NVRAM->lock ^= 1 << lock;
432 }
433
434 /* IO access to NVRAM */
435 static void NVRAM_writeb (void *opaque, uint32_t addr, uint32_t val)
436 {
437     m48t59_t *NVRAM = opaque;
438
439     addr -= NVRAM->io_base;
440     NVRAM_PRINTF("0x%08x => 0x%08x\n", addr, val);
441     switch (addr) {
442     case 0:
443         NVRAM->addr &= ~0x00FF;
444         NVRAM->addr |= val;
445         break;
446     case 1:
447         NVRAM->addr &= ~0xFF00;
448         NVRAM->addr |= val << 8;
449         break;
450     case 3:
451         m48t59_write(NVRAM, val);
452         NVRAM->addr = 0x0000;
453         break;
454     default:
455         break;
456     }
457 }
458
459 static uint32_t NVRAM_readb (void *opaque, uint32_t addr)
460 {
461     m48t59_t *NVRAM = opaque;
462     uint32_t retval;
463
464     addr -= NVRAM->io_base;
465     switch (addr) {
466     case 3:
467         retval = m48t59_read(NVRAM);
468         break;
469     default:
470         retval = -1;
471         break;
472     }
473     NVRAM_PRINTF("0x%08x <= 0x%08x\n", addr, retval);
474
475     return retval;
476 }
477
478 /* Initialisation routine */
479 m48t59_t *m48t59_init (int IRQ, uint32_t io_base, uint16_t size)
480 {
481     m48t59_t *s;
482
483     s = qemu_mallocz(sizeof(m48t59_t));
484     if (!s)
485         return NULL;
486     s->buffer = qemu_mallocz(size);
487     if (!s->buffer) {
488         qemu_free(s);
489         return NULL;
490     }
491     s->IRQ = IRQ;
492     s->size = size;
493     s->io_base = io_base;
494     s->addr = 0;
495     register_ioport_read(io_base, 0x04, 1, NVRAM_readb, s);
496     register_ioport_write(io_base, 0x04, 1, NVRAM_writeb, s);
497     s->alrm_timer = qemu_new_timer(vm_clock, &alarm_cb, s);
498     s->wd_timer = qemu_new_timer(vm_clock, &watchdog_cb, s);
499     s->lock = 0;
500
501     return s;
502 }