qemu/hw/misc/allwinner-h3-dramc.c
<<
>>
Prefs
   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)
 359