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
|
// AVR-Dx RTC Periodic Interrupt Timer (PIT)
// Generates periodic interrupts from the 32768 Hz internal ULP oscillator.
import type { CPU, AVRInterruptConfig } from 'avr8js/cpu/cpu';
const FREQ_HZ = 32768;
const CTRLA = 0x0;
const STATUS = 0x1;
const INTCTRL = 0x2;
const INTFLAGS = 0x3;
const PI_bm = 0x01;
const PITEN_bm = 0x01;
const PERIOD_gm = 0x78; // bits [6:3]
// PIT period to number of 32768Hz clock cycles
// PERIOD field is bits [6:3] of CTRLA
export const PERIOD_CYCLES = [
0, // 0x00: OFF
4, // 0x01: CYC4
8, // 0x02: CYC8
16, // 0x03: CYC16
32, // 0x04: CYC32
64, // 0x05: CYC64
128, // 0x06: CYC128
256, // 0x07: CYC256
512, // 0x08: CYC512
1024, // 0x09: CYC1024
2048, // 0x0A: CYC2048
4096, // 0x0B: CYC4096
8192, // 0x0C: CYC8192
16384, // 0x0D: CYC16384
32768, // 0x0E: CYC32768
0,
] as const;
export class AVRDxRTCPIT {
private pitCallback: (() => void) | null = null;
private irq: AVRInterruptConfig;
tickCount = 0;
constructor(private cpu: CPU, private base: number, irqNo: number, private cpuFreqHz: number) {
this.irq = {
address: irqNo * 2,
flagRegister: base + INTFLAGS,
flagMask: PI_bm,
enableRegister: base + INTCTRL,
enableMask: PI_bm,
}
// CTRLA - period select + enable
cpu.writeHooks[base + CTRLA] = (value) => {
cpu.data[base + CTRLA] = value;
this.reconfigure();
return true;
};
// STATUS - read-only busy flag (always report not busy for simplicity)
// TODO: when should this report busy? do we need this?
cpu.readHooks[base + STATUS] = () => 0;
cpu.writeHooks[base + STATUS] = () => true; // ignore writes
// INTCTRL - enable interrupt
cpu.writeHooks[base + INTCTRL] = (value) => {
cpu.data[base + INTCTRL] = value;
if (value & PI_bm) {
cpu.updateInterruptEnable(this.irq, value);
}
return true;
};
// INTFLAGS - write 1 to clear
cpu.writeHooks[base + INTFLAGS] = (value) => {
if (value & PI_bm) {
cpu.data[base + INTFLAGS] &= ~PI_bm;
cpu.clearInterrupt(this.irq);
}
return true;
};
}
private get cycles() {
const ctrla = this.cpu.data[this.base + CTRLA];
if (!(ctrla & PITEN_bm)) return;
return PERIOD_CYCLES[(ctrla & PERIOD_gm) >> 3] || undefined;
}
// effective tick frequency (Hz), null if disabled
get frequency() {
const c = this.cycles;
return c ? FREQ_HZ / this.cycles : null;
}
private reconfigure() {
if (this.pitCallback) {
this.cpu.clearClockEvent(this.pitCallback);
this.pitCallback = null;
}
const cycles = this.cycles;
if (!cycles) return;
this.scheduleTick(Math.round(cycles * this.cpuFreqHz / FREQ_HZ));
}
private scheduleTick(cycles: number) {
this.pitCallback = this.cpu.addClockEvent(() => this.onTick(cycles), cycles);
}
private onTick(cycles: number) {
this.tickCount++;
this.pitCallback = null;
this.cpu.setInterruptFlag(this.irq);
// Re-schedule if still enabled
if (this.cpu.data[this.base + CTRLA] & PITEN_bm) {
this.scheduleTick(cycles);
}
}
}
|