| 1 | /* |
| 2 | * Allwinner R40 SDRAM Controller emulation |
| 3 | * |
| 4 | * CCopyright (C) 2023 qianfan Zhao <qianfanguijin@163.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/core/sysbus.h" |
| 24 | #include "migration/vmstate.h" |
| 25 | #include "qemu/log.h" |
| 26 | #include "qemu/module.h" |
| 27 | #include "system/address-spaces.h" |
| 28 | #include "hw/core/qdev-properties.h" |
| 29 | #include "qapi/error.h" |
| 30 | #include "qemu/bitops.h" |
| 31 | #include "hw/misc/allwinner-r40-dramc.h" |
| 32 | #include "trace.h" |
| 33 | |
| 34 | #define REG_INDEX(offset) (offset / sizeof(uint32_t)) |
| 35 | |
| 36 | /* DRAMCOM register offsets */ |
| 37 | enum { |
| 38 | REG_DRAMCOM_CR = 0x0000, /* Control Register */ |
| 39 | }; |
| 40 | |
| 41 | /* DRAMCOMM register flags */ |
| 42 | enum { |
| 43 | REG_DRAMCOM_CR_DUAL_RANK = (1 << 0), |
| 44 | }; |
| 45 | |
| 46 | /* DRAMCTL register offsets */ |
| 47 | enum { |
| 48 | REG_DRAMCTL_PIR = 0x0000, /* PHY Initialization Register */ |
| 49 | REG_DRAMCTL_PGSR = 0x0010, /* PHY General Status Register */ |
| 50 | REG_DRAMCTL_STATR = 0x0018, /* Status Register */ |
| 51 | REG_DRAMCTL_PGCR = 0x0100, /* PHY general configuration registers */ |
| 52 | }; |
| 53 | |
| 54 | /* DRAMCTL register flags */ |
| 55 | enum { |
| 56 | REG_DRAMCTL_PGSR_INITDONE = (1 << 0), |
| 57 | REG_DRAMCTL_PGSR_READ_TIMEOUT = (1 << 13), |
| 58 | REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT = (1 << 25), |
| 59 | }; |
| 60 | |
| 61 | enum { |
| 62 | REG_DRAMCTL_STATR_ACTIVE = (1 << 0), |
| 63 | }; |
| 64 | |
| 65 | #define DRAM_MAX_ROW_BITS 16 |
| 66 | #define DRAM_MAX_COL_BITS 13 /* 8192 */ |
| 67 | #define DRAM_MAX_BANK 3 |
| 68 | |
| 69 | static uint64_t dram_autodetect_cells[DRAM_MAX_ROW_BITS] |
| 70 | [DRAM_MAX_BANK] |
| 71 | [DRAM_MAX_COL_BITS]; |
| 72 | struct VirtualDDRChip { |
| 73 | uint32_t ram_size; |
| 74 | uint8_t bank_bits; |
| 75 | uint8_t row_bits; |
| 76 | uint8_t col_bits; |
| 77 | }; |
| 78 | |
| 79 | /* |
| 80 | * Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported, |
| 81 | * 2GiB memory is not supported due to dual rank feature. |
| 82 | */ |
| 83 | static const struct VirtualDDRChip dummy_ddr_chips[] = { |
| 84 | { |
| 85 | .ram_size = 256, |
| 86 | .bank_bits = 3, |
| 87 | .row_bits = 12, |
| 88 | .col_bits = 13, |
| 89 | }, { |
| 90 | .ram_size = 512, |
| 91 | .bank_bits = 3, |
| 92 | .row_bits = 13, |
| 93 | .col_bits = 13, |
| 94 | }, { |
| 95 | .ram_size = 1024, |
| 96 | .bank_bits = 3, |
| 97 | .row_bits = 14, |
| 98 | .col_bits = 13, |
| 99 | }, { |
| 100 | 0 |
| 101 | } |
| 102 | }; |
| 103 | |
| 104 | static const struct VirtualDDRChip *get_match_ddr(uint32_t ram_size) |
| 105 | { |
| 106 | const struct VirtualDDRChip *ddr; |
| 107 | |
| 108 | for (ddr = &dummy_ddr_chips[0]; ddr->ram_size; ddr++) { |
| 109 | if (ddr->ram_size == ram_size) { |
| 110 | return ddr; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | return NULL; |
| 115 | } |
| 116 | |
| 117 | static uint64_t *address_to_autodetect_cells(AwR40DramCtlState *s, |
| 118 | const struct VirtualDDRChip *ddr, |
| 119 | uint32_t offset) |
| 120 | { |
| 121 | int row_index = 0, bank_index = 0, col_index = 0; |
| 122 | uint32_t row_addr, bank_addr, col_addr; |
| 123 | |
| 124 | row_addr = extract32(offset, s->set_col_bits + s->set_bank_bits, |
| 125 | s->set_row_bits); |
| 126 | bank_addr = extract32(offset, s->set_col_bits, s->set_bank_bits); |
| 127 | col_addr = extract32(offset, 0, s->set_col_bits); |
| 128 | |
| 129 | for (int i = 0; i < ddr->row_bits; i++) { |
| 130 | if (row_addr & BIT(i)) { |
| 131 | row_index = i; |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | for (int i = 0; i < ddr->bank_bits; i++) { |
| 136 | if (bank_addr & BIT(i)) { |
| 137 | bank_index = i; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | for (int i = 0; i < ddr->col_bits; i++) { |
| 142 | if (col_addr & BIT(i)) { |
| 143 | col_index = i; |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | trace_allwinner_r40_dramc_offset_to_cell(offset, row_index, bank_index, |
| 148 | col_index); |
| 149 | return &dram_autodetect_cells[row_index][bank_index][col_index]; |
| 150 | } |
| 151 | |
| 152 | static void allwinner_r40_dramc_map_rows(AwR40DramCtlState *s, uint8_t row_bits, |
| 153 | uint8_t bank_bits, uint8_t col_bits) |
| 154 | { |
| 155 | const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); |
| 156 | bool enable_detect_cells; |
| 157 | |
| 158 | trace_allwinner_r40_dramc_map_rows(row_bits, bank_bits, col_bits); |
| 159 | |
| 160 | if (!ddr) { |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | s->set_row_bits = row_bits; |
| 165 | s->set_bank_bits = bank_bits; |
| 166 | s->set_col_bits = col_bits; |
| 167 | |
| 168 | enable_detect_cells = ddr->bank_bits != bank_bits |
| 169 | || ddr->row_bits != row_bits |
| 170 | || ddr->col_bits != col_bits; |
| 171 | |
| 172 | if (enable_detect_cells) { |
| 173 | trace_allwinner_r40_dramc_detect_cells_enable(); |
| 174 | } else { |
| 175 | trace_allwinner_r40_dramc_detect_cells_disable(); |
| 176 | } |
| 177 | |
| 178 | memory_region_set_enabled(&s->detect_cells, enable_detect_cells); |
| 179 | } |
| 180 | |
| 181 | static uint64_t allwinner_r40_dramcom_read(void *opaque, hwaddr offset, |
| 182 | unsigned size) |
| 183 | { |
| 184 | const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 185 | const uint32_t idx = REG_INDEX(offset); |
| 186 | |
| 187 | if (idx >= AW_R40_DRAMCOM_REGS_NUM) { |
| 188 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 189 | __func__, (uint32_t)offset); |
| 190 | return 0; |
| 191 | } |
| 192 | |
| 193 | trace_allwinner_r40_dramcom_read(offset, s->dramcom[idx], size); |
| 194 | return s->dramcom[idx]; |
| 195 | } |
| 196 | |
| 197 | static void allwinner_r40_dramcom_write(void *opaque, hwaddr offset, |
| 198 | uint64_t val, unsigned size) |
| 199 | { |
| 200 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 201 | const uint32_t idx = REG_INDEX(offset); |
| 202 | |
| 203 | trace_allwinner_r40_dramcom_write(offset, val, size); |
| 204 | |
| 205 | if (idx >= AW_R40_DRAMCOM_REGS_NUM) { |
| 206 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 207 | __func__, (uint32_t)offset); |
| 208 | return; |
| 209 | } |
| 210 | |
| 211 | switch (offset) { |
| 212 | case REG_DRAMCOM_CR: /* Control Register */ |
| 213 | if (!(val & REG_DRAMCOM_CR_DUAL_RANK)) { |
| 214 | allwinner_r40_dramc_map_rows(s, ((val >> 4) & 0xf) + 1, |
| 215 | ((val >> 2) & 0x1) + 2, |
| 216 | (((val >> 8) & 0xf) + 3)); |
| 217 | } |
| 218 | break; |
| 219 | }; |
| 220 | |
| 221 | s->dramcom[idx] = (uint32_t) val; |
| 222 | } |
| 223 | |
| 224 | static uint64_t allwinner_r40_dramctl_read(void *opaque, hwaddr offset, |
| 225 | unsigned size) |
| 226 | { |
| 227 | const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 228 | const uint32_t idx = REG_INDEX(offset); |
| 229 | |
| 230 | if (idx >= AW_R40_DRAMCTL_REGS_NUM) { |
| 231 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 232 | __func__, (uint32_t)offset); |
| 233 | return 0; |
| 234 | } |
| 235 | |
| 236 | trace_allwinner_r40_dramctl_read(offset, s->dramctl[idx], size); |
| 237 | return s->dramctl[idx]; |
| 238 | } |
| 239 | |
| 240 | static void allwinner_r40_dramctl_write(void *opaque, hwaddr offset, |
| 241 | uint64_t val, unsigned size) |
| 242 | { |
| 243 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 244 | const uint32_t idx = REG_INDEX(offset); |
| 245 | |
| 246 | trace_allwinner_r40_dramctl_write(offset, val, size); |
| 247 | |
| 248 | if (idx >= AW_R40_DRAMCTL_REGS_NUM) { |
| 249 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 250 | __func__, (uint32_t)offset); |
| 251 | return; |
| 252 | } |
| 253 | |
| 254 | switch (offset) { |
| 255 | case REG_DRAMCTL_PIR: /* PHY Initialization Register */ |
| 256 | s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |= REG_DRAMCTL_PGSR_INITDONE; |
| 257 | s->dramctl[REG_INDEX(REG_DRAMCTL_STATR)] |= REG_DRAMCTL_STATR_ACTIVE; |
| 258 | break; |
| 259 | } |
| 260 | |
| 261 | s->dramctl[idx] = (uint32_t) val; |
| 262 | } |
| 263 | |
| 264 | static uint64_t allwinner_r40_dramphy_read(void *opaque, hwaddr offset, |
| 265 | unsigned size) |
| 266 | { |
| 267 | const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 268 | const uint32_t idx = REG_INDEX(offset); |
| 269 | |
| 270 | if (idx >= AW_R40_DRAMPHY_REGS_NUM) { |
| 271 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 272 | __func__, (uint32_t)offset); |
| 273 | return 0; |
| 274 | } |
| 275 | |
| 276 | trace_allwinner_r40_dramphy_read(offset, s->dramphy[idx], size); |
| 277 | return s->dramphy[idx]; |
| 278 | } |
| 279 | |
| 280 | static void allwinner_r40_dramphy_write(void *opaque, hwaddr offset, |
| 281 | uint64_t val, unsigned size) |
| 282 | { |
| 283 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 284 | const uint32_t idx = REG_INDEX(offset); |
| 285 | |
| 286 | trace_allwinner_r40_dramphy_write(offset, val, size); |
| 287 | |
| 288 | if (idx >= AW_R40_DRAMPHY_REGS_NUM) { |
| 289 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", |
| 290 | __func__, (uint32_t)offset); |
| 291 | return; |
| 292 | } |
| 293 | |
| 294 | s->dramphy[idx] = (uint32_t) val; |
| 295 | } |
| 296 | |
| 297 | static const MemoryRegionOps allwinner_r40_dramcom_ops = { |
| 298 | .read = allwinner_r40_dramcom_read, |
| 299 | .write = allwinner_r40_dramcom_write, |
| 300 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 301 | .valid = { |
| 302 | .min_access_size = 4, |
| 303 | .max_access_size = 4, |
| 304 | }, |
| 305 | .impl.min_access_size = 4, |
| 306 | }; |
| 307 | |
| 308 | static const MemoryRegionOps allwinner_r40_dramctl_ops = { |
| 309 | .read = allwinner_r40_dramctl_read, |
| 310 | .write = allwinner_r40_dramctl_write, |
| 311 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 312 | .valid = { |
| 313 | .min_access_size = 4, |
| 314 | .max_access_size = 4, |
| 315 | }, |
| 316 | .impl.min_access_size = 4, |
| 317 | }; |
| 318 | |
| 319 | static const MemoryRegionOps allwinner_r40_dramphy_ops = { |
| 320 | .read = allwinner_r40_dramphy_read, |
| 321 | .write = allwinner_r40_dramphy_write, |
| 322 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 323 | .valid = { |
| 324 | .min_access_size = 4, |
| 325 | .max_access_size = 4, |
| 326 | }, |
| 327 | .impl.min_access_size = 4, |
| 328 | }; |
| 329 | |
| 330 | static uint64_t allwinner_r40_detect_read(void *opaque, hwaddr offset, |
| 331 | unsigned size) |
| 332 | { |
| 333 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 334 | const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); |
| 335 | uint64_t data = 0; |
| 336 | |
| 337 | if (ddr) { |
| 338 | data = *address_to_autodetect_cells(s, ddr, (uint32_t)offset); |
| 339 | } |
| 340 | |
| 341 | trace_allwinner_r40_dramc_detect_cell_read(offset, data); |
| 342 | return data; |
| 343 | } |
| 344 | |
| 345 | static void allwinner_r40_detect_write(void *opaque, hwaddr offset, |
| 346 | uint64_t data, unsigned size) |
| 347 | { |
| 348 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 349 | const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); |
| 350 | |
| 351 | if (ddr) { |
| 352 | uint64_t *cell = address_to_autodetect_cells(s, ddr, (uint32_t)offset); |
| 353 | trace_allwinner_r40_dramc_detect_cell_write(offset, data); |
| 354 | *cell = data; |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | static const MemoryRegionOps allwinner_r40_detect_ops = { |
| 359 | .read = allwinner_r40_detect_read, |
| 360 | .write = allwinner_r40_detect_write, |
| 361 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 362 | .valid = { |
| 363 | .min_access_size = 4, |
| 364 | .max_access_size = 4, |
| 365 | }, |
| 366 | .impl.min_access_size = 4, |
| 367 | }; |
| 368 | |
| 369 | /* |
| 370 | * mctl_r40_detect_rank_count in u-boot will write the high 1G of DDR |
| 371 | * to detect whether the board support dual_rank or not. Create a virtual memory |
| 372 | * if the board's ram_size less or equal than 1G, and set read time out flag of |
| 373 | * REG_DRAMCTL_PGSR when the user touch this high dram. |
| 374 | */ |
| 375 | static uint64_t allwinner_r40_dualrank_detect_read(void *opaque, hwaddr offset, |
| 376 | unsigned size) |
| 377 | { |
| 378 | AwR40DramCtlState *s = AW_R40_DRAMC(opaque); |
| 379 | uint32_t reg; |
| 380 | |
| 381 | reg = s->dramctl[REG_INDEX(REG_DRAMCTL_PGCR)]; |
| 382 | if (reg & REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT) { /* Enable read time out */ |
| 383 | /* |
| 384 | * this driver only support one rank, mark READ_TIMEOUT when try |
| 385 | * read the second rank. |
| 386 | */ |
| 387 | s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |
| 388 | |= REG_DRAMCTL_PGSR_READ_TIMEOUT; |
| 389 | } |
| 390 | |
| 391 | return 0; |
| 392 | } |
| 393 | |
| 394 | static const MemoryRegionOps allwinner_r40_dualrank_detect_ops = { |
| 395 | .read = allwinner_r40_dualrank_detect_read, |
| 396 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 397 | .valid = { |
| 398 | .min_access_size = 4, |
| 399 | .max_access_size = 4, |
| 400 | }, |
| 401 | .impl.min_access_size = 4, |
| 402 | }; |
| 403 | |
| 404 | static void allwinner_r40_dramc_reset(DeviceState *dev) |
| 405 | { |
| 406 | AwR40DramCtlState *s = AW_R40_DRAMC(dev); |
| 407 | |
| 408 | /* Set default values for registers */ |
| 409 | memset(&s->dramcom, 0, sizeof(s->dramcom)); |
| 410 | memset(&s->dramctl, 0, sizeof(s->dramctl)); |
| 411 | memset(&s->dramphy, 0, sizeof(s->dramphy)); |
| 412 | } |
| 413 | |
| 414 | static void allwinner_r40_dramc_realize(DeviceState *dev, Error **errp) |
| 415 | { |
| 416 | AwR40DramCtlState *s = AW_R40_DRAMC(dev); |
| 417 | |
| 418 | if (!get_match_ddr(s->ram_size)) { |
| 419 | error_report("%s: ram-size %u MiB is not supported", |
| 420 | __func__, s->ram_size); |
| 421 | exit(1); |
| 422 | } |
| 423 | |
| 424 | /* R40 support max 2G memory but we only support up to 1G now. */ |
| 425 | memory_region_init_io(&s->detect_cells, OBJECT(s), |
| 426 | &allwinner_r40_detect_ops, s, |
| 427 | "DRAMCELLS", 1 * GiB); |
| 428 | memory_region_add_subregion_overlap(get_system_memory(), s->ram_addr, |
| 429 | &s->detect_cells, 10); |
| 430 | memory_region_set_enabled(&s->detect_cells, false); |
| 431 | |
| 432 | /* |
| 433 | * We only support DRAM size up to 1G now, so prepare a high memory page |
| 434 | * after 1G for dualrank detect. |
| 435 | */ |
| 436 | memory_region_init_io(&s->dram_high, OBJECT(s), |
| 437 | &allwinner_r40_dualrank_detect_ops, s, |
| 438 | "DRAMHIGH", KiB); |
| 439 | memory_region_add_subregion(get_system_memory(), s->ram_addr + GiB, |
| 440 | &s->dram_high); |
| 441 | } |
| 442 | |
| 443 | static void allwinner_r40_dramc_init(Object *obj) |
| 444 | { |
| 445 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| 446 | AwR40DramCtlState *s = AW_R40_DRAMC(obj); |
| 447 | |
| 448 | /* DRAMCOM registers, index 0 */ |
| 449 | memory_region_init_io(&s->dramcom_iomem, OBJECT(s), |
| 450 | &allwinner_r40_dramcom_ops, s, |
| 451 | "DRAMCOM", 4 * KiB); |
| 452 | sysbus_init_mmio(sbd, &s->dramcom_iomem); |
| 453 | |
| 454 | /* DRAMCTL registers, index 1 */ |
| 455 | memory_region_init_io(&s->dramctl_iomem, OBJECT(s), |
| 456 | &allwinner_r40_dramctl_ops, s, |
| 457 | "DRAMCTL", 4 * KiB); |
| 458 | sysbus_init_mmio(sbd, &s->dramctl_iomem); |
| 459 | |
| 460 | /* DRAMPHY registers. index 2 */ |
| 461 | memory_region_init_io(&s->dramphy_iomem, OBJECT(s), |
| 462 | &allwinner_r40_dramphy_ops, s, |
| 463 | "DRAMPHY", 4 * KiB); |
| 464 | sysbus_init_mmio(sbd, &s->dramphy_iomem); |
| 465 | } |
| 466 | |
| 467 | static const Property allwinner_r40_dramc_properties[] = { |
| 468 | DEFINE_PROP_UINT64("ram-addr", AwR40DramCtlState, ram_addr, 0x0), |
| 469 | DEFINE_PROP_UINT32("ram-size", AwR40DramCtlState, ram_size, 256), /* MiB */ |
| 470 | }; |
| 471 | |
| 472 | static const VMStateDescription allwinner_r40_dramc_vmstate = { |
| 473 | .name = "allwinner-r40-dramc", |
| 474 | .version_id = 1, |
| 475 | .minimum_version_id = 1, |
| 476 | .fields = (const VMStateField[]) { |
| 477 | VMSTATE_UINT32_ARRAY(dramcom, AwR40DramCtlState, |
| 478 | AW_R40_DRAMCOM_REGS_NUM), |
| 479 | VMSTATE_UINT32_ARRAY(dramctl, AwR40DramCtlState, |
| 480 | AW_R40_DRAMCTL_REGS_NUM), |
| 481 | VMSTATE_UINT32_ARRAY(dramphy, AwR40DramCtlState, |
| 482 | AW_R40_DRAMPHY_REGS_NUM), |
| 483 | VMSTATE_END_OF_LIST() |
| 484 | } |
| 485 | }; |
| 486 | |
| 487 | static void allwinner_r40_dramc_class_init(ObjectClass *klass, const void *data) |
| 488 | { |
| 489 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 490 | |
| 491 | device_class_set_legacy_reset(dc, allwinner_r40_dramc_reset); |
| 492 | dc->vmsd = &allwinner_r40_dramc_vmstate; |
| 493 | dc->realize = allwinner_r40_dramc_realize; |
| 494 | device_class_set_props(dc, allwinner_r40_dramc_properties); |
| 495 | } |
| 496 | |
| 497 | static const TypeInfo allwinner_r40_dramc_info = { |
| 498 | .name = TYPE_AW_R40_DRAMC, |
| 499 | .parent = TYPE_SYS_BUS_DEVICE, |
| 500 | .instance_init = allwinner_r40_dramc_init, |
| 501 | .instance_size = sizeof(AwR40DramCtlState), |
| 502 | .class_init = allwinner_r40_dramc_class_init, |
| 503 | }; |
| 504 | |
| 505 | static void allwinner_r40_dramc_register(void) |
| 506 | { |
| 507 | type_register_static(&allwinner_r40_dramc_info); |
| 508 | } |
| 509 | |
| 510 | type_init(allwinner_r40_dramc_register) |