mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-07 05:03:32 +01:00
c3344eaa88
* Changed the SI buffer processing so that transfers do not have to be completed instantly * Added a second socket at port 49420 (0xc10c) which sends clock information to the GBA slaves * Handled disconnections from the GBA and GC * Made the transfers asynchronous * Blocks the socket before the connection times out Requires VBA-M SVN 1235 or later.
592 lines
16 KiB
C++
592 lines
16 KiB
C++
// Copyright 2013 Dolphin Emulator Project
|
|
// Licensed under GPLv2
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Common/ChunkFile.h"
|
|
#include "Common/CommonTypes.h"
|
|
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/Movie.h"
|
|
#include "Core/NetPlayProto.h"
|
|
#include "Core/HW/MMIO.h"
|
|
#include "Core/HW/ProcessorInterface.h"
|
|
#include "Core/HW/SI.h"
|
|
#include "Core/HW/SI_DeviceGBA.h"
|
|
#include "Core/HW/SystemTimers.h"
|
|
#include "Core/HW/VideoInterface.h"
|
|
|
|
|
|
namespace SerialInterface
|
|
{
|
|
|
|
static int changeDevice;
|
|
static int et_transfer_pending;
|
|
|
|
void RunSIBuffer(u64 userdata, int cyclesLate);
|
|
void UpdateInterrupts();
|
|
|
|
// SI Interrupt Types
|
|
enum SIInterruptType
|
|
{
|
|
INT_RDSTINT = 0,
|
|
INT_TCINT = 1,
|
|
};
|
|
static void GenerateSIInterrupt(SIInterruptType _SIInterrupt);
|
|
|
|
// SI Internal Hardware Addresses
|
|
enum
|
|
{
|
|
SI_CHANNEL_0_OUT = 0x00,
|
|
SI_CHANNEL_0_IN_HI = 0x04,
|
|
SI_CHANNEL_0_IN_LO = 0x08,
|
|
SI_CHANNEL_1_OUT = 0x0C,
|
|
SI_CHANNEL_1_IN_HI = 0x10,
|
|
SI_CHANNEL_1_IN_LO = 0x14,
|
|
SI_CHANNEL_2_OUT = 0x18,
|
|
SI_CHANNEL_2_IN_HI = 0x1C,
|
|
SI_CHANNEL_2_IN_LO = 0x20,
|
|
SI_CHANNEL_3_OUT = 0x24,
|
|
SI_CHANNEL_3_IN_HI = 0x28,
|
|
SI_CHANNEL_3_IN_LO = 0x2C,
|
|
SI_POLL = 0x30,
|
|
SI_COM_CSR = 0x34,
|
|
SI_STATUS_REG = 0x38,
|
|
SI_EXI_CLOCK_COUNT = 0x3C,
|
|
};
|
|
|
|
// SI Channel Output
|
|
union USIChannelOut
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 OUTPUT1 : 8;
|
|
u32 OUTPUT0 : 8;
|
|
u32 CMD : 8;
|
|
u32 : 8;
|
|
};
|
|
};
|
|
|
|
// SI Channel Input High u32
|
|
union USIChannelIn_Hi
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 INPUT3 : 8;
|
|
u32 INPUT2 : 8;
|
|
u32 INPUT1 : 8;
|
|
u32 INPUT0 : 6;
|
|
u32 ERRLATCH : 1; // 0: no error 1: Error latched. Check SISR.
|
|
u32 ERRSTAT : 1; // 0: no error 1: error on last transfer
|
|
};
|
|
};
|
|
|
|
// SI Channel Input Low u32
|
|
union USIChannelIn_Lo
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 INPUT7 : 8;
|
|
u32 INPUT6 : 8;
|
|
u32 INPUT5 : 8;
|
|
u32 INPUT4 : 8;
|
|
};
|
|
};
|
|
|
|
// SI Channel
|
|
struct SSIChannel
|
|
{
|
|
USIChannelOut m_Out;
|
|
USIChannelIn_Hi m_InHi;
|
|
USIChannelIn_Lo m_InLo;
|
|
ISIDevice* m_pDevice;
|
|
};
|
|
|
|
// SI Poll: Controls how often a device is polled
|
|
union USIPoll
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 VBCPY3 : 1; // 1: write to output buffer only on vblank
|
|
u32 VBCPY2 : 1;
|
|
u32 VBCPY1 : 1;
|
|
u32 VBCPY0 : 1;
|
|
u32 EN3 : 1; // Enable polling of channel
|
|
u32 EN2 : 1; // does not affect communication RAM transfers
|
|
u32 EN1 : 1;
|
|
u32 EN0 : 1;
|
|
u32 Y : 8; // Polls per frame
|
|
u32 X : 10; // Polls per X lines. begins at vsync, min 7, max depends on video mode
|
|
u32 : 6;
|
|
};
|
|
};
|
|
|
|
// SI Communication Control Status Register
|
|
union USIComCSR
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 TSTART : 1; // write: start transfer read: transfer status
|
|
u32 CHANNEL : 2; // determines which SI channel will be used on the communication interface.
|
|
u32 : 3;
|
|
u32 CALLBEN : 1; // Callback enable
|
|
u32 CMDEN : 1; // Command enable?
|
|
u32 INLNGTH : 7;
|
|
u32 : 1;
|
|
u32 OUTLNGTH : 7; // Communication Channel Output Length in bytes
|
|
u32 : 1;
|
|
u32 CHANEN : 1; // Channel enable?
|
|
u32 CHANNUM : 2; // Channel number?
|
|
u32 RDSTINTMSK : 1; // Read Status Interrupt Status Mask
|
|
u32 RDSTINT : 1; // Read Status Interrupt Status
|
|
u32 COMERR : 1; // Communication Error (set 0)
|
|
u32 TCINTMSK : 1; // Transfer Complete Interrupt Mask
|
|
u32 TCINT : 1; // Transfer Complete Interrupt
|
|
};
|
|
USIComCSR() {Hex = 0;}
|
|
USIComCSR(u32 _hex) {Hex = _hex;}
|
|
};
|
|
|
|
// SI Status Register
|
|
union USIStatusReg
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 UNRUN3 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
|
|
u32 OVRUN3 : 1; // (RWC) write 1: bit cleared read 1: overrun error
|
|
u32 COLL3 : 1; // (RWC) write 1: bit cleared read 1: collision error
|
|
u32 NOREP3 : 1; // (RWC) write 1: bit cleared read 1: response error
|
|
u32 WRST3 : 1; // (R) 1: buffer channel0 not copied
|
|
u32 RDST3 : 1; // (R) 1: new Data available
|
|
u32 : 2; // 7:6
|
|
u32 UNRUN2 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
|
|
u32 OVRUN2 : 1; // (RWC) write 1: bit cleared read 1: overrun error
|
|
u32 COLL2 : 1; // (RWC) write 1: bit cleared read 1: collision error
|
|
u32 NOREP2 : 1; // (RWC) write 1: bit cleared read 1: response error
|
|
u32 WRST2 : 1; // (R) 1: buffer channel0 not copied
|
|
u32 RDST2 : 1; // (R) 1: new Data available
|
|
u32 : 2; // 15:14
|
|
u32 UNRUN1 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
|
|
u32 OVRUN1 : 1; // (RWC) write 1: bit cleared read 1: overrun error
|
|
u32 COLL1 : 1; // (RWC) write 1: bit cleared read 1: collision error
|
|
u32 NOREP1 : 1; // (RWC) write 1: bit cleared read 1: response error
|
|
u32 WRST1 : 1; // (R) 1: buffer channel0 not copied
|
|
u32 RDST1 : 1; // (R) 1: new Data available
|
|
u32 : 2; // 23:22
|
|
u32 UNRUN0 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
|
|
u32 OVRUN0 : 1; // (RWC) write 1: bit cleared read 1: overrun error
|
|
u32 COLL0 : 1; // (RWC) write 1: bit cleared read 1: collision error
|
|
u32 NOREP0 : 1; // (RWC) write 1: bit cleared read 1: response error
|
|
u32 WRST0 : 1; // (R) 1: buffer channel0 not copied
|
|
u32 RDST0 : 1; // (R) 1: new Data available
|
|
u32 : 1;
|
|
u32 WR : 1; // (RW) write 1 start copy, read 0 copy done
|
|
};
|
|
USIStatusReg() {Hex = 0;}
|
|
USIStatusReg(u32 _hex) {Hex = _hex;}
|
|
};
|
|
|
|
// SI EXI Clock Count
|
|
union USIEXIClockCount
|
|
{
|
|
u32 Hex;
|
|
struct
|
|
{
|
|
u32 LOCK : 1; // 1: prevents CPU from setting EXI clock to 32MHz
|
|
u32 : 0;
|
|
};
|
|
};
|
|
|
|
// STATE_TO_SAVE
|
|
static SSIChannel g_Channel[MAX_SI_CHANNELS];
|
|
static USIPoll g_Poll;
|
|
static USIComCSR g_ComCSR;
|
|
static USIStatusReg g_StatusReg;
|
|
static USIEXIClockCount g_EXIClockCount;
|
|
static u8 g_SIBuffer[128];
|
|
|
|
void DoState(PointerWrap &p)
|
|
{
|
|
for (int i = 0; i < MAX_SI_CHANNELS; i++)
|
|
{
|
|
p.Do(g_Channel[i].m_InHi.Hex);
|
|
p.Do(g_Channel[i].m_InLo.Hex);
|
|
p.Do(g_Channel[i].m_Out.Hex);
|
|
|
|
ISIDevice* pDevice = g_Channel[i].m_pDevice;
|
|
SIDevices type = pDevice->GetDeviceType();
|
|
p.Do(type);
|
|
ISIDevice* pSaveDevice = (type == pDevice->GetDeviceType()) ? pDevice : SIDevice_Create(type, i);
|
|
pSaveDevice->DoState(p);
|
|
if (pSaveDevice != pDevice)
|
|
{
|
|
// if we had to create a temporary device, discard it if we're not loading.
|
|
// also, if no movie is active, we'll assume the user wants to keep their current devices
|
|
// instead of the ones they had when the savestate was created.
|
|
if (p.GetMode() != PointerWrap::MODE_READ || !Movie::IsMovieActive())
|
|
{
|
|
delete pSaveDevice;
|
|
}
|
|
else
|
|
{
|
|
AddDevice(pSaveDevice);
|
|
}
|
|
}
|
|
}
|
|
p.Do(g_Poll);
|
|
p.DoPOD(g_ComCSR);
|
|
p.DoPOD(g_StatusReg);
|
|
p.Do(g_EXIClockCount);
|
|
p.Do(g_SIBuffer);
|
|
}
|
|
|
|
|
|
void Init()
|
|
{
|
|
for (int i = 0; i < MAX_SI_CHANNELS; i++)
|
|
{
|
|
g_Channel[i].m_Out.Hex = 0;
|
|
g_Channel[i].m_InHi.Hex = 0;
|
|
g_Channel[i].m_InLo.Hex = 0;
|
|
|
|
if (Movie::IsMovieActive())
|
|
AddDevice(Movie::IsUsingPad(i) ? (Movie::IsUsingBongo(i) ? SIDEVICE_GC_TARUKONGA : SIDEVICE_GC_CONTROLLER) : SIDEVICE_NONE, i);
|
|
else if (!NetPlay::IsNetPlayRunning())
|
|
AddDevice(SConfig::GetInstance().m_SIDevice[i], i);
|
|
}
|
|
|
|
g_Poll.Hex = 0;
|
|
g_Poll.X = 7;
|
|
|
|
g_ComCSR.Hex = 0;
|
|
|
|
g_StatusReg.Hex = 0;
|
|
|
|
g_EXIClockCount.Hex = 0;
|
|
//g_EXIClockCount.LOCK = 1; // Supposedly set on reset, but logs from real Wii don't look like it is...
|
|
memset(g_SIBuffer, 0, 128);
|
|
|
|
changeDevice = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
|
|
et_transfer_pending = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);
|
|
}
|
|
|
|
void Shutdown()
|
|
{
|
|
for (int i = 0; i < MAX_SI_CHANNELS; i++)
|
|
RemoveDevice(i);
|
|
GBAConnectionWaiter_Shutdown();
|
|
}
|
|
|
|
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|
{
|
|
// Register SI buffer direct accesses.
|
|
for (int i = 0; i < 0x80; i += 4)
|
|
mmio->Register(base | (0x80 + i),
|
|
MMIO::DirectRead<u32>((u32*)&g_SIBuffer[i]),
|
|
MMIO::DirectWrite<u32>((u32*)&g_SIBuffer[i])
|
|
);
|
|
|
|
// In and out for the 4 SI channels.
|
|
for (int i = 0; i < MAX_SI_CHANNELS; ++i)
|
|
{
|
|
// We need to clear the RDST bit for the SI channel when reading.
|
|
// CH0 -> Bit 24 + 5
|
|
// CH1 -> Bit 16 + 5
|
|
// CH2 -> Bit 8 + 5
|
|
// CH3 -> Bit 0 + 5
|
|
int rdst_bit = 8 * (3 - i) + 5;
|
|
|
|
mmio->Register(base | (SI_CHANNEL_0_OUT + 0xC * i),
|
|
MMIO::DirectRead<u32>(&g_Channel[i].m_Out.Hex),
|
|
MMIO::DirectWrite<u32>(&g_Channel[i].m_Out.Hex)
|
|
);
|
|
mmio->Register(base | (SI_CHANNEL_0_IN_HI + 0xC * i),
|
|
MMIO::ComplexRead<u32>([i, rdst_bit](u32) {
|
|
g_StatusReg.Hex &= ~(1 << rdst_bit);
|
|
UpdateInterrupts();
|
|
return g_Channel[i].m_InHi.Hex;
|
|
}),
|
|
MMIO::DirectWrite<u32>(&g_Channel[i].m_InHi.Hex)
|
|
);
|
|
mmio->Register(base | (SI_CHANNEL_0_IN_LO + 0xC * i),
|
|
MMIO::ComplexRead<u32>([i, rdst_bit](u32) {
|
|
g_StatusReg.Hex &= ~(1 << rdst_bit);
|
|
UpdateInterrupts();
|
|
return g_Channel[i].m_InLo.Hex;
|
|
}),
|
|
MMIO::DirectWrite<u32>(&g_Channel[i].m_InLo.Hex)
|
|
);
|
|
}
|
|
|
|
mmio->Register(base | SI_POLL,
|
|
MMIO::DirectRead<u32>(&g_Poll.Hex),
|
|
MMIO::DirectWrite<u32>(&g_Poll.Hex)
|
|
);
|
|
|
|
mmio->Register(base | SI_COM_CSR,
|
|
MMIO::DirectRead<u32>(&g_ComCSR.Hex),
|
|
MMIO::ComplexWrite<u32>([](u32, u32 val) {
|
|
USIComCSR tmpComCSR(val);
|
|
|
|
g_ComCSR.CHANNEL = tmpComCSR.CHANNEL;
|
|
g_ComCSR.INLNGTH = tmpComCSR.INLNGTH;
|
|
g_ComCSR.OUTLNGTH = tmpComCSR.OUTLNGTH;
|
|
g_ComCSR.RDSTINTMSK = tmpComCSR.RDSTINTMSK;
|
|
g_ComCSR.TCINTMSK = tmpComCSR.TCINTMSK;
|
|
|
|
g_ComCSR.COMERR = 0;
|
|
|
|
if (tmpComCSR.RDSTINT) g_ComCSR.RDSTINT = 0;
|
|
if (tmpComCSR.TCINT) g_ComCSR.TCINT = 0;
|
|
|
|
// be careful: run si-buffer after updating the INT flags
|
|
if (tmpComCSR.TSTART)
|
|
{
|
|
g_ComCSR.TSTART = 1;
|
|
RunSIBuffer(0, 0);
|
|
}
|
|
else if (g_ComCSR.TSTART)
|
|
{
|
|
CoreTiming::RemoveEvent(et_transfer_pending);
|
|
}
|
|
|
|
if (!g_ComCSR.TSTART)
|
|
UpdateInterrupts();
|
|
})
|
|
);
|
|
|
|
mmio->Register(base | SI_STATUS_REG,
|
|
MMIO::DirectRead<u32>(&g_StatusReg.Hex),
|
|
MMIO::ComplexWrite<u32>([](u32, u32 val) {
|
|
USIStatusReg tmpStatus(val);
|
|
|
|
// clear bits ( if (tmp.bit) SISR.bit=0 )
|
|
if (tmpStatus.NOREP0) g_StatusReg.NOREP0 = 0;
|
|
if (tmpStatus.COLL0) g_StatusReg.COLL0 = 0;
|
|
if (tmpStatus.OVRUN0) g_StatusReg.OVRUN0 = 0;
|
|
if (tmpStatus.UNRUN0) g_StatusReg.UNRUN0 = 0;
|
|
|
|
if (tmpStatus.NOREP1) g_StatusReg.NOREP1 = 0;
|
|
if (tmpStatus.COLL1) g_StatusReg.COLL1 = 0;
|
|
if (tmpStatus.OVRUN1) g_StatusReg.OVRUN1 = 0;
|
|
if (tmpStatus.UNRUN1) g_StatusReg.UNRUN1 = 0;
|
|
|
|
if (tmpStatus.NOREP2) g_StatusReg.NOREP2 = 0;
|
|
if (tmpStatus.COLL2) g_StatusReg.COLL2 = 0;
|
|
if (tmpStatus.OVRUN2) g_StatusReg.OVRUN2 = 0;
|
|
if (tmpStatus.UNRUN2) g_StatusReg.UNRUN2 = 0;
|
|
|
|
if (tmpStatus.NOREP3) g_StatusReg.NOREP3 = 0;
|
|
if (tmpStatus.COLL3) g_StatusReg.COLL3 = 0;
|
|
if (tmpStatus.OVRUN3) g_StatusReg.OVRUN3 = 0;
|
|
if (tmpStatus.UNRUN3) g_StatusReg.UNRUN3 = 0;
|
|
|
|
// send command to devices
|
|
if (tmpStatus.WR)
|
|
{
|
|
g_Channel[0].m_pDevice->SendCommand(g_Channel[0].m_Out.Hex, g_Poll.EN0);
|
|
g_Channel[1].m_pDevice->SendCommand(g_Channel[1].m_Out.Hex, g_Poll.EN1);
|
|
g_Channel[2].m_pDevice->SendCommand(g_Channel[2].m_Out.Hex, g_Poll.EN2);
|
|
g_Channel[3].m_pDevice->SendCommand(g_Channel[3].m_Out.Hex, g_Poll.EN3);
|
|
|
|
g_StatusReg.WR = 0;
|
|
g_StatusReg.WRST0 = 0;
|
|
g_StatusReg.WRST1 = 0;
|
|
g_StatusReg.WRST2 = 0;
|
|
g_StatusReg.WRST3 = 0;
|
|
}
|
|
})
|
|
);
|
|
|
|
mmio->Register(base | SI_EXI_CLOCK_COUNT,
|
|
MMIO::DirectRead<u32>(&g_EXIClockCount.Hex),
|
|
MMIO::DirectWrite<u32>(&g_EXIClockCount.Hex)
|
|
);
|
|
}
|
|
|
|
void UpdateInterrupts()
|
|
{
|
|
// check if we have to update the RDSTINT flag
|
|
if (g_StatusReg.RDST0 || g_StatusReg.RDST1 ||
|
|
g_StatusReg.RDST2 || g_StatusReg.RDST3)
|
|
g_ComCSR.RDSTINT = 1;
|
|
else
|
|
g_ComCSR.RDSTINT = 0;
|
|
|
|
// check if we have to generate an interrupt
|
|
if ((g_ComCSR.RDSTINT & g_ComCSR.RDSTINTMSK) ||
|
|
(g_ComCSR.TCINT & g_ComCSR.TCINTMSK))
|
|
{
|
|
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_SI, true);
|
|
}
|
|
else
|
|
{
|
|
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_SI, false);
|
|
}
|
|
}
|
|
|
|
void GenerateSIInterrupt(SIInterruptType _SIInterrupt)
|
|
{
|
|
switch (_SIInterrupt)
|
|
{
|
|
case INT_RDSTINT: g_ComCSR.RDSTINT = 1; break;
|
|
case INT_TCINT: g_ComCSR.TCINT = 1; break;
|
|
}
|
|
|
|
UpdateInterrupts();
|
|
}
|
|
|
|
void RemoveDevice(int _iDeviceNumber)
|
|
{
|
|
delete g_Channel[_iDeviceNumber].m_pDevice;
|
|
g_Channel[_iDeviceNumber].m_pDevice = nullptr;
|
|
}
|
|
|
|
void AddDevice(ISIDevice* pDevice)
|
|
{
|
|
int _iDeviceNumber = pDevice->GetDeviceNumber();
|
|
|
|
//_dbg_assert_(SERIALINTERFACE, _iDeviceNumber < MAX_SI_CHANNELS);
|
|
|
|
// delete the old device
|
|
RemoveDevice(_iDeviceNumber);
|
|
|
|
// create the new one
|
|
g_Channel[_iDeviceNumber].m_pDevice = pDevice;
|
|
}
|
|
|
|
void AddDevice(const SIDevices _device, int _iDeviceNumber)
|
|
{
|
|
ISIDevice *pDevice = SIDevice_Create(_device, _iDeviceNumber);
|
|
AddDevice(pDevice);
|
|
}
|
|
|
|
static void SetNoResponse(u32 channel)
|
|
{
|
|
// raise the NO RESPONSE error
|
|
switch (channel)
|
|
{
|
|
case 0: g_StatusReg.NOREP0 = 1; break;
|
|
case 1: g_StatusReg.NOREP1 = 1; break;
|
|
case 2: g_StatusReg.NOREP2 = 1; break;
|
|
case 3: g_StatusReg.NOREP3 = 1; break;
|
|
}
|
|
g_ComCSR.COMERR = 1;
|
|
}
|
|
|
|
void ChangeDeviceCallback(u64 userdata, int cyclesLate)
|
|
{
|
|
u8 channel = (u8)(userdata >> 32);
|
|
SIDevices device = (SIDevices)(u32)userdata;
|
|
|
|
// Skip redundant (spammed) device changes
|
|
if (GetDeviceType(channel) != device)
|
|
{
|
|
g_Channel[channel].m_Out.Hex = 0;
|
|
g_Channel[channel].m_InHi.Hex = 0;
|
|
g_Channel[channel].m_InLo.Hex = 0;
|
|
|
|
SetNoResponse(channel);
|
|
|
|
AddDevice(device, channel);
|
|
}
|
|
}
|
|
|
|
void ChangeDevice(SIDevices device, int channel)
|
|
{
|
|
// Called from GUI, so we need to make it thread safe.
|
|
// Let the hardware see no device for .5b cycles
|
|
// TODO: Calling GetDeviceType here isn't threadsafe.
|
|
if (GetDeviceType(channel) != device)
|
|
{
|
|
CoreTiming::ScheduleEvent_Threadsafe(0, changeDevice, ((u64)channel << 32) | SIDEVICE_NONE);
|
|
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | device);
|
|
}
|
|
}
|
|
|
|
void UpdateDevices()
|
|
{
|
|
// Update channels and set the status bit if there's new data
|
|
g_StatusReg.RDST0 = !!g_Channel[0].m_pDevice->GetData(g_Channel[0].m_InHi.Hex, g_Channel[0].m_InLo.Hex);
|
|
g_StatusReg.RDST1 = !!g_Channel[1].m_pDevice->GetData(g_Channel[1].m_InHi.Hex, g_Channel[1].m_InLo.Hex);
|
|
g_StatusReg.RDST2 = !!g_Channel[2].m_pDevice->GetData(g_Channel[2].m_InHi.Hex, g_Channel[2].m_InLo.Hex);
|
|
g_StatusReg.RDST3 = !!g_Channel[3].m_pDevice->GetData(g_Channel[3].m_InHi.Hex, g_Channel[3].m_InLo.Hex);
|
|
|
|
UpdateInterrupts();
|
|
}
|
|
|
|
SIDevices GetDeviceType(int channel)
|
|
{
|
|
if (channel < 0 || channel > 3)
|
|
return SIDEVICE_NONE;
|
|
else
|
|
return g_Channel[channel].m_pDevice->GetDeviceType();
|
|
}
|
|
|
|
void RunSIBuffer(u64 userdata, int cyclesLate)
|
|
{
|
|
if (g_ComCSR.TSTART)
|
|
{
|
|
// Math inLength
|
|
int inLength = g_ComCSR.INLNGTH;
|
|
if (inLength == 0)
|
|
inLength = 128;
|
|
else
|
|
inLength++;
|
|
|
|
// Math outLength
|
|
int outLength = g_ComCSR.OUTLNGTH;
|
|
if (outLength == 0)
|
|
outLength = 128;
|
|
else
|
|
outLength++;
|
|
|
|
int numOutput = 0;
|
|
|
|
numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength);
|
|
|
|
DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer chan: %d inLen: %i outLen: %i processed: %i", g_ComCSR.CHANNEL, inLength, outLength, numOutput);
|
|
|
|
if (numOutput != 0)
|
|
{
|
|
g_ComCSR.TSTART = 0;
|
|
GenerateSIInterrupt(INT_TCINT);
|
|
}
|
|
else
|
|
{
|
|
CoreTiming::ScheduleEvent(g_Channel[g_ComCSR.CHANNEL].m_pDevice->TransferInterval() - cyclesLate, et_transfer_pending);
|
|
}
|
|
}
|
|
}
|
|
|
|
int GetTicksToNextSIPoll()
|
|
{
|
|
// Poll for input at regular intervals (once per frame) when playing or recording a movie
|
|
if (Movie::IsMovieActive())
|
|
{
|
|
if (Movie::IsNetPlayRecording())
|
|
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate / 2;
|
|
else
|
|
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate;
|
|
}
|
|
if (NetPlay::IsNetPlayRunning())
|
|
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate / 2;
|
|
|
|
if (!g_Poll.Y && g_Poll.X)
|
|
return VideoInterface::GetTicksPerLine() * g_Poll.X;
|
|
else if (!g_Poll.Y)
|
|
return SystemTimers::GetTicksPerSecond() / 60;
|
|
|
|
return std::min(VideoInterface::GetTicksPerFrame() / g_Poll.Y, VideoInterface::GetTicksPerLine() * g_Poll.X);
|
|
}
|
|
|
|
} // end of namespace SerialInterface
|