 45b1f81d90
			
		
	
	
		45b1f81d90
		
	
	
	
	
		
			
			Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <20231221031652.119827-35-richard.henderson@linaro.org>
		
			
				
	
	
		
			464 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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 "qemu/bitops.h"
 | |
| #include "hw/sysbus.h"
 | |
| #include "hw/loongarch/virt.h"
 | |
| #include "hw/pci-host/ls7a.h"
 | |
| #include "hw/irq.h"
 | |
| #include "hw/intc/loongarch_pch_pic.h"
 | |
| #include "hw/qdev-properties.h"
 | |
| #include "migration/vmstate.h"
 | |
| #include "trace.h"
 | |
| #include "qapi/error.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 {
 | |
|         /*
 | |
|          * intirr means requested pending irq
 | |
|          * do not clear pending irq for edge-triggered on lowering edge
 | |
|          */
 | |
|         val = mask & s->intisr & ~s->intirr;
 | |
|         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 < s->irq_num);
 | |
|     trace_loongarch_pch_pic_irq_handler(irq, level);
 | |
| 
 | |
|     if (s->intedge & mask) {
 | |
|         /* Edge triggered */
 | |
|         if (level) {
 | |
|             if ((s->last_intirr & mask) == 0) {
 | |
|                 /* marked pending on a rising edge */
 | |
|                 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:
 | |
|         /*
 | |
|          * With 7A1000 manual
 | |
|          *   bit  0-15 pch irqchip version
 | |
|          *   bit 16-31 irq number supported with pch irqchip
 | |
|          */
 | |
|         val = deposit32(PCH_PIC_INT_ID_VER, 16, 16, s->irq_num - 1);
 | |
|         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_realize(DeviceState *dev, Error **errp)
 | |
| {
 | |
|     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(dev);
 | |
| 
 | |
|     if (!s->irq_num || s->irq_num  > VIRT_PCH_PIC_IRQ_NUM) {
 | |
|         error_setg(errp, "Invalid 'pic_irq_num'");
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     qdev_init_gpio_out(dev, s->parent_irq, s->irq_num);
 | |
|     qdev_init_gpio_in(dev, pch_pic_irq_handler, s->irq_num);
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 
 | |
| }
 | |
| 
 | |
| static Property loongarch_pch_pic_properties[] = {
 | |
|     DEFINE_PROP_UINT32("pch_pic_irq_num",  LoongArchPCHPIC, irq_num, 0),
 | |
|     DEFINE_PROP_END_OF_LIST(),
 | |
| };
 | |
| 
 | |
| static const VMStateDescription vmstate_loongarch_pch_pic = {
 | |
|     .name = TYPE_LOONGARCH_PCH_PIC,
 | |
|     .version_id = 1,
 | |
|     .minimum_version_id = 1,
 | |
|     .fields = (const 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->realize = loongarch_pch_pic_realize;
 | |
|     dc->reset = loongarch_pch_pic_reset;
 | |
|     dc->vmsd = &vmstate_loongarch_pch_pic;
 | |
|     device_class_set_props(dc, loongarch_pch_pic_properties);
 | |
| }
 | |
| 
 | |
| 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)
 |