/* RS232Regs.java (c) 2014 Ed Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.machine.ti99.dsr.rs232; import v9t9.common.cpu.ICpu; import v9t9.common.dsr.IRS232Handler.DataSize; import v9t9.common.dsr.IRS232Handler.Parity; import v9t9.common.dsr.IRS232Handler.Stop; import v9t9.common.machine.IMachine; import v9t9.engine.Dumper; import v9t9.engine.dsr.rs232.RS232; import v9t9.machine.ti99.cpu.Cpu9900; import ejs.base.properties.IPersistable; import ejs.base.properties.IProperty; import ejs.base.settings.ISettingSection; /** The RS232 device is characterized by a dense packing of registers into a small number of bits. Depending on the settings of CRU bits 11-14, a different register set is selected. We use an array to store these registers. Also, certain bits are called "flag" bits since writing their value instantiates the changes made to the register. This saves us a bit of time. */ public class RS232Regs implements IPersistable { static final public short CRU_BASE = 0x1300; static final public int RATE_DIV8 = 0x400; static final public int RATE_MASK = 0x3ff; static final public int CTRL_RCL0 = 1; static final public int CTRL_RCL1 = 2; static final public int CTRL_CLK4M = 8; static final public int CTRL_Podd = 16; static final public int CTRL_Penb = 32; static final public int CTRL_SBS2 = 64; // 00 = 1.5, 01 = 2, 1X = 1 static final public int CTRL_SBS1 = 128; // static final public int RS_RTSON = (1<<16); // [16] request to send static final public int RS_BRKON = (1<<17); // [17] break on static final public int RS_RIENB = (1<<18); // [18] receive interrupt enable static final public int RS_XBIENB = (1<<19); // [19] xmit buffer interrupt enable static final public int RS_TIMENB = (1<<20); // [20] timer interrupt enable static final public int RS_DSCENB = (1<<21); // [21] data status change interrupt enable // [22-30] unused static final public int RS_RESET = (1<<31); // [31] reset static final public int RS_RXCHAR = (0xff); // [0-7] received char // [8] always zero static final public int RS_RCVERR = (1<<9); // [9] receive error static final public int RS_RPER = (1<<10); // [10] receive parity error static final public int RS_ROVER = (1<<11); // [11] receive overrun error static final public int RS_RFER = (1<<12); // [12] receive framing error static final public int RS_RFBD = (1<<13); // [13] receive full bit detect static final public int RS_RSBD = (1<<14); // [14] receive start bit detect static final public int RS_RIN = (1<<15); // [15] receive input static final public int RS_RBINT = (1<<16); // [16] receiver interrupt (rbrl - rienb) static final public int RS_XBINT = (1<<17); // [17] transmitter interrupt (xbre - xbienb) // [18] always zero static final public int RS_TIMINT = (1<<19); // [19] timer interrupt (timelp - timenb) static final public int RS_DSCINT = (1<<20); // [20] data set status change interrupt (dsch - dscenb) static final public int RS_RBRL = (1<<21); // [21] receive buffer register loaded static final public int RS_XBRE = (1<<22); // [22] transmit buffer register empty static final public int RS_XSRE = (1<<23); // [23] transmit shift register empty static final public int RS_TIMERR = (1<<24); // [24] time error static final public int RS_TIMELP = (1<<25); // [25] timer elapsed static final public int RS_RTS = (1<<26); // [26] request to send static final public int RS_DSR = (1<<27); // [27] data set ready static final public int RS_CTS = (1<<28); // [28] clear to send static final public int RS_DSCH = (1<<29); // [29] data set status change static final public int RS_FLAG = (1<<30); // [30] register load control flag set static final public int RS_INT = (1<<31); // [31] interrupt private RS232 rs232; // WRITE: private byte regsel; // 0..0xf of private short wrbits; // bits written to register // wrbits copied here private short txchar; // [loadctrl=0] transmitted char private short xmitrate; // [loadctrl>=1] transmit rate register: timing private short rcvrate; // [loadctrl>=2] receive rate register: timing private int recvbps, xmitbps; // cached calculation for baud rate private short invl; // [loadctrl>=4] interval register: ??? private short ctrl; // [loadctrl>=8] control register: clock, parity, stop private int wrport; // bitmap for remaining write registers // READ: private int rdport; // bitmap for read registers private Dumper dumper; private IMachine machine; private IProperty cyclesPerSecond; /** * */ public RS232Regs(IMachine machine, RS232 rs232, Dumper dumper) { this.machine = machine; this.rs232 = rs232; this.dumper = dumper; cyclesPerSecond = machine.getSettings().get(ICpu.settingCyclesPerSecond); } public int getWordSize() { return ((ctrl & (RS232Regs.CTRL_RCL0 + RS232Regs.CTRL_RCL1)) + 5); } /** * */ public void clear() { wrbits = txchar = xmitrate = rcvrate = invl = ctrl = 0; recvbps = xmitbps = 0; wrport = rdport = 0; regsel = 0xf; rs232.clear(); } /** * @return */ public short getRegisterSelect() { return regsel; } private int calcBPSRate(short rate) { int bps; // BPS = 3 MHz / ((CLK4M ? 4 : 3) * 2 * rate x 8*DIV8) int div; // input speed div = (((ctrl & RS232Regs.CTRL_CLK4M) != 0 ? 4 : 3) * 2 * (rate & RS232Regs.RATE_MASK) * ((rate & RS232Regs.RATE_DIV8) != 0 ? 8 : 1)); if (div == 0) bps = 50; else bps = (cyclesPerSecond.getInt() / div); return bps; } private int calcIntervalRate(short rate) { int hz; // BPS = 3 MHz / rate int div; // input speed div = ((ctrl & RS232Regs.CTRL_CLK4M) != 0 ? 4 : 3) * 64 * (rate & 0xff); if (div == 0) hz = 0; else hz = (cyclesPerSecond.getInt() / div); return hz; } public enum RegisterSelect { CONTROL, INTERVAL, RECVRATE, XMITRATE, EMIT } private RegisterSelect deriveRegisterSelect() { RegisterSelect sel = null; if ((regsel & 8) != 0) sel = RegisterSelect.CONTROL; else if ((regsel & 4) != 0) sel = RegisterSelect.INTERVAL; else if ((regsel & 2) != 0) sel = RegisterSelect.RECVRATE; else if ((regsel & 1) != 0) sel = RegisterSelect.XMITRATE; else sel = RegisterSelect.EMIT; return sel; } /* * Flag bits for registers multiplexed through loadctrl */ int FLAG_TXCHAR() { return (1<<(getWordSize()-1)); } static final int FLAG_XMITRATE = (1<<10); static final int FLAG_RCVRATE = (1<<10); static final int FLAG_INVL = (1<<7); static final int FLAG_CTRL = (1<<7); /** * Apply register changes once the last bit in a register is set (dbit == 1<<bit) * or when a register select bit is changed (dbit == 0). */ public void triggerChange(int dbit) { RegisterSelect sel = deriveRegisterSelect(); if (sel == RegisterSelect.CONTROL) { if (dbit == 0 || dbit == FLAG_CTRL) { this.ctrl = (short) (getWriteBits() & 0x7ff); dump(); rs232.setControlBits(getDataSize(), getParity(), getStop()); rs232.flushBuffers(); if (dbit != 0) { this.regsel = (byte) ((regsel & ~8) | 4); } } } if (sel == RegisterSelect.INTERVAL) { if (dbit == 0 || dbit == FLAG_INVL) { this.invl = (short) (getWriteBits() & 0xff); dump(); int rate = calcIntervalRate(invl); rs232.setIntervalRate(rate); rs232.flushBuffers(); if (dbit != 0) { this.regsel = (byte) ((regsel & ~4) | 2); } } } if (sel == RegisterSelect.RECVRATE) { if (dbit == 0 || dbit == FLAG_RCVRATE) { rcvrate = ((short) (getWriteBits() & 0x7ff)); recvbps = calcBPSRate(rcvrate); dump(); rs232.setReceiveRate(recvbps); rs232.flushBuffers(); if (dbit != 0) { if ((regsel & 1) != 0) sel = RegisterSelect.XMITRATE; // auto-set next this.regsel = (byte) ((regsel & ~2) | 1); } } } if (sel == RegisterSelect.XMITRATE) { if (dbit == 0 || dbit == FLAG_XMITRATE) { xmitrate = ((short) (getWriteBits() & 0x7ff)); xmitbps = calcBPSRate(xmitrate); dump(); rs232.setTransmitRate(xmitbps); rs232.flushBuffers(); if (dbit != 0) this.regsel = (byte) (regsel & ~1); } } if (sel == RegisterSelect.EMIT) { if (dbit == FLAG_TXCHAR()) { this.txchar = (short) (getWriteBits() & (0xff >> (8 - getWordSize()))); dump(); rs232.transmitChar((byte) txchar); updateFlagsAndInts(); } } } // Update status bits when buffer state changes, // possibly triggering an interrupt. public void updateFlagsAndInts() { boolean interruptible = false; // all buffered chars are immediately // available to be read. // if (!rs232.getRecvBuffer().isEmpty()) { rdport |= RS_RBRL; // trigger interrupt? if ((wrport & RS_RIENB) != 0) { rdport |= RS_RBINT; interruptible = true; } } else { rdport &= ~(RS_RBRL | RS_RBINT); } // contrary to popular opinion, we will set RS_XBRE // here if the buffer is NOT FULL, so the 99/4A // can continue to stuff the buffer. // if (!rs232.getXmitBuffer().isFull()) { rdport |= RS_XBRE; // trigger interrupt? if ((wrport & RS_XBIENB) != 0) { rdport |= RS_XBINT; interruptible = true; } } else { rdport &= ~(RS_XBRE | RS_XBINT); } // TODO: data lines // NOTE: when buffering, the DSR state can change physically // while interrupts should still be generated logically. // Be sure to bias the DSR state by the buffer state. // // The typical routine for RS_RIENB goes like this: // // <interrupt> // test bit 31 -- must be 1 else RESET // test bit RBINT -- must be 1 else RESET // test bit DSR -- must be 1 else RESET // test bit RBRL -- must be 1 else RESET // read char // set bit RIENB -- ACK; this resets RBRL // // We will immediately set RBRL and RBINT again if needed. // We won't reset the interrupt state until all the bits // are off. if (interruptible) { dumper.info("RS232: *** Interrupt ***"); rdport |= RS_INT; machine.getCru().triggerInterrupt(Cpu9900.INTLEVEL_INTREQ); } else { rdport &= ~RS_INT; machine.getCru().acknowledgeInterrupt(Cpu9900.INTLEVEL_INTREQ); } } public void dump() { StringBuilder sb = new StringBuilder(); sb.append("RS232:\n"); sb.append(String.format(("* regsel=%s\t\n"), deriveRegisterSelect())); if (regsel >= 8) { sb.append("* "); sb.append(String.format(("ctrl=%02X\nrcl0=%b,rcl1=%b, clk4m=%b, Podd=%b,Penb=%b, SBS2=%b,SBS1=%b\t"), ctrl, 0 != (ctrl & RS232Regs.CTRL_RCL0), 0 != (ctrl & RS232Regs.CTRL_RCL1), 0 != (ctrl & RS232Regs.CTRL_CLK4M), 0 != (ctrl & RS232Regs.CTRL_Podd), 0 != (ctrl & RS232Regs.CTRL_Penb), 0 != (ctrl & RS232Regs.CTRL_SBS2), 0 != (ctrl & RS232Regs.CTRL_SBS1))); } if (regsel < 8 && regsel >= 4) { sb.append("* "); sb.append(String.format(("invl=%03X\t"), invl)); } if (regsel < 4 && regsel >= 2) { sb.append("* "); sb.append(String.format(("rcvrate=%03X\t"), rcvrate)); } if (regsel < 2 && regsel >= 1) { sb.append("* "); sb.append(String.format(("xmitrate=%03X\t"), xmitrate)); } if (regsel < 1) { sb.append("* "); sb.append(String.format(("txchar=%02X (%c)\t"), txchar, txchar)); } sb.append(String.format(("recvbps=%d, xmitbps=%d\n"), recvbps, xmitbps)); /* module_logger(&realRS232DSR, _L | L_3,("rtson=%d, brkon=%d, rienb=%d, xbienb=%d, timenb=%d, dscenb=%d\n"), !!(wrport&RS_RTSON), !!(wrport&RS_BRKON), !!(wrport&RS_RIENB), !!(wrport&RS_XBIENB), !!(wrport&RS_TIMEMB), !!(wrport&RS_DSCENB)); module_logger(&realRS232DSR, _L | L_3,("rcverr=%d,rper=%d,rover=%d,rfer=%d,rfbd=%d,rsbd=%d,rin=%d\n"), !!(rdport&RS_RCVERR), !!(rdport&RS_RPER), !!(rdport&RS_ROVER), !!(rdport&RS_RFER), !!(rdport&RS_RFBD), !!(rdport&RS_RSBD), !!(rdport&RS_RIN)); module_logger(&realRS232DSR, _L | L_3,("rbint=%d,xbint=%d,timint=%d,dscint=%d,rbrl=%d,xbre=%d,xsre=%d\n"), !!(rdport&RS_RBINT), !!(rdport&RS_XBINT), !!(rdport&RS_TIMINT), !!(rdport&RS_DSCINT), !!(rdport&RS_RBRL), !!(rdport&RS_XBRE), !!(rdport&RS_XSRE)); module_logger(&realRS232DSR, _L | L_3,("timerr=%d,timelp=%d,rts=%d,dsr=%d,cts=%d,dsch=%d,flag=%d,int=%d\n"), !!(rdport&RS_TIMERR), !!(rdport&RS_TIMELP), !!(rdport&RS_RTS), !!(rdport&RS_DSR), !!(rdport&RS_CTS), !!(rdport&RS_DSCH), !!(rdport&RS_FLAG), !!(rdport&RS_INT)); */ dumper.info(sb.toString()); } public void setReadPort(int rdport) { this.rdport = rdport; } public int getReadPort() { return rdport; } public void setWritePort(int flags) { wrport = flags; } public int getWritePort() { return wrport; } public void setWriteBits(int flags) { wrbits = (short) flags; } public int getWriteBits() { return wrbits; } public RS232 getRS232() { return rs232; } /** * @param i */ public void setRegisterSelect(int i) { regsel = (byte) i; } /** * @param data * @param bit */ public void updateRegisterSelect(int data, int bit) { if (data != 0) regsel |= bit; else regsel &= ~bit; } /** * @param data * @param bit */ public void updateWriteBits(int data, int bit) { if (data != 0) wrbits |= bit; else wrbits &= ~bit; } /** * @param data * @param bit */ public void updateWritePort(int data, int bit) { if (data != 0) wrport |= bit; else wrport &= ~bit; } /** * @return */ public DataSize getDataSize() { switch ((ctrl & CTRL_RCL0 + CTRL_RCL1) >> 0) { case 0: return DataSize.FIVE; case 1: return DataSize.SIX; case 2: return DataSize.SEVEN; case 3: default: return DataSize.EIGHT; } } /** * @return */ public Parity getParity() { switch ((ctrl & CTRL_Penb + CTRL_Podd) >> 4) { default: case 0: case 1: return Parity.NONE; case 2: return Parity.EVEN; case 3: return Parity.ODD; } } /** * @return */ public Stop getStop() { switch ((ctrl & CTRL_SBS1 + CTRL_SBS2) >> 6) { case 0: return Stop.STOP_1_5; case 1: return Stop.STOP_2; case 2: case 3: default: return Stop.STOP_1; } } /* (non-Javadoc) * @see ejs.base.properties.IPersistable#loadState(ejs.base.settings.ISettingSection) */ @Override public void loadState(ISettingSection section) { if (section == null) return; regsel = (byte) section.getInt("RegisterSelect"); wrbits = (short) section.getInt("WriteBits"); txchar = (short) section.getInt("XmitChar"); xmitrate = (short) section.getInt("XmitRate"); rcvrate = (short) section.getInt("RecvRate"); recvbps = section.getInt("RecvBps"); xmitbps = section.getInt("XmitBps"); invl = (short) section.getInt("Interval"); ctrl = (short) section.getInt("Control"); wrport = (short) section.getInt("WritePort"); rdport = (short) section.getInt("ReadPort"); } /* (non-Javadoc) * @see ejs.base.properties.IPersistable#saveState(ejs.base.settings.ISettingSection) */ @Override public void saveState(ISettingSection section) { section.put("RegisterSelect", regsel); section.put("WriteBits", wrbits); section.put("XmitChar", txchar); section.put("XmitRate", xmitrate); section.put("RecvRate", rcvrate); section.put("RecvBps", recvbps); section.put("XmitBps", xmitbps); section.put("Interval", invl); section.put("Control", ctrl); section.put("WritePort", wrport); section.put("ReadPort", rdport); } }