linux-user: fix ppc target_stat64 st_blocks layout
[qemu] / hw / pl031.c
1 /*
2  * ARM AMBA PrimeCell PL031 RTC
3  *
4  * Copyright (c) 2007 CodeSourcery
5  *
6  * This file is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  */
11
12 #include "sysbus.h"
13 #include "qemu-timer.h"
14
15 //#define DEBUG_PL031
16
17 #ifdef DEBUG_PL031
18 #define DPRINTF(fmt, ...) \
19 do { printf("pl031: " fmt , ## __VA_ARGS__); } while (0)
20 #else
21 #define DPRINTF(fmt, ...) do {} while(0)
22 #endif
23
24 #define RTC_DR      0x00    /* Data read register */
25 #define RTC_MR      0x04    /* Match register */
26 #define RTC_LR      0x08    /* Data load register */
27 #define RTC_CR      0x0c    /* Control register */
28 #define RTC_IMSC    0x10    /* Interrupt mask and set register */
29 #define RTC_RIS     0x14    /* Raw interrupt status register */
30 #define RTC_MIS     0x18    /* Masked interrupt status register */
31 #define RTC_ICR     0x1c    /* Interrupt clear register */
32
33 typedef struct {
34     SysBusDevice busdev;
35     QEMUTimer *timer;
36     qemu_irq irq;
37
38     uint32_t tick_offset;
39
40     uint32_t mr;
41     uint32_t lr;
42     uint32_t cr;
43     uint32_t im;
44     uint32_t is;
45 } pl031_state;
46
47 static const unsigned char pl031_id[] = {
48     0x31, 0x10, 0x14, 0x00,         /* Device ID        */
49     0x0d, 0xf0, 0x05, 0xb1          /* Cell ID      */
50 };
51
52 static void pl031_update(pl031_state *s)
53 {
54     qemu_set_irq(s->irq, s->is & s->im);
55 }
56
57 static void pl031_interrupt(void * opaque)
58 {
59     pl031_state *s = (pl031_state *)opaque;
60
61     s->im = 1;
62     DPRINTF("Alarm raised\n");
63     pl031_update(s);
64 }
65
66 static uint32_t pl031_get_count(pl031_state *s)
67 {
68     /* This assumes qemu_get_clock returns the time since the machine was
69        created.  */
70     return s->tick_offset + qemu_get_clock(vm_clock) / get_ticks_per_sec();
71 }
72
73 static void pl031_set_alarm(pl031_state *s)
74 {
75     int64_t now;
76     uint32_t ticks;
77
78     now = qemu_get_clock(vm_clock);
79     ticks = s->tick_offset + now / get_ticks_per_sec();
80
81     /* The timer wraps around.  This subtraction also wraps in the same way,
82        and gives correct results when alarm < now_ticks.  */
83     ticks = s->mr - ticks;
84     DPRINTF("Alarm set in %ud ticks\n", ticks);
85     if (ticks == 0) {
86         qemu_del_timer(s->timer);
87         pl031_interrupt(s);
88     } else {
89         qemu_mod_timer(s->timer, now + (int64_t)ticks * get_ticks_per_sec());
90     }
91 }
92
93 static uint32_t pl031_read(void *opaque, target_phys_addr_t offset)
94 {
95     pl031_state *s = (pl031_state *)opaque;
96
97     if (offset >= 0xfe0  &&  offset < 0x1000)
98         return pl031_id[(offset - 0xfe0) >> 2];
99
100     switch (offset) {
101     case RTC_DR:
102         return pl031_get_count(s);
103     case RTC_MR:
104         return s->mr;
105     case RTC_IMSC:
106         return s->im;
107     case RTC_RIS:
108         return s->is;
109     case RTC_LR:
110         return s->lr;
111     case RTC_CR:
112         /* RTC is permanently enabled.  */
113         return 1;
114     case RTC_MIS:
115         return s->is & s->im;
116     case RTC_ICR:
117         fprintf(stderr, "qemu: pl031_read: Unexpected offset 0x%x\n",
118                 (int)offset);
119         break;
120     default:
121         hw_error("pl031_read: Bad offset 0x%x\n", (int)offset);
122         break;
123     }
124
125     return 0;
126 }
127
128 static void pl031_write(void * opaque, target_phys_addr_t offset,
129                         uint32_t value)
130 {
131     pl031_state *s = (pl031_state *)opaque;
132
133
134     switch (offset) {
135     case RTC_LR:
136         s->tick_offset += value - pl031_get_count(s);
137         pl031_set_alarm(s);
138         break;
139     case RTC_MR:
140         s->mr = value;
141         pl031_set_alarm(s);
142         break;
143     case RTC_IMSC:
144         s->im = value & 1;
145         DPRINTF("Interrupt mask %d\n", s->im);
146         pl031_update(s);
147         break;
148     case RTC_ICR:
149         /* The PL031 documentation (DDI0224B) states that the interupt is
150            cleared when bit 0 of the written value is set.  However the
151            arm926e documentation (DDI0287B) states that the interrupt is
152            cleared when any value is written.  */
153         DPRINTF("Interrupt cleared");
154         s->is = 0;
155         pl031_update(s);
156         break;
157     case RTC_CR:
158         /* Written value is ignored.  */
159         break;
160
161     case RTC_DR:
162     case RTC_MIS:
163     case RTC_RIS:
164         fprintf(stderr, "qemu: pl031_write: Unexpected offset 0x%x\n",
165                 (int)offset);
166         break;
167
168     default:
169         hw_error("pl031_write: Bad offset 0x%x\n", (int)offset);
170         break;
171     }
172 }
173
174 static CPUWriteMemoryFunc * const  pl031_writefn[] = {
175     pl031_write,
176     pl031_write,
177     pl031_write
178 };
179
180 static CPUReadMemoryFunc * const  pl031_readfn[] = {
181     pl031_read,
182     pl031_read,
183     pl031_read
184 };
185
186 static int pl031_init(SysBusDevice *dev)
187 {
188     int iomemtype;
189     pl031_state *s = FROM_SYSBUS(pl031_state, dev);
190     struct tm tm;
191
192     iomemtype = cpu_register_io_memory(pl031_readfn, pl031_writefn, s);
193     if (iomemtype == -1) {
194         hw_error("pl031_init: Can't register I/O memory\n");
195     }
196
197     sysbus_init_mmio(dev, 0x1000, iomemtype);
198
199     sysbus_init_irq(dev, &s->irq);
200     /* ??? We assume vm_clock is zero at this point.  */
201     qemu_get_timedate(&tm, 0);
202     s->tick_offset = mktimegm(&tm);
203
204     s->timer = qemu_new_timer(vm_clock, pl031_interrupt, s);
205     return 0;
206 }
207
208 static void pl031_register_devices(void)
209 {
210     sysbus_register_dev("pl031", sizeof(pl031_state), pl031_init);
211 }
212
213 device_init(pl031_register_devices)