aboutsummaryrefslogtreecommitdiff
path: root/hw/misc
diff options
context:
space:
mode:
Diffstat (limited to 'hw/misc')
-rw-r--r--hw/misc/Kconfig5
-rw-r--r--hw/misc/allwinner-r40-ccu.c209
-rw-r--r--hw/misc/allwinner-r40-dramc.c513
-rw-r--r--hw/misc/allwinner-sramc.c184
-rw-r--r--hw/misc/axp209.c238
-rw-r--r--hw/misc/axp2xx.c283
-rw-r--r--hw/misc/meson.build5
-rw-r--r--hw/misc/trace-events26
8 files changed, 1219 insertions, 244 deletions
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 2ef5781..e4c2149 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -170,13 +170,16 @@ config VIRT_CTRL
config LASI
bool
+config ALLWINNER_SRAMC
+ bool
+
config ALLWINNER_A10_CCM
bool
config ALLWINNER_A10_DRAMC
bool
-config AXP209_PMU
+config AXP2XX_PMU
bool
depends on I2C
diff --git a/hw/misc/allwinner-r40-ccu.c b/hw/misc/allwinner-r40-ccu.c
new file mode 100644
index 0000000..d82fee1
--- /dev/null
+++ b/hw/misc/allwinner-r40-ccu.c
@@ -0,0 +1,209 @@
+/*
+ * Allwinner R40 Clock Control Unit emulation
+ *
+ * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/allwinner-r40-ccu.h"
+
+/* CCU register offsets */
+enum {
+ REG_PLL_CPUX_CTRL = 0x0000,
+ REG_PLL_AUDIO_CTRL = 0x0008,
+ REG_PLL_VIDEO0_CTRL = 0x0010,
+ REG_PLL_VE_CTRL = 0x0018,
+ REG_PLL_DDR0_CTRL = 0x0020,
+ REG_PLL_PERIPH0_CTRL = 0x0028,
+ REG_PLL_PERIPH1_CTRL = 0x002c,
+ REG_PLL_VIDEO1_CTRL = 0x0030,
+ REG_PLL_SATA_CTRL = 0x0034,
+ REG_PLL_GPU_CTRL = 0x0038,
+ REG_PLL_MIPI_CTRL = 0x0040,
+ REG_PLL_DE_CTRL = 0x0048,
+ REG_PLL_DDR1_CTRL = 0x004c,
+ REG_AHB1_APB1_CFG = 0x0054,
+ REG_APB2_CFG = 0x0058,
+ REG_MMC0_CLK = 0x0088,
+ REG_MMC1_CLK = 0x008c,
+ REG_MMC2_CLK = 0x0090,
+ REG_MMC3_CLK = 0x0094,
+ REG_USBPHY_CFG = 0x00cc,
+ REG_PLL_DDR_AUX = 0x00f0,
+ REG_DRAM_CFG = 0x00f4,
+ REG_PLL_DDR1_CFG = 0x00f8,
+ REG_DRAM_CLK_GATING = 0x0100,
+ REG_GMAC_CLK = 0x0164,
+ REG_SYS_32K_CLK = 0x0310,
+ REG_PLL_LOCK_CTRL = 0x0320,
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* CCU register flags */
+enum {
+ REG_PLL_ENABLE = (1 << 31),
+ REG_PLL_LOCK = (1 << 28),
+};
+
+static uint64_t allwinner_r40_ccu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwR40ClockCtlState *s = AW_R40_CCU(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case 0x324 ... AW_R40_CCU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ return s->regs[idx];
+}
+
+static void allwinner_r40_ccu_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwR40ClockCtlState *s = AW_R40_CCU(opaque);
+
+ switch (offset) {
+ case REG_DRAM_CFG: /* DRAM Configuration(for DDR0) */
+ /* bit16: SDRCLK_UPD (SDRCLK configuration 0 update) */
+ val &= ~(1 << 16);
+ break;
+ case REG_PLL_DDR1_CTRL: /* DDR1 Control register */
+ /* bit30: SDRPLL_UPD */
+ val &= ~(1 << 30);
+ if (val & REG_PLL_ENABLE) {
+ val |= REG_PLL_LOCK;
+ }
+ break;
+ case REG_PLL_CPUX_CTRL:
+ case REG_PLL_AUDIO_CTRL:
+ case REG_PLL_VE_CTRL:
+ case REG_PLL_VIDEO0_CTRL:
+ case REG_PLL_DDR0_CTRL:
+ case REG_PLL_PERIPH0_CTRL:
+ case REG_PLL_PERIPH1_CTRL:
+ case REG_PLL_VIDEO1_CTRL:
+ case REG_PLL_SATA_CTRL:
+ case REG_PLL_GPU_CTRL:
+ case REG_PLL_MIPI_CTRL:
+ case REG_PLL_DE_CTRL:
+ if (val & REG_PLL_ENABLE) {
+ val |= REG_PLL_LOCK;
+ }
+ break;
+ case 0x324 ... AW_R40_CCU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+
+ s->regs[REG_INDEX(offset)] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_r40_ccu_ops = {
+ .read = allwinner_r40_ccu_read,
+ .write = allwinner_r40_ccu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_r40_ccu_reset(DeviceState *dev)
+{
+ AwR40ClockCtlState *s = AW_R40_CCU(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ /* Set default values for registers */
+ s->regs[REG_INDEX(REG_PLL_CPUX_CTRL)] = 0x00001000;
+ s->regs[REG_INDEX(REG_PLL_AUDIO_CTRL)] = 0x00035514;
+ s->regs[REG_INDEX(REG_PLL_VIDEO0_CTRL)] = 0x03006207;
+ s->regs[REG_INDEX(REG_PLL_VE_CTRL)] = 0x03006207;
+ s->regs[REG_INDEX(REG_PLL_DDR0_CTRL)] = 0x00001000,
+ s->regs[REG_INDEX(REG_PLL_PERIPH0_CTRL)] = 0x00041811;
+ s->regs[REG_INDEX(REG_PLL_PERIPH1_CTRL)] = 0x00041811;
+ s->regs[REG_INDEX(REG_PLL_VIDEO1_CTRL)] = 0x03006207;
+ s->regs[REG_INDEX(REG_PLL_SATA_CTRL)] = 0x00001811;
+ s->regs[REG_INDEX(REG_PLL_GPU_CTRL)] = 0x03006207;
+ s->regs[REG_INDEX(REG_PLL_MIPI_CTRL)] = 0x00000515;
+ s->regs[REG_INDEX(REG_PLL_DE_CTRL)] = 0x03006207;
+ s->regs[REG_INDEX(REG_PLL_DDR1_CTRL)] = 0x00001800;
+ s->regs[REG_INDEX(REG_AHB1_APB1_CFG)] = 0x00001010;
+ s->regs[REG_INDEX(REG_APB2_CFG)] = 0x01000000;
+ s->regs[REG_INDEX(REG_PLL_DDR_AUX)] = 0x00000001;
+ s->regs[REG_INDEX(REG_PLL_DDR1_CFG)] = 0x0ccca000;
+ s->regs[REG_INDEX(REG_SYS_32K_CLK)] = 0x0000000f;
+}
+
+static void allwinner_r40_ccu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwR40ClockCtlState *s = AW_R40_CCU(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_r40_ccu_ops, s,
+ TYPE_AW_R40_CCU, AW_R40_CCU_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_r40_ccu_vmstate = {
+ .name = "allwinner-r40-ccu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwR40ClockCtlState, AW_R40_CCU_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_r40_ccu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_r40_ccu_reset;
+ dc->vmsd = &allwinner_r40_ccu_vmstate;
+}
+
+static const TypeInfo allwinner_r40_ccu_info = {
+ .name = TYPE_AW_R40_CCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_r40_ccu_init,
+ .instance_size = sizeof(AwR40ClockCtlState),
+ .class_init = allwinner_r40_ccu_class_init,
+};
+
+static void allwinner_r40_ccu_register(void)
+{
+ type_register_static(&allwinner_r40_ccu_info);
+}
+
+type_init(allwinner_r40_ccu_register)
diff --git a/hw/misc/allwinner-r40-dramc.c b/hw/misc/allwinner-r40-dramc.c
new file mode 100644
index 0000000..ea61247
--- /dev/null
+++ b/hw/misc/allwinner-r40-dramc.c
@@ -0,0 +1,513 @@
+/*
+ * Allwinner R40 SDRAM Controller emulation
+ *
+ * CCopyright (C) 2023 qianfan Zhao <qianfanguijin@163.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/error-report.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/bitops.h"
+#include "hw/misc/allwinner-r40-dramc.h"
+#include "trace.h"
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* DRAMCOM register offsets */
+enum {
+ REG_DRAMCOM_CR = 0x0000, /* Control Register */
+};
+
+/* DRAMCOMM register flags */
+enum {
+ REG_DRAMCOM_CR_DUAL_RANK = (1 << 0),
+};
+
+/* DRAMCTL register offsets */
+enum {
+ REG_DRAMCTL_PIR = 0x0000, /* PHY Initialization Register */
+ REG_DRAMCTL_PGSR = 0x0010, /* PHY General Status Register */
+ REG_DRAMCTL_STATR = 0x0018, /* Status Register */
+ REG_DRAMCTL_PGCR = 0x0100, /* PHY general configuration registers */
+};
+
+/* DRAMCTL register flags */
+enum {
+ REG_DRAMCTL_PGSR_INITDONE = (1 << 0),
+ REG_DRAMCTL_PGSR_READ_TIMEOUT = (1 << 13),
+ REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT = (1 << 25),
+};
+
+enum {
+ REG_DRAMCTL_STATR_ACTIVE = (1 << 0),
+};
+
+#define DRAM_MAX_ROW_BITS 16
+#define DRAM_MAX_COL_BITS 13 /* 8192 */
+#define DRAM_MAX_BANK 3
+
+static uint64_t dram_autodetect_cells[DRAM_MAX_ROW_BITS]
+ [DRAM_MAX_BANK]
+ [DRAM_MAX_COL_BITS];
+struct VirtualDDRChip {
+ uint32_t ram_size;
+ uint8_t bank_bits;
+ uint8_t row_bits;
+ uint8_t col_bits;
+};
+
+/*
+ * Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported,
+ * 2GiB memory is not supported due to dual rank feature.
+ */
+static const struct VirtualDDRChip dummy_ddr_chips[] = {
+ {
+ .ram_size = 256,
+ .bank_bits = 3,
+ .row_bits = 12,
+ .col_bits = 13,
+ }, {
+ .ram_size = 512,
+ .bank_bits = 3,
+ .row_bits = 13,
+ .col_bits = 13,
+ }, {
+ .ram_size = 1024,
+ .bank_bits = 3,
+ .row_bits = 14,
+ .col_bits = 13,
+ }, {
+ 0
+ }
+};
+
+static const struct VirtualDDRChip *get_match_ddr(uint32_t ram_size)
+{
+ const struct VirtualDDRChip *ddr;
+
+ for (ddr = &dummy_ddr_chips[0]; ddr->ram_size; ddr++) {
+ if (ddr->ram_size == ram_size) {
+ return ddr;
+ }
+ }
+
+ return NULL;
+}
+
+static uint64_t *address_to_autodetect_cells(AwR40DramCtlState *s,
+ const struct VirtualDDRChip *ddr,
+ uint32_t offset)
+{
+ int row_index = 0, bank_index = 0, col_index = 0;
+ uint32_t row_addr, bank_addr, col_addr;
+
+ row_addr = extract32(offset, s->set_col_bits + s->set_bank_bits,
+ s->set_row_bits);
+ bank_addr = extract32(offset, s->set_col_bits, s->set_bank_bits);
+ col_addr = extract32(offset, 0, s->set_col_bits);
+
+ for (int i = 0; i < ddr->row_bits; i++) {
+ if (row_addr & BIT(i)) {
+ row_index = i;
+ }
+ }
+
+ for (int i = 0; i < ddr->bank_bits; i++) {
+ if (bank_addr & BIT(i)) {
+ bank_index = i;
+ }
+ }
+
+ for (int i = 0; i < ddr->col_bits; i++) {
+ if (col_addr & BIT(i)) {
+ col_index = i;
+ }
+ }
+
+ trace_allwinner_r40_dramc_offset_to_cell(offset, row_index, bank_index,
+ col_index);
+ return &dram_autodetect_cells[row_index][bank_index][col_index];
+}
+
+static void allwinner_r40_dramc_map_rows(AwR40DramCtlState *s, uint8_t row_bits,
+ uint8_t bank_bits, uint8_t col_bits)
+{
+ const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size);
+ bool enable_detect_cells;
+
+ trace_allwinner_r40_dramc_map_rows(row_bits, bank_bits, col_bits);
+
+ if (!ddr) {
+ return;
+ }
+
+ s->set_row_bits = row_bits;
+ s->set_bank_bits = bank_bits;
+ s->set_col_bits = col_bits;
+
+ enable_detect_cells = ddr->bank_bits != bank_bits
+ || ddr->row_bits != row_bits
+ || ddr->col_bits != col_bits;
+
+ if (enable_detect_cells) {
+ trace_allwinner_r40_dramc_detect_cells_enable();
+ } else {
+ trace_allwinner_r40_dramc_detect_cells_disable();
+ }
+
+ memory_region_set_enabled(&s->detect_cells, enable_detect_cells);
+}
+
+static uint64_t allwinner_r40_dramcom_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_R40_DRAMCOM_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_r40_dramcom_read(offset, s->dramcom[idx], size);
+ return s->dramcom[idx];
+}
+
+static void allwinner_r40_dramcom_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_r40_dramcom_write(offset, val, size);
+
+ if (idx >= AW_R40_DRAMCOM_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ switch (offset) {
+ case REG_DRAMCOM_CR: /* Control Register */
+ if (!(val & REG_DRAMCOM_CR_DUAL_RANK)) {
+ allwinner_r40_dramc_map_rows(s, ((val >> 4) & 0xf) + 1,
+ ((val >> 2) & 0x1) + 2,
+ (((val >> 8) & 0xf) + 3));
+ }
+ break;
+ };
+
+ s->dramcom[idx] = (uint32_t) val;
+}
+
+static uint64_t allwinner_r40_dramctl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_R40_DRAMCTL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_r40_dramctl_read(offset, s->dramctl[idx], size);
+ return s->dramctl[idx];
+}
+
+static void allwinner_r40_dramctl_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_r40_dramctl_write(offset, val, size);
+
+ if (idx >= AW_R40_DRAMCTL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ switch (offset) {
+ case REG_DRAMCTL_PIR: /* PHY Initialization Register */
+ s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |= REG_DRAMCTL_PGSR_INITDONE;
+ s->dramctl[REG_INDEX(REG_DRAMCTL_STATR)] |= REG_DRAMCTL_STATR_ACTIVE;
+ break;
+ }
+
+ s->dramctl[idx] = (uint32_t) val;
+}
+
+static uint64_t allwinner_r40_dramphy_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_R40_DRAMPHY_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_r40_dramphy_read(offset, s->dramphy[idx], size);
+ return s->dramphy[idx];
+}
+
+static void allwinner_r40_dramphy_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_r40_dramphy_write(offset, val, size);
+
+ if (idx >= AW_R40_DRAMPHY_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ s->dramphy[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_r40_dramcom_ops = {
+ .read = allwinner_r40_dramcom_read,
+ .write = allwinner_r40_dramcom_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const MemoryRegionOps allwinner_r40_dramctl_ops = {
+ .read = allwinner_r40_dramctl_read,
+ .write = allwinner_r40_dramctl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const MemoryRegionOps allwinner_r40_dramphy_ops = {
+ .read = allwinner_r40_dramphy_read,
+ .write = allwinner_r40_dramphy_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static uint64_t allwinner_r40_detect_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size);
+ uint64_t data = 0;
+
+ if (ddr) {
+ data = *address_to_autodetect_cells(s, ddr, (uint32_t)offset);
+ }
+
+ trace_allwinner_r40_dramc_detect_cell_read(offset, data);
+ return data;
+}
+
+static void allwinner_r40_detect_write(void *opaque, hwaddr offset,
+ uint64_t data, unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size);
+
+ if (ddr) {
+ uint64_t *cell = address_to_autodetect_cells(s, ddr, (uint32_t)offset);
+ trace_allwinner_r40_dramc_detect_cell_write(offset, data);
+ *cell = data;
+ }
+}
+
+static const MemoryRegionOps allwinner_r40_detect_ops = {
+ .read = allwinner_r40_detect_read,
+ .write = allwinner_r40_detect_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+/*
+ * mctl_r40_detect_rank_count in u-boot will write the high 1G of DDR
+ * to detect wether the board support dual_rank or not. Create a virtual memory
+ * if the board's ram_size less or equal than 1G, and set read time out flag of
+ * REG_DRAMCTL_PGSR when the user touch this high dram.
+ */
+static uint64_t allwinner_r40_dualrank_detect_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(opaque);
+ uint32_t reg;
+
+ reg = s->dramctl[REG_INDEX(REG_DRAMCTL_PGCR)];
+ if (reg & REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT) { /* Enable read time out */
+ /*
+ * this driver only support one rank, mark READ_TIMEOUT when try
+ * read the second rank.
+ */
+ s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)]
+ |= REG_DRAMCTL_PGSR_READ_TIMEOUT;
+ }
+
+ return 0;
+}
+
+static const MemoryRegionOps allwinner_r40_dualrank_detect_ops = {
+ .read = allwinner_r40_dualrank_detect_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_r40_dramc_reset(DeviceState *dev)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(dev);
+
+ /* Set default values for registers */
+ memset(&s->dramcom, 0, sizeof(s->dramcom));
+ memset(&s->dramctl, 0, sizeof(s->dramctl));
+ memset(&s->dramphy, 0, sizeof(s->dramphy));
+}
+
+static void allwinner_r40_dramc_realize(DeviceState *dev, Error **errp)
+{
+ AwR40DramCtlState *s = AW_R40_DRAMC(dev);
+
+ if (!get_match_ddr(s->ram_size)) {
+ error_report("%s: ram-size %u MiB is not supported",
+ __func__, s->ram_size);
+ exit(1);
+ }
+
+ /* detect_cells */
+ sysbus_mmio_map_overlap(SYS_BUS_DEVICE(s), 3, s->ram_addr, 10);
+ memory_region_set_enabled(&s->detect_cells, false);
+
+ /*
+ * We only support DRAM size up to 1G now, so prepare a high memory page
+ * after 1G for dualrank detect. index = 4
+ */
+ memory_region_init_io(&s->dram_high, OBJECT(s),
+ &allwinner_r40_dualrank_detect_ops, s,
+ "DRAMHIGH", KiB);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->dram_high);
+ sysbus_mmio_map(SYS_BUS_DEVICE(s), 4, s->ram_addr + GiB);
+}
+
+static void allwinner_r40_dramc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwR40DramCtlState *s = AW_R40_DRAMC(obj);
+
+ /* DRAMCOM registers, index 0 */
+ memory_region_init_io(&s->dramcom_iomem, OBJECT(s),
+ &allwinner_r40_dramcom_ops, s,
+ "DRAMCOM", 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramcom_iomem);
+
+ /* DRAMCTL registers, index 1 */
+ memory_region_init_io(&s->dramctl_iomem, OBJECT(s),
+ &allwinner_r40_dramctl_ops, s,
+ "DRAMCTL", 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramctl_iomem);
+
+ /* DRAMPHY registers. index 2 */
+ memory_region_init_io(&s->dramphy_iomem, OBJECT(s),
+ &allwinner_r40_dramphy_ops, s,
+ "DRAMPHY", 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramphy_iomem);
+
+ /* R40 support max 2G memory but we only support up to 1G now. index 3 */
+ memory_region_init_io(&s->detect_cells, OBJECT(s),
+ &allwinner_r40_detect_ops, s,
+ "DRAMCELLS", 1 * GiB);
+ sysbus_init_mmio(sbd, &s->detect_cells);
+}
+
+static Property allwinner_r40_dramc_properties[] = {
+ DEFINE_PROP_UINT64("ram-addr", AwR40DramCtlState, ram_addr, 0x0),
+ DEFINE_PROP_UINT32("ram-size", AwR40DramCtlState, ram_size, 256), /* MiB */
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription allwinner_r40_dramc_vmstate = {
+ .name = "allwinner-r40-dramc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(dramcom, AwR40DramCtlState,
+ AW_R40_DRAMCOM_REGS_NUM),
+ VMSTATE_UINT32_ARRAY(dramctl, AwR40DramCtlState,
+ AW_R40_DRAMCTL_REGS_NUM),
+ VMSTATE_UINT32_ARRAY(dramphy, AwR40DramCtlState,
+ AW_R40_DRAMPHY_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_r40_dramc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_r40_dramc_reset;
+ dc->vmsd = &allwinner_r40_dramc_vmstate;
+ dc->realize = allwinner_r40_dramc_realize;
+ device_class_set_props(dc, allwinner_r40_dramc_properties);
+}
+
+static const TypeInfo allwinner_r40_dramc_info = {
+ .name = TYPE_AW_R40_DRAMC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_r40_dramc_init,
+ .instance_size = sizeof(AwR40DramCtlState),
+ .class_init = allwinner_r40_dramc_class_init,
+};
+
+static void allwinner_r40_dramc_register(void)
+{
+ type_register_static(&allwinner_r40_dramc_info);
+}
+
+type_init(allwinner_r40_dramc_register)
diff --git a/hw/misc/allwinner-sramc.c b/hw/misc/allwinner-sramc.c
new file mode 100644
index 0000000..a8b731f
--- /dev/null
+++ b/hw/misc/allwinner-sramc.c
@@ -0,0 +1,184 @@
+/*
+ * Allwinner R40 SRAM controller emulation
+ *
+ * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "hw/misc/allwinner-sramc.h"
+#include "trace.h"
+
+/*
+ * register offsets
+ * https://linux-sunxi.org/SRAM_Controller_Register_Guide
+ */
+enum {
+ REG_SRAM_CTL1_CFG = 0x04, /* SRAM Control register 1 */
+ REG_SRAM_VER = 0x24, /* SRAM Version register */
+ REG_SRAM_R40_SOFT_ENTRY_REG0 = 0xbc,
+};
+
+/* REG_SRAMC_VERSION bit defines */
+#define SRAM_VER_READ_ENABLE (1 << 15)
+#define SRAM_VER_VERSION_SHIFT 16
+#define SRAM_VERSION_SUN8I_R40 0x1701
+
+static uint64_t allwinner_sramc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AwSRAMCState *s = AW_SRAMC(opaque);
+ AwSRAMCClass *sc = AW_SRAMC_GET_CLASS(s);
+ uint64_t val = 0;
+
+ switch (offset) {
+ case REG_SRAM_CTL1_CFG:
+ val = s->sram_ctl1;
+ break;
+ case REG_SRAM_VER:
+ /* bit15: lock bit, set this bit before reading this register */
+ if (s->sram_ver & SRAM_VER_READ_ENABLE) {
+ val = SRAM_VER_READ_ENABLE |
+ (sc->sram_version_code << SRAM_VER_VERSION_SHIFT);
+ }
+ break;
+ case REG_SRAM_R40_SOFT_ENTRY_REG0:
+ val = s->sram_soft_entry_reg0;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_sramc_read(offset, val);
+
+ return val;
+}
+
+static void allwinner_sramc_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwSRAMCState *s = AW_SRAMC(opaque);
+
+ trace_allwinner_sramc_write(offset, val);
+
+ switch (offset) {
+ case REG_SRAM_CTL1_CFG:
+ s->sram_ctl1 = val;
+ break;
+ case REG_SRAM_VER:
+ /* Only the READ_ENABLE bit is writeable */
+ s->sram_ver = val & SRAM_VER_READ_ENABLE;
+ break;
+ case REG_SRAM_R40_SOFT_ENTRY_REG0:
+ s->sram_soft_entry_reg0 = val;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_sramc_ops = {
+ .read = allwinner_sramc_read,
+ .write = allwinner_sramc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const VMStateDescription allwinner_sramc_vmstate = {
+ .name = "allwinner-sramc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sram_ver, AwSRAMCState),
+ VMSTATE_UINT32(sram_soft_entry_reg0, AwSRAMCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_sramc_reset(DeviceState *dev)
+{
+ AwSRAMCState *s = AW_SRAMC(dev);
+ AwSRAMCClass *sc = AW_SRAMC_GET_CLASS(s);
+
+ switch (sc->sram_version_code) {
+ case SRAM_VERSION_SUN8I_R40:
+ s->sram_ctl1 = 0x1300;
+ break;
+ }
+}
+
+static void allwinner_sramc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_sramc_reset;
+ dc->vmsd = &allwinner_sramc_vmstate;
+}
+
+static void allwinner_sramc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwSRAMCState *s = AW_SRAMC(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sramc_ops, s,
+ TYPE_AW_SRAMC, 1 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const TypeInfo allwinner_sramc_info = {
+ .name = TYPE_AW_SRAMC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_sramc_init,
+ .instance_size = sizeof(AwSRAMCState),
+ .class_init = allwinner_sramc_class_init,
+};
+
+static void allwinner_r40_sramc_class_init(ObjectClass *klass, void *data)
+{
+ AwSRAMCClass *sc = AW_SRAMC_CLASS(klass);
+
+ sc->sram_version_code = SRAM_VERSION_SUN8I_R40;
+}
+
+static const TypeInfo allwinner_r40_sramc_info = {
+ .name = TYPE_AW_SRAMC_SUN8I_R40,
+ .parent = TYPE_AW_SRAMC,
+ .class_init = allwinner_r40_sramc_class_init,
+};
+
+static void allwinner_sramc_register(void)
+{
+ type_register_static(&allwinner_sramc_info);
+ type_register_static(&allwinner_r40_sramc_info);
+}
+
+type_init(allwinner_sramc_register)
diff --git a/hw/misc/axp209.c b/hw/misc/axp209.c
deleted file mode 100644
index 2908ed9..0000000
--- a/hw/misc/axp209.c
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * AXP-209 PMU Emulation
- *
- * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include "qemu/osdep.h"
-#include "qemu/log.h"
-#include "trace.h"
-#include "hw/i2c/i2c.h"
-#include "migration/vmstate.h"
-
-#define TYPE_AXP209_PMU "axp209_pmu"
-
-#define AXP209(obj) \
- OBJECT_CHECK(AXP209I2CState, (obj), TYPE_AXP209_PMU)
-
-/* registers */
-enum {
- REG_POWER_STATUS = 0x0u,
- REG_OPERATING_MODE,
- REG_OTG_VBUS_STATUS,
- REG_CHIP_VERSION,
- REG_DATA_CACHE_0,
- REG_DATA_CACHE_1,
- REG_DATA_CACHE_2,
- REG_DATA_CACHE_3,
- REG_DATA_CACHE_4,
- REG_DATA_CACHE_5,
- REG_DATA_CACHE_6,
- REG_DATA_CACHE_7,
- REG_DATA_CACHE_8,
- REG_DATA_CACHE_9,
- REG_DATA_CACHE_A,
- REG_DATA_CACHE_B,
- REG_POWER_OUTPUT_CTRL = 0x12u,
- REG_DC_DC2_OUT_V_CTRL = 0x23u,
- REG_DC_DC2_DVS_CTRL = 0x25u,
- REG_DC_DC3_OUT_V_CTRL = 0x27u,
- REG_LDO2_4_OUT_V_CTRL,
- REG_LDO3_OUT_V_CTRL,
- REG_VBUS_CH_MGMT = 0x30u,
- REG_SHUTDOWN_V_CTRL,
- REG_SHUTDOWN_CTRL,
- REG_CHARGE_CTRL_1,
- REG_CHARGE_CTRL_2,
- REG_SPARE_CHARGE_CTRL,
- REG_PEK_KEY_CTRL,
- REG_DC_DC_FREQ_SET,
- REG_CHR_TEMP_TH_SET,
- REG_CHR_HIGH_TEMP_TH_CTRL,
- REG_IPSOUT_WARN_L1,
- REG_IPSOUT_WARN_L2,
- REG_DISCHR_TEMP_TH_SET,
- REG_DISCHR_HIGH_TEMP_TH_CTRL,
- REG_IRQ_BANK_1_CTRL = 0x40u,
- REG_IRQ_BANK_2_CTRL,
- REG_IRQ_BANK_3_CTRL,
- REG_IRQ_BANK_4_CTRL,
- REG_IRQ_BANK_5_CTRL,
- REG_IRQ_BANK_1_STAT = 0x48u,
- REG_IRQ_BANK_2_STAT,
- REG_IRQ_BANK_3_STAT,
- REG_IRQ_BANK_4_STAT,
- REG_IRQ_BANK_5_STAT,
- REG_ADC_ACIN_V_H = 0x56u,
- REG_ADC_ACIN_V_L,
- REG_ADC_ACIN_CURR_H,
- REG_ADC_ACIN_CURR_L,
- REG_ADC_VBUS_V_H,
- REG_ADC_VBUS_V_L,
- REG_ADC_VBUS_CURR_H,
- REG_ADC_VBUS_CURR_L,
- REG_ADC_INT_TEMP_H,
- REG_ADC_INT_TEMP_L,
- REG_ADC_TEMP_SENS_V_H = 0x62u,
- REG_ADC_TEMP_SENS_V_L,
- REG_ADC_BAT_V_H = 0x78u,
- REG_ADC_BAT_V_L,
- REG_ADC_BAT_DISCHR_CURR_H,
- REG_ADC_BAT_DISCHR_CURR_L,
- REG_ADC_BAT_CHR_CURR_H,
- REG_ADC_BAT_CHR_CURR_L,
- REG_ADC_IPSOUT_V_H,
- REG_ADC_IPSOUT_V_L,
- REG_DC_DC_MOD_SEL = 0x80u,
- REG_ADC_EN_1,
- REG_ADC_EN_2,
- REG_ADC_SR_CTRL,
- REG_ADC_IN_RANGE,
- REG_GPIO1_ADC_IRQ_RISING_TH,
- REG_GPIO1_ADC_IRQ_FALLING_TH,
- REG_TIMER_CTRL = 0x8au,
- REG_VBUS_CTRL_MON_SRP,
- REG_OVER_TEMP_SHUTDOWN = 0x8fu,
- REG_GPIO0_FEAT_SET,
- REG_GPIO_OUT_HIGH_SET,
- REG_GPIO1_FEAT_SET,
- REG_GPIO2_FEAT_SET,
- REG_GPIO_SIG_STATE_SET_MON,
- REG_GPIO3_SET,
- REG_COULOMB_CNTR_CTRL = 0xb8u,
- REG_POWER_MEAS_RES,
- NR_REGS
-};
-
-#define AXP209_CHIP_VERSION_ID (0x01)
-#define AXP209_DC_DC2_OUT_V_CTRL_RESET (0x16)
-#define AXP209_IRQ_BANK_1_CTRL_RESET (0xd8)
-
-/* A simple I2C slave which returns values of ID or CNT register. */
-typedef struct AXP209I2CState {
- /*< private >*/
- I2CSlave i2c;
- /*< public >*/
- uint8_t regs[NR_REGS]; /* peripheral registers */
- uint8_t ptr; /* current register index */
- uint8_t count; /* counter used for tx/rx */
-} AXP209I2CState;
-
-/* Reset all counters and load ID register */
-static void axp209_reset_enter(Object *obj, ResetType type)
-{
- AXP209I2CState *s = AXP209(obj);
-
- memset(s->regs, 0, NR_REGS);
- s->ptr = 0;
- s->count = 0;
- s->regs[REG_CHIP_VERSION] = AXP209_CHIP_VERSION_ID;
- s->regs[REG_DC_DC2_OUT_V_CTRL] = AXP209_DC_DC2_OUT_V_CTRL_RESET;
- s->regs[REG_IRQ_BANK_1_CTRL] = AXP209_IRQ_BANK_1_CTRL_RESET;
-}
-
-/* Handle events from master. */
-static int axp209_event(I2CSlave *i2c, enum i2c_event event)
-{
- AXP209I2CState *s = AXP209(i2c);
-
- s->count = 0;
-
- return 0;
-}
-
-/* Called when master requests read */
-static uint8_t axp209_rx(I2CSlave *i2c)
-{
- AXP209I2CState *s = AXP209(i2c);
- uint8_t ret = 0xff;
-
- if (s->ptr < NR_REGS) {
- ret = s->regs[s->ptr++];
- }
-
- trace_axp209_rx(s->ptr - 1, ret);
-
- return ret;
-}
-
-/*
- * Called when master sends write.
- * Update ptr with byte 0, then perform write with second byte.
- */
-static int axp209_tx(I2CSlave *i2c, uint8_t data)
-{
- AXP209I2CState *s = AXP209(i2c);
-
- if (s->count == 0) {
- /* Store register address */
- s->ptr = data;
- s->count++;
- trace_axp209_select(data);
- } else {
- trace_axp209_tx(s->ptr, data);
- if (s->ptr == REG_DC_DC2_OUT_V_CTRL) {
- s->regs[s->ptr++] = data;
- }
- }
-
- return 0;
-}
-
-static const VMStateDescription vmstate_axp209 = {
- .name = TYPE_AXP209_PMU,
- .version_id = 1,
- .fields = (VMStateField[]) {
- VMSTATE_UINT8_ARRAY(regs, AXP209I2CState, NR_REGS),
- VMSTATE_UINT8(count, AXP209I2CState),
- VMSTATE_UINT8(ptr, AXP209I2CState),
- VMSTATE_END_OF_LIST()
- }
-};
-
-static void axp209_class_init(ObjectClass *oc, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(oc);
- I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
- ResettableClass *rc = RESETTABLE_CLASS(oc);
-
- rc->phases.enter = axp209_reset_enter;
- dc->vmsd = &vmstate_axp209;
- isc->event = axp209_event;
- isc->recv = axp209_rx;
- isc->send = axp209_tx;
-}
-
-static const TypeInfo axp209_info = {
- .name = TYPE_AXP209_PMU,
- .parent = TYPE_I2C_SLAVE,
- .instance_size = sizeof(AXP209I2CState),
- .class_init = axp209_class_init
-};
-
-static void axp209_register_devices(void)
-{
- type_register_static(&axp209_info);
-}
-
-type_init(axp209_register_devices);
diff --git a/hw/misc/axp2xx.c b/hw/misc/axp2xx.c
new file mode 100644
index 0000000..41538c1
--- /dev/null
+++ b/hw/misc/axp2xx.c
@@ -0,0 +1,283 @@
+/*
+ * AXP-2XX PMU Emulation, supported lists:
+ * AXP209
+ * AXP221
+ *
+ * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+#include "trace.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+
+#define TYPE_AXP2XX "axp2xx_pmu"
+#define TYPE_AXP209_PMU "axp209_pmu"
+#define TYPE_AXP221_PMU "axp221_pmu"
+
+OBJECT_DECLARE_TYPE(AXP2xxI2CState, AXP2xxClass, AXP2XX)
+
+#define NR_REGS (0xff)
+
+/* A simple I2C slave which returns values of ID or CNT register. */
+typedef struct AXP2xxI2CState {
+ /*< private >*/
+ I2CSlave i2c;
+ /*< public >*/
+ uint8_t regs[NR_REGS]; /* peripheral registers */
+ uint8_t ptr; /* current register index */
+ uint8_t count; /* counter used for tx/rx */
+} AXP2xxI2CState;
+
+typedef struct AXP2xxClass {
+ /*< private >*/
+ I2CSlaveClass parent_class;
+ /*< public >*/
+ void (*reset_enter)(AXP2xxI2CState *s, ResetType type);
+} AXP2xxClass;
+
+#define AXP209_CHIP_VERSION_ID (0x01)
+#define AXP209_DC_DC2_OUT_V_CTRL_RESET (0x16)
+
+/* Reset all counters and load ID register */
+static void axp209_reset_enter(AXP2xxI2CState *s, ResetType type)
+{
+ memset(s->regs, 0, NR_REGS);
+ s->ptr = 0;
+ s->count = 0;
+
+ s->regs[0x03] = AXP209_CHIP_VERSION_ID;
+ s->regs[0x23] = AXP209_DC_DC2_OUT_V_CTRL_RESET;
+
+ s->regs[0x30] = 0x60;
+ s->regs[0x32] = 0x46;
+ s->regs[0x34] = 0x41;
+ s->regs[0x35] = 0x22;
+ s->regs[0x36] = 0x5d;
+ s->regs[0x37] = 0x08;
+ s->regs[0x38] = 0xa5;
+ s->regs[0x39] = 0x1f;
+ s->regs[0x3a] = 0x68;
+ s->regs[0x3b] = 0x5f;
+ s->regs[0x3c] = 0xfc;
+ s->regs[0x3d] = 0x16;
+ s->regs[0x40] = 0xd8;
+ s->regs[0x42] = 0xff;
+ s->regs[0x43] = 0x3b;
+ s->regs[0x80] = 0xe0;
+ s->regs[0x82] = 0x83;
+ s->regs[0x83] = 0x80;
+ s->regs[0x84] = 0x32;
+ s->regs[0x86] = 0xff;
+ s->regs[0x90] = 0x07;
+ s->regs[0x91] = 0xa0;
+ s->regs[0x92] = 0x07;
+ s->regs[0x93] = 0x07;
+}
+
+#define AXP221_PWR_STATUS_ACIN_PRESENT BIT(7)
+#define AXP221_PWR_STATUS_ACIN_AVAIL BIT(6)
+#define AXP221_PWR_STATUS_VBUS_PRESENT BIT(5)
+#define AXP221_PWR_STATUS_VBUS_USED BIT(4)
+#define AXP221_PWR_STATUS_BAT_CHARGING BIT(2)
+#define AXP221_PWR_STATUS_ACIN_VBUS_POWERED BIT(1)
+
+/* Reset all counters and load ID register */
+static void axp221_reset_enter(AXP2xxI2CState *s, ResetType type)
+{
+ memset(s->regs, 0, NR_REGS);
+ s->ptr = 0;
+ s->count = 0;
+
+ /* input power status register */
+ s->regs[0x00] = AXP221_PWR_STATUS_ACIN_PRESENT
+ | AXP221_PWR_STATUS_ACIN_AVAIL
+ | AXP221_PWR_STATUS_ACIN_VBUS_POWERED;
+
+ s->regs[0x01] = 0x00; /* no battery is connected */
+
+ /*
+ * CHIPID register, no documented on datasheet, but it is checked in
+ * u-boot spl. I had read it from AXP221s and got 0x06 value.
+ * So leave 06h here.
+ */
+ s->regs[0x03] = 0x06;
+
+ s->regs[0x10] = 0xbf;
+ s->regs[0x13] = 0x01;
+ s->regs[0x30] = 0x60;
+ s->regs[0x31] = 0x03;
+ s->regs[0x32] = 0x43;
+ s->regs[0x33] = 0xc6;
+ s->regs[0x34] = 0x45;
+ s->regs[0x35] = 0x0e;
+ s->regs[0x36] = 0x5d;
+ s->regs[0x37] = 0x08;
+ s->regs[0x38] = 0xa5;
+ s->regs[0x39] = 0x1f;
+ s->regs[0x3c] = 0xfc;
+ s->regs[0x3d] = 0x16;
+ s->regs[0x80] = 0x80;
+ s->regs[0x82] = 0xe0;
+ s->regs[0x84] = 0x32;
+ s->regs[0x8f] = 0x01;
+
+ s->regs[0x90] = 0x07;
+ s->regs[0x91] = 0x1f;
+ s->regs[0x92] = 0x07;
+ s->regs[0x93] = 0x1f;
+
+ s->regs[0x40] = 0xd8;
+ s->regs[0x41] = 0xff;
+ s->regs[0x42] = 0x03;
+ s->regs[0x43] = 0x03;
+
+ s->regs[0xb8] = 0xc0;
+ s->regs[0xb9] = 0x64;
+ s->regs[0xe6] = 0xa0;
+}
+
+static void axp2xx_reset_enter(Object *obj, ResetType type)
+{
+ AXP2xxI2CState *s = AXP2XX(obj);
+ AXP2xxClass *sc = AXP2XX_GET_CLASS(s);
+
+ sc->reset_enter(s, type);
+}
+
+/* Handle events from master. */
+static int axp2xx_event(I2CSlave *i2c, enum i2c_event event)
+{
+ AXP2xxI2CState *s = AXP2XX(i2c);
+
+ s->count = 0;
+
+ return 0;
+}
+
+/* Called when master requests read */
+static uint8_t axp2xx_rx(I2CSlave *i2c)
+{
+ AXP2xxI2CState *s = AXP2XX(i2c);
+ uint8_t ret = 0xff;
+
+ if (s->ptr < NR_REGS) {
+ ret = s->regs[s->ptr++];
+ }
+
+ trace_axp2xx_rx(s->ptr - 1, ret);
+
+ return ret;
+}
+
+/*
+ * Called when master sends write.
+ * Update ptr with byte 0, then perform write with second byte.
+ */
+static int axp2xx_tx(I2CSlave *i2c, uint8_t data)
+{
+ AXP2xxI2CState *s = AXP2XX(i2c);
+
+ if (s->count == 0) {
+ /* Store register address */
+ s->ptr = data;
+ s->count++;
+ trace_axp2xx_select(data);
+ } else {
+ trace_axp2xx_tx(s->ptr, data);
+ s->regs[s->ptr++] = data;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_axp2xx = {
+ .name = TYPE_AXP2XX,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(regs, AXP2xxI2CState, NR_REGS),
+ VMSTATE_UINT8(ptr, AXP2xxI2CState),
+ VMSTATE_UINT8(count, AXP2xxI2CState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void axp2xx_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
+
+ rc->phases.enter = axp2xx_reset_enter;
+ dc->vmsd = &vmstate_axp2xx;
+ isc->event = axp2xx_event;
+ isc->recv = axp2xx_rx;
+ isc->send = axp2xx_tx;
+}
+
+static const TypeInfo axp2xx_info = {
+ .name = TYPE_AXP2XX,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(AXP2xxI2CState),
+ .class_size = sizeof(AXP2xxClass),
+ .class_init = axp2xx_class_init,
+ .abstract = true,
+};
+
+static void axp209_class_init(ObjectClass *oc, void *data)
+{
+ AXP2xxClass *sc = AXP2XX_CLASS(oc);
+
+ sc->reset_enter = axp209_reset_enter;
+}
+
+static const TypeInfo axp209_info = {
+ .name = TYPE_AXP209_PMU,
+ .parent = TYPE_AXP2XX,
+ .class_init = axp209_class_init
+};
+
+static void axp221_class_init(ObjectClass *oc, void *data)
+{
+ AXP2xxClass *sc = AXP2XX_CLASS(oc);
+
+ sc->reset_enter = axp221_reset_enter;
+}
+
+static const TypeInfo axp221_info = {
+ .name = TYPE_AXP221_PMU,
+ .parent = TYPE_AXP2XX,
+ .class_init = axp221_class_init,
+};
+
+static void axp2xx_register_devices(void)
+{
+ type_register_static(&axp2xx_info);
+ type_register_static(&axp209_info);
+ type_register_static(&axp221_info);
+}
+
+type_init(axp2xx_register_devices);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a40245a..78ca857 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -37,6 +37,7 @@ subdir('macio')
softmmu_ss.add(when: 'CONFIG_IVSHMEM_DEVICE', if_true: files('ivshmem.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_SRAMC', if_true: files('allwinner-sramc.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_CCM', if_true: files('allwinner-a10-ccm.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_DRAMC', if_true: files('allwinner-a10-dramc.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-ccu.c'))
@@ -44,7 +45,9 @@ specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'
softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-sysctrl.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sid.c'))
-softmmu_ss.add(when: 'CONFIG_AXP209_PMU', if_true: files('axp209.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true: files('allwinner-r40-ccu.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true: files('allwinner-r40-dramc.c'))
+softmmu_ss.add(when: 'CONFIG_AXP2XX_PMU', if_true: files('axp2xx.c'))
softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c'))
softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c'))
softmmu_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c47876a..4d1a0e1 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -15,18 +15,36 @@ allwinner_h3_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write
allwinner_h3_dramphy_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
allwinner_h3_dramphy_write(uint64_t offset, uint64_t data, unsigned size) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+# allwinner-r40-dramc.c
+allwinner_r40_dramc_detect_cells_disable(void) "Disable detect cells"
+allwinner_r40_dramc_detect_cells_enable(void) "Enable detect cells"
+allwinner_r40_dramc_map_rows(uint8_t row_bits, uint8_t bank_bits, uint8_t col_bits) "DRAM layout: row_bits %d, bank_bits %d, col_bits %d"
+allwinner_r40_dramc_offset_to_cell(uint64_t offset, int row, int bank, int col) "offset 0x%" PRIx64 " row %d bank %d col %d"
+allwinner_r40_dramc_detect_cell_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 ""
+allwinner_r40_dramc_detect_cell_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 ""
+allwinner_r40_dramcom_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_r40_dramcom_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_r40_dramctl_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_r40_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_r40_dramphy_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_r40_dramphy_write(uint64_t offset, uint64_t data, unsigned size) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+
# allwinner-sid.c
allwinner_sid_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+# allwinner-sramc.c
+allwinner_sramc_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64
+allwinner_sramc_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64
+
# avr_power.c
avr_power_read(uint8_t value) "power_reduc read value:%u"
avr_power_write(uint8_t value) "power_reduc write value:%u"
-# axp209.c
-axp209_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8
-axp209_select(uint8_t reg) "Accessing reg 0x%" PRIx8
-axp209_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8
+# axp2xx
+axp2xx_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8
+axp2xx_select(uint8_t reg) "Accessing reg 0x%" PRIx8
+axp2xx_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8
# eccmemctl.c
ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x"