| /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| /* |
| * QEMU Loongson 7A1000 I/O interrupt controller. |
| * |
| * Copyright (C) 2021 Loongson Technology Corporation Limited |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/sysbus.h" |
| #include "hw/loongarch/virt.h" |
| #include "hw/irq.h" |
| #include "hw/intc/loongarch_pch_pic.h" |
| #include "migration/vmstate.h" |
| #include "trace.h" |
| |
| static void pch_pic_update_irq(LoongArchPCHPIC *s, uint64_t mask, int level) |
| { |
| uint64_t val; |
| int irq; |
| |
| if (level) { |
| val = mask & s->intirr & ~s->int_mask; |
| if (val) { |
| irq = ctz64(val); |
| s->intisr |= MAKE_64BIT_MASK(irq, 1); |
| qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 1); |
| } |
| } else { |
| val = mask & s->intisr; |
| if (val) { |
| irq = ctz64(val); |
| s->intisr &= ~MAKE_64BIT_MASK(irq, 1); |
| qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 0); |
| } |
| } |
| } |
| |
| static void pch_pic_irq_handler(void *opaque, int irq, int level) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint64_t mask = 1ULL << irq; |
| |
| assert(irq < PCH_PIC_IRQ_NUM); |
| trace_loongarch_pch_pic_irq_handler(irq, level); |
| |
| if (s->intedge & mask) { |
| /* Edge triggered */ |
| if (level) { |
| if ((s->last_intirr & mask) == 0) { |
| s->intirr |= mask; |
| } |
| s->last_intirr |= mask; |
| } else { |
| s->last_intirr &= ~mask; |
| } |
| } else { |
| /* Level triggered */ |
| if (level) { |
| s->intirr |= mask; |
| s->last_intirr |= mask; |
| } else { |
| s->intirr &= ~mask; |
| s->last_intirr &= ~mask; |
| } |
| } |
| pch_pic_update_irq(s, mask, level); |
| } |
| |
| static uint64_t loongarch_pch_pic_low_readw(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint64_t val = 0; |
| uint32_t offset = addr & 0xfff; |
| |
| switch (offset) { |
| case PCH_PIC_INT_ID_LO: |
| val = PCH_PIC_INT_ID_VAL; |
| break; |
| case PCH_PIC_INT_ID_HI: |
| val = PCH_PIC_INT_ID_NUM; |
| break; |
| case PCH_PIC_INT_MASK_LO: |
| val = (uint32_t)s->int_mask; |
| break; |
| case PCH_PIC_INT_MASK_HI: |
| val = s->int_mask >> 32; |
| break; |
| case PCH_PIC_INT_EDGE_LO: |
| val = (uint32_t)s->intedge; |
| break; |
| case PCH_PIC_INT_EDGE_HI: |
| val = s->intedge >> 32; |
| break; |
| case PCH_PIC_HTMSI_EN_LO: |
| val = (uint32_t)s->htmsi_en; |
| break; |
| case PCH_PIC_HTMSI_EN_HI: |
| val = s->htmsi_en >> 32; |
| break; |
| case PCH_PIC_AUTO_CTRL0_LO: |
| case PCH_PIC_AUTO_CTRL0_HI: |
| case PCH_PIC_AUTO_CTRL1_LO: |
| case PCH_PIC_AUTO_CTRL1_HI: |
| break; |
| default: |
| break; |
| } |
| |
| trace_loongarch_pch_pic_low_readw(size, addr, val); |
| return val; |
| } |
| |
| static uint64_t get_writew_val(uint64_t value, uint32_t target, bool hi) |
| { |
| uint64_t mask = 0xffffffff00000000; |
| uint64_t data = target; |
| |
| return hi ? (value & ~mask) | (data << 32) : (value & mask) | data; |
| } |
| |
| static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr, |
| uint64_t value, unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint32_t offset, old_valid, data = (uint32_t)value; |
| uint64_t old, int_mask; |
| offset = addr & 0xfff; |
| |
| trace_loongarch_pch_pic_low_writew(size, addr, data); |
| |
| switch (offset) { |
| case PCH_PIC_INT_MASK_LO: |
| old = s->int_mask; |
| s->int_mask = get_writew_val(old, data, 0); |
| old_valid = (uint32_t)old; |
| if (old_valid & ~data) { |
| pch_pic_update_irq(s, (old_valid & ~data), 1); |
| } |
| if (~old_valid & data) { |
| pch_pic_update_irq(s, (~old_valid & data), 0); |
| } |
| break; |
| case PCH_PIC_INT_MASK_HI: |
| old = s->int_mask; |
| s->int_mask = get_writew_val(old, data, 1); |
| old_valid = (uint32_t)(old >> 32); |
| int_mask = old_valid & ~data; |
| if (int_mask) { |
| pch_pic_update_irq(s, int_mask << 32, 1); |
| } |
| int_mask = ~old_valid & data; |
| if (int_mask) { |
| pch_pic_update_irq(s, int_mask << 32, 0); |
| } |
| break; |
| case PCH_PIC_INT_EDGE_LO: |
| s->intedge = get_writew_val(s->intedge, data, 0); |
| break; |
| case PCH_PIC_INT_EDGE_HI: |
| s->intedge = get_writew_val(s->intedge, data, 1); |
| break; |
| case PCH_PIC_INT_CLEAR_LO: |
| if (s->intedge & data) { |
| s->intirr &= (~data); |
| pch_pic_update_irq(s, data, 0); |
| s->intisr &= (~data); |
| } |
| break; |
| case PCH_PIC_INT_CLEAR_HI: |
| value <<= 32; |
| if (s->intedge & value) { |
| s->intirr &= (~value); |
| pch_pic_update_irq(s, value, 0); |
| s->intisr &= (~value); |
| } |
| break; |
| case PCH_PIC_HTMSI_EN_LO: |
| s->htmsi_en = get_writew_val(s->htmsi_en, data, 0); |
| break; |
| case PCH_PIC_HTMSI_EN_HI: |
| s->htmsi_en = get_writew_val(s->htmsi_en, data, 1); |
| break; |
| case PCH_PIC_AUTO_CTRL0_LO: |
| case PCH_PIC_AUTO_CTRL0_HI: |
| case PCH_PIC_AUTO_CTRL1_LO: |
| case PCH_PIC_AUTO_CTRL1_HI: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static uint64_t loongarch_pch_pic_high_readw(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint64_t val = 0; |
| uint32_t offset = addr & 0xfff; |
| |
| switch (offset) { |
| case STATUS_LO_START: |
| val = (uint32_t)(s->intisr & (~s->int_mask)); |
| break; |
| case STATUS_HI_START: |
| val = (s->intisr & (~s->int_mask)) >> 32; |
| break; |
| case POL_LO_START: |
| val = (uint32_t)s->int_polarity; |
| break; |
| case POL_HI_START: |
| val = s->int_polarity >> 32; |
| break; |
| default: |
| break; |
| } |
| |
| trace_loongarch_pch_pic_high_readw(size, addr, val); |
| return val; |
| } |
| |
| static void loongarch_pch_pic_high_writew(void *opaque, hwaddr addr, |
| uint64_t value, unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint32_t offset, data = (uint32_t)value; |
| offset = addr & 0xfff; |
| |
| trace_loongarch_pch_pic_high_writew(size, addr, data); |
| |
| switch (offset) { |
| case STATUS_LO_START: |
| s->intisr = get_writew_val(s->intisr, data, 0); |
| break; |
| case STATUS_HI_START: |
| s->intisr = get_writew_val(s->intisr, data, 1); |
| break; |
| case POL_LO_START: |
| s->int_polarity = get_writew_val(s->int_polarity, data, 0); |
| break; |
| case POL_HI_START: |
| s->int_polarity = get_writew_val(s->int_polarity, data, 1); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static uint64_t loongarch_pch_pic_readb(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| uint64_t val = 0; |
| uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET; |
| int64_t offset_tmp; |
| |
| switch (offset) { |
| case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END: |
| offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET; |
| if (offset_tmp >= 0 && offset_tmp < 64) { |
| val = s->htmsi_vector[offset_tmp]; |
| } |
| break; |
| case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END: |
| offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET; |
| if (offset_tmp >= 0 && offset_tmp < 64) { |
| val = s->route_entry[offset_tmp]; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| trace_loongarch_pch_pic_readb(size, addr, val); |
| return val; |
| } |
| |
| static void loongarch_pch_pic_writeb(void *opaque, hwaddr addr, |
| uint64_t data, unsigned size) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque); |
| int32_t offset_tmp; |
| uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET; |
| |
| trace_loongarch_pch_pic_writeb(size, addr, data); |
| |
| switch (offset) { |
| case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END: |
| offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET; |
| if (offset_tmp >= 0 && offset_tmp < 64) { |
| s->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff); |
| } |
| break; |
| case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END: |
| offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET; |
| if (offset_tmp >= 0 && offset_tmp < 64) { |
| s->route_entry[offset_tmp] = (uint8_t)(data & 0xff); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static const MemoryRegionOps loongarch_pch_pic_reg32_low_ops = { |
| .read = loongarch_pch_pic_low_readw, |
| .write = loongarch_pch_pic_low_writew, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 8, |
| }, |
| .impl = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| }; |
| |
| static const MemoryRegionOps loongarch_pch_pic_reg32_high_ops = { |
| .read = loongarch_pch_pic_high_readw, |
| .write = loongarch_pch_pic_high_writew, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 8, |
| }, |
| .impl = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| }; |
| |
| static const MemoryRegionOps loongarch_pch_pic_reg8_ops = { |
| .read = loongarch_pch_pic_readb, |
| .write = loongarch_pch_pic_writeb, |
| .valid = { |
| .min_access_size = 1, |
| .max_access_size = 1, |
| }, |
| .impl = { |
| .min_access_size = 1, |
| .max_access_size = 1, |
| }, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| }; |
| |
| static void loongarch_pch_pic_reset(DeviceState *d) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(d); |
| int i; |
| |
| s->int_mask = -1; |
| s->htmsi_en = 0x0; |
| s->intedge = 0x0; |
| s->intclr = 0x0; |
| s->auto_crtl0 = 0x0; |
| s->auto_crtl1 = 0x0; |
| for (i = 0; i < 64; i++) { |
| s->route_entry[i] = 0x1; |
| s->htmsi_vector[i] = 0x0; |
| } |
| s->intirr = 0x0; |
| s->intisr = 0x0; |
| s->last_intirr = 0x0; |
| s->int_polarity = 0x0; |
| } |
| |
| static void loongarch_pch_pic_init(Object *obj) |
| { |
| LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(obj); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| |
| memory_region_init_io(&s->iomem32_low, obj, |
| &loongarch_pch_pic_reg32_low_ops, |
| s, PCH_PIC_NAME(.reg32_part1), 0x100); |
| memory_region_init_io(&s->iomem8, obj, &loongarch_pch_pic_reg8_ops, |
| s, PCH_PIC_NAME(.reg8), 0x2a0); |
| memory_region_init_io(&s->iomem32_high, obj, |
| &loongarch_pch_pic_reg32_high_ops, |
| s, PCH_PIC_NAME(.reg32_part2), 0xc60); |
| sysbus_init_mmio(sbd, &s->iomem32_low); |
| sysbus_init_mmio(sbd, &s->iomem8); |
| sysbus_init_mmio(sbd, &s->iomem32_high); |
| |
| qdev_init_gpio_out(DEVICE(obj), s->parent_irq, PCH_PIC_IRQ_NUM); |
| qdev_init_gpio_in(DEVICE(obj), pch_pic_irq_handler, PCH_PIC_IRQ_NUM); |
| } |
| |
| static const VMStateDescription vmstate_loongarch_pch_pic = { |
| .name = TYPE_LOONGARCH_PCH_PIC, |
| .version_id = 1, |
| .minimum_version_id = 1, |
| .fields = (VMStateField[]) { |
| VMSTATE_UINT64(int_mask, LoongArchPCHPIC), |
| VMSTATE_UINT64(htmsi_en, LoongArchPCHPIC), |
| VMSTATE_UINT64(intedge, LoongArchPCHPIC), |
| VMSTATE_UINT64(intclr, LoongArchPCHPIC), |
| VMSTATE_UINT64(auto_crtl0, LoongArchPCHPIC), |
| VMSTATE_UINT64(auto_crtl1, LoongArchPCHPIC), |
| VMSTATE_UINT8_ARRAY(route_entry, LoongArchPCHPIC, 64), |
| VMSTATE_UINT8_ARRAY(htmsi_vector, LoongArchPCHPIC, 64), |
| VMSTATE_UINT64(last_intirr, LoongArchPCHPIC), |
| VMSTATE_UINT64(intirr, LoongArchPCHPIC), |
| VMSTATE_UINT64(intisr, LoongArchPCHPIC), |
| VMSTATE_UINT64(int_polarity, LoongArchPCHPIC), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static void loongarch_pch_pic_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->reset = loongarch_pch_pic_reset; |
| dc->vmsd = &vmstate_loongarch_pch_pic; |
| } |
| |
| static const TypeInfo loongarch_pch_pic_info = { |
| .name = TYPE_LOONGARCH_PCH_PIC, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(LoongArchPCHPIC), |
| .instance_init = loongarch_pch_pic_init, |
| .class_init = loongarch_pch_pic_class_init, |
| }; |
| |
| static void loongarch_pch_pic_register_types(void) |
| { |
| type_register_static(&loongarch_pch_pic_info); |
| } |
| |
| type_init(loongarch_pch_pic_register_types) |