summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/gameboy/huc1.cpp
blob: 71c937cef95a85944594186c6c13392828af471b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************

 Hudson Soft HuC1 Memory Controller

 Provides ROM and RAM banking and infrared I/O.  Supports up to 1 MiB ROM
 (64 16 KiB pages) and up to 32 KiB static RAM (4 8 KiB pages).  If RAM bank
 lines are used for coarse ROM banking, up to 4 MiB ROM (256 16 KiB pages)
 can be supported.

 The HuC1 controller appears to only respond to A15-A13 and D6-D0, i.e.
 addresses are effectively masked with 0xE000 and data is effectively masked
 with 0x3F.

 The HuC1 controller doesn't support disabling cartridge RAM without
 selecting infrared I/O.  However, some games still write 0x00 or 0x0A to
 the infrared/RAM select register as if it behaved like the Nintendo MBC
 series RAM enable register.  Some games write to the 0x6000-0x7FFF range,
 but this has no detectable effect.

 0x0000-3FFF R  - Fixed ROM bank, always first page of ROM.
 0x4000-7FFF R  - Selectable ROM bank, page 0-255 of ROM.
 0xA000-BFFF RW - Static RAM or infrared I/O.

 0x0000-1FFF W  - Select infrared (0x0E) or RAM (not 0x0E) at 0xA000.
 0x2000-3FFF W  - Select ROM page mapped at 0x4000.
 0x4000-5FFF W  - Select RAM page mapped at 0xA000.

 TODO:
 * If you try to use the infrared link feature in gbkiss, it says to press
   the B button to stop communication, but it doesn't respond to any inputs.
   Is this normal?
 * What is the default state for banking and infrared select on reset?
 * Does ROM bank 0 map to bank 1 like MBC1?
 * How many RAM page lines are there?  No games use more than 2.
 * Do any bits of the infrared I/O registers have any significance besides
   the least significant bit?
 * Do any values besides 0x0E have special significance for infrared/RAM
   select?

 ***************************************************************************/

#include "emu.h"
#include "huc1.h"

#include "cartbase.ipp"

#include <string>

//#define VERBOSE 1
//#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"


namespace bus::gameboy {

namespace {

class huc1_device : public mbc_ram_device_base<mbc_dual_device_base>
{
public:
	static constexpr feature_type unemulated_features() { return feature::COMMS; }

	huc1_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_reset() override ATTR_COLD;

private:
	void infrared_select(u8 data);
	void bank_switch_fine(u8 data);
	void bank_switch_coarse(u8 data);
	u8 read_ir(address_space &space);
	void write_ir(u8 data);

	memory_view m_view_ir;
};


huc1_device::huc1_device(
		machine_config const &mconfig,
		char const *tag,
		device_t *owner,
		u32 clock) :
	mbc_ram_device_base<mbc_dual_device_base>(mconfig, GB_ROM_HUC1, tag, owner, clock),
	m_view_ir(*this, "ir")
{
}


std::error_condition huc1_device::load(std::string &message)
{
	// check for valid ROM/RAM regions
	set_bank_bits_rom(2, 6);
	set_bank_bits_ram(2);
	if (!check_rom(message) || !check_ram(message))
		return image_error::BADSOFTWARE;

	// if that checked out, install memory
	install_rom();
	install_ram(*cart_space());

	// install memory controller handlers
	cart_space()->install_write_handler(
			0x0000, 0x1fff,
			emu::rw_delegate(*this, FUNC(huc1_device::infrared_select)));
	cart_space()->install_write_handler(
			0x2000, 0x3fff,
			emu::rw_delegate(*this, FUNC(huc1_device::bank_switch_fine)));
	cart_space()->install_write_handler(
			0x4000, 0x5fff,
			emu::rw_delegate(*this, FUNC(huc1_device::bank_switch_coarse)));

	// install infrared handlers
	cart_space()->install_view(0xa000, 0xbfff, m_view_ir);
	m_view_ir[0].install_read_handler(
			0xa000, 0xbfff,
			emu::rw_delegate(*this, FUNC(huc1_device::read_ir)));
	m_view_ir[0].install_write_handler(
			0xa000, 0xbfff,
			emu::rw_delegate(*this, FUNC(huc1_device::write_ir)));

	// all good
	return std::error_condition();
}


void huc1_device::device_reset()
{
	mbc_ram_device_base<mbc_dual_device_base>::device_reset();

	// TODO: what's the proper reset state?
	m_view_ir.disable();

	set_bank_rom_fine(0);
	set_bank_rom_coarse(0);
	set_bank_ram(0);
}


void huc1_device::infrared_select(u8 data)
{
	if (0x0e == (data & 0x0e))
	{
		LOG("%s: Infrared I/O selected\n", machine().describe_context());
		m_view_ir.select(0);
	}
	else
	{
		LOG("%s: Cartridge RAM selected\n", machine().describe_context());
		m_view_ir.disable();
	}
}


void huc1_device::bank_switch_fine(u8 data)
{
	// TODO: does zero map to bank 1 like MBC1?
	set_bank_rom_fine(data & 0x3f);
}


void huc1_device::bank_switch_coarse(u8 data)
{
	// TODO: how many output lines are physically present?
	set_bank_rom_coarse(data & 0x03);
	set_bank_ram(data & 0x03);
}


u8 huc1_device::read_ir(address_space &space)
{
	LOG("%s: Infrared read\n");
	return (space.unmap() & 0xc0) | 0x00; // least significant bit clear - dark
}


void huc1_device::write_ir(u8 data)
{
	// bit zero high to turn on the IR LED, or low to turn it off
	LOG("%s: Infrared write 0x%02X\n", machine().describe_context(), data);
}

} // anonymous namespace

} // namespace bus::gameboy


DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_HUC1, device_gb_cart_interface, bus::gameboy::huc1_device, "gb_rom_huc1", "Game Boy Hudson Soft HuC1 Cartridge")