qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

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 */
     36 enum {
     37     REG_DRAMCOM_CR    = 0x0000, /* Control Register */
     38 };
     39 
     40 /* DRAMCTL register offsets */
     41 enum {
     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 */
     48 enum {
     49     REG_DRAMCTL_PGSR_INITDONE = (1 << 0),
     50 };
     51 
     52 enum {
     53     REG_DRAMCTL_STATR_ACTIVE  = (1 << 0),
     54 };
     55 
     56 static 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 
     98 static 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 
    115 static 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 
    142 static 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 
    159 static 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 
    185 static 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 
    202 static 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 
    219 static 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 
    230 static 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 
    241 static 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 
    252 static 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 
    262 static 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 
    293 static 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 
    317 static 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 
    323 static 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 
    335 static 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 
    345 static 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 
    353 static void allwinner_h3_dramc_register(void)
    354 {
    355     type_register_static(&allwinner_h3_dramc_info);
    356 }
    357 
    358 type_init(allwinner_h3_dramc_register)