summaryrefslogtreecommitdiff
path: root/tools/proxyclient/m1n1/hw/nco.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/proxyclient/m1n1/hw/nco.py')
-rw-r--r--tools/proxyclient/m1n1/hw/nco.py89
1 files changed, 89 insertions, 0 deletions
diff --git a/tools/proxyclient/m1n1/hw/nco.py b/tools/proxyclient/m1n1/hw/nco.py
new file mode 100644
index 0000000..3d1840d
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/nco.py
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: MIT
+
+__all__ = ["NCO"]
+
+def galois_lfsr(init, poly):
+ state = init
+ for i in range((1 << poly.bit_length() - 1) - 1):
+ if state & 1:
+ state = (state >> 1) ^ (poly >> 1)
+ else:
+ state = (state >> 1)
+ yield state
+
+def gen_lookup_tables():
+ fwd, inv = dict(), dict()
+ lfsr_states = [0] + list(reversed(list(galois_lfsr(0x7ff, 0xa01))))
+ for cycle, sr_state in enumerate(lfsr_states):
+ fwd[cycle + 2] = sr_state
+ inv[sr_state] = cycle + 2
+ return fwd, inv
+
+
+class NCOChannel:
+ def __init__(self, parent, base):
+ self.parent = parent
+ self.base = base
+ self.p = parent.u.proxy
+
+ def enabled(self):
+ return bool(self.p.read32(self.base) & (1<<31))
+
+ def enable(self):
+ self.p.set32(self.base, 1<<31)
+
+ def disable(self):
+ self.p.clear32(self.base, 1<<31)
+
+ def set_rate(self, target):
+ was_enabled = self.enabled()
+ for off, val in enumerate(NCO.calc_regvals(self.parent.fin, target)):
+ self.p.write32(self.base + off*4, val)
+ if was_enabled:
+ self.enable()
+
+ def get_rate(self):
+ return NCO.calc_rate(self.parent.fin,
+ [self.p.read32(self.base + off*4) for off in range(4)]
+ )
+
+ def __repr__(self):
+ return f"<NCO channel @ 0x{self.base:x}>"
+
+
+class NCO:
+ TBL, TBL_INV = gen_lookup_tables()
+
+ @classmethod
+ def calc_rate(self, fin, regvals):
+ try:
+ div = self.TBL_INV[regvals[1] >> 2] << 2 | regvals[1] & 3
+ except KeyError:
+ raise ValueError("bad configuration")
+ inc1 = regvals[2]
+ inc2 = regvals[3] - 0x1_0000_0000
+ return 2 * fin * (inc1 - inc2) // (div * (inc1 - inc2) + inc1)
+
+ @classmethod
+ def calc_regvals(self, fin, fout):
+ div = 2 * fin // fout
+ inc1 = (2 * fin - div * fout)
+ inc2 = inc1 - fout
+ try:
+ return [0, self.TBL[div >> 2] << 2 | div & 3, inc1, inc2 + 0x1_0000_0000]
+ except KeyError:
+ raise ValueError("target rate out of range")
+
+ def __init__(self, u, devpath, stride=0x4000):
+ self.u = u
+ node = u.adt[devpath]
+ self.fin = u.adt["/arm-io"].clock_frequencies[node.clock_ids[0] - 256]
+
+ reg = node.get_reg(0)
+ self.chans = [
+ NCOChannel(self, base)
+ for base in range(reg[0], reg[0] + reg[1], stride)
+ ]
+
+ def __getitem__(self, idx):
+ return self.chans[idx]