]> Git Repo - qemu.git/blob - hw/exynos4210_rtc.c
Merge branch 'eflags3' of git://github.com/rth7680/qemu
[qemu.git] / hw / exynos4210_rtc.c
1 /*
2  * Samsung exynos4210 Real Time Clock
3  *
4  * Copyright (c) 2012 Samsung Electronics Co., Ltd.
5  *  Ogurtsov Oleg <[email protected]>
6  *
7  *  This program is free software; you can redistribute it and/or modify it
8  *  under the terms of the GNU General Public License as published by the
9  *  Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful, but WITHOUT
13  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15  *  for more details.
16  *
17  *  You should have received a copy of the GNU General Public License along
18  *  with this program; if not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21
22 /* Description:
23  * Register RTCCON:
24  *  CLKSEL Bit[1] not used
25  *  CLKOUTEN Bit[9] not used
26  */
27
28 #include "sysbus.h"
29 #include "qemu/timer.h"
30 #include "qemu-common.h"
31 #include "ptimer.h"
32
33 #include "hw.h"
34 #include "qemu/timer.h"
35 #include "sysemu/sysemu.h"
36
37 #include "exynos4210.h"
38
39 #define DEBUG_RTC 0
40
41 #if DEBUG_RTC
42 #define DPRINTF(fmt, ...) \
43         do { fprintf(stdout, "RTC: [%24s:%5d] " fmt, __func__, __LINE__, \
44                 ## __VA_ARGS__); } while (0)
45 #else
46 #define DPRINTF(fmt, ...) do {} while (0)
47 #endif
48
49 #define     EXYNOS4210_RTC_REG_MEM_SIZE     0x0100
50
51 #define     INTP            0x0030
52 #define     RTCCON          0x0040
53 #define     TICCNT          0x0044
54 #define     RTCALM          0x0050
55 #define     ALMSEC          0x0054
56 #define     ALMMIN          0x0058
57 #define     ALMHOUR         0x005C
58 #define     ALMDAY          0x0060
59 #define     ALMMON          0x0064
60 #define     ALMYEAR         0x0068
61 #define     BCDSEC          0x0070
62 #define     BCDMIN          0x0074
63 #define     BCDHOUR         0x0078
64 #define     BCDDAY          0x007C
65 #define     BCDDAYWEEK      0x0080
66 #define     BCDMON          0x0084
67 #define     BCDYEAR         0x0088
68 #define     CURTICNT        0x0090
69
70 #define     TICK_TIMER_ENABLE   0x0100
71 #define     TICNT_THRESHHOLD    2
72
73
74 #define     RTC_ENABLE          0x0001
75
76 #define     INTP_TICK_ENABLE    0x0001
77 #define     INTP_ALM_ENABLE     0x0002
78
79 #define     ALARM_INT_ENABLE    0x0040
80
81 #define     RTC_BASE_FREQ       32768
82
83 typedef struct Exynos4210RTCState {
84     SysBusDevice busdev;
85     MemoryRegion iomem;
86
87     /* registers */
88     uint32_t    reg_intp;
89     uint32_t    reg_rtccon;
90     uint32_t    reg_ticcnt;
91     uint32_t    reg_rtcalm;
92     uint32_t    reg_almsec;
93     uint32_t    reg_almmin;
94     uint32_t    reg_almhour;
95     uint32_t    reg_almday;
96     uint32_t    reg_almmon;
97     uint32_t    reg_almyear;
98     uint32_t    reg_curticcnt;
99
100     ptimer_state    *ptimer;        /* tick timer */
101     ptimer_state    *ptimer_1Hz;    /* clock timer */
102     uint32_t        freq;
103
104     qemu_irq        tick_irq;   /* Time Tick Generator irq */
105     qemu_irq        alm_irq;    /* alarm irq */
106
107     struct tm   current_tm;     /* current time */
108 } Exynos4210RTCState;
109
110 #define TICCKSEL(value) ((value & (0x0F << 4)) >> 4)
111
112 /*** VMState ***/
113 static const VMStateDescription vmstate_exynos4210_rtc_state = {
114     .name = "exynos4210.rtc",
115     .version_id = 1,
116     .minimum_version_id = 1,
117     .minimum_version_id_old = 1,
118     .fields = (VMStateField[]) {
119         VMSTATE_UINT32(reg_intp, Exynos4210RTCState),
120         VMSTATE_UINT32(reg_rtccon, Exynos4210RTCState),
121         VMSTATE_UINT32(reg_ticcnt, Exynos4210RTCState),
122         VMSTATE_UINT32(reg_rtcalm, Exynos4210RTCState),
123         VMSTATE_UINT32(reg_almsec, Exynos4210RTCState),
124         VMSTATE_UINT32(reg_almmin, Exynos4210RTCState),
125         VMSTATE_UINT32(reg_almhour, Exynos4210RTCState),
126         VMSTATE_UINT32(reg_almday, Exynos4210RTCState),
127         VMSTATE_UINT32(reg_almmon, Exynos4210RTCState),
128         VMSTATE_UINT32(reg_almyear, Exynos4210RTCState),
129         VMSTATE_UINT32(reg_curticcnt, Exynos4210RTCState),
130         VMSTATE_PTIMER(ptimer, Exynos4210RTCState),
131         VMSTATE_PTIMER(ptimer_1Hz, Exynos4210RTCState),
132         VMSTATE_UINT32(freq, Exynos4210RTCState),
133         VMSTATE_INT32(current_tm.tm_sec, Exynos4210RTCState),
134         VMSTATE_INT32(current_tm.tm_min, Exynos4210RTCState),
135         VMSTATE_INT32(current_tm.tm_hour, Exynos4210RTCState),
136         VMSTATE_INT32(current_tm.tm_wday, Exynos4210RTCState),
137         VMSTATE_INT32(current_tm.tm_mday, Exynos4210RTCState),
138         VMSTATE_INT32(current_tm.tm_mon, Exynos4210RTCState),
139         VMSTATE_INT32(current_tm.tm_year, Exynos4210RTCState),
140         VMSTATE_END_OF_LIST()
141     }
142 };
143
144 #define BCD3DIGITS(x) \
145     ((uint32_t)to_bcd((uint8_t)(x % 100)) + \
146     ((uint32_t)to_bcd((uint8_t)((x % 1000) / 100)) << 8))
147
148 static void check_alarm_raise(Exynos4210RTCState *s)
149 {
150     unsigned int alarm_raise = 0;
151     struct tm stm = s->current_tm;
152
153     if ((s->reg_rtcalm & 0x01) &&
154         (to_bcd((uint8_t)stm.tm_sec) == (uint8_t)s->reg_almsec)) {
155         alarm_raise = 1;
156     }
157     if ((s->reg_rtcalm & 0x02) &&
158         (to_bcd((uint8_t)stm.tm_min) == (uint8_t)s->reg_almmin)) {
159         alarm_raise = 1;
160     }
161     if ((s->reg_rtcalm & 0x04) &&
162         (to_bcd((uint8_t)stm.tm_hour) == (uint8_t)s->reg_almhour)) {
163         alarm_raise = 1;
164     }
165     if ((s->reg_rtcalm & 0x08) &&
166         (to_bcd((uint8_t)stm.tm_mday) == (uint8_t)s->reg_almday)) {
167         alarm_raise = 1;
168     }
169     if ((s->reg_rtcalm & 0x10) &&
170          (to_bcd((uint8_t)stm.tm_mon) == (uint8_t)s->reg_almmon)) {
171         alarm_raise = 1;
172     }
173     if ((s->reg_rtcalm & 0x20) &&
174         (BCD3DIGITS(stm.tm_year) == s->reg_almyear)) {
175         alarm_raise = 1;
176     }
177
178     if (alarm_raise) {
179         DPRINTF("ALARM IRQ\n");
180         /* set irq status */
181         s->reg_intp |= INTP_ALM_ENABLE;
182         qemu_irq_raise(s->alm_irq);
183     }
184 }
185
186 /*
187  * RTC update frequency
188  * Parameters:
189  *     reg_value - current RTCCON register or his new value
190  */
191 static void exynos4210_rtc_update_freq(Exynos4210RTCState *s,
192                                        uint32_t reg_value)
193 {
194     uint32_t freq;
195
196     freq = s->freq;
197     /* set frequncy for time generator */
198     s->freq = RTC_BASE_FREQ / (1 << TICCKSEL(reg_value));
199
200     if (freq != s->freq) {
201         ptimer_set_freq(s->ptimer, s->freq);
202         DPRINTF("freq=%dHz\n", s->freq);
203     }
204 }
205
206 /* month is between 0 and 11. */
207 static int get_days_in_month(int month, int year)
208 {
209     static const int days_tab[12] = {
210         31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
211     };
212     int d;
213     if ((unsigned)month >= 12) {
214         return 31;
215     }
216     d = days_tab[month];
217     if (month == 1) {
218         if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
219             d++;
220         }
221     }
222     return d;
223 }
224
225 /* update 'tm' to the next second */
226 static void rtc_next_second(struct tm *tm)
227 {
228     int days_in_month;
229
230     tm->tm_sec++;
231     if ((unsigned)tm->tm_sec >= 60) {
232         tm->tm_sec = 0;
233         tm->tm_min++;
234         if ((unsigned)tm->tm_min >= 60) {
235             tm->tm_min = 0;
236             tm->tm_hour++;
237             if ((unsigned)tm->tm_hour >= 24) {
238                 tm->tm_hour = 0;
239                 /* next day */
240                 tm->tm_wday++;
241                 if ((unsigned)tm->tm_wday >= 7) {
242                     tm->tm_wday = 0;
243                 }
244                 days_in_month = get_days_in_month(tm->tm_mon,
245                                                   tm->tm_year + 1900);
246                 tm->tm_mday++;
247                 if (tm->tm_mday < 1) {
248                     tm->tm_mday = 1;
249                 } else if (tm->tm_mday > days_in_month) {
250                     tm->tm_mday = 1;
251                     tm->tm_mon++;
252                     if (tm->tm_mon >= 12) {
253                         tm->tm_mon = 0;
254                         tm->tm_year++;
255                     }
256                 }
257             }
258         }
259     }
260 }
261
262 /*
263  * tick handler
264  */
265 static void exynos4210_rtc_tick(void *opaque)
266 {
267     Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
268
269     DPRINTF("TICK IRQ\n");
270     /* set irq status */
271     s->reg_intp |= INTP_TICK_ENABLE;
272     /* raise IRQ */
273     qemu_irq_raise(s->tick_irq);
274
275     /* restart timer */
276     ptimer_set_count(s->ptimer, s->reg_ticcnt);
277     ptimer_run(s->ptimer, 1);
278 }
279
280 /*
281  * 1Hz clock handler
282  */
283 static void exynos4210_rtc_1Hz_tick(void *opaque)
284 {
285     Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
286
287     rtc_next_second(&s->current_tm);
288     /* DPRINTF("1Hz tick\n"); */
289
290     /* raise IRQ */
291     if (s->reg_rtcalm & ALARM_INT_ENABLE) {
292         check_alarm_raise(s);
293     }
294
295     ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
296     ptimer_run(s->ptimer_1Hz, 1);
297 }
298
299 /*
300  * RTC Read
301  */
302 static uint64_t exynos4210_rtc_read(void *opaque, hwaddr offset,
303         unsigned size)
304 {
305     uint32_t value = 0;
306     Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
307
308     switch (offset) {
309     case INTP:
310         value = s->reg_intp;
311         break;
312     case RTCCON:
313         value = s->reg_rtccon;
314         break;
315     case TICCNT:
316         value = s->reg_ticcnt;
317         break;
318     case RTCALM:
319         value = s->reg_rtcalm;
320         break;
321     case ALMSEC:
322         value = s->reg_almsec;
323         break;
324     case ALMMIN:
325         value = s->reg_almmin;
326         break;
327     case ALMHOUR:
328         value = s->reg_almhour;
329         break;
330     case ALMDAY:
331         value = s->reg_almday;
332         break;
333     case ALMMON:
334         value = s->reg_almmon;
335         break;
336     case ALMYEAR:
337         value = s->reg_almyear;
338         break;
339
340     case BCDSEC:
341         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_sec);
342         break;
343     case BCDMIN:
344         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_min);
345         break;
346     case BCDHOUR:
347         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_hour);
348         break;
349     case BCDDAYWEEK:
350         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_wday);
351         break;
352     case BCDDAY:
353         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mday);
354         break;
355     case BCDMON:
356         value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mon + 1);
357         break;
358     case BCDYEAR:
359         value = BCD3DIGITS(s->current_tm.tm_year);
360         break;
361
362     case CURTICNT:
363         s->reg_curticcnt = ptimer_get_count(s->ptimer);
364         value = s->reg_curticcnt;
365         break;
366
367     default:
368         fprintf(stderr,
369                 "[exynos4210.rtc: bad read offset " TARGET_FMT_plx "]\n",
370                 offset);
371         break;
372     }
373     return value;
374 }
375
376 /*
377  * RTC Write
378  */
379 static void exynos4210_rtc_write(void *opaque, hwaddr offset,
380         uint64_t value, unsigned size)
381 {
382     Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
383
384     switch (offset) {
385     case INTP:
386         if (value & INTP_ALM_ENABLE) {
387             qemu_irq_lower(s->alm_irq);
388             s->reg_intp &= (~INTP_ALM_ENABLE);
389         }
390         if (value & INTP_TICK_ENABLE) {
391             qemu_irq_lower(s->tick_irq);
392             s->reg_intp &= (~INTP_TICK_ENABLE);
393         }
394         break;
395     case RTCCON:
396         if (value & RTC_ENABLE) {
397             exynos4210_rtc_update_freq(s, value);
398         }
399         if ((value & RTC_ENABLE) > (s->reg_rtccon & RTC_ENABLE)) {
400             /* clock timer */
401             ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
402             ptimer_run(s->ptimer_1Hz, 1);
403             DPRINTF("run clock timer\n");
404         }
405         if ((value & RTC_ENABLE) < (s->reg_rtccon & RTC_ENABLE)) {
406             /* tick timer */
407             ptimer_stop(s->ptimer);
408             /* clock timer */
409             ptimer_stop(s->ptimer_1Hz);
410             DPRINTF("stop all timers\n");
411         }
412         if (value & RTC_ENABLE) {
413             if ((value & TICK_TIMER_ENABLE) >
414                 (s->reg_rtccon & TICK_TIMER_ENABLE) &&
415                 (s->reg_ticcnt)) {
416                 ptimer_set_count(s->ptimer, s->reg_ticcnt);
417                 ptimer_run(s->ptimer, 1);
418                 DPRINTF("run tick timer\n");
419             }
420             if ((value & TICK_TIMER_ENABLE) <
421                 (s->reg_rtccon & TICK_TIMER_ENABLE)) {
422                 ptimer_stop(s->ptimer);
423             }
424         }
425         s->reg_rtccon = value;
426         break;
427     case TICCNT:
428         if (value > TICNT_THRESHHOLD) {
429             s->reg_ticcnt = value;
430         } else {
431             fprintf(stderr,
432                     "[exynos4210.rtc: bad TICNT value %u ]\n",
433                     (uint32_t)value);
434         }
435         break;
436
437     case RTCALM:
438         s->reg_rtcalm = value;
439         break;
440     case ALMSEC:
441         s->reg_almsec = (value & 0x7f);
442         break;
443     case ALMMIN:
444         s->reg_almmin = (value & 0x7f);
445         break;
446     case ALMHOUR:
447         s->reg_almhour = (value & 0x3f);
448         break;
449     case ALMDAY:
450         s->reg_almday = (value & 0x3f);
451         break;
452     case ALMMON:
453         s->reg_almmon = (value & 0x1f);
454         break;
455     case ALMYEAR:
456         s->reg_almyear = (value & 0x0fff);
457         break;
458
459     case BCDSEC:
460         if (s->reg_rtccon & RTC_ENABLE) {
461             s->current_tm.tm_sec = (int)from_bcd((uint8_t)value);
462         }
463         break;
464     case BCDMIN:
465         if (s->reg_rtccon & RTC_ENABLE) {
466             s->current_tm.tm_min = (int)from_bcd((uint8_t)value);
467         }
468         break;
469     case BCDHOUR:
470         if (s->reg_rtccon & RTC_ENABLE) {
471             s->current_tm.tm_hour = (int)from_bcd((uint8_t)value);
472         }
473         break;
474     case BCDDAYWEEK:
475         if (s->reg_rtccon & RTC_ENABLE) {
476             s->current_tm.tm_wday = (int)from_bcd((uint8_t)value);
477         }
478         break;
479     case BCDDAY:
480         if (s->reg_rtccon & RTC_ENABLE) {
481             s->current_tm.tm_mday = (int)from_bcd((uint8_t)value);
482         }
483         break;
484     case BCDMON:
485         if (s->reg_rtccon & RTC_ENABLE) {
486             s->current_tm.tm_mon = (int)from_bcd((uint8_t)value) - 1;
487         }
488         break;
489     case BCDYEAR:
490         if (s->reg_rtccon & RTC_ENABLE) {
491             /* 3 digits */
492             s->current_tm.tm_year = (int)from_bcd((uint8_t)value) +
493                     (int)from_bcd((uint8_t)((value >> 8) & 0x0f)) * 100;
494         }
495         break;
496
497     default:
498         fprintf(stderr,
499                 "[exynos4210.rtc: bad write offset " TARGET_FMT_plx "]\n",
500                 offset);
501         break;
502
503     }
504 }
505
506 /*
507  * Set default values to timer fields and registers
508  */
509 static void exynos4210_rtc_reset(DeviceState *d)
510 {
511     Exynos4210RTCState *s = (Exynos4210RTCState *)d;
512
513     qemu_get_timedate(&s->current_tm, 0);
514
515     DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n",
516             s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday,
517             s->current_tm.tm_hour, s->current_tm.tm_min, s->current_tm.tm_sec);
518
519     s->reg_intp = 0;
520     s->reg_rtccon = 0;
521     s->reg_ticcnt = 0;
522     s->reg_rtcalm = 0;
523     s->reg_almsec = 0;
524     s->reg_almmin = 0;
525     s->reg_almhour = 0;
526     s->reg_almday = 0;
527     s->reg_almmon = 0;
528     s->reg_almyear = 0;
529
530     s->reg_curticcnt = 0;
531
532     exynos4210_rtc_update_freq(s, s->reg_rtccon);
533     ptimer_stop(s->ptimer);
534     ptimer_stop(s->ptimer_1Hz);
535 }
536
537 static const MemoryRegionOps exynos4210_rtc_ops = {
538     .read = exynos4210_rtc_read,
539     .write = exynos4210_rtc_write,
540     .endianness = DEVICE_NATIVE_ENDIAN,
541 };
542
543 /*
544  * RTC timer initialization
545  */
546 static int exynos4210_rtc_init(SysBusDevice *dev)
547 {
548     Exynos4210RTCState *s = FROM_SYSBUS(Exynos4210RTCState, dev);
549     QEMUBH *bh;
550
551     bh = qemu_bh_new(exynos4210_rtc_tick, s);
552     s->ptimer = ptimer_init(bh);
553     ptimer_set_freq(s->ptimer, RTC_BASE_FREQ);
554     exynos4210_rtc_update_freq(s, 0);
555
556     bh = qemu_bh_new(exynos4210_rtc_1Hz_tick, s);
557     s->ptimer_1Hz = ptimer_init(bh);
558     ptimer_set_freq(s->ptimer_1Hz, RTC_BASE_FREQ);
559
560     sysbus_init_irq(dev, &s->alm_irq);
561     sysbus_init_irq(dev, &s->tick_irq);
562
563     memory_region_init_io(&s->iomem, &exynos4210_rtc_ops, s, "exynos4210-rtc",
564             EXYNOS4210_RTC_REG_MEM_SIZE);
565     sysbus_init_mmio(dev, &s->iomem);
566
567     return 0;
568 }
569
570 static void exynos4210_rtc_class_init(ObjectClass *klass, void *data)
571 {
572     DeviceClass *dc = DEVICE_CLASS(klass);
573     SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
574
575     k->init = exynos4210_rtc_init;
576     dc->reset = exynos4210_rtc_reset;
577     dc->vmsd = &vmstate_exynos4210_rtc_state;
578 }
579
580 static const TypeInfo exynos4210_rtc_info = {
581     .name          = "exynos4210.rtc",
582     .parent        = TYPE_SYS_BUS_DEVICE,
583     .instance_size = sizeof(Exynos4210RTCState),
584     .class_init    = exynos4210_rtc_class_init,
585 };
586
587 static void exynos4210_rtc_register_types(void)
588 {
589     type_register_static(&exynos4210_rtc_info);
590 }
591
592 type_init(exynos4210_rtc_register_types)
This page took 0.057757 seconds and 4 git commands to generate.