Commit f1ee9876 authored by Federico Vaga's avatar Federico Vaga

Merge remote-tracking branch 'sw/develop' into develop

parents afa65e47 4def3e9f
......@@ -12,4 +12,15 @@ transcript
work/
NullFile
*.orig
*.html
\ No newline at end of file
*.html
*.o
*.ko
*~
*.mod.c
/.tmp_versions
.*cmd
modules.order
Module.symvers
*TAGS
GPATH
Makefile.specific
......@@ -19,4 +19,6 @@
[submodule "hdl/ip_cores/svec"]
path = hdl/ip_cores/svec
url = https://ohwr.org/project/svec.git
[submodule "zio"]
path = software/zio
url = git://ohwr.org/misc/zio.git
..
SPDX-License-Identifier: CC-0.0
SPDX-FileCopyrightText: 2019 CERN
=========
Changelog
=========
Unreleased
==========
Changed
-------
- drv,lib: API change fmctdc_buffer_mode() -> fmctdc_transfer_mode()
This diff is collapsed.
"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN
"""
import pytest
import subprocess
import time
import re
import os
from PyFmcTdc import FmcTdc
class PulseGenerator(object):
def __init__(self, id):
self.id = id
def disable(self, ch):
pass
def generate_pulse(self, ch, rel_time_us,
period_ns, count, sync):
pass
class SCPI(PulseGenerator):
def __init__(self, scpi_id):
super(SCPI, self).__init__(scpi_id)
import pyvisa
self.mgr = pyvisa.ResourceManager()
self.instr = self.mgr.open_resource(self.id)
self.instr.query_delay=0
self.instr.timeout = 10000
self.instr.read_termination = '\n'
self.instr.write_termination = '\n'
self.instr.write("*RST")
self.instr.query_ascii_values("*OPC?")
self.instr.write("*CLS")
self.instr.write("INITIATE:CONTINUOUS OFF")
self.instr.write("OUTPUT:STATE OFF")
def disable(self, ch):
self.instr.write("OUTPUT:STATE OFF")
def generate_pulse(self, ch, rel_time_us,
period_ns, count, sync):
self.instr.write("OUTPUT:STATE OFF")
# START Custom Agilent 33600A commands
self.instr.write("SOURCE:BURST:STATE OFF")
# END Custom Agilent 33600A commands
self.instr.write("SOURCE:VOLTAGE:LEVEL:IMMEDIATE:AMPLITUDE 2.5V")
self.instr.write("SOURCE:VOLTAGE:LEVEL:IMMEDIATE:OFFSET 1.25V")
self.instr.write("SOURCE:FUNCTION:SHAPE PULSE")
self.instr.write("SOURCE:PULSE:WIDTH 101ns")
self.instr.write("SOURCE:PULSE:PERIOD {:d}ns".format(period_ns))
# START Custom Agilent 33600A commands
self.instr.write("TRIGGER:DELAY {:d}e-6".format(rel_time_us))
burst_period_ns = int(count/(1/period_ns)) + 500
self.instr.write("SOURCE:BURST:INTERNAL:PERIOD {:d}ns".format(burst_period_ns))
self.instr.write("SOURCE:BURST:NCYCLES {:d}".format(count))
self.instr.write("SOURCE:BURST:STATE ON")
# END Custom Agilent 33600A commands
self.instr.write("OUTPUT:STATE ON")
self.instr.query_ascii_values("*OPC?")
self.instr.write("INITIATE:IMMEDIATE")
if sync:
self.instr.query_ascii_values("*OPC?")
class FmcFineDelay(PulseGenerator):
CHANNEL_NUMBER = 4
def __init__(self, fd_id):
super(FmcFineDelay, self).__init__(fd_id)
def disable(self, ch):
cmd = ["/usr/local/bin/fmc-fdelay-pulse",
"-d", "0x{:x}".format(self.id),
"-o", str(ch),
"-m", "disable",
]
proc = subprocess.Popen(cmd)
proc.wait()
def generate_pulse(self, ch, rel_time_us,
period_ns, count, sync):
cmd = ["/usr/local/bin/fmc-fdelay-pulse",
"-d", "0x{:x}".format(self.id),
"-o", str(ch),
"-m", "pulse",
"-r", "{:d}u".format(rel_time_us),
"-T", "{:d}n".format(period_ns),
"-c", str(count),
"-t"
]
proc = subprocess.Popen(cmd)
proc.wait()
if sync:
time.sleep(1 + 2 * (period_ns * count) / 1000000000.0)
@pytest.fixture(scope="module")
def fmcfd():
if pytest.fd_id is not None:
gen = FmcFineDelay(pytest.fd_id)
elif pytest.scpi is not None:
gen = SCPI(pytest.scpi)
yield gen
if isinstance(gen, FmcFineDelay):
for ch in range(FmcFineDelay.CHANNEL_NUMBER):
gen.disable(ch + 1)
elif isinstance(gen, SCPI):
gen.disable(0)
@pytest.fixture(scope="function")
def fmctdc():
tdc = FmcTdc(pytest.tdc_id)
for ch in tdc.chan:
ch.enable = False
ch.termination = False
ch.timestamp_mode = "post"
ch.flush()
yield tdc
def pytest_addoption(parser):
parser.addoption("--tdc-id", type=lambda x : int(x, 16),
required=True, help="Fmc TDC Linux Identifier")
parser.addoption("--fd-id", type=lambda x : int(x, 16), default=None,
help="Fmc Fine-Delay Linux Identifier")
parser.addoption("--scpi", type=str, default=None,
help="SCPI Connection String")
parser.addoption("--dump-range", type=int, default=10,
help="Timestamps to show before and after an error")
parser.addoption("--channel", type=int, default=[],
action="append", choices=range(FmcTdc.CHANNEL_NUMBER),
help="Channel(s) to be used for acquisition tests. Default all channels")
parser.addoption("--usr-acq-count", type=int, default=0,
help="Number of pulses to generate during a acquisition test.")
parser.addoption("--usr-acq-period-ns", type=int, default=0,
help="Pulses period (ns) during a acquisition test.")
def pytest_configure(config):
pytest.tdc_id = config.getoption("--tdc-id")
pytest.fd_id = config.getoption("--fd-id")
pytest.scpi = config.getoption("--scpi")
if pytest.scpi is None and pytest.fd_id is None:
raise Exception("You must set --fd-id or --scpi")
pytest.channels = config.getoption("--channel")
if len(pytest.channels) == 0:
pytest.channels = range(FmcTdc.CHANNEL_NUMBER)
if len(pytest.channels) != 1 and pytest.scpi is not None:
raise Exception("With --scpi we can test only the channel connected to the Waveform generator. Set --channel")
pytest.usr_acq = (config.getoption("--usr-acq-period-ns"),
config.getoption("--usr-acq-count"))
pytest.dump_range = config.getoption("--dump-range")
pytest.transfer_mode = None
with open("/sys/bus/zio/devices/tdc-1n5c-{:04x}/transfer-mode".format(pytest.tdc_id)) as f_mode:
mode = int(f_mode.read().rstrip())
for k, v in FmcTdc.TRANSFER_MODE.items():
if mode == v:
pytest.transfer_mode = k
pytest.carrier = None
full_path = os.readlink("/sys/bus/zio/devices/tdc-1n5c-{:04x}".format(pytest.tdc_id))
for carr in ["spec", "svec"]:
is_carr = re.search(carr, full_path)
if is_carr is not None:
pytest.carrier = carr
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2020 CERN
[pytest]
addopts = -v -p no:cacheprovider
\ No newline at end of file
"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN
"""
import pytest
import random
from PyFmcTdc import FmcTdc
class TestFmctdcGetterSetter(object):
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("term", [True, False])
def test_termination(self, fmctdc, i, term):
"""Set temination and read it back"""
fmctdc.chan[i].termination = term
assert term == fmctdc.chan[i].termination
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("term", [True, False])
def test_termination(self, fmctdc, i, term):
"""Set temination and read it back"""
fmctdc.chan[i].termination = term
assert term == fmctdc.chan[i].termination
# TODO vmalloc error EBUSY
# @pytest.mark.parametrize("buffer_type", FmcTdc.BUFFER_TYPE.keys())
# def test_buffer_type(self, fmctdc, buffer_type):
# """Set buffer type and read it back"""
# fmctdc.buffer_type = buffer_type
# assert buffer_type == fmctdc.buffer_type
def test_transfer_mode(self, fmctdc):
"""Set buffer type and read it back"""
assert fmctdc.transfer_mode in FmcTdc.TRANSFER_MODE
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("term", [True, False])
def test_termination(self, fmctdc, i, term):
"""Set temination and read it back"""
fmctdc.chan[i].termination = term
assert term == fmctdc.chan[i].termination
@pytest.mark.parametrize("ch", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("enable", [True, False])
def test_enable(self, fmctdc, ch, enable):
"""Set enable status and read it back"""
"""When a channel gets enabled only that one become enabled all
the other remains disables"""
fmctdc.chan[ch].enable = True
for i in range(FmcTdc.CHANNEL_NUMBER):
assert fmctdc.chan[i].enable == (i == ch)
fmctdc.chan[ch].enable = False
for i in range(FmcTdc.CHANNEL_NUMBER):
assert fmctdc.chan[i].enable == False
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("buffer_mode", FmcTdc.FmcTdcChannel.BUFFER_MODE.keys())
def test_buffer_mode(self, fmctdc, i, buffer_mode):
"""Set buffer mode and read it back"""
fmctdc.chan[i].buffer_mode = buffer_mode
assert buffer_mode == fmctdc.chan[i].buffer_mode
# TODO vmalloc problems first
# @pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
# @pytest.mark.parametrize("buffer_len", range(1, 10))
# def test_buffer_len(self, fmctdc, i, buffer_len):
# """Set buffer length and read it back"""
# fmctdc.chan[i].buffer_len = buffer_len
# assert buffer_len == fmctdc.chan[i].buffer_len
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
def test_fileno(self, fmctdc, i):
"""file descriptors are always positive numbers"""
assert 0 < fmctdc.chan[i].fileno
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("offset", random.sample(range(1000000), 10))
def test_offset(self, fmctdc, i, offset):
"""Set user offset and read it back"""
fmctdc.chan[i].offset = offset
assert offset == fmctdc.chan[i].offset
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
def test_stats(self, fmctdc, i):
"""Set user offset and read it back"""
st = fmctdc.chan[i].stats
@pytest.mark.parametrize("i", range(FmcTdc.CHANNEL_NUMBER))
@pytest.mark.parametrize("timestamp_mode",
FmcTdc.FmcTdcChannel.TIMESTAMP_MODE.keys())
def test_timestamp_mode(self, fmctdc, i, timestamp_mode):
"""Set timestamp mode and read it back"""
fmctdc.chan[i].timestamp_mode = timestamp_mode
assert timestamp_mode == fmctdc.chan[i].timestamp_mode
"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN
"""
import pytest
import random
import select
import time
import sys
import os
from PyFmcTdc import FmcTdc, FmcTdcTime
TDC_FD_CABLING = [1, 2, 3, 4, 4]
min_period = 5 if pytest.transfer_mode == "fifo" else 4
fmctdc_acq_100ms = [(200, 65000),
(250, 65000),
(500, 65000),
(1000, 65000),
(1700, 65000),
# Let's keep the test within 100ms duration
# vvvvvvvvvvv
(1875, 60000),
(2500, 40000),
(5000, 20000),
(10000, 10000),
(100000, 1000),
(1000000, 100),
(10000000, 10)]
@pytest.fixture(scope="function", params=pytest.channels)
def fmctdc_chan(request):
tdc = FmcTdc(pytest.tdc_id)
for ch in tdc.chan:
ch.enable = False
tdc.chan[request.param].termination = False
tdc.chan[request.param].timestamp_mode = "post"
tdc.chan[request.param].flush()
tdc.chan[request.param].coalescing_timeout = 1
tdc.whiterabbit_mode = False
tdc.time = FmcTdcTime(0, 0, 0, 0, 0)
tdc.chan[request.param].enable = True
yield tdc.chan[request.param]
tdc.chan[request.param].enable = False
del tdc
class TestFmctdcAcquisition(object):
def test_acq_single_channel_disable(self, fmctdc_chan, fmcfd):
"""Acquistion does not start if the channel is not enable"""
fmctdc_chan.enable = False
fmcfd.generate_pulse(TDC_FD_CABLING[fmctdc_chan.idx], 0, 1000000000, 1, True)
with pytest.raises(OSError):
ts = fmctdc_chan.read(1, os.O_NONBLOCK)
@pytest.mark.parametrize("period_ns,count", fmctdc_acq_100ms)
def test_acq_chan_stats(self, fmctdc_chan, fmcfd, period_ns, count):
"""Check that unders a controlled acquisiton statistics increase
correctly. Test 100 milli-second acquisition at different
frequencies"""
stats_before = fmctdc_chan.stats
fmctdc_chan.buffer_len = max(count + 1, 64)
fmcfd.generate_pulse(TDC_FD_CABLING[fmctdc_chan.idx], 1000,
period_ns, count, True)
stats_after = fmctdc_chan.stats
assert stats_before[0] + count == stats_after[0]
@pytest.mark.parametrize("period_ns,count", fmctdc_acq_100ms)
def test_acq_chan_read_count(self, fmctdc_chan, fmcfd, period_ns, count):
"""Check that unders a controlled acquisiton the number of read
timestamps is correct. Test 100 milli-second acquisition at different
frequencies"""
fmctdc_chan.buffer_len = max(count + 1, 64)
fmcfd.generate_pulse(TDC_FD_CABLING[fmctdc_chan.idx], 1000,
period_ns, count, True)
ts = fmctdc_chan.read(count, os.O_NONBLOCK)
assert len(ts) == count
@pytest.mark.parametrize("period_ns,count", fmctdc_acq_100ms)
def test_acq_timestamp_seq_num(self, fmctdc_chan, fmcfd, period_ns, count):
"""Check that unders a controlled acquisiton the sequence
number of each timestamps increase by 1. Test 100 milli-second
acquisition at different frequencies"""
fmctdc_chan.buffer_len = max(count + 1, 64)
prev = None
fmcfd.generate_pulse(TDC_FD_CABLING[fmctdc_chan.idx], 1000,
period_ns, count, True)
ts = fmctdc_chan.read(count, os.O_NONBLOCK)
assert len(ts) == count
for i in range(len(ts)):
if prev == None:
prev = ts[i]
continue
assert ts[i].seq_id == (prev.seq_id + 1) & 0xFFFFFFF, \
"Missed {:d} timestamps (idx: {:d}, max: {:d}, prev: {{ {:s}, curr: {:s} }}, full dump;\n{:s}".format(ts[i].seq_id - prev.seq_id + 1,
i,
len(ts),
str(prev),
str(ts[i]),
"\n".join([str(x) for x in ts[max(0, i - pytest.dump_range):min(i + pytest.dump_range, len(ts) -1)]]))
prev = ts[i]
@pytest.mark.skipif(0 in pytest.usr_acq,
reason="Missing user acquisition option")
@pytest.mark.skipif(pytest.carrier == "spec" and \
pytest.transfer_mode == "fifo" and \
pytest.usr_acq[0] < 7000,
reason="On SPEC with FIFO acquisition we can't do more than 100kHz")
@pytest.mark.parametrize("period_ns,count", [pytest.usr_acq])
def test_acq_timestamp_single_channel(self, capsys, fmctdc_chan, fmcfd,
period_ns, count):
"""Run an acquisition with users parameters for period and count.
The Fine-Delay can generate a burst of maximum 65536 pulses, so we
compute and approximated timeout to stop the test and we let
the Fine-Delay generating an infinite train of pulses.
Since the test can be very long, periodically this test will print the
timestamp sequence number, you should see it increasing.
"""
poll = select.poll()
poll.register(fmctdc_chan.fileno, select.POLLIN)
pending = count
prev = None
# be able to buffer for 1 second
fmctdc_chan.buffer_len = int(1/(period_ns/1000000000.0)) + 1
stats_o = fmctdc_chan.stats
trans_b = stats_o[1]
fmcfd.generate_pulse(TDC_FD_CABLING[fmctdc_chan.idx], 1000,
period_ns, 0, False)
timeout = time.time() + 1 + (period_ns * count) / 1000000000.0
while pending > 0:
t = time.time()
if t >= timeout:
break
ret = poll.poll(1)
if len(ret) == 0:
continue
ts = fmctdc_chan.read(1000, os.O_NONBLOCK)
assert len(ts) <= 1000
for i in range(len(ts)):
if prev == None:
prev = ts[i]
continue
assert ts[i].seq_id == (prev.seq_id + 1) & 0xFFFFFFF, \
"Missed {:d} timestamps (idx: {:d}, max: {:d}, prev: {{ {:s}, curr: {:s} }}, full dump;\n{:s}".format(ts[i].seq_id - prev.seq_id + 1,
i,
len(ts),
str(prev),
str(ts[i]),
"\n".join([str(x) for x in ts[max(0, i - pytest.dump_range):min(i + pytest.dump_range, len(ts) -1)]]))
prev = ts[i]
pending -= len(ts)
poll.unregister(fmctdc_chan.fileno)
fmcfd.disable(TDC_FD_CABLING[fmctdc_chan.idx])
assert stats_o[0] == stats_o[1]
assert fmctdc_chan.stats[0] == fmctdc_chan.stats[1]
assert fmctdc_chan.stats[0] - stats_o[0] >= count
assert pending <= 0, "Some timestamp could be missing"
"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN
"""
import pytest
class TestFmctdcTemperature(object):
def test_temperature_read(self, fmctdc):
assert 0 < fmctdc.temperature
"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN
"""
import pytest
import random
import time
from PyFmcTdc import FmcTdcTime
class TestFmctdcTime(object):
def test_whiterabbit_mode(self, fmctdc):
"""It must be possible to toggle the White-Rabbit status"""
fmctdc.whiterabbit_mode = True
assert fmctdc.whiterabbit_mode == True
fmctdc.whiterabbit_mode = False
assert fmctdc.whiterabbit_mode == False
def test_time_set_fail_wr(self, fmctdc):
"""Time can't be changed when White-Rabbit is enabled"""
fmctdc.whiterabbit_mode = True
with pytest.raises(OSError):
fmctdc.time = FmcTdcTime(10, 0, 0, 0, 0)
@pytest.mark.parametrize("t", random.sample(range(1000000), 10))
def test_time_set(self, fmctdc, t):
"""Time can be changed when White-Rabbit is disabled"""
fmctdc.whiterabbit_mode = False
t_base = FmcTdcTime(t, 0, 0, 0, 0)
fmctdc.time = t_base
assert t_base.seconds == fmctdc.time.seconds
@pytest.mark.parametrize("whiterabbit", [False, True])
def test_time_flows(self, fmctdc, whiterabbit):
"""Just check that the time flows more or less correctly second by
second for a minute"""
fmctdc.whiterabbit_mode = whiterabbit
for i in range(20):
t_prev = fmctdc.time
time.sleep(1)
t_diff = abs(float(fmctdc.time) - float(t_prev))
assert 0.999 < t_diff < 1.001
# include parent_common.mk for buildsystem's defines
# use absolute path for REPO_PARENT
REPO_PARENT ?= $(shell /bin/pwd)/..
-include $(REPO_PARENT)/parent_common.mk
all: kernel lib tools
DIRS = kernel lib tools
$(SPEC_SW_ABS):
kernel:
lib:
tools: lib
DESTDIR ?= /usr/local
.PHONY: all clean modules install modules_install $(DIRS)
install modules_install:
all clean modules install modules_install: $(DIRS)
clean: TARGET = clean
modules: TARGET = modules
install: TARGET = install
modules_install: TARGET = modules_install
$(DIRS):
$(MAKE) -C $@ $(TARGET)
cppcheck:
for d in $(DIRS); do $(MAKE) -C $$d cppcheck || exit 1; done
WARNING!
This is work in progress. It is buggy and can be incomplete!
Please check doc/
In ./doc/, fmc-tdc.in is the source file, and you can "make" to get
pdf and other formats provided you have the proper tools installed
(mainly texinfo and tex).
doxygen-fmctdc-output
*.pdf
_build
\ No newline at end of file
#
# Makefile for the documentation directory
#
# Copyright 1994,2000,2010,2011 Alessandro Rubini <rubini@linux.it>
#
#################
# There is not basenames here, all *.in are considered input
INPUT = $(wildcard *.in)
TEXI = $(INPUT:.in=.texi)
INFO = $(INPUT:.in=.info)
HTML = $(INPUT:.in=.html)
TXT = $(INPUT:.in=.txt)
PDF = $(INPUT:.in=.pdf)
ALL = $(PDF)
MAKEINFO ?= makeinfo
%.texi: %.in
@rm -f $@
sed -f ./infofilter $< > $@
emacs -batch --no-site-file -l fixinfo $@
chmod -w $@
%.pdf: %.texi
texi2pdf --batch $<
%.info: %.texi
$(MAKEINFO) $< -o $@
%.html: %.texi
$(MAKEINFO) --html --no-split -o $@ $<
%.txt: %.texi
$(MAKEINFO) --no-headers $< > $@
##############################################
.PHONY: all images check terse clean install
.INTERMEDIATE: $(TEXI)
# Exports for doxygen
export GIT_VERSION = $(shell cd $(src); git describe --dirty --long --tags)
export EXCLUDE_FILES = "../lib/fmctdc-lib-private.h"
export BRIEF = "API Documentation"
export OUTPUT ?= doxy-fmctdc
all: doxygen images $(ALL)
$(MAKE) terse
doxygen:
doxygen ./doxygen-fmctdc-config
images::
if [ -d images ]; then $(MAKE) -C images || exit 1; fi
check: _err.ps
gs -sDEVICE=linux -r320x200x16 $<
terse:
for n in cp fn ky pg toc tp vr aux log; do rm -f *.$$n; done
rm -f *~
clean: terse
rm -f $(ALL) $(TEXI)
rm -rf $(OUTPUT)
install:
# add the other unused targets, so the rule in ../Makefile works
modules modules_install:
PROJECT_NAME = "FMC TDC - Software"
PROJECT_NUMBER = $(GIT_VERSION)
PROJECT_BRIEF = $(BRIEF)
PROJECT_LOGO =
OUTPUT_DIRECTORY = $(OUTPUT)
CREATE_SUBDIRS = YES
TAB_SIZE = 8
OPTIMIZE_OUTPUT_FOR_C = YES
EXTRACT_STATIC = NO
CASE_SENSE_NAMES = YES
WARN_NO_PARAMDOC = YES
INPUT = ../lib readme.md
RECURSIVE = YES
EXCLUDE = $(EXCLUDE_FILES)
GENERATE_HTML = YES
GENERATE_LATEX = YES
This diff is collapsed.
;; use:
;; emacs -batch -l ./fixinfo.el <file>
;; or, better:
;; emacs -batch --no-site-file -l ./fixinfo.el <file>
(defun fixinfo (file)
(find-file-other-window file)
(message (concat "Maxing texinfo tree in " file))
(texinfo-all-menus-update)
(texinfo-every-node-update)
(save-buffer)
(kill-buffer (current-buffer))
)
;; loop over command line arguments
(mapcar 'fixinfo command-line-args-left)
(kill-emacs)
This diff is collapsed.
#! /usr/bin/sed -f
# allow "%" as a comment char, but only at the beginning of the line
s/^%/@c /
#s/[^\\]%.*$//
s/^\\%/%/
#preserve blanks and braces in @example blocks
/^@example/,/^@end example/ s/{/@{/g
/^@example/,/^@end example/ s/}/@}/g
/^@example/,/^@end example/ p
/^@example/,/^@end example/ d
/^@smallexample/,/^@end smallexample/ s/{/@{/g
/^@smallexample/,/^@end smallexample/ s/}/@}/g
/^@smallexample/,/^@end smallexample/ p
/^@smallexample/,/^@end smallexample/ d
# remove leading blanks
s/^[ ]*//
Library Overview {#mainpage}
================
This is the **fmc-tdc** library documentation. Here you can find all
the information about the *fmc-tdc* API and the main library behaviour that
you need to be aware of.
If you are reading this from the doxygen documentation, then you can find
the API documentation in the usual Doxygen places. Otherwise, you can get
the API documentation directly from the source code that you can find in
the *lib* directory.
In this document we are going to provides you some clues to understand how
to use the libray API.
Initialization
==============
To be able to use this library the first thing to do is to initialize a library
instance using fmctdc_init(); form this point on you are able to use the
library API. Of course, when you finished to use the library you have to
remove this instance using fmctdc_exit().
By default all TDC channels are disabled. So, in order to start the time-stamps
acquisition you have to enables your channels using fmctdc_channel_enable().
Now you are ready to read your time-stamps. The procedure to read time-stamp is:
-# open a channel with fmctdc_open()
-# read time-stamps as much as you want with fmctdc_read()
-# close the channel with fmctdc_close()
If you fear that the channel buffer is not empyt when you start your acquisition
you can flush it by using fmctdc_flush(). Calling fmctdc_flush() will temporary
disable the acquisition on the given channel.
Time Stamps
===========
The main purpose of this library is to provide *time-stamps* without any
specific policy. All special policies to apply to the time-stamps must be
done on your side.
MODES
-----
The library provides two time-stamp modes that you can configure for
each channel: **base-time** and **difference-time**. The selection of
one or the other mode will change the meaning of the time-stamp that
you will read. To select the time-stamp mode you have to use
the fmctdc_reference_set() function.
The standard mode is the *base-time* mode. When the library is in this mode
for a given channel, the time-stamps coming from this channel will be pure
time-stamps according to the TDC internal base-time.
The *difference-time* mode can be enabled by assigning a channel reference
to a given channel (a.k.a. target). When you assing a channel reference to
a channel the time-stamp produced by will be a time difference between the
pulse on the target channel and the last pulse on the reference channel.
In order to disable the *difference-time* mode, and go back to
the *base-time* mode, you must remove the channel reference.
Bear in mind that the time-stamp mode will affect immediatly the time-stamp
acquisition but **not** the time-stamps already in the buffer.
Buffer
======
The buffer is place where time-stamps are stored. You can configure only the
lenght of the buffer and its operational mode
Modes
-----
You have mainly two buffer modes: **FIFO** and **CIRC** (circular). You can
change the buffer mode for a singel channel using fmctdc_set_buffer_mode().
In *FIFO* mode when the buffer is full all new time-stamps will be dropped,
instead when you use *CIRC* mode old time-stamps will be overwritten by
the new ones.
\ No newline at end of file
# ZIO comes from the Makefile
ZIO_VERSION = $(shell cd $(ZIO_ABS); git describe --always --dirty --long --tags)
VERSION = $(shell cd $(src); git describe --always --dirty --long --tags)
KBUILD_EXTRA_SYMBOLS += $(ZIO_EXTRA_SYMBOLS-y)
KBUILD_EXTRA_SYMBOLS += $(FMC_EXTRA_SYMBOLS-y)
ccflags-y = -DVERSION=\"$(VERSION)\"
ccflags-y += -I$(src)
ccflags-y += -I$(ZIO_ABS)/include
ccflags-y += -I$(FMC_ABS)/include
ccflags-y += -I$(FMC_ABS)/include/uapi
ccflags-$(CONFIG_FMC_TDC_DEBUG) += -DDEBUG
ccflags-$(CONFIG_FMC_TDC_VERBOSE_DEBUG) += -DVERBOSE_DEBUG
ccflags-y += -Werror
# Extract minimum com major, minor and patch number
ccflags-y += -D__ZIO_MIN_MAJOR_VERSION=$(shell echo $(ZIO_VERSION) | cut -d '-' -f 1 | cut -d '.' -f 1 | tr -d 'v'; )
ccflags-y += -D__ZIO_MIN_MINOR_VERSION=$(shell echo $(ZIO_VERSION) | cut -d '-' -f 1 | cut -d '.' -f 2; )
# add versions of supermodule. It is useful when fine-delay-sw is included as sub-module
# of a bigger project that we want to track
ifdef CONFIG_SUPER_REPO
ifdef CONFIG_SUPER_REPO_VERSION
SUBMODULE_VERSIONS += MODULE_INFO(version_$(CONFIG_SUPER_REPO),\"$(CONFIG_SUPER_REPO_VERSION)\");
endif
endif
# add versions of used submodules
SUBMODULE_VERSIONS += MODULE_INFO(version_zio,\"$(ZIO_VERSION)\");
ccflags-y += -DADDITIONAL_VERSIONS="$(SUBMODULE_VERSIONS)"
subdirs-ccflags-y = $(ccflags-y)
obj-m := fmc-tdc.o
obj-m += fmc-tdc-spec.o
obj-m += fmc-tdc-svec.o
fmc-tdc-objs := acam.o
fmc-tdc-objs += calibration.o
fmc-tdc-objs += ft-buf.o
fmc-tdc-objs += ft-core.o
fmc-tdc-objs += ft-fifo.o
fmc-tdc-objs += ft-time.o
fmc-tdc-objs += ft-zio.o
fmc-tdc-objs += ft-debug.o
fmc-tdc-spec-objs := fmc-tdc-spec-core.o
fmc-tdc-svec-objs := fmc-tdc-svec-core.o
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2019 CERN
-include Makefile.specific
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT ?= $(shell /bin/pwd)/../..
-include $(REPO_PARENT)/parent_common.mk
CPPCHECK ?= cppcheck
DKMS ?= 0
CURDIR := $(shell /bin/pwd)
KVERSION ?= $(shell uname -r)
LINUX ?= /lib/modules/$(KVERSION)/build
ifdef REPO_PARENT
ZIO ?= $(REPO_PARENT)/fmc/zio
FMC ?= $(REPO_PARENT)/fmc-sw
VMEBUS ?= $(REPO_PARENT)/vmebridge-ng
endif
ifeq ($(DKMS), 1)
# Take last installed version (if installed using RPM it should be OK)
ZIO_VERSION ?= $(shell basename $(shell ls -d $(DKMSTREE)/zio/* | grep -E "\/[0-9]+\.[0-9]+\.[0-9]+" | sort -V | tail -n 1))
ZIO_ABS ?= $(DKMSTREE)/zio/$(ZIO_VERSION)/source
ZIO_EXTRA_SYMBOLS-y = $(DKMSTREE)/zio/kernel-$(KVERSION)-$(shell uname -p)/module/Module.symvers
else
ifndef ZIO
$(error "Missing ZIO environment variable")
endif
ifndef FMC
$(error "Missing FMC environment variable")
endif
ifndef VMEBUS
$(error "Missing VMEBUS environment variable")
endif
ZIO_ABS ?= $(abspath $(ZIO))
ZIO_EXTRA_SYMBOLS-y = $(ZIO_ABS)/drivers/zio/Module.symvers
ZIO_VERSION ?= $(shell cd $(ZIO_ABS); git describe --always --dirty --long --tags)
FMC_ABS ?= $(abspath $(FMC))
FMC_EXTRA_SYMBOLS-y = $(FMC_ABS)/drivers/fmc/Module.symvers
endif
VMEBUS_ABS ?= $(abspath $(VMEBUS) )
GIT_VERSION = $(shell git describe --always --dirty --long --tags)
all modules:
$(MAKE) -C $(LINUX) M=$(CURDIR) ZIO_ABS=$(ZIO_ABS) FMC_ABS=$(FMC_ABS) \
ZIO_EXTRA_SYMBOLS-y=$(ZIO_EXTRA_SYMBOLS-y) \
FMC_EXTRA_SYMBOLS-y=$(FMC_EXTRA_SYMBOLS-y) \
ZIO_VERSION=$(ZIO_VERSION) \
GIT_VERSION=$(GIT_VERSION) \
VMEBUS_ABS=$(VMEBUS_ABS) modules
install modules_install: modules
$(MAKE) -C $(LINUX) M=$(CURDIR) modules_install
# be able to run the "clean" rule even if $(LINUX) is not valid
clean:
rm -rf *.o *~ .*.cmd *.ko *.mod.c .tmp_versions Module.symvers \
Module.markers modules.order
cppcheck:
$(CPPCHECK) -q -I. -I$(ZIO_ABS)/include -I$(FMC_BUS_ABS)/ --enable=all *.c *.h
/*
* ACAM TDC-GPX routines support for fmc-tdc driver.
*
* Copyright (C) 2013 CERN (http://www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
#include "hw/acam_gpx.h"
#define NB_ACAM_REGS 11
static struct {
int reg;
u32 value;
} acam_config[NB_ACAM_REGS] = {
{
0, AR0_ROsc | AR0_HQSel | AR0_TRiseEn(0) |
AR0_TRiseEn(1) | AR0_TRiseEn(2) |
AR0_TRiseEn(3) | AR0_TRiseEn(4) |
AR0_TRiseEn(5) |
AR0_TFallEn(1) | AR0_TFallEn(2) |
AR0_TFallEn(3) | AR0_TFallEn(4) | AR0_TFallEn(5)}, {
1, 0}, {
2, AR2_IMode | AR2_Disable(6) | AR2_Disable(7) | AR2_Disable(8)}, {
3, 0}, {
4, AR4_StartTimer(15) | AR4_EFlagHiZN}, {
5, AR5_StartOff1(2000)}, {
6, AR6_Fill(0xfc)}, {
7, AR7_RefClkDiv(7) | AR7_HSDiv(234) | AR7_NegPhase | AR7_ResAdj}, {
11, AR11_HFifoErrU(0) | AR11_HFifoErrU(1) |
AR11_HFifoErrU(2) | AR11_HFifoErrU(3) |
AR11_HFifoErrU(4) | AR11_HFifoErrU(5) |
AR11_HFifoErrU(6) | AR11_HFifoErrU(7)}, {
12, AR12_StartNU | AR12_HFifoE}, {
14, 0}
};
static inline int acam_is_pll_locked(struct fmctdc_dev *ft)
{
uint32_t status;
ft_writel(ft, TDC_CTRL_READ_ACAM_CFG, TDC_REG_CTRL);
udelay(100);
status = ft_readl(ft, TDC_REG_ACAM_READBACK(12));
return !(status & AR12_NotLocked);
}
int ft_acam_init(struct fmctdc_dev *ft)
{
int i;
unsigned long tmo;
dev_dbg(&ft->pdev->dev, "%s: initializing ACAM TDC...\n", __func__);
ft_writel(ft, TDC_CTRL_RESET_ACAM, TDC_REG_CTRL);
udelay(100);
for (i = 0; i < NB_ACAM_REGS; i++) {
ft_writel(ft, acam_config[i].value,
TDC_REG_ACAM_CONFIG(acam_config[i].reg));
}
/* commit ACAM config regs */
ft_writel(ft, TDC_CTRL_LOAD_ACAM_CFG, TDC_REG_CTRL);
udelay(100);
/* and reset the chip (keeps configuration) */
ft_writel(ft, TDC_CTRL_RESET_ACAM, TDC_REG_CTRL);
udelay(100);
/* wait for the ACAM's PLL to lock (2 seconds) */
tmo = jiffies + 2 * HZ;
while (time_before(jiffies, tmo)) {
if (acam_is_pll_locked(ft)) {
dev_dbg(&ft->pdev->dev, "%s: ACAM initialization OK.\n",
__func__);
return 0;
}
}
dev_err(&ft->pdev->dev, "%s: ACAM PLL doesn't lock\n", __func__);
return -EIO;
}
void ft_acam_exit(struct fmctdc_dev *ft)
{
/* Disable ACAM inputs and PLL */
ft_writel(ft, TDC_CTRL_DIS_ACQ, TDC_REG_CTRL);
ft_writel(ft, 0, TDC_REG_ACAM_CONFIG(0));
ft_writel(ft, 0, TDC_REG_ACAM_CONFIG(7));
ft_writel(ft, TDC_CTRL_LOAD_ACAM_CFG, TDC_REG_CTRL);
udelay(100);
}
/*
* EEPROM calibration block retreival code for fmc-tdc.
*
* Copyright (C) 2013 CERN (www.cern.ch)
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/firmware.h>
#include <linux/jhash.h>
#include <linux/slab.h>
#include <linux/fmc.h>
#include <linux/ipmi/fru.h>
#include <linux/zio.h>
#include "fmc-tdc.h"
#define WR_CALIB_OFFSET 229460
static u32 wr_calibration_offset = WR_CALIB_OFFSET;
module_param_named(wr_offset_fix, wr_calibration_offset, int, 0444);
MODULE_PARM_DESC(wr_offset_fix,
"Overwrite the White-Rabbit calibration offset for calibration value computer before 2018. (Default: 229460 [ps])");
static u32 wr_calibration_offset_carrier = 0;
module_param_named(wr_offset_carrier, wr_calibration_offset_carrier, int, 0444);
MODULE_PARM_DESC(wr_offset_carrier,
"White-Rabbit carrier calibration offset. (Default SPEC: 0 [ps])");
/* dummy calibration data - used in case of empty/corrupted EEPROM */
static struct ft_calibration default_calibration = {
{0, 86, 609, 572, 335}, /* zero_offset */
43343, /* vcxo_default_tune */
0,
WR_CALIB_OFFSET, /* white-rabbit offset */
};
#define WR_OFFSET_FIX_YEAR (2018)
#define IPMI_FRU_SIZE 256
/**
* HACK area to get the calibration year
*/
static u32 __get_ipmi_fru_id_year(struct fmctdc_dev *ft)
{
struct fru_board_info_area *bia;
struct fru_type_length *tmp;
unsigned long year = 0;
char year_ascii[5];
void *fru = NULL;
int err, i;
fru = kmalloc(IPMI_FRU_SIZE, GFP_KERNEL);
if (!fru)
goto out_mem;
err = fmc_slot_eeprom_read(ft->slot, fru, 0x0, IPMI_FRU_SIZE);
if (err)
goto out_read;
bia = fru_get_board_area((const struct fru_common_header *)fru);
tmp = bia->tl;
for (i = 0; i < 4; ++i) {
tmp = fru_next_tl(tmp);
if (!tmp)
goto out_fru;
}
if (!fru_length(tmp))
goto out_fru;
if (fru_type(tmp) != FRU_TYPE_ASCII)
goto out_fru;
memcpy(year_ascii, tmp->data, 4);
year_ascii[4] = '\0';
err = kstrtoul(year_ascii, 10, &year);
if (err)
year = 0;
out_fru:
out_read:
kfree(fru);
out_mem:
return year;
}
/**
* @calib: calibration data
*
* We know for sure that our structure is only made of 32bit fields
*/
static void ft_calib_le32_to_cpus(struct ft_calibration_raw *calib)
{
int i;
uint32_t *p = (uint32_t *)calib;
for (i = 0; i < sizeof(*calib) / sizeof(uint32_t); i++)
le32_to_cpus(p + i); /* s == in situ */
}
/**
* @calib: calibration data
*
* We know for sure that our structure is only made of 32bit fields
*/
static void ft_calib_cpu_to_le32s(struct ft_calibration_raw *calib)
{
int i;
uint32_t *p = (uint32_t *)calib;
for (i = 0; i < sizeof(*calib) / sizeof(uint32_t); i++)
cpu_to_le32s(p + i); /* s == in situ */
}
static void ft_calib_cpy_from_raw(struct ft_calibration *calib,
struct ft_calibration_raw *calib_raw)
{
int i;
ft_calib_le32_to_cpus(calib_raw);
calib->zero_offset[0] = 0;
for (i = 1; i < FT_NUM_CHANNELS; i++)
calib->zero_offset[i] = calib_raw->zero_offset[i - 1] / 100;
calib->vcxo_default_tune = calib_raw->vcxo_default_tune / 100;
calib->calibration_temp = calib_raw->calibration_temp;
calib->wr_offset = calib_raw->wr_offset / 100;
calib->wr_offset += wr_calibration_offset_carrier;
}
static void ft_calib_cpy_to_raw(struct ft_calibration_raw *calib_raw,
struct ft_calibration *calib)
{
int i;
for (i = 1; i < FT_NUM_CHANNELS; i++)
calib_raw->zero_offset[i - 1] = calib->zero_offset[i] * 100;
calib_raw->vcxo_default_tune = calib->vcxo_default_tune * 100;
calib_raw->calibration_temp = calib->calibration_temp;
calib_raw->wr_offset = (calib->wr_offset - wr_calibration_offset_carrier) * 100;
ft_calib_cpu_to_le32s(calib_raw);
}
static ssize_t ft_write_eeprom(struct file *file, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct fmctdc_dev *ft = to_zio_dev(dev)->priv_d;
struct ft_calibration_raw *calib_raw = (struct ft_calibration_raw *) buf;
if (off != 0 || count != sizeof(*calib_raw))
return -EINVAL;
ft_calib_cpy_from_raw(&ft->calib, calib_raw);
return count;
}
static ssize_t ft_read_eeprom(struct file *file, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct fmctdc_dev *ft = to_zio_dev(dev)->priv_d;
struct ft_calibration_raw *calib_raw = (struct ft_calibration_raw *) buf;
if (off != 0 || count < sizeof(*calib_raw))
return -EINVAL;
ft_calib_cpy_to_raw(calib_raw, &ft->calib);
return count;
}
struct bin_attribute dev_attr_calibration = {
.attr = {
.name = "calibration_data",
.mode = 0644,
},
.size = sizeof(struct ft_calibration_raw),
.write = ft_write_eeprom,
.read = ft_read_eeprom,
};
#define FT_EEPROM_CALIB_OFFSET 0x100
int ft_calib_init(struct fmctdc_dev *ft)
{
struct ft_calibration_raw calib;
int ret;
ret = fmc_slot_eeprom_read(ft->slot, &calib,
FT_EEPROM_CALIB_OFFSET, sizeof(calib));
if (ret < 0) {
dev_warn(&ft->pdev->dev,
"Failed to read calibration from EEPROM: using identity calibration %d\n",
ret);
memcpy(&calib, &default_calibration, sizeof(calib));
goto out;
}
ft_calib_cpy_from_raw(&ft->calib, &calib);
/* FIX wrong calibration on old FMC-TDC mezzanine */
if (__get_ipmi_fru_id_year(ft) < WR_OFFSET_FIX_YEAR)
ft->calib.wr_offset = wr_calibration_offset;
out:
ft->calib.wr_offset += wr_calibration_offset_carrier;
return 0;
}
void ft_calib_exit(struct fmctdc_dev *ft)
{
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include "platform_data/fmc-tdc.h"
enum ft_spec_dev_offsets {
FT_SPEC_TDC_MEM_START = 0x00001E000,
FT_SPEC_TDC_MEM_END = 0x000030000,
};
static const struct fmc_tdc_platform_data fmc_tdc_pdata = {
.flags = 0,
};
static int ft_spec_probe(struct platform_device *pdev)
{
static struct resource ft_spec_fdt_res[] = {
{
.name = "fmc-tdc-mem",
.flags = IORESOURCE_MEM,
},
{
.name = "fmc-tdc-dma",
.flags = IORESOURCE_DMA,
}, {
.name = "fmc-tdc-irq",
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
}
};
struct platform_device_info pdevinfo = {
.parent = &pdev->dev,
.name = "fmc-tdc",
.id = PLATFORM_DEVID_AUTO,
.res = ft_spec_fdt_res,
.num_res = ARRAY_SIZE(ft_spec_fdt_res),
.data = &fmc_tdc_pdata,
.size_data = sizeof(fmc_tdc_pdata),
.dma_mask = DMA_BIT_MASK(32),
};
struct platform_device *pdev_child;
struct resource *rmem;
struct resource *r;
int irq;
int dma_dev_chan;
rmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!rmem) {
dev_err(&pdev->dev, "Missing memory resource\n");
return -EINVAL;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Missing IRQ number\n");
return -EINVAL;
}
r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!r) {
dev_err(&pdev->dev, "Missing DMA engine\n");
return -EINVAL;
}
dma_dev_chan = r->start;
ft_spec_fdt_res[0].parent = rmem;
ft_spec_fdt_res[0].start = rmem->start + FT_SPEC_TDC_MEM_START;
ft_spec_fdt_res[0].end = rmem->start + FT_SPEC_TDC_MEM_END;
ft_spec_fdt_res[1].start = dma_dev_chan;
ft_spec_fdt_res[2].start = irq;
pdev_child = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev_child))
return PTR_ERR(pdev_child);
platform_set_drvdata(pdev, pdev_child);
return 0;
}
static int ft_spec_remove(struct platform_device *pdev)
{
struct platform_device *pdev_child = platform_get_drvdata(pdev);
platform_device_unregister(pdev_child);
return 0;
}
/**
* List of supported platform
*/
enum ft_spec_version {
FT_SPEC_VER = 0,
};
static const struct platform_device_id ft_spec_id_table[] = {
{
.name = "fmc-tdc-spec",
.driver_data = FT_SPEC_VER,
}, {
.name = "id:000010DC574E0001",
.driver_data = FT_SPEC_VER,
}, {
.name = "id:000010dc574e0001",
.driver_data = FT_SPEC_VER,
},
{},
};
static struct platform_driver ft_spec_driver = {
.driver = {
.name = "fmc-tdc-spec",
.owner = THIS_MODULE,
},
.id_table = ft_spec_id_table,
.probe = ft_spec_probe,
.remove = ft_spec_remove,
};
module_platform_driver(ft_spec_driver);
MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
MODULE_DESCRIPTION("Driver for the SPEC TDC 1 ns 5 channels");
MODULE_DEVICE_TABLE(platform, ft_spec_id_table);
MODULE_SOFTDEP("pre: spec_fmc_carrier fmc-tdc");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2020 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/fmc.h>
#include "platform_data/fmc-tdc.h"
enum ft_svec_dev_offsets {
FT_SVEC_TDC1_MEM_START = 0x00000E000,
FT_SVEC_TDC1_MEM_END = 0x0001DFFF,
FT_SVEC_TDC2_MEM_START = 0x0001E000,
FT_SVEC_TDC2_MEM_END = 0x000030000,
};
static struct fmc_tdc_platform_data fmc_tdc_pdata = {
.flags = FMC_TDC_BIG_ENDIAN,
};
/* MFD devices */
enum svec_fpga_mfd_devs_enum {
FT_SVEC_MFD_FT1 = 0,
FT_SVEC_MFD_FT2,
};
static struct resource ft_svec_fdt_res1[] = {
{
.name = "fmc-tdc-mem.1",
.flags = IORESOURCE_MEM,
.start = FT_SVEC_TDC1_MEM_START,
.end = FT_SVEC_TDC1_MEM_END,
}, {
.name = "fmc-tdc-irq.1",
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
.start = 0,
.end = 0,
},
};
static struct resource ft_svec_fdt_res2[] = {
{
.name = "fmc-tdc-mem.2",
.flags = IORESOURCE_MEM,
.start = FT_SVEC_TDC2_MEM_START,
.end = FT_SVEC_TDC2_MEM_END,
},
{
.name = "fmc-tdc-irq.2",
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
.start = 1,
.end = 1,
},
};
#define MFD_TDC(_n) \
{ \
.name = "fmc-tdc", \
.platform_data = &fmc_tdc_pdata, \
.pdata_size = sizeof(fmc_tdc_pdata), \
.num_resources = ARRAY_SIZE(ft_svec_fdt_res##_n), \
.resources = ft_svec_fdt_res##_n, \
}
static const struct mfd_cell ft_svec_mfd_devs1[] = {
MFD_TDC(1),
};
static const struct mfd_cell ft_svec_mfd_devs2[] = {
MFD_TDC(2),
};
static const struct mfd_cell ft_svec_mfd_devs3[] = {
MFD_TDC(1),
MFD_TDC(2),
};
static const struct mfd_cell *ft_svec_mfd_devs[] = {
ft_svec_mfd_devs1,
ft_svec_mfd_devs2,
ft_svec_mfd_devs3,
};
static int ft_svec_probe(struct platform_device *pdev)
{
struct resource *rmem;
int idev = 0;
int ndev;
int irq;
int i;
rmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!rmem) {
dev_err(&pdev->dev, "Missing memory resource\n");
return -EINVAL;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Missing IRQ number\n");
return -EINVAL;
}
for (i = 1; i <= 2; ++i) {
struct fmc_slot *slot = fmc_slot_get(pdev->dev.parent, i);
int present;
if (IS_ERR(slot)) {
dev_err(&pdev->dev,
"Can't find FMC slot %d err: %ld\n",
i, PTR_ERR(slot));
return PTR_ERR(slot);
}
present = fmc_slot_present(slot);
fmc_slot_put(slot);
dev_dbg(&pdev->dev, "FMC-TDC slot: %d, present: %d\n",
i, present);
if (present)
idev |= BIT(i - 1);
}
if (idev == 0)
return -ENODEV;
idev--;
/*
* We know that this design uses the HTVIC IRQ controller.
* This IRQ controller has a linear mapping, so it is enough
* to give the first one as input
*/
ndev = 1 + !!(idev & 0x2);
dev_dbg(&pdev->dev, "Found %d, point to mfd_cell %d\n", ndev, idev);
return mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
ft_svec_mfd_devs[idev], ndev,
rmem, irq, NULL);
}
static int ft_svec_remove(struct platform_device *pdev)
{
mfd_remove_devices(&pdev->dev);
return 0;
}
/**
* List of supported platform
*/
enum ft_svec_version {
FT_SVEC_VER = 0,
};
static const struct platform_device_id ft_svec_id_table[] = {
{
.name = "fmc-tdc-svec",
.driver_data = FT_SVEC_VER,
}, {
.name = "id:000010DC574E0002",
.driver_data = FT_SVEC_VER,
}, {
.name = "id:000010dc574e0002",
.driver_data = FT_SVEC_VER,
},
{},
};
static struct platform_driver ft_svec_driver = {
.driver = {
.name = "fmc-tdc-svec",
.owner = THIS_MODULE,
},
.id_table = ft_svec_id_table,
.probe = ft_svec_probe,
.remove = ft_svec_remove,
};
module_platform_driver(ft_svec_driver);
MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
MODULE_DESCRIPTION("Driver for the SVEC TDC 1 ns 5 channels");
MODULE_DEVICE_TABLE(platform, ft_svec_id_table);
MODULE_SOFTDEP("pre: svec_fmc_carrier fmc-tdc");
/*
* fmc-tdc (a.k.a) FmcTdc1ns5cha main header.
*
* Copyright (C) 2012-2013 CERN (www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __FMC_TDC_H__
#define __FMC_TDC_H__
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <stdint.h>
#endif
#define FT_VERSION_MAJ 8U /* version of the driver */
#define FT_VERSION_MIN 0U
#define FT_ZIO_TRIG_TYPE_NAME "tdc1n5c-trg\0"
/* default gatewares */
#define FT_GATEWARE_SVEC "fmc/svec-fmc-tdc.bin"
#define FT_GATEWARE_SPEC "fmc/spec-fmc-tdc.bin"
#define FT_BUFFER_EVENTS 256
#define FT_CH_1 1
#define FT_NUM_CHANNELS 5
#define FT_FIFO_MAX 64
enum ft_versions {
TDC_VER = 0,
};
enum ft_zattr_dev_idx {
FT_ATTR_DEV_VERSION = 0,
FT_ATTR_DEV_SECONDS,
FT_ATTR_DEV_COARSE,
FT_ATTR_DEV_SEQUENCE,
FT_ATTR_DEV_COMMAND, /* see below for commands */
FT_ATTR_DEV_ENABLE_INPUTS,
FT_ATTR_DEV_RESERVE_7,
FT_ATTR_DEV__LAST,
};
enum ft_zattr_in_idx {
/* PLEASE check "NOTE:" above if you edit this */
FT_ATTR_TDC_SECONDS = FT_ATTR_DEV__LAST,
FT_ATTR_TDC_COARSE,
FT_ATTR_TDC_FRAC,
FT_ATTR_TDC_TERMINATION,
FT_ATTR_TDC_ZERO_OFFSET,
FT_ATTR_TDC_USER_OFFSET,
FT_ATTR_TDC_WR_OFFSET,
FT_ATTR_TDC_TRANSFER_MODE,
FT_ATTR_TDC_COALESCING_TIME,
FT_ATTR_TDC_RAW_READOUT_MODE,
FT_ATTR_TDC_RECV,
FT_ATTR_TDC_TRANS,
FT_ATTR_TDC__LAST,
};
enum ft_zattr_paremeters {
FT_ATTR_PARAM_TEMP = FT_ATTR_TDC__LAST,
};
enum ft_command {
FT_CMD_WR_ENABLE = 0, /* Enable White Rabbit */
FT_CMD_WR_DISABLE, /* Disable it */
FT_CMD_WR_QUERY, /* Check if WR is locked */
FT_CMD_SET_HOST_TIME, /* Set board time to current host time */
FT_CMD_IDENTIFY_ON, /* Identify card by blinking status LEDs, reserved for future use. */
FT_CMD_IDENTIFY_OFF
};
/* Hardware TDC timestamp */
struct ft_hw_timestamp {
uint32_t seconds; /* 1 second resolution */
uint32_t coarse; /* 8 ns resolution */
uint32_t frac; /* In ACAM bins (81 ps) */
uint32_t metadata; /* channel, polarity, etc. */
} __packed;
#define FT_HW_TS_META_CHN_MASK 0x7
#define FT_HW_TS_META_CHN_SHIFT 0
#define FT_HW_TS_META_CHN(_meta) ((_meta & FT_HW_TS_META_CHN_MASK) >> FT_HW_TS_META_CHN_SHIFT)
#define FT_HW_TS_META_POL_MASK 0x8
#define FT_HW_TS_META_POL_SHIFT 3
#define FT_HW_TS_META_POL(_meta) ((_meta & FT_HW_TS_META_POL_MASK) >> FT_HW_TS_META_POL_SHIFT)
#define FT_HW_TS_META_SEQ_MASK 0xFFFFFFF0
#define FT_HW_TS_META_SEQ_SHIFT 4
#define FT_HW_TS_META_SEQ(_meta) ((_meta & FT_HW_TS_META_SEQ_MASK) >> FT_HW_TS_META_SEQ_SHIFT)
struct ft_calibration_raw {
int32_t zero_offset[FT_NUM_CHANNELS - 1];
uint32_t vcxo_default_tune;
uint32_t calibration_temp;
int32_t wr_offset;
};
/* rest of the file is kernel-only */
#ifdef __KERNEL__
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/fmc.h>
#include <linux/dmaengine.h>
#include <linux/workqueue.h>
#include <linux/debugfs.h>
struct ft_memory_ops {
u32 (*read)(void *addr);
void (*write)(u32 value, void *addr);
};
#define TDC_IRQ 0
#define TDC_DMA 0
enum ft_mem_resource {
TDC_MEM_BASE = 0,
};
enum ft_bus_resource {
TDC_BUS_FMC_SLOT = 0,
};
#include <linux/zio-dma.h>
#include <linux/zio-trigger.h>
#include "hw/tdc_regs.h"
#include "hw/tdc_eic.h"
#include "hw/tdc_dma_eic.h"
extern struct zio_trigger_type ft_trig_type;
extern int irq_timeout_ms_default;
#define FT_USER_OFFSET_RANGE 1000000000 /* picoseconds */
#define TDC_CHANNEL_BUFFER_SIZE_BYTES 0x1000000 // 16MB
enum ft_channel_flags {
FT_FLAG_CH_TERMINATED = 0,
FT_FLAG_CH_DO_INPUT,
};
/* Carrier-specific operations (gateware does not fully decouple carrier specific stuff, such as
DMA or resets, from mezzanine-specific operations). */
struct fmctdc_dev;
/**
* Channel statistics
*/
struct fmctdc_channel_stats {
uint32_t received;
uint32_t transferred;
};
/**
* struct ft_calibration - TDC calibration data
* @zero_offset: Input-to-WR timebase offset in ps
* @vcxo_default_tune: Default DAC value for VCXO. Set during init and for
* local timing
* @calibration_temp: Temperature at which the device has been calibrated
* @wr_offset: White Rabbit timescale offset in ps
*
* All of these are little endian in the EEPROM
*/
struct ft_calibration {
int32_t zero_offset[FT_NUM_CHANNELS];
uint32_t vcxo_default_tune;
uint32_t calibration_temp;
int32_t wr_offset;
};
struct ft_channel_state {
unsigned long flags;
int32_t user_offset;
int active_buffer;
#define __FT_BUF_MAX 2
uint32_t buf_addr[__FT_BUF_MAX];
uint32_t buf_size; // in timestamps
struct fmctdc_channel_stats stats;
dma_cookie_t cookie;
struct sg_table sgt;
struct ft_hw_timestamp dummy[FT_FIFO_MAX];
};
enum ft_transfer_mode {
FT_ACQ_TYPE_FIFO = 0,
FT_ACQ_TYPE_DMA,
};
struct fmctdc_trig {
struct zio_ti ti;
};
static inline struct fmctdc_trig *to_fmctdc_trig(struct zio_ti *ti_ptr)
{
return container_of(ti_ptr, struct fmctdc_trig, ti);
}
/*
* Main TDC device context
* @lock it protects: offset (user vs user), wr_mode (user vs user)
* @irq_imr it holds the IMR value since our last modification. Use it
* **only** in the DMA IRQ handlers
* @dma_chan_mask: bitmask to keep track of which channels are
* transferring data. Timestamp interrupts are disabled
* while DMA is running and we touch and this is the only
* place where we use it: so, we do not need to protect it.
*/
struct fmctdc_dev {
enum ft_transfer_mode mode;
/* HW buffer/FIFO access lock */
spinlock_t lock;
/* base addresses, taken from SDB */
void *ft_base;
void *ft_core_base;
void *ft_i2c_base;
void *ft_owregs_base;
void *ft_irq_base;
void *ft_fifo_base;
void *ft_dma_base;
void *ft_dma_eic_base;
struct ft_memory_ops memops;
/* IRQ base index (for SVEC) */
struct fmc_slot *slot;
struct platform_device *pdev;
struct zio_device *zdev, *hwzdev;
/* carrier private data */
void *carrier_data;
/* current calibration block */
struct ft_calibration calib;
/* DS18S20 temperature sensor 1-wire ID */
uint8_t ds18_id[8];
/* next temperature measurement pending? */
unsigned long next_t;
/* temperature, degrees Celsius scaled by 16 and its ready flag */
int temp;
int temp_ready;
/* output lots of debug stuff? */
struct ft_channel_state channels[FT_NUM_CHANNELS];
int wr_mode;
uint32_t irq_imr;
struct zio_dma_sgt *zdma;
unsigned long dma_chan_mask;
struct dma_chan *dchan;
int pending_dma;
struct work_struct irq_work;
struct dentry *dbg_dir;
struct debugfs_regset32 dbg_reg32;
struct dentry *dbg_reg;
};
static inline u32 ft_ioread(struct fmctdc_dev *ft, void *addr)
{
return ft->memops.read(addr);
}
static inline void ft_iowrite(struct fmctdc_dev *ft,
u32 value, void *addr)
{
ft->memops.write(value, addr);
}
static inline uint32_t ft_readl(struct fmctdc_dev *ft, unsigned long reg)
{
return ft_ioread(ft, ft->ft_core_base + reg);
}
static inline void ft_writel(struct fmctdc_dev *ft, uint32_t v,
unsigned long reg)
{
ft_iowrite(ft, v, ft->ft_core_base + reg);
}
int ft_calib_init(struct fmctdc_dev *ft);
void ft_calib_exit(struct fmctdc_dev *ft);
void ft_enable_acquisition(struct fmctdc_dev *ft, int enable);
int ft_acam_init(struct fmctdc_dev *ft);
void ft_acam_exit(struct fmctdc_dev *ft);
int ft_pll_init(struct fmctdc_dev *ft);
void ft_pll_exit(struct fmctdc_dev *ft);
void ft_ts_apply_offset(struct ft_hw_timestamp *ts, int32_t offset_picos);
void ft_ts_sub(struct ft_hw_timestamp *a, struct ft_hw_timestamp *b);
void ft_set_tai_time(struct fmctdc_dev *ft, uint64_t seconds, uint32_t coarse);
void ft_get_tai_time(struct fmctdc_dev *ft, uint64_t * seconds,
uint32_t * coarse);
void ft_set_host_time(struct fmctdc_dev *ft);
int ft_wr_mode(struct fmctdc_dev *ft, int on);
int ft_wr_query(struct fmctdc_dev *ft);
extern struct bin_attribute dev_attr_calibration;
int ft_fifo_init(struct fmctdc_dev *ft);
void ft_fifo_exit(struct fmctdc_dev *ft);
int ft_buf_init(struct fmctdc_dev *ft);
void ft_buf_exit(struct fmctdc_dev *ft);
int ft_time_init(struct fmctdc_dev *ft);
void ft_time_exit(struct fmctdc_dev *ft);
void ft_zio_kill_buffer(struct fmctdc_dev *ft, int channel);
int ft_zio_register(void);
void ft_zio_unregister(void);
int ft_zio_init(struct fmctdc_dev *ft);
void ft_zio_exit(struct fmctdc_dev *ft);
void ft_set_vcxo_tune (struct fmctdc_dev *ft, int value);
struct zio_channel;
int ft_enable_termination(struct fmctdc_dev *ft, int channel, int enable);
void ft_irq_coalescing_size_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t size);
void ft_irq_coalescing_timeout_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t timeout_ms);
uint32_t ft_irq_coalescing_timeout_get(struct fmctdc_dev *ft,
unsigned int chan);
int ft_debug_init(struct fmctdc_dev *ft);
void ft_debug_exit(struct fmctdc_dev *ft);
/**
* It enables the acquisition on a give channel
* @ft FmcTdc FMC TDC device instance
* @chan channel number [0, N]
*/
static inline void ft_enable(struct fmctdc_dev *ft, unsigned int chan)
{
uint32_t ien;
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
ien |= (TDC_INPUT_ENABLE_CH1 << chan);
ft_writel(ft, ien, TDC_REG_INPUT_ENABLE);
}
/**
* It disables the acquisition on a give channel
* @ft FmcTdc FMC TDC device instance
* @chan channel number [0, N]
*/
static inline void ft_disable(struct fmctdc_dev *ft, unsigned int chan)
{
uint32_t ien;
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
ien &= ~(TDC_INPUT_ENABLE_CH1 << chan);
ft_writel(ft, ien, TDC_REG_INPUT_ENABLE);
}
#endif // __KERNEL__
#endif // __FMC_TDC_H__
This diff is collapsed.
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright CERN 2018-2019
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/debugfs.h>
#include "fmc-tdc.h"
#define FT_DBG_REG32_BUF(_n) \
{ \
.name = "TDC-BUF:ch"#_n":csr", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_CSR + (_n * 0x40), \
}, \
{ \
.name = "TDC-BUF:ch"#_n":cur-base", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_CUR_BASE + (_n * 0x40), \
}, \
{ \
.name = "TDC-BUF:ch"#_n":cur-count", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_CUR_COUNT + (_n * 0x40),\
}, \
{ \
.name = "TDC-BUF:ch"#_n":cur-size", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_CUR_SIZE + (_n * 0x40), \
}, \
{ \
.name = "TDC-BUF:ch"#_n":next-base", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_NEXT_BASE + (_n * 0x40),\
}, \
{ \
.name = "TDC-BUF:ch"#_n":next-size", \
.offset = TDC_MEZZ_MEM_DMA_OFFSET + TDC_BUF_REG_NEXT_SIZE + (_n * 0x40),\
}
static const struct debugfs_reg32 ft_debugfs_reg32[] = {
FT_DBG_REG32_BUF(0),
FT_DBG_REG32_BUF(1),
FT_DBG_REG32_BUF(2),
FT_DBG_REG32_BUF(3),
FT_DBG_REG32_BUF(4),
};
int ft_debug_init(struct fmctdc_dev *ft)
{
int err;
ft->dbg_dir = debugfs_create_dir(dev_name(&ft->pdev->dev), NULL);
if (IS_ERR_OR_NULL(ft->dbg_dir)) {
err = PTR_ERR(ft->dbg_dir);
dev_err(&ft->zdev->head.dev,
"Cannot create debugfs directory \"%s\" (%d)\n",
dev_name(&ft->zdev->head.dev), err);
return err;
}
switch (ft->mode) {
case FT_ACQ_TYPE_DMA:
ft->dbg_reg32.regs = ft_debugfs_reg32;
ft->dbg_reg32.nregs = ARRAY_SIZE(ft_debugfs_reg32);
ft->dbg_reg32.base = ft->ft_base;
ft->dbg_reg = debugfs_create_regset32("regs", 0444,
ft->dbg_dir,
&ft->dbg_reg32);
if (IS_ERR_OR_NULL(ft->dbg_reg)) {
err = PTR_ERR(ft->dbg_reg);
dev_warn(&ft->pdev->dev,
"Cannot create debugfs file \"regs\" (%d)\n",
err);
}
break;
default:
break;
}
return 0;
}
void ft_debug_exit(struct fmctdc_dev *ft)
{
debugfs_remove_recursive(ft->dbg_dir);
}
/*
* fmc-tdc (a.k.a) FmcTdc1ns5cha main header.
*
* Copyright (C) 2018 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/zio.h>
#include <linux/zio-trigger.h>
#include <linux/zio-buffer.h>
#include "fmc-tdc.h"
#include "hw/timestamp_fifo_regs.h"
#define TDC_EIC_EIC_IMR_TDC_FIFO_SHIFT 0
#define TDC_EIC_EIC_IMR_TDC_FIFO_MASK (TDC_EIC_EIC_ISR_TDC_FIFO1 | \
TDC_EIC_EIC_ISR_TDC_FIFO2 | \
TDC_EIC_EIC_ISR_TDC_FIFO3 | \
TDC_EIC_EIC_ISR_TDC_FIFO4 | \
TDC_EIC_EIC_ISR_TDC_FIFO5)
/**
* Get a time stamp from the fifo, if you set the 'last' flag it takes the last
* recorded time-stamp
*/
static int ft_timestamp_get(struct zio_cset *cset, struct ft_hw_timestamp *hwts)
{
struct fmctdc_dev *ft = cset->zdev->priv_d;
void *fifo_addr = ft->ft_fifo_base + TDC_FIFO_OFFSET * cset->index;
hwts->seconds = ft_ioread(ft, fifo_addr + TSF_REG_FIFO_R0);
hwts->coarse = ft_ioread(ft, fifo_addr + TSF_REG_FIFO_R1);
hwts->frac = ft_ioread(ft, fifo_addr + TSF_REG_FIFO_R2);
hwts->metadata = ft_ioread(ft, fifo_addr + TSF_REG_FIFO_R3);
return 1;
}
static void ft_timestamp_get_n(struct zio_cset *cset,
struct ft_hw_timestamp *hwts,
unsigned int n)
{
int i;
for (i = 0; i < n; ++i)
ft_timestamp_get(cset, &hwts[i]);
}
static void ft_fifo_flush(struct fmctdc_dev *ft, unsigned int n)
{
void *fifo_csr_addr = ft->ft_fifo_base
+ TDC_FIFO_OFFSET * n
+ TSF_REG_FIFO_CSR;
ft_iowrite(ft, TSF_FIFO_CSR_CLEAR_BUS, fifo_csr_addr);
}
/**
* Extract a timestamp from the FIFO
*/
static void ft_readout_fifo_n(struct zio_cset *cset, unsigned int n)
{
struct fmctdc_dev *ft;
struct ft_channel_state *st;
int trans = 0;
ft = cset->zdev->priv_d;
st = &ft->channels[cset->index];
st->stats.received += n;
cset->ti->nsamples = n;
zio_arm_trigger(cset->ti);
if (likely(cset->chan->active_block)) {
ft_timestamp_get_n(cset, cset->chan->active_block->data, n);
trans = n;
}
zio_trigger_data_done(cset);
st->stats.transferred += trans;
}
/**
* It gets the IRQ buffer status
* @ft FmcTdc instance
*
* Return: IRQ buffer status
*/
static inline uint32_t ft_irq_fifo_status(struct fmctdc_dev *ft)
{
uint32_t irq_stat;
irq_stat = ft_ioread(ft, ft->ft_irq_base + TDC_EIC_REG_EIC_ISR);
return irq_stat & TDC_EIC_EIC_IMR_TDC_FIFO_MASK;
}
static irqreturn_t ft_irq_handler_ts_fifo(int irq, void *dev_id)
{
struct fmctdc_dev *ft = dev_id;
uint32_t irq_stat_orig, fifo_stat, irq_stat;
void *fifo_csr_addr;
unsigned long *loop;
struct zio_cset *cset;
int i, n;
int redo = 10;
irq_stat_orig = ft_irq_fifo_status(ft);
if (!irq_stat_orig)
return IRQ_NONE;
irq_stat = irq_stat_orig;
loop = (unsigned long *) &irq_stat;
do {
for_each_set_bit(i, loop, FT_NUM_CHANNELS) {
cset = &ft->zdev->cset[i];
fifo_csr_addr = ft->ft_fifo_base +
TDC_FIFO_OFFSET * cset->index + TSF_REG_FIFO_CSR;
fifo_stat = ft_ioread(ft, fifo_csr_addr);
n = TSF_FIFO_CSR_USEDW_R(fifo_stat);
if (n == 0)
continue; /* Still something to read */
if (fifo_stat & TSF_FIFO_CSR_FULL) { /* We are loosing TS */
ft_fifo_flush(ft, cset->index);
} else {
ft_readout_fifo_n(cset, n);
/*
* If the FIFO is more than half full,
* read again for a maximum of 'redo'
* times. OPTIMIZATION.
*/
if (n < FT_FIFO_MAX / 2)
irq_stat &= ~BIT(i);
else
redo--;
}
}
} while(irq_stat && redo);
ft_iowrite(ft, irq_stat_orig, ft->ft_irq_base + TDC_EIC_REG_EIC_ISR);
return IRQ_HANDLED;
}
int ft_fifo_init(struct fmctdc_dev *ft)
{
struct resource *r;
int ret;
ft_irq_coalescing_timeout_set(ft, -1, irq_timeout_ms_default);
ft_irq_coalescing_size_set(ft, -1, 40);
r = platform_get_resource(ft->pdev, IORESOURCE_IRQ, TDC_IRQ);
ret = request_any_context_irq(r->start, ft_irq_handler_ts_fifo, 0,
r->name, ft);
if (ret < 0) {
dev_err(&ft->pdev->dev,
"Request interrupt 'FIFO' failed: %d\n",
ret);
return ret;
}
ft_iowrite(ft, TDC_EIC_EIC_IMR_TDC_FIFO_MASK,
ft->ft_irq_base + TDC_EIC_REG_EIC_IER);
return 0;
}
void ft_fifo_exit(struct fmctdc_dev *ft)
{
ft_iowrite(ft, ~0, ft->ft_irq_base + TDC_EIC_REG_EIC_IDR);
free_irq(platform_get_irq(ft->pdev, TDC_IRQ), ft);
}
/*
* Time-related routines for fmc-tdc driver.
*
* Copyright (C) 2013 CERN (http://www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
void ft_ts_from_picos(uint32_t picos, struct ft_hw_timestamp *result)
{
result->frac = (picos % 8000) * 4096 / 8000;
result->coarse = (picos / 8000);
result->seconds = 0;
}
void ft_ts_add(struct ft_hw_timestamp *a, struct ft_hw_timestamp *b)
{
a->frac += b->frac;
if (unlikely(a->frac >= 4096)) {
a->frac -= 4096;
a->coarse++;
}
a->coarse += b->coarse;
if (unlikely(a->coarse >= 125000000)) {
a->coarse -= 125000000;
a->seconds++;
}
a->seconds += b->seconds;
}
void ft_ts_sub(struct ft_hw_timestamp *a, struct ft_hw_timestamp *b)
{
int32_t d_frac, d_coarse = 0;
d_frac = a->frac - b->frac;
if (unlikely(d_frac < 0)) {
d_frac += 4096;
d_coarse--;
}
d_coarse += a->coarse - b->coarse;
if (unlikely(d_coarse < 0)) {
d_coarse += 125000000;
a->seconds--;
}
a->coarse = d_coarse;
a->frac = d_frac;
a->seconds -= b->seconds;
}
void ft_ts_apply_offset(struct ft_hw_timestamp *ts, int32_t offset_picos)
{
struct ft_hw_timestamp offset_ts;
ft_ts_from_picos(offset_picos < 0 ? -offset_picos : offset_picos,
&offset_ts);
if (offset_picos < 0)
ft_ts_sub(ts, &offset_ts);
else
ft_ts_add(ts, &offset_ts);
}
void ft_set_tai_time(struct fmctdc_dev *ft, uint64_t seconds, uint32_t coarse)
{
uint32_t ien;
/* can't change time when inputs are enabled */
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
ft_writel(ft, ien & ~TDC_INPUT_ENABLE_FLAG, TDC_REG_INPUT_ENABLE);
if (coarse != 0)
dev_warn(&ft->pdev->dev,
"Warning: ignoring sub-second part (%d) when setting time.\n",
coarse);
ft_writel(ft, seconds & 0xffffffff, TDC_REG_START_UTC);
ft_writel(ft, TDC_CTRL_LOAD_UTC, TDC_REG_CTRL);
ft_writel(ft, ien | TDC_INPUT_ENABLE_FLAG, TDC_REG_INPUT_ENABLE);
}
void ft_get_tai_time(struct fmctdc_dev *ft, uint64_t *seconds,
uint32_t *coarse)
{
*seconds = ft_readl(ft, TDC_REG_CURRENT_UTC);
*coarse = 0;
}
void ft_set_host_time(struct fmctdc_dev *ft)
{
struct timespec local_ts;
uint32_t ien;
/* can't change time when inputs are enabled */
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
ft_writel(ft, ien & ~TDC_INPUT_ENABLE_FLAG, TDC_REG_INPUT_ENABLE);
getnstimeofday(&local_ts);
ft_writel(ft, local_ts.tv_sec & 0xffffffff, TDC_REG_START_UTC);
ft_writel(ft, TDC_CTRL_LOAD_UTC, TDC_REG_CTRL);
ft_writel(ft, ien | TDC_INPUT_ENABLE_FLAG, TDC_REG_INPUT_ENABLE);
}
void ft_set_vcxo_tune(struct fmctdc_dev *ft, int value)
{
ft_writel(ft, value, TDC_REG_DAC_TUNE);
ft_writel(ft, TDC_CTRL_CONFIG_DAC, TDC_REG_CTRL);
}
int ft_wr_mode(struct fmctdc_dev *ft, int on)
{
unsigned long flags;
spin_lock_irqsave(&ft->lock, flags);
if (on) {
ft_writel(ft, TDC_WR_CTRL_ENABLE, TDC_REG_WR_CTRL);
ft->wr_mode = 1;
} else {
ft_writel(ft, 0, TDC_REG_WR_CTRL);
ft->wr_mode = 0;
ft_set_vcxo_tune(ft, ft->calib.vcxo_default_tune & 0xffff);
}
spin_unlock_irqrestore(&ft->lock, flags);
return 0;
}
int ft_wr_query(struct fmctdc_dev *ft)
{
uint32_t wr_stat;
wr_stat = ft_readl(ft, TDC_REG_WR_STAT);
if (!ft->wr_mode)
return -ENODEV;
if (!(wr_stat & TDC_WR_STAT_LINK))
return -ENOLINK;
if (wr_stat & TDC_WR_STAT_AUX_LOCKED)
return 0;
return -EAGAIN;
}
int ft_time_init(struct fmctdc_dev *ft)
{
/* program the VCXO DAC to the default calibration value */
ft_set_vcxo_tune(ft, ft->calib.vcxo_default_tune);
ft_set_tai_time(ft, 0, 0);
return 0;
}
void ft_time_exit(struct fmctdc_dev *ft)
{
}
This diff is collapsed.
/*
* acam_gpx.h
*
* Copyright (c) 2012-2013 CERN (http://www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; version 2 of the License.
*/
#ifndef __ACAM_GPX_H
#define __ACAM_GPX_H
/* ACAM TDC-GPX Register bits definitions */
#define AR0_ROsc (1<<0)
#define AR0_RiseEn0 (1<<1)
#define AR0_FallEn0 (1<<2)
#define AR0_RiseEn1 (1<<3)
#define AR0_FallEn1 (1<<4)
#define AR0_RiseEn2 (1<<5)
#define AR0_FallEn2 (1<<6)
#define AR0_HQSel (1<<7)
#define AR0_TRiseEn(port) (1<<(10+port))
#define AR0_TFallEn(port) (1<<(19+port))
#define AR1_Adj(chan, value) (((value) & 0xf) << (chan * 4))
#define AR2_GMode (1<<0)
#define AR2_IMode (1<<1)
#define AR2_RMode (1<<2)
#define AR2_Disable(chan) (1<<(3+chan))
#define AR2_Adj(chan, value) (((value)&0xf)<<(12+4*(chan-7)))
#define AR2_DelRise1(value) (((value)&0x3)<<(20))
#define AR2_DelFall1(value) (((value)&0x3)<<(22))
#define AR2_DelRise2(value) (((value)&0x3)<<(24))
#define AR2_DelFall2(value) (((value)&0x3)<<(26))
#define AR3_DelTx(chan, value) (((value)&0x3)<<(5 + (chan-1) * 2))
#define AR3_RaSpeed(chan, value) (((value)&0x3)<<(21 + (chan) * 2))
#define AR4_RaSpeed(chan, value) (((value)&0x3)<<(10 + (chan-3) * 2))
#define AR3_Zero (0) // nothing interesting for the Fine Delay
#define AR4_StartTimer(value) ((value) & 0xff)
#define AR4_Quiet (1<<8)
#define AR4_MMode (1<<9)
#define AR4_MasterReset (1<<22)
#define AR4_PartialReset (1<<23)
#define AR4_AluTrigSoft (1<<24)
#define AR4_EFlagHiZN (1<<25)
#define AR4_MTimerStart (1<<26)
#define AR4_MTimerStop (1<<27)
#define AR5_StartOff1(value) ((value)&0x3ffff)
#define AR5_StopDisStart (1<<21)
#define AR5_StartDisStart (1<<22)
#define AR5_MasterAluTrig (1<<23)
#define AR5_PartialAluTrig (1<<24)
#define AR5_MasterOenTrig (1<<25)
#define AR5_PartialOenTrig (1<<26)
#define AR5_StartRetrig (1<<27)
#define AR6_Fill(value) ((value)&0xff)
#define AR6_StartOff2(value) (((value)&0x3ffff)<<8)
#define AR6_InSelECL (1<<26)
#define AR6_PowerOnECL (1<<27)
#define AR7_HSDiv(value) ((value)&0xff)
#define AR7_RefClkDiv(value) (((value)&0x7)<<8)
#define AR7_ResAdj (1<<11)
#define AR7_NegPhase (1<<12)
#define AR7_Track (1<<13)
#define AR7_MTimer(value) (((value) & 0x1ff)<<15)
#define AR14_16BitMode (1<<4)
#define AR8I_IFIFO1(reg) ((reg) & 0x1ffff)
#define AR8I_Slope1(reg) ((reg) & (1<<17) ? 1 : 0)
#define AR8I_StartN1(reg) (((reg) >> 18) & 0xff)
#define AR8I_ChaCode1(reg) (((reg) >> 26) & 0x3)
#define AR9I_IFIFO2(reg) ((reg) & 0x1ffff)
#define AR9I_Slope2(reg) ((reg) & (1<<17) ? 1 : 0)
#define AR9I_StartN2(reg) (((reg) >> 18) & 0xff)
#define AR9I_ChaCode2(reg) (((reg) >> 26) & 0x3)
#define AR8R_IFIFO1(reg) ((reg) & 0x3fffff)
#define AR9R_IFIFO2(reg) ((reg) & 0x3fffff)
#define AR11_StopCounter0(num) ((num) & 0xff)
#define AR11_StopCounter1(num) (((num) & 0xff) << 8)
#define AR11_HFifoErrU(num) (1 << (num+16))
#define AR11_IFifoErrU(num) (1 << (num+24))
#define AR11_NotLockErrU (1 << 26)
#define AR12_HFifoE (1<<11)
#define AR12_NotLocked (1<<10)
#define AR12_StartNU (1<<26)
#endif
#ifndef __GENNUM_DMA__
#define __GENNUM_DMA__
/*
* fa_dma_item: The information about a DMA transfer
* @start_addr: pointer where start to retrieve data from device memory
* @dma_addr_l: low 32bit of the dma address on host memory
* @dma_addr_h: high 32bit of the dma address on host memory
* @dma_len: number of bytes to transfer from device to host
* @next_addr_l: low 32bit of the address of the next memory area to use
* @next_addr_h: high 32bit of the address of the next memory area to use
* @attribute: dma information about data transferm. At the moment it is used
* only to provide the "last item" bit, direction is fixed to
* device->host
*/
struct gncore_dma_item {
uint32_t start_addr; /* 0x00 */
uint32_t dma_addr_l; /* 0x04 */
uint32_t dma_addr_h; /* 0x08 */
uint32_t dma_len; /* 0x0C */
uint32_t next_addr_l; /* 0x10 */
uint32_t next_addr_h; /* 0x14 */
uint32_t attribute; /* 0x18 */
uint32_t reserved; /* ouch */
};
#define GENUM_DMA_STA_MASK 0x7
#define GENUM_DMA_STA_SHIFT 0
enum gncore_dma_status {
GENNUM_DMA_STA_IDLE = 0,
GENNUM_DMA_STA_DONE,
GENNUM_DMA_STA_BUSY,
GENNUM_DMA_STA_ERROR,
GENNUM_DMA_STA_ABORT,
};
#define GENNUM_DMA_STA_ERR_P2L_MASK 0x78
#define GENNUM_DMA_STA_ERR_P2L_SHIFT 3
enum gncore_dma_status_p2l {
GENNUM_DMA_STA_ERROR_P2L_COMP = 3,
GENNUM_DMA_STA_ERROR_P2L_BUSY,
};
#define GENNUM_DMA_STA_ERR_L2P_MASK 0x780
#define GENNUM_DMA_STA_ERR_L2P_SHIFT 7
enum gncore_dma_status_l2p {
GENNUM_DMA_STA_ERROR_L2P_TX = 7,
GENNUM_DMA_STA_ERROR_L2P_TIMEOUT,
GENNUM_DMA_STA_ERROR_L2P_ABORT,
};
#define GENNUM_DMA_CTL 0x00
#define GENNUM_DMA_STA 0x04
#define GENNUM_DMA_ADDR 0x08
#define GENNUM_DMA_ADDR_L 0x0C
#define GENNUM_DMA_ADDR_H 0x10
#define GENNUM_DMA_LEN 0x14
#define GENNUM_DMA_NEXT_L 0x18
#define GENNUM_DMA_NEXT_H 0x1C
#define GENNUM_DMA_ATTR 0x20
#define GENNUM_DMA_CTL_SWP 0xc
#define GENNUM_DMA_CTL_ABORT 0x2
#define GENNUM_DMA_CTL_START 0x1
#define GENNUM_DMA_ATTR_DIR 0x00000002
#define GENNUM_DMA_ATTR_MORE 0x00000001
#endif /* __GENNUM_DMA__ */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
libfmctdc.a
libfmctdc.so*
.depend
Makefile.specific
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
zio @ db48e6cb
Subproject commit db48e6cbd784958920fc13bbe2965ad46ff0baaa
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment