void C6821::SetCB2(BYTE byData) { byData = byData ? 1 : 0; // CA2 is in input mode if ( C2_INPUT( m_byCTLB ) ) { // the new state has caused a transition if ( m_byICB2 ^ byData ) { // handle the active transition if ( ( byData && C2_LOW_TO_HIGH( m_byCTLB ) ) || ( !byData && C2_HIGH_TO_LOW( m_byCTLB ) ) ) { // mark the IRQ SET_IRQ2( m_byCTLB ); // update externals UpdateInterrupts(); } } } // set the new value for CA2 m_byICB2 = byData; }
void C6821::SetCA1(BYTE byData) { byData = byData ? 1 : 0; // the new state has caused a transition if ( m_byCA1 ^ byData ) { // handle the active transition if ( ( byData && C1_LOW_TO_HIGH( m_byCTLA ) ) || ( !byData && C1_HIGH_TO_LOW( m_byCTLA ) ) ) { // mark the IRQ SET_IRQ1(m_byCTLA); // update externals UpdateInterrupts(); // CA2 is configured as output and in read strobe mode and cleared by a CA1 transition if ( C2_OUTPUT( m_byCTLA ) && C2_STROBE_MODE( m_byCTLA ) && STROBE_C1_RESET( m_byCTLA ) ) { // call the CA2 output function if ( !m_byOCA2 ) PIA_W_CALLBACK( m_stOutCA2, 1 ); // clear CA2 m_byOCA2 = 1; } } } // set the new value for CA1 m_byCA1 = byData; }
void CEXIChannel::AddDevice(IEXIDevice* pDevice, const int device_num, bool notifyPresenceChanged) { _dbg_assert_(EXPANSIONINTERFACE, device_num < NUM_DEVICES); // delete the old device if (m_pDevices[device_num] != NULL) { delete m_pDevices[device_num]; m_pDevices[device_num] = NULL; } // replace it with the new one m_pDevices[device_num] = pDevice; if(notifyPresenceChanged) { // This means "device presence changed", software has to check // m_Status.EXT to see if it is now present or not if (m_ChannelId != 2) { m_Status.EXTINT = 1; UpdateInterrupts(); } } }
void GenerateSIInterrupt(SIInterruptType _SIInterrupt) { switch(_SIInterrupt) { case INT_RDSTINT: g_ComCSR.RDSTINT = 1; break; case INT_TCINT: g_ComCSR.TCINT = 1; break; } UpdateInterrupts(); }
void GenerateDSPInterrupt(DSPInterruptType type, bool _bSet) { switch (type) { case INT_DSP: g_dspState.DSPControl.DSP = _bSet ? 1 : 0; break; case INT_ARAM: g_dspState.DSPControl.ARAM = _bSet ? 1 : 0; if (_bSet) g_dspState.DSPControl.DMAState = 0; break; case INT_AID: g_dspState.DSPControl.AID = _bSet ? 1 : 0; break; } UpdateInterrupts(); }
void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt) { switch(_DVDInterrupt) { case INT_DEINT: m_DISR.DEINT = 1; break; case INT_TCINT: m_DISR.TCINT = 1; break; case INT_BRKINT: m_DISR.BRKINT = 1; break; case INT_CVRINT: m_DICVR.CVRINT = 1; break; } UpdateInterrupts(); }
void Write32(const u32 _iValue, const u32 _iAddress) { DEBUG_LOG(DVDINTERFACE, "(w32): 0x%08x @ 0x%08x", _iValue, _iAddress); switch (_iAddress & 0xFF) { case DI_STATUS_REGISTER: { UDISR tmpStatusReg(_iValue); m_DISR.DEINITMASK = tmpStatusReg.DEINITMASK; m_DISR.TCINTMASK = tmpStatusReg.TCINTMASK; m_DISR.BRKINTMASK = tmpStatusReg.BRKINTMASK; m_DISR.BREAK = tmpStatusReg.BREAK; if (tmpStatusReg.DEINT) m_DISR.DEINT = 0; if (tmpStatusReg.TCINT) m_DISR.TCINT = 0; if (tmpStatusReg.BRKINT) m_DISR.BRKINT = 0; if (m_DISR.BREAK) { _dbg_assert_(DVDINTERFACE, 0); } UpdateInterrupts(); } break; case DI_COVER_REGISTER: { UDICVR tmpCoverReg(_iValue); m_DICVR.CVRINTMASK = tmpCoverReg.CVRINTMASK; if (tmpCoverReg.CVRINT) m_DICVR.CVRINT = 0; UpdateInterrupts(); } break; case DI_COMMAND_0: m_DICMDBUF[0].Hex = _iValue; break; case DI_COMMAND_1: m_DICMDBUF[1].Hex = _iValue; break; case DI_COMMAND_2: m_DICMDBUF[2].Hex = _iValue; break; case DI_DMA_ADDRESS_REGISTER: { m_DIMAR.Hex = _iValue & ~0xfc00001f; } break; case DI_DMA_LENGTH_REGISTER: { m_DILENGTH.Hex = _iValue & ~0x1f; } break; case DI_DMA_CONTROL_REGISTER: { m_DICR.Hex = _iValue & 7; if (m_DICR.TSTART) { if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed) { u64 ticksUntilTC = m_DILENGTH.Length * (SystemTimers::GetTicksPerSecond() / (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii?DISC_TRANSFER_RATE_WII:DISC_TRANSFER_RATE_GC)) + (SystemTimers::GetTicksPerSecond() * DISC_ACCESS_TIME_MS / 1000); CoreTiming::ScheduleEvent((int)ticksUntilTC, tc); } else { ExecuteCommand(m_DICR); } } } break; case DI_IMMEDIATE_DATA_BUFFER: m_DIIMMBUF.Hex = _iValue; break; case DI_CONFIG_REGISTER: { WARN_LOG(DVDINTERFACE, "Write to DICFG, ignored as it's read-only"); } break; default: _dbg_assert_msg_(DVDINTERFACE, 0, "Write to unknown DI address 0x%08x", _iAddress); break; } }
void RegisterMMIO(MMIO::Mapping* mmio, u32 base) { // Declare all the boilerplate direct MMIOs. struct { u32 addr; u16* ptr; bool align_writes_on_32_bytes; } directly_mapped_vars[] = { { AR_INFO, &g_ARAM_Info.Hex }, { AR_MODE, &g_AR_MODE }, { AR_REFRESH, &g_AR_REFRESH }, { AR_DMA_MMADDR_H, MMIO::Utils::HighPart(&g_arDMA.MMAddr) }, { AR_DMA_MMADDR_L, MMIO::Utils::LowPart(&g_arDMA.MMAddr), true }, { AR_DMA_ARADDR_H, MMIO::Utils::HighPart(&g_arDMA.ARAddr) }, { AR_DMA_ARADDR_L, MMIO::Utils::LowPart(&g_arDMA.ARAddr), true }, { AR_DMA_CNT_H, MMIO::Utils::HighPart(&g_arDMA.Cnt.Hex) }, // AR_DMA_CNT_L triggers DMA { AUDIO_DMA_START_HI, MMIO::Utils::HighPart(&g_audioDMA.SourceAddress) }, { AUDIO_DMA_START_LO, MMIO::Utils::LowPart(&g_audioDMA.SourceAddress) }, }; for (auto& mapped_var : directly_mapped_vars) { u16 write_mask = mapped_var.align_writes_on_32_bytes ? 0xFFE0 : 0xFFFF; mmio->Register(base | mapped_var.addr, MMIO::DirectRead<u16>(mapped_var.ptr), MMIO::DirectWrite<u16>(mapped_var.ptr, write_mask) ); } // DSP mail MMIOs call DSP emulator functions to get results or write data. mmio->Register(base | DSP_MAIL_TO_DSP_HI, MMIO::ComplexRead<u16>([](u32) { if (dsp_slice > DSP_MAIL_SLICE && dsp_is_lle) { dsp_emulator->DSP_Update(DSP_MAIL_SLICE); dsp_slice -= DSP_MAIL_SLICE; } return dsp_emulator->DSP_ReadMailBoxHigh(true); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { dsp_emulator->DSP_WriteMailBoxHigh(true, val); }) ); mmio->Register(base | DSP_MAIL_TO_DSP_LO, MMIO::ComplexRead<u16>([](u32) { return dsp_emulator->DSP_ReadMailBoxLow(true); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { dsp_emulator->DSP_WriteMailBoxLow(true, val); }) ); mmio->Register(base | DSP_MAIL_FROM_DSP_HI, MMIO::ComplexRead<u16>([](u32) { if (dsp_slice > DSP_MAIL_SLICE && dsp_is_lle) { dsp_emulator->DSP_Update(DSP_MAIL_SLICE); dsp_slice -= DSP_MAIL_SLICE; } return dsp_emulator->DSP_ReadMailBoxHigh(false); }), MMIO::InvalidWrite<u16>() ); mmio->Register(base | DSP_MAIL_FROM_DSP_LO, MMIO::ComplexRead<u16>([](u32) { return dsp_emulator->DSP_ReadMailBoxLow(false); }), MMIO::InvalidWrite<u16>() ); mmio->Register(base | DSP_CONTROL, MMIO::ComplexRead<u16>([](u32) { return (g_dspState.DSPControl.Hex & ~DSP_CONTROL_MASK) | (dsp_emulator->DSP_ReadControlRegister() & DSP_CONTROL_MASK); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { UDSPControl tmpControl; tmpControl.Hex = (val & ~DSP_CONTROL_MASK) | (dsp_emulator->DSP_WriteControlRegister(val) & DSP_CONTROL_MASK); // Not really sure if this is correct, but it works... // Kind of a hack because DSP_CONTROL_MASK should make this bit // only viewable to dsp emulator if (val & 1 /*DSPReset*/) { g_audioDMA.AudioDMAControl.Hex = 0; } // Update DSP related flags g_dspState.DSPControl.DSPReset = tmpControl.DSPReset; g_dspState.DSPControl.DSPAssertInt = tmpControl.DSPAssertInt; g_dspState.DSPControl.DSPHalt = tmpControl.DSPHalt; g_dspState.DSPControl.DSPInit = tmpControl.DSPInit; // Interrupt (mask) g_dspState.DSPControl.AID_mask = tmpControl.AID_mask; g_dspState.DSPControl.ARAM_mask = tmpControl.ARAM_mask; g_dspState.DSPControl.DSP_mask = tmpControl.DSP_mask; // Interrupt if (tmpControl.AID) g_dspState.DSPControl.AID = 0; if (tmpControl.ARAM) g_dspState.DSPControl.ARAM = 0; if (tmpControl.DSP) g_dspState.DSPControl.DSP = 0; // unknown g_dspState.DSPControl.unk3 = tmpControl.unk3; g_dspState.DSPControl.pad = tmpControl.pad; if (g_dspState.DSPControl.pad != 0) { PanicAlert("DSPInterface (w) g_dspState.DSPControl (CC00500A) gets a value with junk in the padding %08x", val); } UpdateInterrupts(); }) ); // ARAM MMIO controlling the DMA start. mmio->Register(base | AR_DMA_CNT_L, MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&g_arDMA.Cnt.Hex)), MMIO::ComplexWrite<u16>([](u32, u16 val) { g_arDMA.Cnt.Hex = (g_arDMA.Cnt.Hex & 0xFFFF0000) | (val & ~31); Do_ARAM_DMA(); }) ); // Audio DMA MMIO controlling the DMA start. mmio->Register(base | AUDIO_DMA_CONTROL_LEN, MMIO::DirectRead<u16>(&g_audioDMA.AudioDMAControl.Hex), MMIO::ComplexWrite<u16>([](u32, u16 val) { g_audioDMA.AudioDMAControl.Hex = val; g_audioDMA.ReadAddress = g_audioDMA.SourceAddress; g_audioDMA.BlocksLeft = g_audioDMA.AudioDMAControl.NumBlocks; }) ); // Audio DMA blocks remaining is invalid to write to, and requires logic on // the read side. mmio->Register(base | AUDIO_DMA_BLOCKS_LEFT, MMIO::ComplexRead<u16>([](u32) { return (g_audioDMA.BlocksLeft > 0 ? g_audioDMA.BlocksLeft - 1 : 0); }), MMIO::InvalidWrite<u16>() ); // 32 bit reads/writes are a combination of two 16 bit accesses. for (int i = 0; i < 0x1000; i += 4) { mmio->Register(base | i, MMIO::ReadToSmaller<u32>(mmio, base | i, base | (i + 2)), MMIO::WriteToSmaller<u32>(mmio, base | i, base | (i + 2)) ); } }
static void GenerateAudioInterrupt() { m_Control.AIINT = 1; UpdateInterrupts(); }
void RegisterMMIO(MMIO::Mapping* mmio, u32 base) { mmio->Register( base | AI_CONTROL_REGISTER, MMIO::DirectRead<u32>(&m_Control.hex), MMIO::ComplexWrite<u32>([](u32, u32 val) { AICR tmpAICtrl(val); if (m_Control.AIINTMSK != tmpAICtrl.AIINTMSK) { DEBUG_LOG(AUDIO_INTERFACE, "Change AIINTMSK to %d", tmpAICtrl.AIINTMSK); m_Control.AIINTMSK = tmpAICtrl.AIINTMSK; } if (m_Control.AIINTVLD != tmpAICtrl.AIINTVLD) { DEBUG_LOG(AUDIO_INTERFACE, "Change AIINTVLD to %d", tmpAICtrl.AIINTVLD); m_Control.AIINTVLD = tmpAICtrl.AIINTVLD; } // Set frequency of streaming audio if (tmpAICtrl.AISFR != m_Control.AISFR) { // AISFR rates below are intentionally inverted wrt yagcd DEBUG_LOG(AUDIO_INTERFACE, "Change AISFR to %s", tmpAICtrl.AISFR ? "48khz" : "32khz"); m_Control.AISFR = tmpAICtrl.AISFR; g_AISSampleRate = tmpAICtrl.AISFR ? 48000 : 32000; g_sound_stream->GetMixer()->SetStreamInputSampleRate(g_AISSampleRate); g_CPUCyclesPerSample = SystemTimers::GetTicksPerSecond() / g_AISSampleRate; } // Set frequency of DMA if (tmpAICtrl.AIDFR != m_Control.AIDFR) { DEBUG_LOG(AUDIO_INTERFACE, "Change AIDFR to %s", tmpAICtrl.AIDFR ? "32khz" : "48khz"); m_Control.AIDFR = tmpAICtrl.AIDFR; g_AIDSampleRate = tmpAICtrl.AIDFR ? 32000 : 48000; g_sound_stream->GetMixer()->SetDMAInputSampleRate(g_AIDSampleRate); } // Streaming counter if (tmpAICtrl.PSTAT != m_Control.PSTAT) { DEBUG_LOG(AUDIO_INTERFACE, "%s streaming audio", tmpAICtrl.PSTAT ? "start" : "stop"); m_Control.PSTAT = tmpAICtrl.PSTAT; g_LastCPUTime = CoreTiming::GetTicks(); CoreTiming::RemoveEvent(et_AI); CoreTiming::ScheduleEvent(GetAIPeriod(), et_AI); } // AI Interrupt if (tmpAICtrl.AIINT) { DEBUG_LOG(AUDIO_INTERFACE, "Clear AIS Interrupt"); m_Control.AIINT = 0; } // Sample Count Reset if (tmpAICtrl.SCRESET) { DEBUG_LOG(AUDIO_INTERFACE, "Reset AIS sample counter"); m_SampleCounter = 0; g_LastCPUTime = CoreTiming::GetTicks(); } UpdateInterrupts(); })); mmio->Register(base | AI_VOLUME_REGISTER, MMIO::DirectRead<u32>(&m_Volume.hex), MMIO::ComplexWrite<u32>([](u32, u32 val) { m_Volume.hex = val; g_sound_stream->GetMixer()->SetStreamingVolume(m_Volume.left, m_Volume.right); })); mmio->Register(base | AI_SAMPLE_COUNTER, MMIO::ComplexRead<u32>([](u32) { return m_SampleCounter + static_cast<u32>((CoreTiming::GetTicks() - g_LastCPUTime) / g_CPUCyclesPerSample); }), MMIO::ComplexWrite<u32>([](u32, u32 val) { m_SampleCounter = val; g_LastCPUTime = CoreTiming::GetTicks(); CoreTiming::RemoveEvent(et_AI); CoreTiming::ScheduleEvent(GetAIPeriod(), et_AI); })); mmio->Register(base | AI_INTERRUPT_TIMING, MMIO::DirectRead<u32>(&m_InterruptTiming), MMIO::ComplexWrite<u32>([](u32, u32 val) { DEBUG_LOG(AUDIO_INTERFACE, "AI_INTERRUPT_TIMING=%08x@%08x", val, PowerPC::ppcState.pc); m_InterruptTiming = val; CoreTiming::RemoveEvent(et_AI); CoreTiming::ScheduleEvent(GetAIPeriod(), et_AI); })); }
void UpdateInterrupts_Wrapper(u64 userdata, int cyclesLate) { UpdateInterrupts(userdata); }
void C6821::Write(BYTE byRS, BYTE byData) { byRS &= 3; switch( byRS ) { /******************* port A output/DDR write *******************/ case PIA_DDRA: // write output register if ( OUTPUT_SELECTED( m_byCTLA ) ) { // update the output value m_byOA = byData; // send it to the output function if ( m_byDDRA ) PIA_W_CALLBACK( m_stOutA, m_byOA & m_byDDRA ); } // write DDR register else { if ( m_byDDRA != byData ) { m_byDDRA = byData; // send it to the output function if ( m_byDDRA ) PIA_W_CALLBACK( m_stOutA, m_byOA & m_byDDRA ); } } break; /******************* port B output/DDR write *******************/ case PIA_DDRB: // write output register if ( OUTPUT_SELECTED( m_byCTLB ) ) { // update the output value m_byOB = byData; // send it to the output function if ( m_byDDRB ) PIA_W_CALLBACK( m_stOutB, m_byOB & m_byDDRB ); // CB2 is configured as output and in write strobe mode if ( C2_OUTPUT( m_byCTLB ) && C2_STROBE_MODE( m_byCTLB ) ) { // this will cause a transition low; call the output function if we're currently high if ( m_byOCB2 ) PIA_W_CALLBACK( m_stOutCB2, 0 ); m_byOCB2 = 0; // if the CB2 strobe is cleared by the E, reset it right away if ( STROBE_E_RESET( m_byCTLB ) ) { PIA_W_CALLBACK( m_stOutCB2, 1 ); m_byOCB2 = 1; } } } // write DDR register else { if ( m_byDDRB != byData ) { m_byDDRB = byData; // send it to the output function if ( m_byDDRB ) PIA_W_CALLBACK( m_stOutB, m_byOB & m_byDDRB ); } } break; /******************* port A control write *******************/ case PIA_CTLA: // Bit 7 and 6 read only byData &= 0x3f; // CA2 is configured as output and in set/reset mode if ( C2_OUTPUT( byData ) ) { // determine the new value int temp = SET_C2( byData ) ? 1 : 0; // if this creates a transition, call the CA2 output function if ( m_byOCA2 ^ temp) PIA_W_CALLBACK( m_stOutCA2, temp ); // set the new value m_byOCA2 = temp; } // update the control register m_byCTLA = ( m_byCTLA & ~0x3F ) | byData; // update externals UpdateInterrupts(); break; /******************* port B control write *******************/ case PIA_CTLB: /* Bit 7 and 6 read only - PD 16/01/00 */ byData &= 0x3f; // CB2 is configured as output and in set/reset mode if ( C2_OUTPUT( byData ) ) { // determine the new value int temp = SET_C2( byData ) ? 1 : 0; // if this creates a transition, call the CA2 output function if ( m_byOCB2 ^ temp) PIA_W_CALLBACK( m_stOutCB2, temp ); // set the new value m_byOCB2 = temp; } // update the control register m_byCTLB = ( m_byCTLB & ~0x3F ) | byData; // update externals UpdateInterrupts(); break; } }
BYTE C6821::Read(BYTE byRS) { BYTE retval = 0; byRS &= 3; switch ( byRS ) { /******************* port A output/DDR read *******************/ case PIA_DDRA: // read output register if ( OUTPUT_SELECTED(m_byCTLA) ) { // combine input and output values retval = ( m_byOA & m_byDDRA ) | ( m_byIA & ~m_byDDRA ); // IRQ flags implicitly cleared by a read CLEAR_IRQ1( m_byCTLA ); CLEAR_IRQ1( m_byCTLB ); UpdateInterrupts(); // CA2 is configured as output and in read strobe mode if ( C2_OUTPUT(m_byCTLA) && C2_STROBE_MODE(m_byCTLA) ) { // this will cause a transition low; call the output function if we're currently high if ( m_byOCA2 ) PIA_W_CALLBACK( m_stOutCA2, 0 ); m_byOCA2 = 0; // if the CA2 strobe is cleared by the E, reset it right away if ( STROBE_E_RESET( m_byCTLA ) ) { PIA_W_CALLBACK( m_stOutCA2, 1 ); m_byOCA2 = 1; } } } // read DDR register else { retval = m_byDDRA; } break; /******************* port B output/DDR read *******************/ case PIA_DDRB: // read output register if ( OUTPUT_SELECTED( m_byCTLB ) ) { // combine input and output values retval = ( m_byOB & m_byDDRB ) + ( m_byIB & ~m_byDDRB ); // IRQ flags implicitly cleared by a read CLEAR_IRQ2( m_byCTLA ); CLEAR_IRQ2( m_byCTLB ); UpdateInterrupts(); } /* read DDR register */ else { retval = m_byDDRB; } break; /******************* port A control read *******************/ case PIA_CTLA: // read control register retval = m_byCTLA; // when CA2 is an output, IRQA2 = 0, and is not affected by CA2 transitions. if ( C2_OUTPUT( m_byCTLA ) ) retval &= ~PIA_IRQ2; break; /******************* port B control read *******************/ case PIA_CTLB: retval = m_byCTLB; // when CB2 is an output, IRQB2 = 0, and is not affected by CB2 transitions. if ( C2_OUTPUT( m_byCTLB ) ) retval &= ~PIA_IRQ2; break; } return retval; }
void CEXIChannel::Write32(const u32 _iValue, const u32 _iRegister) { DEBUG_LOG(EXPANSIONINTERFACE, "(w32) 0x%08x channel: %i reg: %s", _iValue, m_ChannelId, Debug_GetRegisterName(_iRegister)); switch (_iRegister) { case EXI_STATUS: { UEXI_STATUS newStatus(_iValue); m_Status.EXIINTMASK = newStatus.EXIINTMASK; if (newStatus.EXIINT) m_Status.EXIINT = 0; m_Status.TCINTMASK = newStatus.TCINTMASK; if (newStatus.TCINT) m_Status.TCINT = 0; m_Status.CLK = newStatus.CLK; if (m_ChannelId == 0 || m_ChannelId == 1) { m_Status.EXTINTMASK = newStatus.EXTINTMASK; if (newStatus.EXTINT) m_Status.EXTINT = 0; } if (m_ChannelId == 0) m_Status.ROMDIS = newStatus.ROMDIS; IEXIDevice* pDevice = GetDevice(m_Status.CHIP_SELECT ^ newStatus.CHIP_SELECT); m_Status.CHIP_SELECT = newStatus.CHIP_SELECT; if (pDevice != NULL) pDevice->SetCS(m_Status.CHIP_SELECT); UpdateInterrupts(); } break; case EXI_DMAADDR: INFO_LOG(EXPANSIONINTERFACE, "Wrote DMAAddr, chan %i", m_ChannelId); m_DMAMemoryAddress = _iValue; break; case EXI_DMALENGTH: INFO_LOG(EXPANSIONINTERFACE, "Wrote DMALength, chan %i", m_ChannelId); m_DMALength = _iValue; break; case EXI_DMACONTROL: INFO_LOG(EXPANSIONINTERFACE, "Wrote DMAControl, chan %i", m_ChannelId); m_Control.Hex = _iValue; if (m_Control.TSTART) { IEXIDevice* pDevice = GetDevice(m_Status.CHIP_SELECT); if (pDevice == NULL) return; if (m_Control.DMA == 0) { // immediate data switch (m_Control.RW) { case EXI_READ: m_ImmData = pDevice->ImmRead(m_Control.TLEN + 1); break; case EXI_WRITE: pDevice->ImmWrite(m_ImmData, m_Control.TLEN + 1); break; case EXI_READWRITE: pDevice->ImmReadWrite(m_ImmData, m_Control.TLEN + 1); break; default: _dbg_assert_msg_(EXPANSIONINTERFACE,0,"EXI Imm: Unknown transfer type %i", m_Control.RW); } m_Control.TSTART = 0; } else { // DMA switch (m_Control.RW) { case EXI_READ: pDevice->DMARead (m_DMAMemoryAddress, m_DMALength); break; case EXI_WRITE: pDevice->DMAWrite(m_DMAMemoryAddress, m_DMALength); break; default: _dbg_assert_msg_(EXPANSIONINTERFACE,0,"EXI DMA: Unknown transfer type %i", m_Control.RW); } m_Control.TSTART = 0; } if(!m_Control.TSTART) // completed ! { m_Status.TCINT = 1; UpdateInterrupts(); } } break; case EXI_IMMDATA: INFO_LOG(EXPANSIONINTERFACE, "Wrote IMMData, chan %i", m_ChannelId); m_ImmData = _iValue; break; } }