From 15e46176ffee2b18ee6b8d65d8256d27e5593999 Mon Sep 17 00:00:00 2001 From: John Hardy Date: Mon, 9 Feb 2026 14:28:06 +1100 Subject: [PATCH 1/3] Improve SD SPI addressing and docs --- docs/tec1g-emulation-review.md | 2 +- src/platforms/tec1g/README.md | 4 ++-- src/platforms/tec1g/sd-spi.ts | 8 ++++++-- tests/platforms/tec1g/sd-spi.test.ts | 21 +++++++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/tec1g-emulation-review.md b/docs/tec1g-emulation-review.md index 65d7a0c..188f7eb 100644 --- a/docs/tec1g-emulation-review.md +++ b/docs/tec1g-emulation-review.md @@ -405,7 +405,7 @@ Disco LEDs (Fullisik under mechanical keys) — **N/A** | Matrix keyboard | Med | **Complete** | 100% | | CONFIG DIP switch | Low | **Complete** | 100% | | RTC (DS1302) | Low | **Complete** | 100% | -| SD card SPI | Low | **Missing** | 0% | +| SD card SPI | Low | **Complete** | 95% | | Cartridge | Low | **Partial** | 40% | | Joystick | Low | **Missing** | 0% | | Status LED bar | Low | **Missing** | 0% | diff --git a/src/platforms/tec1g/README.md b/src/platforms/tec1g/README.md index 138be4c..4f326d6 100644 --- a/src/platforms/tec1g/README.md +++ b/src/platforms/tec1g/README.md @@ -71,8 +71,8 @@ The TEC-1G panel can switch speed modes; the serial timing assumes FAST mode. - Bit 4: FF-D4 (reserved — not yet decoded). - Bit 5: CAPS (caps lock — not yet decoded). - Bits 6-7: FF-D5/FF-D6 (reserved — not yet decoded). -- `OUT 0xFC`: RTC DS1302 (stub — not yet emulated, reads return 0xFF). -- `OUT 0xFD`: SD card SPI (stub — not yet emulated, reads return 0xFF). +- `OUT 0xFC`: RTC DS1302 (bit-banged emulation). +- `OUT 0xFD`: SD card SPI (bit-banged emulation, SPI mode). ## Serial (bitbang) - TX uses bit 6 on `OUT 0x01`; RX uses bit 7 on `IN 0x00` (mirrored on `IN 0x03`). diff --git a/src/platforms/tec1g/sd-spi.ts b/src/platforms/tec1g/sd-spi.ts index 883ba05..750c863 100644 --- a/src/platforms/tec1g/sd-spi.ts +++ b/src/platforms/tec1g/sd-spi.ts @@ -11,6 +11,7 @@ type SdCommand = { export type SdSpiOptions = { csMask?: number; csActiveLow?: boolean; + highCapacity?: boolean; image?: Uint8Array; }; @@ -24,6 +25,7 @@ const DEFAULT_CS_MASK = 0x04; export class SdSpi { private csMask: number; private csActiveLow: boolean; + private highCapacity: boolean; private csActive = false; private clk = false; private inShift = 0; @@ -47,6 +49,7 @@ export class SdSpi { public constructor(options: SdSpiOptions = {}) { this.csMask = options.csMask ?? DEFAULT_CS_MASK; this.csActiveLow = options.csActiveLow !== false; + this.highCapacity = options.highCapacity !== false; this.image = options.image ?? null; } @@ -229,7 +232,8 @@ export class SdSpi { break; } case 58: { - this.pendingResponse = [this.ready ? 0x00 : 0x01, 0x40, 0x00, 0x00, 0x00]; + const ocr = this.highCapacity ? 0x40 : 0x00; + this.pendingResponse = [this.ready ? 0x00 : 0x01, ocr, 0x00, 0x00, 0x00]; this.delayBytes = 1; break; } @@ -253,7 +257,7 @@ export class SdSpi { } private readBlock(arg: number): number[] { - const start = arg >>> 0; + const start = this.highCapacity ? ((arg >>> 0) << 9) >>> 0 : (arg >>> 0); const payload = new Array(512).fill(0x00); if (!this.image || this.image.length === 0) { return payload; diff --git a/tests/platforms/tec1g/sd-spi.test.ts b/tests/platforms/tec1g/sd-spi.test.ts index aeeb4fe..087edcf 100644 --- a/tests/platforms/tec1g/sd-spi.test.ts +++ b/tests/platforms/tec1g/sd-spi.test.ts @@ -123,4 +123,25 @@ describe('SdSpi', () => { expect(readByte(spi)).toBe(0x00); expect(readByte(spi)).toBe(0x5a); }); + + it('treats CMD17 argument as block address for high capacity cards', () => { + const image = new Uint8Array(2048); + image[0x0202] = 0xa5; + const spi = new SdSpi({ csMask: CS_BIT, image, highCapacity: true }); + writeSpi(spi, 0x00); + sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]); + readResponseByte(spi); + sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]); + readResponseByte(spi); + sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]); + readResponseByte(spi); + sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]); + readResponseByte(spi); + sendCommand(spi, [0x51, 0x00, 0x00, 0x00, 0x01, 0xff]); + expect(readResponseByte(spi)).toBe(0x00); + expect(readByte(spi)).toBe(0xfe); + expect(readByte(spi)).toBe(0x00); + expect(readByte(spi)).toBe(0x00); + expect(readByte(spi)).toBe(0xa5); + }); }); From a74854e3f39efd24d9de7a7a3076caf95642ff78 Mon Sep 17 00:00:00 2001 From: John Hardy Date: Mon, 9 Feb 2026 14:38:54 +1100 Subject: [PATCH 2/3] Clarify SD SPI addressing --- src/platforms/tec1g/sd-spi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platforms/tec1g/sd-spi.ts b/src/platforms/tec1g/sd-spi.ts index 750c863..f5cb111 100644 --- a/src/platforms/tec1g/sd-spi.ts +++ b/src/platforms/tec1g/sd-spi.ts @@ -257,6 +257,7 @@ export class SdSpi { } private readBlock(arg: number): number[] { + // SDHC uses block (LBA) addressing; SDSC uses byte addressing. const start = this.highCapacity ? ((arg >>> 0) << 9) >>> 0 : (arg >>> 0); const payload = new Array(512).fill(0x00); if (!this.image || this.image.length === 0) { From 00fe1dc739ded1a6de1bcbd7a94ec54e3cb9da90 Mon Sep 17 00:00:00 2001 From: John Hardy Date: Mon, 9 Feb 2026 14:44:02 +1100 Subject: [PATCH 3/3] Clean SD SPI checklist --- docs/tec1g-emulation-review.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tec1g-emulation-review.md b/docs/tec1g-emulation-review.md index 188f7eb..c986f13 100644 --- a/docs/tec1g-emulation-review.md +++ b/docs/tec1g-emulation-review.md @@ -767,7 +767,6 @@ External add-on emulation. Fully independent of other stages. - [x] Add `sdEnabled: boolean` and optional `sdImagePath: string` config options - [x] Instantiate `SdSpi` in runtime when enabled - [x] Route port 0xFD writes to `sdSpi.write(value)`, reads to `sdSpi.read()` -- [ ] If `sdImagePath` provided, load file as virtual disk image for block reads - [x] If `sdImagePath` provided, load file as virtual disk image for block reads - [x] Add SD presence indicator to SYS_INPUT (not applicable — no dedicated bit in schematic) - [x] Unit tests: end-to-end port 0xFD initialization sequence