-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
mbc6.cpp
529 lines (448 loc) · 14.8 KB
/
mbc6.cpp
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
Nintendo Game Boy Memory Bank Controller 6
Only used for one game, the cartridges contain a Macronix MX29F008TC-14
Flash ROM (8-bit, 1 MiB), as well as the MBC6 chip, program ROM,
W24257S-70LL static RAM, MM1134, and CR1616 backup cell. Supports up to
1 MiB ROM (128 8 KiB pages) and 32 KiB static RAM (8 4 KiB pages).
The MBC6 chip seems to have support for an additional ROM chip, as setting
bit 7 of the page seems to deselect the program ROM. RAM banking lines
could theoretically be used for outer ROM banking, allowing up to 8 MiB
program ROM (1024 8 KiB pages), or 16 MiB program ROM (2048 8 KiB pages)
if dual ROM chips are supported.
The MX29F008TC doesn't use either of the standard "boot sector" layouts
used by MX29LV008CT and MX29F008CB chips. It seems to use 4 KiB sectors.
The reported silicon ID is 0xC2 0x81. It's not clear how write protection
works. The game doesn't seem to set up conventional sector protection
(command 0x60). It's possible the Flash chips had all sectors protected
before being installed in cartridges and the Flash write enable output just
drives RESET# to Vhv to temporarily disable sector protection.
0x0000-3FFF R - Fixed ROM bank, always first 16 KiB of ROM.
0x4000-5FFF RW - Lower selectable ROM/Flash bank.
0x6000-7FFF RW - Upper selectable ROM/Flash bank.
0xA000-AFFF RW - Lower selectable static RAM bank if enabled.
0xB000-BFFF RW - Upper selectable static RAM bank if enabled.
0x0000-03FF W - Static RAM enable - write 0x0A on low nybble to enable,
any other value to disable.
0x0400-07FF W - Select static RAM page mapped at 0xA000.
0x0800-0BFF W - Select static RAM page mapped at 0xB000.
0x0C00-0FFF W - Least significant bit enables Flash access.
0x1000-???? W - Least significant bit enables Flash write. Seems to be
required to enable or disable Flash access, too. Doesn't
prevent writing commands to Flash, only erasing or writing
data (can read the silicon ID without setting this).
0x2000-27FF W - Select ROM/Flash page mapped at 0x4000.
0x2800-2FFF W - Select ROM (0x00) or Flash (0x08) at 0x4000.
0x3000-37FF W - Select ROM/Flash page mapped at 0x6000.
0x3800-3FFF W - Select ROM (0x00) or Flash (0x08) at 0x6000.
TODO:
* Hook up Flash write enable.
* What's the correct reset state?
* What happens if bit 7 of the page number is set when Flash is selected?
* What do the unknown bits in the Flash enable register do?
***************************************************************************/
#include "emu.h"
#include "mbc6.h"
#include "cartbase.h"
#include "bus/generic/slot.h"
#include "machine/intelfsh.h"
#include <string>
//#define VERBOSE 1
//#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"
namespace bus::gameboy {
namespace {
class mbc6_device : public mbc_8k_device_base
{
public:
static constexpr feature_type imperfect_features() { return feature::ROM; }
mbc6_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
virtual std::error_condition load(std::string &message) override ATTR_COLD;
virtual void unload() override ATTR_COLD;
protected:
virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
virtual void device_start() override ATTR_COLD;
virtual void device_reset() override ATTR_COLD;
private:
static inline constexpr unsigned PAGE_RAM_SIZE = 0x1000;
template <unsigned Bank> u8 read_flash(offs_t offset);
template <unsigned Bank> void write_flash(offs_t offset, u8 data);
void bank_switch_rom(offs_t offset, u8 data);
void select_flash(offs_t offset, u8 data);
template <unsigned Bank> void bank_switch_ram(u8 data);
void enable_flash(u8 data);
void enable_flash_write(u8 data);
void enable_ram(u8 data);
bool check_ram(std::string &message) ATTR_COLD;
void install_ram() ATTR_COLD;
required_device<intelfsh8_device> m_flash;
memory_view m_view_rom[2];
memory_view m_view_ram;
memory_bank_array_creator<2> m_bank_ram;
u8 m_bank_mask_ram;
u8 m_bank_sel_rom[2];
u8 m_bank_sel_ram[2];
u8 m_flash_select[2];
u8 m_flash_enable;
u8 m_flash_writable;
};
mbc6_device::mbc6_device(
machine_config const &mconfig,
char const *tag,
device_t *owner,
u32 clock) :
mbc_8k_device_base(mconfig, GB_ROM_MBC6, tag, owner, clock),
m_flash(*this, "flash"),
m_view_rom{ { *this, "romlow"}, { *this, "romhigh" } },
m_view_ram(*this, "ram"),
m_bank_ram(*this, { "ramlow", "ramhigh" }),
m_bank_mask_ram(0U),
m_bank_sel_rom{ 0U, 0U },
m_bank_sel_ram{ 0U, 0U },
m_flash_select{ 0U, 0U },
m_flash_enable(0U),
m_flash_writable(0U)
{
}
std::error_condition mbc6_device::load(std::string &message)
{
// first check that ROM/RAM regions are supportable
set_bank_bits_rom(7);
if (!check_rom(message) || !check_ram(message))
return image_error::BADSOFTWARE;
// install views for ROM/flash and RAM
cart_space()->install_view(0x4000, 0x5fff, m_view_rom[0]);
cart_space()->install_view(0x6000, 0x7fff, m_view_rom[1]);
cart_space()->install_view(0xa000, 0xbfff, m_view_ram);
// set up ROM and RAM as appropriate
install_rom(*cart_space(), m_view_rom[0][0], m_view_rom[1][0]);
install_ram();
// install memory controller handlers
cart_space()->install_write_handler(
0x0000, 0x03ff,
emu::rw_delegate(*this, FUNC(mbc6_device::enable_ram)));
cart_space()->install_write_handler(
0x0400, 0x07ff,
emu::rw_delegate(*this, FUNC(mbc6_device::bank_switch_ram<0>)));
cart_space()->install_write_handler(
0x0800, 0x0bff,
emu::rw_delegate(*this, FUNC(mbc6_device::bank_switch_ram<1>)));
cart_space()->install_write_handler(
0x0c00, 0x0fff,
emu::rw_delegate(*this, FUNC(mbc6_device::enable_flash)));
cart_space()->install_write_handler(
0x1000, 0x1000, // TODO: what range does this actually respond to?
emu::rw_delegate(*this, FUNC(mbc6_device::enable_flash_write)));
cart_space()->install_write_handler(
0x2000, 0x27ff, 0x0000, 0x0000, 0x1000,
emu::rw_delegate(*this, FUNC(mbc6_device::bank_switch_rom)));
cart_space()->install_write_handler(
0x2800, 0x2fff, 0x0000, 0x0000, 0x1000,
emu::rw_delegate(*this, FUNC(mbc6_device::select_flash)));
// install Flash handlers
m_view_rom[0][1].install_readwrite_handler(
0x4000, 0x5fff,
emu::rw_delegate(*this, FUNC(mbc6_device::read_flash<0>)),
emu::rw_delegate(*this, FUNC(mbc6_device::write_flash<0>)));
m_view_rom[1][1].install_readwrite_handler(
0x6000, 0x7fff,
emu::rw_delegate(*this, FUNC(mbc6_device::read_flash<1>)),
emu::rw_delegate(*this, FUNC(mbc6_device::write_flash<1>)));
// all good
return std::error_condition();
}
void mbc6_device::unload()
{
memory_region *const nvramregion(cart_nvram_region());
if (nvramregion && nvramregion->bytes())
battery_save(nvramregion->base(), nvramregion->bytes());
}
void mbc6_device::device_add_mconfig(machine_config &config)
{
MACRONIX_29F008TC(config, m_flash);
}
void mbc6_device::device_start()
{
mbc_8k_device_base::device_start();
save_item(NAME(m_bank_sel_rom));
save_item(NAME(m_bank_sel_ram));
save_item(NAME(m_flash_select));
save_item(NAME(m_flash_enable));
save_item(NAME(m_flash_writable));
}
void mbc6_device::device_reset()
{
mbc_8k_device_base::device_reset();
m_bank_sel_rom[0] = 0U;
m_bank_sel_rom[1] = 0U;
m_bank_sel_ram[0] = 0U;
m_bank_sel_ram[1] = 0U;
m_flash_select[0] = 0U;
m_flash_select[1] = 0U;
m_flash_enable = 0U;
m_flash_writable = 0U;
m_view_rom[0].select(0);
m_view_rom[1].select(0);
m_view_ram.disable();
if (m_bank_mask_ram)
{
m_bank_ram[0]->set_entry(0);
m_bank_ram[1]->set_entry(0);
}
set_bank_rom_low(0);
set_bank_rom_high(0);
}
template <unsigned Bank>
u8 mbc6_device::read_flash(offs_t offset)
{
offset |= offs_t(m_bank_sel_rom[Bank] & 0x7f) << 13;
return m_flash->read(offset);
}
template <unsigned Bank>
void mbc6_device::write_flash(offs_t offset, u8 data)
{
offset |= offs_t(m_bank_sel_rom[Bank] & 0x7f) << 13;
LOG(
"%s: Write Flash %s 0x%05X = 0x%02X\n",
machine().describe_context(),
Bank ? "high" : "low",
offset,
data);
m_flash->write(offset, data);
}
void mbc6_device::bank_switch_rom(offs_t offset, u8 data)
{
auto const bank(BIT(offset, 12));
m_bank_sel_rom[bank] = data;
if (!m_flash_select[bank])
{
if (BIT(data, 7))
{
LOG(
"%s: ROM bank %s unmapped\n",
machine().describe_context(),
bank ? "high" : "low");
m_view_rom[bank].disable(); // is there a chip select for a second program ROM?
}
else
{
m_view_rom[bank].select(0);
}
}
if (bank)
set_bank_rom_high(data & 0x7f);
else
set_bank_rom_low(data & 0x7f);
}
void mbc6_device::select_flash(offs_t offset, u8 data)
{
auto const bank(BIT(offset, 12));
m_flash_select[bank] = BIT(data, 3);
if (m_flash_select[bank])
{
LOG(
"%s: Flash bank %s selected (%s)\n",
machine().describe_context(),
bank ? "high" : "low",
m_flash_enable ? "enabled" : "disabled");
if (m_flash_enable)
m_view_rom[bank].select(1);
else
m_view_rom[bank].disable();
}
else if (BIT(m_bank_sel_rom[bank], 7))
{
LOG(
"%s: ROM bank %s unmapped\n",
machine().describe_context(),
bank ? "high" : "low");
m_view_rom[bank].disable(); // is there a chip select for a second program ROM?
}
else
{
LOG(
"%s: ROM bank %s selected\n",
machine().describe_context(),
bank ? "high" : "low");
m_view_rom[bank].select(0);
}
// game writes 0xc6 when selecting ROM during boot - what do the other bits do?
if (data & ~0x08)
{
LOG(
"%s: ROM/Flash select %s with unknown bits set 0x%02X\n",
machine().describe_context(),
bank ? "high" : "low",
data);
}
}
template <unsigned Bank>
void mbc6_device::bank_switch_ram(u8 data)
{
if (m_bank_mask_ram)
{
data &= m_bank_mask_ram;
LOG(
"%s: Set RAM bank %s = 0x%04X\n",
machine().describe_context(),
Bank ? "high" : "low",
data * PAGE_RAM_SIZE);
m_bank_ram[Bank]->set_entry(data);
}
}
void mbc6_device::enable_flash(u8 data)
{
// game seems to always set Flash write enable before changing this
if (m_flash_writable)
{
m_flash_enable = BIT(data, 0);
if (m_flash_enable)
{
LOG("%s: Flash enabled\n", machine().describe_context());
if (m_flash_select[0])
m_view_rom[0].select(1);
if (m_flash_select[1])
m_view_rom[1].select(1);
}
else
{
LOG("%s: Flash disabled\n", machine().describe_context());
if (m_flash_select[0])
m_view_rom[0].disable();
if (m_flash_select[1])
m_view_rom[1].disable();
}
if (data & ~0x01)
{
logerror(
"%s: Flash enable with unknown bits set 0x%02X\n",
machine().describe_context(),
data);
}
}
else
{
logerror(
"%s: Flash enable 0x%02X when Flash write disabled\n",
machine().describe_context(),
data);
}
}
void mbc6_device::enable_flash_write(u8 data)
{
// FIXME: this should also control whether Flash can be erased/written
m_flash_writable = BIT(data, 0);
LOG(
"%s: Flash write %s\n",
machine().describe_context(),
m_flash_writable ? "enabled" : "disabled");
if (data & ~0x01)
{
logerror(
"%s: Flash write enable with unknown bits set 0x%02X\n",
machine().describe_context(),
data);
}
}
void mbc6_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();
}
}
bool mbc6_device::check_ram(std::string &message)
{
memory_region *const ramregion(cart_ram_region());
memory_region *const nvramregion(cart_nvram_region());
auto const rambytes(ramregion ? ramregion->bytes() : 0);
auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0);
if (rambytes && nvrambytes && ((rambytes & (PAGE_RAM_SIZE - 1)) || (nvrambytes & (PAGE_RAM_SIZE - 1)) || ((nvrambytes - 1) & nvrambytes)))
{
message = "Unsupported cartridge RAM size (if not all RAM is battery-backed, battery backed RAM size must be a power of two no smaller than 4 KiB, and total RAM size must be a multiple of 4 KiB)";
return false;
}
auto const ramtotal(rambytes + nvrambytes);
if (((PAGE_RAM_SIZE < ramtotal) && (ramtotal & (PAGE_RAM_SIZE - 1))) && ((u32(PAGE_RAM_SIZE) << 3) < ramtotal))
{
message = "Unsupported cartridge RAM size (must be no larger than 4 KiB, or a multiple of 4 KiB no larger than 32 KiB)";
return false;
}
return true;
}
void mbc6_device::install_ram()
{
memory_region *const ramregion(this->cart_ram_region());
memory_region *const nvramregion(this->cart_nvram_region());
auto const rambytes(ramregion ? ramregion->bytes() : 0);
auto const nvrambytes(nvramregion ? nvramregion->bytes() : 0);
auto const ramtotal(rambytes + nvrambytes);
u8 *const rambase(rambytes ? &ramregion->as_u8() : nullptr);
u8 *const nvrambase(nvrambytes ? &nvramregion->as_u8() : nullptr);
if (!ramtotal)
{
// need to avoid fatal errors
m_bank_mask_ram = 0U;
m_view_ram[0];
}
else if (PAGE_RAM_SIZE >= ramtotal)
{
// install small amounts of RAM directly - mixed volatile and non-volatile RAM is not supported
device_generic_cart_interface::install_non_power_of_two<0>(
nvrambytes ? nvrambytes : rambytes,
PAGE_RAM_SIZE - 1,
0,
0xa000,
[this, base = nvrambase ? nvrambase : rambase] (offs_t begin, offs_t end, offs_t mirror, offs_t src)
{
LOG(
"Install RAM 0x%05X-0x%05X at 0x%04X-0x%04X mirror 0x%04X\n",
src,
src + (end - begin),
begin,
end,
0x1000 | mirror);
m_view_ram[0].install_ram(begin, end, 0x1000 | mirror, &base[src]);
});
m_bank_mask_ram = 0U;
}
else
{
// install banked RAM, assuming lower pages are battery-backed
m_bank_mask_ram = device_generic_cart_interface::map_non_power_of_two(
unsigned(ramtotal / PAGE_RAM_SIZE),
[this, nvrampages = nvrambytes / PAGE_RAM_SIZE, rambase, nvrambase] (unsigned entry, unsigned page)
{
bool const nonvolatile(page < nvrampages);
u8 *const base(nonvolatile ? nvrambase : rambase);
auto const offset((page - (nonvolatile ? 0 : nvrampages)) * PAGE_RAM_SIZE);
LOG(
"Install %s 0x%05X-0x%05X in bank entry %u\n",
nonvolatile ? "NVRAM" : "RAM",
offset,
offset + (PAGE_RAM_SIZE - 1),
entry);
m_bank_ram[0]->configure_entry(entry, &base[offset]);
m_bank_ram[1]->configure_entry(entry, &base[offset]);
});
m_view_ram[0].install_readwrite_bank(0xa000, 0xafff, m_bank_ram[0]);
m_view_ram[0].install_readwrite_bank(0xb000, 0xbfff, m_bank_ram[1]);
}
if (nvrambytes)
{
save_pointer(NAME(nvrambase), nvrambytes);
battery_load(nvrambase, nvrambytes, nullptr);
}
if (rambytes)
save_pointer(NAME(rambase), rambytes);
}
} // anonymous namespace
} // namespace bus::gameboy
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC6, device_gb_cart_interface, bus::gameboy::mbc6_device, "gb_rom_mbc6", "Game Boy MBC6 Cartridge")