summaryrefslogtreecommitdiff
path: root/src/peripherals/avrdx-nvmctrl.ts
blob: 23210232ca3d9ed0022f24b948ec2a654e56ab37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// AVR-Dx NVMCTRL + Mapped EEPROM
// EEPROM is memory-mapped at 0x1400-0x14FF and accessed via NVMCTRL commands.

import type { CPU } from 'avr8js/cpu/cpu';
import type { AVRDxCCP } from './avrdx-ccp';

const CTRLA    = 0x0000;
// const CTRLB    = 0x0001;
// const STATUS   = 0x0002;
// const INTCTRL  = 0x0003;
// const INTFLAGS = 0x0004;
// const DATAL    = 0x0006;
// const DATAH    = 0x0007;
// const ADDR0    = 0x0008;
// const ADDR1    = 0x0009;
// const ADDR2    = 0x000A;
// const ADDR3    = 0x000B;

// CMD values
const CMD_NONE_gc           = 0x00;
const CMD_NOOP_gc           = 0x01;
// const CMD_FLWR_gc           = 0x02;
// const CMD_FLPER_gc          = 0x08;
// const CMD_FLMPER2_gc        = 0x09;
// const CMD_FLMPER4_gc        = 0x0A;
// const CMD_FLMPER8_gc        = 0x0B;
// const CMD_FLMPER16_gc       = 0x0C;
// const CMD_FLMPER32_gc       = 0x0D;
const CMD_EEWR_gc           = 0x12;
const CMD_EEERWR_gc         = 0x13;
const CMD_EEBER_gc          = 0x18;
// const CMD_EEMBER2_gc        = 0x19;
// const CMD_EEMBER4_gc        = 0x1A;
// const CMD_EEMBER8_gc        = 0x1B;
// const CMD_EEMBER16_gc       = 0x1C;
// const CMD_EEMBER32_gc       = 0x1D;

export class AVRDxNVMCTRL {
  readonly eeprom: Uint8Array;
  /** Page buffer for EEPROM writes (tracks which bytes have been written) */
  private pageBuffer: Uint8Array;
  private pageBufferDirty: Uint8Array;

  constructor(cpu: CPU, base: number, start: number, private size: number, private ccp: AVRDxCCP, init: undefined | Uint8Array = undefined) {
    this.eeprom = new Uint8Array(size);
    this.pageBuffer = new Uint8Array(size);
    this.pageBufferDirty = new Uint8Array(size);

    this.eeprom.fill(0xFF);
    if (init) this.loadEeprom(init);

    // CTRLA - CCP protected, executes NVM commands
    cpu.writeHooks[base + CTRLA] = (value: number) => {
      if (this.ccp.isUnlocked()) {
        cpu.data[base + CTRLA] = value;
        this.executeCommand(value);
      }
      return true;
    };

    // Mapped EEPROM read hooks (0x1400-0x14FF)
    for (let i = 0; i < size; i++) {
      cpu.readHooks[start + i] = () => this.eeprom[i];

      // Writes to mapped EEPROM go to the page buffer
      cpu.writeHooks[start + i] = (value: number) => {
        this.pageBuffer[i] = value;
        this.pageBufferDirty[i] = 1;

        // Check the current active command
        const cmd = cpu.data[base + CTRLA];

        if (cmd === CMD_EEERWR_gc) {
          // Erase+write: replace byte directly
          this.eeprom[i] = value;
          this.pageBufferDirty[i] = 0;
        } else if (cmd === CMD_EEWR_gc) {
          // Write-only: AND with existing data (can only clear bits)
          this.eeprom[i] &= value;
          this.pageBufferDirty[i] = 0;
        }
        return true;
      };
    }
  }

  loadEeprom(data: Uint8Array) {
    this.eeprom.set(data.subarray(0, this.size));
  }

  private executeCommand(cmd: number) {
    switch (cmd) {
      case CMD_NONE_gc:
      case CMD_NOOP_gc:
        this.pageBufferDirty.fill(0);
        break;
      case CMD_EEWR_gc:
        // Write-only mode: subsequent mapped EEPROM writes AND with existing data.
        // Actual writes happen in the mapped EEPROM write hooks.
        // Don't clear command — it stays active until NONE/NOOP.
        break;
      case CMD_EEERWR_gc:
        // Erase+write mode: subsequent mapped EEPROM writes replace data directly.
        // Actual writes happen in the mapped EEPROM write hooks.
        // Don't clear command — it stays active until NONE/NOOP.
        break;
      case CMD_EEBER_gc:
        // Erase EEPROM page (the page containing the address in ADDR)
        // For simplicity, erase the whole EEPROM
        // TODO: figure out page size
        this.eeprom.fill(0xFF);
        this.pageBufferDirty.fill(0);
        break;
      default:
        // TODO: implement other commands (if needed)
        break;
    }
  }
}