2 * Allwinner H3 SDRAM Controller emulation
4 * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
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.
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.
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/>.
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"
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"
33 #define REG_INDEX(offset) (offset / sizeof(uint32_t))
35 /* DRAMCOM register offsets */
37 REG_DRAMCOM_CR
= 0x0000, /* Control Register */
40 /* DRAMCTL register offsets */
42 REG_DRAMCTL_PIR
= 0x0000, /* PHY Initialization Register */
43 REG_DRAMCTL_PGSR
= 0x0010, /* PHY General Status Register */
44 REG_DRAMCTL_STATR
= 0x0018, /* Status Register */
47 /* DRAMCTL register flags */
49 REG_DRAMCTL_PGSR_INITDONE
= (1 << 0),
53 REG_DRAMCTL_STATR_ACTIVE
= (1 << 0),
56 static void allwinner_h3_dramc_map_rows(AwH3DramCtlState
*s
, uint8_t row_bits
,
57 uint8_t bank_bits
, uint16_t page_size
)
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.
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.
71 uint8_t row_bits_actual
= 0;
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;
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();
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
);
91 memory_region_set_enabled(&s
->row_mirror_alias
, true);
92 memory_region_set_address(&s
->row_mirror_alias
, row_mirror
);
94 trace_allwinner_h3_dramc_rowmirror_enable(row_mirror
);
98 static uint64_t allwinner_h3_dramcom_read(void *opaque
, hwaddr offset
,
101 const AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
102 const uint32_t idx
= REG_INDEX(offset
);
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
);
110 trace_allwinner_h3_dramcom_read(offset
, s
->dramcom
[idx
], size
);
112 return s
->dramcom
[idx
];
115 static void allwinner_h3_dramcom_write(void *opaque
, hwaddr offset
,
116 uint64_t val
, unsigned size
)
118 AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
119 const uint32_t idx
= REG_INDEX(offset
);
121 trace_allwinner_h3_dramcom_write(offset
, val
, size
);
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
);
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));
139 s
->dramcom
[idx
] = (uint32_t) val
;
142 static uint64_t allwinner_h3_dramctl_read(void *opaque
, hwaddr offset
,
145 const AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
146 const uint32_t idx
= REG_INDEX(offset
);
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
);
154 trace_allwinner_h3_dramctl_read(offset
, s
->dramctl
[idx
], size
);
156 return s
->dramctl
[idx
];
159 static void allwinner_h3_dramctl_write(void *opaque
, hwaddr offset
,
160 uint64_t val
, unsigned size
)
162 AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
163 const uint32_t idx
= REG_INDEX(offset
);
165 trace_allwinner_h3_dramctl_write(offset
, val
, size
);
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
);
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
;
182 s
->dramctl
[idx
] = (uint32_t) val
;
185 static uint64_t allwinner_h3_dramphy_read(void *opaque
, hwaddr offset
,
188 const AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
189 const uint32_t idx
= REG_INDEX(offset
);
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
);
197 trace_allwinner_h3_dramphy_read(offset
, s
->dramphy
[idx
], size
);
199 return s
->dramphy
[idx
];
202 static void allwinner_h3_dramphy_write(void *opaque
, hwaddr offset
,
203 uint64_t val
, unsigned size
)
205 AwH3DramCtlState
*s
= AW_H3_DRAMC(opaque
);
206 const uint32_t idx
= REG_INDEX(offset
);
208 trace_allwinner_h3_dramphy_write(offset
, val
, size
);
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
);
216 s
->dramphy
[idx
] = (uint32_t) val
;
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
,
224 .min_access_size
= 4,
225 .max_access_size
= 4,
227 .impl
.min_access_size
= 4,
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
,
235 .min_access_size
= 4,
236 .max_access_size
= 4,
238 .impl
.min_access_size
= 4,
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
,
246 .min_access_size
= 4,
247 .max_access_size
= 4,
249 .impl
.min_access_size
= 4,
252 static void allwinner_h3_dramc_reset(DeviceState
*dev
)
254 AwH3DramCtlState
*s
= AW_H3_DRAMC(dev
);
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
));
262 static void allwinner_h3_dramc_realize(DeviceState
*dev
, Error
**errp
)
264 AwH3DramCtlState
*s
= AW_H3_DRAMC(dev
);
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
) {
270 } else if (i
== 12) {
271 error_report("%s: ram-size %u MiB is not supported",
272 __func__
, s
->ram_size
);
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
,
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);
293 static void allwinner_h3_dramc_init(Object
*obj
)
295 SysBusDevice
*sbd
= SYS_BUS_DEVICE(obj
);
296 AwH3DramCtlState
*s
= AW_H3_DRAMC(obj
);
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
);
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
);
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
);
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()
323 static const VMStateDescription allwinner_h3_dramc_vmstate
= {
324 .name
= "allwinner-h3-dramc",
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()
335 static void allwinner_h3_dramc_class_init(ObjectClass
*klass
, void *data
)
337 DeviceClass
*dc
= DEVICE_CLASS(klass
);
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
);
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
,
353 static void allwinner_h3_dramc_register(void)
355 type_register_static(&allwinner_h3_dramc_info
);
358 type_init(allwinner_h3_dramc_register
)