////////////////////////////////////////////////////////////////////// // // // cpuPeripherals.pas: CPU - Peripheral devices // // Code for DMA, timers, and the joypad // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM Core, released April 1st, 2003 // // The Initial Developer of the Original Code is Bottled Light, // // Inc. Portions created by Bottled Light, Inc. are Copyright // // (C) 2001 - 2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // DMA transfers with start mode 3 are a touchy area. Its used // // for FIFO refills in conjunction with channels 1 and 2, but I'm // // not certain if the fields forced to a specific value by the // // sound hardware are all accounted for, if that makes any sense. // // For example, the length/width fields are certainly ignored, // // FIFO refills always transfer 4 words. In addition, start mode // // 3 seems to be invalid for channel 0, but it may behave // // specially for channel 3, I'm not certain. // // // // The timer code is a touchy subject with me, this is either the // // 7th or 9th complete rewrite, I'm not even certain anymore. // // It finally seems to work perfectly, and its actually faster // // than a few of the older versions. If there are any more // // timer related problems, don't even THINK about telling me, // // just fix it and send me the revised code. // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit cpuPeripherals; ///////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses nexus, AddressSpace; ////////////////////////////////////////////////////////////////////// // FlushTimers() updates all of the timers to their current states. procedure FlushTimers; // WriteTimerCR() is called when a timer control register is modified. procedure WriteTimerCR(timer: byte); // InitiateDMATransfer() is called to initiate a DMA transfer. procedure InitiateDMATransfer(channel: uint32); ////////////////////////////////////////////////////////////////////// // Exported functions //////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // vmKeyInput() is how the UI notifies the core when a key is pressed. procedure vmKeyInput(mask: integer); ////////////////////////////////////////////////////////////////////// exports vmKeyInput; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses SysUtils, cpuMemory, cpuMisc, cpuARMCore, cpuSound; ////////////////////////////////////////////////////////////////////// procedure AdvanceTimer(timer: byte; cycles: integer); var cr, speed: byte; begin // Calculate the new value cr := registers[TIMER0_CR + timer shl 2]; Inc(Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^, cycles); speed := registers[TIMER0_SPEED + timer]; if Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr speed >= $10000 then begin // Trigger an interrupt if requested if cr and TIMER_IRQ <> 0 then TriggerIRQ(IRQ_TIMER0 shl timer); // Try to trigger a FIFO increment and possible reload if registers[SOUND_ENABLED] and (1 shl 7) <> 0 then begin soundChanging; if (timer = soundA.timer) and soundA.enabled then begin DequeueTDSoundRecord(soundA); if soundA.writeIndex <= 16 then begin if ((Puint16(@(registers[DMA1_CR]))^ shr 12) and 3 = 3) then InitiateDMATransfer(1) else begin FillChar(soundA.fifo[soundA.writeIndex], 16, 0); Inc(soundA.writeIndex, 16); end; end; end; if (timer = soundB.timer) and soundB.enabled then begin DequeueTDSoundRecord(soundB); if soundB.writeIndex <= 16 then begin if ((Puint16(@(registers[DMA2_CR]))^ shr 12) and 3 = 3) then InitiateDMATransfer(2) else begin FillChar(soundB.fifo[soundB.writeIndex], 16, 0); Inc(soundB.writeIndex, 16); end; end; end; end; // Cascade into the other timers if they're set to cascade mode if timer < 3 then begin cr := registers[TIMER0_CR + (timer+1) shl 2]; if (cr and TIMER_ENABLED <> 0) and (cr and TIMER_CASCADE <> 0) then AdvanceTimer(timer+1, Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr (16 + speed)); end; // Reload the timer Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ := Puint16(@(registers[TIMER0_LATCH + timer shl 1]))^ shl speed + Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ and not ($FFFFFFFF shl (16 + speed)); end; end; ////////////////////////////////////////////////////////////////////// // This updates all of the timers to their current states. procedure flushTimers; var timer: byte; cycles: integer; begin cycles := timerQuotaAtLastFlush - quota; timerQuotaAtLastFlush := quota; if cycles < 1 then Exit; for timer := 0 to 3 do begin if (registers[TIMER0_CR + timer shl 2] and TIMER_CASCADE = 0) and (registers[TIMER0_CR + timer shl 2] and TIMER_ENABLED <> 0) then AdvanceTimer(timer, cycles); // Write the value back to the register Puint16(@(registers[TIMER0 + timer shl 2]))^ := Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr registers[TIMER0_SPEED + timer]; end; end; ////////////////////////////////////////////////////////////////////// // WriteTimerCR() is called when a timer control register is modified. procedure WriteTimerCR(timer: byte); const timerDividerTable: array[0..3] of byte = (0, 6, 8, 10); var cr: byte; begin cr := registers[TIMER0_CR + timer shl 2]; if cr and TIMER_ENABLED <> 0 then begin if cr and TIMER_CASCADE = 0 then registers[TIMER0_SPEED + timer] := timerDividerTable[cr and 3] else registers[TIMER0_SPEED + timer] := 0; Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ := Puint16(@(registers[TIMER0_LATCH + timer shl 1]))^ shl registers[TIMER0_SPEED + timer]; end; end; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Performs a DMA transfer procedure DmaTransfer(channel: byte; src, dest, count: uint32; var cr: uint16); var srcDelta, destDelta, srcMode, destMode, startMode, delta: uint32; words: boolean; data: uint32; i: uint32; preQuota: integer; begin if cr and DMA_ENABLED = 0 then Exit; words := cr and (1 shl 10) <> 0; if words then delta := 4 else delta := 2; startMode := (cr shr 12) and 3; // Set up the source mode srcMode := (cr shr 7) and $3; if srcMode = 0 then begin srcDelta := delta; end else if srcMode = 1 then begin srcDelta := -delta; end else srcDelta := 0; // Set up the destination mode destMode := (cr shr 5) and $3; if (destMode = 0) or (destMode = 3) then begin destDelta := delta; end else if destMode = 1 then begin destDelta := -delta; end else destDelta := 0; if startMode = 3 then begin if channel in [1,2] then begin // srcDelta := 4; destDelta := 0; count := 4; words := true; end else if channel = 0 then begin logWriteLn('Error, illegal startup mode 3 for DMA channel 0'); Exit; end; end; // Write the xfer to the log preQuota := quota; if logDMATransfers then begin LogWrite(Format('DMA%d $%x bytes from $%8.8x to $%8.8x, CR=$%4.4x', [channel, count*delta, src, dest, cr])); end; if src shr 28 = dest shr 28 then begin // Same bus, all N cycles if words then begin for i := 1 to count do begin data := memReadWordUnc(src); memWriteWordUnc(dest, data); Inc(src, srcDelta); Inc(dest, destDelta); end; end else begin for i := 1 to count do begin data := memReadHalfWordUnc(src); memWriteHalfWordUnc(dest, data); Inc(src, srcDelta); Inc(dest, destDelta); end; end; end else begin // Different buses, all S cycles after the 1st access if words then begin if count > 0 then begin data := memReadWordUnc(src); memWriteWordUnc(dest, data); Inc(src, srcDelta); Inc(dest, destDelta); end; for i := 2 to count do begin lastAddress := src; data := memReadWordUnc(src); Inc(src, srcDelta); lastAddress := dest; memWriteWordUnc(dest, data); Inc(dest, destDelta); end; end else begin if count > 0 then begin data := memReadHalfWordUnc(src); memWriteHalfWordUnc(dest, data); Inc(src, srcDelta); Inc(dest, destDelta); end; for i := 2 to count do begin lastAddress := src; data := memReadHalfWordUnc(src); Inc(src, srcDelta); lastAddress := dest; memWriteHalfWordUnc(dest, data); Inc(dest, destDelta); end; end; end; // If not in repeat mode, clear the enabled bit if cr and DMA_REPEAT = 0 then Puint16(@(registers[channel*$C + DMA0_CR]))^ := Puint16(@(registers[channel*$C + DMA0_CR]))^ and not DMA_ENABLED; Puint32(@(registers[channel*$C + DMA0_SRC]))^ := src; if destMode <> 3 then Puint32(@(registers[channel*$C + DMA0_DEST]))^ := dest; if logDMATransfers then begin LogWriteLn(Format(' in %d cycles', [preQuota-quota])); end; // Attempt to trigger an IRQ if the IRQ bit is set if cr and DMA_IRQ_REQ <> 0 then TriggerIRQ(IRQ_DMA0 shl channel); end; ////////////////////////////////////////////////////////////////////// // InitiateDMATransfer() is called to initiate a DMA transfer. procedure InitiateDMATransfer(channel: uint32); var count, src, dest: uint32; begin case channel of 0: begin // Channel 0 has 27-bit source and destination ranges, and a 14-bit count src := Puint32(@(registers[DMA0_SRC]))^ and $07FFFFFF; dest := Puint32(@(registers[DMA0_DEST]))^ and $07FFFFFF; count := Puint16(@(registers[DMA0_COUNT]))^ and $3FFF; if count = 0 then count := $4000; DmaTransfer(0, src, dest, count, Puint16(@(registers[DMA0_CR]))^); end; 1: begin // Channel 1 has a 28-bit source, 27-bit destination, and a 14-bit count src := Puint32(@(registers[DMA1_SRC]))^ and $0FFFFFFF; dest := Puint32(@(registers[DMA1_DEST]))^ and $07FFFFFF; count := Puint16(@(registers[DMA1_COUNT]))^ and $3FFF; if count = 0 then count := $4000; DmaTransfer(1, src, dest, count, Puint16(@(registers[DMA1_CR]))^); end; 2: begin // Channel 2 has a 28-bit source, 27-bit destination, and a 14-bit count src := Puint32(@(registers[DMA2_SRC]))^ and $0FFFFFFF; dest := Puint32(@(registers[DMA2_DEST]))^ and $07FFFFFF; count := Puint16(@(registers[DMA2_COUNT]))^ and $3FFF; if count = 0 then count := $4000; DmaTransfer(2, src, dest, count, Puint16(@(registers[DMA2_CR]))^); end; 3: begin // Channel 3 has a 28-bit source and destination range, and a 16-bit count src := Puint32(@(registers[DMA3_SRC]))^ and $0FFFFFFF; dest := Puint32(@(registers[DMA3_DEST]))^ and $0FFFFFFF; count := Puint16(@(registers[DMA3_COUNT]))^; if count = 0 then count := $10000; DmaTransfer(3, src, dest, count, Puint16(@(registers[DMA3_CR]))^); end; end; end; ////////////////////////////////////////////////////////////////////// // Input control (joystick or keyboard, whatever you want to call it) ////////////////////////////////////////////////////////////////////// // vmKeyInput() is how the UI notifies the core when a key is pressed. procedure vmKeyInput(mask: integer); var k, cr: uint16; begin Puint16(@(registers[KEYS]))^ := mask; if mask <> downkeys then begin // Attempt to trigger a key press IRQ k := (not Puint16(@(registers[KEYS]))^) and $3FF; cr := Puint16(@(registers[KEY_IRQS]))^; if cr and KEY_IRQ_ENABLED <> 0 then if cr and KEY_IRQ_AND <> 0 then begin cr := cr and $3FF; if k and cr = cr then TriggerIRQ(IRQ_KEY); end else if k and (cr and $3FF) <> 0 then TriggerIRQ(IRQ_KEY); end; downkeys := mask; end; ////////////////////////////////////////////////////////////////////// end. //////////////////////////////////////////////////////////////////////