153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * Aspeed PECI Controller | ||
|  |  * | ||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
 | ||
|  |  * | ||
|  |  * This code is licensed under the GPL version 2 or later. See the COPYING | ||
|  |  * file in the top-level directory. | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "qemu/osdep.h"
 | ||
|  | #include "qemu/log.h"
 | ||
|  | #include "hw/irq.h"
 | ||
|  | #include "hw/misc/aspeed_peci.h"
 | ||
|  | #include "hw/registerfields.h"
 | ||
|  | #include "trace.h"
 | ||
|  | 
 | ||
|  | #define ASPEED_PECI_CC_RSP_SUCCESS (0x40U)
 | ||
|  | 
 | ||
|  | /* Command Register */ | ||
|  | REG32(PECI_CMD, 0x08) | ||
|  |     FIELD(PECI_CMD, FIRE, 0, 1) | ||
|  | 
 | ||
|  | /* Interrupt Control Register */ | ||
|  | REG32(PECI_INT_CTRL, 0x18) | ||
|  | 
 | ||
|  | /* Interrupt Status Register */ | ||
|  | REG32(PECI_INT_STS, 0x1C) | ||
|  |     FIELD(PECI_INT_STS, CMD_DONE, 0, 1) | ||
|  | 
 | ||
|  | /* Rx/Tx Data Buffer Registers */ | ||
|  | REG32(PECI_WR_DATA0, 0x20) | ||
|  | REG32(PECI_RD_DATA0, 0x30) | ||
|  | 
 | ||
|  | static void aspeed_peci_raise_interrupt(AspeedPECIState *s, uint32_t status) | ||
|  | { | ||
|  |     trace_aspeed_peci_raise_interrupt(s->regs[R_PECI_INT_CTRL], status); | ||
|  | 
 | ||
|  |     s->regs[R_PECI_INT_STS] = s->regs[R_PECI_INT_CTRL] & status; | ||
|  |     if (!s->regs[R_PECI_INT_STS]) { | ||
|  |         return; | ||
|  |     } | ||
|  |     qemu_irq_raise(s->irq); | ||
|  | } | ||
|  | 
 | ||
|  | static uint64_t aspeed_peci_read(void *opaque, hwaddr offset, unsigned size) | ||
|  | { | ||
|  |     AspeedPECIState *s = ASPEED_PECI(opaque); | ||
|  |     uint64_t data; | ||
|  | 
 | ||
|  |     if (offset >= ASPEED_PECI_NR_REGS << 2) { | ||
|  |         qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                       "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", | ||
|  |                       __func__, offset); | ||
|  |         return 0; | ||
|  |     } | ||
|  |     data = s->regs[offset >> 2]; | ||
|  | 
 | ||
|  |     trace_aspeed_peci_read(offset, data); | ||
|  |     return data; | ||
|  | } | ||
|  | 
 | ||
|  | static void aspeed_peci_write(void *opaque, hwaddr offset, uint64_t data, | ||
|  |                               unsigned size) | ||
|  | { | ||
|  |     AspeedPECIState *s = ASPEED_PECI(opaque); | ||
|  | 
 | ||
|  |     trace_aspeed_peci_write(offset, data); | ||
|  | 
 | ||
|  |     if (offset >= ASPEED_PECI_NR_REGS << 2) { | ||
|  |         qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                       "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", | ||
|  |                       __func__, offset); | ||
|  |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     switch (offset) { | ||
|  |     case A_PECI_INT_STS: | ||
|  |         s->regs[R_PECI_INT_STS] &= ~data; | ||
|  |         if (!s->regs[R_PECI_INT_STS]) { | ||
|  |             qemu_irq_lower(s->irq); | ||
|  |         } | ||
|  |         break; | ||
|  |     case A_PECI_CMD: | ||
|  |         /*
 | ||
|  |          * Only the FIRE bit is writable. Once the command is complete, it | ||
|  |          * should be cleared. Since we complete the command immediately, the | ||
|  |          * value is not stored in the register array. | ||
|  |          */ | ||
|  |         if (!FIELD_EX32(data, PECI_CMD, FIRE)) { | ||
|  |             break; | ||
|  |         } | ||
|  |         if (s->regs[R_PECI_INT_STS]) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, "%s: Interrupt status must be " | ||
|  |                           "cleared before firing another command: 0x%08x\n", | ||
|  |                           __func__, s->regs[R_PECI_INT_STS]); | ||
|  |             break; | ||
|  |         } | ||
|  |         s->regs[R_PECI_RD_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; | ||
|  |         s->regs[R_PECI_WR_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; | ||
|  |         aspeed_peci_raise_interrupt(s, | ||
|  |                                     FIELD_DP32(0, PECI_INT_STS, CMD_DONE, 1)); | ||
|  |         break; | ||
|  |     default: | ||
|  |         s->regs[offset / sizeof(s->regs[0])] = data; | ||
|  |         break; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static const MemoryRegionOps aspeed_peci_ops = { | ||
|  |     .read = aspeed_peci_read, | ||
|  |     .write = aspeed_peci_write, | ||
|  |     .endianness = DEVICE_LITTLE_ENDIAN, | ||
|  | }; | ||
|  | 
 | ||
|  | static void aspeed_peci_realize(DeviceState *dev, Error **errp) | ||
|  | { | ||
|  |     AspeedPECIState *s = ASPEED_PECI(dev); | ||
|  |     SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | ||
|  | 
 | ||
|  |     memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_peci_ops, s, | ||
|  |                           TYPE_ASPEED_PECI, 0x1000); | ||
|  |     sysbus_init_mmio(sbd, &s->mmio); | ||
|  |     sysbus_init_irq(sbd, &s->irq); | ||
|  | } | ||
|  | 
 | ||
|  | static void aspeed_peci_reset(DeviceState *dev) | ||
|  | { | ||
|  |     AspeedPECIState *s = ASPEED_PECI(dev); | ||
|  | 
 | ||
|  |     memset(s->regs, 0, sizeof(s->regs)); | ||
|  | } | ||
|  | 
 | ||
|  | static void aspeed_peci_class_init(ObjectClass *klass, void *data) | ||
|  | { | ||
|  |     DeviceClass *dc = DEVICE_CLASS(klass); | ||
|  | 
 | ||
|  |     dc->realize = aspeed_peci_realize; | ||
|  |     dc->reset = aspeed_peci_reset; | ||
|  |     dc->desc = "Aspeed PECI Controller"; | ||
|  | } | ||
|  | 
 | ||
|  | static const TypeInfo aspeed_peci_types[] = { | ||
|  |     { | ||
|  |         .name = TYPE_ASPEED_PECI, | ||
|  |         .parent = TYPE_SYS_BUS_DEVICE, | ||
|  |         .instance_size = sizeof(AspeedPECIState), | ||
|  |         .class_init = aspeed_peci_class_init, | ||
|  |         .abstract = false, | ||
|  |     }, | ||
|  | }; | ||
|  | 
 | ||
|  | DEFINE_TYPES(aspeed_peci_types); |