aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/watchdog.ts
blob: dc66220dc1a532ccf1816ad9fb452ba79948a5e8 (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * AVR8 Watchdog Timer
 * Part of AVR8js
 * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
 *
 * Copyright (C) 2021 Uri Shaked
 */

import { AVRClock } from '..';
import { AVRInterruptConfig, CPU } from '../cpu/cpu';
import { u8 } from '../types';

export interface WatchdogConfig {
  watchdogInterrupt: u8;
  MCUSR: u8;
  WDTCSR: u8;
}

// Register bits:
const MCUSR_WDRF = 0x8; //  Watchdog System Reset Flag

const WDTCSR_WDIF = 0x80;
const WDTCSR_WDIE = 0x40;
const WDTCSR_WDP3 = 0x20;
const WDTCSR_WDCE = 0x10; // Watchdog Change Enable
const WDTCSR_WDE = 0x8;
const WDTCSR_WDP2 = 0x4;
const WDTCSR_WDP1 = 0x2;
const WDTCSR_WDP0 = 0x1;
const WDTCSR_WDP210 = WDTCSR_WDP2 | WDTCSR_WDP1 | WDTCSR_WDP0;

const WDTCSR_PROTECT_MASK = WDTCSR_WDE | WDTCSR_WDP3 | WDTCSR_WDP210;

export const watchdogConfig: WatchdogConfig = {
  watchdogInterrupt: 0x0c,
  MCUSR: 0x54,
  WDTCSR: 0x60,
};

export class AVRWatchdog {
  readonly clockFrequency = 128000;

  /**
   * Used to keep track on the last write to WDCE. Once written, the WDE/WDP* bits can be changed.
   */
  private changeEnabledCycles = 0;
  private watchdogTimeout = 0;
  private enabledValue = false;
  private scheduled = false;

  // Interrupts
  private Watchdog: AVRInterruptConfig = {
    address: this.config.watchdogInterrupt,
    flagRegister: this.config.WDTCSR,
    flagMask: WDTCSR_WDIF,
    enableRegister: this.config.WDTCSR,
    enableMask: WDTCSR_WDIE,
  };

  constructor(private cpu: CPU, private config: WatchdogConfig, private clock: AVRClock) {
    const { WDTCSR } = config;
    this.cpu.onWatchdogReset = () => {
      this.resetWatchdog();
    };
    cpu.writeHooks[WDTCSR] = (value: u8, oldValue: u8) => {
      if (value & WDTCSR_WDCE && value & WDTCSR_WDE) {
        this.changeEnabledCycles = this.cpu.cycles + 4;
        value = value & ~WDTCSR_PROTECT_MASK;
      } else {
        if (this.cpu.cycles >= this.changeEnabledCycles) {
          value = (value & ~WDTCSR_PROTECT_MASK) | (oldValue & WDTCSR_PROTECT_MASK);
        }
        this.enabledValue = !!(value & WDTCSR_WDE || value & WDTCSR_WDIE);
        this.cpu.data[WDTCSR] = value;
      }

      if (this.enabled) {
        this.resetWatchdog();
      }

      if (this.enabled && !this.scheduled) {
        this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
      }

      this.cpu.clearInterruptByFlag(this.Watchdog, value);
      return true;
    };
  }

  resetWatchdog() {
    const cycles = Math.floor((this.clock.frequency / this.clockFrequency) * this.prescaler);
    this.watchdogTimeout = this.cpu.cycles + cycles;
  }

  checkWatchdog = () => {
    if (this.enabled && this.cpu.cycles >= this.watchdogTimeout) {
      // Watchdog timed out!
      const wdtcsr = this.cpu.data[this.config.WDTCSR];
      if (wdtcsr & WDTCSR_WDIE) {
        this.cpu.setInterruptFlag(this.Watchdog);
      }
      if (wdtcsr & WDTCSR_WDE) {
        if (wdtcsr & WDTCSR_WDIE) {
          this.cpu.data[this.config.WDTCSR] &= ~WDTCSR_WDIE;
        } else {
          this.cpu.reset();
          this.scheduled = false;
          this.cpu.data[this.config.MCUSR] |= MCUSR_WDRF;
          return;
        }
      }
      this.resetWatchdog();
    }
    if (this.enabled) {
      this.scheduled = true;
      this.cpu.addClockEvent(this.checkWatchdog, this.watchdogTimeout - this.cpu.cycles);
    } else {
      this.scheduled = false;
    }
  };

  get enabled() {
    return this.enabledValue;
  }

  /**
   * The base clock frequency is 128KHz. Thus, a prescaler of 2048 gives 16ms timeout.
   */
  get prescaler() {
    const wdtcsr = this.cpu.data[this.config.WDTCSR];
    const value = ((wdtcsr & WDTCSR_WDP3) >> 2) | (wdtcsr & WDTCSR_WDP210);
    return 2048 << value;
  }
}