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()
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
"""
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 source diff could not be displayed because it is too large. You can view the blob instead.
;; 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)
\input texinfo @c -*-texinfo-*-
%
% fmc-tdc.in - main file for the documentation
%
%%%%
%------------------------------------------------------------------------------
%
% NOTE FOR THE UNAWARE USER
% =========================
%
% This file is a texinfo source. It isn't the binary file of some strange
% editor of mine. If you want ASCII, you should "make fine-delay.txt".
%
%------------------------------------------------------------------------------
%
% This is not a conventional info file...
% I use three extra features:
% - The '%' as a comment marker, if at beginning of line ("\%" -> "%")
% - leading blanks are allowed (this is something I can't live without)
% - braces are automatically escaped when they appear in example blocks
%
@comment %**start of header
@documentlanguage en
@documentencoding ISO-8859-1
@setfilename fmc-tdc.info
@settitle fmc-tdc
@iftex
@afourpaper
@end iftex
@paragraphindent none
@comment %**end of header
@setchapternewpage off
@set update-month July 2013
@c @set release 1.1
@set tagname fmc-tdc-sw-v1.0
@c WARNING: in @example I Can't use @value{tagname}, so please look for this
@c string when updating the document.
@finalout
@titlepage
@title FMC TDC User's Manual
@subtitle @value{update-month}
@subtitle FMC TDC 1ns-5cha hardware and software manual
@author CERN BE-CO-HT / Tomasz Wlostowski, Alessandro Rubini
@end titlepage
@headings single
@c ##########################################################################
@iftex
@contents
@end iftex
@c ##########################################################################
@node Top
@top Introduction
This is the user manual for the
@i{FmcTdc1ns5cha} board developed on @code{ohwr.org}, referenced futher
as the @i{FmcTdc}. The manual is heavily based on the documentation
of the @i{Fine Delay} card, written by Alessandro Rubini.
@c ##########################################################################
@node Repositories and Releases
@chapter Repositories and Releases
The code and documentation are distributed in the following places:
@table @code
@item http://www.ohwr.org/projects/fmc-tdc-sw/documents
This place hosts the pdf documentation for official releases.
@item http://www.ohwr.org/projects/fmc-tdc-sw/files
Here we place the @i{.tar.gz} file for every release,
including the @i{git} tree and compiled documentation (for
those who lack TeX).
@item git://ohwr.org/fmc-projects/fmc-tdc/fmc-tdc-sw.git
Read-only repository for the software and documentation.
@item git@@ohwr.org:fmc-projects/fmc-tdc/fmc-tdc-sw.git
Read-write repositories, for those authorized.
@end table
@c The official release of this
@c repository has a tag called called
@c @code{@value{tagname}}. The same tag is used in related
@c repositories (@i{zio}, @i{spec-sw} and the hardware repository).
@c Any official hot fixes, if any, for this
@c release live in the branch called
@c @code{@value{tagname}-fixes}, in each repository.
@b{Note:} If you got this from the repository (as opposed to a named
@i{tar.gz} or @i{pdf} file) it may happen that you are looking at a later commit
than the release this manual claims to document.
It is a fact of life that developers forget
to re-read and fix documentation while updating the code. In that case,
please run ``@code{git describe HEAD}'' to ensure where you are.
@c ##########################################################################
@node Hardware Description
@chapter Hardware Description
The @i{FmcTdc} is an FPGA Mezzanine Card (FMC - VITA 57 standard), containing a 5-channel Time To
Digital Converter (TDC). All channels share same time base, therefore one can relate timestamps of pulses coming to different channels.
@c ==========================================================================
@node Requirements and Supported Platforms
@section Requirements and Supported Platforms
@i{FmcTdc} can work with any VITA 57-compliant FMC carrier, provided that the carrier's FPGA has enough logic resources.
This release of the driver software supports the following carriers:
@itemize
@item SPEC (Simple PCI-Express Carrier),
@item SVEC (Simple VME64x Carrier)
@end itemize
In order to operate @i{FmcTdc}, the following hardware/software components are required:
@itemize @bullet
@item A standard PC with at least one free 4x (or wider) PCI-Express slot and a SPEC PCI-Express FMC carrier (supplied with an @i{FmcTdc}),
@item In case of a VME version: any VME64x crate with a controller (tested on a MEN A20) and a SVEC VME64x FMC carrier (supplied with one or two @i{FmcTdc}s),
@item 50-ohm cables with 1-pin LEMO 00 plugs for connecting the I/O signals,
@item Any Linux (kernel 2.6 or 3.0+) distribution.
@end itemize
@b{Note}: The software has been developed on Linux-3.11, and supports legacy kernels down to 2.6.24 (CERN RT-patched kernel).
@c ==========================================================================
@node Mechanical/Environmental
@section Mechanical/Environmental
@noindent @b{Mechanical and environmental specs:}
@itemize @bullet
@item Format: FMC (VITA 57),
@item Operating temperature range: 0 - 90 degC,
@item Carrier connection: 160-pin Low Pin Count FMC connector.
@end itemize
@c ==========================================================================
@node Electrical
@section Electrical
@noindent @b{Inputs/Outputs:}
@itemize @bullet
@item 5 trigger inputs (LEMO 00),
@item 6 LEDs: 5 for indicating input termination, 1 as a general-purpose status indicator,
@item Carrier communication via 160-pin Low Pin Count FMC connector.
@end itemize
@noindent @b{Trigger input:}
@itemize
@item TTL/LVTTL levels, DC-coupled,
@item 2 kOhm or 50 Ohm input impedance (software-selectable). 50 Ohm termination is indicated by a corresponding LED in the front panel,
@item Power-up input impedance: 2 kOhm,
@item Protected against short circuit, overcurrent (> 200 mA) and overvoltage (up to +15 V),
@item Maximum input pulse edge rise time: 20 ns.
@end itemize
@noindent @b{Power supply:}
@itemize
@item Used power supplies: P12V0, P3V3, P3V3_AUX, VADJ (voltage monitor only).
@item Typical current consumption: FIXME (P12V0) + FIXME (P3V3).
@item Power dissipation: [fixme: Eva] W.
@end itemize
@c ==========================================================================
@node Timing
@section Timing
@noindent @b{Time base:}
@itemize @bullet
@item On-board oscillator accuracy: +/- 4 ppm (i.e. max. 4 ns error for pulses separated by 1 ms).
@item When using White Rabbit as the timing reference: depending on the characteristics of the grandmaster clock and the carrier used.
%On SPEC v 4.0 FMC carrier, the accuracy is better than 1 ns.
@end itemize
@noindent @b{Input timing:}
@itemize @bullet
@item Minimum pulse width: @math{t_{IW}} = 100 ns. Pulses below 100 ns are rejected. Width checking is done in software by subtracting rising and falling edge timestamps.
@item Minimum pulse spacing: 100 ns.
@item Only rising edges are time tagged.
@item TDC accuracy: 700 ps peak-peak (six sigma)
@item TDC precision: 500 ps peak-peak (six sigma).
@item TDC resolution: 81 ps.
@end itemize
@c ##########################################################################
@node Driver Features
@chapter Driver Features
This driver is based on @i{ZIO} and @i{fmc-bus}. The features it provides are:
@itemize
@item Setting up the board.
@item Reading time.
@item The obvious: reading timestamps of incoming pulses.
@item User-defined input offsets.
@item Readout of card temperature.
@item Controlling channel termination.
@item Globally enabling/disabling timestamp acquisition.
@end itemize
The features that are planned, but are @b{not supported} in this release are:
@itemize
@item White Rabbit synchronization
@end itemize
For each feature offered the driver (and documentation) the driver
tries to offer the following items; sometimes however one of them is missing for a specific
driver functionality, if we don't consider it important enough.
@itemize @bullet
@item A description how does the feature work.
@item A C-language API to access the feature with data structures.
@item An example program based on that API.
@end itemize
@c ##########################################################################
@node Installation
@chapter Installation
This driver depends on three other modules (three @code{ohwr.org}
packages), as well as the Linux kernel. Also, it
must talk to a specific FPGA binary file running in the device.
@c ==========================================================================
@node Gateware Dependencies
@section Gateware Dependencies
This version of the driver has been developed to run with
the FPGA binary included in the
package as @code{binaries/[carrier]-fmc-tdc.bin}.
The @code{[carrier]} prefix denotes the type of FMC carrier the gateware is for -
that is, @code{spec} or @code{svec}. These binary files are included in the software release package. They are also
available directly in the @i{Files} tab on the OHWR project website.
If the gateware is updated, I'll take care to always include in this
package the exact binary the software is developed and verified
against.
@c ==========================================================================
@node Gateware Installation
@section Gateware Installation
To install the FPGA image in the target system, please follow the
instructions in the documentation of @i{spec-sw} or @i{svec-sw}.
To summarize, you'll need to place the @code{.bin} file, properly renamed, in
@i{/lib/firmware} or a subdirectory thereof.
The default name used by this driver is
@file{fmc/spec-fmc-tdc.bin} or @file{fmc/svec-fmc-tdc.bin}.
If you have several @i{FMC TDC} cards in the same host, you can
load different binaries in different cards, using appropriate
module parameters.
The following example commands are sufficient for most users:
@smallexample
$ sudo mkdir -p /lib/firmware/fmc
$ sudo cp spec-fmc-tdc.bin svec-fmc-tdc.bin /lib/firmware/fmc
@end smallexample
@c ==========================================================================
@node Software Dependencies
@section Software Dependencies
The kernel versions I am using during development is 3.11. Everything
used here is known to build with all versions since 2.6.32 and up to 3.11. Note that the @i{FmcTdc} driver
will compile older kernels (such as 2.6.24-rt that is used at CERN), but the modules it depends on will have to
be built from backport branches.
The driver, then, is based on the ZIO framework, available from
@code{ohwr.org}. I'm developing with the @code{v1.0-fixes} branch of
the framework. Again, this commit of ZIO is known
to work in the kernel range 2.6.32..3.11 and a backport to 2.6.24 is
available.
The FMC mezzanine is supported by means of the @i{fmc-bus}
software project. Such support used to be part of the @i{spec-sw}
package, but is not a project of its own. This @i{FmcTdc}
kernel module registers as a @i{driver} for the FMC bus abstraction,
and is verified with version @t{v2013-05.1} of the FMC package.
The same kernel range applies.
Both packages (ZIO and @i{fmc-bus})
are currently checked out as @i{git submodules}
of this package, and each of them is retrieved at the right version
to be compatible with this driver. This means you may just
ignore software dependencies and everything should work.
The carrier driver is not strictly related to this package, but
@i{FmcTdc} is released against version @t{v2013-04} of
@i{spec-sw} and version [fixme] of @i{svec-sw}.
Unfortunately, all the packages are moving fast: we are approaching a stable
and long-lasting status but we are not there yet. Please stick
to the released versions named in this section, unless you are involved
in development.
@c ==========================================================================
@node Software Installation
@section Software Installation
To install this software package, you need to tell it where your
kernel sources live, so the package can pick the right header files.
You need to set only one environment variable:
@table @code
@item LINUX
The top-level directory of the Linux kernel you are compiling
against. If not set, the default may work if you compile in the same
host where you expect to run the driver.
@end table
Most likely, this is all you need to set. After this, you can
run:
@example
make
sudo make install LINUX=$LINUX
@end example
In addition to the normal installation procedure for
@code{fmc-tdc.ko} you'll see the following message:
@example
WARNING: Consider "make prereq_install"
@end example
The @i{prerequisite} packages are @i{zio} and @i{fmc-bus};
unless you already installed your own preferred version, you are
expected to install the version this packages suggests. This step
can be performed by:
@example
make
sudo make prereq_install LINUX=$LINUX
@end example
The step is not performed by default to avoid overwriting some
other versions of the drivers. After @code{make prereq_install},
the warning message won't be repeated any more if you change this
driver and @code{make install} again.
After installation, your carrier driver should load automatically
(for example, the PCI bus will load @code{spec.ko}), but @code{
fmc-tdc.ko} must be loaded manually, because support for
automatic loading is not yet in place. The suggested command is
one or the other of the following two:
@smallexample
modprobe fmc-tdc [<parameter> ...] # after make install
insmod kernel/fmc-tdc.ko [<parameter> ...] # if not installed
@end smallexample
Available module parameters are described in @ref{Module Parameters}.
Unless you customized or want to customize one of the three
related packages, you can skip the rest of this section.
Note that in case of the VME version, one must configure VME base addresses/LUNs
through parameters of the SVEC driver. A sample set of commands is below:
@smallexample
modprobe fmc-tdc [<parameter> ...] # after make install
modprobe svec slot=4 vmebase=0xa0000000 lun=0 vector=0x86
@end smallexample
It assumes a SVEC with A32 mapping at @code{0xa0000000}, identified as card @code{0} in the system and residing in slot @code{4} of the VME crate.
@sp 1
In order to compile @i{FmcTdc} against a specific repository of one
of the related packages, ignoring the local @i{submodule}
you can use one or more of the following
environment variables:
@table @code
@item ZIO
@itemx FMC_BUS
The top-level directory of the repository checkout of each
package. Most users won't need to set them, as the Makefiles
point them to the proper place by default.
@end table
If any of the above is set, headers and dependencies for the
respective package are taken from the chosen directory. If you
@code{make prereq_install} with any of these variables set, they are
be used to know where to install from, instead of using local submodules.
@c ==========================================================================
@node Module Parameters
@section Module Parameters
The driver accepts a few load-time parameters for configuration. You
can pass them to @i{insmod} amd @i{modprobe} directly, or write them
in @code{/etc/modules.conf} or the proper file in @code{/etc/modutils/}.
The following parameters are used:
@table @code
@item verbose=
The parameter defaults to 0. If set, it enables more diagnostic
messages during probe (you may find it is not used, but it is
left in to be useful during further development, and avoid
compile-time changes like use of @code{#ifdef DEBUG}).
@item poll_interval=
The period of the buffer polling timer.
The timer is used to poll for input events on the SVEC card, whose software does
not support interrupts yet. The default interval is 10 milliseconds.
You may want to use the timer while porting to a different carrier,
before sorting out IRQ issues.
@item buffer_size
Numer of entries in the software timestamp buffer (independent buffers
per channel). Defaults to 8192.
@item show_sdb
Defaults to 0. If enabled, the driver will print out the contents of the SDB device tree
in the gateware. Reserved for debugging purposes.
@end table
The module also uses the two parameters provided by the @i{fmc}
framework:
@table @code
@item busid=
A list of bus identifiers the driver will accept to driver.
Other identifiers will lead to a failure in the @i{probe}
function. The meaning of the identifiers is carrier-specific;
the SPEC uses the bus number and @i{devfn}, where the latter
is most likely zero.
@item gateware=
A list of gateware file names. The names passed are made to
match the @i{busid} parameters, in the same order. This
means that you can't make the driver load a different gateware
file without passing the respective @i{busid}. Actually, to
change the gateware for all boards, you may just replace
the file in @file{/lib/firmware}. (Maybe I'll add an
option to change the name at load time for all boards).
@c FIXME: name for gateware file (the global one)
@end table
For example, this host has one SPEC cards:
@smallexample
# lspci | grep CERN
01:00.0 Non-VGA unclassified device: CERN/ECP/EDU Device 018d (rev 03)
@end smallexample
Installing the @code{fmc-tdc} driver should show the following output:
@smallexample
# insmod fmc-tdc.ko
[321272.596475] spec 0000:01:00.0: reprogramming with fmc/spec-fmc-tdc.bin
[321272.791378] spec 0000:01:00.0: FPGA programming successful
[321272.791478] spec 0000:01:00.0: Gateware successfully loaded
[321272.791483] fmc_tdc FmcTdc1ns5cha-0100: ft_spec_reset: resetting TDC core through Gennum.
[321275.798831] fmc_tdc FmcTdc1ns5cha-0100: calib: zero_offset[0] = 0
[321275.798835] fmc_tdc FmcTdc1ns5cha-0100: calib: zero_offset[1] = 86
[321275.798837] fmc_tdc FmcTdc1ns5cha-0100: calib: zero_offset[2] = 609
[321275.798840] fmc_tdc FmcTdc1ns5cha-0100: calib: zero_offset[3] = 572
[321275.798842] fmc_tdc FmcTdc1ns5cha-0100: calib: zero_offset[4] = 335
[321275.798844] fmc_tdc FmcTdc1ns5cha-0100: calib: vcxo_default_tune 43343
[321275.814487] fmc_tdc FmcTdc1ns5cha-0100: ft_acam_init: ACAM initialization OK.
[321276.580679] fmc_tdc FmcTdc1ns5cha-0100: ft_read_temp: Scratchpad:
[321276.580683] e3:02:4b:46:7f:ff:0d:10:c0
[321276.580692] fmc_tdc FmcTdc1ns5cha-0100: ft_read_temp: Temperature 0x2e3 (12 bits: 46.187)
@end smallexample
@c ##########################################################################
@node Source Code Conventions
@chapter Source Code Conventions
This is a random list of conventions I use in this package
@itemize @bullet
@item All internal symbols in the driver begin with @code{ft_}
(excluding local variables like @i{i} and similar stuff). So you know
if something is local or comes from the kernel.
@item All library functions and public data begin with @code{fmctdc_}.
@item The board passed as a library token (@code{struct fmctdc_board})
is opaque, so the user doesn't access it. Internally it is called
@code{userb} because @code{b} is the real one being used. If you need
to access library internals from a user file just include @code{fmctdc-lib-private.h}
after including @code{fmctdc-lib.h}.
@item The driver header is called @code{fmc-tdc.h} while the user one
is @code{fmctdc-lib.h}. The latter includes the former, which user
programs should not refer to.
@item The @i{test} contains a set of example programs for the library.
@end itemize
@c ##########################################################################
@node API Overview
@chapter API Overview
In this chapter we will not discuss about the details of the API; for this
purpose please generate (and read) the doxygen documentation:
@smallexample
cd doc
make doxygen
@end smallexample
Here we will present generic concept behind the API, all implementation details
are available on the doxygen documentation.
@c ==========================================================================
@node Time-Stamp modes
@section Time-Stamp modes
The driver provides two time-stamp modes that you can configure for
each channel:
@itemize @bullet
@item base time
@item difference time
@end itemize
The standard mode is the @i{base time} mode. When this mode is enabled for a
given channel, the provided time-stamps will be a pure time-stamp according to
the TDC internal base-time. You can change the internal base-time of a TDC
board when the acquisition is off.
The @i{difference time} mode can be enabled by assigning a channel reference
to a given channel (target). When you assing a channel reference to a channel
the time-stamps produced by the driver 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 mode, and go back to the base time mode,
you must remove the channel reference.
@c ##########################################################################
@chapter Example programs
@c ==========================================================================
@node Initialization and Cleanup
@section Initialization and Cleanup
The sample program @i{fmctdc-list} lists the boards currently on the system,
using @i{fmctdc_init}:
@smallexample
# ./fmctdc-list
Found 1 board(s):
0100, /dev/zio/ft-0100, /sys/bus/zio/devices/ft-0100
@end smallexample
@c ==========================================================================
@node Time Management
@section Time Management
The program @i{fmctdc-board} is a command-line front-end to the library,
to validate the library works as expected. The first parameter is the board bus ID, the second one is the command, the third one is the new seconds counter value:
@smallexample
# ./fmctdc-time 0100 get
Current TAI time is 813.000000000 s
# ./fmctdc-time 0100 set 10000
# ./fmctdc-time 0100 get
Current TAI time is 10001.000000000 s
# ./fmctdc-time 0100 host
# ./fmctdc-time 0100 get
Current TAI time is 1376987950.000000000 s
@end smallexample
@c ==========================================================================
@node Input Configuration
@section Input Configuration
The example program @i{fmctdc-term} demonstrates use of the function.
It just enables or disables the 50-ohm resistor. The effect is
usually verifiable by hooking a scope to the input signal:
@smallexample
# ./fmctdc-term 0100
channel 1: 50 Ohm termination is off
channel 2: 50 Ohm termination is off
channel 3: 50 Ohm termination is off
channel 4: 50 Ohm termination is off
channel 5: 50 Ohm termination is off
# ./fmctdc-term 0100 1 on
channel 1: 50 Ohm termination is on
@end smallexample
The other example, @i{fmctdc-acquisition}, works as follows:
@smallexample
# ./fmctdc-acquisition 0100 off
# ./fmctdc-acquisition 0100
board 0100: acquisition is off
@end smallexample
@c ==========================================================================
@node Reading Input Time-stamps
@section Reading Input Time-stamps
There is an example program provided, called @i{fmctdc-read}:
Calling @i{fmctdc-read} with just the board ID will keep reading all timestamps from
all channels. One can limit the number of samples to be read by using @code{-s} parameter.
@smallexample
# fmctdc-read 0100
channel 1 seq 1699 ts 1376988979.899,210,574,710 ps
channel 3 seq 11 ts 1376988979.899,210,599,375 ps
channel 1 seq 1700 ts 1376988980.055,610,386,324 ps
channel 3 seq 12 ts 1376988980.055,610,410,908 ps
(...)
@end smallexample
The program also demonstrates the non-blocking mode, which can be enabled by @code{-n} parameter.
Unfortunately, even if there are samples pending, @i{read}
will only return one of them, because the ZIO device will only see the
next sample slightly after returning the previous one. This is a buffering
problem with our use of ZIO. Therefore, calling @i{read} in non-blocking mode will only return the first
pending sample from each channel.
The @i{read} utility can also dump hardware (WR) timestamps instead of converting them to seconds. It is also possible to specify
the range of channels to be read:
@smallexample
# ./fmctdc-read -w 0100 1
channel 1 seq 1981 ts 1376989024:000548630:2712
channel 1 seq 1982 ts 1376989024:020097955:1262
channel 1 seq 1983 ts 1376989024:039649072:3338
@end smallexample
Note that if the input pulses come too often, the timestamp buffers will overflow. This is indicated by making a gap in the sequence ID. Due to gateware
issues, it is @b{not possible} to determine the exact number of lost samples by subtracting the sequence IDs.
@node Setting user input offsets
@section Setting user input offsets
It is possible to add an user-defined offset to all timestamps coming to a particular channel, for example to compensate for cabling delay.
There is no dedicated API function for that purpose, it may be added in future relases. Setting the offset currently can be done through ZIO @code{sysfs} parameters.
For example:
@smallexample
# echo 10000 > /sys/bus/zio/devices/ft-0100/ft-ch1/user-offset
@end smallexample
will result with the driver adding extra 10 ns (= 10000 ps) to each timestamp coming to channel 1 of the card @code{0100}.
@c ##########################################################################
@node Known Bugs and Missing Features
@chapter Known Bugs and Missing Features
This package is still work in progress, and unfortunately the same
applies to the packages it depends on -- @i{zio} and @i{fmc-bus}.
@c ==========================================================================
@node Bugs in Related Packages
@section Bugs in Related Packages
The current package set (i.e., @i{zio}, @i{fmc-bus} and this one) has
the following known issues exposed by @i{fine-delay}:
@itemize @bullet
@item The auto-loading of @i{fmc} modules is not yet working:
@item The @i{user} trigger of ZIO is really user-driven, so the driver
can't push stuff to the buffer until asked to. Also, a related buglet
prevents to return data immediately when asked. This will be fixed,
but it currently results in the @i{read} function only returning one
sample, and an immediately-following non-blocking @i{read} will say
nothing is there, yet.
@end itemize
@c ==========================================================================
@node Bugs in This Package
@section Bugs in This Package
This is the list of known bugs and missing features over what hardware
allows:
@itemize @bullet
@item Calibration information in the EEPROM is not verified, as its the format does not include
any means of verification (hash, checksum, etc.)
@item Purging the buffers by disabling acquisition sometimes leaves a single old sample in the
ZIO buffer. This is probably due to a bug in ZIO.
@end itemize
@node Gateware issues
@section Gateware issues
This is the list of known gateware issues, that sometimes affected the architecture and features of the driver.
@itemize @bullet
@item Time setting is possible only when acquisition is disabled.
@item No coarse time counter setting possible.
@item Seconds counter has 32 bits. This may bring a surprise in 2038 if the time is set according to the Unix epoch.
@item No White Rabbit support.
@item Acquisition can be only enabled/disabled globally for all channels.
@item No support for hardware sequence numbers. This, combined with the HW timestamp buffer shared between all channels,
makes it impossible to count the number of lost samples if the HW buffer overflows.
@item Pulse width checking has to be done in software.
@item No possibility to check FMC's PLL status on the SPEC (if the mezznanine is broken, the OS will
freeze without reporting any error).
@item SVEC version must have two mezzanines inserted, even if only one of them is used. This is due to clocking internal
Wishbone bus directly from FMC PLLs. Both of them need to be running before the driver can read the SDB tree without dropping
an error and/or crashing the machine.
@end itemize
@c ==========================================================================
@node Wish List
@section Wish List
Other less important issues may be dealt with over time, but are not
urgent as I write this:
@itemize
@item The driver should register its own ZIO trigger, or use the new
attribute for ``greedy-input'' planned in new versions of ZIO
(thank you Federico). Currently there's no buffering and reading is
slower than it could be.
@item White Rabbit support, as in the @i{Fine Delay}.
@end itemize
@c ##########################################################################
@node Troubleshooting
@chapter Troubleshooting
This chapters lists a few errors that may happen and how to deal with
them.
@c ==========================================================================
@node ZIO Doesn't Compile
@section ZIO Doesn't Compile
Compilation of ZIO ma fail with error like:
@smallexample
zio-ad788x.c:180: error: implicit declaration of function "spi_async_locked"
@end smallexample
This happens because the function wasn't there in your older kernel
version, and your system is configured to enable @code{CONFIG_SPI}.
To fix, please just remove the @i{zio-ad788x} line from
@code{drivers/Makefile}.
@c ==========================================================================
@node make modules_install misbehaves
@section make modules_install misbehaves
The command @i{sudo make modules_install} may place the modules in the wrong
directory or fail with an error like:
@smallexample
make: *** /lib/modules/2.6.37+/build: No such file or directory.
@end smallexample
This happens when you compiled by setting @code{LINUX=} and your
@i{sudo} is not propagating the environment to its child processes.
In this case, you should run this command instead
@smallexample
sudo make modules_install LINUX=$LINUX
@end smallexample
@c ==========================================================================
@node Version Mismatch
@section Version Mismatch
The @i{fmctdc} library may report a version mismatch like this:
@example
spusa# ./lib/fmctdc-time get
fmctdc_init: version mismatch, lib(1) != drv(2)
./lib/fmctdc-time: fmctdc_init(): Input/output error
@end example
This reports a difference in the way ZIO attributes are laid out, so user
space may exchange wrong data in the ZIO control block, or may try to
access inexistent files in @i{/sys}. I suggest recompiling both the kernel
driver and user space from a single release of the source package.
@c ##########################################################################
@bye
@c LocalWords: gnudd titlepage iftex texinfo CERN documentlanguage settitle
@c LocalWords: documentencoding setfilename afourpaper paragraphindent FPGA
@c LocalWords: setchapternewpage finalout gateware ohwr modprobe insmod cset
@c LocalWords: smallexample ctrl timestamp fdelay struct spusa gitorious http
@c LocalWords: tagname FmcDelay timestamping Timestamps perf picosecond ATTR
@c LocalWords: usec EEPROM sudo
#! /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__
/*
* 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/dmaengine.h>
#include <linux/workqueue.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_DMA_SHIFT 5
#define TDC_EIC_EIC_IMR_TDC_DMA_MASK (TDC_EIC_EIC_ISR_TDC_DMA1 | \
TDC_EIC_EIC_ISR_TDC_DMA2 | \
TDC_EIC_EIC_ISR_TDC_DMA3 | \
TDC_EIC_EIC_ISR_TDC_DMA4 | \
TDC_EIC_EIC_ISR_TDC_DMA5)
static int dma_buf_ddr_burst_size_default = 16;
module_param_named(dma_buf_ddr_burst_size, dma_buf_ddr_burst_size_default,
int, 0444);
MODULE_PARM_DESC(dma_buf_ddr_burst_size,
"DDR size coalesing timeout (default: 16 timestamps).");
static void ft_buffer_burst_size_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t size)
{
void *base = ft->ft_dma_base + (0x40 * chan);
uint32_t tmp;
tmp = ft_ioread(ft, base + TDC_BUF_REG_CSR);
tmp &= ~TDC_BUF_CSR_BURST_SIZE_MASK;
tmp |= TDC_BUF_CSR_BURST_SIZE_W(size);
ft_iowrite(ft, tmp, base + TDC_BUF_REG_CSR);
}
static void ft_buffer_burst_enable(struct fmctdc_dev *ft,
unsigned int chan)
{
void *base = ft->ft_dma_base + (0x40 * chan);
uint32_t tmp;
tmp = ft_ioread(ft, base + TDC_BUF_REG_CSR);
tmp |= TDC_BUF_CSR_ENABLE;
ft_iowrite(ft, tmp, base + TDC_BUF_REG_CSR);
}
static void ft_buffer_burst_disable(struct fmctdc_dev *ft,
unsigned int chan)
{
void *base = ft->ft_dma_base + (0x40 * chan);
uint32_t tmp;
tmp = ft_ioread(ft, base + TDC_BUF_REG_CSR);
tmp &= ~TDC_BUF_CSR_ENABLE;
ft_iowrite(ft, tmp, base + TDC_BUF_REG_CSR);
}
/**
* It tells if the trigger is armed or not
* @ti trigger instance
*
* Return 1 when armed, 0 when un-armed
*/
static inline int zio_trigger_is_armed(struct zio_ti *ti)
{
return !!(ti->flags & ZIO_TI_ARMED);
}
/**
* It tells if the channel has an active block
* @chan channel instance
*
* Return 1 when it has an active block, 0 when it has not an active block
*/
static inline int zio_chan_has_active_block(struct zio_channel *chan)
{
return !!(chan->active_block);
}
/**
* It tells if the acquisition can be done.
* @cset channel set instance
*
* It returns 1, if at least 1 channel is ready for acquisition
*
* Return: 1 when it is possible to acquire, 0 when it is not possible
* to acquire
*/
static int zio_cset_can_acquire(struct zio_cset *cset)
{
int i;
if (!zio_trigger_is_armed(cset->ti))
return 0;
for (i = 0; i < cset->n_chan; ++i) {
if (zio_chan_has_active_block(&cset->chan[i]))
break;
}
if (i == cset->n_chan)
return 0;
return 1;
}
/**
* It disbles interrupts on specific channels according to the given mask
* @ft FmcTdc device instance
* @chan_mask channel bitmask, a bit to one will disable the corresponding
* IRQ channel line
*
* NOTE Use it **only** in the DMA Buffer IRQ handler
*
* We do not use any spinlock here. This function should be called
* only by ft_irq_handler_ts_dma() and nobody else. Since this piece
* of code disables interrupts, there is no risk that it can run because
* of ft_irq_handler_ts_dma().
*/
static void ft_irq_disable_save(struct fmctdc_dev *ft)
{
ft->irq_imr = ft_ioread(ft, ft->ft_irq_base + TDC_EIC_REG_EIC_IMR);
ft_iowrite(ft, ft->irq_imr, ft->ft_irq_base + TDC_EIC_REG_EIC_IDR);
}
/**
* It restores the previous known IRQ status
* @ft FmcTdc device instance
*
* NOTE: Use it only in the DMA completion handler
*
* We do not use any spinlock here. This function should be called
* only by ft_irq_handler_dma_complete() and nobody else. This handler can
* run only after ft_irq_handler_ts_dma() successfully complete; within this
* time IRQ are disabled, so nobody can touch ``irq_imr``
*/
static void ft_irq_enable_restore(struct fmctdc_dev *ft)
{
ft_iowrite(ft, ft->irq_imr, ft->ft_irq_base + TDC_EIC_REG_EIC_IER);
ft->irq_imr = 0;
}
/**
* It changes the current acquisition buffer
* @ft FmcTdc instance
* @chan channel number [0, N-1]
*
* Return: it returns buffer index that needs to be transfered
*
* It stops acquisition to the 'current' buffer and switch to the 'next'
* buffer. This works with the assumption that buffers are properly configured
* with valid address and size.
*/
static unsigned int ft_buffer_switch(struct fmctdc_dev *ft, int chan)
{
struct ft_channel_state *st = &ft->channels[chan];
void *base = ft->ft_dma_base + chan * 0x40;
uint32_t csr, base_cur;
unsigned int transfer_buffer;
csr = ft_ioread(ft, base + TDC_BUF_REG_CSR);
csr |= TDC_BUF_CSR_SWITCH_BUFFERS;
ft_iowrite(ft, csr, base + TDC_BUF_REG_CSR);
/*
* It waits until all pending DDR memory transactions from the active
* buffer are committed to the memory.
* This is almost instant (e.g. < 1us), but we never know with
* the PCs going ever faster
*/
while (!(ft_ioread(ft, base + TDC_BUF_REG_CSR) & TDC_BUF_CSR_DONE))
;
/* clear CSR.DONE flag (write 1) */
csr = ft_ioread(ft, base + TDC_BUF_REG_CSR);
csr |= TDC_BUF_CSR_DONE;
ft_iowrite(ft, csr, base + TDC_BUF_REG_CSR);
/*
* we have two buffers in the hardware: the current one and the 'next'
* one. From the point of view of this interrupt handler, the current
* one is to be read out and switched to the 'next' buffer.,
*/
transfer_buffer = st->active_buffer;
base_cur = st->buf_addr[st->active_buffer];
st->active_buffer = 1 - st->active_buffer;
/* update the pointer to the next buffer */
ft_iowrite(ft, base_cur, base + TDC_BUF_REG_NEXT_BASE);
ft_iowrite(ft, st->buf_size | TDC_BUF_NEXT_SIZE_VALID,
base + TDC_BUF_REG_NEXT_SIZE);
return transfer_buffer;
}
/**
* It gets the current number of timestamps in the buffer
* @ft FmcTdc instance
* @chan channel number [0, N -1]
*
* Please note that the returned value refers to the last 'acquired'
* buffer. In other words, after ft_buffer_switch() the acquisition will
* continue on the next buffer and we get the sample counter from the current
* buffer
*/
static unsigned int ft_buffer_count(struct fmctdc_dev *ft, unsigned int chan)
{
void *base = ft->ft_dma_base + chan * 0x40;
return ft_ioread(ft, base + TDC_BUF_REG_CUR_COUNT);
}
/**
* It gets the IRQ buffer status
* @ft FmcTdc instance
*
* Return: IRQ buffer status
*/
static inline uint32_t ft_irq_buff_status(struct fmctdc_dev *ft)
{
uint32_t irq_stat, imr;
irq_stat = ft_ioread(ft, ft->ft_irq_base + TDC_EIC_REG_EIC_ISR);
imr = ft_ioread(ft, ft->ft_irq_base + TDC_EIC_REG_EIC_IMR);
return irq_stat & imr;
}
/**
* It validates the IRQ status
* @ft FmcTdc instance
*
* This is a paranoiac check, but experience tells that with this design
* it is better to double check
*
* Return: 1 if it is valid, otherwise 0
*/
static inline unsigned int ft_irq_status_is_valid(struct fmctdc_dev *ft,
uint32_t irq_stat)
{
uint32_t chan_stat;
chan_stat = ft_readl(ft, TDC_REG_INPUT_ENABLE);
chan_stat &= TDC_INPUT_ENABLE_CH_ALL;
chan_stat >>= TDC_INPUT_ENABLE_CH1_SHIFT;
return !WARN((chan_stat & irq_stat) == 0,
"Received an unexpected interrupt: 0x%X 0x%X\n",
chan_stat, irq_stat);
}
static int ft_zio_block_nr_pages(struct zio_block *block)
{
unsigned int nr_pages;
long kaddr = (long)block->data;
nr_pages = ((kaddr & ~PAGE_MASK) + block->datalen + ~PAGE_MASK);
nr_pages >>= PAGE_SHIFT;
return nr_pages;
}
static int sg_alloc_table_from_block(struct device *dev,
struct sg_table *sgt,
struct zio_block *block)
{
struct page **pages;
unsigned int nr_pages;
size_t max_segment_size;
void *data;
int i;
int err;
if (!block)
return -EINVAL;
nr_pages = ft_zio_block_nr_pages(block);
pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
data = (void *) block->data;
if (is_vmalloc_addr(data)) {
for (i = 0; i < nr_pages; ++i) {
pages[i] = vmalloc_to_page(data + PAGE_SIZE * i);
}
} else {
for (i = 0; i < nr_pages; ++i)
pages[i] = virt_to_page(data + PAGE_SIZE * i);
}
max_segment_size = dma_get_max_seg_size(dev);
max_segment_size &= PAGE_MASK; /* to make alloc_table happy */
err = __sg_alloc_table_from_pages(sgt, pages, nr_pages,
offset_in_page(data),
block->datalen,
max_segment_size, GFP_KERNEL);
if (err)
sg_free_table(sgt);
kfree(pages);
return err;
}
static int ft_zio_block_dma_map_sg(struct fmctdc_dev *ft, unsigned int ch,
struct zio_block *block,
enum dma_data_direction dir)
{
struct ft_channel_state *st = &ft->channels[ch];
int err;
int sg_len;
if (!block)
return -EINVAL;
err = sg_alloc_table_from_block(ft->dchan->device->dev,
&st->sgt, block);
if (err)
return err;
sg_len = dma_map_sg(&ft->pdev->dev,
st->sgt.sgl, st->sgt.nents, dir);
if (sg_len <= 0) {
err = sg_len;
goto err_map;
}
return sg_len;
err_map:
sg_free_table(&st->sgt);
return err;
}
static void ft_zio_block_dma_unmap_sg(struct fmctdc_dev *ft, unsigned int ch,
enum dma_data_direction dir)
{
struct ft_channel_state *st = &ft->channels[ch];
dma_unmap_sg(&ft->pdev->dev,
st->sgt.sgl, st->sgt.nents, dir);
sg_free_table(&st->sgt);
}
static void ft_dma_complete(void *arg)
{
struct zio_cset *cset = arg;
struct fmctdc_dev *ft = cset->zdev->priv_d;
unsigned long flags;
if (WARN(!ft->dchan, "Channel unavailable\n"))
return;
ft_zio_block_dma_unmap_sg(ft, cset->index, DMA_DEV_TO_MEM);
spin_lock_irqsave(&ft->lock, flags);
ft->pending_dma--;
if (!ft->pending_dma)
ft_irq_enable_restore(ft);
spin_unlock_irqrestore(&ft->lock, flags);
spin_lock(&cset->lock);
cset->flags &= ~ZIO_CSET_HW_BUSY;
spin_unlock(&cset->lock);
zio_trigger_data_done(cset);
}
/**
* It matches a valid DMA channel
*/
static bool ft_dmaengine_filter(struct dma_chan *dchan, void *arg)
{
struct dma_device *ddev = dchan->device;
int dev_id = (*((int *)arg) >> 16) & 0xFFFF;
int chan_id = *((int *)arg) & 0xFFFF;
return ddev->dev_id == dev_id && dchan->chan_id == chan_id;
}
static int ft_dma_config(struct fmctdc_dev *ft, unsigned int ch)
{
struct zio_cset *cset = &ft->zdev->cset[ch];
struct ft_channel_state *st = &ft->channels[ch];
struct dma_async_tx_descriptor *tx;
unsigned int transfer;
int sg_len;
struct dma_slave_config sconfig;
int err;
transfer = ft_buffer_switch(ft, ch);
cset->ti->nsamples = ft_buffer_count(ft, ch);
ft->channels[ch].stats.received += cset->ti->nsamples;
memset(&sconfig, 0, sizeof(sconfig));
sconfig.direction = DMA_DEV_TO_MEM;
sconfig.src_addr = st->buf_addr[transfer];
err = dmaengine_slave_config(ft->dchan, &sconfig);
if (err) {
dev_err(&ft->pdev->dev,
"DMA failed for channel %i: can't config %d\n",
ch, err);
return err;
}
zio_arm_trigger(cset->ti); /* actually arm'n'fire */
if (!zio_cset_can_acquire(cset)) {
dev_warn(&ft->pdev->dev,
"DMA failed for channel %i: can't arm ZIO trigger\n",
ch);
return -EBUSY;
}
sg_len = ft_zio_block_dma_map_sg(ft, ch, cset->chan->active_block,
DMA_DEV_TO_MEM);
if (sg_len <= 0) {
err = sg_len;
dev_err(&ft->pdev->dev,
"DMA failed for channel %i: can't map host memory %d\n",
ch, err);
goto err_map;
}
tx = dmaengine_prep_slave_sg(ft->dchan,
st->sgt.sgl, sg_len, DMA_DEV_TO_MEM, 0);
if (!tx) {
dev_err(&ft->pdev->dev,
"DMA failed for channel %i: can't prepare transfer\n",
ch);
err = -EINVAL;
goto err_prep;
}
tx->callback = ft_dma_complete;
tx->callback_param = (void *)cset;
st->cookie = dmaengine_submit(tx);
if (st->cookie < 0) {
dev_err(&ft->pdev->dev,
"DMA failed for channel %i: can't submit %d\n",
ch, st->cookie);
err = st->cookie;
goto err_submit;
}
return 0;
err_submit:
err_prep:
dma_unmap_sg(&ft->pdev->dev,
st->sgt.sgl, st->sgt.nents, DMA_DEV_TO_MEM);
err_map:
sg_free_table(&st->sgt);
return err;
}
static void ft_irq_work(struct work_struct *work)
{
struct fmctdc_dev *ft = container_of(work, struct fmctdc_dev,
irq_work);
unsigned int n_transfers = 0;
unsigned long flags;
unsigned int i;
for_each_set_bit(i, &ft->dma_chan_mask, ft->zdev->n_cset) {
int err = ft_dma_config(ft, i);
if (err)
continue;
n_transfers++;
}
if (likely(n_transfers)) {
spin_lock_irqsave(&ft->lock, flags);
ft->pending_dma = n_transfers;
spin_unlock_irqrestore(&ft->lock, flags);
dma_async_issue_pending(ft->dchan);
} else {
ft_irq_enable_restore(ft);
}
}
static irqreturn_t ft_irq_handler_ts_dma(int irq, void *dev_id)
{
struct fmctdc_dev *ft = dev_id;
uint32_t irq_stat;
WARN(ft->pending_dma,
"Possible data loss: IRQ arrived while pending DMA");
irq_stat = ft_irq_buff_status(ft);
irq_stat &= TDC_EIC_EIC_IMR_TDC_DMA_MASK;
irq_stat >>= TDC_EIC_EIC_IMR_TDC_DMA_SHIFT;
if (!irq_stat || !ft_irq_status_is_valid(ft, irq_stat))
return IRQ_NONE;
ft_irq_disable_save(ft);
ft->dma_chan_mask = irq_stat;
queue_work(system_highpri_wq, &ft->irq_work);
return IRQ_HANDLED;
}
/**
* It configure the double buffers for a given channel
* @param[in] ft FmcTdc device instance
* @param[in] channel range [0, N-1]
*/
static void ft_buffer_size_set(struct fmctdc_dev *ft, int channel)
{
void *base = ft->ft_dma_base + (0x40 * channel);
uint32_t val;
struct ft_channel_state *st;
if (ft->mode != FT_ACQ_TYPE_DMA)
return;
st = &ft->channels[channel];
st->buf_size = TDC_CHANNEL_BUFFER_SIZE_BYTES / sizeof(struct ft_hw_timestamp);
st->active_buffer = 0;
ft_buffer_burst_disable(ft, channel);
/* Buffer 1 */
st->buf_addr[0] = TDC_CHANNEL_BUFFER_SIZE_BYTES * (2 * channel);
ft_iowrite(ft, st->buf_addr[0], base + TDC_BUF_REG_CUR_BASE);
val = (st->buf_size << TDC_BUF_CUR_SIZE_SIZE_SHIFT);
val |= TDC_BUF_CUR_SIZE_VALID;
ft_iowrite(ft, val, base + TDC_BUF_REG_CUR_SIZE);
/* Buffer 2 */
st->buf_addr[1] = TDC_CHANNEL_BUFFER_SIZE_BYTES * (2 * channel + 1);
ft_iowrite(ft, st->buf_addr[1], base + TDC_BUF_REG_NEXT_BASE);
val = (st->buf_size << TDC_BUF_NEXT_SIZE_SIZE_SHIFT);
val |= TDC_BUF_NEXT_SIZE_VALID;
ft_iowrite(ft, val, base + TDC_BUF_REG_NEXT_SIZE);
ft_buffer_burst_size_set(ft, channel, dma_buf_ddr_burst_size_default);
ft_buffer_burst_enable(ft, channel);
dev_vdbg(&ft->pdev->dev,
"Config channel %d: base = %p buf[0] = 0x%08x, buf[1] = 0x%08x, %d timestamps per buffer\n",
channel, base, st->buf_addr[0], st->buf_addr[1],
st->buf_size);
dev_vdbg(&ft->pdev->dev, "CSR: %08x\n",
ft_ioread(ft, base + TDC_BUF_REG_CSR));
}
/**
* It clears the double buffers configuration for a given channel
* @param[in] ft FmcTdc device instance
* @param[in] channel range [0, N-1]
*/
static void ft_buffer_size_clr(struct fmctdc_dev *ft, int channel)
{
void *base = ft->ft_dma_base + (0x40 * channel);
ft_iowrite(ft, 0, base + TDC_BUF_REG_CUR_SIZE);
ft_iowrite(ft, 0, base + TDC_BUF_REG_NEXT_SIZE);
ft_buffer_burst_disable(ft, channel);
}
int ft_buf_init(struct fmctdc_dev *ft)
{
struct resource *r;
int dma_dev_id;
dma_cap_mask_t dma_mask;
unsigned int i;
int ret;
ft_irq_coalescing_timeout_set(ft, -1, irq_timeout_ms_default);
ft_irq_coalescing_size_set(ft, -1, 40);
INIT_WORK(&ft->irq_work, ft_irq_work);
r = platform_get_resource(ft->pdev, IORESOURCE_DMA, TDC_DMA);
if (!r) {
dev_err(&ft->pdev->dev, "Can't set find DMA channel\n");
return -ENODEV;
}
dma_dev_id = r->start;
r = platform_get_resource(ft->pdev, IORESOURCE_IRQ, TDC_IRQ);
ret = request_any_context_irq(r->start, ft_irq_handler_ts_dma, 0,
r->name, ft);
if (ret < 0) {
dev_err(&ft->pdev->dev,
"Request interrupt 'DMA Start' failed: %d\n",
ret);
return ret;
}
dma_cap_zero(dma_mask);
dma_cap_set(DMA_SLAVE, dma_mask);
dma_cap_set(DMA_PRIVATE, dma_mask);
ft->dchan = dma_request_channel(dma_mask, ft_dmaengine_filter,
&dma_dev_id);
if (!ft->dchan) {
dev_dbg(&ft->pdev->dev,
"DMA transfer Failed: can't request channel\n");
ret = -EINVAL;
goto err_dma;
}
/*
* We enable interrupts on all channel. but if we do not enable
* the channel, we should not receive anything. So, even if
* ZIO is not ready to receive data at this time we should not
* see any trouble.
* If we have problems here, the HDL is broken!
*/
ft_iowrite(ft,
DMA_EIC_EIC_IER_DMA_DONE | DMA_EIC_EIC_IER_DMA_ERROR,
ft->ft_dma_eic_base + DMA_EIC_REG_EIC_IER);
ft_iowrite(ft, TDC_EIC_EIC_IMR_TDC_DMA_MASK,
ft->ft_irq_base + TDC_EIC_REG_EIC_IER);
for (i = 0; i < FT_NUM_CHANNELS; i++)
ft_buffer_size_set(ft, i);
return 0;
err_dma:
free_irq(platform_get_irq(ft->pdev, TDC_IRQ), ft);
return ret;
}
void ft_buf_exit(struct fmctdc_dev *ft)
{
unsigned int i;
ft_iowrite(ft, ~0, ft->ft_irq_base + TDC_EIC_REG_EIC_IDR);
ft_iowrite(ft,
DMA_EIC_EIC_IDR_DMA_DONE | DMA_EIC_EIC_IDR_DMA_ERROR,
ft->ft_dma_eic_base + DMA_EIC_REG_EIC_IER);
dma_release_channel(ft->dchan);
free_irq(platform_get_irq(ft->pdev, TDC_IRQ), ft);
for (i = 0; i < FT_NUM_CHANNELS; i++)
ft_buffer_size_clr(ft, i);
}
/*
* Main fmc-tdc driver module.
*
* 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
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/ipmi/fru.h>
#include <linux/fmc.h>
#include <linux/zio.h>
#include <linux/zio-trigger.h>
#include "fmc-tdc.h"
#include "platform_data/fmc-tdc.h"
#include "hw/tdc_regs.h"
int irq_timeout_ms_default = 10;
module_param_named(irq_timeout_ms, irq_timeout_ms_default, int, 0444);
MODULE_PARM_DESC(irq_timeout_ms, "IRQ coalesing timeout (default: 10ms).");
static int test_data_period = 0;
module_param_named(test_data_period, test_data_period, int, 0444);
MODULE_PARM_DESC(test_data_period,
"It sets how many fake timestamps to generate every seconds on the first channel, 0 to disable (default: 0)");
#define FT_EEPROM_TYPE "at24c64"
/**
* It sets the coalescing timeout for the DMA buffers
* @ft FmcTdc instance
* @chan channel buffer -1 for all channels, otherwise [0, 4]
* @timeout timeout in milliseconds
*/
static void ft_dma_irq_coalescing_timeout_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t timeout)
{
int i;
for (i = (chan == -1 ? 0 : chan);
i < (chan == -1 ? ft->zdev->n_cset : chan + 1);
++i) {
uint32_t tmp;
void *base;
base = ft->ft_dma_base + (0x40 * i);
tmp = ft_ioread(ft, base + TDC_BUF_REG_CSR);
tmp &= ~TDC_BUF_CSR_IRQ_TIMEOUT_MASK;
tmp |= TDC_BUF_CSR_IRQ_TIMEOUT_W(timeout);
ft_iowrite(ft, tmp, base + TDC_BUF_REG_CSR);
}
}
/**
* It gets the coalescing timeout for the DMA buffers
* @ft FmcTdc instance
* @chan channel buffer [0, 4]
*
* Return: timeout in milliseconds
*/
static uint32_t ft_dma_irq_coalescing_timeout_get(struct fmctdc_dev *ft,
unsigned int chan)
{
void *base = ft->ft_dma_base + (0x40 * chan);
uint32_t tmp;
tmp = ft_ioread(ft, base + TDC_BUF_REG_CSR);
return TDC_BUF_CSR_IRQ_TIMEOUT_R(tmp);
}
/**
* It sets the coalescing timeout according to the acquisition mode
* @ft FmcTdc instance
* @chan channe [0, 4] (used only for DMA acquisition mode)
* @timeout_ms timeout in milliseconds to trigger IRQ
*/
void ft_irq_coalescing_timeout_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t timeout_ms)
{
switch (ft->mode) {
case FT_ACQ_TYPE_FIFO:
if (unlikely(chan != -1)) {
dev_dbg(&ft->pdev->dev,
"%s: FIFO acquisition mode has a gobal coalesing timeout. Ignore channel %d, set global value\n",
__func__, chan);
}
ft_writel(ft, timeout_ms, TDC_REG_IRQ_TIMEOUT);
break;
case FT_ACQ_TYPE_DMA:
ft_dma_irq_coalescing_timeout_set(ft, chan, timeout_ms);
break;
}
}
/**
* It sets the coalescing size according to the acquisition mode
* @ft FmcTdc instance
* @chan channe [0, 4] (used only for DMA acquisition mode)
*
* Return: timeout in milliseconds
*/
uint32_t ft_irq_coalescing_timeout_get(struct fmctdc_dev *ft,
unsigned int chan)
{
uint32_t timeout = 0;
switch (ft->mode) {
case FT_ACQ_TYPE_FIFO:
if (unlikely(chan != -1)) {
dev_dbg(&ft->pdev->dev,
"%s: FIFO acquisition mode has a gobal coalesing timeout. Ignore channel %d, get global value\n",
__func__, chan);
}
timeout = ft_readl(ft, TDC_REG_IRQ_THRESHOLD);
break;
case FT_ACQ_TYPE_DMA:
timeout = ft_dma_irq_coalescing_timeout_get(ft, chan);
break;
default:
dev_err(&ft->pdev->dev, "%s: unknown acquisition mode %d\n",
__func__, ft->mode);
}
return timeout;
}
/**
* It sets the coalescing size according to the acquisition mode
* @ft FmcTdc instance
* @chan channe [0, 4] (used only for DMA acquisition mode)
* @size number of samples to trigger IRQ
*/
void ft_irq_coalescing_size_set(struct fmctdc_dev *ft,
unsigned int chan,
uint32_t size)
{
switch (ft->mode) {
case FT_ACQ_TYPE_FIFO:
if (unlikely(chan != -1)) {
dev_warn(&ft->pdev->dev,
"FIFO acquisition mode has a gobal coalesing size. Ignore channel %d, apply globally\n",
chan);
}
ft_writel(ft, size, TDC_REG_IRQ_THRESHOLD);
break;
case FT_ACQ_TYPE_DMA:
/* There is none */
break;
}
}
static int ft_init_channel(struct fmctdc_dev *ft, int channel)
{
return 0;
}
int ft_enable_termination(struct fmctdc_dev *ft, int channel, int enable)
{
struct ft_channel_state *st;
uint32_t ien;
if (channel < FT_CH_1 || channel > FT_NUM_CHANNELS)
return -EINVAL;
st = &ft->channels[channel - 1];
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
if (enable)
ien |= (1 << (channel - 1));
else
ien &= ~(1 << (channel - 1));
ft_writel(ft, ien, TDC_REG_INPUT_ENABLE);
if (enable)
set_bit(FT_FLAG_CH_TERMINATED, &st->flags);
else
clear_bit(FT_FLAG_CH_TERMINATED, &st->flags);
return 0;
}
static int ft_channels_init(struct fmctdc_dev *ft)
{
int i;
for (i = FT_CH_1; i <= FT_NUM_CHANNELS; i++) {
int ret = ft_init_channel(ft, i);
if (ret < 0)
return ret;
/* termination is off by default */
ft_enable_termination(ft, i, 0);
}
return 0;
}
static void ft_channels_exit(struct fmctdc_dev *ft)
{
}
struct ft_modlist {
char *name;
int (*init)(struct fmctdc_dev *ft);
void (*exit)(struct fmctdc_dev *ft);
};
static struct ft_modlist init_subsystems[] = {
{"acam-tdc", ft_acam_init, ft_acam_exit},
{"time", ft_time_init, ft_time_exit},
{"channels", ft_channels_init, ft_channels_exit},
{"zio", ft_zio_init, ft_zio_exit}
};
/**
* It configures the test data
* @chan channel number [0, 4]
* @period period in 125Mhz ticks (125000000 -1 = 1Hz)
* @enable enable or disable
*/
void ft_test_data(struct fmctdc_dev *ft,
unsigned int chan,
unsigned int period,
bool enable)
{
uint32_t tmp = 0;
ft_writel(ft, 0, TDC_REG_FAKE_TS_CSR);
if (!enable)
return;
if (chan >= ft->zdev->n_cset) {
dev_err(&ft->pdev->dev, "%s Invalid channel %d\n",
__func__, chan);
return;
}
if (period == 0) {
dev_err(&ft->pdev->dev, "%s Invalid period %d\n",
__func__, period);
return;
}
tmp |= TDC_FAKE_TS_EN;
tmp |= ((chan << TDC_FAKE_TS_CHAN_SHIFT) & TDC_FAKE_TS_CHAN_MASK);
tmp |= ((period << TDC_FAKE_TS_PERIOD_SHIFT) & TDC_FAKE_TS_PERIOD_MASK);
ft_writel(ft, tmp, TDC_REG_FAKE_TS_CSR);
dev_warn(&ft->pdev->dev,
"Channel 0 is running in test mode 0x%x\n",
tmp);
}
static int ft_resource_validation(struct platform_device *pdev)
{
struct resource *r;
r = platform_get_resource(pdev, IORESOURCE_IRQ, TDC_IRQ);
if (!r) {
dev_err(&pdev->dev,
"The TDC needs an interrupt number\n");
return -ENXIO;
}
r = platform_get_resource(pdev, IORESOURCE_MEM, TDC_MEM_BASE);
if (!r) {
dev_err(&pdev->dev,
"The TDC needs base address\n");
return -ENXIO;
}
return 0;
}
#define FT_FMC_NAME "FmcTdc1ns5cha"
static bool ft_fmc_slot_is_valid(struct fmctdc_dev *ft)
{
int ret;
void *fru = NULL;
char *fmc_name = NULL;
if (!fmc_slot_fru_valid(ft->slot)) {
dev_err(&ft->pdev->dev,
"Can't identify FMC card: invalid FRU\n");
return -EINVAL;
}
fru = kmalloc(FRU_SIZE_MAX, GFP_KERNEL);
if (!fru)
return -ENOMEM;
ret = fmc_slot_eeprom_read(ft->slot, fru, 0x0, FRU_SIZE_MAX);
if (ret != FRU_SIZE_MAX) {
dev_err(&ft->pdev->dev, "Failed to read FRU header\n");
goto err;
}
fmc_name = fru_get_product_name(fru);
ret = strcmp(fmc_name, FT_FMC_NAME);
if (ret) {
dev_err(&ft->pdev->dev,
"Invalid FMC card: expectd '%s', found '%s'\n",
FT_FMC_NAME, fmc_name);
goto err;
}
kfree(fmc_name);
kfree(fru);
return true;
err:
kfree(fmc_name);
kfree(fru);
return false;
}
static int ft_endianess(struct fmctdc_dev *ft)
{
const struct fmc_tdc_platform_data *fmc_tdc_pdata;
fmc_tdc_pdata = ft->pdev->dev.platform_data;
return !!(fmc_tdc_pdata->flags & FMC_TDC_BIG_ENDIAN);
}
static int ft_memops_detect(struct fmctdc_dev *ft)
{
int ret;
ret = ft_endianess(ft);
if (ret < 0) {
dev_err(&ft->pdev->dev, "Failed to detect endianess\n");
return -EINVAL;
}
if (ret) {
ft->memops.read = ioread32be;
ft->memops.write = iowrite32be;
} else {
ft->memops.read = ioread32;
ft->memops.write = iowrite32;
}
return 0;
}
/**
* probe and remove are called by the FMC bus core
*/
int ft_probe(struct platform_device *pdev)
{
struct ft_modlist *m;
struct fmctdc_dev *ft;
struct device *dev = &pdev->dev;
struct resource *r;
int i, ret, err;
uint32_t stat;
uint32_t slot_nr;
err = ft_resource_validation(pdev);
if (err)
return err;
ft = kzalloc(sizeof(struct fmctdc_dev), GFP_KERNEL);
if (!ft)
return -ENOMEM;
platform_set_drvdata(pdev, ft);
ft->pdev = pdev;
r = platform_get_resource(pdev, IORESOURCE_MEM, TDC_MEM_BASE);
ft->ft_base = ioremap(r->start, resource_size(r));
ft->ft_core_base = ft->ft_base + TDC_MEZZ_CORE_OFFSET;
ft->ft_irq_base = ft->ft_base + TDC_MEZZ_EIC_OFFSET;
ft->ft_owregs_base = ft->ft_base + TDC_MEZZ_ONEWIRE_OFFSET;
ft->ft_fifo_base = ft->ft_base + TDC_MEZZ_MEM_OFFSET;
ft->ft_dma_base = ft->ft_base + TDC_MEZZ_MEM_DMA_OFFSET;
ft->ft_dma_eic_base = ft->ft_base + TDC_MEZZ_MEM_DMA_EIC_OFFSET;
spin_lock_init(&ft->lock);
ret = ft_memops_detect(ft);
if (ret)
goto err_memops;
/*
* Even if the HDL supports both acquisition mechanism at the same
* time, here for the time being we don't.
*/
stat = ft_ioread(ft, ft->ft_core_base + TDC_REG_STAT);
if (stat & TDC_STAT_DMA) {
ft->mode = FT_ACQ_TYPE_DMA;
} else if (stat & TDC_STAT_FIFO) {
ft->mode = FT_ACQ_TYPE_FIFO;
} else {
dev_err(dev,
"Unsupported acquisition type, tdc_reg_stat 0x%x\n",
stat);
ret = -ENODEV;
goto err_mode_selection;
}
slot_nr = stat & TDC_STAT_FMC_SLOT ? 2 : 1;
ft->slot = fmc_slot_get(pdev->dev.parent->parent, slot_nr);
if (IS_ERR(ft->slot)) {
dev_err(&ft->pdev->dev,
"Can't find FMC slot %d err: %ld\n",
slot_nr, PTR_ERR(ft->slot));
goto out_fmc;
}
if (!fmc_slot_present(ft->slot)) {
dev_err(&ft->pdev->dev,
"Can't identify FMC card: missing card\n");
goto out_fmc_pre;
}
if (strcmp(fmc_slot_eeprom_type_get(ft->slot), FT_EEPROM_TYPE)) {
dev_warn(&ft->pdev->dev,
"use non standard EERPOM type \"%s\"\n",
FT_EEPROM_TYPE);
ret = fmc_slot_eeprom_type_set(ft->slot, FT_EEPROM_TYPE);
if (ret < 0) {
dev_err(&ft->pdev->dev,
"Failed to change EEPROM type to \"%s\"",
FT_EEPROM_TYPE);
goto out_fmc_eeprom;
}
}
if(!ft_fmc_slot_is_valid(ft))
goto out_fmc_err;
ret = ft_calib_init(ft);
if (ret < 0)
goto err_calib;
/* init all subsystems */
for (i = 0, m = init_subsystems; i < ARRAY_SIZE(init_subsystems);
i++, m++) {
ret = m->init(ft);
if (ret < 0)
goto err;
}
ft_test_data(ft, 0, test_data_period, !!test_data_period);
switch (ft->mode) {
case FT_ACQ_TYPE_DMA:
ret = ft_buf_init(ft);
break;
case FT_ACQ_TYPE_FIFO:
ret = ft_fifo_init(ft);
break;
}
if (ret < 0)
goto err;
ft_debug_init(ft);
ft_writel(ft, TDC_INPUT_ENABLE_FLAG, TDC_REG_INPUT_ENABLE);
ft_writel(ft, TDC_CTRL_EN_ACQ, TDC_REG_CTRL);
platform_set_drvdata(pdev, ft);
return 0;
err:
while (--m, --i >= 0)
if (m->exit)
m->exit(ft);
ft_calib_exit(ft);
err_calib:
out_fmc_err:
out_fmc_eeprom:
out_fmc_pre:
fmc_slot_put(ft->slot);
out_fmc:
err_mode_selection:
err_memops:
iounmap(ft->ft_base);
kfree(ft);
platform_set_drvdata(pdev, NULL);
return ret;
}
int ft_remove(struct platform_device *pdev)
{
struct fmctdc_dev *ft = platform_get_drvdata(pdev);
int i;
if (!ft)
return 0; /* No init, no exit */
ft_writel(ft, TDC_CTRL_DIS_ACQ, TDC_REG_CTRL);
ft_writel(ft, 0, TDC_REG_INPUT_ENABLE);
ft_debug_exit(ft);
switch (ft->mode) {
case FT_ACQ_TYPE_DMA:
ft_buf_exit(ft);
break;
case FT_ACQ_TYPE_FIFO:
ft_fifo_exit(ft);
break;
}
i = ARRAY_SIZE(init_subsystems);
while (--i >= 0) {
struct ft_modlist *m = init_subsystems + i;
if (m->exit)
m->exit(ft);
}
ft_calib_exit(ft);
fmc_slot_put(ft->slot);
iounmap(ft->ft_base);
kfree(ft);
return 0;
}
static const struct platform_device_id ft_id[] = {
{
.name = "fmc-tdc",
.driver_data = TDC_VER,
},
};
static struct platform_driver ft_platform_driver = {
.driver = {
.name = KBUILD_MODNAME,
},
.probe = ft_probe,
.remove = ft_remove,
.id_table = ft_id,
};
static int ft_init(void)
{
int ret;
ret = zio_register_trig(&ft_trig_type, FT_ZIO_TRIG_TYPE_NAME);
if (ret) {
pr_err("fmc-tdc: cannot register ZIO trigger type \"%s\" (error %i)\n",
FT_ZIO_TRIG_TYPE_NAME, ret);
goto err_zio_trg;
}
ret = ft_zio_register();
if (ret < 0)
goto err_zio;
ret = platform_driver_register(&ft_platform_driver);
if (ret < 0)
goto err_plat;
return 0;
err_plat:
ft_zio_unregister();
err_zio:
zio_unregister_trig(&ft_trig_type);
err_zio_trg:
return ret;
}
static void ft_exit(void)
{
platform_driver_unregister(&ft_platform_driver);
ft_zio_unregister();
zio_unregister_trig(&ft_trig_type);
}
module_init(ft_init);
module_exit(ft_exit);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL and additional rights"); /* LGPL */
MODULE_SOFTDEP("pre: zio fmc zio-buf-vmalloc");
ADDITIONAL_VERSIONS;
// 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)
{
}
/*
* ZIO interface for the fmc-tdc driver.
*
* Copyright (C) 2012-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/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/zio.h>
#include <linux/zio-buffer.h>
#include <linux/zio-trigger.h>
#include "fmc-tdc.h"
#include "hw/timestamp_fifo_regs.h"
#include "hw/tdc_onewire_regs.h"
/* The sample size. Mandatory, device-wide */
ZIO_ATTR_DEFINE_STD(ZIO_DEV, ft_zattr_dev_std) = {
ZIO_ATTR(zdev, ZIO_ATTR_NBITS, ZIO_RO_PERM, 0, 32), /* 32 bits. Really? */
ZIO_SET_ATTR_VERSION(ZIO_HEX_VERSION(FT_VERSION_MAJ, FT_VERSION_MIN, 0)),
};
/* Extended attributes for the device */
static struct zio_attribute ft_zattr_dev[] = {
ZIO_ATTR_EXT("seconds", ZIO_RW_PERM, FT_ATTR_DEV_SECONDS, 0),
ZIO_ATTR_EXT("coarse", ZIO_RW_PERM, FT_ATTR_DEV_COARSE, 0),
ZIO_ATTR_EXT("command", ZIO_WO_PERM, FT_ATTR_DEV_COMMAND, 0),
ZIO_ATTR_EXT("wr-offset", ZIO_RO_PERM, FT_ATTR_TDC_WR_OFFSET, 0),
ZIO_PARAM_EXT("temperature", ZIO_RO_PERM, FT_ATTR_PARAM_TEMP, 0),
ZIO_PARAM_EXT("transfer-mode", ZIO_RO_PERM,
FT_ATTR_TDC_TRANSFER_MODE, 0),
};
/* Extended attributes for the TDC (== input) cset */
static struct zio_attribute ft_zattr_input[] = {
ZIO_ATTR_EXT("termination", ZIO_RW_PERM, FT_ATTR_TDC_TERMINATION, 0),
ZIO_ATTR_EXT("zero-offset", ZIO_RO_PERM, FT_ATTR_TDC_ZERO_OFFSET, 0),
ZIO_ATTR_EXT("user-offset", ZIO_RW_PERM, FT_ATTR_TDC_USER_OFFSET, 0),
ZIO_ATTR_EXT("irq_coalescing_time", ZIO_RW_PERM,
FT_ATTR_TDC_COALESCING_TIME, 10),
ZIO_ATTR_EXT("raw_readout_mode", ZIO_RW_PERM,
FT_ATTR_TDC_RAW_READOUT_MODE, 0),
ZIO_ATTR_EXT("received", ZIO_RO_PERM, FT_ATTR_TDC_RECV, 0),
ZIO_ATTR_EXT("transferred", ZIO_RO_PERM, FT_ATTR_TDC_TRANS, 0),
};
/* This identifies if our "struct device" is device, input, output */
enum ft_devtype {
FT_TYPE_WHOLEDEV,
FT_TYPE_INPUT
};
/**
* It applies all calibration offsets to the hardware for a given channel.
*/
static void ft_update_offsets(struct fmctdc_dev *ft, int channel)
{
struct ft_channel_state *st = &ft->channels[channel];
struct ft_hw_timestamp hw_offset = {0, 0, 0, 0};
void *fifo_addr;
fifo_addr = ft->ft_fifo_base + TDC_FIFO_OFFSET * channel;
ft_ts_apply_offset(&hw_offset, ft->calib.zero_offset[channel]);
ft_ts_apply_offset(&hw_offset, -ft->calib.wr_offset);
if (st->user_offset)
ft_ts_apply_offset(&hw_offset, st->user_offset);
ft_iowrite(ft, hw_offset.seconds, fifo_addr + TSF_REG_OFFSET1 );
ft_iowrite(ft, hw_offset.coarse, fifo_addr + TSF_REG_OFFSET2 );
ft_iowrite(ft, hw_offset.frac, fifo_addr + TSF_REG_OFFSET3 );
}
static enum ft_devtype __ft_get_type(struct device *dev)
{
struct zio_obj_head *head = to_zio_head(dev);
if (head->zobj_type == ZIO_DEV)
return FT_TYPE_WHOLEDEV;
return FT_TYPE_INPUT;
}
static void ft_raw_mode_set(struct fmctdc_dev *ft,
unsigned int chan,
unsigned int raw_enable)
{
void *fifo_addr = ft->ft_fifo_base + TDC_FIFO_OFFSET * chan;
uint32_t csr = ft_ioread(ft, fifo_addr + TSF_REG_CSR);
if (raw_enable)
csr |= TSF_CSR_RAW_MODE;
else
csr &= ~TSF_CSR_RAW_MODE;
ft_iowrite(ft, csr, fifo_addr + TSF_REG_CSR);
}
static int ft_raw_mode_get(struct fmctdc_dev *ft,
unsigned int chan)
{
void *fifo_addr = ft->ft_fifo_base + TDC_FIFO_OFFSET * chan;
uint32_t csr = ft_ioread(ft, fifo_addr + TSF_REG_CSR);
return (csr & TSF_CSR_RAW_MODE) ? 1 : 0;
}
static int ft_temperature_get(struct fmctdc_dev *ft, int *temp)
{
int stat = ft_ioread(ft, ft->ft_owregs_base + TDC_OW_REG_CSR);
if (!(stat & TDC_OW_CSR_VALID))
return -EIO;
*temp = ft_ioread(ft, ft->ft_owregs_base + TDC_OW_REG_TEMP);
return 0;
}
static int ft_unique_id_get(struct fmctdc_dev *ft, uint64_t *id)
{
int stat = ft_ioread( ft, ft->ft_owregs_base + TDC_OW_REG_CSR);
uint32_t tmp_l, tmp_h;
if( !( stat & TDC_OW_CSR_VALID ) )
return -EIO;
tmp_l = ft_ioread(ft, ft->ft_owregs_base + TDC_OW_REG_ID_L);
tmp_h = ft_ioread(ft, ft->ft_owregs_base + TDC_OW_REG_ID_H);
*id = ((uint64_t)tmp_h << 32) | tmp_l;
return 0;
}
/* TDC input attributes: only the user offset is special */
static int ft_zio_info_channel(struct device *dev, struct zio_attribute *zattr,
uint32_t *usr_val)
{
struct zio_cset *cset;
struct fmctdc_dev *ft;
struct ft_channel_state *st;
cset = to_zio_cset(dev);
ft = cset->zdev->priv_d;
st = &ft->channels[cset->index];
switch (zattr->id) {
case FT_ATTR_TDC_USER_OFFSET:
*usr_val = st->user_offset;
break;
case FT_ATTR_TDC_ZERO_OFFSET:
*usr_val = ft->calib.zero_offset[cset->index];
break;
case FT_ATTR_TDC_TERMINATION:
*usr_val = test_bit(FT_FLAG_CH_TERMINATED, &st->flags);
break;
case FT_ATTR_TDC_COALESCING_TIME:
*usr_val = ft_irq_coalescing_timeout_get(ft, cset->index);
break;
case FT_ATTR_TDC_RAW_READOUT_MODE:
*usr_val = ft_raw_mode_get(ft, cset->index);
break;
case FT_ATTR_TDC_RECV:
*usr_val = st->stats.received;
break;
case FT_ATTR_TDC_TRANS:
*usr_val = st->stats.transferred;
break;
}
return 0;
}
/* Overall and device-wide attributes: only get_time is special */
static int ft_zio_info_get(struct device *dev, struct zio_attribute *zattr,
uint32_t *usr_val)
{
struct zio_device *zdev;
struct fmctdc_dev *ft;
struct zio_attribute *attr;
int ret;
if (__ft_get_type(dev) == FT_TYPE_INPUT)
return ft_zio_info_channel(dev, zattr, usr_val);
zdev = to_zio_dev(dev);
attr = zdev->zattr_set.ext_zattr;
ft = zdev->priv_d;
switch (zattr->id) {
case FT_ATTR_TDC_TRANSFER_MODE:
*usr_val = ft->mode;
break;
case FT_ATTR_PARAM_TEMP:
ret = ft_temperature_get(ft, &ft->temp);
if (ret < 0)
return ret;
*usr_val = ft->temp;
break;
case FT_ATTR_DEV_COARSE:
case FT_ATTR_DEV_SECONDS:
{
uint64_t seconds;
uint32_t coarse;
ft_get_tai_time(ft, &seconds, &coarse);
attr[FT_ATTR_DEV_COARSE].value = coarse;
attr[FT_ATTR_DEV_SECONDS].value = (uint32_t) seconds;
*usr_val =
(zattr->id ==
FT_ATTR_DEV_COARSE ? coarse : (uint32_t) seconds);
break;
}
case FT_ATTR_TDC_WR_OFFSET:
*usr_val = ft->calib.wr_offset;
break;
}
return 0;
}
static int ft_zio_conf_channel(struct device *dev, struct zio_attribute *zattr,
uint32_t usr_val)
{
struct zio_cset *cset;
struct fmctdc_dev *ft;
struct ft_channel_state *st;
int32_t user_offs;
cset = to_zio_cset(dev);
ft = cset->zdev->priv_d;
st = &ft->channels[cset->index];
switch (zattr->id) {
case FT_ATTR_TDC_TERMINATION:
ft_enable_termination(ft, cset->index + 1, usr_val);
break;
case FT_ATTR_TDC_USER_OFFSET:
user_offs = usr_val;
if (user_offs < -FT_USER_OFFSET_RANGE
|| user_offs > FT_USER_OFFSET_RANGE)
return -EINVAL;
spin_lock(&ft->lock);
st->user_offset = usr_val;
ft_update_offsets(ft, cset->index);
spin_unlock(&ft->lock);
break;
case FT_ATTR_TDC_COALESCING_TIME:
ft_irq_coalescing_timeout_set(ft, cset->index, usr_val);
break;
case FT_ATTR_TDC_RAW_READOUT_MODE:
ft_raw_mode_set(ft, cset->index, !!usr_val);
break;
default:
return -EINVAL;
}
return 0;
}
/*
* The input is asynchronous, but we know that from this point on
* our hardware will be busy in transfering data to the host.
* For this reason we need to flag the cset as BUSY
*/
static int ft_zio_input(struct zio_cset *cset)
{
return -EAGAIN;
}
/* conf_set dispatcher and and device-wide attributes */
static int ft_zio_conf_set(struct device *dev, struct zio_attribute *zattr,
uint32_t usr_val)
{
struct zio_device *zdev;
struct fmctdc_dev *ft;
struct zio_attribute *attr;
if (__ft_get_type(dev) == FT_TYPE_INPUT)
return ft_zio_conf_channel(dev, zattr, usr_val);
/* Remains: wholedev */
zdev = to_zio_dev(dev);
attr = zdev->zattr_set.ext_zattr;
ft = zdev->priv_d;
if (zattr->id == FT_ATTR_DEV_SECONDS) {
int ret = ft_wr_query(ft);
if (ret != -ENODEV)
return -EPERM;
attr[FT_ATTR_DEV_SECONDS].value = usr_val;
ft_set_tai_time(ft,
attr[FT_ATTR_DEV_SECONDS].value,
attr[FT_ATTR_DEV_COARSE].value);
return 0;
}
switch (zattr->id) {
case FT_ATTR_DEV_COARSE:
attr[FT_ATTR_DEV_COARSE].value = usr_val;
return 0;
case FT_ATTR_DEV_COMMAND:
switch (usr_val) {
case FT_CMD_SET_HOST_TIME:
ft_set_host_time(ft);
return 0;
case FT_CMD_WR_ENABLE:
return ft_wr_mode(ft, 1);
case FT_CMD_WR_DISABLE:
return ft_wr_mode(ft, 0);
case FT_CMD_WR_QUERY:
return ft_wr_query(ft);
default:
return -EINVAL;
}
default:
return -EPERM;
}
}
/*
* The probe function receives a new zio_device, which is different from
* what we allocated (that one is the "hardwre" device) but has the
* same private data. So we make the link and return success.
*/
static int ft_zio_probe(struct zio_device *zdev)
{
struct fmctdc_dev *ft;
int err;
/* link the new device from the fd structure */
ft = zdev->priv_d;
ft->zdev = zdev;
err = device_create_bin_file(&zdev->head.dev, &dev_attr_calibration);
if (err)
return err;
/* We don't have csets at this point, so don't do anything more */
return 0;
}
/*
* zfad_zio_remove
* @zdev: the real zio device
*/
static int ft_zio_remove(struct zio_device *zdev)
{
device_remove_bin_file(&zdev->head.dev, &dev_attr_calibration);
return 0;
}
/* Our sysfs operations to access internal settings */
static const struct zio_sysfs_operations ft_zio_sysfs_ops = {
.conf_set = ft_zio_conf_set,
.info_get = ft_zio_info_get,
};
/**
* It enables/disables interrupts according to the enable/disable
* status of the correspondent channel
*/
static void ft_change_flags(struct zio_obj_head *head, unsigned long mask)
{
struct zio_channel *chan;
struct ft_channel_state *st;
struct fmctdc_dev *ft;
uint32_t ien;
/* We manage only status flag */
if (!(mask & ZIO_STATUS))
return;
chan = to_zio_chan(&head->dev);
ft = chan->cset->zdev->priv_d;
st = &ft->channels[chan->cset->index];
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
if (chan->flags & ZIO_STATUS) {
uint32_t csr;
void *fifo_addr;
/* DISABLED */
ft_disable(ft, chan->cset->index);
fifo_addr = ft->ft_fifo_base + TDC_FIFO_OFFSET * chan->cset->index;
zio_trigger_abort_disable(chan->cset, 0);
csr = ft_ioread(ft, fifo_addr + TSF_REG_FIFO_CSR);
ft_iowrite(ft, csr | TSF_FIFO_CSR_CLEAR_BUS, fifo_addr + TSF_REG_FIFO_CSR);
} else {
/* ENABLED */
ft_enable(ft, chan->cset->index);
}
/*
* NOTE: above we have a little HACK. According to ZIO v1.1, ZIO invokes
* this function in a spin-lock context. The TDC assigns this function to
* the channel, so ZIO will take the channel lock. Then on arm() and
* abort() ZIO takes the cset flag. So this will not fail, but bear in
* mind that if you do this when it is assigned to a cset it wont work
*/
}
static struct zio_channel ft_chan_tmpl = {
.change_flags = ft_change_flags,
.flags = ZIO_DISABLED,
};
#define DECLARE_CHANNEL(ch_name) \
{\
ZIO_SET_OBJ_NAME(ch_name),\
.raw_io = ft_zio_input,\
.chan_template = &ft_chan_tmpl,\
.n_chan = 1,\
.ssize = sizeof(struct ft_hw_timestamp), \
.flags = ZIO_DISABLED | \
ZIO_DIR_INPUT,\
.zattr_set = {\
.ext_zattr = ft_zattr_input,\
.n_ext_attr = ARRAY_SIZE(ft_zattr_input),\
},\
}
/* We have 5 csets, since each output triggers separately */
static struct zio_cset ft_cset[] = {
DECLARE_CHANNEL("ft-ch1"),
DECLARE_CHANNEL("ft-ch2"),
DECLARE_CHANNEL("ft-ch3"),
DECLARE_CHANNEL("ft-ch4"),
DECLARE_CHANNEL("ft-ch5"),
};
static struct zio_device ft_tmpl = {
.owner = THIS_MODULE,
.preferred_trigger = FT_ZIO_TRIG_TYPE_NAME,
.preferred_buffer = "vmalloc",
.s_op = &ft_zio_sysfs_ops,
.cset = ft_cset,
.n_cset = ARRAY_SIZE(ft_cset),
.zattr_set = {
.std_zattr = ft_zattr_dev_std,
.ext_zattr = ft_zattr_dev,
.n_ext_attr = ARRAY_SIZE(ft_zattr_dev),
},
};
static const struct zio_device_id ft_table[] = {
{"tdc-1n5c", &ft_tmpl},
{},
};
static struct zio_driver ft_zdrv = {
.driver = {
.name = "tdc-1n5c",
.owner = THIS_MODULE,
},
.id_table = ft_table,
.probe = ft_zio_probe,
.remove = ft_zio_remove,
/* Take the version from ZIO git sub-module */
.min_version = ZIO_VERSION(__ZIO_MIN_MAJOR_VERSION,
__ZIO_MIN_MINOR_VERSION,
0), /* Change it if you use new features from
a specific patch */
};
#define FT_TRIG_POST_DEFAULT 1
enum ft_trig_options {
FT_TRIG_POST = 0,
};
/**
* It puts the given timestamp in the ZIO control
* @cset ZIO cset instant
* @hwts the timestamp to convert
*/
static void ft_zio_update_ctrl(struct zio_cset *cset,
struct ft_hw_timestamp *hwts)
{
struct fmctdc_dev *ft = cset->zdev->priv_d;
struct zio_control *ctrl;
uint32_t *v;
struct ft_channel_state *st;
st = &ft->channels[cset->index];
ctrl = cset->chan->current_ctrl;
v = ctrl->attr_channel.ext_val;
/* Write the timestamp in the trigger, it will reach the control */
cset->ti->tstamp.tv_sec = hwts->seconds;
cset->ti->tstamp.tv_nsec = hwts->coarse; /* we use 8ns steps */
cset->ti->tstamp_extra = hwts->frac;
/* Synchronize ZIO sequence number with ours (ZIO does +1 on this) */
ctrl->seq_num = FT_HW_TS_META_SEQ(hwts->metadata) - 1;
v[FT_ATTR_TDC_ZERO_OFFSET] = ft->calib.zero_offset[cset->index];
v[FT_ATTR_TDC_USER_OFFSET] = st->user_offset;
}
static ZIO_ATTR_DEFINE_STD(ZIO_TRG, ft_trig_std_zattr) = {
/* Number of shots */
ZIO_ATTR(trig, ZIO_ATTR_TRIG_POST_SAMP, ZIO_RW_PERM, FT_TRIG_POST,
FT_TRIG_POST_DEFAULT),
};
static int ft_trig_conf_set(struct device *dev, struct zio_attribute *zattr,
uint32_t usr_val)
{
return 0;
}
static int ft_trig_info_get(struct device *dev, struct zio_attribute *zattr,
uint32_t *usr_val)
{
return 0;
}
static const struct zio_sysfs_operations ft_trig_s_op = {
.conf_set = ft_trig_conf_set,
.info_get = ft_trig_info_get,
};
static struct zio_ti *ft_trig_create(struct zio_trigger_type *trig,
struct zio_cset *cset,
struct zio_control *ctrl, fmode_t flags)
{
struct fmctdc_trig *tti;
tti = kzalloc(sizeof(*tti), GFP_KERNEL);
if (!tti)
return ERR_PTR(-ENOMEM);
tti->ti.flags = ZIO_DISABLED;
tti->ti.cset = cset;
return &tti->ti;
}
static void ft_trig_destroy(struct zio_ti *ti)
{
struct fmctdc_trig *tti = to_fmctdc_trig(ti);
kfree(tti);
}
/**
* It completes an acquisition.
* @cset the ZIO channel set that completed the acquisition
*
* Rembember that here the cset->lock is taken and we can do
* what we want with the cset
*/
static int ft_trig_data_done(struct zio_cset *cset)
{
struct ft_hw_timestamp *ts;
int i, ret;
if (!cset->chan->active_block)
goto out;
ts = cset->chan->active_block->data;
for(i = 0; i < cset->ti->nsamples; ++i) {
dev_dbg(&cset->head.dev,
"%s TS = {ts-num: %d, ts-num-max: %d, sec: 0x%x, coarse: 0x%x frac: 0x%x, meta: 0x%x}\n",
__func__, i, cset->ti->nsamples,
ts[i].seconds,ts[i].coarse,
ts[i].frac, ts[i].metadata);
}
ft_zio_update_ctrl(cset, &ts[0]);
out:
ret = zio_generic_data_done(cset);
return ret;
}
static int ft_trig_push(struct zio_ti *ti, struct zio_channel *chan,
struct zio_block *block)
{
dev_err(&ti->head.dev, "output not supported\n");
return -EIO;
}
static const struct zio_trigger_operations ft_trig_ops = {
.create = ft_trig_create,
.destroy = ft_trig_destroy,
.change_status = NULL,
.data_done = ft_trig_data_done,
.arm = NULL,
.abort = NULL,
.push_block = ft_trig_push,
};
/* Definition of the trigger type -- can't be static */
struct zio_trigger_type ft_trig_type = {
.owner = THIS_MODULE,
.zattr_set = {
.std_zattr = ft_trig_std_zattr,
.ext_zattr = NULL,
.n_ext_attr = 0,
},
.s_op = &ft_trig_s_op,
.t_op = &ft_trig_ops,
};
/* Register and unregister are used to set up the template driver */
int ft_zio_register(void)
{
int err;
err = zio_register_driver(&ft_zdrv);
if (err)
return err;
return 0;
}
void ft_zio_unregister(void)
{
zio_unregister_driver(&ft_zdrv);
/* FIXME */
}
/* Init and exit are called for each FD card we have */
int ft_zio_init(struct fmctdc_dev *ft)
{
uint64_t id = 0;
int err = 0;
int dev_id;
int i;
ft->hwzdev = zio_allocate_device();
if (IS_ERR(ft->hwzdev))
return PTR_ERR(ft->hwzdev);
ft->hwzdev->head.dev.parent = &ft->pdev->dev;
/* Mandatory fields */
ft->hwzdev->owner = THIS_MODULE;
ft->hwzdev->priv_d = ft;
ft->hwzdev->head.dev.parent = &ft->pdev->dev;
dev_id = ft->pdev->id;
err = zio_register_device(ft->hwzdev, "tdc-1n5c", dev_id);
if (err)
goto err_dev_reg;
for(i = 0; i < FT_NUM_CHANNELS; i++) {
ft_raw_mode_set(ft, i, 0);
ft_update_offsets(ft, i);
}
ft_unique_id_get(ft, &id);
dev_dbg(&ft->pdev->dev, "TDC OW Serial Number 0x%016llx\n", id);
return 0;
err_dev_reg:
zio_free_device(ft->hwzdev);
return err;
}
void ft_zio_exit(struct fmctdc_dev *ft)
{
zio_unregister_device(ft->hwzdev);
zio_free_device(ft->hwzdev);
}
/*
* 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__ */
/*
Register definitions for slave core: TDC DMA Buffer Control Registers
* File : tdc_buffer_control_regs.h
* Author : auto-generated by wbgen2 from wbgen/tdc_buffer_control_regs.wb
* Created : Thu Jul 19 16:52:59 2018
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE wbgen/tdc_buffer_control_regs.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_TDC_BUFFER_CONTROL_REGS_WB
#define __WBGEN2_REGDEFS_TDC_BUFFER_CONTROL_REGS_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <inttypes.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Control/Status register */
/* definitions for field: Enable DMA in reg: Control/Status register */
#define TDC_BUF_CSR_ENABLE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: IRQ Timeout (ms) in reg: Control/Status register */
#define TDC_BUF_CSR_IRQ_TIMEOUT_MASK WBGEN2_GEN_MASK(1, 10)
#define TDC_BUF_CSR_IRQ_TIMEOUT_SHIFT 1
#define TDC_BUF_CSR_IRQ_TIMEOUT_W(value) WBGEN2_GEN_WRITE(value, 1, 10)
#define TDC_BUF_CSR_IRQ_TIMEOUT_R(reg) WBGEN2_GEN_READ(reg, 1, 10)
/* definitions for field: Burst size (timestamps) in reg: Control/Status register */
#define TDC_BUF_CSR_BURST_SIZE_MASK WBGEN2_GEN_MASK(11, 10)
#define TDC_BUF_CSR_BURST_SIZE_SHIFT 11
#define TDC_BUF_CSR_BURST_SIZE_W(value) WBGEN2_GEN_WRITE(value, 11, 10)
#define TDC_BUF_CSR_BURST_SIZE_R(reg) WBGEN2_GEN_READ(reg, 11, 10)
/* definitions for field: Switch buffers in reg: Control/Status register */
#define TDC_BUF_CSR_SWITCH_BUFFERS WBGEN2_GEN_MASK(21, 1)
/* definitions for field: DMA complete in reg: Control/Status register */
#define TDC_BUF_CSR_DONE WBGEN2_GEN_MASK(22, 1)
/* definitions for field: DMA overflow in reg: Control/Status register */
#define TDC_BUF_CSR_OVERFLOW WBGEN2_GEN_MASK(23, 1)
/* definitions for register: Current buffer base address register */
/* definitions for register: Current buffer base count register */
/* definitions for register: Current buffer base size/valid flag register */
/* definitions for field: Size (in transfers) in reg: Current buffer base size/valid flag register */
#define TDC_BUF_CUR_SIZE_SIZE_MASK WBGEN2_GEN_MASK(0, 30)
#define TDC_BUF_CUR_SIZE_SIZE_SHIFT 0
#define TDC_BUF_CUR_SIZE_SIZE_W(value) WBGEN2_GEN_WRITE(value, 0, 30)
#define TDC_BUF_CUR_SIZE_SIZE_R(reg) WBGEN2_GEN_READ(reg, 0, 30)
/* definitions for field: Valid flag in reg: Current buffer base size/valid flag register */
#define TDC_BUF_CUR_SIZE_VALID WBGEN2_GEN_MASK(30, 1)
/* definitions for register: Next buffer base address register */
/* definitions for register: Next buffer base size/valid flag register */
/* definitions for field: Size (in transfers) in reg: Next buffer base size/valid flag register */
#define TDC_BUF_NEXT_SIZE_SIZE_MASK WBGEN2_GEN_MASK(0, 30)
#define TDC_BUF_NEXT_SIZE_SIZE_SHIFT 0
#define TDC_BUF_NEXT_SIZE_SIZE_W(value) WBGEN2_GEN_WRITE(value, 0, 30)
#define TDC_BUF_NEXT_SIZE_SIZE_R(reg) WBGEN2_GEN_READ(reg, 0, 30)
/* definitions for field: Valid flag in reg: Next buffer base size/valid flag register */
#define TDC_BUF_NEXT_SIZE_VALID WBGEN2_GEN_MASK(30, 1)
/* [0x0]: REG Control/Status register */
#define TDC_BUF_REG_CSR 0x00000000
/* [0x4]: REG Current buffer base address register */
#define TDC_BUF_REG_CUR_BASE 0x00000004
/* [0x8]: REG Current buffer base count register */
#define TDC_BUF_REG_CUR_COUNT 0x00000008
/* [0xc]: REG Current buffer base size/valid flag register */
#define TDC_BUF_REG_CUR_SIZE 0x0000000c
/* [0x10]: REG Next buffer base address register */
#define TDC_BUF_REG_NEXT_BASE 0x00000010
/* [0x14]: REG Next buffer base size/valid flag register */
#define TDC_BUF_REG_NEXT_SIZE 0x00000014
#endif
/*
Register definitions for slave core: GN4124 DMA enhanced interrupt controller
* File : /afs/cern.ch/user/f/fvaga/workspace-afs/projects/fmc-tdc/sources/fmc-tdc-sw/kernel/hw/tdc_dma_eic.h
* Author : auto-generated by wbgen2 from dma_eic.wb
* Created : Fri Aug 10 12:07:55 2018
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE dma_eic.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_DMA_EIC_WB
#define __WBGEN2_REGDEFS_DMA_EIC_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <inttypes.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Interrupt disable register */
/* definitions for field: DMA done interrupt in reg: Interrupt disable register */
#define DMA_EIC_EIC_IDR_DMA_DONE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: DMA error interrupt in reg: Interrupt disable register */
#define DMA_EIC_EIC_IDR_DMA_ERROR WBGEN2_GEN_MASK(1, 1)
/* definitions for register: Interrupt enable register */
/* definitions for field: DMA done interrupt in reg: Interrupt enable register */
#define DMA_EIC_EIC_IER_DMA_DONE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: DMA error interrupt in reg: Interrupt enable register */
#define DMA_EIC_EIC_IER_DMA_ERROR WBGEN2_GEN_MASK(1, 1)
/* definitions for register: Interrupt mask register */
/* definitions for field: DMA done interrupt in reg: Interrupt mask register */
#define DMA_EIC_EIC_IMR_DMA_DONE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: DMA error interrupt in reg: Interrupt mask register */
#define DMA_EIC_EIC_IMR_DMA_ERROR WBGEN2_GEN_MASK(1, 1)
/* definitions for register: Interrupt status register */
/* definitions for field: DMA done interrupt in reg: Interrupt status register */
#define DMA_EIC_EIC_ISR_DMA_DONE WBGEN2_GEN_MASK(0, 1)
/* definitions for field: DMA error interrupt in reg: Interrupt status register */
#define DMA_EIC_EIC_ISR_DMA_ERROR WBGEN2_GEN_MASK(1, 1)
/* [0x20]: REG Interrupt disable register */
#define DMA_EIC_REG_EIC_IDR 0x00000000
/* [0x24]: REG Interrupt enable register */
#define DMA_EIC_REG_EIC_IER 0x00000004
/* [0x28]: REG Interrupt mask register */
#define DMA_EIC_REG_EIC_IMR 0x00000008
/* [0x2c]: REG Interrupt status register */
#define DMA_EIC_REG_EIC_ISR 0x0000000c
#endif
/*
Register definitions for slave core: TDC EIC
* File : /afs/cern.ch/user/f/fvaga/workspace-afs/projects/fmc-tdc/sources/fmc-tdc-sw/kernel/hw/tdc_eic.h
* Author : auto-generated by wbgen2 from tdc_eic.wb
* Created : Tue Aug 7 08:55:50 2018
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE tdc_eic.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_TDC_EIC_WB
#define __WBGEN2_REGDEFS_TDC_EIC_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <inttypes.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Interrupt disable register */
/* definitions for field: FMC TDC timestamps interrupt (FIFO1) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_FIFO1 WBGEN2_GEN_MASK(0, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO2) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_FIFO2 WBGEN2_GEN_MASK(1, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO3) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_FIFO3 WBGEN2_GEN_MASK(2, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO4) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_FIFO4 WBGEN2_GEN_MASK(3, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO5) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_FIFO5 WBGEN2_GEN_MASK(4, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA1) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_DMA1 WBGEN2_GEN_MASK(5, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA2) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_DMA2 WBGEN2_GEN_MASK(6, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA3) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_DMA3 WBGEN2_GEN_MASK(7, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA4) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_DMA4 WBGEN2_GEN_MASK(8, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA5) in reg: Interrupt disable register */
#define TDC_EIC_EIC_IDR_TDC_DMA5 WBGEN2_GEN_MASK(9, 1)
/* definitions for register: Interrupt enable register */
/* definitions for field: FMC TDC timestamps interrupt (FIFO1) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_FIFO1 WBGEN2_GEN_MASK(0, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO2) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_FIFO2 WBGEN2_GEN_MASK(1, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO3) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_FIFO3 WBGEN2_GEN_MASK(2, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO4) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_FIFO4 WBGEN2_GEN_MASK(3, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO5) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_FIFO5 WBGEN2_GEN_MASK(4, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA1) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_DMA1 WBGEN2_GEN_MASK(5, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA2) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_DMA2 WBGEN2_GEN_MASK(6, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA3) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_DMA3 WBGEN2_GEN_MASK(7, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA4) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_DMA4 WBGEN2_GEN_MASK(8, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA5) in reg: Interrupt enable register */
#define TDC_EIC_EIC_IER_TDC_DMA5 WBGEN2_GEN_MASK(9, 1)
/* definitions for register: Interrupt mask register */
/* definitions for field: FMC TDC timestamps interrupt (FIFO1) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_FIFO1 WBGEN2_GEN_MASK(0, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO2) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_FIFO2 WBGEN2_GEN_MASK(1, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO3) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_FIFO3 WBGEN2_GEN_MASK(2, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO4) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_FIFO4 WBGEN2_GEN_MASK(3, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO5) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_FIFO5 WBGEN2_GEN_MASK(4, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA1) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_DMA1 WBGEN2_GEN_MASK(5, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA2) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_DMA2 WBGEN2_GEN_MASK(6, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA3) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_DMA3 WBGEN2_GEN_MASK(7, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA4) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_DMA4 WBGEN2_GEN_MASK(8, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA5) in reg: Interrupt mask register */
#define TDC_EIC_EIC_IMR_TDC_DMA5 WBGEN2_GEN_MASK(9, 1)
/* definitions for register: Interrupt status register */
/* definitions for field: FMC TDC timestamps interrupt (FIFO1) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_FIFO1 WBGEN2_GEN_MASK(0, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO2) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_FIFO2 WBGEN2_GEN_MASK(1, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO3) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_FIFO3 WBGEN2_GEN_MASK(2, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO4) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_FIFO4 WBGEN2_GEN_MASK(3, 1)
/* definitions for field: FMC TDC timestamps interrupt (FIFO5) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_FIFO5 WBGEN2_GEN_MASK(4, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA1) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_DMA1 WBGEN2_GEN_MASK(5, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA2) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_DMA2 WBGEN2_GEN_MASK(6, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA3) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_DMA3 WBGEN2_GEN_MASK(7, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA4) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_DMA4 WBGEN2_GEN_MASK(8, 1)
/* definitions for field: FMC TDC timestamps interrupt (DMA5) in reg: Interrupt status register */
#define TDC_EIC_EIC_ISR_TDC_DMA5 WBGEN2_GEN_MASK(9, 1)
/* [0x20]: REG Interrupt disable register */
#define TDC_EIC_REG_EIC_IDR 0x00000000
/* [0x24]: REG Interrupt enable register */
#define TDC_EIC_REG_EIC_IER 0x00000004
/* [0x28]: REG Interrupt mask register */
#define TDC_EIC_REG_EIC_IMR 0x00000008
/* [0x2c]: REG Interrupt status register */
#define TDC_EIC_REG_EIC_ISR 0x0000000c
#endif
/*
Register definitions for slave core: TDC Onewire Master
* File : tdc_onewire_regs.h
* Author : auto-generated by wbgen2 from wbgen/tdc_onewire_wb.wb
* Created : Tue Sep 11 11:16:49 2018
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE wbgen/tdc_onewire_wb.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_TDC_ONEWIRE_WB_WB
#define __WBGEN2_REGDEFS_TDC_ONEWIRE_WB_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <inttypes.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Status Register */
/* definitions for field: Temperature & ID valid in reg: Status Register */
#define TDC_OW_CSR_VALID WBGEN2_GEN_MASK(0, 1)
/* definitions for register: Board Temperature */
/* definitions for register: Board Unique ID (MSW) */
/* definitions for register: Board Unique ID (LSW) */
/* [0x0]: REG Status Register */
#define TDC_OW_REG_CSR 0x00000000
/* [0x4]: REG Board Temperature */
#define TDC_OW_REG_TEMP 0x00000004
/* [0x8]: REG Board Unique ID (MSW) */
#define TDC_OW_REG_ID_H 0x00000008
/* [0xc]: REG Board Unique ID (LSW) */
#define TDC_OW_REG_ID_L 0x0000000c
#endif
/*
* tdc_registers.h
*
* Copyright (c) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
*
* 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 __TDC_REGISTERS_H
#define __TDC_REGISTERS_H
#include <hw/gennum-dma.h>
#include <hw/tdc_buffer_control_regs.h>
/* Gennum chip register */
#define TDC_REG_ACAM_READBACK(index) (0x0040 + (index * 4))
#define TDC_REG_ACAM_CONFIG(index) (0x0000 + (index * 4))
/* TDC core registers */
#define TDC_REG_START_UTC 0x0080
#define TDC_REG_INPUT_ENABLE 0x0084
#define TDC_REG_IRQ_THRESHOLD 0x0090
#define TDC_REG_IRQ_TIMEOUT 0x0094
#define TDC_REG_DAC_TUNE 0x0098
#define TDC_REG_CURRENT_UTC 0x00a0
#define TDC_REG_BUFFER_PTR 0x00a8
#define TDC_REG_STAT 0x00ac
#define TDC_REG_CTRL 0x00fc
#define TDC_REG_WR_CTRL 0x00b4
#define TDC_REG_WR_STAT 0x00b0
#define TDC_REG_FAKE_TS_CSR 0x00b8
/* TDC_REG_FAKE_TS_CSR bits */
#define TDC_FAKE_TS_EN BIT(31)
#define TDC_FAKE_TS_CHAN_MASK 0xE0000000
#define TDC_FAKE_TS_CHAN_SHIFT 28
#define TDC_FAKE_TS_PERIOD_MASK 0x0FFFFFFF
#define TDC_FAKE_TS_PERIOD_SHIFT 0
/* TDC_REG_STAT bits */
#define TDC_STAT_DMA BIT(0)
#define TDC_STAT_FIFO BIT(1)
#define TDC_STAT_FMC_SLOT BIT(2)
#define TDC_FIFO_OFFSET 0x100
#define TDC_WR_CTRL_ENABLE BIT(0)
#define TDC_WR_STAT_ENABLED BIT(6)
#define TDC_WR_STAT_LINK BIT(2)
#define TDC_WR_STAT_TIME_VALID BIT(8)
#define TDC_WR_STAT_AUX_LOCKED BIT(4)
/* TDC_REG_CTRL bits */
#define TDC_CTRL_EN_ACQ BIT(0)
#define TDC_CTRL_DIS_ACQ BIT(1)
#define TDC_CTRL_LOAD_ACAM_CFG BIT(2)
#define TDC_CTRL_READ_ACAM_CFG BIT(3)
#define TDC_CTRL_READ_ACAM_STAT BIT(4)
#define TDC_CTRL_READ_ACAM_IFIFO1 BIT(5)
#define TDC_CTRL_READ_ACAM_IFIFO2 BIT(6)
#define TDC_CTRL_READ_ACAM_START01_R BIT(7)
#define TDC_CTRL_RESET_ACAM BIT(8)
#define TDC_CTRL_LOAD_UTC BIT(9)
#define TDC_CTRL_CLEAR_DACAPO_FLAG BIT(10)
#define TDC_CTRL_CONFIG_DAC BIT(11)
/* TDC_REG_INPUT_ENABLE bits */
#define TDC_INPUT_ENABLE_CH1_SHIFT 16
#define TDC_INPUT_ENABLE_CH2_SHIFT 17
#define TDC_INPUT_ENABLE_CH3_SHIFT 18
#define TDC_INPUT_ENABLE_CH4_SHIFT 19
#define TDC_INPUT_ENABLE_CH5_SHIFT 20
#define TDC_INPUT_ENABLE_FLAG BIT(7)
#define TDC_INPUT_ENABLE_CH1 BIT(TDC_INPUT_ENABLE_CH1_SHIFT)
#define TDC_INPUT_ENABLE_CH2 BIT(TDC_INPUT_ENABLE_CH2_SHIFT)
#define TDC_INPUT_ENABLE_CH3 BIT(TDC_INPUT_ENABLE_CH3_SHIFT)
#define TDC_INPUT_ENABLE_CH4 BIT(TDC_INPUT_ENABLE_CH4_SHIFT)
#define TDC_INPUT_ENABLE_CH5 BIT(TDC_INPUT_ENABLE_CH5_SHIFT)
#define TDC_INPUT_ENABLE_CH_ALL (TDC_INPUT_ENABLE_CH1 | \
TDC_INPUT_ENABLE_CH2 | \
TDC_INPUT_ENABLE_CH3 | \
TDC_INPUT_ENABLE_CH4 | \
TDC_INPUT_ENABLE_CH5)
/* IRQ status/enable bits */
#define TDC_IRQ_TDC_TSTAMP BIT(0)
#define TDC_IRQ_TDC_TIME BIT(1)
#define TDC_EVENT_BUFFER_SIZE 256
#define TDC_EVENT_CHANNEL_MASK 0xF
#define TDC_EVENT_SLOPE_MASK 0xF0
#define TDC_EVENT_FIFO_LF_MASK 0xF00
#define TDC_EVENT_FIFO_EF_MASK 0xF000
#define TDC_EVENT_DACAPO_FLAG BIT(0)
/* Carrier CSRs */
#define TDC_REG_CARRIER_CTL0 0x0 /* a.k.a. Carrier revision/PCB id reg */
#define TDC_REG_CARRIER_STATUS 0x4
#define TDC_REG_CARRIER_CTL1 0x8
#define TDC_REG_CARRIER_RST 0xc
#define TDC_CARRIER_CTL0_PLL_STAT_FMC0 BIT(5)
#define TDC_CARRIER_CTL0_PLL_STAT_FMC1 BIT(6)
#define TDC_CARRIER_CTL1_RSTN_FMC0 BIT(3)
#define TDC_CARRIER_CTL1_RSTN_FMC1 BIT(4)
#define TDC_SVEC_CARRIER_BASE 0x1000
#define TDC_SPEC_CARRIER_BASE 0x20000
#define TDC_SPEC_DMA_BASE 0x50000
/* TDC core submodule offsets (wrs to the TDC control registers block) */
#define TDC_MEZZ_ONEWIRE_OFFSET 0x1000
#define TDC_MEZZ_CORE_OFFSET 0x2000
#define TDC_MEZZ_EIC_OFFSET 0x3000
#define TDC_MEZZ_I2C_OFFSET 0x4000
#define TDC_MEZZ_MEM_OFFSET 0x5000
#define TDC_MEZZ_MEM_DMA_OFFSET 0x6000
#define TDC_MEZZ_MEM_DMA_EIC_OFFSET 0x7000
#endif /* __TDC_REGISTERS_H */
/*
Register definitions for slave core: Timestamp FIFO
* File : timestamp_fifo_regs.h
* Author : auto-generated by wbgen2 from wbgen/timestamp_fifo_wb.wb
* Created : Sun Sep 2 15:37:55 2018
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE wbgen/timestamp_fifo_wb.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_TIMESTAMP_FIFO_WB_WB
#define __WBGEN2_REGDEFS_TIMESTAMP_FIFO_WB_WB
#ifdef __KERNEL__
#include <linux/types.h>
#else
#include <inttypes.h>
#endif
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Delta Timestamp Word 1 */
/* definitions for register: Delta Timestamp Word 2 */
/* definitions for register: Delta Timestamp Word 3 */
/* definitions for register: Channel Offset Word 1 */
/* definitions for register: Channel Offset Word 2 */
/* definitions for register: Channel Offset Word 3 */
/* definitions for register: Control/Status */
/* definitions for field: Delta Timestamp Ready in reg: Control/Status */
#define TSF_CSR_DELTA_READY WBGEN2_GEN_MASK(0, 1)
/* definitions for field: Read Delta Timestamp in reg: Control/Status */
#define TSF_CSR_DELTA_READ WBGEN2_GEN_MASK(1, 1)
/* definitions for field: Reset Sequence Counter in reg: Control/Status */
#define TSF_CSR_RST_SEQ WBGEN2_GEN_MASK(2, 1)
/* definitions for field: Delta Timestamp Reference Channel in reg: Control/Status */
#define TSF_CSR_DELTA_REF_MASK WBGEN2_GEN_MASK(3, 3)
#define TSF_CSR_DELTA_REF_SHIFT 3
#define TSF_CSR_DELTA_REF_W(value) WBGEN2_GEN_WRITE(value, 3, 3)
#define TSF_CSR_DELTA_REF_R(reg) WBGEN2_GEN_READ(reg, 3, 3)
/* definitions for field: Raw readout mode in reg: Control/Status */
#define TSF_CSR_RAW_MODE WBGEN2_GEN_MASK(6, 1)
/* definitions for register: FIFO 'Timestamp FIFO' data output register 0 */
/* definitions for field: The timestamp (word 0) in reg: FIFO 'Timestamp FIFO' data output register 0 */
#define TSF_FIFO_R0_TS0_MASK WBGEN2_GEN_MASK(0, 32)
#define TSF_FIFO_R0_TS0_SHIFT 0
#define TSF_FIFO_R0_TS0_W(value) WBGEN2_GEN_WRITE(value, 0, 32)
#define TSF_FIFO_R0_TS0_R(reg) WBGEN2_GEN_READ(reg, 0, 32)
/* definitions for register: FIFO 'Timestamp FIFO' data output register 1 */
/* definitions for field: The timestamp (word 1) in reg: FIFO 'Timestamp FIFO' data output register 1 */
#define TSF_FIFO_R1_TS1_MASK WBGEN2_GEN_MASK(0, 32)
#define TSF_FIFO_R1_TS1_SHIFT 0
#define TSF_FIFO_R1_TS1_W(value) WBGEN2_GEN_WRITE(value, 0, 32)
#define TSF_FIFO_R1_TS1_R(reg) WBGEN2_GEN_READ(reg, 0, 32)
/* definitions for register: FIFO 'Timestamp FIFO' data output register 2 */
/* definitions for field: The timestamp (word 2) in reg: FIFO 'Timestamp FIFO' data output register 2 */
#define TSF_FIFO_R2_TS2_MASK WBGEN2_GEN_MASK(0, 32)
#define TSF_FIFO_R2_TS2_SHIFT 0
#define TSF_FIFO_R2_TS2_W(value) WBGEN2_GEN_WRITE(value, 0, 32)
#define TSF_FIFO_R2_TS2_R(reg) WBGEN2_GEN_READ(reg, 0, 32)
/* definitions for register: FIFO 'Timestamp FIFO' data output register 3 */
/* definitions for field: The timestamp (word 3) in reg: FIFO 'Timestamp FIFO' data output register 3 */
#define TSF_FIFO_R3_TS3_MASK WBGEN2_GEN_MASK(0, 32)
#define TSF_FIFO_R3_TS3_SHIFT 0
#define TSF_FIFO_R3_TS3_W(value) WBGEN2_GEN_WRITE(value, 0, 32)
#define TSF_FIFO_R3_TS3_R(reg) WBGEN2_GEN_READ(reg, 0, 32)
/* definitions for register: FIFO 'Timestamp FIFO' control/status register */
/* definitions for field: FIFO full flag in reg: FIFO 'Timestamp FIFO' control/status register */
#define TSF_FIFO_CSR_FULL WBGEN2_GEN_MASK(16, 1)
/* definitions for field: FIFO empty flag in reg: FIFO 'Timestamp FIFO' control/status register */
#define TSF_FIFO_CSR_EMPTY WBGEN2_GEN_MASK(17, 1)
/* definitions for field: FIFO clear in reg: FIFO 'Timestamp FIFO' control/status register */
#define TSF_FIFO_CSR_CLEAR_BUS WBGEN2_GEN_MASK(18, 1)
/* definitions for field: FIFO counter in reg: FIFO 'Timestamp FIFO' control/status register */
#define TSF_FIFO_CSR_USEDW_MASK WBGEN2_GEN_MASK(0, 6)
#define TSF_FIFO_CSR_USEDW_SHIFT 0
#define TSF_FIFO_CSR_USEDW_W(value) WBGEN2_GEN_WRITE(value, 0, 6)
#define TSF_FIFO_CSR_USEDW_R(reg) WBGEN2_GEN_READ(reg, 0, 6)
/* [0x0]: REG Delta Timestamp Word 1 */
#define TSF_REG_DELTA1 0x00000000
/* [0x4]: REG Delta Timestamp Word 2 */
#define TSF_REG_DELTA2 0x00000004
/* [0x8]: REG Delta Timestamp Word 3 */
#define TSF_REG_DELTA3 0x00000008
/* [0xc]: REG Channel Offset Word 1 */
#define TSF_REG_OFFSET1 0x0000000c
/* [0x10]: REG Channel Offset Word 2 */
#define TSF_REG_OFFSET2 0x00000010
/* [0x14]: REG Channel Offset Word 3 */
#define TSF_REG_OFFSET3 0x00000014
/* [0x18]: REG Control/Status */
#define TSF_REG_CSR 0x00000018
/* [0x1c]: REG FIFO 'Timestamp FIFO' data output register 0 */
#define TSF_REG_FIFO_R0 0x0000001c
/* [0x20]: REG FIFO 'Timestamp FIFO' data output register 1 */
#define TSF_REG_FIFO_R1 0x00000020
/* [0x24]: REG FIFO 'Timestamp FIFO' data output register 2 */
#define TSF_REG_FIFO_R2 0x00000024
/* [0x28]: REG FIFO 'Timestamp FIFO' data output register 3 */
#define TSF_REG_FIFO_R3 0x00000028
/* [0x2c]: REG FIFO 'Timestamp FIFO' control/status register */
#define TSF_REG_FIFO_CSR 0x0000002c
#endif
/*
* Copyright (C) 2020 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __FMC_TDC_PDATA_H__
#define __FMC_TDC_PDATA_H__
#define FMC_TDC_BIG_ENDIAN BIT(0)
struct fmc_tdc_platform_data {
unsigned long flags;
};
#endif
libfmctdc.a
libfmctdc.so*
.depend
Makefile.specific
\ No newline at end of file
# This is not a kbuild Makefile. It is a plain Makefile so it can be copied
# If it exists includes Makefile.specific. In this Makefile, you should put
# specific Makefile code that you want to run before this. For example,
# build a particular environment.
-include Makefile.specific
# include parent_common.mk for buildsystem's defines
IGNORE_CPU_SUFFIX := y
REPO_PARENT ?=
-include $(REPO_PARENT)/parent_common.mk
ZIO ?= ../zio
ZIO_ABS ?= $(abspath $(ZIO) )
VERSION := $(shell git describe --tags --abbrev=0 | tr -d 'v')
SO_VERSION_XYZ := $(shell echo $(VERSION) | grep -o -E "[0-9]+\.[0-9]+\.[0-9]")
SO_VERSION_X := $(shell echo $(SO_VERSION_XYZ) | cut -d "." -f 1)
LIB = libfmctdc.a
LIBS = libfmctdc.so
LIBS_XYZ = $(LIBS).$(SO_VERSION_XYZ)
LOBJ := fmctdc-lib.o
LOBJ += fmctdc-lib-math.o
GIT_VERSION := $(shell git describe --dirty --long --tags)
ZIO_GIT_VERSION := $(shell cd $(ZIO_ABS); git describe --dirty --long --tags)
CFLAGS = -Wall -Werror -ggdb -O2 -I../kernel -I$(ZIO_ABS)/include
CFLAGS += -fPIC
CFLAGS += -DGIT_VERSION="\"$(GIT_VERSION)\""
CFLAGS += -DZIO_GIT_VERSION="\"$(ZIO_GIT_VERSION)\""
CFLAGS += $(EXTRACFLAGS)
DESTDIR ?= /usr/local
CPPCHECK ?= cppcheck
modules all: lib
lib: $(LIB) $(LIBS_XYZ)
%: %.c $(LIB)
$(CC) $(CFLAGS) $*.c $(LDFLAGS) -o $@
$(LIB): $(LOBJ)
$(AR) r $@ $^
$(LIBS_XYZ): $(LIB)
$(CC) -shared -o $@ -Wl,--whole-archive,-soname,$@ $^ -Wl,--no-whole-archive
clean:
rm -f $(LIB) $(LIBS_XYZ) .depend *.o *~
.depend: Makefile $(wildcard *.c *.h ../*.h)
$(CC) $(CFLAGS) -M $(LOBJ:.o=.c) -o $@
install:
install -d $(DESTDIR)/lib
install -d $(DESTDIR)/include/fmc-tdc
install -m 644 -D $(LIB) $(DESTDIR)/lib
install -m 0755 $(LIBS_XYZ) $(DESTDIR)/lib
install -m 644 -D fmctdc-lib.h $(DESTDIR)/include/fmc-tdc
ln -sf $(LIBS_XYZ) $(DESTDIR)/lib/$(LIBS).$(SO_VERSION_X)
ln -sf $(LIBS).$(SO_VERSION_X) $(DESTDIR)/lib/$(LIBS)
modules_install:
cppcheck:
$(CPPCHECK) -q -I. -I../kernel -I $(ZIO_ABS)/include --suppress=missingIncludeSystem --enable=warning,style,information,missingInclude *.c *.h
-include .depend
.PHONY=cppcheck
# SPDX-License-Identifier: CC0-1.0
#
# SPDX-FileCopyrightText: 2020 CERN
*.pyc
MANIFEST
# SPDX-License-Identifier: CC0-1.0
#
# SPDX-FileCopyrightText: 2020 CERN
-include Makefile.specific
all:
clean:
install:
python setup.py install
.PHONY: all clean install
"""
@package docstring
@author: Federico Vaga <federico.vaga@cern.ch>
SPDX-License-Identifier: LGPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN (home.cern)
"""
import threading
import ctypes
import errno
import time
import os
class FmcTdcTime(ctypes.Structure):
_fields_ = [
("seconds", ctypes.c_uint64),
("coarse", ctypes.c_uint32),
("frac", ctypes.c_uint32),
("seq_id", ctypes.c_uint32),
("debug", ctypes.c_uint32),
]
def __str__(self):
return "seq: {:d} timestamp: {:f} raw: {:08x}:{:08x}:{:08x}, debug: {:08x}".format(self.seq_id, float(self), self.seconds, self.coarse, self.frac, self.debug)
def __float__(self):
ts = self.seconds
ts = ts + (self.coarse / 1000000000.0 * 8)
ts = ts + ((self.frac * 7.999) / 4095) / 1000000000
return ts
def libfmctdc_create():
"""
Initialize the libfmctdc C library
:return: a valid object to access the libfmctdc library
"""
def error_check_int(ret, func, args):
"""Generic error checker for functions returning 0 as success
and -1 as error"""
if ret < 0:
raise OSError(ctypes.get_errno(),
fmctdc_strerror(ctypes.get_errno()), "")
else:
return ret
def error_check_pointer(ret, func, args):
"""Generic error handler for functions returning pointers"""
if ret is None:
raise OSError(ctypes.get_errno(),
fmctdc_strerror(ctypes.get_errno()), "")
else:
return ret
libfmctdc = ctypes.CDLL("libfmctdc.so", use_errno=True)
libfmctdc.fmctdc_init.argtypes = []
libfmctdc.fmctdc_init.restype = ctypes.c_int
libfmctdc.fmctdc_init.errcheck = error_check_int
libfmctdc.fmctdc_strerror.argtypes = [ctypes.c_int]
libfmctdc.fmctdc_strerror.restype = ctypes.c_char_p
libfmctdc.fmctdc_open.argtypes = [ctypes.c_int]
libfmctdc.fmctdc_open.restype = ctypes.c_void_p
libfmctdc.fmctdc_open.errcheck = error_check_pointer
libfmctdc.fmctdc_close.argtypes = [ctypes.c_void_p]
libfmctdc.fmctdc_close.restype = ctypes.c_int
libfmctdc.fmctdc_close.errcheck = error_check_int
# Device
libfmctdc.fmctdc_read_temperature.argtypes = [ctypes.c_void_p]
libfmctdc.fmctdc_read_temperature.restype = ctypes.c_float
libfmctdc.fmctdc_set_termination.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_int]
libfmctdc.fmctdc_transfer_mode.argtypes = [ctypes.c_void_p,
ctypes.POINTER(ctypes.c_int)]
libfmctdc.fmctdc_transfer_mode.restype = ctypes.c_int
libfmctdc.fmctdc_transfer_mode.errcheck = error_check_int
libfmctdc.fmctdc_set_buffer_type.argtypes = [ctypes.c_void_p,
ctypes.c_int]
libfmctdc.fmctdc_set_buffer_type.restype = ctypes.c_int
libfmctdc.fmctdc_set_buffer_type.errcheck = error_check_int
libfmctdc.fmctdc_get_buffer_type.argtypes = [ctypes.c_void_p]
libfmctdc.fmctdc_get_buffer_type.restype = ctypes.c_int
libfmctdc.fmctdc_get_buffer_type.errcheck = error_check_int
libfmctdc.fmctdc_set_time.argtypes = [ctypes.c_void_p,
ctypes.POINTER(FmcTdcTime)]
libfmctdc.fmctdc_set_time.restype = ctypes.c_int
libfmctdc.fmctdc_set_time.errcheck = error_check_int
libfmctdc.fmctdc_get_time.argtypes = [ctypes.c_void_p,
ctypes.POINTER(FmcTdcTime)]
libfmctdc.fmctdc_get_time.restype = ctypes.c_int
libfmctdc.fmctdc_get_time.errcheck = error_check_int
libfmctdc.fmctdc_wr_mode.argtypes =[ctypes.c_void_p,
ctypes.c_int]
libfmctdc.fmctdc_wr_mode.restype = ctypes.c_int
libfmctdc.fmctdc_wr_mode.errcheck = error_check_int
libfmctdc.fmctdc_check_wr_mode.argtypes =[ctypes.c_void_p]
libfmctdc.fmctdc_check_wr_mode.restype = ctypes.c_int
# Channel
libfmctdc.fmctdc_set_termination.restype = ctypes.c_int
libfmctdc.fmctdc_set_termination.errcheck = error_check_int
libfmctdc.fmctdc_get_termination.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_get_termination.restype = ctypes.c_int
libfmctdc.fmctdc_get_termination.errcheck = error_check_int
libfmctdc.fmctdc_channel_status_set.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_int]
libfmctdc.fmctdc_channel_status_set.restype = ctypes.c_int
libfmctdc.fmctdc_channel_status_set.errcheck = error_check_int
libfmctdc.fmctdc_channel_status_get.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_channel_status_get.restype = ctypes.c_int
libfmctdc.fmctdc_channel_status_get.errcheck = error_check_int
libfmctdc.fmctdc_set_buffer_mode.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_int]
libfmctdc.fmctdc_set_buffer_mode.restype = ctypes.c_int
libfmctdc.fmctdc_set_buffer_mode.errcheck = error_check_int
libfmctdc.fmctdc_get_buffer_mode.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_get_buffer_mode.restype = ctypes.c_int
libfmctdc.fmctdc_get_buffer_mode.errcheck = error_check_int
libfmctdc.fmctdc_set_buffer_len.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_uint]
libfmctdc.fmctdc_set_buffer_len.restype = ctypes.c_int
libfmctdc.fmctdc_set_buffer_len.errcheck = error_check_int
libfmctdc.fmctdc_get_buffer_len.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_get_buffer_len.restype = ctypes.c_int
libfmctdc.fmctdc_get_buffer_len.errcheck = error_check_int
libfmctdc.fmctdc_fileno_channel.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_fileno_channel.restype = ctypes.c_int
libfmctdc.fmctdc_fileno_channel.errcheck = error_check_int
libfmctdc.fmctdc_read.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(FmcTdcTime),
ctypes.c_int,
ctypes.c_int,
]
libfmctdc.fmctdc_read.restype = ctypes.c_int
libfmctdc.fmctdc_read.errcheck = error_check_int
libfmctdc.fmctdc_fread.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(FmcTdcTime),
ctypes.c_int,
ctypes.c_int,
]
libfmctdc.fmctdc_fread.restype = ctypes.c_int
libfmctdc.fmctdc_fread.errcheck = error_check_int
libfmctdc.fmctdc_flush.argtypes = [ctypes.c_void_p,
ctypes.c_uint]
libfmctdc.fmctdc_flush.restype = ctypes.c_int
libfmctdc.fmctdc_flush.errcheck = error_check_int
libfmctdc.fmctdc_set_offset_user.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_int32]
libfmctdc.fmctdc_set_offset_user.restype = ctypes.c_int
libfmctdc.fmctdc_set_offset_user.errcheck = error_check_int
libfmctdc.fmctdc_get_offset_user.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(ctypes.c_int32)]
libfmctdc.fmctdc_get_offset_user.restype = ctypes.c_int
libfmctdc.fmctdc_get_offset_user.errcheck = error_check_int
libfmctdc.fmctdc_ts_mode_set.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_int]
libfmctdc.fmctdc_ts_mode_set.restype = ctypes.c_int
libfmctdc.fmctdc_ts_mode_set.errcheck = error_check_int
libfmctdc.fmctdc_ts_mode_get.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(ctypes.c_int)]
libfmctdc.fmctdc_ts_mode_get.restype = ctypes.c_int
libfmctdc.fmctdc_ts_mode_get.errcheck = error_check_int
libfmctdc.fmctdc_stats_recv_get.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(ctypes.c_uint32)]
libfmctdc.fmctdc_stats_recv_get.restype = ctypes.c_int
libfmctdc.fmctdc_stats_recv_get.errcheck = error_check_int
libfmctdc.fmctdc_stats_trans_get.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(ctypes.c_uint32)]
libfmctdc.fmctdc_stats_trans_get.restype = ctypes.c_int
libfmctdc.fmctdc_stats_trans_get.errcheck = error_check_int
libfmctdc.fmctdc_coalescing_timeout_set.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_uint]
libfmctdc.fmctdc_coalescing_timeout_set.restype = ctypes.c_int
libfmctdc.fmctdc_coalescing_timeout_set.errcheck = error_check_int
libfmctdc.fmctdc_coalescing_timeout_get.argtypes = [ctypes.c_void_p,
ctypes.c_uint,
ctypes.POINTER(ctypes.c_uint)]
libfmctdc.fmctdc_coalescing_timeout_get.restype = ctypes.c_int
libfmctdc.fmctdc_coalescing_timeout_get.errcheck = error_check_int
return libfmctdc
libfmctdc = libfmctdc_create()
def fmctdc_strerror(err):
"""
Return FMC-TDC errors
:ivar err: error number
:return: an error string
"""
return libfmctdc.fmctdc_strerror(err)
class FmcTdc(object):
"""
It is a Python class that represent an FMC TDC device
:param devid: FMC TDC device identifier
:ivar device_id: device ID associated with the instance
:ivar tkn: device token to be used with the libfmctdc library
:ivar libfmctdc: the libfmctdc library
"""
CHANNEL_NUMBER = 5
BUFFER_TYPE = {"kmalloc": 0,
"vmalloc": 1}
TRANSFER_MODE = {"fifo": 0,
"dma": 1}
class FmcTdcChannel(object):
BUFFER_MODE = {"fifo": 0,
"circ": 1}
TIMESTAMP_MODE = {"post": 0,
"raw": 1}
def __init__(self, tkn, channel):
self.tkn = tkn
self.idx = channel
@property
def termination(self):
return bool(libfmctdc.fmctdc_get_termination(self.tkn, self.idx))
@termination.setter
def termination(self, val):
libfmctdc.fmctdc_set_termination(self.tkn, self.idx, int(val))
@property
def enable(self):
return bool(libfmctdc.fmctdc_channel_status_get(self.tkn,
self.idx))
@enable.setter
def enable(self, val):
libfmctdc.fmctdc_channel_status_set(self.tkn, self.idx, int(val))
@property
def buffer_mode(self):
ret = libfmctdc.fmctdc_get_buffer_mode(self.tkn, self.idx)
for k, v in self.BUFFER_MODE.items():
if ret == v:
return k
raise Exception("Unknown buffer mode")
@buffer_mode.setter
def buffer_mode(self, val):
libfmctdc.fmctdc_set_buffer_mode(self.tkn, self.idx,
self.BUFFER_MODE[val])
@property
def buffer_len(self):
ret = libfmctdc.fmctdc_get_buffer_len(self.tkn, self.idx)
@buffer_len.setter
def buffer_len(self, val):
libfmctdc.fmctdc_set_buffer_len(self.tkn, self.idx, int(val))
@property
def fileno(self):
return libfmctdc.fmctdc_fileno_channel(self.tkn, self.idx)
@property
def offset(self):
off = ctypes.c_int32(0)
libfmctdc.fmctdc_get_offset_user(self.tkn, self.idx,
ctypes.pointer(off))
return int(off.value)
@offset.setter
def offset(self, val):
libfmctdc.fmctdc_set_offset_user(self.tkn, self.idx,
int(val))
@property
def coalescing_timeout(self):
timeout = ctypes.c_uint(0)
libfmctdc.fmctdc_coalescing_timeout_get(self.tkn, self.idx,
ctypes.pointer(timeout))
return int(timeout.value)
@coalescing_timeout.setter
def coalescing_timeout(self, val):
libfmctdc.fmctdc_coalescing_timeout_set(self.tkn, self.idx,
int(val))
@property
def timestamp_mode(self):
mode = ctypes.c_int(0)
libfmctdc.fmctdc_ts_mode_get(self.tkn, self.idx,
ctypes.pointer(mode))
for k, v in self.TIMESTAMP_MODE.items():
if mode.value == v:
return k
raise Exception("Unknown buffer mode")
@timestamp_mode.setter
def timestamp_mode(self, val):
libfmctdc.fmctdc_ts_mode_set(self.tkn, self.idx,
self.TIMESTAMP_MODE[val])
@property
def stats(self):
recv = ctypes.c_uint32(0)
libfmctdc.fmctdc_stats_recv_get(self.tkn, self.idx,
ctypes.pointer(recv))
trans = ctypes.c_uint32(0)
libfmctdc.fmctdc_stats_recv_get(self.tkn, self.idx,
ctypes.pointer(trans))
return (recv.value, trans.value)
def read(self, n=1, flags=0):
ts = (FmcTdcTime * n)()
ret = libfmctdc.fmctdc_read(self.tkn, self.idx, ts ,n ,flags)
return list(ts)[:ret]
def fread(self, n=1, flags=0):
ts = (FmcTdcTime * n)()
libfmctdc.fmctdc_fread(self.tkn, self.idx, ts, n ,flags)
return list(ts)
def flush(self):
libfmctdc.fmctdc_flush(self.tkn, self.idx)
def __init__(self, devid):
if devid is None:
raise Exception("Invalid device ID")
self.device_id = devid
libfmctdc.fmctdc_init()
ctypes.set_errno(0)
self.tkn = libfmctdc.fmctdc_open(self.device_id)
self.chan = []
for i in range(self.CHANNEL_NUMBER):
self.chan.append(self.FmcTdcChannel(self.tkn, i))
def __del__(self):
if hasattr(self, 'tkn'):
libfmctdc.fmctdc_close(self.tkn)
libfmctdc.fmctdc_exit()
@property
def temperature(self):
return libfmctdc.fmctdc_read_temperature(self.tkn)
@property
def time(self):
ts = FmcTdcTime()
libfmctdc.fmctdc_get_time(self.tkn, ctypes.pointer(ts))
return ts
@time.setter
def time(self, val):
libfmctdc.fmctdc_set_time(self.tkn, ctypes.pointer(val))
@property
def whiterabbit_mode(self):
ret = libfmctdc.fmctdc_check_wr_mode(self.tkn)
if ret == 0:
return True
elif ret == -1 and ctypes.get_errno() == errno.ENODEV:
return False
else:
raise OSError(ctypes.get_errno(),
fmctdc_strerror(ctypes.get_errno()), "")
@whiterabbit_mode.setter
def whiterabbit_mode(self, val):
libfmctdc.fmctdc_wr_mode(self.tkn, int(val))
end = time.time() + 30
timeout = True
while time.time() < end:
time.sleep(0.1)
ret = libfmctdc.fmctdc_check_wr_mode(self.tkn)
if val and ret == 0:
timeout = False
break
if not val and ret == -1 and ctypes.get_errno() == errno.ENODEV:
timeout = False
break
if timeout:
raise OSError(ctypes.get_errno(),
fmctdc_strerror(ctypes.get_errno()), "")
@property
def buffer_type(self):
ret = libfmctdc.fmctdc_get_buffer_type(self.tkn)
for k, v in self.BUFFER_TYPE.items():
if ret == v:
return k
raise Exception("Unknown buffer type")
@buffer_type.setter
def buffer_type(self, val):
libfmctdc.fmctdc_set_buffer_type(self.tkn, self.BUFFER_TYPE[val])
@property
def transfer_mode(self):
mode = ctypes.c_int(0)
libfmctdc.fmctdc_transfer_mode(self.tkn, ctypes.pointer(mode))
for k, v in self.TRANSFER_MODE.items():
if mode.value == v:
return k
raise Exception("Unknown transfer mode")
"""
@package docstring
@author: Federico Vaga <federico.vaga@cern.ch>
SPDX-License-Identifier: LGPL-3.0-or-later
SPDX-FileCopyrightText: 2020 CERN (home.cern)
"""
from .PyFmcTdc import FmcTdc, FmcTdcTime
__all__ = (
"FmcTdc",
"FmcTdcTime",
)
#!/usr/bin/env python
"""
SPDX-License-Identifier: CC0-1.0
SPDX-FileCopyrightText: 2020 CERN
"""
from distutils.core import setup
setup(name='PyFmcTdc',
version='3.0',
description='Python Module to handle FMC TDC devices',
author='Federico Vaga',
author_email='federico.vaga@cern.ch',
maintainer="Federico Vaga",
maintainer_email="federico.vaga@cern.ch",
url='http://www.ohwr.org/projects/fmc-tdc',
packages=['PyFmcTdc'],
license='LGPL-3.0-or-later',
)
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library - timestamp math.
*
* Copyright (C) 2012-2018 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "fmctdc-lib.h"
#include "fmctdc-lib-private.h"
/**
* It provides a nano-second approximation of the timestamp.
* @param[in] a timestamp
* @return it returns the time stamp in nano-seconds
*/
uint64_t fmctdc_ts_approx_ns(struct fmctdc_time *a)
{
uint64_t ns = 0;
ns += a->seconds * 1000000000ULL;
ns += ((uint64_t)a->coarse) * 8ULL;
ns += ((uint64_t)a->frac) * 8000ULL / 4096ULL / 1000ULL;
return ns;
}
/**
* It provides a pico-seconds representation of the time stamp. Bear in mind
* that it may overflow. If you thing that it may happen, check the timestamp
* @param[in] a timestamp
* @return it returns the time stamp in pico-seconds
*/
uint64_t fmctdc_ts_ps(struct fmctdc_time *a)
{
uint64_t ps = 0;
ps += (uint64_t) a->seconds * 1000000000000ULL;
ps += (uint64_t) a->coarse * 8000ULL;
ps += (uint64_t) a->frac * 8000ULL / 4096ULL;
return ps;
}
/**
* It normalizes the timestamp
* @param[in,out] a timestamp
*/
void fmctdc_ts_norm(struct fmctdc_time *a)
{
uint64_t tmp;
if (a->frac >= 4096) {
tmp = a->frac / 4096UL;
a->coarse += tmp;
a->frac -= tmp * 4096UL;
}
if (a->coarse >= 125000000) {
tmp = a->coarse / 125000000UL;
a->seconds += tmp;
a->coarse -= tmp * 125000000UL;
}
}
/**
* It perform the subtraction: r = a - b (a > b)
* @param[out] r result
* @param[in] a normalized timestamp
* @param[in] b normalized timestamp
*/
static void __fmctdc_ts_sub(struct fmctdc_time *r,
const struct fmctdc_time *a,
const struct fmctdc_time *b)
{
int32_t d_frac, d_coarse = 0, d_seconds = 0;
memset(r, 0, sizeof(*r));
d_frac = a->frac - b->frac;
if (d_frac < 0) {
d_frac += 4096;
d_coarse--;
}
d_coarse += (a->coarse - b->coarse);
if (d_coarse < 0) {
d_coarse += 125000000;
d_seconds--;
}
d_seconds += (a->seconds - b->seconds);
r->coarse = d_coarse;
r->frac = d_frac;
r->seconds = d_seconds;
}
/**
* It perform the subtraction: r = a - b
* @param[out] r result
* @param[in] a normalized timestamp
* @param[in] b normalized timestamp
* @return 1 if the difference is negative, otherwise 0
*/
int fmctdc_ts_sub(struct fmctdc_time *r,
const struct fmctdc_time *a,
const struct fmctdc_time *b)
{
int negative = 0;
if (a->seconds < b->seconds)
negative = 1;
else if (a->seconds == b->seconds &&
a->coarse < b->coarse)
negative = 1;
else if (a->seconds == b->seconds &&
a->coarse == b->coarse &&
a->frac < b->frac)
negative = 1;
if (negative)
__fmctdc_ts_sub(r, b, a);
else
__fmctdc_ts_sub(r, a, b);
return negative;
}
/**
* It perform an addiction: r = a + b
* @param[out] r result
* @param[in] a normalized timestamp
* @param[in] b normalized timestamp
*/
void fmctdc_ts_add(struct fmctdc_time *r,
const struct fmctdc_time *a,
const struct fmctdc_time *b)
{
memset(r, 0, sizeof(*r));
r->frac = a->frac + b->frac;
if (r->frac >= 4096) {
r->frac -= 4096;
r->coarse++;
}
r->coarse += a->coarse + b->coarse;
if (r->coarse >= 125000000) {
r->coarse -= 125000000;
r->seconds++;
}
r->seconds += a->seconds + b->seconds;
}
/*
* The "official" fmc-tdc API
*
* Copyright (C) 2012-2018 CERN (www.cern.ch)
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#ifndef __FMCTDC_LIB_PRIVATE_H__
#define __FMCTDC_LIB_PRIVATE_H__
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fmc-tdc.h>
/* Internal structure */
struct __fmctdc_board {
uint32_t dev_id;
char *devbase;
char *sysbase;
int fdcc[FMCTDC_NUM_CHANNELS]; /**< current controls */
int fdc[5]; /* The 5 control channels */
int fdd[5]; /* The 5 data channels */
int ch_ref[FMCTDC_NUM_CHANNELS]; /**< reference channel */
};
static inline int fmctdc_is_verbose(void)
{
return getenv("FMCTDC_LIB_VERBOSE") != 0;
}
#define __define_board(b, ub) struct __fmctdc_board *b = (void *)(ub)
/* These two from ../tools/fdelay-raw.h, used internally */
static inline int __fmctdc_sysfs_get(char *path, uint32_t * resp)
{
FILE *f = fopen(path, "r");
if (!f)
return -1;
errno = 0;
if (fscanf(f, "%u", resp) != 1) {
fclose(f);
if (!errno)
errno = EINVAL;
return -1;
}
fclose(f);
return 0;
}
static inline int __fmctdc_sysfs_set(char *path, uint32_t * value)
{
char s[16];
int fd, ret, len;
len = sprintf(s, "%u\n", *value);
fd = open(path, O_WRONLY);
if (fd < 0)
return -1;
ret = write(fd, s, len);
close(fd);
if (ret < 0)
return -1;
if (ret == len)
return 0;
errno = EINVAL;
return -1;
}
/* And these two for the board structure */
static inline int fmctdc_sysfs_get(struct __fmctdc_board *b, char *name,
uint32_t * resp)
{
char pathname[128];
sprintf(pathname, "%s/%s", b->sysbase, name);
return __fmctdc_sysfs_get(pathname, resp);
}
static inline int fmctdc_sysfs_set(struct __fmctdc_board *b, char *name,
uint32_t * value)
{
char pathname[128];
sprintf(pathname, "%s/%s", b->sysbase, name);
return __fmctdc_sysfs_set(pathname, value);
}
static inline int __fmctdc_command(struct __fmctdc_board *b, uint32_t cmd)
{
return fmctdc_sysfs_set(b, "command", &cmd);
}
#endif /* __FMCTDC_LIB_PRIVATE_H__ */
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library.
*
* Copyright (C) 2012-2018 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/zio.h>
#include <linux/zio-user.h>
#include "fmctdc-lib.h"
#include "fmctdc-lib-private.h"
const char * const libfmctdc_version_s = "libfmctdc version: " GIT_VERSION;
const char * const libfmctdc_zio_version_s = "libfmctdc is using zio version: " ZIO_GIT_VERSION;
#define NSAMPLE 1 /* fake number of samples for the TDC */
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
static char *names[] = { "seconds", "coarse" }; /**< names used to retrive
time-stamps from sysfs */
static const char *fmctdc_error_string[] = {
[FMCTDC_ERR_VMALLOC - __FMCTDC_ERR_MIN] =
"Missing ZIO vmalloc support",
[FMCTDC_ERR_UNKNOWN_BUFFER_TYPE - __FMCTDC_ERR_MIN] =
"Unknown buffer type",
[FMCTDC_ERR_NOT_CONSISTENT_BUFFER_TYPE - __FMCTDC_ERR_MIN] =
"Buffer type configuration not consistent",
[FMCTDC_ERR_VERSION_MISMATCH - __FMCTDC_ERR_MIN] =
"Incompatible version driver-library",
};
/**
* It returns the error message associated to the given error code
* @param[in] err error code
*/
const char *fmctdc_strerror(int err)
{
if (err < __FMCTDC_ERR_MIN || err > __FMCTDC_ERR_MAX)
return strerror(err);
return fmctdc_error_string[err - __FMCTDC_ERR_MIN];
}
/**
* Init the library. You must call this function before use any other
* library function.
* @return 0 on success, otherwise -1 and errno is appropriately set
*/
int fmctdc_init(void)
{
return 0;
}
/**
* It releases all the resources used by the library and allocated
* by fmctdc_init().
*/
void fmctdc_exit(void)
{
}
/**
* It opens one specific device. -1 arguments mean "not installed"
* @param[in] dev_id FMC device id. -1 to ignore it and use only the offset
* @return an instance token, otherwise NULL and errno is appripriately set.
* ENODEV if the device was not found. EINVAL there is a mismatch with
* the arguments
*/
#define __FMCTDC_OPEN_PATH_MAX 128
struct fmctdc_board *fmctdc_open(int dev_id)
{
struct __fmctdc_board *b = NULL;
uint32_t nsamples = NSAMPLE;
char path[__FMCTDC_OPEN_PATH_MAX];
uint32_t v;
int i;
int ret;
struct stat sb;
if (dev_id < 0) {
errno = EINVAL;
return NULL;
}
b = malloc(sizeof(*b));
if (!b)
return NULL;
b->dev_id = dev_id;
/* get sysfs */
snprintf(path, sizeof(path),
"/sys/bus/zio/devices/tdc-1n5c-%04x", b->dev_id);
ret = stat(path, &sb);
if (ret < 0)
goto err_stat_s;
if (!S_ISDIR(sb.st_mode))
goto err_stat_s;
b->sysbase = strdup(path);
if (!b->sysbase)
goto err_dup_sys;
/* get dev */
snprintf(path, sizeof(path),
"/dev/zio/tdc-1n5c-%04x-0-0-ctrl", b->dev_id);
ret = stat(path, &sb);
if (ret < 0)
goto err_stat_d;
if (!S_ISCHR(sb.st_mode))
goto err_stat_d;
b->devbase = strndup(path, strlen(path) - strlen("-0-0-ctrl"));
if (!b->sysbase)
goto err_dup_dev;
ret = fmctdc_sysfs_get(b, "version", &v);
if (ret)
goto err_version;
if (v != FT_VERSION_MAJ) {
errno = FMCTDC_ERR_VERSION_MISMATCH;
goto err_version;
}
ret = fmctdc_get_buffer_type((struct fmctdc_board *)b);
if (ret < 0)
goto err_buf;
/* Trim all block sizes to 1 sample (i.e. 4 bytes) */
fmctdc_sysfs_set(b, "ft-ch1/trigger/post-samples", &nsamples);
fmctdc_sysfs_set(b, "ft-ch2/trigger/post-samples", &nsamples);
fmctdc_sysfs_set(b, "ft-ch3/trigger/post-samples", &nsamples);
fmctdc_sysfs_set(b, "ft-ch4/trigger/post-samples", &nsamples);
fmctdc_sysfs_set(b, "ft-ch5/trigger/post-samples", &nsamples);
for (i = 0; i < FMCTDC_NUM_CHANNELS; i++) {
b->fdc[i] = -1;
b->fdd[i] = -1;
b->fdcc[i] = -1;
/* Open Control */
snprintf(path, sizeof(path), "%s-%d-0-ctrl",
b->devbase, i);
b->fdc[i] = open(path, O_RDONLY | O_NONBLOCK);
if (b->fdc[i] < 0)
goto error;
/* Open Data - even if not really used for the time being */
snprintf(path, sizeof(path), "%s-%d-0-data",
b->devbase, i);
b->fdd[i] = open(path, O_RDONLY | O_NONBLOCK);
if (b->fdd[i] < 0)
goto error;
/* Open Current Control */
snprintf(path, sizeof(path), "%s/ft-ch%d/chan0/current-control",
b->sysbase, i + 1);
b->fdcc[i] = open(path, O_RDONLY);
if (b->fdcc[i] < 0)
goto error;
}
return (void *)b;
error:
while (i--) {
if (b->fdc[i] >= 0)
close(b->fdc[i]);
if (b->fdd[i] >= 0)
close(b->fdd[i]);
if (b->fdcc[i] >= 0)
close(b->fdcc[i]);
}
err_buf:
err_version:
free(b->devbase);
err_stat_d:
err_dup_dev:
free(b->sysbase);
err_dup_sys:
err_stat_s:
free(b);
return NULL;
}
/**
* It opens one specific device by logical unit number (CERN/BE-CO-like).
* The function uses a symbolic link in /dev that points to the standard device.
* The link is created by the local installation procedure, and it allows to get
* the device id according to the LUN.
* Read also fmctdc_open() documentation.
* @param[in] lun Logical Unit Number
* @return an instance token, otherwise NULL and errno is appripriately set
*/
struct fmctdc_board *fmctdc_open_by_lun(int lun)
{
ssize_t ret;
char dev_id_str[4];
char path_pattern[] = "/dev/fmc-tdc.%d";
char path[sizeof(path_pattern) + 1];
uint32_t dev_id;
ret = snprintf(path, sizeof(path), path_pattern, lun);
if (ret < 0 || ret >= sizeof(path)) {
errno = EINVAL;
return NULL;
}
ret = readlink(path, dev_id_str, sizeof(dev_id_str));
if (ret != 4) { /* 4 digits */
errno = ENODEV;
return NULL;
}
if (sscanf(dev_id_str, "%4x", &dev_id) != 1) {
errno = ENODEV;
return NULL;
}
return fmctdc_open(dev_id);
}
/**
* It closes a TDC instance opened with fmctdc_open() or fmctdc_open_by_lun()
* @param[in] userb TDC board instance token
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_close(struct fmctdc_board *userb)
{
__define_board(b, userb);
int j;
if (!b) {
errno = EINVAL;
return -1;
}
for (j = 0; j < ARRAY_SIZE(b->fdc); j++) {
if (b->fdc[j] >= 0)
close(b->fdc[j]);
if (b->fdd[j] >= 0)
close(b->fdd[j]);
if (b->fdcc[j] >= 0)
close(b->fdcc[j]);
}
free(b->devbase);
free(b->sysbase);
free(b);
return 0;
}
/**
* It reads the current temperature of a TDC device
* @param[in] userb TDC board instance token
* @return temperature
*/
float fmctdc_read_temperature(struct fmctdc_board *userb)
{
uint32_t t;
__define_board(b, userb);
fmctdc_sysfs_get(b, "temperature", &t);
return (float)t / 16.0;
}
/**
* The function enables/disables the 50 Ohm termination of the given channel.
* Termination may be changed anytime.
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @param[in] on status of the termination to set
* @return 0 on success, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_set_termination(struct fmctdc_board *userb, unsigned int channel,
int on)
{
__define_board(b, userb);
uint32_t val;
char attr[32];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/termination", channel + 1);
val = on ? 1 : 0;
return fmctdc_sysfs_set(b, attr, &val);
}
/**
* The function returns current temrmination status: 0 if the given channel
* is high-impedance and positive if it is 50 Ohm-terminated.
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @return termination status, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_get_termination(struct fmctdc_board *userb, unsigned int channel)
{
__define_board(b, userb);
uint32_t val;
char attr[32];
int ret;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/termination", channel + 1);
ret = fmctdc_sysfs_get(b, attr, &val);
if (ret)
return ret;
return val;
}
/**
* It gets the acquisition status of a TDC channel
* @param[in] userb TDC board instance token
* @param[in] channel channel to which we want read the status
* @return the acquisition status (0 disabled, 1 enabled), otherwise -1 and
* errno is set appropriately
*/
int fmctdc_channel_status_get(struct fmctdc_board *userb, unsigned int channel)
{
__define_board(b, userb);
uint32_t val;
char attr[64];
int ret;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/enable", channel + 1);
ret = fmctdc_sysfs_get(b, attr, &val);
if (ret)
return ret;
return val;
}
/**
* The function enables/disables timestamp acquisition for the given channel.
* @param[in] userb TDC board instance token
* @param[in] channel channel to which we want change status
* @param[in] status enable status to set
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_channel_status_set(struct fmctdc_board *userb, unsigned int channel,
enum fmctdc_channel_status status)
{
__define_board(b, userb);
uint32_t val = status;
char attr[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/enable", channel + 1);
return fmctdc_sysfs_set(b, attr, &val);
}
/**
* It enables a given channel.
* NOTE: it is just a wrapper of fmctdc_channel_status_set()
* @param[in] userb TDC board instance token
* @param[in] channel channel to which we want change status
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_channel_enable(struct fmctdc_board *userb, unsigned int channel)
{
return fmctdc_channel_status_set(userb, channel, FMCTDC_STATUS_ENABLE);
}
/**
* It disable a given channel.
* NOTE: it is just a wrapper of fmctdc_channel_status_set()
* @param[in] userb TDC board instance token
* @param[in] channel channel to which we want change status
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_channel_disable(struct fmctdc_board *userb, unsigned int channel)
{
return fmctdc_channel_status_set(userb, channel, FMCTDC_STATUS_DISABLE);
}
/**
* The function sets the buffer type for a channel
* @param[in] userb TDC board instance token
* @param[in] ch to use
* @param[in] mode buffer mode to use
* @return 0 on success, otherwise a negative errno code is set
* appropriately
*/
static int fmctdc_set_buffer_type_chan(struct fmctdc_board *userb,
unsigned int ch,
enum fmctdc_buffer_type type)
{
struct __fmctdc_board *b = (struct __fmctdc_board *)userb;
char path[128];
int fd;
int ret;
snprintf(path, sizeof(path),
"%s/ft-ch%u/current_buffer", b->sysbase, ch + 1);
fd = open(path, O_WRONLY);
if (!fd)
return -1;
switch (type) {
case FMCTDC_BUFFER_KMALLOC:
ret = write(fd, "kmalloc", 7);
break;
case FMCTDC_BUFFER_VMALLOC:
ret = write(fd, "vmalloc", 7);
break;
default:
ret = -1;
break;
}
close(fd);
return ret != 7 ? -1 : 0;
}
static int fmctdc_get_buffer_type_chan(struct fmctdc_board *userb,
unsigned int ch,
enum fmctdc_buffer_type *type)
{
struct __fmctdc_board *b = (struct __fmctdc_board *)userb;
char path[128];
char buffer_type[8];
int fd;
int ret;
snprintf(path, sizeof(path),
"%s/ft-ch%u/current_buffer", b->sysbase, ch + 1);
fd = open(path, O_RDONLY);
if (!fd)
return -1;
ret = read(fd, buffer_type, sizeof(buffer_type));
close(fd);
if (ret < 0)
return -1;
if (strncmp("kmalloc", buffer_type, 7) == 0) {
*type = FMCTDC_BUFFER_KMALLOC;
} else if (strncmp("vmalloc", buffer_type, 7) == 0) {
*type = FMCTDC_BUFFER_VMALLOC;
} else {
errno = FMCTDC_ERR_UNKNOWN_BUFFER_TYPE;
return -1;
}
return 0;
}
/**
* The function sets the buffer type for a device
* @param[in] userb TDC board instance token
* @param[in] mode buffer mode to use
* @return 0 on success, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_set_buffer_type(struct fmctdc_board *userb,
enum fmctdc_buffer_type type)
{
int i;
int err = 0;
for (i = 0; i < FMCTDC_NUM_CHANNELS; ++i) {
err = fmctdc_set_buffer_type_chan(userb, i, type);
if (err) {
errno = FMCTDC_ERR_NOT_CONSISTENT_BUFFER_TYPE;
break;
}
}
return err;
}
/**
* The function returns current buffer type: 0 for kmallo, 1 for vmalloc.
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @return buffer type, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_get_buffer_type(struct fmctdc_board *userb)
{
int i;
int err = 0;
enum fmctdc_buffer_type type_prev = -1, type_cur;
for (i = 0; i < FMCTDC_NUM_CHANNELS; ++i) {
err = fmctdc_get_buffer_type_chan(userb, i, &type_cur);
if (err) {
errno = FMCTDC_ERR_NOT_CONSISTENT_BUFFER_TYPE;
break;
}
if (i == 0) {
type_prev = type_cur;
continue;
}
if (type_prev != type_cur) {
/* There are channels with a different configuration */
err = -1;
errno = FMCTDC_ERR_NOT_CONSISTENT_BUFFER_TYPE;
break;
}
type_prev = type_cur;
}
return err ? err : type_prev;
}
/**
* The function returns current buffer mode: 0 for FIFO, 1 for circular buffer.
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @return buffer mode, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_get_buffer_mode(struct fmctdc_board *userb, unsigned int channel)
{
__define_board(b, userb);
uint32_t val;
char attr[64];
int ret;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/chan0/buffer/prefer-new",
channel + 1);
ret = fmctdc_sysfs_get(b, attr, &val);
if (ret)
return ret;
return val;
}
/**
* The function sets the buffer mode for a channel
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @param[in] mode buffer mode to use
* @return 0 on success, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_set_buffer_mode(struct fmctdc_board *userb, unsigned int channel,
enum fmctdc_buffer_mode mode)
{
__define_board(b, userb);
uint32_t val;
char attr[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/chan0/buffer/prefer-new",
channel + 1);
val = mode;
return fmctdc_sysfs_set(b, attr, &val);
}
/**
* The function returns current driver buffer length (number of timestamps)
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @return buffer lenght, otherwise a negative errno code is set
* appropriately
*/
int fmctdc_get_buffer_len(struct fmctdc_board *userb, unsigned int channel)
{
__define_board(b, userb);
uint32_t val;
char attr[64];
int ret;
char path[64];
snprintf(path, sizeof(path), "%s/ft-ch%u/chan0/buffer/max-buffer-kb",
b->sysbase, channel + 1);
if (access(path, R_OK | W_OK)) {
errno = FMCTDC_ERR_VMALLOC;
return -1;
}
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/chan0/buffer/max-buffer-kb",
channel + 1);
ret = fmctdc_sysfs_get(b, attr, &val);
if (ret)
return ret;
val = (val * 1024) / sizeof(struct ft_hw_timestamp);
return val;
}
/**
* The function set the buffer length
* @param[in] userb TDC board instance token
* @param[in] channel to use
* @param[in] length maximum number of timestamps to store (min: 64)
* @return 0 on success, otherwise a negative errno code is set
* appropriately
*
* Internally, the buffer allocates memory in chunks of minimun 1KiB. This
* means, for example, that if you ask for 65 timestamp the buffer will
* allocate space for 128. This because 64 timestamps fit in 1KiB, to store
* 65 we need 2KiB (128 timestamps)
*/
int fmctdc_set_buffer_len(struct fmctdc_board *userb, unsigned int channel,
unsigned int length)
{
__define_board(b, userb);
uint32_t val;
char attr[64];
char path[128];
snprintf(path, sizeof(path), "%s/ft-ch%u/chan0/buffer/max-buffer-kb",
b->sysbase, channel + 1);
if (access(path, R_OK | W_OK)) {
errno = FMCTDC_ERR_VMALLOC;
return -1;
}
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
if (length < 64) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/chan0/buffer/max-buffer-kb",
channel + 1);
val = ((length * sizeof(struct ft_hw_timestamp)) / 1024) + 1;
return fmctdc_sysfs_set(b, attr, &val);
}
/**
* It get the file descriptor of a TDC channel. So, for example, you can
* poll(2) and select(2).
* Note that, the file descriptor is the file-descriptor of a
* ZIO control char-device.
* @param[in] userb TDC board instance token
* @param[in] channel channel to use
* @return a file descriptor, otherwise -1 and errno is set appropriately
*/
int fmctdc_fileno_channel(struct fmctdc_board *userb, unsigned int channel)
{
__define_board(b, userb);
return b->fdc[channel];
}
static void fmctdc_ts_convert(struct fmctdc_time *t, struct ft_hw_timestamp *o)
{
t->seconds = o->seconds;
t->coarse = o->coarse;
t->frac = o->frac & 0xfff;
t->debug = o->metadata;
t->seq_id = FT_HW_TS_META_SEQ(o->metadata);
}
static void fmctdc_ts_convert_n(struct fmctdc_time *t,
struct ft_hw_timestamp *o,
unsigned int n)
{
int i;
for (i = 0; i < n; ++i)
fmctdc_ts_convert(&t[i], &o[i]);
}
/**
* It reads up to *max* timestamps from the buffer
* @param[in] userb TDC board instance token
* @param[in] channel channel to use [0, 4]
* @param[out] t array of time-stamps
* @param[in] n number of elements to save in the array
* @return the number of samples read
*/
static int __fmctdc_read(struct fmctdc_board *userb, unsigned int channel,
struct fmctdc_time *t, int max)
{
__define_board(b, userb);
struct ft_hw_timestamp *data;
int n;
data = calloc(max, sizeof(*data));
if (!data) {
errno = ENOMEM;
return -1;
}
n = read(b->fdd[channel], data, sizeof(*data) * max);
if (n < 0)
goto err;
if (n % sizeof(*data) != 0) {
errno = EIO;
goto err;
}
n /= sizeof(*data); /* convert to number of samples */
fmctdc_ts_convert_n(t, data, n);
free(data);
return n;
err:
free(data);
return -1;
}
/**
* It reads a given number of time-stamps from the driver. It will wait at
* most once and return the number of samples that it received from a given
* input channel.
*
* Timestamps are to the base time.
*
* This "read" behaves like the system call and obeys O_NONBLOCK
* @param[in] userb TDC board instance token
* @param[in] channel channel to use [0, 4]
* @param[out] t array of time-stamps
* @param[in] n number of elements to save in the array
* @param[in] flags tune the behaviour of the function.
* O_NONBLOCK - do not block
* @return number of acquired time-stamps, otherwise -1 and errno is set
* appropriately.
* - EINVAL for invalid arguments
* - EIO for invalid IO transfer
* - EAGAIN if nothing ready to read in NONBLOCK mode
*/
int fmctdc_read(struct fmctdc_board *userb, unsigned int channel,
struct fmctdc_time *t, int n, int flags)
{
__define_board(b, userb);
int i;
fd_set set;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
i = 0;
while (i < n) {
int n_ts = __fmctdc_read(userb, channel, &t[i], 1);
if (n_ts < 0 && errno != EAGAIN) {
if (i == 0)
return -1;
else
break;
}
if (n_ts > 0) {
i += n_ts;
continue;
}
if (i) /* error but before we got something */
break;
/* EAGAIN at first sample */
if (n_ts < 0 && flags == O_NONBLOCK)
return -1;
/* So, first sample and blocking read. Wait.. */
FD_ZERO(&set);
FD_SET(b->fdc[channel], &set);
if (select(b->fdc[channel] + 1, &set, NULL, NULL, NULL) < 0)
return -1;
}
return i;
}
/**
* this "fread" behaves like stdio: it reads all the samples. Read fmctdc_read()
* for more details about the function.
* @param[in] userb TDC board instance token
* @param[in] channel channel to use
* @param[out] t array of time-stamps
* @param[in] n number of elements to save in the array
* @return number of acquired time-stamps, otherwise -1 and errno is set
* appropriately
*/
int fmctdc_fread(struct fmctdc_board *userb, unsigned int channel,
struct fmctdc_time *t, int n)
{
int i;
for (i = 0; i < n;) {
int loop = fmctdc_read(userb, channel, t + i, n - i, 0);
if (loop < 0)
return -1;
i += loop;
}
return i;
}
/**
* It sets the TDC base-time according to the given time-stamp.
* Note that, for the time being, it sets only seconds.
* Note that, you can set the time only when the acquisition is disabled.
* @param[in] userb TDC board instance token
* @param[in] t time-stamp
* @return 0 on success, otherwise -1 and errno is set
*/
int fmctdc_set_time(struct fmctdc_board *userb, struct fmctdc_time *t)
{
__define_board(b, userb);
uint32_t attrs[ARRAY_SIZE(names)];
int i;
attrs[0] = t->seconds & 0xffffffff;
attrs[1] = t->coarse;
for (i = ARRAY_SIZE(names) - 1; i >= 0; i--) {
int ret = fmctdc_sysfs_set(b, names[i], attrs + i);
if (ret < 0)
return ret;
}
return 0;
}
/**
* It gets the base-time of a TDC device.
* Note that, for the time being, it gets only seconds.
* @param[in] userb TDC board instance token
* @param[out] t time-stamp
* @return 0 on success, otherwise -1 and errno is set
*/
int fmctdc_get_time(struct fmctdc_board *userb, struct fmctdc_time *t)
{
__define_board(b, userb);
uint32_t attrs[ARRAY_SIZE(names)];
int i;
for (i = 0; i < ARRAY_SIZE(names); i++) {
int ret = fmctdc_sysfs_get(b, names[i], attrs + i);
if (ret < 0)
return ret;
}
t->seconds = attrs[0];
t->coarse = attrs[1];
t->frac = 0;
return 0;
}
/**
* It sets the TDC base-time according to the host time
* @param[in] userb TDC board instance token
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_set_host_time(struct fmctdc_board *userb)
{
__define_board(b, userb);
return __fmctdc_command(b, FT_CMD_SET_HOST_TIME);
}
/**
* It enables/disables the WhiteRabbit timing system on a TDC device
* @param[in] userb TDC board instance token
* @param[in] on white-rabbit status to set
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_wr_mode(struct fmctdc_board *userb, int on)
{
__define_board(b, userb);
return __fmctdc_command(b, on ? FT_CMD_WR_ENABLE : FT_CMD_WR_DISABLE);
}
/**
* It check the current status of the WhiteRabbit timing system on a TDC device
* @param[in] userb TDC board instance token
* @return 0 if it properly works, -1 on error and errno is set appropriately.
* - ENOLINK if it is not synchronized and
* - ENODEV if it is not enabled
*/
extern int fmctdc_check_wr_mode(struct fmctdc_board *userb)
{
__define_board(b, userb);
if (__fmctdc_command(b, FT_CMD_WR_QUERY) == 0)
return 0;
return -1;
}
/**
* It removes all samples from the channel buffer. In order to doing this,
* the function temporary disable any active acquisition, only when the flush
* is completed the acquisition will be re-enabled
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_flush(struct fmctdc_board *userb, unsigned int channel)
{
struct __fmctdc_board *b = (void *)(userb);
int en, err;
uint32_t val = 1;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
en = fmctdc_channel_status_get(userb, channel);
if (en < 0)
return -1;
/* Disable acquisition, it will flush the hw buffer */
err = fmctdc_channel_status_set(userb, channel, FMCTDC_STATUS_DISABLE);
if (err)
return err;
if (1) {
/*
* For some reason the ZIO flush attribute does not work.
* I do not have time to investigate it. Flush it by reading
*/
struct fmctdc_time ts[100];
int n;
do {
n = fmctdc_read(userb, channel, ts, 100, O_NONBLOCK);
} while (n > 0);
} else {
char path[64];
/* Flush ZIO buffer */
snprintf(path, sizeof(path),
"ft-ch%u/chan0/buffer/flush",
channel + 1);
err = fmctdc_sysfs_set(b, path, &val);
if (err)
return err;
}
/* Re-enable if it was enable */
return fmctdc_channel_status_set(userb, channel, en);
}
/**
* It sets the user offset to be applied on incoming timestamps. All the
* timestamps read from the driver (this means also from this library) will
* be already corrected using this offset.
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[in] offset the number of pico-seconds to be added
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_set_offset_user(struct fmctdc_board *userb,
unsigned int channel, int32_t offset)
{
__define_board(b, userb);
uint32_t val = (uint32_t)offset;
char attr[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(attr, sizeof(attr), "ft-ch%u/user-offset", channel + 1);
return fmctdc_sysfs_set(b, attr, &val);
}
/**
* It get the current user offset applied to the incoming timestamps
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[out] offset the number of pico-seconds to be added
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_get_offset_user(struct fmctdc_board *userb,
unsigned int channel, int32_t *offset)
{
struct __fmctdc_board *b = (void *)(userb);
uint32_t val;
char path[64];
int err;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/user-offset", channel + 1);
err = fmctdc_sysfs_get(b, path, &val);
if (err)
return -1;
*offset = (int32_t)val;
return 0;
}
/**
* It gets the current transfer mode
* @param[in] userb TDC board instance token
* @param[out] mode transfer mode
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_transfer_mode(struct fmctdc_board *userb,
enum ft_transfer_mode *mode)
{
struct __fmctdc_board *b = (void *)(userb);
uint32_t val;
int err;
err = fmctdc_sysfs_get(b, "transfer-mode", &val);
if (err)
return -1;
*mode = val;
return 0;
}
/**
* It sets the coalescing timeout on a given channel
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[in] timeout_ms ms timeout to trigger IRQ
* @return 0 on success, otherwise -1 and errno is set appropriately
*
* It does not work per-channel for the following acquisition mechanism:
* - FIFO (it will return the global IRQ coalescing timeout)
*/
int fmctdc_coalescing_timeout_set(struct fmctdc_board *userb,
unsigned int channel,
unsigned int timeout_ms)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
uint32_t val = timeout_ms;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/irq_coalescing_time",
channel + 1);
return fmctdc_sysfs_set(b, path, &val);
}
/**
* It gets the coalescing timeout from a given channel
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[out] timeout_ms ms timeout to trigger IRQ
* @return 0 on success, otherwise -1 and errno is set appropriately
*
* It does not work per-channel for the following acuqisition mechanism:
* - FIFO: there is a global configuration for all channels
*/
int fmctdc_coalescing_timeout_get(struct fmctdc_board *userb,
unsigned int channel,
unsigned int *timeout_ms)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
uint32_t val;
int err;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/irq_coalescing_time",
channel + 1);
err = fmctdc_sysfs_get(b, path, &val);
if (err)
return -1;
*timeout_ms = val;
return 0;
}
/**
* It sets the timestamp mode
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[in] mode time-stamp mode
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_ts_mode_set(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_ts_mode mode)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/raw_readout_mode",
channel + 1);
return fmctdc_sysfs_set(b, path, &mode);
}
/**
* It gets the timestamp mode
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[out] mode time-stamp mode
* @return 0 on success, otherwise -1 and errno is set appropriately
*
*/
int fmctdc_ts_mode_get(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_ts_mode *mode)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
uint32_t val;
int err;
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/raw_readout_mode",
channel + 1);
err = fmctdc_sysfs_get(b, path, &val);
if (err)
return -1;
*mode = val;
return 0;
}
/**
* It gets the number of received pulses (on hardware)
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[out] val number of received pulses
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_stats_recv_get(struct fmctdc_board *userb,
unsigned int channel,
uint32_t *val)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/received",
channel + 1);
return fmctdc_sysfs_get(b, path, val);
}
/**
* It gets the number of transferred timestamps
* @param[in] userb TDC board instance token
* @param[in] channel target channel [0, 4]
* @param[out] val number of transferred timestamps
* @return 0 on success, otherwise -1 and errno is set appropriately
*/
int fmctdc_stats_trans_get(struct fmctdc_board *userb,
unsigned int channel,
uint32_t *val)
{
struct __fmctdc_board *b = (void *)(userb);
char path[64];
if (channel >= FMCTDC_NUM_CHANNELS) {
errno = EINVAL;
return -1;
}
snprintf(path, sizeof(path), "ft-ch%u/transferred",
channel + 1);
return fmctdc_sysfs_get(b, path, val);
}
/*
* The "official" fmc-tdc API
*
* Copyright (C) 2012-2018 CERN (www.cern.ch)
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#ifndef __FMCTDC_LIB_H__
#define __FMCTDC_LIB_H__
#ifdef __cplusplus
#pragma GCC diagnostic ignored "-Wwrite-strings"
extern "C" {
#endif
#include <inttypes.h>
#include <stdint.h>
/**
* printf format for timestamps with pico-second resolution
*/
#define PRItsps "%010"PRIu64"s %012"PRIu64"ps"
/**
* printf value for timestamps with pico-second resolution
*/
#define PRItspsVAL(_ts) (_ts)->seconds, (uint64_t)(((uint64_t)(_ts)->coarse * 8000ULL) + ((uint64_t)(_ts)->frac *8000ULL / 4096ULL))
/**
* printf format for timestamps with White-Rabbit notation
*/
#define PRItswr "%10"PRIu64":%09u:%04u"
/**
* printf value for timestamp with White-Rabbit notation
*/
#define PRItswrVAL(_ts) (_ts)->seconds, (_ts)->coarse, (_ts)->frac
#define __FMCTDC_ERR_MIN 4096
enum fmctdc_error_numbers {
FMCTDC_ERR_VMALLOC = __FMCTDC_ERR_MIN,
FMCTDC_ERR_UNKNOWN_BUFFER_TYPE,
FMCTDC_ERR_NOT_CONSISTENT_BUFFER_TYPE,
FMCTDC_ERR_VERSION_MISMATCH,
__FMCTDC_ERR_MAX,
};
/**
* Enumeration for all TDC channels
*/
enum fmctdc_channel {
FMCTDC_CH_1 = 0,
FMCTDC_CH_2,
FMCTDC_CH_3,
FMCTDC_CH_4,
FMCTDC_CH_5,
FMCTDC_CH_LAST = FMCTDC_CH_5,
FMCTDC_NUM_CHANNELS = 5
};
/**
* Enumeration of all buffer modes
*/
enum fmctdc_buffer_mode {
FMCTDC_BUFFER_FIFO = 0, /**< FIFO policy: when buffer is full, new
time-stamps will be dropped */
FMCTDC_BUFFER_CIRC, /**< circular buffer policy: when the buffer is
full, old time-stamps will be overwritten by
new ones */
};
/**
* Enumeration of all buffer types
*/
enum fmctdc_buffer_type {
FMCTDC_BUFFER_KMALLOC = 0, /**< kernel allocator: kmalloc */
FMCTDC_BUFFER_VMALLOC, /**< kernel allocator: vmalloc */
};
/**
* Enumeration for all possible status of a channel
*/
enum fmctdc_channel_status {
FMCTDC_STATUS_DISABLE = 0, /**< The cannel is disable */
FMCTDC_STATUS_ENABLE, /**< the channel is enable */
};
/**
*
*/
enum ft_transfer_mode {
FT_ACQ_TYPE_FIFO = 0,
FT_ACQ_TYPE_DMA,
};
/**
* Enumeration for all possible time-stmap mode
*/
enum fmctdc_ts_mode {
FMCTDC_TS_MODE_POST = 0, /**< after post-processing */
FMCTDC_TS_MODE_RAW, /**< directly from ACAM chip. This should be used
ONLY when debugging low level issues */
};
/**
* Opaque data type used as token. Do not try to access.
*/
struct fmctdc_board;
/**
* FMC-TDC time-stamp descriptor
*/
struct fmctdc_time {
uint64_t seconds; /**< TAI seconds. Note this is *not* an UTC time;
the counter does not support leap seconds. The
internal counter is also limited to 32 bits
(2038-error-prone). */
uint32_t coarse; /**< number of ticks of 8ns since the beginning of
the last second*/
uint32_t frac; /**< fractional part of an 8 ns tick, rescaled
to (0..4095) range - i.e. 0 = 0 ns, and
4095 = 7.999 ns. */
uint32_t seq_id; /**< channel sequence number*/
uint32_t debug; /**< debug stuff, driver/firmware-specific */
};
/**
* @file fmctdc-lib.c
*/
/**
* @defgroup libutil Utilities
* Set of library utilities
* @{
*/
extern const char *fmctdc_strerror(int err);
extern int fmctdc_init(void);
extern void fmctdc_exit(void);
/**@}*/
/**
* @defgroup libboard Board Configuration
* Set of function to configure TDC board and retrieve information
* about the current status
* @{
*/
extern int fmctdc_set_time(struct fmctdc_board *b, struct fmctdc_time *t);
extern int fmctdc_get_time(struct fmctdc_board *b, struct fmctdc_time *t);
extern int fmctdc_set_host_time(struct fmctdc_board *b);
extern int fmctdc_wr_mode(struct fmctdc_board *b, int on);
extern int fmctdc_check_wr_mode(struct fmctdc_board *b);
extern float fmctdc_read_temperature(struct fmctdc_board *b);
/**@}*/
/**
* @defgroup libchan Channel Configuration
* Set of function to configure TDC channels and retrieve information
* about the current status
* @{
*/
extern int fmctdc_channel_status_set(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_channel_status status);
extern int fmctdc_channel_enable(struct fmctdc_board *userb,
unsigned int channel);
extern int fmctdc_channel_disable(struct fmctdc_board *userb,
unsigned int channel);
extern int fmctdc_channel_status_get(struct fmctdc_board *userb,
unsigned int channel);
extern int fmctdc_set_termination(struct fmctdc_board *b, unsigned int channel,
int enable);
extern int fmctdc_get_termination(struct fmctdc_board *b, unsigned int channel);
extern int fmctdc_get_buffer_type(struct fmctdc_board *userb);
extern int fmctdc_set_buffer_type(struct fmctdc_board *userb,
enum fmctdc_buffer_type type);
extern int fmctdc_get_buffer_mode(struct fmctdc_board *userb,
unsigned int channel);
extern int fmctdc_set_buffer_mode(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_buffer_mode mode);
extern int fmctdc_get_buffer_len(struct fmctdc_board *userb,
unsigned int channel);
extern int fmctdc_set_buffer_len(struct fmctdc_board *userb,
unsigned int channel,
unsigned int length);
extern int fmctdc_set_offset_user(struct fmctdc_board *userb,
unsigned int channel, int32_t offset);
extern int fmctdc_get_offset_user(struct fmctdc_board *userb,
unsigned int channel, int32_t *offset);
extern int fmctdc_transfer_mode(struct fmctdc_board *userb,
enum ft_transfer_mode *mode);
extern int fmctdc_coalescing_timeout_set(struct fmctdc_board *userb,
unsigned int channel,
unsigned int timeout_ms);
extern int fmctdc_coalescing_timeout_get(struct fmctdc_board *userb,
unsigned int channel,
unsigned int *timeout_ms);
extern int fmctdc_ts_mode_set(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_ts_mode mode);
extern int fmctdc_ts_mode_get(struct fmctdc_board *userb,
unsigned int channel,
enum fmctdc_ts_mode *mode);
/**@}*/
#include "fmctdc-lib-private.h"
/**
* @defgroup libacq Time-stamps Acquisition
* Set of functions to read time-stamps from the board
* @{
*/
extern struct fmctdc_board *fmctdc_open(int dev_id);
extern struct fmctdc_board *fmctdc_open_by_lun(int lun);
extern int fmctdc_close(struct fmctdc_board *);
extern int fmctdc_fread(struct fmctdc_board *b, unsigned int channel,
struct fmctdc_time *t, int n);
extern int fmctdc_fileno_channel(struct fmctdc_board *b, unsigned int channel);
extern int fmctdc_read(struct fmctdc_board *b, unsigned int channel,
struct fmctdc_time *t, int n, int flags);
extern int fmctdc_readhw(struct fmctdc_board *b, unsigned int channel,
struct ft_hw_timestamp *t, int n, int flags);
extern int fmctdc_flush(struct fmctdc_board *userb, unsigned int channel);
/**@}*/
/**
* @defgroup libstats Statistics
* Set of functions to get statistics
* @{
*/
extern int fmctdc_stats_recv_get(struct fmctdc_board *userb,
unsigned int channel,
uint32_t *val);
extern int fmctdc_stats_trans_get(struct fmctdc_board *userb,
unsigned int channel,
uint32_t *val);
/**@}*/
/**
*@file fmctdc-lib-math.c
*/
/**
* @defgroup libmath Time-Stamp Math
* Set of mathematical functions on time-stamps
* @{
*/
extern uint64_t fmctdc_ts_approx_ns(struct fmctdc_time *a);
extern uint64_t fmctdc_ts_ps(struct fmctdc_time *a);
extern void fmctdc_ts_norm(struct fmctdc_time *a);
extern int fmctdc_ts_sub(struct fmctdc_time *r,
const struct fmctdc_time *a,
const struct fmctdc_time *b);
extern void fmctdc_ts_add(struct fmctdc_time *r,
const struct fmctdc_time *a,
const struct fmctdc_time *b);
extern int _fmctdc_tscmp(struct fmctdc_time *a, struct fmctdc_time *b);
/**@}*/
/**
* libfmctdc version string
*/
extern const char * const libfmctdc_version_s;
/**
* zio version string used during compilation of libfmctdc
*/
extern const char * const libfmctdc_zio_version_s;
#ifdef __cplusplus
}
#endif
#endif /* __FMCTDC_LIB_H__ */
fmc-tdc-acquisition
fmc-tdc-list
fmc-tdc-read
fmc-tdc-temperature
fmc-tdc-term
fmc-tdc-time
fmc-tdc-tstamp
fmc-tdc-offset
fmc-tdc-perftest
fmc-tdc-calibration
Makefile.specific
# If it exists includes Makefile.specific. In this Makefile, you should put
# specific Makefile code that you want to run before this. For example,
# build a particular environment.
-include Makefile.specific
# include parent_common.mk for buildsystem's defines
IGNORE_CPU_SUFFIX := y
REPO_PARENT ?=
-include $(REPO_PARENT)/parent_common.mk
DESTDIR ?= /usr/local/
LIBTDC = ../lib/
TESTS = fmc-tdc-term \
fmc-tdc-temperature \
fmc-tdc-time \
fmc-tdc-tstamp \
fmc-tdc-perftest \
fmc-tdc-offset \
fmc-tdc-calibration
CFLAGS = -ggdb -I. -I$(LIBTDC) -I../kernel -Wall -Werror $(EXTRACFLAGS)
GIT_VERSION := $(shell git describe --dirty --long --tags)
CFLAGS += -DGIT_VERSION="\"$(GIT_VERSION)\""
COMMON_SRCS = test-common.c
LDFLAGS = -L$(LIBTDC)
LDLIBS = -lfmctdc -lrt
CPPCHECK ?= cppcheck
all: $(TESTS)
$(TESTS): $(COMMON_SRCS:.c=.o) $(LIBTDC)/libfmctdc.a
fmc-tdc-list:
clean:
rm -f $(TESTS) fmc-tdc-list test-common.o
# make nothing for modules_install, but avoid errors
modules_install:
install:
install -d $(DESTDIR)/bin
install -D fmc-tdc-list $(DESTDIR)/bin
install -D $(TESTS) $(DESTDIR)/bin
cppcheck:
$(CPPCHECK) -q -I. -I../kernel -I$(LIBTDC) --suppress=missingIncludeSystem --enable=all *.c *.h
// SPDX-License-Identifier: GPL-3.0-or-later
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fmc-tdc.h>
char git_version[] = "git version: " GIT_VERSION;
static const char program_name[] = "fmc-tdc-calibration";
static char options[] = "hf:o:D:b";
static const char help_msg[] =
"Usage: fmc-tdc-calibration [options]\n"
"\n"
"It reads calibration data from a file that contains it in binary\n"
"form and it shows it on STDOUT in binary form or in human readable\n"
"one (default).\n"
"This could be used to change the TDC calibration data at runtime\n"
"by redirectiong the binary output of this program to the proper \n"
"sysfs binary attribute\n"
"Rembember that we expect all values to be little endian\n"
"Rembember that the TDC driver supports only ps precision, but\n"
"calibration data is typically is stored with sub-picosecond\n"
"precision. For this reason, according to your source, calibration\n"
"values may disagree on the fs part\n"
"\n"
"General options:\n"
"-h Print this message\n"
"-b Show Calibration in binary form \n"
"\n"
"Read options:\n"
"-f Source file where to read calibration data from\n"
"-o Offset in bytes within the file (default 0)\n"
"Write options:\n"
"-D FMC TDC Target Device ID\n"
"\n";
/**
* Read calibration data from file
* @path: file path
* @calib: calibration data
* @offset: offset in file
*
* Return: number of bytes read
*/
static int fau_calibration_read(char *path, struct ft_calibration_raw *calib,
off_t offset)
{
int fd;
int ret = 0;
uint32_t *data32 = (uint32_t *)calib;
int i;
fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
ret = lseek(fd, offset, SEEK_SET);
if (ret >= 0)
ret = read(fd, calib, sizeof(*calib));
close(fd);
/* Fix endianess */
for (i = 0; i < sizeof(*calib) / sizeof(uint32_t); ++i)
data32[i] = le32toh(data32[i]);
return ret;
}
/**
* Print calibration data on stdout in humand readable format
* @calib: calibration data
*/
static void fau_calibration_dump_human(struct ft_calibration_raw *calib)
{
int i;
fprintf(stdout, "Temperature: %"PRIu32" C\n",
calib->calibration_temp);
fprintf(stdout, "White Rabbit Offset: %"PRIi32" fs\n",
calib->wr_offset * 10);
fputs("Zero Offset\n", stdout);
for (i = 0 ; i < FT_NUM_CHANNELS - 1; ++i)
fprintf(stdout, " ch%d-ch%d: %"PRIi32" fs\n",
i + 1, i + 2, calib->zero_offset[i] * 10);
fputc('\n', stdout);
}
/**
* Print binary calibration data on stdout
* @calib: calibration data
*/
static void fau_calibration_dump_machine(struct ft_calibration_raw *calib)
{
write(fileno(stdout), calib, sizeof(*calib));
}
/**
* Write calibration data to device
* @devid: Device ID
* @calib: calibration data
*
* Return: number of bytes wrote
*/
static int fau_calibration_write(unsigned int devid,
struct ft_calibration_raw *calib)
{
struct ft_calibration_raw calib_cp;
uint32_t *data32;
char path[128];
int fd;
int ret;
int i;
sprintf(path,
"/sys/bus/zio/devices/tdc-1n5c-%04x/calibration_data",
devid);
/* Fix endianess */
memcpy(&calib_cp, calib, sizeof(calib_cp));
data32 = (uint32_t *) &calib_cp;
for (i = 0; i < sizeof(calib_cp) / sizeof(uint32_t); ++i)
data32[i] = htole32(data32[i]);
fd = open(path, O_WRONLY);
if (fd < 0)
return -1;
ret = write(fd, &calib_cp, sizeof(calib_cp));
close(fd);
return ret;
}
int main(int argc, char *argv[])
{
char c;
int ret;
char *path = NULL;
unsigned int offset = 0;
unsigned int devid = 0;
int show_bin = 0, write = 0;
struct ft_calibration_raw calib;
while ((c = getopt(argc, argv, options)) != -1) {
switch (c) {
default:
case 'h':
fprintf(stderr, help_msg);
exit(EXIT_SUCCESS);
case 'D':
ret = sscanf(optarg, "0x%x", &devid);
if (ret != 1) {
fprintf(stderr,
"Invalid devid %s\n",
optarg);
exit(EXIT_FAILURE);
}
write = 1;
break;
case 'f':
path = optarg;
break;
case 'o':
ret = sscanf(optarg, "0x%x", &offset);
if (ret != 1) {
ret = sscanf(optarg, "%u", &offset);
if (ret != 1) {
fprintf(stderr,
"Invalid offset %s\n",
optarg);
exit(EXIT_FAILURE);
}
}
break;
case 'b':
show_bin = 1;
break;
}
}
if (!path) {
fputs("Calibration file is mandatory\n", stderr);
exit(EXIT_FAILURE);
}
/* Read EEPROM file */
ret = fau_calibration_read(path, &calib, offset);
if (ret < 0) {
fprintf(stderr, "Can't read calibration data from '%s'. %s\n",
path, strerror(errno));
exit(EXIT_FAILURE);
}
if (ret != sizeof(calib)) {
fprintf(stderr,
"Can't read all calibration data from '%s'. %s\n",
path, strerror(errno));
exit(EXIT_FAILURE);
}
/* Show calibration data*/
if (show_bin)
fau_calibration_dump_machine(&calib);
else if(!write)
fau_calibration_dump_human(&calib);
/* Write calibration data */
if (write) {
ret = fau_calibration_write(devid, &calib);
if (ret < 0) {
fprintf(stderr,
"Can't write calibration data to '0x%x'. %s\n",
devid, strerror(errno));
exit(EXIT_FAILURE);
}
if (ret != sizeof(calib)) {
fprintf(stderr,
"Can't write all calibration data to '0x%x'. %s\n",
devid, strerror(errno));
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2020 CERN
* Author: Federico Vaga <federico.vaga@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <glob.h>
#include <getopt.h>
static const char git_version[] = "git version: " GIT_VERSION;
static void help(char *name)
{
fprintf(stderr, "%s: Lists boards\n"
"\t-V print version\n"
"\t-V print help\n",
name);
}
static void print_version(char *name)
{
printf("%s %s\n", name, git_version);
}
int main(int argc, char **argv)
{
glob_t g;
int err, i;
char opt;
while ((opt = getopt(argc, argv, "hV")) != -1) {
switch (opt) {
case 'h':
case '?':
help(argv[0]);
exit(EXIT_SUCCESS);
break;
case 'V':
print_version(argv[0]);
exit(EXIT_SUCCESS);
}
}
err = glob("/dev/zio/tdc-1n5c-[A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9]-0-0-ctrl",
GLOB_NOSORT, NULL, &g);
if (err == GLOB_NOMATCH)
goto out_glob;
for (i = 0; i < g.gl_pathc; i++) {
uint32_t dev_id;
char dev_id_str[7]= "0x";
/* Keep only the ID */
strncpy(dev_id_str + 2,
g.gl_pathv[i] + strlen("/dev/zio/tdc-1n5c-"), 4);
dev_id = strtol(dev_id_str, NULL, 0);
printf(" FMC-TDC Device ID %04x\n", dev_id);
}
globfree(&g);
out_glob:
exit(err ? EXIT_FAILURE : EXIT_SUCCESS);
}
/*
* Copyright (C) 2014-2018 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <inttypes.h>
#include "test-common.h"
char git_version[] = "git version: " GIT_VERSION;
int main(int argc, char **argv)
{
int32_t offset;
int err, i, channel, ch_start = FMCTDC_CH_1, ch_end = FMCTDC_CH_LAST;
init(argc, argv);
check_help(argc, argv, 2,
"[-h] [-V] <device> <channel> [offset-ps]",
"It sets or gets the user-offset applied to the incoming timestamps\n",
"");
open_board(argv[1]);
if (argc >= 3) {
channel = atoi(argv[2]);
if (channel < FMCTDC_CH_1 || channel > FMCTDC_CH_LAST) {
fprintf(stderr, "%s: invalid channel.\n", argv[0]);
return -1;
}
ch_start = channel;
ch_end = channel;
}
if (argc >= 4) {
int ret = sscanf(argv[3], "%"SCNi32, &offset);
if (ret != 1) {
fprintf(stderr, "%s: invalid command.\n", argv[0]);
return -1;
}
err = fmctdc_set_offset_user(brd, channel, offset);
if (err) {
fprintf(stderr, "%s: error setting the user-offset: %s\n",
argv[0], strerror(errno));
return -1;
}
}
for (i = ch_start; i <= ch_end; i++) {
err = fmctdc_get_offset_user(brd, i, &offset);
if (err)
printf("channel %d: ERROR\n", i);
else
printf("channel %d: %d ps\n", i, offset);
}
return 0;
}
/*
* Copyright (c) 2014-2018 CERN
* Author: Federico Vaga <federico.vaga@cern.ch>
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <getopt.h>
#include "fmctdc-lib.h"
char git_version[] = "git_version: " GIT_VERSION;
/* Previous time stamp for each channel */
static unsigned int stop = 0;
enum tstamp_print_format
{
TSTAMP_FMT_PS = 0,
TSTAMP_FMT_WR,
};
/**
* Print help message
* @param[in] name program name
*/
static void help(char *name)
{
fprintf(stderr,
"%s -D <device> -c <channel> -n <number_of_samples>\n",
basename(name));
fprintf(stderr,
"reads timestamps from fmc-tdc channels, measures the readout performance & correctness.\n\n");
}
/**
* It stops timestamps readout after a signal
* @param[in] signum signal number
*/
static void termination_handler(int signum)
{
fprintf(stderr, "\nfmc-tdc-tstamp: killing application\n");
stop = 1;
}
static struct fmctdc_time prev_ts;
static int total_samples = 0;
uint64_t delta_min = 0xffffffffffffffffULL;
uint64_t delta_max = 0;
double avg_acc = 0.0;
int misses = 0;
typedef struct
{
int repeat;
uint64_t start_tics;
uint64_t timeout;
} timeout_t;
/* get monotonic number of useconds */
uint64_t get_monotonic_us(void)
{
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
return (uint64_t) tv.tv_sec * 1000000ULL +
(uint64_t) (tv.tv_nsec / 1000);
}
int tmo_init(timeout_t *tmo, uint32_t milliseconds, int repeat)
{
tmo->repeat = repeat;
tmo->start_tics = get_monotonic_us();
tmo->timeout = (uint64_t)milliseconds * 1000ULL;
return 0;
}
int tmo_expired(timeout_t *tmo)
{
uint64_t time = get_monotonic_us();
int expired = (time > tmo->start_tics + tmo->timeout);
if (tmo->repeat && expired)
while (time > tmo->start_tics + tmo->timeout)
tmo->start_tics += tmo->timeout;
return expired;
}
static int prev_fine = 0;
static int64_t avg_delta_d = 0;
static int delta_stab_count = 0;
static const int64_t max_delta_span = 2000;
static int delta_stable = 0;
void process_timestamps(struct fmctdc_time *ts, int n_ts)
{
int i;
for (i = 0; i < n_ts; i++) {
int fine = ts[i].debug & 0x1fff;
if (ts->seq_id < 10) /* skip first few ts, just to be sure
the buffer has been flushed */
continue;
if (total_samples > 0)
{
struct fmctdc_time delta;
delta = ts[i];
fmctdc_ts_sub(&delta, &ts[i], &prev_ts);
int64_t ps = fmctdc_ts_ps(&delta);
if ( (prev_ts.seq_id + 1) != ts[i].seq_id) {
fprintf(stderr,
"\n\nSuspicious timestamps (gap in sequence ids):\n");
fprintf(stderr,"Previous : "PRItswr"\n",
PRItswrVAL(&prev_ts));
fprintf(stderr,"Current : "PRItswr"\n",
PRItswrVAL(&ts[i]));
misses++;
} else {
int64_t avg_delta = (int64_t)((1e12 * avg_acc) / (double)total_samples);
int64_t delta_diff = abs(ps - avg_delta);
int curr_f_stable = (double)abs(avg_delta_d - avg_delta) < (double)(avg_delta / 100000.0);
if (curr_f_stable && delta_stab_count < 10000)
delta_stab_count++;
else if (!curr_f_stable && delta_stab_count > 10)
delta_stab_count-=10;
delta_stable = delta_stab_count > 8000;
avg_delta_d = avg_delta;
if( delta_stable ) {
if (ps < delta_min)
delta_min = ps;
else if (ps > delta_max)
delta_max = ps;
avg_delta_d = avg_delta;
if (delta_diff > max_delta_span) {
int frac_prev_from_acam = (prev_fine * 81 * 4096 / 8000) % 4096;
int frac_curr_from_acam = (fine * 81 * 4096 / 8000) % 4096;
int err_prev = frac_prev_from_acam - prev_ts.frac;
int err_curr = frac_curr_from_acam - ts[i].frac;
fprintf(stderr,
"\n\nSuspicious timestamps (span exceeded: current-previous = %"PRIi64" ps, average = %"PRIi64" ps, error = %"PRIi64" ps, threshold = %"PRIi64" ps):\n",
ps, avg_delta, delta_diff,
max_delta_span);
fprintf(stderr,
"Previous : "PRItswr", ACAM bins: %d (DMA timestamp vs RAW ACAM readout error: %d)\n",
PRItswrVAL(&prev_ts),
prev_fine,
err_prev);
fprintf(stderr,
"Current : "PRItswr", ACAM bins: %d (DMA timestamp vs RAW ACAM readout error: %d)\n",
PRItswrVAL(&ts[i]),
fine,
err_curr);
}
}
avg_acc += (double)ps / 1e12;
}
//printf("Miss: %d %d\n\n", i, total_samples);
}
prev_ts = ts[i];
prev_fine = fine;
total_samples++;
}
}
void display_stats()
{
if( delta_stable )
fprintf(stderr,
"Got %d timestamps so far (rate %.1f Hz, misses : %d, dmin %"PRIi64" dmax %"PRIi64" span %"PRIi64")... \r",
total_samples, 1.0 / (avg_acc / (double)total_samples),
misses, delta_min, delta_max, delta_max - delta_min);
else
fprintf(stderr,
"Got %d timestamps so far (input frequency UNSTABLE)... \r",
total_samples);
}
static timeout_t refresh_timeout;
int main(int argc, char **argv)
{
struct fmctdc_board *brd;
unsigned int dev_id = 0xFFFFFFFF;
struct fmctdc_time *ts;
int fd, ret, n_boards;
char opt;
struct sigaction new_action, old_action;
int ts_buf_size = 1024;
int channel = -1;
int nsamples = -1;
/* Set up the structure to specify the new action. */
new_action.sa_handler = termination_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGINT, &new_action, NULL);
atexit(fmctdc_exit);
/* Initialize FMC TDC library */
n_boards = fmctdc_init();
if (n_boards < 0)
{
fprintf(stderr, "%s: fmctdc_init(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Parse Options */
while ((opt = getopt(argc, argv, "D:c:n:h")) != -1)
{
switch (opt)
{
case 'D':
ret = sscanf(optarg, "0x%04x", &dev_id);
if (!ret)
{
help(argv[0]);
exit(EXIT_SUCCESS);
}
break;
case 'h':
case '?':
help(argv[0]);
exit(EXIT_SUCCESS);
break;
case 'n':
sscanf(optarg, "%d", &nsamples);
break;
case 'c':
sscanf(optarg, "%d", &channel);
break;
}
}
/* Open FMC TDC device */
brd = fmctdc_open(dev_id); /* look for dev_id form the beginning */
if (!brd)
{
if (dev_id == 0xFFFFFFFF)
fprintf(stderr, "Missing device identifier\n");
else
fprintf(stderr, "Can't open device 0x%x: %s\n", dev_id,
strerror(errno));
exit(EXIT_FAILURE);
}
if (channel < 0)
{
fprintf(stderr, "Channel number expected\n");
exit(EXIT_FAILURE);
}
if (nsamples < 0)
{
fprintf(stderr, "Number of samples expected\n");
exit(EXIT_FAILURE);
}
ret = fmctdc_flush(brd, channel);
if (ret)
fprintf(stderr,
"fmc-tdc-tstamp: failed to flush channel %d: %s\n",
FMCTDC_NUM_CHANNELS, fmctdc_strerror(errno));
fd = fmctdc_fileno_channel(brd, channel);
ret = fmctdc_channel_enable(brd, channel);
if (ret)
fprintf(stderr,
"%s: chan %d: cannot enable acquisition: %s.\n",
argv[0], channel, fmctdc_strerror(errno));
/* Read Time-Stamps */
ts = calloc(ts_buf_size, sizeof(*ts));
if (!ts)
{
fprintf(stderr, "%s: cannot allocate memory\n", argv[0]);
goto out;
}
tmo_init(&refresh_timeout, 1000, 1);
fprintf(stderr,
"WARNING!!! Please connect a pulse source of a stable frequency to the selected input.\n\n");
fprintf(stderr,
"Reading & checking %d samples...\n",
nsamples);
while (nsamples > 0)
{
int to_read = nsamples > ts_buf_size ? ts_buf_size : nsamples;
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN | POLLERR;
if (stop)
break;
ret = poll(&pfd, 1, 10);
if (ret <= 0)
continue;
if (!(pfd.revents & POLLIN))
continue;
int n_ts = fmctdc_read(brd, channel, ts, to_read, 0);
if (n_ts == 0) /* no timestamp */
continue;
// fprintf(stderr,"Got %d\n", n_ts);
process_timestamps(ts, n_ts);
if (tmo_expired(&refresh_timeout))
{
display_stats();
}
nsamples -= n_ts;
}
fprintf(stderr, "\n");
free(ts);
out:
ret = fmctdc_channel_disable(brd, channel);
fmctdc_close(brd);
exit(EXIT_SUCCESS);
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2018 CERN
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "test-common.h"
char git_version[] = "git version: " GIT_VERSION;
int main(int argc, char **argv)
{
init(argc, argv);
check_help(argc, argv, 2,
"[-h] [-V] <device>",
"Displays current temperature of the mezzanine.\n", "");
open_board(argv[1]);
printf("%.1f deg C\n", fmctdc_read_temperature(brd));
return 0;
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2018 CERN
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "test-common.h"
char git_version[] = "git version: " GIT_VERSION;
int main(int argc, char **argv)
{
init(argc, argv);
check_help(argc, argv, 2,
"[-h] [-V] <device> <channel> [on/off]",
"Enables or disables the 50 Ohm termination of a given input channel.\n"
"No on/off command returns the current state of termination resistor.",
"");;
open_board(argv[1]);
if (argc == 2) {
int i;
for (i = FMCTDC_CH_1; i <= FMCTDC_CH_LAST; i++)
printf("channel %d: 50 Ohm termination is %s\n", i,
fmctdc_get_termination(brd, i) ? "on" : "off");
return 0;
}
int channel = atoi(argv[2]);
if (channel < FMCTDC_CH_1 || channel > FMCTDC_CH_LAST) {
fprintf(stderr, "%s: invalid channel.\n", argv[0]);
return -1;
}
if (argc >= 4) {
int term_on;
if (!strcasecmp(argv[3], "on"))
term_on = 1;
else if (!strcasecmp(argv[3], "off"))
term_on = 0;
else {
fprintf(stderr, "%s: invalid command.\n", argv[0]);
return -1;
}
if (fmctdc_set_termination(brd, channel, term_on) < 0) {
fprintf(stderr, "%s: error setting termination: %s\n",
argv[0], strerror(errno));
return -1;
}
}
printf("channel %d: 50 Ohm termination is %s\n", channel,
fmctdc_get_termination(brd, channel) ? "on" : "off");
return 0;
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2018 CERN
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "test-common.h"
char git_version[] = "git version: " GIT_VERSION;
void perror_hint ( const char *func )
{
perror(func);
fprintf(stderr, "Hint: are trying to change time while acquisition is enabled?\n");
}
int main(int argc, char **argv)
{
struct fmctdc_time ts;
char *cmd;
init(argc, argv);
check_help(argc, argv, 3,
"[-h] [-V] <device> <command> [timeval]",
"Gets/sets the mezzanine TAI time and controls White Rabbit timing.",
"Commands are:\n"
" get - shows current time and White Rabbit status.\n"
" set <seconds> - sets current board time.\n"
" local - sets the time source to the card's local oscillator.\n"
" wr - sets the time source to White Rabbit.\n"
" host - sets the time source to local oscillator and coarsely\n"
" synchronizes the card to the system clock.\n");
open_board(argv[1]);
cmd = argv[2];
if (!strcmp(cmd, "get")) {
if (fmctdc_get_time(brd, &ts) < 0) {
perror("fmctdc_get_time()");
return -1;
}
int err = fmctdc_check_wr_mode(brd);
printf("WR Status: ");
switch(err)
{
case ENODEV: printf("disabled.\n"); break;
case ENOLINK: printf("link down.\n"); break;
case EAGAIN: printf("synchronization in progress.\n"); break;
case 0: printf("synchronized.\n"); break;
default: printf("error: %s\n", strerror(err)); break;
}
printf("Current TAI time is %llu.%09u s\n", (unsigned long long) ts.seconds,
ts.coarse * 8);
} else if (!strcmp(cmd, "set")) {
if (argc < 4) {
fprintf(stderr, "%s: time value expected\n", argv[0]);
}
ts.coarse = 0;
ts.seconds = atoi(argv[3]);
if (fmctdc_set_time(brd, &ts) < 0) {
perror_hint("fmctdc_set_time()");
return -1;
}
} else if (!strcmp(cmd, "host")) {
if (fmctdc_set_host_time(brd) < 0) {
perror_hint("fmctdc_set_host_time()");
return -1;
}
} else if (!strcmp(cmd, "wr")) {
int err = fmctdc_wr_mode(brd, 1);
if(err == ENOTSUP)
{
fprintf(stderr, "%s: no support for White Rabbit (check the gateware).\n",
argv[0]);
exit(1);
} else if (err) {
perror_hint("fmctdc_wr_mode()");
exit(1);
}
setbuf(stdout, NULL);
printf("Locking the card to WR: ");
while ((err = fmctdc_check_wr_mode(brd)) != 0) {
if( err == ENOLINK ) {
fprintf(stderr, "\n%s: no White Rabbit link (check the cable and the switch).\n",
argv[0]);
return -1;
}
printf(".");
sleep(1);
}
printf(" locked!\n");
} else if (!strcmp(cmd, "local"))
{
int err = fmctdc_wr_mode(brd, 0);
if(err < 0) {
perror_hint("fmctdc_wr_mode()");
return -1;
}
return 0;
} else {
fprintf(stderr, "%s: unrecognized command.\n", cmd);
return -1;
}
return 0;
}
/*
* Copyright (c) 2014-2018 CERN
* Author: Federico Vaga <federico.vaga@cern.ch>
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>
#include <poll.h>
#include <signal.h>
#include <getopt.h>
#include "fmctdc-lib.h"
char git_version[] = "git_version: " GIT_VERSION;
/* Previous time stamp for each channel */
static unsigned int stop = 0, fmt_wr = 0;
enum tstamp_print_format {
TSTAMP_FMT_PS = 0,
TSTAMP_FMT_WR,
};
/**
* It prints the given timestamp
* @param[in] ts timestamp
* @param[in] fmt timestamp print format
*/
static void print_ts(struct fmctdc_time ts, enum tstamp_print_format fmt)
{
switch (fmt) {
case TSTAMP_FMT_WR:
fprintf(stdout, PRItswr, PRItswrVAL(&ts));
break;
case TSTAMP_FMT_PS:
fprintf(stdout, PRItsps, PRItspsVAL(&ts));
break;
default:
fprintf(stdout, "--- invalid format ---\n");
break;
}
}
void dump(unsigned int ch, struct fmctdc_time *ts)
{
static struct fmctdc_time ts_prev_lst[FMCTDC_NUM_CHANNELS] = {{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0}};
struct fmctdc_time ts_tmp;
uint64_t ns;
double s, hz;
fprintf(stdout,
"channel %u | channel seq %-12u\n ts ",
ch, ts->seq_id);
print_ts(*ts, fmt_wr);
fprintf(stdout, "\n");
/* We are in normal mode, calculate the difference */
fmctdc_ts_sub(&ts_tmp, ts, &ts_prev_lst[ch]);
fprintf(stdout, " diff ");
print_ts(ts_tmp, fmt_wr);
ns = (uint64_t) ts_tmp.coarse * 8ULL;
ns += (uint64_t) (ts_tmp.frac * 8000ULL / 4096ULL) / 1000ULL;
s = ts_tmp.seconds + ((double)ns/1000000000ULL);
hz = 1/s;
fprintf(stdout, " [%f Hz]\n", hz);
ts_prev_lst[ch] = *ts;
}
/* We could use print_version from test-common.c, but to avoid creating
* dependencies use local copy */
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
printf("%s\n", libfmctdc_version_s);
printf("%s\n", libfmctdc_zio_version_s);
}
enum tstamp_testing_modes {
TST_MODE_1 = 1,
__TST_MODE_MAX,
};
/**
* Print help message
* @param[in] name program name
*/
static void help(char *name)
{
fprintf(stderr, "%s [options] -D <device_id> -L <cern-lun> [channels]\n",
basename(name));
fprintf(stderr,
"reads timestamps from fmc-tdc channels. No [channels] means all channels.\n\n");
fprintf(stderr, "Options are:\n");
fprintf(stderr, " -D : device identifier in hex, e.g. 0x1234\n");
fprintf(stderr, " -L : CERN LUN number\n");
fprintf(stderr, " -n : non-blocking mode\n");
fprintf(stderr, " -s n_samples: dump 'n_samples' timestamps\n");
fprintf(stderr, " -w : user White Rabbit format\n");
fprintf(stderr, " -f: flush buffer\n");
fprintf(stderr, " -r: read buffer, no acquisition start\n");
fprintf(stderr, " -m: buffer mode: 'fifo' or 'circ'\n");
fprintf(stderr, " -l: maximum buffer lenght\n");
fprintf(stderr, " -S n_samples: output decimation, number of samples to skip\n");
fprintf(stderr, " -h: print this message\n\n");
fprintf(stderr, " -V: print version info\n\n");
fprintf(stderr, " -t <mode>: It does some test of the incoming timestampts\n\n");
fprintf(stderr, " -o <ms>: IRQ coalescing milleseconds timeout\n\n");
fprintf(stderr, " -e: stop on error\n\n");
fprintf(stderr, " -a <ch> Enable raw-timestamps\n\n");
fprintf(stderr, " channels enumerations go from %d to %d \n\n",
FMCTDC_CH_1, FMCTDC_CH_LAST);
fprintf(stderr, " Testing Modes\n");
fprintf(stderr, " Following a list of testing modes. When running in testing mode the program will run a validation routine and on failure it will stop the timestamp acquisition. All tests assumes frequncy in range [1Hz, 1MHz]\n\n");
fprintf(stderr, " %d: on channel %d the sequence number grows +1 and the timestamps are always in the future. This means that only channel one must be connected.\n",
TST_MODE_1, FMCTDC_CH_1);
}
/**
* It stops timestamps readout after a signal
* @param[in] signum signal number
*/
static void termination_handler(int signum)
{
fprintf(stderr, "\nfmc-tdc-tstamp: killing application\n");
stop = 1;
}
static int tstamp_testing_mode_1(struct fmctdc_time *ts,
unsigned int chan,
unsigned int n)
{
/* any previous timestamp , that's why I use static */
static struct fmctdc_time ts_prev_lst[FMCTDC_NUM_CHANNELS] = {{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0},
{0, 0, 0, -1, 0}};
struct fmctdc_time *ts_tmp;
uint64_t ns_p, ns_c;
int i;
ts_tmp = &ts_prev_lst[chan];
for (i = 0; i < n; *ts_tmp = ts[i], ++i) {
if (ts_tmp->seq_id == -1)
continue;
if (ts_tmp->seq_id + 1 != ts[i].seq_id && ts[i].seq_id != 0) {
fprintf(stderr,
"*** Invalid sequence number. Previous %u, current %u, expected +1\n",
ts_tmp->seq_id, ts[i].seq_id);
goto err;
}
ns_p = fmctdc_ts_approx_ns(ts_tmp);
ns_c = fmctdc_ts_approx_ns(&ts[i]);
if (ns_p >= ns_c) {
fprintf(stderr,
"*** Invalid timestamp. Previous %d %"PRIu64"ns, current %d %"PRIu64"ns current one should be greater\n",
ts_tmp->seq_id, ns_p, ts[i].seq_id, ns_c);
goto err;
}
}
return 0;
err:
*ts_tmp = ts[n - 1];
return -EINVAL;
}
static int tstamp_testing_mode(struct fmctdc_time *ts,
unsigned int chan,
unsigned int n,
enum tstamp_testing_modes mode)
{
int err = 0;
switch (mode) {
case TST_MODE_1:
err = tstamp_testing_mode_1(ts, chan, n);
break;
default:
break;
}
return err;
}
#define FMCTDC_CFG_VALID (1 << 0)
struct fmctdc_config_chan {
unsigned long flags;
enum fmctdc_ts_mode mode;
};
int main(int argc, char **argv)
{
struct fmctdc_board *brd;
unsigned int dev_id = 0xFFFFFFFF;
unsigned int lun = 0xFFFFFFFF;
struct fmctdc_time *ts;
int channels[FMCTDC_NUM_CHANNELS];
int chan_count = 0, i, n, ch, fd, n_ts, ret, n_boards;
int nblock = 0, buflen = 1000000;
enum fmctdc_buffer_mode bufmode = FMCTDC_BUFFER_FIFO;
int n_samples = -1;
unsigned int n_show = 1;
int flush = 0, read = 0;
char opt;
struct sigaction new_action, old_action;
int ch_valid[FMCTDC_NUM_CHANNELS] = {0, 1, 2, 3, 4};
struct pollfd p[FMCTDC_NUM_CHANNELS];
enum tstamp_testing_modes mode = 0;
int timeout_ms = -1;
int stop_on_err = 0;
struct fmctdc_config_chan ch_cfg[FMCTDC_NUM_CHANNELS];
int tmp;
/* Set up the structure to specify the new action. */
new_action.sa_handler = termination_handler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction (SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction (SIGINT, &new_action, NULL);
atexit(fmctdc_exit);
/* Initialize FMC TDC library */
n_boards = fmctdc_init();
if (n_boards < 0) {
fprintf(stderr, "%s: fmctdc_init(): %s\n", argv[0],
strerror(errno));
exit(1);
}
for (i = 0; i < FMCTDC_NUM_CHANNELS; ++i) {
ch_cfg[i].flags = 0;
ch_cfg[i].mode = FMCTDC_TS_MODE_POST;
}
/* Parse Options */
while ((opt = getopt(argc, argv, "D:hwns:frm:l:L:c:VS:t:o:ea:")) != -1) {
switch (opt) {
case 'D':
ret = sscanf(optarg, "0x%04x", &dev_id);
if (!ret) {
help(argv[0]);
exit(EXIT_SUCCESS);
}
break;
case 'L':
ret = sscanf(optarg, "%u", &lun);
if (!ret) {
help(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 'h':
case '?':
help(argv[0]);
exit(EXIT_SUCCESS);
break;
case 'V':
print_version(argv[0]);
exit(EXIT_SUCCESS);
case 'f':
flush = 1;
break;
case 'r':
read = 1;
break;
case 'm':
if (strcmp(optarg, "fifo") == 0) {
bufmode = FMCTDC_BUFFER_FIFO;
} else if (strcmp(optarg, "circ") == 0) {
bufmode = FMCTDC_BUFFER_CIRC;
} else {
help(argv[0]);
exit(EXIT_SUCCESS);
}
break;
case 'l':
sscanf(optarg, "%i", &buflen);
break;
case 's':
sscanf(optarg, "%i", &n_samples);
break;
case 'n':
nblock = 1;
break;
case 'w':
fmt_wr = 1;
break;
case 'e':
stop_on_err = 1;
break;
case 'c':
if (chan_count >= FMCTDC_NUM_CHANNELS) {
fprintf(stderr,
"%s: too many channels, maximum %d\n",
argv[0], FMCTDC_NUM_CHANNELS);
break;
}
ret = sscanf(optarg, "%i", &tmp);
if (ret != 1 || tmp >= FMCTDC_NUM_CHANNELS) {
fprintf(stderr, "%s: invalid channel number %i\n",
argv[0], tmp);
help(argv[0]);
exit(EXIT_FAILURE);
}
ch_valid[chan_count++] = tmp;
ch_cfg[tmp].flags |= FMCTDC_CFG_VALID;
break;
case 'S':
sscanf(optarg, "%u", &n_show);
if (n_show == 0) {
fprintf(stderr, "%s: invalid 'n_show', min 1\n", argv[0]);
help(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 't':
sscanf(optarg, "%u", &mode);
if (mode < TST_MODE_1 || mode > __TST_MODE_MAX) {
fprintf(stderr, "%s: invalid test mode %i\n", argv[0], mode);
help(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 'o':
ret = sscanf(optarg, "%i", &timeout_ms);
if (ret != 1) {
fprintf(stderr, "%s: invalid IRQ coalescing timeout %s\n",
argv[0], optarg);
help(argv[0]);
exit(EXIT_FAILURE);
}
break;
case 'a':
ret = sscanf(optarg, "%d", &tmp);
if (ret != 1) {
fprintf(stderr, "Missing argument\n");
help(argv[0]);
exit(EXIT_FAILURE);
}
ch_cfg[tmp].mode = FMCTDC_TS_MODE_RAW;
break;
}
}
if (dev_id == 0xFFFFFFFF && lun == dev_id) {
fprintf(stderr, "Missing device identifier or CENR LUN\n");
exit(EXIT_FAILURE);
}
/* Open FMC TDC device */
if (dev_id != 0xFFFFFFFF) {
brd = fmctdc_open(dev_id);
if (!brd) {
fprintf(stderr, "Can't open device id 0x%x: %s\n",
dev_id, strerror(errno));
exit(EXIT_FAILURE);
}
} else {
brd = fmctdc_open_by_lun(lun);
if (!brd) {
fprintf(stderr, "Can't open device lun %u: %s\n",
lun, strerror(errno));
exit(EXIT_FAILURE);
}
}
/* Open Channels from command line */
memset(channels, 0, sizeof(channels));
memset(p, 0, sizeof(p));
if (!chan_count) {
chan_count = FMCTDC_NUM_CHANNELS;
for (i = 0; i < FMCTDC_NUM_CHANNELS; i++)
ch_cfg[i].flags |= FMCTDC_CFG_VALID;
}
for (i = 0; i < FMCTDC_NUM_CHANNELS; i++) {
if ((ch_cfg[i].flags & FMCTDC_CFG_VALID) == 0)
continue;
ret = fmctdc_ts_mode_set(brd, i, ch_cfg[i].mode);
if (ret) {
fprintf(stderr,
"%s: chan %d: cannot set time-stamp mode: %s.\n",
argv[0], i, fmctdc_strerror(errno));
}
}
for (i = 0; i < chan_count; i++) {
ch = ch_valid[i];
if (flush) {
ret = fmctdc_flush(brd, ch);
if (ret)
fprintf(stderr,
"fmc-tdc-tstamp: failed to flush channel %d: %s\n",
ch, fmctdc_strerror(errno));
}
channels[ch] = fmctdc_fileno_channel(brd, ch);
p[ch].fd = channels[ch];
p[ch].events = POLLIN | POLLERR;
if (timeout_ms > 0) {
ret = fmctdc_coalescing_timeout_set(brd, ch, timeout_ms);
if (ret) {
fprintf(stderr,
"%s: chan %d: cannot set IRQ coalescing timeout: %s. Use default\n",
argv[0], ch, fmctdc_strerror(errno));
}
}
/* set buffer mode */
ret = fmctdc_set_buffer_mode(brd, ch, bufmode);
if (ret) {
fprintf(stderr,
"%s: chan %d: cannot set buffer mode: %s. Use default\n",
argv[0], ch, fmctdc_strerror(errno));
}
/* set buffer lenght */
ret = fmctdc_set_buffer_len(brd, ch, buflen);
if (ret) {
fprintf(stderr,
"%s: chan %d: cannot set buffer lenght: %s. Use default\n",
argv[0], ch, fmctdc_strerror(errno));
}
if (!read)
ret = fmctdc_channel_enable(brd, ch);
if (ret)
fprintf(stderr,
"%s: chan %d: cannot enable acquisition: %s.\n",
argv[0], i, fmctdc_strerror(errno));
}
/* Read Time-Stamps */
ts = calloc(n_show, sizeof(*ts));
if (!ts) {
fprintf(stderr, "%s: cannot allocate memory\n", argv[0]);
goto out;
}
n = 0;
while ((n < n_samples || n_samples <= 0) && (!stop)) {
if (!nblock) {
ret = poll(p, FMCTDC_NUM_CHANNELS, 10);
if (ret <= 0)
continue;
}
/* Now we can read the timestamp */
for (i = 0; i < chan_count; i++) {
unsigned int chan = ch_valid[i];
fd = channels[chan];
if (fd < 0)
continue;
if (!(p[chan].revents & POLLIN))
continue;
n_ts = fmctdc_read(brd, chan, ts, n_show,
nblock ? O_NONBLOCK : 0);
if (n_ts < 0)
goto err_acq;
if (n_ts == 0) /* no timestamp */
continue;
ret = tstamp_testing_mode(ts, chan, n_ts, mode);
if (ret && stop_on_err)
stop = 1;
if (n % n_show == 0)
dump(chan, &ts[0]);
n += n_ts;
}
}
err_acq:
free(ts);
out:
/* Restore default time-stamping */
for (i = 0; i <= FMCTDC_CH_LAST; i++) {
if (!read)
ret = fmctdc_channel_disable(brd, i);
if (ret)
fprintf(stderr,
"%s: chan %d: cannot disable acquisition: %s.\n",
argv[0], i, fmctdc_strerror(errno));
}
fmctdc_close(brd);
exit(EXIT_SUCCESS);
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2018 CERN
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "test-common.h"
extern char git_version[];
int n_boards;
struct fmctdc_board *brd = NULL;
void open_board(char *dev_id_str)
{
unsigned int dev_id;
if (sscanf(dev_id_str, "%04x", &dev_id) != 1) {
fprintf(stderr, "Error parsing device ID %s\n", dev_id_str);
exit(-1);
}
brd = fmctdc_open(dev_id);
if (!brd) {
fprintf(stderr, "Can't open device %s: %s\n", dev_id_str,
strerror(errno));
exit(-1);
}
}
static void print_version(char *pname)
{
printf("%s %s\n", pname, git_version);
printf("%s\n", libfmctdc_version_s);
printf("%s\n", libfmctdc_zio_version_s);
}
void check_help(int argc, char **argv, int min_args, char *usage, char *desc,
char *options)
{
if (argc >= 2 && !strcmp(argv[1], "-h")) {
printf("%s: %s\n", argv[0], desc);
printf("usage: %s %s\n", argv[0], usage);
printf("%s\n", options);
exit(0);
} else if ((argc >= 2) && (!strcmp(argv[1], "-V"))) {
print_version(argv[0]);
exit(0);
} else if (argc < min_args) {
printf("usage: %s %s\n", argv[0], usage);
exit(0);
}
}
static void cleanup()
{
if (brd)
fmctdc_close(brd);
fmctdc_exit();
}
void init(int argc, char *argv[])
{
n_boards = fmctdc_init();
if (n_boards < 0) {
fprintf(stderr, "%s: fmctdc_init(): %s\n", argv[0],
strerror(errno));
exit(1);
}
atexit(cleanup);
}
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Copyright (c) 2014-2018 CERN
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef __TEST_COMMON_H
#define __TEST_COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include "fmctdc-lib.h"
#include "fmctdc-lib-private.h" /* for some extra debugging stuff */
extern int n_boards;
extern struct fmctdc_board *brd;
void usage_msg(const char *name, const char *msg);
void open_board(char *dev_id_str);
void check_help(int argc, char **argv, int min_args, char *usage, char *desc,
char *options);
void init(int argc, char *argv[]);
#endif
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