// GPIO matrix, IO_MUX and pad model export class GPIO { constructor(soc) { this.soc = soc; this.iomux = new IoMux(soc); this.listeners = []; this.reset(); } reset() { this.r = new Int32Array(0x400); this.out = new Uint32Array(2); this.en = new Uint32Array(2); this.pin = new Int32Array(40); this.inSel = new Int32Array(256); this.outSel = new Int32Array(40); for (let i = 0; i < 40; i++) this.outSel[i] = 256; this.status = new Uint32Array(2); this.extLow = new Uint32Array(2); // external devices pulling pad low this.extHigh = new Uint32Array(2); // external devices driving pad high this.pullup = new Uint32Array(2); this.strap = 0x12; this.lastLevel = new Uint32Array(2); this.sigOut = new Map(); // signal -> value (peripheral outputs) this.pad = new Uint8Array(40); this.matrixVersion = (this.matrixVersion || 0) + 1; } // pad electrical level (0/1) level(n) { const w = n >> 5, b = 1 << (n & 31); const drv = this.driving(n); if (drv === 0) return 0; if (drv === 1) { return (this.extLow[w] & b) ? 0 : 1; } // not driven by chip if (this.extLow[w] & b) return 0; if (this.extHigh[w] & b) return 1; if (this.pullup[w] & b) return 1; return (this.floating ? this.floating[n] : 0) | 0; } // returns 0, 1 or -1 (high-z) driving(n) { const rio = this.soc.rtcio; if (rio && rio.drives(n)) return rio.outLevel(n); const sel = this.outSel[n]; const sig = sel & 0x1FF; let en, val; if (sig === 256 || sig === 0x100) { en = (this.en[n >> 5] >>> (n & 31)) & 1; val = (this.out[n >> 5] >>> (n & 31)) & 1; } else { const oenSel = (sel >> 10) & 1; en = oenSel ? ((this.en[n >> 5] >>> (n & 31)) & 1) : 1; if (((sel >> 11) & 1)) en ^= 1; val = this.sigOut.get(sig) | 0; if ((sel >> 9) & 1) val ^= 1; } if (!en) return -1; if ((this.pin[n] >> 2) & 1) return val ? -1 : 0; // open drain return val; } inputLevel(n) { return this.level(n); } reg32Read(off) { switch (off) { case 0x04: return this.out[0]; case 0x10: return this.out[1]; case 0x20: return this.en[0]; case 0x2c: return this.en[1]; case 0x38: return this.strap; case 0x3c: return this.inReg(0); case 0x40: return this.inReg(1); case 0x44: return this.status[0]; case 0x50: return this.status[1]; case 0x60: return this.intFor(1, 0); case 0x68: return this.intFor(0, 0); case 0x74: return this.intFor(1, 1); case 0x7c: return this.intFor(0, 1); } if (off >= 0x88 && off < 0x88 + 40 * 4) return this.pin[(off - 0x88) >> 2]; if (off >= 0x130 && off < 0x530) return this.inSel[(off - 0x130) >> 2]; if (off >= 0x530 && off < 0x530 + 40 * 4) return this.outSel[(off - 0x530) >> 2]; return this.r[off >> 2]; } inReg(w) { let v = 0; for (let i = 0; i < 32; i++) { const n = w * 32 + i; if (n < 40 && this.level(n)) v |= (1 << i); } return v | 0; } intFor(cpu, w) { // cpu 0 = PRO (int_ena bit 2, nmi bit3), cpu 1 = APP (bit 0, nmi bit1) let v = 0; const st = this.status[w]; for (let i = 0; i < 32; i++) { const n = w * 32 + i; if (n >= 40) break; if (!((st >>> i) & 1)) continue; const ena = (this.pin[n] >> 13) & 31; if (cpu === 0 ? (ena & 4) : (ena & 1)) v |= (1 << i); } return v | 0; } read(off) { return this.reg32Read(off); } write(off, v) { v |= 0; const old = this.snapshot(); switch (off) { case 0x04: this.out[0] = v; break; case 0x08: this.out[0] |= v; break; case 0x0c: this.out[0] &= ~v; break; case 0x10: this.out[1] = v & 0xFF; break; case 0x14: this.out[1] |= v & 0xFF; break; case 0x18: this.out[1] &= ~v; break; case 0x20: this.en[0] = v; break; case 0x24: this.en[0] |= v; break; case 0x28: this.en[0] &= ~v; break; case 0x2c: this.en[1] = v & 0xFF; break; case 0x30: this.en[1] |= v & 0xFF; break; case 0x34: this.en[1] &= ~v; break; case 0x48: this.status[0] |= v; break; case 0x4c: this.status[0] &= ~v; break; case 0x54: this.status[1] |= v; break; case 0x58: this.status[1] &= ~v; break; default: if (off >= 0x88 && off < 0x88 + 40 * 4) this.pin[(off - 0x88) >> 2] = v; else if (off >= 0x130 && off < 0x530) this.inSel[(off - 0x130) >> 2] = v; else if (off >= 0x530 && off < 0x530 + 40 * 4) { this.outSel[(off - 0x530) >> 2] = v; this.matrixVersion++; } else this.r[off >> 2] = v; } this.padsChanged(old); this.updateInt(); } snapshot() { const a = new Uint8Array(40); for (let i = 0; i < 40; i++) a[i] = this.level(i); return a; } // called whenever pad levels might have changed (chip drive or external) padsChanged(old) { old = old || this.lastSnap; const now = this.snapshot(); this.lastSnap = now; if (!old) return; for (let n = 0; n < 40; n++) { if (old[n] === now[n]) continue; const w = n >> 5, b = 1 << (n & 31); const type = (this.pin[n] >> 7) & 7; if ((type === 1 && now[n]) || (type === 2 && !now[n]) || type === 3) this.status[w] |= b; for (const l of this.listeners) l(n, now[n]); } for (const t of this.levelWatch || []) t(); } updateInt() { // level interrupts for (let n = 0; n < 40; n++) { const type = (this.pin[n] >> 7) & 7; const w = n >> 5, b = 1 << (n & 31); if (type === 4 && !this.level(n)) this.status[w] |= b; else if (type === 5 && this.level(n)) this.status[w] |= b; } this.soc.setIrqCpu(0, 22, this.intFor(0, 0) | this.intFor(0, 1)); this.soc.setIrqCpu(1, 22, this.intFor(1, 0) | this.intFor(1, 1)); } // external drive from devices setExt(n, low) { const w = n >> 5, b = 1 << (n & 31); const old = this.snapshot(); if (low) this.extLow[w] |= b; else this.extLow[w] &= ~b; this.padsChanged(old); this.updateInt(); } setPullup(n, on) { const w = n >> 5, b = 1 << (n & 31); if (on) this.pullup[w] |= b; else this.pullup[w] &= ~b; } setSignalOut(sig, v) { this.sigOut.set(sig, v); } // peripheral input: value for input signal (e.g. UART RX) = pad level via inSel signalIn(sig) { const s = this.inSel[sig]; const pad = s & 0x3F; if (s & 0x40) return 1; // constant high if (pad === 0x30) return 0; // constant low if (pad === 0x38 || (s & 0x40)) return 1; return this.level(pad); } } class IoMux { constructor(soc) { this.soc = soc; this.r = new Int32Array(0x400); } read(off) { return this.r[off >> 2]; } write(off, v) { this.r[off >> 2] = v; } }