/* * Apple S5L8950X DRAM controller training/status model. * * iBEC programs the timing registers and then waits for the controller and * PHY training state in register zero. QEMU's RAM is already usable, so the * training phases complete synchronously while ordinary register values are * retained for debugger inspection. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "hw/arm/s5l8950x.h" #include "hw/core/sysbus.h" #include "qom/object.h" #define S5L8950X_MEMCTL_REGION_SIZE 0x1000 #define S5L8950X_MEMCTL_NUM_REGS \ (S5L8950X_MEMCTL_REGION_SIZE / sizeof(uint32_t)) /* Controller ready, PHY ready and all low training stages complete. */ #define S5L8950X_MEMCTL_READY 0x801003ffu OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XMemctlState, S5L8950X_MEMCTL) struct S5L8950XMemctlState { SysBusDevice parent_obj; MemoryRegion iomem; uint32_t regs[S5L8950X_MEMCTL_NUM_REGS]; }; static uint64_t s5l8950x_memctl_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XMemctlState *s = opaque; if (offset == 0) { uint32_t status = s->regs[0] | S5L8950X_MEMCTL_READY; /* * The 0b101 command starts a training phase whose lane bits are * active-low. iBEC clears the command before waiting for them to * return to the completed state. */ if ((s->regs[0x14 / 4] & 5) == 5) { status &= ~0x000003f0u; } /* * Register 0x08 bit 31 requests a controller state transition. The * corresponding completion state is exposed in status bit 10 and is * cleared again when iBEC drops the request. */ if (s->regs[0x08 / 4] & BIT(31)) { status |= BIT(10); } else { status &= ~BIT(10); } return status; } /* * The PHY command/status block starts at 0x34. Its status register at * 0x44 reports both command-complete stages once the programmed timing * values have been accepted. */ if (offset == 0x44) { return s->regs[offset / sizeof(uint32_t)] | 0x00500000u; } return s->regs[offset / sizeof(uint32_t)]; } static void s5l8950x_memctl_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XMemctlState *s = opaque; s->regs[offset / sizeof(uint32_t)] = value; } static const MemoryRegionOps s5l8950x_memctl_ops = { .read = s5l8950x_memctl_read, .write = s5l8950x_memctl_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, }, }; static void s5l8950x_memctl_reset(DeviceState *dev) { S5L8950XMemctlState *s = S5L8950X_MEMCTL(dev); memset(s->regs, 0, sizeof(s->regs)); } static void s5l8950x_memctl_init(Object *obj) { S5L8950XMemctlState *s = S5L8950X_MEMCTL(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); memory_region_init_io(&s->iomem, obj, &s5l8950x_memctl_ops, s, TYPE_S5L8950X_MEMCTL, S5L8950X_MEMCTL_REGION_SIZE); sysbus_init_mmio(sbd, &s->iomem); } static void s5l8950x_memctl_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_memctl_reset); } static const TypeInfo s5l8950x_memctl_info = { .name = TYPE_S5L8950X_MEMCTL, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XMemctlState), .instance_init = s5l8950x_memctl_init, .class_init = s5l8950x_memctl_class_init, }; static void s5l8950x_memctl_register_types(void) { type_register_static(&s5l8950x_memctl_info); } type_init(s5l8950x_memctl_register_types)