summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/gameboy/mmm01.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/gameboy/mmm01.cpp')
-rw-r--r--src/devices/bus/gameboy/mmm01.cpp349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/devices/bus/gameboy/mmm01.cpp b/src/devices/bus/gameboy/mmm01.cpp
new file mode 100644
index 00000000000..65bf84acbb7
--- /dev/null
+++ b/src/devices/bus/gameboy/mmm01.cpp
@@ -0,0 +1,349 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ Nintendo Game Boy MMM01 memory controller
+
+ Designed to support multi-game cartridges. Included games can vary in
+ program ROM non-volatile RAM size requirements. Starts in configuration
+ mode, where coarse ROM and RAM page selection can be configured. After
+ leaving configuration mode, the only way to re-enter it is to reset the
+ MMM01 chip.
+
+ After leaving configuration mode, the behaviour will be similar to one of
+ the two standard wiring arrangements for cartridges using the MBC1 chip.
+
+ Supports up to 8 MiB ROM (512 16 KiB pages) and 128 KiB static RAM (16
+ 8 KiB pages) total. Each included game can use up to 2 MiB ROM (128 16 KiB
+ pages) and 8 KiB static RAM (one page), or up to 512 KiB ROM (32 16 KiB
+ pages) and 32 KiB static RAM (4 8 KiB pages). In configuration mode, up to
+ 32 KiB ROM and 128 KiB static RAM (16 8 KiB pages) can be used.
+
+ The MMM01 controller only responds to A15-A13 and D6-D0, i.e. addresses are
+ effectively masked with 0xE000 and data is effectively masked with 0x7F.
+ In configuration mode, most of the control register bits that are unused by
+ the MBC1 chip are repurposed.
+
+ 0x0000-3FFF R - Low ROM bank.
+ 0x4000-7FFF R - High ROM bank.
+ 0xA000-BFFF RW - Static RAM if enabled.
+
+ 0x0000-1FFF W - -X------ * Exit configuration mode.
+ --XX---- * Static RAM bank fine protect.
+ ----XXXX Static RAM enable.
+ 0x2000-3FFF W - -XX----- * ROM bank mid (RA20-RA19) or static RAM bank
+ fine (AA14-AA13), depending on mode.
+ ---XXXXX ROM bank fine (RA18-RA14).
+ 0x4000-5FFF W - -X------ * Low bank coarse enable protect.
+ --XX---- * ROM bank coarse (RA22-RA21).
+ ----XX-- * Static RAM bank coarse (AA16-AA15).
+ ------XX Static RAM bank fine (AA14-AA13) or ROM bank
+ mid (RA20-RA19), depending on mode.
+ 0x6000-7FFF W - -X------ * ROM bank mid/RAM bank coarse select.
+ --XXXX-- * ROM bank fine protect.
+ -------X Low bank coarse enable.
+
+ * Fields only writable in configuration mode.
+
+ ***************************************************************************/
+
+#include "emu.h"
+#include "mmm01.h"
+
+#include "cartbase.ipp"
+
+#include <string>
+
+//#define VERBOSE 1
+//#define LOG_OUTPUT_FUNC osd_printf_info
+#include "logmacro.h"
+
+
+namespace bus::gameboy {
+
+namespace {
+
+class mmm01_device : public mbc_ram_device_base<mbc_dual_uniform_device_base>
+{
+public:
+ mmm01_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+
+ virtual std::error_condition load(std::string &message) override ATTR_COLD;
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+private:
+ void enable_ram(u8 data);
+ void bank_switch_fine(u8 data);
+ void bank_switch_coarse(u8 data);
+ void enable_bank_low_mid(u8 data);
+
+ void update_bank_rom_low()
+ {
+ u16 entry;
+ if (!m_config)
+ {
+ entry = (u16(m_bank_sel_rom_coarse) << 7) | (m_bank_sel_rom_fine & m_bank_prot_sel[1]);
+ if (!m_mode_large_prg)
+ entry |= m_bank_sel_mid[1] << 5;
+ else if (m_bank_low_mid)
+ entry |= m_bank_sel_mid[0] << 5;
+ }
+ else
+ {
+ entry = 0x01fe;
+ }
+ set_bank_rom_low(entry);
+ }
+
+ void update_bank_rom_high()
+ {
+ u16 entry;
+ if (!m_config)
+ {
+ entry = (u16(m_bank_sel_rom_coarse) << 7) | m_bank_sel_rom_fine;
+ if (!m_mode_large_prg)
+ entry |= m_bank_sel_mid[1] << 5;
+ else
+ entry |= m_bank_sel_mid[0] << 5;
+ }
+ else
+ {
+ entry = 0x01fe | m_bank_sel_rom_fine;
+ }
+ if (!(m_bank_sel_rom_fine & ~m_bank_prot_sel[1]))
+ entry |= 0x0001;
+ set_bank_rom_high(entry);
+ }
+
+ void update_bank_ram()
+ {
+ u8 entry(m_bank_sel_ram_coarse << 2);
+ if (m_mode_large_prg)
+ entry |= m_bank_sel_mid[1];
+ else if (m_bank_low_mid)
+ entry |= m_bank_sel_mid[0];
+ set_bank_ram(entry);
+ }
+
+ memory_view m_view_ram;
+
+ u8 m_config;
+ u8 m_mode_large_prg;
+ u8 m_bank_sel_mid[2];
+ u8 m_bank_sel_ram_coarse;
+ u8 m_bank_sel_rom_coarse;
+ u8 m_bank_sel_rom_fine;
+ u8 m_bank_low_mid;
+ u8 m_bank_prot_sel[2];
+ u8 m_bank_prot_low_mid;
+};
+
+
+mmm01_device::mmm01_device(
+ machine_config const &mconfig,
+ char const *tag,
+ device_t *owner,
+ u32 clock) :
+ mbc_ram_device_base<mbc_dual_uniform_device_base>(mconfig, GB_ROM_MMM01, tag, owner, clock),
+ m_view_ram(*this, "ram"),
+ m_config(0U),
+ m_mode_large_prg(0U),
+ m_bank_sel_mid{ 0U, 0U },
+ m_bank_sel_ram_coarse(0U),
+ m_bank_sel_rom_coarse(0U),
+ m_bank_sel_rom_fine(0U),
+ m_bank_low_mid(0U),
+ m_bank_prot_sel{ 0U, 0U },
+ m_bank_prot_low_mid(0U)
+{
+}
+
+
+std::error_condition mmm01_device::load(std::string &message)
+{
+ // set up ROM and RAM
+ set_bank_bits_rom(9);
+ set_bank_bits_ram(4);
+ if (!check_rom(message) || !check_ram(message))
+ return image_error::BADSOFTWARE;
+ cart_space()->install_view(0xa000, 0xbfff, m_view_ram);
+ install_rom();
+ install_ram(m_view_ram[0]);
+
+ // install memory controller handlers
+ cart_space()->install_write_handler(
+ 0x0000, 0x1fff,
+ emu::rw_delegate(*this, FUNC(mmm01_device::enable_ram)));
+ cart_space()->install_write_handler(
+ 0x2000, 0x3fff,
+ emu::rw_delegate(*this, FUNC(mmm01_device::bank_switch_fine)));
+ cart_space()->install_write_handler(
+ 0x4000, 0x5fff,
+ emu::rw_delegate(*this, FUNC(mmm01_device::bank_switch_coarse)));
+ cart_space()->install_write_handler(
+ 0x6000, 0x6fff,
+ emu::rw_delegate(*this, FUNC(mmm01_device::enable_bank_low_mid)));
+
+ // all good
+ return std::error_condition();
+}
+
+
+void mmm01_device::device_start()
+{
+ mbc_ram_device_base<mbc_dual_uniform_device_base>::device_start();
+
+ save_item(NAME(m_config));
+ save_item(NAME(m_mode_large_prg));
+ save_item(NAME(m_bank_sel_mid));
+ save_item(NAME(m_bank_sel_ram_coarse));
+ save_item(NAME(m_bank_sel_rom_coarse));
+ save_item(NAME(m_bank_sel_rom_fine));
+ save_item(NAME(m_bank_low_mid));
+ save_item(NAME(m_bank_prot_sel));
+ save_item(NAME(m_bank_prot_low_mid));
+}
+
+
+void mmm01_device::device_reset()
+{
+ mbc_ram_device_base<mbc_dual_uniform_device_base>::device_reset();
+
+ m_view_ram.disable();
+ m_config = 1U;
+ m_mode_large_prg = 0U;
+ m_bank_sel_mid[0] = 0U;
+ m_bank_sel_mid[1] = 0U;
+ m_bank_sel_ram_coarse = 0U;
+ m_bank_sel_rom_coarse = 0U;
+ m_bank_sel_rom_fine = 0U;
+ m_bank_low_mid = 0U;
+ m_bank_prot_sel[0] = 0U;
+ m_bank_prot_sel[1] = 0U;
+ m_bank_prot_low_mid = 0U;
+
+ set_bank_rom_low(0x01fe);
+ set_bank_rom_high(0x01ff);
+ set_bank_ram(0);
+}
+
+
+void mmm01_device::enable_ram(u8 data)
+{
+ if (0x0a == (data & 0x0f))
+ {
+ LOG("%s: Cartridge RAM enabled\n", machine().describe_context());
+ m_view_ram.select(0);
+ }
+ else
+ {
+ LOG("%s: Cartridge RAM disabled\n", machine().describe_context());
+ m_view_ram.disable();
+ }
+
+ if (m_config)
+ {
+ m_bank_prot_sel[0] = BIT(data, 4, 2);
+ LOG(
+ "%s: Mid bank protect = 0x%X\n",
+ machine().describe_context(),
+ m_bank_prot_sel[0]);
+ if (BIT(data, 6))
+ {
+ LOG("%s: Configuration mode disabled\n", machine().describe_context());
+ m_config = 0U;
+ update_bank_rom_low();
+ update_bank_rom_high();
+ }
+ }
+}
+
+
+void mmm01_device::bank_switch_fine(u8 data)
+{
+ m_bank_sel_rom_fine = (m_bank_sel_rom_fine & m_bank_prot_sel[1]) | (BIT(data, 0, 5) & ~m_bank_prot_sel[1]);
+ LOG(
+ "%s: Fine ROM bank = 0x%02X\n",
+ machine().describe_context(),
+ m_bank_sel_rom_fine);
+
+ if (m_config)
+ {
+ m_bank_sel_mid[1] = BIT(data, 5, 2);
+ LOG(
+ "%s: Configuration mid bank = 0x%X\n",
+ machine().describe_context(),
+ m_bank_sel_mid[1]);
+ }
+
+ update_bank_rom_high();
+ if (m_config && m_mode_large_prg)
+ update_bank_ram();
+}
+
+
+void mmm01_device::bank_switch_coarse(u8 data)
+{
+ m_bank_sel_mid[0] = (m_bank_sel_mid[0] & m_bank_prot_sel[0]) | (BIT(data, 0, 2) & ~m_bank_prot_sel[0]);
+ LOG(
+ "%s: User mid bank = 0x%X\n",
+ machine().describe_context(),
+ m_bank_sel_mid[0]);
+
+ if (m_config)
+ {
+ m_bank_sel_ram_coarse = BIT(data, 2, 2);
+ m_bank_sel_rom_coarse = BIT(data, 4, 2);
+ m_bank_prot_low_mid = BIT(data, 6);
+ LOG(
+ "%s: Coarse ROM bank = 0x%X, coarse RAM bank = 0x%X, low bank mid enable %s\n",
+ machine().describe_context(),
+ m_bank_sel_rom_coarse,
+ m_bank_sel_ram_coarse,
+ m_bank_prot_low_mid ? "locked" : "unlocked");
+ }
+
+ if (m_config || (!m_mode_large_prg && m_bank_low_mid))
+ update_bank_ram();
+}
+
+
+void mmm01_device::enable_bank_low_mid(u8 data)
+{
+ if (!m_bank_prot_low_mid)
+ {
+ m_bank_low_mid = BIT(data, 0);
+ LOG(
+ "%s: Low bank mid %s\n",
+ machine().describe_context(),
+ m_bank_low_mid ? "enabled" : "disabled");
+ }
+
+ if (m_config)
+ {
+ m_bank_prot_sel[1] = BIT(data, 2, 4) << 1;
+ m_mode_large_prg = BIT(data, 6);
+ LOG(
+ "%s: Fine ROM bank protect = 0x%02X, %s mode\n",
+ machine().describe_context(),
+ m_bank_prot_sel[1],
+ m_mode_large_prg ? "large ROM" : "banked RAM");
+ }
+
+ if (!m_bank_prot_low_mid && !m_config)
+ update_bank_rom_low();
+ if (m_config)
+ update_bank_rom_high();
+ if (!m_bank_prot_low_mid || m_config)
+ update_bank_ram();
+}
+
+} // anonymous namespace
+
+} // namespace bus::gameboy
+
+
+DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MMM01, device_gb_cart_interface, bus::gameboy::mmm01_device, "gb_rom_mmm01", "Game Boy MMM01 Cartridge")