zba/src/bus/timer.zig

153 lines
4.5 KiB
Zig

const std = @import("std");
const TimerControl = @import("io.zig").TimerControl;
const Io = @import("io.zig").Io;
const Scheduler = @import("../scheduler.zig").Scheduler;
const Event = @import("../scheduler.zig").Event;
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
const log = std.log.scoped(.Timer);
pub const Timers = struct {
const Self = @This();
_0: Timer(0),
_1: Timer(1),
_2: Timer(2),
_3: Timer(3),
pub fn init(sched: *Scheduler) Self {
return .{
._0 = Timer(0).init(sched),
._1 = Timer(1).init(sched),
._2 = Timer(2).init(sched),
._3 = Timer(3).init(sched),
};
}
};
fn Timer(comptime id: u2) type {
return struct {
const Self = @This();
/// Read Only, Internal. Please use self.counter()
_counter: u16,
/// Write Only, Internal. Please use self.writeCntLow()
_reload: u16,
/// Write Only, Internal. Please use self.WriteCntHigh()
cnt: TimerControl,
/// Internal.
sched: *Scheduler,
/// Internal
_start_timestamp: u64,
pub fn init(sched: *Scheduler) Self {
return .{
._reload = 0,
._counter = 0,
.cnt = .{ .raw = 0x0000 },
.sched = sched,
._start_timestamp = 0,
};
}
pub fn counter(self: *const Self) u16 {
if (self.cnt.cascade.read())
return self._counter
else
return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
}
pub fn writeCnt(self: *Self, word: u32) void {
self.writeCntLow(@truncate(u16, word));
self.writeCntHigh(@truncate(u16, word >> 16));
}
pub fn writeCntLow(self: *Self, halfword: u16) void {
self._reload = halfword;
}
pub fn writeCntHigh(self: *Self, halfword: u16) void {
const new = TimerControl{ .raw = halfword };
// If Timer happens to be enabled, It will either be resheduled or disabled
self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
if (!self.cnt.enabled.read() and new.enabled.read()) {
// Reload on Rising edge
self._counter = self._reload;
if (!new.cascade.read()) self.scheduleOverflow();
}
self.cnt.raw = halfword;
}
pub fn handleOverflow(self: *Self, cpu: *Arm7tdmi) void {
// Fire IRQ if enabled
const io = &cpu.bus.io;
const tim = &cpu.bus.tim;
if (self.cnt.irq.read()) {
switch (id) {
0 => io.irq.tim0_overflow.set(),
1 => io.irq.tim1_overflow.set(),
2 => io.irq.tim2_overflow.set(),
3 => io.irq.tim3_overflow.set(),
}
cpu.handleInterrupt();
}
// Perform Cascade Behaviour
switch (id) {
0 => if (tim._1.cnt.cascade.read()) {
tim._1._counter +%= 1;
if (tim._1._counter == 0)
tim._1.handleOverflow(cpu);
},
1 => if (tim._2.cnt.cascade.read()) {
tim._2._counter +%= 1;
if (tim._2._counter == 0)
tim._2.handleOverflow(cpu);
},
2 => if (tim._3.cnt.cascade.read()) {
tim._3._counter +%= 1;
if (tim._3._counter == 0)
tim._3.handleOverflow(cpu);
},
3 => {}, // There is no Timer for TIM3 to "cascade" to,
}
// Reschedule Timer if we're not cascading
if (!self.cnt.cascade.read()) {
self._counter = self._reload;
self.scheduleOverflow();
}
}
fn scheduleOverflow(self: *Self) void {
const when = (@as(u64, 0x10000) - self._counter) * self.frequency();
self._start_timestamp = self.sched.now();
self.sched.push(.{ .TimerOverflow = id }, self.sched.now() + when);
}
fn frequency(self: *const Self) u16 {
return switch (self.cnt.frequency.read()) {
0 => 1,
1 => 64,
2 => 256,
3 => 1024,
};
}
};
}