#include "qemu/osdep.h" #include "hw/core/sysbus.h" #include "qemu/log.h" #include "qom/object.h" #define TYPE_S5L8950X_PMGR "s5l8950x-pmgr" OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XPmgrState, S5L8950X_PMGR) #define PMGR_REGION_SIZE 0x10000 #define PMGR_NUM_REGS (PMGR_REGION_SIZE / 4) #define PMGR_GATE_BASE 0x1000 #define PMGR_GATE_COUNT 0x50 #define PMGR_GATE_END (PMGR_GATE_BASE + PMGR_GATE_COUNT * sizeof(uint32_t)) #define PMGR_DOMAIN_CTRL_STRIDE 0x18 #define PMGR_DOMAIN_COUNT 9 #define PMGR_DOMAIN_STATUS_BASE 0x2010 #define PMGR_SPECIAL_DOMAIN_CTRL 0x100 #define PMGR_SPECIAL_DOMAIN_STATUS 0x2034 #define PMGR_DOMAIN_ACTIVE BIT(30) #define PMGR_TRANSITION_DONE BIT(29) #define PMGR_PLL0_CTRL 0x60 #define PMGR_PLL_LOCKED BIT(29) struct S5L8950XPmgrState { SysBusDevice parent_obj; MemoryRegion iomem; uint32_t regs[PMGR_NUM_REGS]; }; static uint64_t s5l8950x_pmgr_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XPmgrState *s = opaque; uint32_t idx = offset / 4; if (idx < PMGR_NUM_REGS) { return s->regs[idx]; } return 0; } static void s5l8950x_pmgr_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { S5L8950XPmgrState *s = opaque; uint32_t idx = offset / 4; if (idx < PMGR_NUM_REGS) { uint32_t reg = (uint32_t)val; /* * Gate registers expose the requested state in bits [3:0] and the * hardware-acknowledged state in bits [7:4]. Clocks settle * immediately in this model, so mirror the request on every write. */ if (offset >= PMGR_GATE_BASE && offset < PMGR_GATE_END) { reg = (reg & ~0xf0u) | ((reg & 0xfu) << 4); } if (offset == PMGR_PLL0_CTRL) { reg |= PMGR_PLL_LOCKED; } /* Power-domain transitions also complete immediately. */ if (offset < PMGR_DOMAIN_CTRL_STRIDE * PMGR_DOMAIN_COUNT && offset % PMGR_DOMAIN_CTRL_STRIDE == 0) { uint32_t domain = offset / PMGR_DOMAIN_CTRL_STRIDE; uint32_t status = (PMGR_DOMAIN_STATUS_BASE / 4) + domain; if (reg & BIT(31)) { reg |= PMGR_TRANSITION_DONE; } s->regs[status] = deposit32(s->regs[status], 30, 1, !!(reg & PMGR_DOMAIN_ACTIVE)); } else if (offset == PMGR_SPECIAL_DOMAIN_CTRL) { uint32_t status = PMGR_SPECIAL_DOMAIN_STATUS / 4; if (reg & BIT(31)) { reg |= PMGR_TRANSITION_DONE; } s->regs[status] = deposit32(s->regs[status], 30, 1, !!(reg & PMGR_DOMAIN_ACTIVE)); } s->regs[idx] = reg; } else { qemu_log_mask(LOG_UNIMP, "%s: Out of bounds write to offset 0x%" HWADDR_PRIx " with value 0x%" PRIx64 "\n", __func__, offset, val); } } static const MemoryRegionOps s5l8950x_pmgr_ops = { .read = s5l8950x_pmgr_read, .write = s5l8950x_pmgr_write, .endianness = DEVICE_LITTLE_ENDIAN, .impl = { .min_access_size = 4, .max_access_size = 4, }, }; static void s5l8950x_pmgr_reset(DeviceState *dev) { S5L8950XPmgrState *s = S5L8950X_PMGR(dev); int i; /* Initialize all registers to 0xF (all clocks enabled) as reasonable defaults */ for (i = 0; i < PMGR_NUM_REGS; i++) { s->regs[i] = 0xF; } for (i = PMGR_GATE_BASE / 4; i < PMGR_GATE_END / 4; i++) { s->regs[i] = 0xFF; } } static void s5l8950x_pmgr_init(Object *obj) { S5L8950XPmgrState *s = S5L8950X_PMGR(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); memory_region_init_io(&s->iomem, obj, &s5l8950x_pmgr_ops, s, TYPE_S5L8950X_PMGR, PMGR_REGION_SIZE); sysbus_init_mmio(sbd, &s->iomem); } static void s5l8950x_pmgr_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_pmgr_reset); } static const TypeInfo s5l8950x_pmgr_info = { .name = TYPE_S5L8950X_PMGR, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XPmgrState), .instance_init = s5l8950x_pmgr_init, .class_init = s5l8950x_pmgr_class_init, }; static void s5l8950x_pmgr_register_types(void) { type_register_static(&s5l8950x_pmgr_info); } type_init(s5l8950x_pmgr_register_types)