/* $Id: I8254.java,v 1.13 2012-07-08 10:40:44 user Exp $ */ package mce.arch; import mce.cpu.CPU; import mce.cpu.ResPlugin; import java.util.ArrayList; import java.util.List; /** * Intel 82c54 chmos programmable interval timer. * This module holds implementation of the Intel's industry standard 8254 counter/timer. */ public class I8254 implements ResPlugin { /** * Class implementing one counter */ public class Counter implements ResPlugin { // counter number private int counterNumber; // current work mode private int workMode; // current read/write mode private int rwMode; // whether the BCD counting is active private boolean bcd; // counter running flag private boolean countRunning; // count hi/lo flipFlop private boolean countHiLoRead; private boolean countHiLoWrite; // current count wave phase (for work mode 3) private boolean countWavePhase; // current count value private int countCurrent; // initial count value private int countInitial; // new initial value has been loaded private boolean countInitialNew; // new initial value has been loaded and is complete private boolean countInitialNewComplete; // set to true if the count is not available (aka count is null) private boolean countNull = true; // latched count value private int countLatch; // whether the counter latch is available or not private boolean countLatchAvailable; // status value private int statusLatch; // whether the status latch is available or not private boolean statusLatchAvailable; // Output signal private boolean signalOutput; // Gate signal private boolean signalGate; /** * Public contructor * * @param counter serial number of this counter */ protected Counter(int counter) { this.counterNumber = counter; } /** * Init counter values * * @param countInitial initial count * @param countCurrent current count * @param countLatch currently latched count * @param workMode count mode * @param isHiWrite true if high byte is being currently written * @param isHiRead true if high byte is being currently read * @param isLatched true if latched byte available * @param rwMode read/write mode of the counter */ public void init(int countInitial, int countCurrent, int countLatch, int workMode, boolean isHiWrite, boolean isHiRead, boolean isLatched, int rwMode) { this.countInitial = countInitial; this.countCurrent = countCurrent; this.countLatch = countLatch; this.workMode = workMode; this.countHiLoWrite = isHiWrite; this.countHiLoRead = isHiRead; this.countLatchAvailable = isLatched; this.rwMode = rwMode; setControl(this.workMode, rwMode, bcd); } /** * ResPlugin interface: do reset routine external to CPU unit (called when CPU does a reset) * * @param ticks number of ticks since last method call * @return number of ticks to add to the ticks count */ public int doCPUReset(CPU cpu, int ticks) { workMode = rwMode = countCurrent = countInitial = countLatch = statusLatch = 0; bcd = countRunning = countHiLoRead = countHiLoWrite = false; countWavePhase = countInitialNew = countInitialNewComplete = false; countLatchAvailable = statusLatchAvailable = signalOutput = signalGate = false; countNull = true; return 0; } /** * Set the signal Output * * @param signal signal level (true/false) */ public void setSignalOutput(boolean signal) { signalOutput = signal; } /** * Set the signal Gate * * @param signal signal level (true/false) */ public void setSignalGate(boolean signal) { if (signal != signalGate) { signalGate = signal; switch (workMode) { case 0: // interrupt on terminal count countRunning = signalGate; break; case 1: // hardware retriggerable one-shot if (signalGate && countInitialNewComplete) { countCurrent = countInitial; countRunning = true; } break; case 2: // Rate generator case 3: // Square wave mode if (signalGate) countInitialNew = true; else setSignalOutput(true); countRunning = signalGate; break; case 4: // Software triggered strobe countRunning = signalGate; break; case 5: // Hardware triggered strobe (retriggerable) if (signalGate) { countInitialNew = true; countRunning = true; } break; } } } /** * Set control word * * @param workMode Work mode to set the counter to * @param rwMode read/write mode to set the counter to * @param bcd set to true when the counter is set to binary coded decimal (BCD) counting */ public void setControl(int workMode, int rwMode, boolean bcd) { this.workMode = workMode & 7; this.rwMode = rwMode & 3; this.bcd = bcd; countNull = true; countInitialNew = true; countInitialNewComplete = false; // if the rwMode is 3, we've to reset HiLo flag countHiLoWrite = rwMode == 2; // do a counter initialization according to the work mode switch (workMode) { case 0: // Interrupt on terminal count setSignalOutput(false); countRunning = false; countNull = true; break; case 1: // Hardware retriggerable one-shot case 2: // Rate generator case 3: // Square wave mode case 4: // Software triggered strobe case 5: // Hardware triggered strobe (retriggerable) setSignalOutput(true); countRunning = false; countWavePhase = true; break; } } /** * Latch counter's count */ public void latchCount() { // do a counter latch command only if no counter latch is available yet if (!countLatchAvailable) { countLatch = countCurrent; countLatchAvailable = true; } } /** * Latch counter's status */ public void latchStatus() { // do a status latch command only if no status latch is available yet if (!statusLatchAvailable) { statusLatch = (signalOutput ? 0x80 : 0) | (countNull ? 0x40 : 0) | (rwMode << 4) | (workMode << 1) | (bcd ? 1 : 0); statusLatchAvailable = true; } } /** * Set counter data * * @param data int containing the data for the counter (initial count lo/hi) */ public void setData(int data) { // normalize bcd number if (bcd) { if ((data & 0xf) > 9) data = (data & 0xf0) | 9; if ((data & 0xf0) > 0x90) data = (data & 0xf) | 0x90; } if (countHiLoWrite) { countInitial = (countInitial & 0xff) | ((data & 0xff) << 8); countInitialNewComplete = true; countHiLoWrite = rwMode != 3; } else { countInitial = (countInitial & 0xff00) | (data & 0xff); countInitialNewComplete = rwMode != 3; countHiLoWrite = rwMode == 3; } // change counter (work mode dependent) switch (workMode) { case 0: // Interrupt on terminal count if (countInitialNewComplete) countInitialNew = true; else countRunning = false; setSignalOutput(false); break; case 1: // Hardware retriggerable one-shot break; case 2: // Rate generator case 3: // Square wave mode if (!countRunning && countInitialNewComplete) { countInitialNew = true; countRunning = true; } break; case 4: // Software triggered strobe if (countInitialNewComplete) { countInitialNew = true; countRunning = true; } break; } } /** * Get data from counter * * @return int containing the data from the counter (count, latched status or latched count) */ public int getData() { // return latched status (if available) if (statusLatchAvailable) { statusLatchAvailable = false; return statusLatch; } // we are prepared to return current count int retCount = countCurrent; // rather return latched count (if available) if (countLatchAvailable) { retCount = countLatch; if (rwMode != 3 || countHiLoRead) countLatchAvailable = false; } // prepare the count for returning and change the HiLo switch accordingly retCount = (countHiLoRead ? (retCount >> 8) : retCount) & 0xff; if (rwMode == 3) countHiLoRead = !countHiLoRead; return retCount; } /** * Do counter tick given number of times * * @param ticks number of ticks to process */ public void tick(int ticks) { // do given number of ticks if (countInitialNew || countRunning) { while (ticks-- > 0) { // do a counter work according to the work mode switch (workMode) { // ------------------ Interrupt on terminal count -------------------- case 0: // load new value into the counter if (countInitialNew) { countCurrent = countInitial; countInitialNew = false; countRunning = true; countNull = false; // process old value } else if (countRunning && signalGate) { countCurrent = decrementCounter(countCurrent, bcd); if ((countCurrent == 0xffff && !bcd) || (countCurrent == 0x9999 && bcd)) { setSignalOutput(true); processListeners(counterNumber); } } break; // ------------------ Hardware retriggerable one-shot ------------------ case 1: // process counter value if (countRunning) { setSignalOutput(false); countCurrent = decrementCounter(countCurrent, bcd); if ((countCurrent == 0xffff && !bcd) || (countCurrent == 0x9999 && bcd)) { countRunning = false; setSignalOutput(true); processListeners(counterNumber); } } break; // -------------------------- Rate generator --------------------------- case 2: // load new value into the counter if (countInitialNew) { countCurrent = countInitial; countInitialNew = false; setSignalOutput(true); countNull = false; // process counter value } else if (countRunning) { countCurrent = decrementCounter(countCurrent, bcd); if (countCurrent == 1) { setSignalOutput(false); countInitialNew = true; processListeners(counterNumber); } } break; // -------------------------- Square wave mode ------------------------- case 3: // load new value into the counter if (countInitialNew) { countCurrent = countInitial; countInitialNew = false; setSignalOutput(countWavePhase); countNull = false; if (!countWavePhase) countCurrent = decrementCounter(countCurrent, bcd); countWavePhase = !countWavePhase; // process counter value } else if (countRunning) { countCurrent = decrementCounter(countCurrent, bcd); if ((countCurrent == 0xffff && !bcd) || (countCurrent == 0x9999 && bcd)) { countInitialNew = true; processListeners(counterNumber); } else { countCurrent = decrementCounter(countCurrent, bcd); if ((countCurrent == 0xffff && !bcd) || (countCurrent == 0x9999 && bcd)) { countInitialNew = true; processListeners(counterNumber); } } } break; // ---------------------- Software triggered strobe ----------------------- case 4: // ---------------------- Hardware triggered strobe ----------------------- case 5: // load new value into the counter if (countInitialNew) { countCurrent = countInitial; countInitialNew = false; setSignalOutput(true); countNull = false; // process counter value } else if (countRunning) { countCurrent = decrementCounter(countCurrent, bcd); if ((countCurrent == 0xffff && !bcd) || (countCurrent == 0x9999 && bcd)) { setSignalOutput(false); countInitialNew = true; processListeners(counterNumber); } } break; } } } } /** * Decrement counter by one * * @param num number to decrement */ private int decrementCounter(int num, boolean bcd) { // handle special case decrement if (num == 0) return bcd ? 0x9999 : 0xffff; // non-bcd numbers are easy if (!bcd) return --num; // decrement the number num--; // normalize the bcd number if ((num & 0xf) == 0xf) { num &= 0xfff9; num -= 0x10; if ((num & 0xf0) == 0xf0) { num &= 0xff9f; num -= 0x100; if ((num & 0xf00) == 0xf00) { num &= 0xf9ff; num -= 0x1000; } } } return num; } /** * Returns text representation of the timer * * @return text representation */ public String toString() { return "workMode " + workMode + ", rwMode " + rwMode + ", bcd " + bcd + ", running " + countRunning + ", hiLoRead " + countHiLoRead + ", hiLoWrite " + countHiLoWrite + ", wavePhase " + countWavePhase + ", current " + countCurrent + ", initial " + countInitial + ", initialNew " + countInitialNew + ", initialNewComplete " + countInitialNewComplete + ", countNull " + countNull + ", latch " + countLatch + ", latchAvail " + countLatchAvailable + ", statusLatch" + statusLatch + ", statusLatchAvail " + statusLatchAvailable + ", signalOut " + signalOutput + ", signalGate " + signalGate; } } // ------------------------ Global I8254 handling routines --------------------------- // available counters protected Counter[] cnt; // counter signal listeners private List listeners = new ArrayList(); /** * Public contructor: initialize counters */ public I8254() { // create new counters cnt = new Counter[3]; cnt[0] = new Counter(0); cnt[1] = new Counter(1); cnt[2] = new Counter(2); } /** * ResPlugin interface: do reset routine external to CPU unit (called when CPU does a reset) * * @param ticks number of ticks since last method call * @return number of ticks to add to the ticks count */ public int doCPUReset(CPU cpu, int ticks) { cnt[0].doCPUReset(cpu, ticks); cnt[1].doCPUReset(cpu, ticks); cnt[2].doCPUReset(cpu, ticks); return 0; } /** * Returns I8254 counter * * @param counter counter number * @return counter object */ public Counter getCounter(int counter) { return counter >= 0 && counter < 3 ? cnt[counter] : null; } /** * Get byte from I8254 port * * @param addr int containing the port number (ports 0 - 3 are relevant) * @return int containing the unsigned byte */ public int getPort(int addr) { addr &= 3; return addr < 3 ? cnt[addr].getData() : 0xff; } /** * Set byte to I8254 port * * @param addr int containing the port number (ports 0 - 3 are relevant) * @param value int containing the unsigned byte */ public void setPort(int addr, int value) { addr &= 3; // set counter data if (addr < 3) { cnt[addr].setData(value); // process the command } else { int sc = (value >> 6) & 3; // process standard command if (sc != 3) { int rw = (value >> 4) & 3; // process mode set command if (rw != 0) { int mode = (value >> 1) & 7; boolean bcd = (value & 1) == 1; cnt[sc].setControl(mode, rw, bcd); // process counter latch command } else cnt[sc].latchCount(); // process read-back command } else { if ((value & 2) == 2) { if ((value & 0x10) == 0) cnt[0].latchStatus(); if ((value & 0x20) == 0) cnt[0].latchCount(); } if ((value & 4) == 4) { if ((value & 0x10) == 0) cnt[1].latchStatus(); if ((value & 0x20) == 0) cnt[1].latchCount(); } if ((value & 8) == 8) { if ((value & 0x10) == 0) cnt[2].latchStatus(); if ((value & 0x20) == 0) cnt[2].latchCount(); } } } } /** * Do one counter ticks * * @param counter counter number to process (0-2) * @param ticks number of ticks to process the timer with */ public void processTimer(int counter, int ticks) { if (counter >= 0 && counter < 3) cnt[counter].tick(ticks); } /** * Sets the signal Gate on the given counter * * @param counter counter number on which to set the gate * @param signal signal level (true/false) */ public void setSignalGate(int counter, boolean signal) { if (counter >= 0 && counter < 3) cnt[counter].setSignalGate(signal); } /** * Adds listener to the timer * * @param listener listener to add */ public synchronized void addListener(I8254Listener listener) { if (!listeners.contains(listener)) listeners.add(listener); } /** * Removes listener to the timer * * @param listener listener to remove */ public synchronized void removeListener(I8254Listener listener) { listeners.remove(listener); } /** * Processes counter signal using configured listeners * * @param counter counter number to process the signal from */ private void processListeners(int counter) { for (I8254Listener listener : listeners) listener.processCounterInterrupt(counter); } /** * Returns text representation of the timer * * @return text representation */ public String toString() { return "cnt0: " + cnt[0] + ", cnt1: " + cnt[1] + ", cnt2: " + cnt[2]; } }