1 /* 2 * QEMU Loongson Local I/O interrupt controler. 3 * 4 * Copyright (c) 2020 Jiaxun Yang <jiaxun.yang@flygoat.com> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <https://www.gnu.org/licenses/>. 18 * 19 */ 20 21 #include "qemu/osdep.h" 22 #include "hw/sysbus.h" 23 #include "qemu/module.h" 24 #include "hw/irq.h" 25 #include "hw/qdev-properties.h" 26 27 #define D(x) 28 29 #define NUM_IRQS 32 30 31 #define NUM_CORES 4 32 #define NUM_IPS 4 33 #define NUM_PARENTS (NUM_CORES * NUM_IPS) 34 #define PARENT_COREx_IPy(x, y) (NUM_IPS * x + y) 35 36 #define R_MAPPER_START 0x0 37 #define R_MAPPER_END 0x20 38 #define R_ISR R_MAPPER_END 39 #define R_IEN 0x24 40 #define R_IEN_SET 0x28 41 #define R_IEN_CLR 0x2c 42 #define R_PERCORE_ISR(x) (0x40 + 0x8 * x) 43 #define R_END 0x64 44 45 #define TYPE_LOONGSON_LIOINTC "loongson.liointc" 46 #define LOONGSON_LIOINTC(obj) \ 47 OBJECT_CHECK(struct loongson_liointc, (obj), TYPE_LOONGSON_LIOINTC) 48 49 struct loongson_liointc { 50 SysBusDevice parent_obj; 51 52 MemoryRegion mmio; 53 qemu_irq parent_irq[NUM_PARENTS]; 54 55 uint8_t mapper[NUM_IRQS]; /* 0:3 for core, 4:7 for IP */ 56 uint32_t isr; 57 uint32_t ien; 58 uint32_t per_core_isr[NUM_CORES]; 59 60 /* state of the interrupt input pins */ 61 uint32_t pin_state; 62 bool parent_state[NUM_PARENTS]; 63 }; 64 65 static void update_irq(struct loongson_liointc *p) 66 { 67 uint32_t irq, core, ip; 68 uint32_t per_ip_isr[NUM_IPS] = {0}; 69 70 /* level triggered interrupt */ 71 p->isr = p->pin_state; 72 73 /* Clear disabled IRQs */ 74 p->isr &= p->ien; 75 76 /* Clear per_core_isr */ 77 for (core = 0; core < NUM_CORES; core++) { 78 p->per_core_isr[core] = 0; 79 } 80 81 /* Update per_core_isr and per_ip_isr */ 82 for (irq = 0; irq < NUM_IRQS; irq++) { 83 if (!(p->isr & (1 << irq))) { 84 continue; 85 } 86 87 for (core = 0; core < NUM_CORES; core++) { 88 if ((p->mapper[irq] & (1 << core))) { 89 p->per_core_isr[core] |= (1 << irq); 90 } 91 } 92 93 for (ip = 0; ip < NUM_IPS; ip++) { 94 if ((p->mapper[irq] & (1 << (ip + 4)))) { 95 per_ip_isr[ip] |= (1 << irq); 96 } 97 } 98 } 99 100 /* Emit IRQ to parent! */ 101 for (core = 0; core < NUM_CORES; core++) { 102 for (ip = 0; ip < NUM_IPS; ip++) { 103 int parent = PARENT_COREx_IPy(core, ip); 104 if (p->parent_state[parent] != 105 (!!p->per_core_isr[core] && !!per_ip_isr[ip])) { 106 p->parent_state[parent] = !p->parent_state[parent]; 107 qemu_set_irq(p->parent_irq[parent], p->parent_state[parent]); 108 } 109 } 110 } 111 } 112 113 static uint64_t 114 liointc_read(void *opaque, hwaddr addr, unsigned int size) 115 { 116 struct loongson_liointc *p = opaque; 117 uint32_t r = 0; 118 119 /* Mapper is 1 byte */ 120 if (size == 1 && addr < R_MAPPER_END) { 121 r = p->mapper[addr]; 122 goto out; 123 } 124 125 /* Rest is 4 byte */ 126 if (size != 4 || (addr % 4)) { 127 goto out; 128 } 129 130 if (addr >= R_PERCORE_ISR(0) && 131 addr < R_PERCORE_ISR(NUM_CORES)) { 132 int core = (addr - R_PERCORE_ISR(0)) / 4; 133 r = p->per_core_isr[core]; 134 goto out; 135 } 136 137 switch (addr) { 138 case R_ISR: 139 r = p->isr; 140 break; 141 case R_IEN: 142 r = p->ien; 143 break; 144 default: 145 break; 146 } 147 148 out: 149 D(qemu_log("%s: size=%d addr=%lx val=%x\n", __func__, size, addr, r)); 150 return r; 151 } 152 153 static void 154 liointc_write(void *opaque, hwaddr addr, 155 uint64_t val64, unsigned int size) 156 { 157 struct loongson_liointc *p = opaque; 158 uint32_t value = val64; 159 160 D(qemu_log("%s: size=%d, addr=%lx val=%x\n", __func__, size, addr, value)); 161 162 /* Mapper is 1 byte */ 163 if (size == 1 && addr < R_MAPPER_END) { 164 p->mapper[addr] = value; 165 goto out; 166 } 167 168 /* Rest is 4 byte */ 169 if (size != 4 || (addr % 4)) { 170 goto out; 171 } 172 173 if (addr >= R_PERCORE_ISR(0) && 174 addr < R_PERCORE_ISR(NUM_CORES)) { 175 int core = (addr - R_PERCORE_ISR(0)) / 4; 176 p->per_core_isr[core] = value; 177 goto out; 178 } 179 180 switch (addr) { 181 case R_IEN_SET: 182 p->ien |= value; 183 break; 184 case R_IEN_CLR: 185 p->ien &= ~value; 186 break; 187 default: 188 break; 189 } 190 191 out: 192 update_irq(p); 193 } 194 195 static const MemoryRegionOps pic_ops = { 196 .read = liointc_read, 197 .write = liointc_write, 198 .endianness = DEVICE_NATIVE_ENDIAN, 199 .valid = { 200 .min_access_size = 1, 201 .max_access_size = 4 202 } 203 }; 204 205 static void irq_handler(void *opaque, int irq, int level) 206 { 207 struct loongson_liointc *p = opaque; 208 209 p->pin_state &= ~(1 << irq); 210 p->pin_state |= level << irq; 211 update_irq(p); 212 } 213 214 static void loongson_liointc_init(Object *obj) 215 { 216 struct loongson_liointc *p = LOONGSON_LIOINTC(obj); 217 int i; 218 219 qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); 220 221 for (i = 0; i < NUM_PARENTS; i++) { 222 sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq[i]); 223 } 224 225 memory_region_init_io(&p->mmio, obj, &pic_ops, p, 226 "loongson.liointc", R_END); 227 sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); 228 } 229 230 static const TypeInfo loongson_liointc_info = { 231 .name = TYPE_LOONGSON_LIOINTC, 232 .parent = TYPE_SYS_BUS_DEVICE, 233 .instance_size = sizeof(struct loongson_liointc), 234 .instance_init = loongson_liointc_init, 235 }; 236 237 static void loongson_liointc_register_types(void) 238 { 239 type_register_static(&loongson_liointc_info); 240 } 241 242 type_init(loongson_liointc_register_types) 243