trunk/src/emu/bus/nes/disksys.c
| r0 | r32068 | |
| 1 | /*********************************************************************************************************** |
| 2 | |
| 3 | |
| 4 | NES/Famicom cartridge emulation for Disk System expansion |
| 5 | |
| 6 | Copyright MESS Team. |
| 7 | Visit http://mamedev.org for licensing and usage restrictions. |
| 8 | |
| 9 | |
| 10 | Here we emulate the RAM expansion + Disk Drive which form the |
| 11 | Famicom Disk System. |
| 12 | |
| 13 | Based on info from NESDev wiki ( http://wiki.nesdev.com/w/index.php/Family_Computer_Disk_System ) |
| 14 | |
| 15 | TODO: |
| 16 | - convert floppy drive + fds format to modern code! |
| 17 | - add sound bits |
| 18 | - stop IRQ from using HOLD_LINE |
| 19 | |
| 20 | ***********************************************************************************************************/ |
| 21 | |
| 22 | |
| 23 | #include "emu.h" |
| 24 | #include "disksys.h" |
| 25 | #include "cpu/m6502/m6502.h" |
| 26 | #include "imagedev/flopdrv.h" |
| 27 | #include "formats/nes_dsk.h" |
| 28 | |
| 29 | #ifdef NES_PCB_DEBUG |
| 30 | #define VERBOSE 1 |
| 31 | #else |
| 32 | #define VERBOSE 0 |
| 33 | #endif |
| 34 | |
| 35 | #define LOG_MMC(x) do { if (VERBOSE) logerror x; } while (0) |
| 36 | |
| 37 | |
| 38 | //----------------------------------------------- |
| 39 | // |
| 40 | // Disk drive implementation |
| 41 | // |
| 42 | //----------------------------------------------- |
| 43 | |
| 44 | static const floppy_interface nes_floppy_interface = |
| 45 | { |
| 46 | FLOPPY_STANDARD_5_25_DSHD, |
| 47 | LEGACY_FLOPPY_OPTIONS_NAME(nes_only), |
| 48 | "floppy_5_25" |
| 49 | }; |
| 50 | |
| 51 | static MACHINE_CONFIG_FRAGMENT( nes_disksys ) |
| 52 | MCFG_LEGACY_FLOPPY_DRIVE_ADD(FLOPPY_0, nes_floppy_interface) |
| 53 | MACHINE_CONFIG_END |
| 54 | |
| 55 | //------------------------------------------------- |
| 56 | // machine_config_additions - device-specific |
| 57 | // machine configurations |
| 58 | //------------------------------------------------- |
| 59 | |
| 60 | machine_config_constructor nes_disksys_device::device_mconfig_additions() const |
| 61 | { |
| 62 | return MACHINE_CONFIG_NAME( nes_disksys ); |
| 63 | } |
| 64 | |
| 65 | |
| 66 | |
| 67 | ROM_START( disksys ) |
| 68 | ROM_REGION(0x2000, "drive", 0) |
| 69 | ROM_SYSTEM_BIOS( 0, "2c33a-01a", "Famicom Disk System Bios") |
| 70 | ROMX_LOAD( "rp2c33a-01a.bin", 0x0000, 0x2000, CRC(5e607dcf) SHA1(57fe1bdee955bb48d357e463ccbf129496930b62), ROM_BIOS(1)) // newer, Nintendo logo has no shadow |
| 71 | ROM_SYSTEM_BIOS( 1, "2c33-01", "Famicom Disk System Bios, older") |
| 72 | ROMX_LOAD( "rp2c33-01.bin", 0x0000, 0x2000, CRC(1c7ae5d5) SHA1(af5af53f66982e749643fdf8b2acbb7d4d3ed229), ROM_BIOS(2)) // older, Nintendo logo has shadow |
| 73 | ROM_END |
| 74 | |
| 75 | //------------------------------------------------- |
| 76 | // rom_region - device-specific ROM region |
| 77 | //------------------------------------------------- |
| 78 | |
| 79 | const rom_entry *nes_disksys_device::device_rom_region() const |
| 80 | { |
| 81 | return ROM_NAME( disksys ); |
| 82 | } |
| 83 | |
| 84 | |
| 85 | void nes_disksys_device::load_proc(device_image_interface &image) |
| 86 | { |
| 87 | nes_disksys_device *disk_sys = static_cast<nes_disksys_device *>(image.device().owner()); |
| 88 | disk_sys->load_disk(image); |
| 89 | } |
| 90 | |
| 91 | void nes_disksys_device::unload_proc(device_image_interface &image) |
| 92 | { |
| 93 | nes_disksys_device *disk_sys = static_cast<nes_disksys_device *>(image.device().owner()); |
| 94 | disk_sys->unload_disk(image); |
| 95 | } |
| 96 | |
| 97 | |
| 98 | //------------------------------------------------ |
| 99 | // |
| 100 | // RAM expansion cart implementation |
| 101 | // |
| 102 | //------------------------------------------------ |
| 103 | |
| 104 | const device_type NES_DISKSYS = &device_creator<nes_disksys_device>; |
| 105 | |
| 106 | |
| 107 | nes_disksys_device::nes_disksys_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 108 | : nes_nrom_device(mconfig, NES_DISKSYS, "FC RAM Expansion + Disk System PCB", tag, owner, clock, "fc_disksys", __FILE__), |
| 109 | m_fds_data(NULL), |
| 110 | m_disk(*this, FLOPPY_0), |
| 111 | m_fds_sides(0) |
| 112 | { |
| 113 | } |
| 114 | |
| 115 | |
| 116 | void nes_disksys_device::device_start() |
| 117 | { |
| 118 | common_start(); |
| 119 | |
| 120 | m_2c33_rom = (UINT8*)memregion("drive")->base(); |
| 121 | |
| 122 | m_disk->floppy_install_load_proc(nes_disksys_device::load_proc); |
| 123 | m_disk->floppy_install_unload_proc(nes_disksys_device::unload_proc); |
| 124 | |
| 125 | irq_timer = timer_alloc(TIMER_IRQ); |
| 126 | irq_timer->adjust(attotime::zero, 0, machine().device<cpu_device>("maincpu")->cycles_to_attotime(1)); |
| 127 | |
| 128 | save_item(NAME(m_fds_motor_on)); |
| 129 | save_item(NAME(m_fds_door_closed)); |
| 130 | save_item(NAME(m_fds_current_side)); |
| 131 | save_item(NAME(m_fds_head_position)); |
| 132 | save_item(NAME(m_fds_status0)); |
| 133 | save_item(NAME(m_read_mode)); |
| 134 | save_item(NAME(m_drive_ready)); |
| 135 | save_item(NAME(m_irq_enable)); |
| 136 | save_item(NAME(m_irq_transfer)); |
| 137 | save_item(NAME(m_irq_count)); |
| 138 | save_item(NAME(m_irq_count_latch)); |
| 139 | |
| 140 | save_item(NAME(m_fds_last_side)); |
| 141 | save_item(NAME(m_fds_count)); |
| 142 | } |
| 143 | |
| 144 | void nes_disksys_device::pcb_reset() |
| 145 | { |
| 146 | // read accesses in 0x6000-0xffff are always handled by |
| 147 | // cutom code below, so no need to setup the prg... |
| 148 | chr8(0, CHRRAM); |
| 149 | set_nt_mirroring(PPU_MIRROR_VERT); |
| 150 | |
| 151 | m_fds_motor_on = 0; |
| 152 | m_fds_door_closed = 0; |
| 153 | m_fds_current_side = 1; |
| 154 | m_fds_head_position = 0; |
| 155 | m_fds_status0 = 0; |
| 156 | m_read_mode = 0; |
| 157 | m_drive_ready = 0; |
| 158 | m_irq_count = 0; |
| 159 | m_irq_count_latch = 0; |
| 160 | m_irq_enable = 0; |
| 161 | m_irq_transfer = 0; |
| 162 | |
| 163 | m_fds_count = 0; |
| 164 | m_fds_last_side = 0; |
| 165 | } |
| 166 | |
| 167 | |
| 168 | /*------------------------------------------------- |
| 169 | mapper specific handlers |
| 170 | -------------------------------------------------*/ |
| 171 | |
| 172 | /*------------------------------------------------- |
| 173 | |
| 174 | RAM is in 0x6000-0xdfff (32K) |
| 175 | ROM is in 0xe000-0xffff (8K) |
| 176 | |
| 177 | registers + disk drive are accessed in |
| 178 | 0x4020-0x403f (read_ex/write_ex below) |
| 179 | |
| 180 | -------------------------------------------------*/ |
| 181 | |
| 182 | WRITE8_MEMBER(nes_disksys_device::write_h) |
| 183 | { |
| 184 | LOG_MMC(("Famicom Disk System write_h, offset %04x, data: %02x\n", offset, data)); |
| 185 | |
| 186 | if (offset < 0x6000) |
| 187 | m_prgram[offset + 0x2000] = data; |
| 188 | } |
| 189 | |
| 190 | READ8_MEMBER(nes_disksys_device::read_h) |
| 191 | { |
| 192 | LOG_MMC(("Famicom Disk System read_h, offset: %04x\n", offset)); |
| 193 | |
| 194 | if (offset < 0x6000) |
| 195 | return m_prgram[offset + 0x2000]; |
| 196 | else |
| 197 | return m_2c33_rom[offset & 0x1fff]; |
| 198 | } |
| 199 | |
| 200 | WRITE8_MEMBER(nes_disksys_device::write_m) |
| 201 | { |
| 202 | LOG_MMC(("Famicom Disk System write_m, offset: %04x, data: %02x\n", offset, data)); |
| 203 | m_prgram[offset] = data; |
| 204 | } |
| 205 | |
| 206 | READ8_MEMBER(nes_disksys_device::read_m) |
| 207 | { |
| 208 | LOG_MMC(("Famicom Disk System read_m, offset: %04x\n", offset)); |
| 209 | return m_prgram[offset]; |
| 210 | } |
| 211 | |
| 212 | void nes_disksys_device::hblank_irq(int scanline, int vblank, int blanked) |
| 213 | { |
| 214 | if (m_irq_transfer) |
| 215 | machine().device("maincpu")->execute().set_input_line(M6502_IRQ_LINE, HOLD_LINE); |
| 216 | } |
| 217 | |
| 218 | WRITE8_MEMBER(nes_disksys_device::write_ex) |
| 219 | { |
| 220 | LOG_MMC(("Famicom Disk System write_ex, offset: %04x, data: %02x\n", offset, data)); |
| 221 | |
| 222 | if (offset >= 0x20 && offset < 0x60) |
| 223 | { |
| 224 | // wavetable |
| 225 | } |
| 226 | |
| 227 | switch (offset) |
| 228 | { |
| 229 | case 0x00: |
| 230 | m_irq_count_latch = (m_irq_count_latch & 0xff00) | data; |
| 231 | break; |
| 232 | case 0x01: |
| 233 | m_irq_count_latch = (m_irq_count_latch & 0x00ff) | (data << 8); |
| 234 | break; |
| 235 | case 0x02: |
| 236 | m_irq_count = m_irq_count_latch; |
| 237 | m_irq_enable = BIT(data, 1); |
| 238 | break; |
| 239 | case 0x03: |
| 240 | // bit0 - Enable disk I/O registers |
| 241 | // bit1 - Enable sound I/O registers |
| 242 | break; |
| 243 | case 0x04: |
| 244 | // write data out to disk |
| 245 | // TEST! |
| 246 | if (m_fds_data && m_fds_current_side && !m_read_mode) |
| 247 | m_fds_data[(m_fds_current_side - 1) * 65500 + m_fds_head_position++] = data; |
| 248 | break; |
| 249 | case 0x05: |
| 250 | // $4025 - FDS Control |
| 251 | // bit0 - Drive Motor Control (0: Stop motor; 1: Turn on motor) |
| 252 | // bit1 - Transfer Reset (Set 1 to reset transfer timing to the initial state) |
| 253 | // bit2 - Read / Write mode (0: write; 1: read) |
| 254 | // bit3 - Mirroring (0: horizontal; 1: vertical) |
| 255 | // bit4 - CRC control (set during CRC calculation of transfer) |
| 256 | // bit5 - Always set to '1' |
| 257 | // bit6 - Read/Write Start (Set to 1 when the drive becomes ready for read/write) |
| 258 | // bit7 - Interrupt Transfer (0: Transfer without using IRQ; 1: Enable IRQ when |
| 259 | // the drive becomes ready) |
| 260 | m_fds_motor_on = BIT(data, 0); |
| 261 | |
| 262 | if (BIT(data, 1)) |
| 263 | m_fds_head_position = 0; |
| 264 | |
| 265 | if (!(data & 0x40) && m_drive_ready && m_fds_head_position > 2) |
| 266 | m_fds_head_position -= 2; // ??? is this some sort of compensation?? |
| 267 | |
| 268 | m_read_mode = BIT(data, 2); |
| 269 | set_nt_mirroring(BIT(data, 3) ? PPU_MIRROR_HORZ : PPU_MIRROR_VERT); |
| 270 | m_drive_ready = data & 0x40; |
| 271 | m_irq_transfer = BIT(data, 7); |
| 272 | break; |
| 273 | case 0x06: |
| 274 | // external connector |
| 275 | break; |
| 276 | case 0x60: // $4080 - Volume envelope - read through $4090 |
| 277 | case 0x62: // $4082 - Frequency low |
| 278 | case 0x63: // $4083 - Frequency high |
| 279 | case 0x64: // $4084 - Mod envelope - read through $4092 |
| 280 | case 0x65: // $4085 - Mod counter |
| 281 | case 0x66: // $4086 - Mod frequency low |
| 282 | case 0x67: // $4087 - Mod frequency high |
| 283 | case 0x68: // $4088 - Mod table write |
| 284 | case 0x69: // $4089 - Wave write / master volume |
| 285 | case 0x6a: // $408a - Envelope speed |
| 286 | break; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | READ8_MEMBER(nes_disksys_device::read_ex) |
| 291 | { |
| 292 | LOG_MMC(("Famicom Disk System read_ex, offset: %04x\n", offset)); |
| 293 | UINT8 ret = 0x00; |
| 294 | |
| 295 | if (offset >= 0x20 && offset < 0x60) |
| 296 | { |
| 297 | // wavetable |
| 298 | } |
| 299 | |
| 300 | switch (offset) |
| 301 | { |
| 302 | case 0x10: |
| 303 | // $4030 - disk status 0 |
| 304 | // bit0 - Timer Interrupt (1: an IRQ occurred) |
| 305 | // bit1 - Byte transfer flag (Set to 1 every time 8 bits have been transfered between |
| 306 | // the RAM adaptor & disk drive through $4024/$4031; Reset to 0 when $4024, |
| 307 | // $4031, or $4030 has been serviced) |
| 308 | // bit4 - CRC control (0: CRC passed; 1: CRC error) |
| 309 | // bit6 - End of Head (1 when disk head is on the most inner track) |
| 310 | // bit7 - Disk Data Read/Write Enable (1 when disk is readable/writable) |
| 311 | ret = m_fds_status0 | 0x80; |
| 312 | // clear the disk IRQ detect flag |
| 313 | m_fds_status0 &= ~0x01; |
| 314 | break; |
| 315 | case 0x11: |
| 316 | // $4031 - data latch |
| 317 | // don't read data if disk is unloaded |
| 318 | if (!m_fds_data) |
| 319 | ret = 0; |
| 320 | else if (m_fds_current_side && m_read_mode) |
| 321 | { |
| 322 | ret = m_fds_data[(m_fds_current_side - 1) * 65500 + m_fds_head_position++]; |
| 323 | if (m_fds_head_position == 65500) |
| 324 | { |
| 325 | printf("end of disk reached!\n"); |
| 326 | m_fds_status0 |= 0x40; |
| 327 | m_fds_head_position -= 2; |
| 328 | } |
| 329 | } |
| 330 | else |
| 331 | ret = 0; |
| 332 | break; |
| 333 | case 0x12: |
| 334 | // $4032 - disk status 1: |
| 335 | // bit0 - Disk flag (0: Disk inserted; 1: Disk not inserted) |
| 336 | // bit1 - Ready flag (0: Disk ready; 1: Disk not ready) |
| 337 | // bit2 - Protect flag (0: Not write protected; 1: Write protected or disk ejected) |
| 338 | if (!m_fds_data) |
| 339 | ret = 1; |
| 340 | else if (m_fds_last_side != m_fds_current_side) |
| 341 | { |
| 342 | // If we've switched disks, report "no disk" for a few reads |
| 343 | ret = 1; |
| 344 | m_fds_count++; |
| 345 | if (m_fds_count == 50) |
| 346 | { |
| 347 | m_fds_last_side = m_fds_current_side; |
| 348 | m_fds_count = 0; |
| 349 | } |
| 350 | } |
| 351 | else |
| 352 | ret = (m_fds_current_side == 0) ? 1 : 0; // 0 if a disk is inserted |
| 353 | break; |
| 354 | case 0x13: |
| 355 | // $4033 - external connector (bits 0-6) + battery status (bit 7) |
| 356 | ret = 0x80; |
| 357 | break; |
| 358 | case 0x70: // $4090 - Volume gain - write through $4080 |
| 359 | case 0x72: // $4092 - Mod gain - read through $4084 |
| 360 | default: |
| 361 | ret = 0x00; |
| 362 | break; |
| 363 | } |
| 364 | |
| 365 | return ret; |
| 366 | } |
| 367 | |
| 368 | //------------------------------------------------- |
| 369 | // device_timer - handler timer events |
| 370 | //------------------------------------------------- |
| 371 | |
| 372 | void nes_disksys_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) |
| 373 | { |
| 374 | if (id == TIMER_IRQ) |
| 375 | { |
| 376 | if (m_irq_enable && m_irq_count) |
| 377 | { |
| 378 | m_irq_count--; |
| 379 | if (!m_irq_count) |
| 380 | { |
| 381 | machine().device("maincpu")->execute().set_input_line(M6502_IRQ_LINE, HOLD_LINE); |
| 382 | m_irq_enable = 0; |
| 383 | m_fds_status0 |= 0x01; |
| 384 | m_irq_count_latch = 0; // used in Kaettekita Mario Bros |
| 385 | } |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | |
| 391 | // Hacky helper to allow user to switch disk side with a simple key |
| 392 | |
| 393 | void nes_disksys_device::disk_flip_side() |
| 394 | { |
| 395 | m_fds_current_side++; |
| 396 | if (m_fds_current_side > m_fds_sides) |
| 397 | m_fds_current_side = 0; |
| 398 | |
| 399 | if (m_fds_current_side == 0) |
| 400 | popmessage("No disk inserted."); |
| 401 | else |
| 402 | popmessage("Disk set to side %d", m_fds_current_side); |
| 403 | } |
| 404 | |
| 405 | |
| 406 | |
| 407 | // Disk Loading / Unloading |
| 408 | |
| 409 | void nes_disksys_device::load_disk(device_image_interface &image) |
| 410 | { |
| 411 | int header = 0; |
| 412 | m_fds_sides = 0; |
| 413 | |
| 414 | if (image.length() % 65500) |
| 415 | header = 0x10; |
| 416 | |
| 417 | m_fds_sides = (image.length() - header) / 65500; |
| 418 | |
| 419 | if (!m_fds_data) |
| 420 | m_fds_data = auto_alloc_array(machine(), UINT8, m_fds_sides * 65500); |
| 421 | |
| 422 | // if there is an header, skip it |
| 423 | image.fseek(header, SEEK_SET); |
| 424 | image.fread(m_fds_data, 65500 * m_fds_sides); |
| 425 | return; |
| 426 | } |
| 427 | |
| 428 | void nes_disksys_device::unload_disk(device_image_interface &image) |
| 429 | { |
| 430 | /* TODO: should write out changes here as well */ |
| 431 | m_fds_sides = 0; |
| 432 | } |
| 433 | |