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 #include "qom/object.h" 27 28 #define D(x) 29 30 #define NUM_IRQS 32 31 32 #define NUM_CORES 4 33 #define NUM_IPS 4 34 #define NUM_PARENTS (NUM_CORES * NUM_IPS) 35 #define PARENT_COREx_IPy(x, y) (NUM_IPS * x + y) 36 37 #define R_MAPPER_START 0x0 38 #define R_MAPPER_END 0x20 39 #define R_ISR R_MAPPER_END 40 #define R_IEN 0x24 41 #define R_IEN_SET 0x28 42 #define R_IEN_CLR 0x2c 43 #define R_PERCORE_ISR(x) (0x40 + 0x8 * x) 44 #define R_END 0x64 45 46 #define TYPE_LOONGSON_LIOINTC "loongson.liointc" 47 DECLARE_INSTANCE_CHECKER(struct loongson_liointc, LOONGSON_LIOINTC, 48 TYPE_LOONGSON_LIOINTC) 49 50 struct loongson_liointc { 51 SysBusDevice parent_obj; 52 53 MemoryRegion mmio; 54 qemu_irq parent_irq[NUM_PARENTS]; 55 56 uint8_t mapper[NUM_IRQS]; /* 0:3 for core, 4:7 for IP */ 57 uint32_t isr; 58 uint32_t ien; 59 uint32_t per_core_isr[NUM_CORES]; 60 61 /* state of the interrupt input pins */ 62 uint32_t pin_state; 63 bool parent_state[NUM_PARENTS]; 64 }; 65 66 static void update_irq(struct loongson_liointc *p) 67 { 68 uint32_t irq, core, ip; 69 uint32_t per_ip_isr[NUM_IPS] = {0}; 70 71 /* level triggered interrupt */ 72 p->isr = p->pin_state; 73 74 /* Clear disabled IRQs */ 75 p->isr &= p->ien; 76 77 /* Clear per_core_isr */ 78 for (core = 0; core < NUM_CORES; core++) { 79 p->per_core_isr[core] = 0; 80 } 81 82 /* Update per_core_isr and per_ip_isr */ 83 for (irq = 0; irq < NUM_IRQS; irq++) { 84 if (!(p->isr & (1 << irq))) { 85 continue; 86 } 87 88 for (core = 0; core < NUM_CORES; core++) { 89 if ((p->mapper[irq] & (1 << core))) { 90 p->per_core_isr[core] |= (1 << irq); 91 } 92 } 93 94 for (ip = 0; ip < NUM_IPS; ip++) { 95 if ((p->mapper[irq] & (1 << (ip + 4)))) { 96 per_ip_isr[ip] |= (1 << irq); 97 } 98 } 99 } 100 101 /* Emit IRQ to parent! */ 102 for (core = 0; core < NUM_CORES; core++) { 103 for (ip = 0; ip < NUM_IPS; ip++) { 104 int parent = PARENT_COREx_IPy(core, ip); 105 if (p->parent_state[parent] != 106 (!!p->per_core_isr[core] && !!per_ip_isr[ip])) { 107 p->parent_state[parent] = !p->parent_state[parent]; 108 qemu_set_irq(p->parent_irq[parent], p->parent_state[parent]); 109 } 110 } 111 } 112 } 113 114 static uint64_t 115 liointc_read(void *opaque, hwaddr addr, unsigned int size) 116 { 117 struct loongson_liointc *p = opaque; 118 uint32_t r = 0; 119 120 /* Mapper is 1 byte */ 121 if (size == 1 && addr < R_MAPPER_END) { 122 r = p->mapper[addr]; 123 goto out; 124 } 125 126 /* Rest is 4 byte */ 127 if (size != 4 || (addr % 4)) { 128 goto out; 129 } 130 131 if (addr >= R_PERCORE_ISR(0) && 132 addr < R_PERCORE_ISR(NUM_CORES)) { 133 int core = (addr - R_PERCORE_ISR(0)) / 4; 134 r = p->per_core_isr[core]; 135 goto out; 136 } 137 138 switch (addr) { 139 case R_ISR: 140 r = p->isr; 141 break; 142 case R_IEN: 143 r = p->ien; 144 break; 145 default: 146 break; 147 } 148 149 out: 150 D(qemu_log("%s: size=%d addr=%lx val=%x\n", __func__, size, addr, r)); 151 return r; 152 } 153 154 static void 155 liointc_write(void *opaque, hwaddr addr, 156 uint64_t val64, unsigned int size) 157 { 158 struct loongson_liointc *p = opaque; 159 uint32_t value = val64; 160 161 D(qemu_log("%s: size=%d, addr=%lx val=%x\n", __func__, size, addr, value)); 162 163 /* Mapper is 1 byte */ 164 if (size == 1 && addr < R_MAPPER_END) { 165 p->mapper[addr] = value; 166 goto out; 167 } 168 169 /* Rest is 4 byte */ 170 if (size != 4 || (addr % 4)) { 171 goto out; 172 } 173 174 if (addr >= R_PERCORE_ISR(0) && 175 addr < R_PERCORE_ISR(NUM_CORES)) { 176 int core = (addr - R_PERCORE_ISR(0)) / 4; 177 p->per_core_isr[core] = value; 178 goto out; 179 } 180 181 switch (addr) { 182 case R_IEN_SET: 183 p->ien |= value; 184 break; 185 case R_IEN_CLR: 186 p->ien &= ~value; 187 break; 188 default: 189 break; 190 } 191 192 out: 193 update_irq(p); 194 } 195 196 static const MemoryRegionOps pic_ops = { 197 .read = liointc_read, 198 .write = liointc_write, 199 .endianness = DEVICE_NATIVE_ENDIAN, 200 .valid = { 201 .min_access_size = 1, 202 .max_access_size = 4 203 } 204 }; 205 206 static void irq_handler(void *opaque, int irq, int level) 207 { 208 struct loongson_liointc *p = opaque; 209 210 p->pin_state &= ~(1 << irq); 211 p->pin_state |= level << irq; 212 update_irq(p); 213 } 214 215 static void loongson_liointc_init(Object *obj) 216 { 217 struct loongson_liointc *p = LOONGSON_LIOINTC(obj); 218 int i; 219 220 qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); 221 222 for (i = 0; i < NUM_PARENTS; i++) { 223 sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq[i]); 224 } 225 226 memory_region_init_io(&p->mmio, obj, &pic_ops, p, 227 "loongson.liointc", R_END); 228 sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); 229 } 230 231 static const TypeInfo loongson_liointc_info = { 232 .name = TYPE_LOONGSON_LIOINTC, 233 .parent = TYPE_SYS_BUS_DEVICE, 234 .instance_size = sizeof(struct loongson_liointc), 235 .instance_init = loongson_liointc_init, 236 }; 237 238 static void loongson_liointc_register_types(void) 239 { 240 type_register_static(&loongson_liointc_info); 241 } 242 243 type_init(loongson_liointc_register_types) 244