allwinner-h3-dramc.c (11855B)
1/* 2 * Allwinner H3 SDRAM Controller emulation 3 * 4 * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20#include "qemu/osdep.h" 21#include "qemu/units.h" 22#include "qemu/error-report.h" 23#include "hw/sysbus.h" 24#include "migration/vmstate.h" 25#include "qemu/log.h" 26#include "qemu/module.h" 27#include "exec/address-spaces.h" 28#include "hw/qdev-properties.h" 29#include "qapi/error.h" 30#include "hw/misc/allwinner-h3-dramc.h" 31#include "trace.h" 32 33#define REG_INDEX(offset) (offset / sizeof(uint32_t)) 34 35/* DRAMCOM register offsets */ 36enum { 37 REG_DRAMCOM_CR = 0x0000, /* Control Register */ 38}; 39 40/* DRAMCTL register offsets */ 41enum { 42 REG_DRAMCTL_PIR = 0x0000, /* PHY Initialization Register */ 43 REG_DRAMCTL_PGSR = 0x0010, /* PHY General Status Register */ 44 REG_DRAMCTL_STATR = 0x0018, /* Status Register */ 45}; 46 47/* DRAMCTL register flags */ 48enum { 49 REG_DRAMCTL_PGSR_INITDONE = (1 << 0), 50}; 51 52enum { 53 REG_DRAMCTL_STATR_ACTIVE = (1 << 0), 54}; 55 56static void allwinner_h3_dramc_map_rows(AwH3DramCtlState *s, uint8_t row_bits, 57 uint8_t bank_bits, uint16_t page_size) 58{ 59 /* 60 * This function simulates row addressing behavior when bootloader 61 * software attempts to detect the amount of available SDRAM. In U-Boot 62 * the controller is configured with the widest row addressing available. 63 * Then a pattern is written to RAM at an offset on the row boundary size. 64 * If the value read back equals the value read back from the 65 * start of RAM, the bootloader knows the amount of row bits. 66 * 67 * This function inserts a mirrored memory region when the configured row 68 * bits are not matching the actual emulated memory, to simulate the 69 * same behavior on hardware as expected by the bootloader. 70 */ 71 uint8_t row_bits_actual = 0; 72 73 /* Calculate the actual row bits using the ram_size property */ 74 for (uint8_t i = 8; i < 12; i++) { 75 if (1 << i == s->ram_size) { 76 row_bits_actual = i + 3; 77 break; 78 } 79 } 80 81 if (s->ram_size == (1 << (row_bits - 3))) { 82 /* When row bits is the expected value, remove the mirror */ 83 memory_region_set_enabled(&s->row_mirror_alias, false); 84 trace_allwinner_h3_dramc_rowmirror_disable(); 85 86 } else if (row_bits_actual) { 87 /* Row bits not matching ram_size, install the rows mirror */ 88 hwaddr row_mirror = s->ram_addr + ((1ULL << (row_bits_actual + 89 bank_bits)) * page_size); 90 91 memory_region_set_enabled(&s->row_mirror_alias, true); 92 memory_region_set_address(&s->row_mirror_alias, row_mirror); 93 94 trace_allwinner_h3_dramc_rowmirror_enable(row_mirror); 95 } 96} 97 98static uint64_t allwinner_h3_dramcom_read(void *opaque, hwaddr offset, 99 unsigned size) 100{ 101 const AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 102 const uint32_t idx = REG_INDEX(offset); 103 104 if (idx >= AW_H3_DRAMCOM_REGS_NUM) { 105 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 106 __func__, (uint32_t)offset); 107 return 0; 108 } 109 110 trace_allwinner_h3_dramcom_read(offset, s->dramcom[idx], size); 111 112 return s->dramcom[idx]; 113} 114 115static void allwinner_h3_dramcom_write(void *opaque, hwaddr offset, 116 uint64_t val, unsigned size) 117{ 118 AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 119 const uint32_t idx = REG_INDEX(offset); 120 121 trace_allwinner_h3_dramcom_write(offset, val, size); 122 123 if (idx >= AW_H3_DRAMCOM_REGS_NUM) { 124 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 125 __func__, (uint32_t)offset); 126 return; 127 } 128 129 switch (offset) { 130 case REG_DRAMCOM_CR: /* Control Register */ 131 allwinner_h3_dramc_map_rows(s, ((val >> 4) & 0xf) + 1, 132 ((val >> 2) & 0x1) + 2, 133 1 << (((val >> 8) & 0xf) + 3)); 134 break; 135 default: 136 break; 137 }; 138 139 s->dramcom[idx] = (uint32_t) val; 140} 141 142static uint64_t allwinner_h3_dramctl_read(void *opaque, hwaddr offset, 143 unsigned size) 144{ 145 const AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 146 const uint32_t idx = REG_INDEX(offset); 147 148 if (idx >= AW_H3_DRAMCTL_REGS_NUM) { 149 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 150 __func__, (uint32_t)offset); 151 return 0; 152 } 153 154 trace_allwinner_h3_dramctl_read(offset, s->dramctl[idx], size); 155 156 return s->dramctl[idx]; 157} 158 159static void allwinner_h3_dramctl_write(void *opaque, hwaddr offset, 160 uint64_t val, unsigned size) 161{ 162 AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 163 const uint32_t idx = REG_INDEX(offset); 164 165 trace_allwinner_h3_dramctl_write(offset, val, size); 166 167 if (idx >= AW_H3_DRAMCTL_REGS_NUM) { 168 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 169 __func__, (uint32_t)offset); 170 return; 171 } 172 173 switch (offset) { 174 case REG_DRAMCTL_PIR: /* PHY Initialization Register */ 175 s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |= REG_DRAMCTL_PGSR_INITDONE; 176 s->dramctl[REG_INDEX(REG_DRAMCTL_STATR)] |= REG_DRAMCTL_STATR_ACTIVE; 177 break; 178 default: 179 break; 180 } 181 182 s->dramctl[idx] = (uint32_t) val; 183} 184 185static uint64_t allwinner_h3_dramphy_read(void *opaque, hwaddr offset, 186 unsigned size) 187{ 188 const AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 189 const uint32_t idx = REG_INDEX(offset); 190 191 if (idx >= AW_H3_DRAMPHY_REGS_NUM) { 192 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 193 __func__, (uint32_t)offset); 194 return 0; 195 } 196 197 trace_allwinner_h3_dramphy_read(offset, s->dramphy[idx], size); 198 199 return s->dramphy[idx]; 200} 201 202static void allwinner_h3_dramphy_write(void *opaque, hwaddr offset, 203 uint64_t val, unsigned size) 204{ 205 AwH3DramCtlState *s = AW_H3_DRAMC(opaque); 206 const uint32_t idx = REG_INDEX(offset); 207 208 trace_allwinner_h3_dramphy_write(offset, val, size); 209 210 if (idx >= AW_H3_DRAMPHY_REGS_NUM) { 211 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 212 __func__, (uint32_t)offset); 213 return; 214 } 215 216 s->dramphy[idx] = (uint32_t) val; 217} 218 219static const MemoryRegionOps allwinner_h3_dramcom_ops = { 220 .read = allwinner_h3_dramcom_read, 221 .write = allwinner_h3_dramcom_write, 222 .endianness = DEVICE_NATIVE_ENDIAN, 223 .valid = { 224 .min_access_size = 4, 225 .max_access_size = 4, 226 }, 227 .impl.min_access_size = 4, 228}; 229 230static const MemoryRegionOps allwinner_h3_dramctl_ops = { 231 .read = allwinner_h3_dramctl_read, 232 .write = allwinner_h3_dramctl_write, 233 .endianness = DEVICE_NATIVE_ENDIAN, 234 .valid = { 235 .min_access_size = 4, 236 .max_access_size = 4, 237 }, 238 .impl.min_access_size = 4, 239}; 240 241static const MemoryRegionOps allwinner_h3_dramphy_ops = { 242 .read = allwinner_h3_dramphy_read, 243 .write = allwinner_h3_dramphy_write, 244 .endianness = DEVICE_NATIVE_ENDIAN, 245 .valid = { 246 .min_access_size = 4, 247 .max_access_size = 4, 248 }, 249 .impl.min_access_size = 4, 250}; 251 252static void allwinner_h3_dramc_reset(DeviceState *dev) 253{ 254 AwH3DramCtlState *s = AW_H3_DRAMC(dev); 255 256 /* Set default values for registers */ 257 memset(&s->dramcom, 0, sizeof(s->dramcom)); 258 memset(&s->dramctl, 0, sizeof(s->dramctl)); 259 memset(&s->dramphy, 0, sizeof(s->dramphy)); 260} 261 262static void allwinner_h3_dramc_realize(DeviceState *dev, Error **errp) 263{ 264 AwH3DramCtlState *s = AW_H3_DRAMC(dev); 265 266 /* Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported */ 267 for (uint8_t i = 8; i < 13; i++) { 268 if (1 << i == s->ram_size) { 269 break; 270 } else if (i == 12) { 271 error_report("%s: ram-size %u MiB is not supported", 272 __func__, s->ram_size); 273 exit(1); 274 } 275 } 276 277 /* Setup row mirror mappings */ 278 memory_region_init_ram(&s->row_mirror, OBJECT(s), 279 "allwinner-h3-dramc.row-mirror", 280 4 * KiB, &error_abort); 281 memory_region_add_subregion_overlap(get_system_memory(), s->ram_addr, 282 &s->row_mirror, 10); 283 284 memory_region_init_alias(&s->row_mirror_alias, OBJECT(s), 285 "allwinner-h3-dramc.row-mirror-alias", 286 &s->row_mirror, 0, 4 * KiB); 287 memory_region_add_subregion_overlap(get_system_memory(), 288 s->ram_addr + 1 * MiB, 289 &s->row_mirror_alias, 10); 290 memory_region_set_enabled(&s->row_mirror_alias, false); 291} 292 293static void allwinner_h3_dramc_init(Object *obj) 294{ 295 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 296 AwH3DramCtlState *s = AW_H3_DRAMC(obj); 297 298 /* DRAMCOM registers */ 299 memory_region_init_io(&s->dramcom_iomem, OBJECT(s), 300 &allwinner_h3_dramcom_ops, s, 301 TYPE_AW_H3_DRAMC, 4 * KiB); 302 sysbus_init_mmio(sbd, &s->dramcom_iomem); 303 304 /* DRAMCTL registers */ 305 memory_region_init_io(&s->dramctl_iomem, OBJECT(s), 306 &allwinner_h3_dramctl_ops, s, 307 TYPE_AW_H3_DRAMC, 4 * KiB); 308 sysbus_init_mmio(sbd, &s->dramctl_iomem); 309 310 /* DRAMPHY registers */ 311 memory_region_init_io(&s->dramphy_iomem, OBJECT(s), 312 &allwinner_h3_dramphy_ops, s, 313 TYPE_AW_H3_DRAMC, 4 * KiB); 314 sysbus_init_mmio(sbd, &s->dramphy_iomem); 315} 316 317static Property allwinner_h3_dramc_properties[] = { 318 DEFINE_PROP_UINT64("ram-addr", AwH3DramCtlState, ram_addr, 0x0), 319 DEFINE_PROP_UINT32("ram-size", AwH3DramCtlState, ram_size, 256 * MiB), 320 DEFINE_PROP_END_OF_LIST() 321}; 322 323static const VMStateDescription allwinner_h3_dramc_vmstate = { 324 .name = "allwinner-h3-dramc", 325 .version_id = 1, 326 .minimum_version_id = 1, 327 .fields = (VMStateField[]) { 328 VMSTATE_UINT32_ARRAY(dramcom, AwH3DramCtlState, AW_H3_DRAMCOM_REGS_NUM), 329 VMSTATE_UINT32_ARRAY(dramctl, AwH3DramCtlState, AW_H3_DRAMCTL_REGS_NUM), 330 VMSTATE_UINT32_ARRAY(dramphy, AwH3DramCtlState, AW_H3_DRAMPHY_REGS_NUM), 331 VMSTATE_END_OF_LIST() 332 } 333}; 334 335static void allwinner_h3_dramc_class_init(ObjectClass *klass, void *data) 336{ 337 DeviceClass *dc = DEVICE_CLASS(klass); 338 339 dc->reset = allwinner_h3_dramc_reset; 340 dc->vmsd = &allwinner_h3_dramc_vmstate; 341 dc->realize = allwinner_h3_dramc_realize; 342 device_class_set_props(dc, allwinner_h3_dramc_properties); 343} 344 345static const TypeInfo allwinner_h3_dramc_info = { 346 .name = TYPE_AW_H3_DRAMC, 347 .parent = TYPE_SYS_BUS_DEVICE, 348 .instance_init = allwinner_h3_dramc_init, 349 .instance_size = sizeof(AwH3DramCtlState), 350 .class_init = allwinner_h3_dramc_class_init, 351}; 352 353static void allwinner_h3_dramc_register(void) 354{ 355 type_register_static(&allwinner_h3_dramc_info); 356} 357 358type_init(allwinner_h3_dramc_register)