diff --git a/docs/tec1g-emulation-review.md b/docs/tec1g-emulation-review.md index 65d7a0c..c986f13 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% | @@ -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 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..f5cb111 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,8 @@ export class SdSpi { } private readBlock(arg: number): number[] { - const start = arg >>> 0; + // 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) { 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); + }); });