1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12#include "qemu_file.h"
13#include "arm_pic.h"
14#include "goldfish_device.h"
15#include "irq.h"
16
17enum {
18    INTERRUPT_STATUS        = 0x00, // number of pending interrupts
19    INTERRUPT_NUMBER        = 0x04,
20    INTERRUPT_DISABLE_ALL   = 0x08,
21    INTERRUPT_DISABLE       = 0x0c,
22    INTERRUPT_ENABLE        = 0x10
23};
24
25struct goldfish_int_state {
26    struct goldfish_device dev;
27    uint32_t level;
28    uint32_t pending_count;
29    uint32_t irq_enabled;
30    uint32_t fiq_enabled;
31    qemu_irq parent_irq;
32    qemu_irq parent_fiq;
33};
34
35#define  GOLDFISH_INT_SAVE_VERSION  1
36
37#define  QFIELD_STRUCT  struct goldfish_int_state
38QFIELD_BEGIN(goldfish_int_fields)
39    QFIELD_INT32(level),
40    QFIELD_INT32(pending_count),
41    QFIELD_INT32(irq_enabled),
42    QFIELD_INT32(fiq_enabled),
43QFIELD_END
44
45static void goldfish_int_save(QEMUFile*  f, void*  opaque)
46{
47    struct goldfish_int_state*  s = opaque;
48
49    qemu_put_struct(f, goldfish_int_fields, s);
50}
51
52static int  goldfish_int_load(QEMUFile*  f, void*  opaque, int  version_id)
53{
54    struct goldfish_int_state*  s = opaque;
55
56    if (version_id != GOLDFISH_INT_SAVE_VERSION)
57        return -1;
58
59    return qemu_get_struct(f, goldfish_int_fields, s);
60}
61
62static void goldfish_int_update(struct goldfish_int_state *s)
63{
64    uint32_t flags;
65
66    flags = (s->level & s->irq_enabled);
67    qemu_set_irq(s->parent_irq, flags != 0);
68
69    flags = (s->level & s->fiq_enabled);
70    qemu_set_irq(s->parent_fiq, flags != 0);
71}
72
73static void goldfish_int_set_irq(void *opaque, int irq, int level)
74{
75    struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
76    uint32_t mask = (1U << irq);
77
78    if(level) {
79        if(!(s->level & mask)) {
80            if(s->irq_enabled & mask)
81                s->pending_count++;
82            s->level |= mask;
83        }
84    }
85    else {
86        if(s->level & mask) {
87            if(s->irq_enabled & mask)
88                s->pending_count--;
89            s->level &= ~mask;
90        }
91    }
92    goldfish_int_update(s);
93}
94
95static uint32_t goldfish_int_read(void *opaque, target_phys_addr_t offset)
96{
97    struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
98
99    switch (offset) {
100    case INTERRUPT_STATUS: /* IRQ_STATUS */
101        return s->pending_count;
102    case INTERRUPT_NUMBER: {
103        int i;
104        uint32_t pending = s->level & s->irq_enabled;
105        for(i = 0; i < 32; i++) {
106            if(pending & (1U << i))
107                return i;
108        }
109        return 0;
110    }
111    default:
112        cpu_abort (cpu_single_env, "goldfish_int_read: Bad offset %x\n", offset);
113        return 0;
114    }
115}
116
117static void goldfish_int_write(void *opaque, target_phys_addr_t offset, uint32_t value)
118{
119    struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
120    uint32_t mask = (1U << value);
121
122    switch (offset) {
123        case INTERRUPT_DISABLE_ALL:
124            s->pending_count = 0;
125            s->level = 0;
126            break;
127
128        case INTERRUPT_DISABLE:
129            if(s->irq_enabled & mask) {
130                if(s->level & mask)
131                    s->pending_count--;
132                s->irq_enabled &= ~mask;
133            }
134            break;
135        case INTERRUPT_ENABLE:
136            if(!(s->irq_enabled & mask)) {
137                s->irq_enabled |= mask;
138                if(s->level & mask)
139                    s->pending_count++;
140            }
141            break;
142
143    default:
144        cpu_abort (cpu_single_env, "goldfish_int_write: Bad offset %x\n", offset);
145        return;
146    }
147    goldfish_int_update(s);
148}
149
150static CPUReadMemoryFunc *goldfish_int_readfn[] = {
151    goldfish_int_read,
152    goldfish_int_read,
153    goldfish_int_read
154};
155
156static CPUWriteMemoryFunc *goldfish_int_writefn[] = {
157    goldfish_int_write,
158    goldfish_int_write,
159    goldfish_int_write
160};
161
162qemu_irq*  goldfish_interrupt_init(uint32_t base, qemu_irq parent_irq, qemu_irq parent_fiq)
163{
164    int ret;
165    struct goldfish_int_state *s;
166    qemu_irq*  qi;
167
168    s = qemu_mallocz(sizeof(*s));
169    qi = qemu_allocate_irqs(goldfish_int_set_irq, s, 32);
170    s->dev.name = "goldfish_interrupt_controller";
171    s->dev.id = -1;
172    s->dev.base = base;
173    s->dev.size = 0x1000;
174    s->parent_irq = parent_irq;
175    s->parent_fiq = parent_fiq;
176
177    ret = goldfish_device_add(&s->dev, goldfish_int_readfn, goldfish_int_writefn, s);
178    if(ret) {
179        qemu_free(s);
180        return NULL;
181    }
182
183    register_savevm( "goldfish_int", 0, GOLDFISH_INT_SAVE_VERSION,
184                     goldfish_int_save, goldfish_int_load, s);
185
186    return qi;
187}
188
189