Files
blueMSX-Core/Core/Memory/romMapperMsxAudio.c

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;
}