diff --git a/devices/memctrl/aspen.cpp b/devices/memctrl/aspen.cpp
new file mode 100644
index 0000000..da8eafb
--- /dev/null
+++ b/devices/memctrl/aspen.cpp
@@ -0,0 +1,178 @@
+/*
+DingusPPC - The Experimental PowerPC Macintosh emulator
+Copyright (C) 2018-24 divingkatae and maximum
+ (theweirdo) spatium
+
+(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
+
+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 3 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 .
+*/
+
+/** Aspen Memory Controller emulation. */
+
+#include
+#include
+#include
+
+AspenCtrl::AspenCtrl() : MemCtrlBase() {
+ this->name = "Aspen";
+
+ supports_types(HWCompType::MEM_CTRL | HWCompType::MMIO_DEV);
+
+ // add MMIO region for the configuration and status registers
+ add_mmio_region(0xF8000000, 0x800, this);
+}
+
+int AspenCtrl::device_postinit() {
+ return this->map_phys_ram();
+}
+
+void AspenCtrl::insert_ram_dimm(int bank_num, uint32_t capacity) {
+ if (bank_num < 0 || bank_num > 3)
+ return;
+
+ uint32_t bank_size = capacity << 20; // convert to MB
+
+ switch(bank_size) {
+ case DRAM_CAP_1MB:
+ case DRAM_CAP_4MB:
+ case DRAM_CAP_8MB:
+ case DRAM_CAP_16MB:
+ bank_sizes[bank_num] = bank_size;
+ break;
+ default:
+ break;
+ }
+}
+
+uint32_t AspenCtrl::read(uint32_t rgn_start, uint32_t offset, int size) {
+ uint8_t reg_num = (offset >> 2) & 0x1F;
+ uint32_t reg_val;
+
+ switch(reg_num) {
+ case SYSTEM_ID:
+ reg_val = 0x40010000;
+ break;
+ case CHIP_REV:
+ reg_val = ASPEN_REV_1;
+ break;
+ case GPIO_IN:
+ case GPIO_OUT:
+ reg_val = 0;
+ break;
+ case GPIO_ENABLE:
+ reg_val = this->gpio_enable << 24;
+ break;
+ default:
+ LOG_F(WARNING, "%s: reading from register at 0x%X", this->name.c_str(), offset);
+ reg_val = 0;
+ }
+
+ return reg_val;
+}
+
+void AspenCtrl::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) {
+ uint8_t reg_num = (offset >> 2) & 0x1F;
+
+ switch(reg_num) {
+ case SYSTEM_ID:
+ break; // ignore writes to this read-only register
+ case MEM_CONFIG:
+ if (this->ram_config != value >> 24) {
+ this->ram_config = value >> 24;
+ LOG_F(INFO, "%s: RAM config changed to 0x%X", this->name.c_str(),
+ this->ram_config);
+ }
+ break;
+ case GPIO_ENABLE:
+ this->gpio_enable = value >> 24;
+ break;
+ case GPIO_OUT:
+ LOG_F(INFO, "%s: output 0x%X to GPIO pins", this->name.c_str(), value >> 24);
+ break;
+ default:
+ LOG_F(WARNING, "%s: unknown register write at offset 0x%X", this->name.c_str(),
+ offset);
+ }
+}
+
+int AspenCtrl::map_phys_ram() {
+ uint32_t total_ram = 0, row_mask, col_mask, offset;
+
+ for (int i = 0; i < 4; i++)
+ total_ram += this->bank_sizes[i];
+
+ LOG_F(INFO, "%s: total RAM size = %d bytes", this->name.c_str(), total_ram);
+
+ uint32_t addr = 0;
+
+ for (int i = 0; i < 4; addr += DRAM_CAP_16MB, i++) {
+ if (!this->bank_sizes[i])
+ continue;
+ if (!add_ram_region(addr, this->bank_sizes[i])) {
+ LOG_F(ERROR, "%s: could not allocate RAM at 0x%X", this->name.c_str(), addr);
+ return -1;
+ }
+
+ switch(this->bank_sizes[i]) {
+ case DRAM_CAP_1MB:
+ row_mask = (1 << 9) - 1;
+ col_mask = (1 << 9) - 1;
+ break;
+ case DRAM_CAP_4MB:
+ row_mask = (1 << 10) - 1;
+ col_mask = (1 << 10) - 1;
+ break;
+ case DRAM_CAP_8MB:
+ row_mask = (1 << 11) - 1;
+ col_mask = (1 << 10) - 1;
+ break;
+ default: // DRAM_CAP_16MB
+ row_mask = (1 << 11) - 1;
+ col_mask = (1 << 11) - 1;
+ }
+
+ offset = ((0xC01000 >> 13) & row_mask) | ((0xC01000 >> 2) & col_mask);
+
+ if (!this->add_mem_mirror_partial(addr + 0xC01000, addr + offset, offset, 0x1000)) {
+ LOG_F(ERROR, "%s: could not create alias for RAM bank %d",
+ this->name.c_str(), i);
+ return -1;
+ }
+
+ if (this->bank_sizes[i] < DRAM_CAP_16MB) {
+ if (!this->add_mem_mirror_partial(addr + 0xC00000, addr + offset, offset, 0x1000)) {
+ LOG_F(ERROR, "%s: could not create alias for RAM bank %d",
+ this->name.c_str(), i);
+ return -1;
+ }
+ }
+
+ if (this->bank_sizes[i] < DRAM_CAP_8MB) {
+ if (!this->add_mem_mirror_partial(addr + 0x400000, addr + offset, offset, 0x1000)) {
+ LOG_F(ERROR, "%s: could not create alias for RAM bank %d",
+ this->name.c_str(), i);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static const DeviceDescription Aspen_Descriptor = {
+ AspenCtrl::create, {}, {}
+};
+
+REGISTER_DEVICE(Aspen, Aspen_Descriptor);
diff --git a/devices/memctrl/aspen.h b/devices/memctrl/aspen.h
new file mode 100644
index 0000000..55fb7ed
--- /dev/null
+++ b/devices/memctrl/aspen.h
@@ -0,0 +1,84 @@
+/*
+DingusPPC - The Experimental PowerPC Macintosh emulator
+Copyright (C) 2018-24 divingkatae and maximum
+ (theweirdo) spatium
+
+(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
+
+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 3 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 .
+*/
+
+/** Aspem Memory Controller definitions.
+
+ Aspen is a custom memory controller and PCI bridge
+ designed especially for Pippin.
+*/
+
+#ifndef ASPEN_MEMCTRL_H
+#define ASPEN_MEMCTRL_H
+
+#include
+#include
+
+#include
+#include
+
+/** Aspen register definitions. */
+enum {
+ SYSTEM_ID = 4, // 0x10
+ CHIP_REV = 5, // 0x14
+ MEM_CONFIG = 6, // 0x18
+ GPIO_IN = 12, // 0x30
+ GPIO_ENABLE = 13, // 0x34
+ GPIO_OUT = 14, // 0x38
+};
+
+#define ASPEN_REV_1 0x01000000
+
+/** Aspen RAM bank size bits. */
+enum {
+ BANK_SIZE_1MB = 0, // 256Kx16, 9 rows x 9 columns
+ BANK_SIZE_4MB = 1, // 1Mx16, 10 rows x 10 columns
+ BANK_SIZE_16MB = 2, // 4Mx16, 11 rows x 11 columns
+ BANK_SIZE_8MB = 3, // 2Mx16, 11 rows x 10 columns
+};
+
+class AspenCtrl : public MemCtrlBase, public MMIODevice {
+public:
+ AspenCtrl();
+ ~AspenCtrl() = default;
+
+ static std::unique_ptr create() {
+ return std::unique_ptr(new AspenCtrl());
+ }
+
+ int device_postinit() override;
+
+ void insert_ram_dimm(int bank_num, uint32_t capacity);
+
+ // MMIODevice methods
+ uint32_t read(uint32_t rgn_start, uint32_t offset, int size) override;
+ void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) override;
+
+private:
+ int map_phys_ram();
+
+ uint8_t gpio_enable = 0;
+
+ uint32_t bank_sizes[4] = {};
+ uint8_t ram_config = (BANK_SIZE_16MB << 6) | (BANK_SIZE_16MB << 4) |
+ (BANK_SIZE_16MB << 2) | (BANK_SIZE_16MB << 0);
+};
+
+#endif // ASPEN_MEMCTRL_H
diff --git a/devices/memctrl/memctrlbase.h b/devices/memctrl/memctrlbase.h
index b1de008..15fc60e 100644
--- a/devices/memctrl/memctrlbase.h
+++ b/devices/memctrl/memctrlbase.h
@@ -30,6 +30,7 @@ class MMIODevice;
/* Common DRAM capacities. */
enum {
+ DRAM_CAP_1MB = (1 << 20),
DRAM_CAP_2MB = (1 << 21),
DRAM_CAP_4MB = (1 << 22),
DRAM_CAP_8MB = (1 << 23),