552 lines
14 KiB
C
Executable File
552 lines
14 KiB
C
Executable File
/*****************************************************************************
|
|
** $Source: /cygdrive/d/Private/_SVNROOT/bluemsx/blueMSX/Src/Memory/romMapperMsxAudio.c,v $
|
|
**
|
|
** $Revision: 1.20 $
|
|
**
|
|
** $Date: 2009-07-18 14:35:59 $
|
|
**
|
|
** More info: http://www.bluemsx.com
|
|
**
|
|
** Copyright (C) 2003-2006 Daniel Vik
|
|
**
|
|
** 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 2 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, write to the Free Software
|
|
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
**
|
|
******************************************************************************
|
|
*/
|
|
#include "romMapperMsxAudio.h"
|
|
#include "MediaDb.h"
|
|
#include "Switches.h"
|
|
#include "SlotManager.h"
|
|
#include "DeviceManager.h"
|
|
#include "DebugDeviceManager.h"
|
|
#include "IoPort.h"
|
|
#include "Board.h"
|
|
#include "Y8950.h"
|
|
#include "MSXMidi.h"
|
|
#include "MidiIO.h"
|
|
#include "SaveState.h"
|
|
#include "Language.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "ArchEvent.h"
|
|
|
|
#define RX_QUEUE_SIZE 256
|
|
|
|
typedef struct {
|
|
MidiIO* midiIo;
|
|
UInt8 command;
|
|
UInt8 rxData;
|
|
UInt8 status;
|
|
UInt8 txBuffer;
|
|
int txPending;
|
|
UInt8 rxQueue[RX_QUEUE_SIZE];
|
|
int rxPending;
|
|
int rxHead;
|
|
void* semaphore;
|
|
UInt32 charTime;
|
|
BoardTimer* timerRecv;
|
|
UInt32 timeRecv;
|
|
BoardTimer* timerTrans;
|
|
UInt32 timeTrans;
|
|
} PhilipsMidi;
|
|
|
|
#define STAT_RXRDY 0x01
|
|
#define STAT_TXEMPTY 0x02
|
|
#define STAT_OE 0x20
|
|
#define ST_INT 0x80
|
|
|
|
#define CMD_WRINT 0x20
|
|
#define CMD_RDINT 0x80
|
|
|
|
static void midiInCallback(PhilipsMidi* midi, UInt8* buffer, UInt32 length)
|
|
{
|
|
archSemaphoreWait(midi->semaphore, -1);
|
|
if (midi->rxPending + length < RX_QUEUE_SIZE) {
|
|
while (length--) {
|
|
midi->rxQueue[midi->rxHead & (RX_QUEUE_SIZE - 1)] = *buffer++;
|
|
midi->rxHead++;
|
|
midi->rxPending++;
|
|
}
|
|
}
|
|
archSemaphoreSignal(midi->semaphore);
|
|
}
|
|
|
|
static void onRecv(PhilipsMidi* midi, UInt32 time)
|
|
{
|
|
midi->timeRecv = 0;
|
|
|
|
if (midi->status & STAT_RXRDY) {
|
|
midi->status |= STAT_OE;
|
|
}
|
|
else if (midi->rxPending != 0) {
|
|
archSemaphoreWait(midi->semaphore, -1);
|
|
midi->rxData = midi->rxQueue[(midi->rxHead - midi->rxPending) & (RX_QUEUE_SIZE - 1)];
|
|
midi->rxPending--;
|
|
archSemaphoreSignal(midi->semaphore);
|
|
midi->status |= STAT_RXRDY;
|
|
if (midi->command & CMD_RDINT) {
|
|
boardSetInt(0x400);
|
|
midi->status |= ST_INT;
|
|
}
|
|
}
|
|
|
|
midi->timeRecv = boardSystemTime() + midi->charTime;
|
|
boardTimerAdd(midi->timerRecv, midi->timeRecv);
|
|
}
|
|
|
|
static void onTrans(PhilipsMidi* midi, UInt32 time)
|
|
{
|
|
midi->timeTrans = 0;
|
|
|
|
if (midi->status & STAT_TXEMPTY) {
|
|
midi->txPending = 0;
|
|
}
|
|
else {
|
|
midiIoTransmit(midi->midiIo, midi->txBuffer);
|
|
midi->timeTrans = boardSystemTime() + midi->charTime;
|
|
boardTimerAdd(midi->timerTrans, midi->timeTrans);
|
|
|
|
midi->status |= STAT_TXEMPTY;
|
|
if (midi->command & CMD_WRINT) {
|
|
boardSetInt(0x400);
|
|
midi->status |= ST_INT;
|
|
}
|
|
}
|
|
}
|
|
|
|
void philipsMidiReset(PhilipsMidi* midi)
|
|
{
|
|
midi->status = STAT_TXEMPTY;
|
|
midi->txPending = 0;
|
|
midi->rxPending = 0;
|
|
midi->command = 0;
|
|
midi->timeRecv = 0;
|
|
midi->timeTrans = 0;
|
|
midi->charTime = 10 * boardFrequency() / 31250;
|
|
|
|
boardTimerRemove(midi->timerRecv);
|
|
boardTimerRemove(midi->timerTrans);
|
|
|
|
midi->timeRecv = boardSystemTime() + midi->charTime;
|
|
boardTimerAdd(midi->timerRecv, midi->timeRecv);
|
|
}
|
|
|
|
PhilipsMidi* philipsMidiCreate()
|
|
{
|
|
PhilipsMidi* midi = (PhilipsMidi*)calloc(1, sizeof(PhilipsMidi));
|
|
|
|
midi->midiIo = midiIoCreate(midiInCallback, midi);
|
|
midi->semaphore = archSemaphoreCreate(1);
|
|
midi->timerRecv = boardTimerCreate(onRecv, midi);
|
|
midi->timerTrans = boardTimerCreate(onTrans, midi);
|
|
|
|
return midi;
|
|
}
|
|
|
|
void philipsMidiDestroy(PhilipsMidi* midi)
|
|
{
|
|
boardTimerDestroy(midi->timerTrans);
|
|
boardTimerDestroy(midi->timerRecv);
|
|
midiIoDestroy(midi->midiIo);
|
|
archSemaphoreDestroy(midi->semaphore);
|
|
free(midi);
|
|
}
|
|
|
|
UInt8 philipsMidiReadStatus(PhilipsMidi* midi)
|
|
{
|
|
UInt8 val = midi->status;
|
|
|
|
boardClearInt(0x400);
|
|
midi->status &= ~ST_INT;
|
|
|
|
return val;
|
|
}
|
|
|
|
UInt8 philipsMidiReadData(PhilipsMidi* midi)
|
|
{
|
|
midi->status &= ~(STAT_RXRDY | STAT_OE);
|
|
return midi->rxData;
|
|
}
|
|
|
|
void philipsMidiWriteCommand(PhilipsMidi* midi, UInt8 value)
|
|
{
|
|
int baudrate = 1;
|
|
int dataBits = 8;
|
|
int parityEnable = 0;
|
|
int stopBits = 1;
|
|
UInt64 charLength;
|
|
|
|
midi->command = value;
|
|
|
|
switch (value & 0x03) {
|
|
case 0:
|
|
baudrate = 1;
|
|
break;
|
|
case 1:
|
|
baudrate = 16;
|
|
break;
|
|
case 2:
|
|
baudrate = 64;
|
|
break;
|
|
case 3:
|
|
philipsMidiReset(midi);
|
|
break;
|
|
}
|
|
|
|
switch (value & 0x1c) {
|
|
case 0:
|
|
dataBits = 7;
|
|
parityEnable = 1;
|
|
stopBits = 2;
|
|
break;
|
|
case 1:
|
|
dataBits = 7;
|
|
parityEnable = 1;
|
|
stopBits = 2;
|
|
break;
|
|
case 2:
|
|
dataBits = 7;
|
|
parityEnable = 1;
|
|
stopBits = 1;
|
|
break;
|
|
case 3:
|
|
dataBits = 7;
|
|
parityEnable = 1;
|
|
stopBits = 1;
|
|
break;
|
|
case 4:
|
|
dataBits = 8;
|
|
parityEnable = 0;
|
|
stopBits = 2;
|
|
break;
|
|
case 5:
|
|
dataBits = 8;
|
|
parityEnable = 0;
|
|
stopBits = 1;
|
|
break;
|
|
case 6:
|
|
dataBits = 8;
|
|
parityEnable = 0;
|
|
stopBits = 1;
|
|
break;
|
|
case 7:
|
|
dataBits = 8;
|
|
parityEnable = 1;
|
|
stopBits = 1;
|
|
break;
|
|
}
|
|
|
|
charLength = (dataBits + parityEnable + stopBits) * baudrate;
|
|
midi->charTime = (UInt32)(charLength * boardFrequency() / 500000);
|
|
|
|
midi->timeRecv = boardSystemTime() + midi->charTime;
|
|
boardTimerAdd(midi->timerRecv, midi->timeRecv);
|
|
}
|
|
|
|
void philipsMidiWriteData(PhilipsMidi* midi, UInt8 value)
|
|
{
|
|
if (!(midi->status & STAT_TXEMPTY)) {
|
|
return;
|
|
}
|
|
|
|
if (midi->txPending == 0) {
|
|
midiIoTransmit(midi->midiIo, value);
|
|
midi->timeTrans = boardSystemTime() + midi->charTime;
|
|
boardTimerAdd(midi->timerTrans, midi->timeTrans);
|
|
midi->txPending = 1;
|
|
}
|
|
else {
|
|
midi->status &= ~STAT_TXEMPTY;
|
|
midi->txBuffer = value;
|
|
}
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
int deviceHandle;
|
|
int debugHandle;
|
|
Y8950* y8950;
|
|
int ioBase;
|
|
UInt8* romData;
|
|
UInt8 ram[0x1000];
|
|
int bankSelect;
|
|
int sizeMask;
|
|
|
|
PhilipsMidi* midi;
|
|
|
|
int slot;
|
|
int sslot;
|
|
int startPage;
|
|
int is_fs_ca1;
|
|
} RomMapperMsxAudio;
|
|
|
|
|
|
static int deviceCount = 0;
|
|
|
|
static void saveState(RomMapperMsxAudio* rm)
|
|
{
|
|
SaveState* state = saveStateOpenForWrite("mapperMsxAudio");
|
|
|
|
saveStateSet(state, "bankSelect", rm->bankSelect);
|
|
saveStateSetBuffer(state, "ram", rm->ram, sizeof(rm->ram));
|
|
|
|
saveStateClose(state);
|
|
|
|
if (rm->y8950 != NULL) {
|
|
y8950SaveState(rm->y8950);
|
|
}
|
|
}
|
|
|
|
static void loadState(RomMapperMsxAudio* rm)
|
|
{
|
|
SaveState* state = saveStateOpenForRead("mapperMsxAudio");
|
|
|
|
rm->bankSelect = saveStateGet(state, "bankSelect", 0);
|
|
saveStateGetBuffer(state, "ram", rm->ram, sizeof(rm->ram));
|
|
|
|
saveStateClose(state);
|
|
|
|
if (rm->y8950 != NULL) {
|
|
y8950LoadState(rm->y8950);
|
|
}
|
|
}
|
|
|
|
static void destroy(RomMapperMsxAudio* rm)
|
|
{
|
|
if (rm->midi) {
|
|
philipsMidiDestroy(rm->midi);
|
|
}
|
|
|
|
ioPortUnregister(0x00);
|
|
ioPortUnregister(0x01);
|
|
ioPortUnregister(0x04);
|
|
ioPortUnregister(0x05);
|
|
|
|
ioPortUnregister(rm->ioBase + 0);
|
|
ioPortUnregister(rm->ioBase + 1);
|
|
|
|
if (rm->y8950) {
|
|
if (ioPortGetRef(0xc0)==rm->y8950&&ioPortGetRef(0xc1)==rm->y8950) {
|
|
ioPortUnregister(0xc0); ioPortUnregister(0xc1);
|
|
}
|
|
if (ioPortGetRef(0xc2)==rm->y8950&&ioPortGetRef(0xc3)==rm->y8950) {
|
|
ioPortUnregister(0xc2); ioPortUnregister(0xc3);
|
|
}
|
|
}
|
|
|
|
deviceCount--;
|
|
|
|
if (rm->y8950 != NULL) {
|
|
y8950Destroy(rm->y8950);
|
|
}
|
|
|
|
if (rm->sizeMask != -1) {
|
|
slotUnregister(rm->slot, rm->sslot, rm->startPage);
|
|
}
|
|
|
|
debugDeviceUnregister(rm->debugHandle);
|
|
deviceManagerUnregister(rm->deviceHandle);
|
|
|
|
if (rm->romData != NULL) {
|
|
free(rm->romData);
|
|
}
|
|
free(rm);
|
|
}
|
|
|
|
static UInt8 read(RomMapperMsxAudio* rm, UInt16 address)
|
|
{
|
|
if (rm->bankSelect == 0 && (address & 0x3fff) >= 0x3000) {
|
|
return rm->ram[(address & 0x3fff) - 0x3000];
|
|
}
|
|
|
|
return rm->romData[(0x8000 * rm->bankSelect + (address & 0x7fff)) & rm->sizeMask];
|
|
}
|
|
|
|
static void write(RomMapperMsxAudio* rm, UInt16 address, UInt8 value)
|
|
{
|
|
address &= 0x7fff;
|
|
|
|
#if 0
|
|
// FS-CA1 port select, bit 0:c0/c1, bit 1:c2/c3
|
|
if (rm->is_fs_ca1&&address==0x7fff&&rm->y8950) {
|
|
if (value&1) {
|
|
ioPortRegister(0xc0, y8950Read, y8950Write, rm->y8950);
|
|
ioPortRegister(0xc1, y8950Read, y8950Write, rm->y8950);
|
|
}
|
|
else if (ioPortGetRef(0xc0)==rm->y8950&&ioPortGetRef(0xc1)==rm->y8950) {
|
|
ioPortUnregister(0xc0); ioPortUnregister(0xc1);
|
|
}
|
|
|
|
if (value&2) {
|
|
ioPortRegister(0xc2, y8950Read, y8950Write, rm->y8950);
|
|
ioPortRegister(0xc3, y8950Read, y8950Write, rm->y8950);
|
|
}
|
|
else if (ioPortGetRef(0xc2)==rm->y8950&&ioPortGetRef(0xc3)==rm->y8950) {
|
|
ioPortUnregister(0xc2); ioPortUnregister(0xc3);
|
|
}
|
|
}
|
|
#endif
|
|
// bankswitch
|
|
if (address==0x7ffe) rm->bankSelect = value & 3;
|
|
|
|
address &= 0x3fff;
|
|
if (rm->bankSelect == 0 && address >= 0x3000) {
|
|
rm->ram[address - 0x3000] = value;
|
|
}
|
|
}
|
|
|
|
|
|
static void reset(RomMapperMsxAudio* rm)
|
|
{
|
|
if (rm->y8950 != NULL) {
|
|
y8950Reset(rm->y8950);
|
|
}
|
|
|
|
if (rm->midi) {
|
|
philipsMidiReset(rm->midi);
|
|
}
|
|
|
|
// FS-CA1
|
|
write (rm,0x7ffe,0);
|
|
write (rm,0x7fff,0);
|
|
}
|
|
|
|
|
|
static void midiWrite(RomMapperMsxAudio* rm, UInt16 ioPort, UInt8 value)
|
|
{
|
|
if (!rm->midi) {
|
|
return;
|
|
}
|
|
|
|
switch (ioPort & 1) {
|
|
case 0x00:
|
|
philipsMidiWriteCommand(rm->midi, value);
|
|
break;
|
|
case 0x01:
|
|
philipsMidiWriteData(rm->midi, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static UInt8 midiRead(RomMapperMsxAudio* rm, UInt16 ioPort)
|
|
{
|
|
if (!rm->midi) {
|
|
return 0xff;
|
|
}
|
|
|
|
switch (ioPort & 1) {
|
|
case 0x00:
|
|
return philipsMidiReadStatus(rm->midi);
|
|
case 0x01:
|
|
return philipsMidiReadData(rm->midi);
|
|
}
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
static void getDebugInfo(RomMapperMsxAudio* rm, DbgDevice* dbgDevice)
|
|
{
|
|
DbgIoPorts* ioPorts;
|
|
|
|
if (rm->y8950 == NULL) {
|
|
return;
|
|
}
|
|
|
|
ioPorts = dbgDeviceAddIoPorts(dbgDevice, langDbgDevMsxAudio(), 2);
|
|
dbgIoPortsAddPort(ioPorts, 0, rm->ioBase + 0, DBG_IO_READWRITE, y8950Peek(rm->y8950, 0));
|
|
dbgIoPortsAddPort(ioPorts, 1, rm->ioBase + 1, DBG_IO_READWRITE, y8950Peek(rm->y8950, 1));
|
|
|
|
ioPorts = dbgDeviceAddIoPorts(dbgDevice, langDbgDevMsxAudioMidi(), 4);
|
|
dbgIoPortsAddPort(ioPorts, 0, 0x00, DBG_IO_WRITE, 0);
|
|
dbgIoPortsAddPort(ioPorts, 1, 0x01, DBG_IO_WRITE, 0);
|
|
dbgIoPortsAddPort(ioPorts, 2, 0x04, DBG_IO_READ, midiRead(rm, 0x04));
|
|
dbgIoPortsAddPort(ioPorts, 3, 0x05, DBG_IO_READ, midiRead(rm, 0x05));
|
|
|
|
y8950GetDebugInfo(rm->y8950, dbgDevice);
|
|
}
|
|
|
|
int romMapperMsxAudioCreate(const char* filename, UInt8* romData,
|
|
int size, int slot, int sslot, int startPage)
|
|
{
|
|
DeviceCallbacks callbacks = { destroy, reset, saveState, loadState };
|
|
DebugCallbacks dbgCallbacks = { getDebugInfo, NULL, NULL, NULL };
|
|
RomMapperMsxAudio* rm;
|
|
int i;
|
|
|
|
rm = malloc(sizeof(RomMapperMsxAudio));
|
|
|
|
rm->deviceHandle = deviceManagerRegister(ROM_MSXAUDIO, &callbacks, rm);
|
|
rm->debugHandle = debugDeviceRegister(DBGTYPE_AUDIO, langDbgDevMsxAudio(), &dbgCallbacks, rm);
|
|
|
|
rm->ioBase = 0xc0 + deviceCount++ * 2;
|
|
|
|
rm->romData = NULL;
|
|
|
|
if (size > 0) {
|
|
int pages=8;
|
|
rm->is_fs_ca1 = (size == 0x20000); // meh
|
|
// pages=rm->is_fs_ca1?4:8;
|
|
|
|
// For FS-CA1, $8000-$FFFF is unmapped
|
|
// firmware locks up, needs more testing
|
|
slotRegister(slot, sslot, startPage, pages, read, read, write, destroy, rm);
|
|
|
|
rm->romData = malloc(size);
|
|
memcpy(rm->romData, romData, size);
|
|
memset(rm->ram, 0, 0x1000);
|
|
rm->bankSelect = 0;
|
|
rm->sizeMask = size - 1;
|
|
rm->slot = slot;
|
|
rm->sslot = sslot;
|
|
rm->startPage = startPage;
|
|
rm->midi = NULL;
|
|
|
|
if (!switchGetAudio()) {
|
|
// FS-CA1 BIOS hack, ret z -> nop
|
|
// not needed if port select register is emulated
|
|
rm->romData[0x408e] = 0;
|
|
}
|
|
|
|
for (i = 0; i < pages; i++) {
|
|
slotMapPage(rm->slot, rm->sslot, rm->startPage + i, NULL, 0, 0);
|
|
}
|
|
}
|
|
|
|
rm->y8950 = NULL;
|
|
|
|
if (boardGetY8950Enable()) {
|
|
rm->y8950 = y8950Create(boardGetMixer());
|
|
|
|
ioPortRegister(rm->ioBase + 0, y8950Read, y8950Write, rm->y8950);
|
|
ioPortRegister(rm->ioBase + 1, y8950Read, y8950Write, rm->y8950);
|
|
|
|
ioPortRegister(0x00, NULL, midiWrite, rm);
|
|
ioPortRegister(0x01, NULL, midiWrite, rm);
|
|
ioPortRegister(0x04, midiRead, NULL, rm);
|
|
ioPortRegister(0x05, midiRead, NULL, rm);
|
|
}
|
|
|
|
if (deviceCount == 1) {
|
|
rm->midi = philipsMidiCreate();
|
|
}
|
|
|
|
reset(rm);
|
|
|
|
return 1;
|
|
}
|
|
|