| /* |
| * QEMU Loongson Local I/O interrupt controler. |
| * |
| * Copyright (c) 2020 Jiaxun Yang <jiaxun.yang@flygoat.com> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/sysbus.h" |
| #include "qemu/module.h" |
| #include "hw/irq.h" |
| #include "hw/qdev-properties.h" |
| #include "qom/object.h" |
| |
| #define D(x) |
| |
| #define NUM_IRQS 32 |
| |
| #define NUM_CORES 4 |
| #define NUM_IPS 4 |
| #define NUM_PARENTS (NUM_CORES * NUM_IPS) |
| #define PARENT_COREx_IPy(x, y) (NUM_IPS * x + y) |
| |
| #define R_MAPPER_START 0x0 |
| #define R_MAPPER_END 0x20 |
| #define R_ISR R_MAPPER_END |
| #define R_IEN 0x24 |
| #define R_IEN_SET 0x28 |
| #define R_IEN_CLR 0x2c |
| #define R_PERCORE_ISR(x) (0x40 + 0x8 * x) |
| #define R_END 0x64 |
| |
| #define TYPE_LOONGSON_LIOINTC "loongson.liointc" |
| DECLARE_INSTANCE_CHECKER(struct loongson_liointc, LOONGSON_LIOINTC, |
| TYPE_LOONGSON_LIOINTC) |
| |
| struct loongson_liointc { |
| SysBusDevice parent_obj; |
| |
| MemoryRegion mmio; |
| qemu_irq parent_irq[NUM_PARENTS]; |
| |
| uint8_t mapper[NUM_IRQS]; /* 0:3 for core, 4:7 for IP */ |
| uint32_t isr; |
| uint32_t ien; |
| uint32_t per_core_isr[NUM_CORES]; |
| |
| /* state of the interrupt input pins */ |
| uint32_t pin_state; |
| bool parent_state[NUM_PARENTS]; |
| }; |
| |
| static void update_irq(struct loongson_liointc *p) |
| { |
| uint32_t irq, core, ip; |
| uint32_t per_ip_isr[NUM_IPS] = {0}; |
| |
| /* level triggered interrupt */ |
| p->isr = p->pin_state; |
| |
| /* Clear disabled IRQs */ |
| p->isr &= p->ien; |
| |
| /* Clear per_core_isr */ |
| for (core = 0; core < NUM_CORES; core++) { |
| p->per_core_isr[core] = 0; |
| } |
| |
| /* Update per_core_isr and per_ip_isr */ |
| for (irq = 0; irq < NUM_IRQS; irq++) { |
| if (!(p->isr & (1 << irq))) { |
| continue; |
| } |
| |
| for (core = 0; core < NUM_CORES; core++) { |
| if ((p->mapper[irq] & (1 << core))) { |
| p->per_core_isr[core] |= (1 << irq); |
| } |
| } |
| |
| for (ip = 0; ip < NUM_IPS; ip++) { |
| if ((p->mapper[irq] & (1 << (ip + 4)))) { |
| per_ip_isr[ip] |= (1 << irq); |
| } |
| } |
| } |
| |
| /* Emit IRQ to parent! */ |
| for (core = 0; core < NUM_CORES; core++) { |
| for (ip = 0; ip < NUM_IPS; ip++) { |
| int parent = PARENT_COREx_IPy(core, ip); |
| if (p->parent_state[parent] != |
| (!!p->per_core_isr[core] && !!per_ip_isr[ip])) { |
| p->parent_state[parent] = !p->parent_state[parent]; |
| qemu_set_irq(p->parent_irq[parent], p->parent_state[parent]); |
| } |
| } |
| } |
| } |
| |
| static uint64_t |
| liointc_read(void *opaque, hwaddr addr, unsigned int size) |
| { |
| struct loongson_liointc *p = opaque; |
| uint32_t r = 0; |
| |
| /* Mapper is 1 byte */ |
| if (size == 1 && addr < R_MAPPER_END) { |
| r = p->mapper[addr]; |
| goto out; |
| } |
| |
| /* Rest is 4 byte */ |
| if (size != 4 || (addr % 4)) { |
| goto out; |
| } |
| |
| if (addr >= R_PERCORE_ISR(0) && |
| addr < R_PERCORE_ISR(NUM_CORES)) { |
| int core = (addr - R_PERCORE_ISR(0)) / 8; |
| r = p->per_core_isr[core]; |
| goto out; |
| } |
| |
| switch (addr) { |
| case R_ISR: |
| r = p->isr; |
| break; |
| case R_IEN: |
| r = p->ien; |
| break; |
| default: |
| break; |
| } |
| |
| out: |
| D(qemu_log("%s: size=%d addr=%lx val=%x\n", __func__, size, addr, r)); |
| return r; |
| } |
| |
| static void |
| liointc_write(void *opaque, hwaddr addr, |
| uint64_t val64, unsigned int size) |
| { |
| struct loongson_liointc *p = opaque; |
| uint32_t value = val64; |
| |
| D(qemu_log("%s: size=%d, addr=%lx val=%x\n", __func__, size, addr, value)); |
| |
| /* Mapper is 1 byte */ |
| if (size == 1 && addr < R_MAPPER_END) { |
| p->mapper[addr] = value; |
| goto out; |
| } |
| |
| /* Rest is 4 byte */ |
| if (size != 4 || (addr % 4)) { |
| goto out; |
| } |
| |
| if (addr >= R_PERCORE_ISR(0) && |
| addr < R_PERCORE_ISR(NUM_CORES)) { |
| int core = (addr - R_PERCORE_ISR(0)) / 8; |
| p->per_core_isr[core] = value; |
| goto out; |
| } |
| |
| switch (addr) { |
| case R_IEN_SET: |
| p->ien |= value; |
| break; |
| case R_IEN_CLR: |
| p->ien &= ~value; |
| break; |
| default: |
| break; |
| } |
| |
| out: |
| update_irq(p); |
| } |
| |
| static const MemoryRegionOps pic_ops = { |
| .read = liointc_read, |
| .write = liointc_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .valid = { |
| .min_access_size = 1, |
| .max_access_size = 4 |
| } |
| }; |
| |
| static void irq_handler(void *opaque, int irq, int level) |
| { |
| struct loongson_liointc *p = opaque; |
| |
| p->pin_state &= ~(1 << irq); |
| p->pin_state |= level << irq; |
| update_irq(p); |
| } |
| |
| static void loongson_liointc_init(Object *obj) |
| { |
| struct loongson_liointc *p = LOONGSON_LIOINTC(obj); |
| int i; |
| |
| qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); |
| |
| for (i = 0; i < NUM_PARENTS; i++) { |
| sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq[i]); |
| } |
| |
| memory_region_init_io(&p->mmio, obj, &pic_ops, p, |
| "loongson.liointc", R_END); |
| sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); |
| } |
| |
| static const TypeInfo loongson_liointc_info = { |
| .name = TYPE_LOONGSON_LIOINTC, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(struct loongson_liointc), |
| .instance_init = loongson_liointc_init, |
| }; |
| |
| static void loongson_liointc_register_types(void) |
| { |
| type_register_static(&loongson_liointc_info); |
| } |
| |
| type_init(loongson_liointc_register_types) |