Commit 8156f0e1 authored by Tomasz Wlostowski's avatar Tomasz Wlostowski

Tom's initial version of the driver.

Done as the evil twin brother of the fine-delay driver. Still a bit incomplete
and lacking documentation. Supports only SPEC carrier so far.
parent d2fe4e12
*.o
*.ko
*~
*.mod.c
/.tmp_versions
.*cmd
modules.order
Module.symvers
\ No newline at end of file
[submodule "zio"]
path = zio
url = git://ohwr.org/misc/zio.git
[submodule "fmc-bus"]
path = fmc-bus
url = git://ohwr.org/fmc-projects/fmc-bus.git
all:
$(MAKE) -C drivers
$(MAKE) -C lib
$(MAKE) -C test
.PHONY: all clean
.PHONY: all clean modules install modules_install
.PHONY: gitmodules prereq prereq_install prereq_install_warn
clean:
$(MAKE) clean -C drivers
$(MAKE) clean -C lib
$(MAKE) clean -C test
DIRS = kernel lib
all clean modules install modules_install: gitmodules
for d in $(DIRS); do $(MAKE) -C $$d $@ || exit 1; done
@if echo $@ | grep -q install; then $(MAKE) prereq_install_warn; fi
all modules: prereq
# a hack, to prevent compiling wr-nic.ko, which won't work on older kernels
CONFIG_WR_NIC=n
export CONFIG_WR_NIC
#### The following targets are used to manage prerequisite repositories
gitmodules:
@test -d fmc-bus/doc || echo "Checking out submodules"
@test -d fmc-bus/doc || git submodule update --init
@git submodule update
# The user can override, using environment variables, all these three:
FMC_BUS ?= fmc-bus
ZIO ?= zio
SUBMOD = $(FMC_BUS) $(ZIO)
prereq:
for d in $(SUBMOD); do $(MAKE) -C $$d || exit 1; done
prereq_install_warn:
@test -f .prereq_installed || \
echo -e "\n\n\tWARNING: Consider \"make prereq_install\"\n"
prereq_install:
for d in $(SUBMOD); do $(MAKE) -C $$d modules_install || exit 1; done
touch .prereq_installed
Introduction
---
WARNING!
In electronic instrumentation and signal processing, a time to digital con-
verter (abbreviated TDC) is a device for recognizing events and providing
a digital representation of the time they occurred. At this case, the FMC
TDC outputs the time of arrival for each incoming pulse.
This is work in progress. It is buggy and can be incomplete!
This is the project to provide support of the FMC TDC board in the
Linux kernel plugged to a SPEC carrier board.
Please check doc/
The project's aim is to provide an loadable module to be used along with this
board for the latest Linux kernel versions. The driver relies on ZIO frame-
work and the FMC bus dependencies, which are other projects hosted in
OHWR.
This version is known to compile and run with kernels 3.3 onwards.
License
---
Unless otherwise indicated, all these files of this project are under GPLv2
only.
If you want a copy of this license, it is provided in the COPYING file or write
to:
Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA.
asking for a written copy of the license.
Documentation
---
For further information, you have a more updated version of the documentation
under the doc/ directory.
Just go there and execute "make". You will need "texlive" and "doxygen" packages
installed on your system in order to generate the documentation files.
Links
---
* FMC TDC HW development website: http://www.ohwr.org/projects/fmc-tdc
* FMC TDC driver support website: http://www.ohwr.org/projects/fmc-tdc-sw
* ZIO framework website: http://www.ohwr.org/projects/zio
* SPEC driver support website: http://www.ohwr.org/projects/spec-sw
* FMC bus driver support website: http://www.ohwr.org/projects/fmc-bus
Contact
---
Mailing list: http://lists.ohwr.org/sympa/info/fmc-tdc-sw
FMC TDC driver support website: http://www.ohwr.org/projects/fmc-tdc-sw
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).
all: fmc-tdc.pdf doxygen-doc
fmc-tdc.pdf: fmc-tdc.tex
texi2pdf $<
doxygen-doc:
rm -rf fmc-tdc-doxygen
mkdir -p fmc-tdc-doxygen
./doxy.sh -n"FMCTDC Device Driver" -o "fmc-tdc-doxygen" ../lib/libtdc.h
clean:
rm -f *.pdf *.out *.toc *.aux *.log
rm -rf fmc-tdc-doxygen
all:
# Doxyfile 1.5.8
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project
#
# All text after a hash (#) is considered a comment and will be ignored
# The format is:
# TAG = value [value, ...]
# For lists items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (" ")
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file
# that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# http://www.gnu.org/software/libiconv for the list of possible encodings.
DOXYFILE_ENCODING = UTF-8
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
# by quotes) that should identify the project.
PROJECT_NAME = "__MAGIC_PROJECT_NAME__"
# The PROJECT_NUMBER tag can be used to enter a project or revision number.
# This could be handy for archiving the generated documentation or
# if some version control system is used.
PROJECT_NUMBER =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
# If a relative path is entered, it will be relative to the location
# where doxygen was started. If left blank the current directory will be used.
OUTPUT_DIRECTORY = "__MAGIC_OUTPUT_DIRECTORY__"
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
# 4096 sub-directories (in 2 levels) under the output directory of each output
# format and will distribute the generated files over these directories.
# Enabling this option can be useful when feeding doxygen a huge amount of
# source files, where putting all generated files in the same directory would
# otherwise cause performance problems for the file system.
CREATE_SUBDIRS = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# The default language is English, other supported languages are:
# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
# Spanish, Swedish, and Ukrainian.
OUTPUT_LANGUAGE = English
# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
# include brief member descriptions after the members that are listed in
# the file and class documentation (similar to JavaDoc).
# Set to NO to disable this.
BRIEF_MEMBER_DESC = YES
# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
# the brief description of a member or function before the detailed description.
# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
REPEAT_BRIEF = YES
# This tag implements a quasi-intelligent brief description abbreviator
# that is used to form the text in various listings. Each string
# in this list, if found as the leading text of the brief description, will be
# stripped from the text and the result after processing the whole list, is
# used as the annotated text. Otherwise, the brief description is used as-is.
# If left blank, the following values are used ("$name" is automatically
# replaced with the name of the entity): "The $name class" "The $name widget"
# "The $name file" "is" "provides" "specifies" "contains"
# "represents" "a" "an" "the"
ABBREVIATE_BRIEF =
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# Doxygen will generate a detailed section even if there is only a brief
# description.
ALWAYS_DETAILED_SEC = YES
# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
# path before files name in the file list and in the header files. If set
# to NO the shortest path that makes the file name unique will be used.
FULL_PATH_NAMES = NO
# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
# can be used to strip a user-defined part of the path. Stripping is
# only done if one of the specified strings matches the left-hand part of
# the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the
# path to strip.
STRIP_FROM_PATH =
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
# the path mentioned in the documentation of a class, which tells
# the reader which header file to include in order to use a class.
# If left blank only the name of the header file containing the class
# definition is used. Otherwise one should specify the include paths that
# are normally passed to the compiler using the -I flag.
STRIP_FROM_INC_PATH =
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
# (but less readable) file names. This can be useful is your file systems
# doesn't support long names like on DOS, Mac, or CD-ROM.
SHORT_NAMES = NO
# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
# will interpret the first line (until the first dot) of a JavaDoc-style
# comment as the brief description. If set to NO, the JavaDoc
# comments will behave just like regular Qt-style comments
# (thus requiring an explicit @brief command for a brief description.)
JAVADOC_AUTOBRIEF = NO
# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
# interpret the first line (until the first dot) of a Qt-style
# comment as the brief description. If set to NO, the comments
# will behave just like regular Qt-style comments (thus requiring
# an explicit \brief command for a brief description.)
QT_AUTOBRIEF = NO
# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
# treat a multi-line C++ special comment block (i.e. a block of //! or ///
# comments) as a brief description. This used to be the default behaviour.
# The new default is to treat a multi-line C++ comment block as a detailed
# description. Set this tag to YES if you prefer the old behaviour instead.
MULTILINE_CPP_IS_BRIEF = NO
# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
# member inherits the documentation from any documented member that it
# re-implements.
INHERIT_DOCS = YES
# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
# a new page for each member. If set to NO, the documentation of a member will
# be part of the file/class/namespace that contains it.
SEPARATE_MEMBER_PAGES = NO
# The TAB_SIZE tag can be used to set the number of spaces in a tab.
# Doxygen uses this value to replace tabs by spaces in code fragments.
TAB_SIZE = 8
# This tag can be used to specify a number of aliases that acts
# as commands in the documentation. An alias has the form "name=value".
# For example adding "sideeffect=\par Side Effects:\n" will allow you to
# put the command \sideeffect (or @sideeffect) in the documentation, which
# will result in a user-defined paragraph with heading "Side Effects:".
# You can put \n's in the value part of an alias to insert newlines.
ALIASES =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
# sources only. Doxygen will then generate output that is more tailored for C.
# For instance, some of the names that are used will be different. The list
# of all members will be omitted, etc.
OPTIMIZE_OUTPUT_FOR_C = YES
# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
# sources only. Doxygen will then generate output that is more tailored for
# Java. For instance, namespaces will be presented as packages, qualified
# scopes will look different, etc.
OPTIMIZE_OUTPUT_JAVA = NO
# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources only. Doxygen will then generate output that is more tailored for
# Fortran.
OPTIMIZE_FOR_FORTRAN = NO
# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
# sources. Doxygen will then generate output that is tailored for
# VHDL.
OPTIMIZE_OUTPUT_VHDL = NO
# Doxygen selects the parser to use depending on the extension of the files it parses.
# With this tag you can assign which parser to use for a given extension.
# Doxygen has a built-in mapping, but you can override or extend it using this tag.
# The format is ext=language, where ext is a file extension, and language is one of
# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C
EXTENSION_MAPPING =
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should
# set this tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
# func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
BUILTIN_STL_SUPPORT = NO
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
CPP_CLI_SUPPORT = NO
# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
# Doxygen will parse them like normal C++ but will assume all classes use public
# instead of private inheritance when no explicit protection keyword is present.
SIP_SUPPORT = NO
# For Microsoft's IDL there are propget and propput attributes to indicate getter
# and setter methods for a property. Setting this option to YES (the default)
# will make doxygen to replace the get and set methods by a property in the
# documentation. This will only work if the methods are indeed getting or
# setting a simple type. If this is not the case, or you want to show the
# methods anyway, you should set this option to NO.
IDL_PROPERTY_SUPPORT = YES
# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES, then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
DISTRIBUTE_GROUP_DOC = NO
# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
# the same type (for instance a group of public functions) to be put as a
# subgroup of that type (e.g. under the Public Functions section). Set it to
# NO to prevent subgrouping. Alternatively, this can be done per class using
# the \nosubgrouping command.
SUBGROUPING = YES
# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
# is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
# namespace, or class. And the struct will be named TypeS. This can typically
# be useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
TYPEDEF_HIDES_STRUCT = NO
# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
# determine which symbols to keep in memory and which to flush to disk.
# When the cache is full, less often used symbols will be written to disk.
# For small to medium size projects (<1000 input files) the default value is
# probably good enough. For larger projects a too small cache size can cause
# doxygen to be busy swapping symbols to and from disk most of the time
# causing a significant performance penality.
# If the system has enough physical memory increasing the cache will improve the
# performance by keeping more symbols in memory. Note that the value works on
# a logarithmic scale so increasing the size by one will rougly double the
# memory usage. The cache size is given by this formula:
# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
# corresponding to a cache size of 2^16 = 65536 symbols
SYMBOL_CACHE_SIZE = 0
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
# documentation are documented, even if no documentation was available.
# Private class members and static file members will be hidden unless
# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
EXTRACT_ALL = YES
# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
# will be included in the documentation.
EXTRACT_PRIVATE = NO
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
EXTRACT_STATIC = NO
# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
# defined locally in source files will be included in the documentation.
# If set to NO only classes defined in header files are included.
EXTRACT_LOCAL_CLASSES = YES
# This flag is only useful for Objective-C code. When set to YES local
# methods, which are defined in the implementation section but not in
# the interface are included in the documentation.
# If set to NO (the default) only methods in the interface are included.
EXTRACT_LOCAL_METHODS = NO
# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base
# name of the file that contains the anonymous namespace. By default
# anonymous namespace are hidden.
EXTRACT_ANON_NSPACES = NO
# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
# undocumented members of documented classes, files or namespaces.
# If set to NO (the default) these members will be included in the
# various overviews, but no documentation section is generated.
# This option has no effect if EXTRACT_ALL is enabled.
HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy.
# If set to NO (the default) these classes will be included in the various
# overviews. This option has no effect if EXTRACT_ALL is enabled.
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
# friend (class|struct|union) declarations.
# If set to NO (the default) these declarations will be included in the
# documentation.
HIDE_FRIEND_COMPOUNDS = NO
# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
# documentation blocks found inside the body of a function.
# If set to NO (the default) these blocks will be appended to the
# function's detailed documentation block.
HIDE_IN_BODY_DOCS = NO
# The INTERNAL_DOCS tag determines if documentation
# that is typed after a \internal command is included. If the tag is set
# to NO (the default) then the documentation will be excluded.
# Set it to YES to include the internal documentation.
INTERNAL_DOCS = NO
# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
# file names in lower-case letters. If set to YES upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
CASE_SENSE_NAMES = YES
# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
# will show members with their full class and namespace scopes in the
# documentation. If set to YES the scope will be hidden.
HIDE_SCOPE_NAMES = NO
# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
# will put a list of the files that are included by a file in the documentation
# of that file.
SHOW_INCLUDE_FILES = YES
# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
# is inserted in the documentation for inline members.
INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
# will sort the (detailed) documentation of file and class members
# alphabetically by member name. If set to NO the members will appear in
# declaration order.
SORT_MEMBER_DOCS = YES
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
# brief documentation of file, namespace and class members alphabetically
# by member name. If set to NO (the default) the members will appear in
# declaration order.
SORT_BRIEF_DOCS = NO
# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
# hierarchy of group names into alphabetical order. If set to NO (the default)
# the group names will appear in their defined order.
SORT_GROUP_NAMES = NO
# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
# sorted by fully-qualified names, including namespaces. If set to
# NO (the default), the class list will be sorted only by class name,
# not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
# Note: This option applies only to the class list, not to the
# alphabetical list.
SORT_BY_SCOPE_NAME = NO
# The GENERATE_TODOLIST tag can be used to enable (YES) or
# disable (NO) the todo list. This list is created by putting \todo
# commands in the documentation.
GENERATE_TODOLIST = YES
# The GENERATE_TESTLIST tag can be used to enable (YES) or
# disable (NO) the test list. This list is created by putting \test
# commands in the documentation.
GENERATE_TESTLIST = YES
# The GENERATE_BUGLIST tag can be used to enable (YES) or
# disable (NO) the bug list. This list is created by putting \bug
# commands in the documentation.
GENERATE_BUGLIST = YES
# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
# disable (NO) the deprecated list. This list is created by putting
# \deprecated commands in the documentation.
GENERATE_DEPRECATEDLIST= YES
# The ENABLED_SECTIONS tag can be used to enable conditional
# documentation sections, marked by \if sectionname ... \endif.
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
# the initial value of a variable or define consists of for it to appear in
# the documentation. If the initializer consists of more lines than specified
# here it will be hidden. Use a value of 0 to hide initializers completely.
# The appearance of the initializer of individual variables and defines in the
# documentation can be controlled using \showinitializer or \hideinitializer
# command in the documentation regardless of this setting.
MAX_INITIALIZER_LINES = 30
# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
# at the bottom of the documentation of classes and structs. If set to YES the
# list will mention the files that were used to generate the documentation.
SHOW_USED_FILES = YES
# If the sources in your project are distributed over multiple directories
# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
# in the documentation. The default is NO.
SHOW_DIRECTORIES = YES
# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
# This will remove the Files entry from the Quick Index and from the
# Folder Tree View (if specified). The default is YES.
SHOW_FILES = YES
# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
# Namespaces page.
# This will remove the Namespaces entry from the Quick Index
# and from the Folder Tree View (if specified). The default is YES.
SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command <command> <input-file>, where <command> is the value of
# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
# provided by doxygen. Whatever the program writes to standard output
# is used as the file version. See the manual for examples.
FILE_VERSION_FILTER =
# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
# doxygen. The layout file controls the global structure of the generated output files
# in an output format independent way. The create the layout file that represents
# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
# file name after the option, if omitted DoxygenLayout.xml will be used as the name
# of the layout file.
LAYOUT_FILE =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
QUIET = NO
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
# NO is used.
WARNINGS = YES
# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
# automatically be disabled.
WARN_IF_UNDOCUMENTED = YES
# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some
# parameters in a documented function, or documenting parameters that
# don't exist or using markup commands wrongly.
WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be abled to get warnings for
# functions that are documented, but have no documentation for their parameters
# or return value. If set to NO (the default) doxygen will only warn about
# wrong or incomplete parameter documentation, but not about the absence of
# documentation.
WARN_NO_PARAMDOC = NO
# The WARN_FORMAT tag determines the format of the warning messages that
# doxygen can produce. The string should contain the $file, $line, and $text
# tags, which will be replaced by the file and line number from which the
# warning originated and the warning text. Optionally the format may contain
# $version, which will be replaced by the version of the file (if it could
# be obtained via FILE_VERSION_FILTER)
WARN_FORMAT = "$file:$line: $text"
# The WARN_LOGFILE tag can be used to specify a file to which warning
# and error messages should be written. If left blank the output is written
# to stderr.
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
# The INPUT tag can be used to specify the files and/or directories that contain
# documented source files. You may enter file names like "myfile.cpp" or
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = __MAGIC_INPUT__
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
# also the default input encoding. Doxygen uses libiconv (or the iconv built
# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
# the list of possible encodings.
INPUT_ENCODING = UTF-8
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
# and *.h) to filter out the source-files in the directories. If left
# blank the following patterns are tested:
# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
FILE_PATTERNS = *.c \
*.h
# The RECURSIVE tag can be used to turn specify whether or not subdirectories
# should be searched for input files as well. Possible values are YES and NO.
# If left blank NO is used.
RECURSIVE = NO
# The EXCLUDE tag can be used to specify files and/or directories that should
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
# directories that are symbolic links (a Unix filesystem feature) are excluded
# from the input.
EXCLUDE_SYMLINKS = NO
# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
# certain files from those directories. Note that the wildcards are matched
# against the file with absolute path, so to exclude all test directories
# for example use the pattern */test/*
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
EXCLUDE_SYMBOLS =
# The EXAMPLE_PATH tag can be used to specify one or more files or
# directories that contain example code fragments that are included (see
# the \include command).
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
# and *.h) to filter out the source-files in the directories. If left
# blank all files are included.
EXAMPLE_PATTERNS =
# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude
# commands irrespective of the value of the RECURSIVE tag.
# Possible values are YES and NO. If left blank NO is used.
EXAMPLE_RECURSIVE = NO
# The IMAGE_PATH tag can be used to specify one or more files or
# directories that contain image that are included in the documentation (see
# the \image command).
IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
# by executing (via popen()) the command <filter> <input-file>, where <filter>
# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
# input file. Doxygen will then use the output that the filter program writes
# to standard output.
# If FILTER_PATTERNS is specified, this tag will be
# ignored.
INPUT_FILTER =
# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis.
# Doxygen will compare the file name with each pattern and apply the
# filter if there is a match.
# The filters are a list of the form:
# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
# is applied to all files.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will be used to filter the input files when producing source
# files to browse (i.e. when SOURCE_BROWSER is set to YES).
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
# If the SOURCE_BROWSER tag is set to YES then a list of source files will
# be generated. Documented entities will be cross-referenced with these sources.
# Note: To get rid of all source code in the generated output, make sure also
# VERBATIM_HEADERS is set to NO.
SOURCE_BROWSER = NO
# Setting the INLINE_SOURCES tag to YES will include the body
# of functions and classes directly in the documentation.
INLINE_SOURCES = NO
# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
# doxygen to hide any special comment blocks from generated source code
# fragments. Normal C and C++ comments will always remain visible.
STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES
# then for each documented function all documented
# functions referencing it will be listed.
REFERENCED_BY_RELATION = YES
# If the REFERENCES_RELATION tag is set to YES
# then for each documented function all documented entities
# called/used by that function will be listed.
REFERENCES_RELATION = YES
# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
# link to the source code.
# Otherwise they will link to the documentation.
REFERENCES_LINK_SOURCE = YES
# If the USE_HTAGS tag is set to YES then the references to source code
# will point to the HTML generated by the htags(1) tool instead of doxygen
# built-in source browser. The htags tool is part of GNU's global source
# tagging system (see http://www.gnu.org/software/global/global.html). You
# will need version 4.8.6 or higher.
USE_HTAGS = NO
# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
# will generate a verbatim copy of the header file for each class for
# which an include is specified. Set to NO to disable this.
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
# of all compounds will be generated. Enable this if the project
# contains a lot of classes, structs, unions or interfaces.
ALPHABETICAL_INDEX = NO
# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
# in which this list will be split (can be a number in the range [1..20])
COLS_IN_ALPHA_INDEX = 5
# In case all classes in a project start with a common prefix, all
# classes will be put under the same header in the alphabetical index.
# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
# should be ignored while generating the index headers.
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
# generate HTML output.
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `html' will be used as the default path.
HTML_OUTPUT = html
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
# doxygen will generate files with .html extension.
HTML_FILE_EXTENSION = .html
# The HTML_HEADER tag can be used to specify a personal HTML header for
# each generated HTML page. If it is left blank doxygen will generate a
# standard header.
HTML_HEADER =
# The HTML_FOOTER tag can be used to specify a personal HTML footer for
# each generated HTML page. If it is left blank doxygen will generate a
# standard footer.
HTML_FOOTER =
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
# style sheet that is used by each HTML page. It can be used to
# fine-tune the look of the HTML output. If the tag is left blank doxygen
# will generate a default style sheet. Note that doxygen will try to copy
# the style sheet file to the HTML output directory, so don't put your own
# stylesheet in the HTML output directory as well, or it will be erased!
HTML_STYLESHEET =
# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
# files or namespaces will be aligned in HTML using tables. If set to
# NO a bullet list will be used.
HTML_ALIGN_MEMBERS = YES
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded. For this to work a browser that supports
# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
HTML_DYNAMIC_SECTIONS = NO
# If the GENERATE_DOCSET tag is set to YES, additional index files
# will be generated that can be used as input for Apple's Xcode 3
# integrated development environment, introduced with OSX 10.5 (Leopard).
# To create a documentation set, doxygen will generate a Makefile in the
# HTML output directory. Running make will produce the docset in that
# directory and running "make install" will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
# it at startup.
# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
GENERATE_DOCSET = NO
# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
# feed. A documentation feed provides an umbrella under which multiple
# documentation sets from a single provider (such as a company or product suite)
# can be grouped.
DOCSET_FEEDNAME = "Doxygen generated docs"
# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
# should uniquely identify the documentation set bundle. This should be a
# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
# will append .docset to the name.
DOCSET_BUNDLE_ID = org.doxygen.Project
# If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the
# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
# of the generated HTML documentation.
GENERATE_HTMLHELP = NO
# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
# be used to specify the file name of the resulting .chm file. You
# can add a path in front of the file if the result should not be
# written to the html output directory.
CHM_FILE =
# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
# be used to specify the location (absolute path including file name) of
# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
# the HTML help compiler on the generated index.hhp.
HHC_LOCATION =
# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
# controls if a separate .chi index file is generated (YES) or that
# it should be included in the master .chm file (NO).
GENERATE_CHI = NO
# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
# is used to encode HtmlHelp index (hhk), content (hhc) and project file
# content.
CHM_INDEX_ENCODING =
# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
# controls whether a binary table of contents is generated (YES) or a
# normal table of contents (NO) in the .chm file.
BINARY_TOC = NO
# The TOC_EXPAND flag can be set to YES to add extra items for group members
# to the contents of the HTML help documentation and to the tree view.
TOC_EXPAND = NO
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
# are set, an additional index file will be generated that can be used as input for
# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
# HTML documentation.
GENERATE_QHP = NO
# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
# be used to specify the file name of the resulting .qch file.
# The path specified is relative to the HTML output folder.
QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating
# Qt Help Project output. For more information please see
# http://doc.trolltech.com/qthelpproject.html#namespace
QHP_NAMESPACE =
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
# Qt Help Project output. For more information please see
# http://doc.trolltech.com/qthelpproject.html#virtual-folders
QHP_VIRTUAL_FOLDER = doc
# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
# For more information please see
# http://doc.trolltech.com/qthelpproject.html#custom-filters
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
# filter section matches.
# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
QHP_SECT_FILTER_ATTRS =
# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
# be used to specify the location of Qt's qhelpgenerator.
# If non-empty doxygen will try to run qhelpgenerator on the generated
# .qhp file.
QHG_LOCATION =
# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
# top of each HTML page. The value NO (the default) enables the index and
# the value YES disables it.
DISABLE_INDEX = NO
# This tag can be used to set the number of enum values (range [1..20])
# that doxygen will group on one line in the generated HTML documentation.
ENUM_VALUES_PER_LINE = 4
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information.
# If the tag value is set to FRAME, a side panel will be generated
# containing a tree-like index structure (just like the one that
# is generated for HTML Help). For this to work a browser that supports
# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
# probably better off using the HTML help feature. Other possible values
# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
# and Class Hierarchy pages using a tree view instead of an ordered list;
# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
# disables this behavior completely. For backwards compatibility with previous
# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
# respectively.
GENERATE_TREEVIEW = YES
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
# used to set the initial width (in pixels) of the frame in which the tree
# is shown.
TREEVIEW_WIDTH = 250
# Use this tag to change the font size of Latex formulas included
# as images in the HTML documentation. The default is 10. Note that
# when you change the font size after a successful doxygen run you need
# to manually remove any form_*.png images from the HTML output directory
# to force them to be regenerated.
FORMULA_FONTSIZE = 10
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
# generate Latex output.
GENERATE_LATEX = YES
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `latex' will be used as the default path.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. If left blank `latex' will be used as the default command name.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
# generate index for LaTeX. If left blank `makeindex' will be used as the
# default command name.
MAKEINDEX_CMD_NAME = makeindex
# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
# LaTeX documents. This may be useful for small projects and may help to
# save some trees in general.
COMPACT_LATEX = NO
# The PAPER_TYPE tag can be used to set the paper type that is used
# by the printer. Possible values are: a4, a4wide, letter, legal and
# executive. If left blank a4wide will be used.
PAPER_TYPE = a4wide
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
# packages that should be included in the LaTeX output.
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
# the generated latex document. The header should contain everything until
# the first chapter. If it is left blank doxygen will generate a
# standard header. Notice: only use this tag if you know what you are doing!
LATEX_HEADER =
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
# is prepared for conversion to pdf (using ps2pdf). The pdf file will
# contain links (just like the HTML output) instead of page references
# This makes the output suitable for online browsing using a pdf viewer.
PDF_HYPERLINKS = NO
# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
# plain latex in the generated Makefile. Set this option to YES to get a
# higher quality PDF documentation.
USE_PDFLATEX = NO
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
# command to the generated LaTeX files. This will instruct LaTeX to keep
# running if errors occur, instead of asking the user for help.
# This option is also used when generating formulas in HTML.
LATEX_BATCHMODE = NO
# If LATEX_HIDE_INDICES is set to YES then doxygen will not
# include the index chapters (such as File Index, Compound Index, etc.)
# in the output.
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
# The RTF output is optimized for Word 97 and may not look very pretty with
# other RTF readers or editors.
GENERATE_RTF = NO
# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `rtf' will be used as the default path.
RTF_OUTPUT = rtf
# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
# RTF documents. This may be useful for small projects and may help to
# save some trees in general.
COMPACT_RTF = NO
# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
# will contain hyperlink fields. The RTF file will
# contain links (just like the HTML output) instead of page references.
# This makes the output suitable for online browsing using WORD or other
# programs which support those fields.
# Note: wordpad (write) and others do not support links.
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's
# config file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an rtf document.
# Syntax is similar to doxygen's config file.
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
# generate man pages
GENERATE_MAN = YES
# The MAN_OUTPUT tag is used to specify where the man pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `man' will be used as the default path.
MAN_OUTPUT = man
# The MAN_EXTENSION tag determines the extension that is added to
# the generated man pages (default is the subroutine's section .3)
MAN_EXTENSION = .3
# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
# then it will generate one additional man file for each entity
# documented in the real man page(s). These additional files
# only source the real man page, but without them the man command
# would be unable to find the correct page. The default is NO.
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
# If the GENERATE_XML tag is set to YES Doxygen will
# generate an XML file that captures the structure of
# the code including all documentation.
GENERATE_XML = NO
# The XML_OUTPUT tag is used to specify where the XML pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `xml' will be used as the default path.
XML_OUTPUT = xml
# The XML_SCHEMA tag can be used to specify an XML schema,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
XML_SCHEMA =
# The XML_DTD tag can be used to specify an XML DTD,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
XML_DTD =
# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
# dump the program listings (including syntax highlighting
# and cross-referencing information) to the XML output. Note that
# enabling this will significantly increase the size of the XML output.
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
# generate an AutoGen Definitions (see autogen.sf.net) file
# that captures the structure of the code including all
# documentation. Note that this feature is still experimental
# and incomplete at the moment.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
# If the GENERATE_PERLMOD tag is set to YES Doxygen will
# generate a Perl module file that captures the structure of
# the code including all documentation. Note that this
# feature is still experimental and incomplete at the
# moment.
GENERATE_PERLMOD = NO
# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
# the necessary Makefile rules, Perl scripts and LaTeX code to be able
# to generate PDF and DVI output from the Perl module output.
PERLMOD_LATEX = NO
# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
# nicely formatted so it can be parsed by a human reader.
# This is useful
# if you want to understand what is going on.
# On the other hand, if this
# tag is set to NO the size of the Perl module output will be much smaller
# and Perl will parse it just the same.
PERLMOD_PRETTY = YES
# The names of the make variables in the generated doxyrules.make file
# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
# This is useful so different doxyrules.make files included by the same
# Makefile don't overwrite each other's variables.
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
# evaluate all C-preprocessor directives found in the sources and include
# files.
ENABLE_PREPROCESSING = YES
# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
# names in the source code. If set to NO (the default) only conditional
# compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES.
MACRO_EXPANSION = NO
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
# then the macro expansion is limited to the macros specified with the
# PREDEFINED and EXPAND_AS_DEFINED tags.
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
# in the INCLUDE_PATH (see below) will be search if a #include is found.
SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by
# the preprocessor.
INCLUDE_PATH =
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
# directories. If left blank, the patterns specified with FILE_PATTERNS will
# be used.
INCLUDE_FILE_PATTERNS =
# The PREDEFINED tag can be used to specify one or more macro names that
# are defined before the preprocessor is started (similar to the -D option of
# gcc). The argument of the tag is a list of macros of the form: name
# or name=definition (no spaces). If the definition and the = are
# omitted =1 is assumed. To prevent a macro definition from being
# undefined via #undef or recursively expanded use the := operator
# instead of the = operator.
PREDEFINED =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
# this tag can be used to specify a list of macro names that should be expanded.
# The macro definition that is found in the sources will be used.
# Use the PREDEFINED tag if you want to use a different macro definition.
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
# doxygen's preprocessor will remove all function-like macros that are alone
# on a line, have an all uppercase name, and do not end with a semicolon. Such
# function macros are typically used for boiler-plate code, and will confuse
# the parser if not removed.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
# The TAGFILES option can be used to specify one or more tagfiles.
# Optionally an initial location of the external documentation
# can be added for each tagfile. The format of a tag file without
# this location is as follows:
#
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
#
# TAGFILES = file1=loc1 "file2 = loc2" ...
# where "loc1" and "loc2" can be relative or absolute paths or
# URLs. If a location is present for each tag, the installdox tool
# does not have to be run to correct the links.
# Note that each tag file must have a unique name
# (where the name does NOT include the path)
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
TAGFILES =
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES all external classes will be listed
# in the class index. If set to NO only the inherited external classes
# will be listed.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will
# be listed.
EXTERNAL_GROUPS = YES
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of `which perl').
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
# or super classes. Setting the tag to NO turns the diagrams off. Note that
# this option is superseded by the HAVE_DOT option below. This is only a
# fallback. It is recommended to install and use dot, since it yields more
# powerful graphs.
CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see
# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
# If set to YES, the inheritance and collaboration graphs will hide
# inheritance and usage relations if the target is undocumented
# or is not a class.
HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz, a graph visualization
# toolkit from AT&T and Lucent Bell Labs. The other options in this section
# have no effect if this option is set to NO (the default)
HAVE_DOT = NO
# By default doxygen will write a font called FreeSans.ttf to the output
# directory and reference it in all dot files that doxygen generates. This
# font does not include all possible unicode characters however, so when you need
# these (or just want a differently looking font) you can specify the font name
# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
# which can be done by putting it in a standard location or by setting the
# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
# containing the font.
DOT_FONTNAME = FreeSans
# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
# The default size is 10pt.
DOT_FONTSIZE = 10
# By default doxygen will tell dot to use the output directory to look for the
# FreeSans.ttf font (which doxygen will put there itself). If you specify a
# different font using DOT_FONTNAME you can set the path where dot
# can find it using this tag.
DOT_FONTPATH =
# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for each documented class showing the direct and
# indirect inheritance relations. Setting this tag to YES will force the
# the CLASS_DIAGRAMS tag to NO.
CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for each documented class showing the direct and
# indirect implementation dependencies (inheritance, containment, and
# class references variables) of the class with other documented classes.
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for groups, showing the direct groups dependencies
GROUP_GRAPHS = YES
# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
UML_LOOK = NO
# If set to YES, the inheritance and collaboration graphs will show the
# relations between templates and their instances.
TEMPLATE_RELATIONS = NO
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
# tags are set to YES then doxygen will generate a graph for each documented
# file showing the direct and indirect include dependencies of the file with
# other documented files.
INCLUDE_GRAPH = YES
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
# documented header file showing the documented files that directly or
# indirectly include this file.
INCLUDED_BY_GRAPH = YES
# If the CALL_GRAPH and HAVE_DOT options are set to YES then
# doxygen will generate a call dependency graph for every global function
# or class method. Note that enabling this option will significantly increase
# the time of a run. So in most cases it will be better to enable call graphs
# for selected functions only using the \callgraph command.
CALL_GRAPH = NO
# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
# doxygen will generate a caller dependency graph for every global function
# or class method. Note that enabling this option will significantly increase
# the time of a run. So in most cases it will be better to enable caller
# graphs for selected functions only using the \callergraph command.
CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
# will graphical hierarchy of all classes instead of a textual one.
GRAPHICAL_HIERARCHY = YES
# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
# then doxygen will show the dependencies a directory has on other directories
# in a graphical way. The dependency relations are determined by the #include
# relations between the files in the directories.
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. Possible values are png, jpg, or gif
# If left blank png will be used.
DOT_IMAGE_FORMAT = png
# The tag DOT_PATH can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
DOT_PATH =
# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the
# \dotfile command).
DOTFILE_DIRS =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
# nodes that will be shown in the graph. If the number of nodes in a graph
# becomes larger than this value, doxygen will truncate the graph, which is
# visualized by representing a node as a red box. Note that doxygen if the
# number of direct children of the root node in a graph is already larger than
# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
DOT_GRAPH_MAX_NODES = 50
# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
# graphs generated by dot. A depth value of 3 means that only nodes reachable
# from the root by following a path via at most 3 edges will be shown. Nodes
# that lay further from the root node will be omitted. Note that setting this
# option to 1 or 2 may greatly reduce the computation time needed for large
# code bases. Also note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not
# seem to support this out of the box. Warning: Depending on the platform used,
# enabling this option may lead to badly anti-aliased labels on the edges of
# a graph (i.e. they become hard to read).
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10)
# support this, this feature is disabled by default.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
# generate a legend page explaining the meaning of the various boxes and
# arrows in the dot generated graphs.
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
# remove the intermediate dot files that are used to generate
# the various graphs.
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Options related to the search engine
#---------------------------------------------------------------------------
# The SEARCHENGINE tag specifies whether or not a search engine should be
# used. If set to NO the values of all tags below this one will be ignored.
SEARCHENGINE = NO
# doxy.sh
# Invokes doxygen modifying a pre-defined doxygen config file.
#
# Known bugs:
# - Exclamation marks (!) in any of the arguments break the script due to
# delimiter collision when invoking sed.
OUTPUT_DIR=doxygen_output
usage()
{
cat << EOF
`basename $0`: generate doxygen documentation
Usage: `basename $0` [-o<OUTPUT_DIRECTORY>] [-n<PROJECT_NAME>] input_files
options:
-h Show this message
-n Name of the project
-o Output directory for the generated files (default: $OUTPUT_DIR)
Example: `basename $0` -o"doxygen_output" -n"MyLib" mylib.c include/mylib.h
EOF
}
while getopts "hn:o:" OPTION
do
case $OPTION in
h)
usage
exit 1
;;
n)
NAME="$OPTARG"
;;
o)
OUTPUT_DIR="$OPTARG"
;;
?)
usage
exit
;;
esac
done
# set subsequent non-option arguments to $1...n
shift $((OPTIND-1))
OPTIND=1
if [[ -z "$*" ]]
then
echo "No input files given"
usage
exit 1
fi
cat $(dirname $0)/default.doxycfg | \
sed "s!__MAGIC_PROJECT_NAME__!$NAME!" | \
sed "s!__MAGIC_OUTPUT_DIRECTORY__!$OUTPUT_DIR!" | \
sed "s!__MAGIC_INPUT__!$*!" | \
doxygen -
\documentclass[a4paper,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage{graphicx}
\usepackage[hidelinks]{hyperref}
\title{User manual}
\author{FMC TDC Linux kernel support}
\date{}
\begin{document}
\maketitle
\tableofcontents
\newpage
\section{Introduction}
%\begin{figure}
\begin{center}
\includegraphics[scale=0.5]{img/fmc-tdc.jpg}
% \label{fig:fmc-tdc}
% \caption{FMC TDC board}
\end{center}
%\end{figure}
$\newline$
In electronic instrumentation and signal processing, a time to digital converter (abbreviated TDC) is a device for recognizing events and providing a digital representation of the time they occurred. At this case, the FMC TDC outputs the time of arrival for each incoming pulse.
This is the project to provide support of the FMC TDC board in the Linux kernel plugged to a SPEC carrier board.
The aim is to provide an loadable module to be used along with this board for
the latest Linux kernel versions. The driver relies on ZIO framework and the FMC
bus dependencies, which are other projects hosted in OHWR.
\section{Installation}
\subsection{Dependencies}
To compile properly this driver, you should have downloaded the following repositories (branch master). For that purpose, you should have installed \textit{git} software.
\begin{itemize}
\item \href{http://www.ohwr.org/projects/zio}{ZIO} framework
\item \href{http://www.ohwr.org/projects/spec-sw}{SPEC} driver
\item \href{http://www.ohwr.org/projects/fmc-bus}{FMC bus} support
\end{itemize}
\subsection{Download the sources}
To download the sources, you should execute the following command:
\indent\indent\texttt{\$ git clone git://ohwr.org/fmc-projects/fmc-tdc/fmc-tdc-sw.git}
\subsection{Compile}
To compile the sources, you should execute the following command:
\indent\indent\texttt{\$ make}
\subsection{Load the drivers}
To load the drivers, we should load the dependencies first:
\indent\indent\texttt{\# insmod <path\_zio>/zio.ko} \\
\indent\indent\texttt{\# insmod <path\_fmc-bus>/kernel/fmc.ko} \\
\indent\indent\texttt{\# insmod <path\_spec-sw>/kernel/spec.ko} \\
Once the dependencies are loaded, we load the drivers:
\indent\indent\texttt{\# insmod <path\_fmc-tdc-sw>/drivers/spec-tdc.ko <parameters>}
\subsubsection{FMC TDC driver parameters}
The spec-tdc.ko driver has several parameters, you can discover them executing: \texttt{modinfo ./spec-tdc.ko}.
\begin{itemize}
\item lun: Logical unit number.
\item bus: PCI bus number where the SPEC+TDC is plugged on.
\item slot: PCI slot where the SPEC+TDC is plugged on.
\end{itemize}
To know which are the bus and slot numbers, one can use the command \textit{lspci}:
\indent\indent\texttt{\$ lspci} \\
\indent\indent\texttt{[...]} \\
\indent\indent\texttt{00:04.0 Non-VGA unclassified device: CERN/ECP/EDU Device 018d (rev 03)} \\
In the previous output, the parameters will be: bus=0 slot=4.
\section{libtdc, an user-space library}
\subsection{Introduction}
To facilitate the task of managing the FMC TDC devices, it is provided an C/C++
user-space library. It is recommended to use it instead of accesing directly to
the driver.
\subsection{API}
\textbf{struct tdc\_board *tdc\_open(int lun);} \\
Open the selected device.\\
\textbf{int tdc\_close(struct tdc\_board *b);} \\
Close the device. \\
\textbf{struct tdc\_time *tdc\_zalloc(unsigned int events);} \\
Allocate a struct tdc\_time buffer with \textit{events} number of events. \\
\textbf{void tdc\_free(struct tdc\_time *buffer);} \\
Free the previously allocated struct tdc\_time buffer. \\
\textbf{int tdc\_start\_acquisition(struct tdc\_board *b);} \\
Start acquisition.\\
\textbf{int tdc\_stop\_acquisition(struct tdc\_board *b);} \\
Stop acquisition.\\
\textbf{int tdc\_set\_host\_utc\_time(struct tdc\_board *b);} \\
Set FMC TDC time reference to local host UTC time. \\
\textbf{int tdc\_set\_utc\_time(struct tdc\_board *b, uint32\_t utc);} \\
Set FMC TDC time reference to a given value.\\
\textbf{int tdc\_get\_utc\_time(struct tdc\_board *b, uint32\_t *utc);} \\
Get FMC TDC time reference value. \\
\textbf{int tdc\_set\_dac\_word(struct tdc\_board *b, uint32\_t dw);} \\
Set FMC TDC DAC with a given value. \\
\textbf{int tdc\_get\_dac\_word(struct tdc\_board *b, uint32\_t *dw);} \\
Get FMC TDC DAC value. \\
\textbf{int tdc\_set\_time\_threshold(struct tdc\_board *b, uint32\_t thres);} \\
Set time threshold (time between IRQ where the event number acquired are less than the timestamp
threshold). In seconds. \\
\textbf{int tdc\_get\_time\_threshold(struct tdc\_board *b, uint32\_t *thres);} \\
Get time threshold.\\
\textbf{int tdc\_set\_timestamp\_threshold(struct tdc\_board *b, uint32\_t thres);} \\
Set timestamp threshold.\\
\textbf{int tdc\_get\_timestamp\_threshold(struct tdc\_board *b, uint32\_t *thres);} \\
Get timestamp threshold.\\
\textbf{int tdc\_set\_channels\_term(struct tdc\_board *b, uint32\_t config);} \\
Set channel termination resistor (50 Ohms). This is not an enable of the channel. \\
\textbf{int tdc\_get\_channels\_term(struct tdc\_board *b, uint32\_t *config);} \\
Get channel termination setup. \\
\textbf{int tdc\_activate\_channels(struct tdc\_board *b);} \\
Activate/enable all the channels to acquire data. This should be done before call tdc\_start\_acquisition(). \\
\textbf{int tdc\_deactivate\_channels(struct tdc\_board *b);} \\
Deactivate/disable all channels. \\
\textbf{int tdc\_get\_circular\_buffer\_pointer(struct tdc\_board *b, uint32\_t *ptr);} \\
Get circular buffer pointer value.\\
\textbf{int tdc\_clear\_dacapo\_flag(struct tdc\_board *b);} \\
Clear Dacapo flag from the circular buffer pointer value.\\
\textbf{int tdc\_read(struct tdc\_board *b, int chan, struct tdc\_time *t,
int n, int flags);} \\
Read \textit{n} events. The allowed flags are 0 (blocking read) or O\_NONBLOCK (non-blocking read).
\section{Test program}
\subsection{Introduction}
The test program is used to check the proper behavior of the board in case of failure or to check if there is a bug in the driver or in the library.
The test program has an CLI interface due to some limitations when accessing remotely to the machine. It is designed to allow the execution of the program under SSH.
\subsection{Dependencies}
\begin{itemize}
\item Python 2.7 or higher.
\end{itemize}
First of all, you should compile the shared object library:
\indent\indent\texttt{\$ cd <path\_fmc-tdc-sw>/lib} \\
\indent\indent\texttt{\$ make libtdc.so}
\subsection{How to use it}
To execute it:
\indent\indent\texttt{\$ cd <path\_fmc-tdc-sw>/test} \\
\indent\indent\texttt{\$ sudo ./test-fmctdc.py} \\
If you want the help, you can execute:\\
\indent\indent\texttt{\$ ./test-fmctdc.py -h} \\
\section{Contact}
\href{http://www.ohwr.org/projects/fmc-tdc-sw}{FMC TDC Linux kernel support website} on \href{http://www.ohwr.org/}{OHWR}:
\indent\indent\texttt{\url{http://www.ohwr.org/projects/fmc-tdc-sw}}\\
\href{http://www.ohwr.org/mailing_list/show?project_id=fmc-tdc-sw}{Mailing list} on \href{http://www.ohwr.org/}{OHWR}:
\indent\indent\texttt{\url{http://www.ohwr.org/mailing_list/show?project_id=fmc-tdc-sw}}
\end{document}
LINUX ?= /lib/modules/$(shell uname -r)/build
ZIO ?= $(HOME)/zio
SPEC_SW ?= $(HOME)/spec-sw
KBUILD_EXTRA_SYMBOLS := $(ZIO)/Module.symvers $(SPEC_SW)/kernel/Module.symvers $(SPEC_SW)/fmc-bus/kernel/Module.symvers
ccflags-y = -I$(ZIO)/include -I$(SPEC_SW)/kernel -I$M -I$(SPEC_SW)/kernel -I$(SPEC_SW)/fmc-bus/kernel/include
#ccflags-y += -DDEBUG
subdirs-ccflags-y = $(ccflags-y)
obj-m := spec-tdc.o
spec-tdc-objs = tdc-core.o tdc-zio.o tdc-fmc.o tdc-acam.o tdc-dma.o
all: modules
modules_install clean modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) $@
/*
* ACAM support for tdc driver
*
* Copyright (C) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "tdc.h"
#include "hw/tdc_regs.h"
void tdc_acam_reset(struct spec_tdc *tdc)
{
writel(TDC_CTRL_RESET_ACAM, tdc->base + TDC_CTRL_REG);
}
static void __tdc_acam_do_load_config(struct spec_tdc *tdc)
{
writel(TDC_CTRL_LOAD_ACAM_CFG, tdc->base + TDC_CTRL_REG);
}
static void __tdc_acam_do_read_config(struct spec_tdc *tdc)
{
writel(TDC_CTRL_READ_ACAM_CFG, tdc->base + TDC_CTRL_REG);
}
u32 tdc_acam_status(struct spec_tdc *tdc)
{
/* Send the command to read acam status */
writel(TDC_CTRL_READ_ACAM_STAT, tdc->base + TDC_CTRL_REG);
return readl(tdc->base + TDC_ACAM_RDBACK_REG_12);
}
u32 tdc_acam_read_ififo1(struct spec_tdc *tdc)
{
/* Send the command to read acam status */
writel(TDC_CTRL_READ_ACAM_IFIFO1, tdc->base + TDC_CTRL_REG);
return readl(tdc->base + TDC_ACAM_RDBACK_REG_8);
}
u32 tdc_acam_read_ififo2(struct spec_tdc *tdc)
{
/* Send the command to read acam status */
writel(TDC_CTRL_READ_ACAM_IFIFO2, tdc->base + TDC_CTRL_REG);
return readl(tdc->base + TDC_ACAM_RDBACK_REG_9);
}
u32 tdc_acam_read_start01(struct spec_tdc *tdc)
{
/* Send the command to read acam status */
writel(TDC_CTRL_READ_ACAM_START01_R, tdc->base + TDC_CTRL_REG);
return readl(tdc->base + TDC_ACAM_RDBACK_REG_10);
}
int tdc_acam_load_config(struct spec_tdc *tdc, struct tdc_acam_cfg *cfg)
{
/* Write the configuration parameters to the registers */
writel(cfg->edge_config, tdc->base + TDC_ACAM_CFG_REG_0);
writel(cfg->channel_adj, tdc->base + TDC_ACAM_CFG_REG_1);
writel(cfg->mode_enable, tdc->base + TDC_ACAM_CFG_REG_2);
writel(cfg->resolution, tdc->base + TDC_ACAM_CFG_REG_3);
writel(cfg->start_timer_set, tdc->base + TDC_ACAM_CFG_REG_4);
writel(cfg->start_retrigger, tdc->base + TDC_ACAM_CFG_REG_5);
writel(cfg->lf_flags_level, tdc->base + TDC_ACAM_CFG_REG_6);
writel(cfg->pll, tdc->base + TDC_ACAM_CFG_REG_7);
writel(cfg->err_flag_cfg, tdc->base + TDC_ACAM_CFG_REG_11);
writel(cfg->int_flag_cfg, tdc->base + TDC_ACAM_CFG_REG_12);
writel(cfg->ctrl_16_bit_mode, tdc->base + TDC_ACAM_CFG_REG_14);
/* Send the load command to the firmware */
__tdc_acam_do_load_config(tdc);
mdelay(100);
return 0;
}
int tdc_acam_get_config(struct spec_tdc *tdc, struct tdc_acam_cfg *cfg)
{
/* Send read config command to retrieve the data to the registers */
__tdc_acam_do_read_config(tdc);
/* Read the configuration values from the read-back registers */
cfg->edge_config = readl(tdc->base + TDC_ACAM_RDBACK_REG_0);
cfg->channel_adj = readl(tdc->base + TDC_ACAM_RDBACK_REG_1);
cfg->mode_enable = readl(tdc->base + TDC_ACAM_RDBACK_REG_2);
cfg->resolution = readl(tdc->base + TDC_ACAM_RDBACK_REG_3);
cfg->start_timer_set = readl(tdc->base + TDC_ACAM_RDBACK_REG_4);
cfg->start_retrigger = readl(tdc->base + TDC_ACAM_RDBACK_REG_5);
cfg->lf_flags_level = readl(tdc->base + TDC_ACAM_RDBACK_REG_6);
cfg->pll = readl(tdc->base + TDC_ACAM_RDBACK_REG_7);
cfg->err_flag_cfg = readl(tdc->base + TDC_ACAM_RDBACK_REG_11);
cfg->int_flag_cfg = readl(tdc->base + TDC_ACAM_RDBACK_REG_12);
cfg->ctrl_16_bit_mode = readl(tdc->base + TDC_ACAM_RDBACK_REG_14);
return 0;
}
int tdc_acam_set_default_config(struct spec_tdc *tdc)
{
struct tdc_acam_cfg cfg;
/* Default setup as indicated in the datasheet */
cfg.edge_config = 0x01F0FC81;
cfg.channel_adj = 0x0;
cfg.mode_enable = 0xE02;
cfg.resolution = 0x0;
cfg.start_timer_set = 0x0200000F;
cfg.start_retrigger = 0x07D0;
cfg.lf_flags_level = 0x03;
cfg.pll = 0x001FEA;
cfg.err_flag_cfg = 0x00FF0000;
cfg.int_flag_cfg = 0x04000000;
cfg.ctrl_16_bit_mode = 0x0;
return tdc_acam_load_config(tdc, &cfg);
}
/*
* core tdc driver
*
* Copyright (C) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include "spec.h"
#include "tdc.h"
#include "hw/tdc_regs.h"
int lun[MAX_DEVICES];
unsigned int nlun;
module_param_array(lun, int, &nlun, S_IRUGO);
int bus[MAX_DEVICES];
unsigned int nbus;
module_param_array(bus, int, &nbus, S_IRUGO);
int slot[MAX_DEVICES];
unsigned int nslot;
module_param_array(slot, int, &nslot, S_IRUGO);
char *gateware = "eva_tdc_for_v2.bin";
module_param(gateware, charp, S_IRUGO);
void tdc_set_utc_time(struct spec_tdc *tdc, u32 value)
{
writel(value, tdc->base + TDC_START_UTC_R);
writel(TDC_CTRL_LOAD_UTC, tdc->base + TDC_CTRL_REG);
}
void tdc_set_local_utc_time(struct spec_tdc *tdc)
{
struct timeval utc_time;
do_gettimeofday(&utc_time);
tdc_set_utc_time(tdc, utc_time.tv_sec);
}
u32 tdc_get_current_utc_time(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_CURRENT_UTC_R);
}
void tdc_set_irq_tstamp_thresh(struct spec_tdc *tdc, u32 val)
{
writel(val, tdc->base + TDC_IRQ_TSTAMP_THRESH_R);
}
u32 tdc_get_irq_tstamp_thresh(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_IRQ_TSTAMP_THRESH_R);
}
void tdc_set_irq_time_thresh(struct spec_tdc *tdc, u32 val)
{
writel(val, tdc->base + TDC_IRQ_TIME_THRESH_R);
}
u32 tdc_get_irq_time_thresh(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_IRQ_TIME_THRESH_R);
}
void tdc_set_dac_word(struct spec_tdc *tdc, u32 val)
{
writel(val, tdc->base + TDC_DAC_WORD_R);
writel(TDC_CTRL_CONFIG_DAC, tdc->base + TDC_CTRL_REG);
}
u32 tdc_get_dac_word(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_DAC_WORD_R);
}
void tdc_clear_da_capo_flag(struct spec_tdc *tdc)
{
writel(TDC_CTRL_CLEAR_DACAPO_FLAG, tdc->base + TDC_CTRL_REG);
}
int tdc_activate_acquisition(struct spec_tdc *tdc)
{
u32 acam_status_test;
/* Before activate the adquisition is required to reset the ACAM chip */
tdc_acam_reset(tdc);
acam_status_test = tdc_acam_status(tdc)-0xC4000800;
if (acam_status_test != 0) {
dev_err(&tdc->fmc->dev, "ACAM status: not ready! 0x%x\n", acam_status_test);
return -EBUSY;
}
writel(TDC_CTRL_EN_ACQ, tdc->base + TDC_CTRL_REG);
return 0;
}
void tdc_deactivate_acquisition(struct spec_tdc *tdc)
{
writel(TDC_CTRL_DIS_ACQ, tdc->base + TDC_CTRL_REG);
}
void tdc_set_input_enable(struct spec_tdc *tdc, u32 value)
{
writel(value, tdc->base + TDC_INPUT_ENABLE_R);
}
u32 tdc_get_input_enable(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_INPUT_ENABLE_R);
}
inline u32 tdc_get_circular_buffer_wr_pointer(struct spec_tdc *tdc)
{
return readl(tdc->base + TDC_CIRCULAR_BUF_PTR_R);
}
static int check_parameters(void)
{
if (nlun < 0 || nlun > MAX_DEVICES) {
pr_err("Invalid number of devices (%d)", nlun);
return -EINVAL;
}
if ((nlun != nbus) || (nlun != nslot)) {
pr_err("Parameter mismatch: %d luns, %d buses and %d slots\n",
nlun, nbus, nslot);
return -EINVAL;
}
if (nlun == 0) {
pr_err("No LUNs provided. The driver won't match any device");
}
return 0;
}
static int tdc_init(void)
{
int err;
err = check_parameters();
if (err < 0)
return err;
err = tdc_zio_init();
if (err < 0)
return err;
err = tdc_fmc_init();
if (err < 0) {
tdc_zio_exit();
return err;
}
return 0;
}
static void tdc_exit(void)
{
tdc_fmc_exit();
tdc_zio_exit();
}
module_init(tdc_init);
module_exit(tdc_exit);
MODULE_LICENSE("GPL");
/*
* DMA support for tdc driver
*
* Copyright (C) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <asm/io.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include "spec.h"
#include "tdc.h"
#include "hw/tdc_regs.h"
/*
* tdc_dma_setup -- Setup DMA operation
*
* @tdc: pointer to spec_tdc struct of the device
* @src: address to copy the data from (in TDC board)
* @dst: address to copy the data to (in host computer)
* @size: size of the DMA transfer (in bytes)
*
*/
int tdc_dma_setup(struct spec_tdc *tdc, unsigned long src, unsigned long dst, int size)
{
dma_addr_t mapping;
mapping = dma_map_single(&tdc->spec->pdev->dev, (char *)dst, size,
DMA_FROM_DEVICE);
tdc->rx_dma = mapping;
if (dma_mapping_error(&tdc->spec->pdev->dev, tdc->rx_dma)) {
dev_err(&tdc->spec->pdev->dev, "dma_map_single Rx failed\n");
return -ENOMEM;
}
/* Write the source and destination addresses */
writel(src, tdc->base + TDC_DMA_C_START_R);
writel(mapping & 0xffffffff, tdc->base + TDC_DMA_H_START_L_R);
writel((mapping >> 32) & 0x00ffffffff, tdc->base + TDC_DMA_H_START_H_R);
writel(0, tdc->base + TDC_DMA_NEXT_L_R);
writel(0, tdc->base + TDC_DMA_NEXT_H_R);
/* Write the DMA length */
writel(size, tdc->base + TDC_DMA_LEN_R);
writel(0x1, tdc->base + TDC_DMA_ATTRIB_R);
return 0;
}
int tdc_dma_start(struct spec_tdc *tdc)
{
writel(0x1, tdc->base + TDC_DMA_CTRL_R);
udelay(50);
writel(0, tdc->base + TDC_DMA_CTRL_R);
return 0;
}
/*
* FMC support for tdc driver
*
* Copyright (C) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/atomic.h>
#include <linux/zio.h>
#include <linux/zio-trigger.h>
#include "spec.h"
#include "tdc.h"
#include "hw/tdc_regs.h"
static struct fmc_driver tdc_fmc_driver;
static struct workqueue_struct *tdc_workqueue;
DEFINE_MUTEX(fmc_dma_lock);
DECLARE_WAIT_QUEUE_HEAD(fmc_wait_dma);
static atomic_t fmc_dma_end;
static struct fmc_gpio tdc_gpio = {
//.carrier_name = "spec",
.gpio = FMC_GPIO_IRQ(0),
.mode = GPIOF_DIR_IN,
.irqmode = IRQF_TRIGGER_RISING,
};
static void tdc_fmc_gennum_setup_local_clock(struct spec_tdc *tdc, int freq)
{
unsigned int divot;
unsigned int data;
/* Setup local clock */
divot = 800/freq - 1;
data = 0xE001F00C + (divot << 4);
writel(data, tdc->gn412x_regs + TDC_PCI_CLK_CSR);
}
static void tdc_fmc_fw_reset(struct spec_tdc *tdc)
{
/* Reset FPGA. Assert ~RSTOUT33 and de-assert it. BAR 4.*/
writel(0x00021040, tdc->gn412x_regs + TDC_PCI_SYS_CFG_SYSTEM);
mdelay(10);
writel(0x00025000, tdc->gn412x_regs + TDC_PCI_SYS_CFG_SYSTEM);
/* Allow the FW to initialize the PLLs */
mdelay(2000);
}
static int tdc_fmc_check_lost_events(u32 curr_wr_ptr, u32 prev_wr_ptr, int *count)
{
u32 dacapo_prev, dacapo_curr;
int dacapo_diff, ptr_diff = 0;
dacapo_prev = prev_wr_ptr >> 12;
dacapo_curr = curr_wr_ptr >> 12;
curr_wr_ptr &= 0x00fff; /* Pick last 12 bits */
curr_wr_ptr >>= 4; /* Remove last 4 bits. */
prev_wr_ptr &= 0x00fff; /* Pick last 12 bits */
prev_wr_ptr >>= 4; /* Remove last 4 bits. */
dacapo_diff = dacapo_curr - dacapo_prev;
switch(dacapo_diff) {
case 1:
ptr_diff = curr_wr_ptr - prev_wr_ptr;
if (ptr_diff > 0) {
*count = TDC_EVENT_BUFFER_SIZE;
return 1; /* We lost data */
}
*count = curr_wr_ptr - prev_wr_ptr + TDC_EVENT_BUFFER_SIZE;
break;
case 0:
/* We didn't lose data */
*count = curr_wr_ptr - prev_wr_ptr;
break;
default:
/* We lost data for sure. Notify to the user */
*count = TDC_EVENT_BUFFER_SIZE;
return 1;
}
return 0;
}
static inline int tdc_is_valid_pulse_width(struct tdc_event rising, struct tdc_event falling)
{
uint64_t up, down;
/* Convert the timestamp to picoseconds */
up = rising.coarse_time * 8000000000 + rising.fine_time * 81;
down = (falling.local_utc - rising.local_utc) * 1000000000000 +
falling.coarse_time * 8000000000 + rising.fine_time * 81;
/* Valid pulse width if it is higher than 100 ns */
return (down - up > 100000);
}
static void tdc_fmc_irq_work(struct work_struct *work)
{
struct spec_tdc *tdc = container_of(work, struct spec_tdc, irq_work);
u32 curr_wr_ptr, prev_wr_ptr;
int ret, dacapo_flag, count, rd_ptr, chan;
struct tdc_event *events, *tmp_data;
struct zio_control *ctrl;
struct zio_ti *ti;
events = kzalloc(TDC_EVENT_BUFFER_SIZE*sizeof(struct tdc_event), GFP_KERNEL);
if(!events) {
dev_err(&tdc->fmc->dev, "error allocating memory for the events\n");
return;
}
/* Setup DMA transfer. Only one process can do it */
mutex_lock(&fmc_dma_lock);
curr_wr_ptr = tdc_get_circular_buffer_wr_pointer(tdc);
if(curr_wr_ptr == tdc->wr_pointer) {
mutex_unlock(&fmc_dma_lock);
goto dma_out; /* No new events happened */
}
prev_wr_ptr = tdc->wr_pointer;
ret = tdc_dma_setup(tdc, 0, (unsigned long)events,
TDC_EVENT_BUFFER_SIZE*sizeof(struct tdc_event));
if (ret) {
dev_err(&tdc->fmc->dev, "error in DMA setup\n");
mutex_unlock(&fmc_dma_lock);
goto dma_out;
}
/* Start DMA transfer and wait for it */
tdc_dma_start(tdc);
/* Wait for the end of DMA transfer. Timeout of a second to avoid locks */
ret = wait_event_timeout(fmc_wait_dma, atomic_read(&fmc_dma_end), HZ);
/* DMA happened */
atomic_set(&fmc_dma_end, 0);
/* In case of timeout, notify the user */
if(!ret) {
dev_err(&tdc->fmc->dev, "timeout in DMA transfer.\n");
mutex_unlock(&fmc_dma_lock);
goto dma_out;
}
/* Check the status of the DMA */
ret = readl(tdc->base + TDC_DMA_STAT_R);
if((ret & TDC_DMA_STAT_ERR) || (ret & TDC_DMA_STAT_ABORT)) {
dev_err(&tdc->fmc->dev, "error in DMA transfer\n");
mutex_unlock(&fmc_dma_lock);
goto dma_out;
}
tdc->wr_pointer = curr_wr_ptr;
mutex_unlock(&fmc_dma_lock);
/* Process the data */
dacapo_flag = tdc_fmc_check_lost_events(curr_wr_ptr, prev_wr_ptr, &count);
/* Start reading in the oldest event */
if(count == TDC_EVENT_BUFFER_SIZE)
rd_ptr = (curr_wr_ptr >> 4) & 0x000ff; /* The oldest is curr_wr_ptr */
else
rd_ptr = (prev_wr_ptr >> 4) & 0x000ff; /* The oldest is prev_wr_ptr */
for ( ; count > 0; count--) {
tmp_data = &events[rd_ptr];
/* Check which channel to deliver the data */
chan = tmp_data->metadata & TDC_EVENT_CHANNEL_MASK;
/* Add the DaCapo flag to notify the user */
tdc->event[chan].dacapo_flag = dacapo_flag;
/* Check if the event is due to rising edge or falling edge */
if (tmp_data->metadata & TDC_EVENT_SLOPE_MASK)
/* Copy the data as it is a rising edge one */
tdc->event[chan].data = *tmp_data;
else {
/* Check pulse width using the falling edge event */
if(tdc_is_valid_pulse_width(tdc->event[chan].data,
*tmp_data)) {
/* Valid pulse width */
ctrl = tdc->zdev->cset[chan].chan->current_ctrl;
ti = tdc->zdev->cset[chan].ti;
ctrl->ssize = 1; /* one event */
ctrl->nsamples = 1; /* one event */
ctrl->nbits = 0; /* no sample data. Only metadata */
ti->tstamp.tv_sec = tdc->event[chan].data.local_utc;
ti->tstamp.tv_nsec = tdc->event[chan].data.coarse_time;
ti->tstamp_extra = tdc->event[chan].data.fine_time;
ctrl->flags = tdc->event[chan].dacapo_flag; /* XXX: Is it OK here? */
ctrl->reserved = tdc->event[chan].data.metadata;
zio_trigger_data_done(&tdc->zdev->cset[chan]);
}
}
rd_ptr = (rd_ptr + 1) % TDC_EVENT_BUFFER_SIZE;
}
dma_out:
kfree(events);
}
irqreturn_t tdc_fmc_irq_handler(int irq, void *dev_id)
{
struct fmc_device *fmc = dev_id;
struct spec_dev *spec = fmc->carrier_data;
struct spec_tdc *tdc = fmc->mezzanine_data;
u32 irq_code;
/* Check the source of the interrupt */
irq_code = readl(fmc->base + TDC_IRQ_STATUS_REG);
/* Tstamp threshold or time threshold */
if((irq_code & TDC_IRQ_TDC_TSTAMP) ||
(irq_code & TDC_IRQ_TDC_TIME_THRESH)) {
queue_work(tdc_workqueue, &tdc->irq_work);
}
/* DMA interrupt */
if((irq_code & TDC_IRQ_GNUM_CORE_0) ||
(irq_code & TDC_IRQ_GNUM_CORE_1)) {
dma_sync_single_for_cpu(&spec->pdev->dev, tdc->rx_dma,
TDC_EVENT_BUFFER_SIZE*sizeof(struct tdc_event),
DMA_FROM_DEVICE);
dma_unmap_single(&spec->pdev->dev, tdc->rx_dma,
TDC_EVENT_BUFFER_SIZE*sizeof(struct tdc_event),
DMA_FROM_DEVICE);
/* Wake up the threads waiting for the DMA transfer */
atomic_set(&fmc_dma_end, 1);
wake_up(&fmc_wait_dma);
}
/* Acknowledge the IRQ and exit */
writel(irq_code, fmc->base + TDC_IRQ_STATUS_REG);
fmc->op->irq_ack(fmc);
return IRQ_HANDLED;
}
static int tdc_fmc_get_device_lun(struct fmc_device *dev)
{
struct spec_dev *spec;
struct pci_dev *pdev;
int i;
spec = dev->carrier_data;
pdev = spec->pdev;
for (i = 0; i < nlun; i++) {
if (PCI_SLOT(pdev->devfn) == slot[i] &&
pdev->bus->number == bus[i]) {
pr_info("Matched LUN %d for device in bus %d and slot %d\n",
lun[i], bus[i], slot[i]);
return lun[i];
}
}
pr_err("No LUN found for device in bus %d and slot %d\n",
pdev->bus->number, PCI_SLOT(pdev->devfn));
return -ENODEV;
}
int tdc_fmc_probe(struct fmc_device *dev)
{
struct spec_tdc *tdc;
struct spec_dev *spec;
int ret, dev_lun;
char gateware_path[128];
if(strcmp(dev->carrier_name, "SPEC") != 0)
return -ENODEV;
dev_lun = tdc_fmc_get_device_lun(dev);
if (dev_lun < 0)
return dev_lun;
sprintf(gateware_path, "fmc/%s", gateware);
pr_info("Using gateware %s\n", gateware_path);
ret = dev->op->reprogram(dev, &tdc_fmc_driver, gateware_path);
if (ret < 0) {
pr_err("%s: error reprogramming the FPGA\n", __func__);
return -ENODEV;
}
tdc = kzalloc(sizeof(struct spec_tdc), GFP_KERNEL);
if (!tdc) {
pr_err("%s: can't allocate device\n", __func__);
return -ENOMEM;
}
/* Initialize structures */
spec = dev->carrier_data;
tdc->spec = spec;
dev->mezzanine_data = tdc;
tdc->lun = dev_lun;
tdc->fmc = dev;
tdc->base = dev->base; /* BAR 0 */
tdc->gn412x_regs = spec->remap[2]; /* BAR 4 */
tdc->wr_pointer = 0;
/* Setup the Gennum 412x local clock frequency */
tdc_fmc_gennum_setup_local_clock(tdc, 160);
/* Reset FPGA to load the firmware */
tdc_fmc_fw_reset(tdc);
/* Setup default config to ACAM chip */
tdc_acam_set_default_config(tdc);
/* Reset ACAM chip */
tdc_acam_reset(tdc);
/* Initialice UTC time */
tdc_set_local_utc_time(tdc);
/* Initialize DAC */
tdc_set_dac_word(tdc, 0xA8F5);
/* Initialize timestamp threshold */
tdc_set_irq_tstamp_thresh(tdc, DEFAULT_TSTAMP_THRESH);
/* Initialize time threshold */
tdc_set_irq_time_thresh(tdc, DEFAULT_TIME_THRESH);
/* Prepare the irq work */
INIT_WORK(&tdc->irq_work, tdc_fmc_irq_work);
/* Setup GPIO to have IRQ */
dev->op->gpio_config(dev, &tdc_gpio, 1);
/* Clear IRQ */
writel(0xF, tdc->base + TDC_IRQ_STATUS_REG);
/* Request the IRQ */
dev->op->irq_request(dev, tdc_fmc_irq_handler, "spec-tdc", IRQF_SHARED);
/* Enable IRQ */
writel(0xF, tdc->base + TDC_IRQ_ENABLE_REG);
return tdc_zio_register_device(tdc);
}
int tdc_fmc_remove(struct fmc_device *dev)
{
struct spec_tdc *tdc = dev->mezzanine_data;
cancel_work_sync(&tdc->irq_work);
tdc->fmc->op->irq_free(tdc->fmc);
tdc_zio_remove(tdc);
kfree(tdc);
return 0;
}
int tdc_fmc_init(void)
{
tdc_workqueue = create_singlethread_workqueue(KBUILD_MODNAME);
atomic_set(&fmc_dma_end, 0);
tdc_fmc_driver.version = FMC_VERSION;
tdc_fmc_driver.probe = tdc_fmc_probe;
tdc_fmc_driver.remove = tdc_fmc_remove;
fmc_driver_register(&tdc_fmc_driver);
return 0;
}
void tdc_fmc_exit(void)
{
destroy_workqueue(tdc_workqueue);
fmc_driver_unregister(&tdc_fmc_driver);
}
/*
* ZIO support for tdc driver
*
* Copyright (C) 2012 CERN (http://www.cern.ch)
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/zio.h>
#include <linux/zio-buffer.h>
#include <linux/zio-trigger.h>
#include "spec.h"
#include "tdc.h"
#include "hw/tdc_regs.h"
#define _RW_ (S_IRUGO | S_IWUGO)
static int tdc_zio_raw_io(struct zio_cset *cset);
/* The sample size. Mandatory, device-wide */
static ZIO_ATTR_DEFINE_STD(ZIO_DEV, tdc_zattr_dev_std) = {
ZIO_ATTR(zdev, ZIO_ATTR_NBITS, S_IRUGO, 0, 32),
};
static struct zio_attribute tdc_zattr_dev[] = {
ZIO_ATTR_EXT("version", S_IRUGO, TDC_ATTR_DEV_VERSION, TDC_VERSION),
ZIO_ATTR_EXT("tstamp_thresh", _RW_,
TDC_ATTR_DEV_TSTAMP_THRESH, DEFAULT_TSTAMP_THRESH),
ZIO_ATTR_EXT("time_thresh", _RW_,
TDC_ATTR_DEV_TIME_THRESH, DEFAULT_TIME_THRESH),
ZIO_ATTR_EXT("current_utc_time", S_IRUGO, TDC_ATTR_DEV_CURRENT_UTC, 0),
ZIO_ATTR_EXT("set_utc_time", S_IWUGO, TDC_ATTR_DEV_SET_UTC, 0),
ZIO_ATTR_EXT("channel_term", _RW_, TDC_ATTR_DEV_INPUT_ENABLED, 0),
ZIO_ATTR_EXT("dac_word", _RW_, TDC_ATTR_DEV_DAC_WORD, 0),
ZIO_ATTR_EXT("activate_acquisition", _RW_,
TDC_ATTR_DEV_ACTIVATE_ACQUISITION, 0),
ZIO_ATTR_EXT("get_wr_pointer", _RW_,
TDC_ATTR_DEV_GET_POINTER, 0),
ZIO_ATTR_EXT("lun", S_IRUGO, TDC_ATTR_DEV_LUN, 1),
ZIO_ATTR_EXT("clear_dacapo_flag", _RW_,
TDC_ATTR_DEV_CLEAR_DACAPO_FLAG, 0),
ZIO_ATTR_EXT("reset_acam", _RW_,
TDC_ATTR_DEV_RESET_ACAM, 0),
};
static struct zio_cset tdc_cset[] = {
{
ZIO_SET_OBJ_NAME("tdc-cset0"),
.raw_io = tdc_zio_raw_io,
.stop_io = NULL,
.n_chan = 1,
.ssize = 0,
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME |
ZIO_CSET_SELF_TIMED,
.zattr_set = {
.ext_zattr = NULL,
.n_ext_attr = 0,
},
},
{
ZIO_SET_OBJ_NAME("tdc-cset1"),
.raw_io = tdc_zio_raw_io,
.stop_io = NULL,
.n_chan = 1,
.ssize = 0,
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME |
ZIO_CSET_SELF_TIMED,
.zattr_set = {
.ext_zattr = NULL,
.n_ext_attr = 0,
},
},
{
ZIO_SET_OBJ_NAME("tdc-cset2"),
.raw_io = tdc_zio_raw_io,
.stop_io = NULL,
.n_chan = 1,
.ssize = 0,
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME |
ZIO_CSET_SELF_TIMED,
.zattr_set = {
.ext_zattr = NULL,
.n_ext_attr = 0,
},
},
{
ZIO_SET_OBJ_NAME("tdc-cset3"),
.raw_io = tdc_zio_raw_io,
.stop_io = NULL,
.n_chan = 1,
.ssize = 0,
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME |
ZIO_CSET_SELF_TIMED,
.zattr_set = {
.ext_zattr = NULL,
.n_ext_attr = 0,
},
},
{
ZIO_SET_OBJ_NAME("tdc-cset4"),
.raw_io = tdc_zio_raw_io,
.stop_io = NULL,
.n_chan = 1,
.ssize = 0,
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME |
ZIO_CSET_SELF_TIMED,
.zattr_set = {
.ext_zattr = NULL,
.n_ext_attr = 0,
},
},
};
static int tdc_zio_conf_set(struct device *dev,
struct zio_attribute *zattr,
uint32_t usr_val)
{
struct zio_device *zdev;
struct zio_attribute *attr;
struct spec_tdc *tdc;
zdev = to_zio_dev(dev);
attr = zdev->zattr_set.ext_zattr;
tdc = zdev->priv_d;
switch (zattr->id) {
case TDC_ATTR_DEV_TSTAMP_THRESH:
tdc_set_irq_tstamp_thresh(tdc, usr_val);
break;
case TDC_ATTR_DEV_TIME_THRESH:
tdc_set_irq_time_thresh(tdc, usr_val);
break;
case TDC_ATTR_DEV_CURRENT_UTC:
break;
case TDC_ATTR_DEV_SET_UTC:
if (usr_val == -1)
tdc_set_local_utc_time(tdc);
else
tdc_set_utc_time(tdc, usr_val);
break;
case TDC_ATTR_DEV_INPUT_ENABLED:
tdc_set_input_enable(tdc, usr_val);
break;
case TDC_ATTR_DEV_DAC_WORD:
tdc_set_dac_word(tdc, usr_val);
break;
case TDC_ATTR_DEV_ACTIVATE_ACQUISITION:
if (usr_val) {
atomic_set(&tdc->busy, 1);
return tdc_activate_acquisition(tdc);
} else {
atomic_set(&tdc->busy, 0);
tdc_deactivate_acquisition(tdc);
}
break;
case TDC_ATTR_DEV_CLEAR_DACAPO_FLAG:
tdc_clear_da_capo_flag(tdc);
break;
case TDC_ATTR_DEV_RESET_ACAM:
tdc_acam_set_default_config(tdc);
tdc_acam_reset(tdc);
break;
default:
return -EINVAL;
}
return 0;
}
static int tdc_zio_info_get(struct device *dev,
struct zio_attribute *zattr,
uint32_t *usr_val)
{
struct zio_device *zdev;
struct zio_attribute *attr;
struct spec_tdc *tdc;
zdev = to_zio_dev(dev);
attr = zdev->zattr_set.ext_zattr;
tdc = zdev->priv_d;
switch (zattr->id) {
case TDC_ATTR_DEV_TSTAMP_THRESH:
*usr_val = tdc_get_irq_tstamp_thresh(tdc);
break;
case TDC_ATTR_DEV_TIME_THRESH:
*usr_val = tdc_get_irq_time_thresh(tdc);
break;
case TDC_ATTR_DEV_CURRENT_UTC:
*usr_val = tdc_get_current_utc_time(tdc);
break;
case TDC_ATTR_DEV_SET_UTC:
break;
case TDC_ATTR_DEV_INPUT_ENABLED:
*usr_val = tdc_get_input_enable(tdc);
break;
case TDC_ATTR_DEV_DAC_WORD:
*usr_val = tdc_get_dac_word(tdc);
break;
case TDC_ATTR_DEV_ACTIVATE_ACQUISITION:
*usr_val = atomic_read(&tdc->busy);
break;
case TDC_ATTR_DEV_GET_POINTER:
*usr_val = tdc_get_circular_buffer_wr_pointer(tdc);
break;
case TDC_ATTR_DEV_LUN:
*usr_val = tdc->lun;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct zio_sysfs_operations tdc_zio_s_op = {
.conf_set = tdc_zio_conf_set,
.info_get = tdc_zio_info_get,
};
static struct zio_device tdc_tmpl = {
.owner = THIS_MODULE,
.s_op = &tdc_zio_s_op,
.cset = tdc_cset,
.n_cset = ARRAY_SIZE(tdc_cset),
.zattr_set = {
.std_zattr = tdc_zattr_dev_std,
.ext_zattr = tdc_zattr_dev,
.n_ext_attr = ARRAY_SIZE(tdc_zattr_dev),
},
};
static const struct zio_device_id tdc_table[] = {
{"tdc", &tdc_tmpl},
{},
};
static int tdc_zio_raw_io(struct zio_cset *cset)
{
/* data_done will come whenever the data is available */
return -EAGAIN;
}
static int tdc_zio_probe(struct zio_device *zdev)
{
/* TODO: implement something if needed. If not, delete this function */
pr_err("%s: register new device\n", __func__);
return 0;
}
static struct zio_driver tdc_zdrv = {
.driver = {
.name = "tdc",
.owner = THIS_MODULE,
},
.id_table = tdc_table,
.probe = tdc_zio_probe,
};
/* Copied from zio-sys.c. This works because ZIO only supports one children */
static int __tdc_match_child(struct device *dev, void *data)
{
// if (dev->type == &zobj_device_type)
return 1;
// return 0;
}
int tdc_zio_register_device(struct spec_tdc *tdc)
{
int err = 0;
struct pci_dev *pdev;
int dev_id;
struct device *dev;
tdc->hwzdev = zio_allocate_device();
if (IS_ERR(tdc->hwzdev))
return PTR_ERR(tdc->hwzdev);
/* Mandatory fields */
tdc->hwzdev->owner = THIS_MODULE;
tdc->hwzdev->priv_d = tdc;
/* Our dev_id is bus+devfn */
pdev = tdc->spec->pdev;
dev_id = (pdev->bus->number << 8) | pdev->devfn;
err = zio_register_device(tdc->hwzdev, "tdc", dev_id);
if (err) {
zio_free_device(tdc->hwzdev);
return err;
}
dev = device_find_child(&tdc->hwzdev->head.dev, NULL, __tdc_match_child);
if (!dev) {
dev_err(&tdc->spec->pdev->dev, "Child device not found!!\n");
return -ENODEV;
}
tdc->zdev = to_zio_dev(dev);
return 0;
}
void tdc_zio_remove(struct spec_tdc *tdc)
{
zio_unregister_device(tdc->hwzdev);
zio_free_device(tdc->hwzdev);
}
int tdc_zio_init(void)
{
return zio_register_driver(&tdc_zdrv);
}
void tdc_zio_exit(void)
{
zio_unregister_driver(&tdc_zdrv);
}
#ifndef __FMC_TDC_H__
#define __FMC_TDC_H__
#define TDC_VERSION 1
#define MAX_DEVICES 16
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/semaphore.h>
#include "hw/tdc_regs.h"
/* module parameters */
extern int lun[MAX_DEVICES];
extern unsigned int nlun;
extern int bus[MAX_DEVICES];
extern unsigned int nbus;
extern int slot[MAX_DEVICES];
extern unsigned int nslot;
extern char *gateware;
#define DEFAULT_TIME_THRESH 0x10
#define DEFAULT_TSTAMP_THRESH 0x10
struct tdc_event {
u32 fine_time; /* In BIN (81 ps resolution) */
u32 coarse_time; /* 8 ns resolution */
u32 local_utc; /* 1 second resolution */
u32 metadata;
} __packed;
struct tdc_event_buffer {
struct tdc_event data;
int dacapo_flag;
};
struct tdc_acam_cfg {
u32 edge_config; /* ACAM reg. 0 */
u32 channel_adj; /* ACAM reg. 1 */
u32 mode_enable; /* ACAM reg. 2 */
u32 resolution; /* ACAM reg. 3 */
u32 start_timer_set; /* ACAM reg. 4 */
u32 start_retrigger; /* ACAM reg. 5 */
u32 lf_flags_level; /* ACAM reg. 6 */
u32 pll; /* ACAM reg. 7 */
u32 err_flag_cfg; /* ACAM reg. 11 */
u32 int_flag_cfg; /* ACAM reg. 12 */
u32 ctrl_16_bit_mode; /* ACAM reg. 14 */
};
/* Device-wide ZIO attributes */
enum tdc_zattr_dev_idx {
TDC_ATTR_DEV_VERSION = 0,
TDC_ATTR_DEV_TSTAMP_THRESH,
TDC_ATTR_DEV_TIME_THRESH,
TDC_ATTR_DEV_CURRENT_UTC,
TDC_ATTR_DEV_SET_UTC,
TDC_ATTR_DEV_INPUT_ENABLED,
TDC_ATTR_DEV_DAC_WORD,
TDC_ATTR_DEV_ACTIVATE_ACQUISITION,
TDC_ATTR_DEV_GET_POINTER,
TDC_ATTR_DEV_LUN,
TDC_ATTR_DEV_CLEAR_DACAPO_FLAG,
TDC_ATTR_DEV_RESET_ACAM,
TDC_ATTR_DEV__LAST,
};
struct spec_tdc {
uint32_t lun;
struct fmc_device *fmc;
struct spec_dev *spec;
struct zio_device *zdev, *hwzdev;
unsigned char __iomem *base; /* regs files are byte-oriented */
unsigned char __iomem *gn412x_regs;
atomic_t busy; /* whether the device is acquiring data */
u32 wr_pointer;
dma_addr_t rx_dma;
struct work_struct irq_work;
struct tdc_event_buffer event[TDC_CHAN_NUMBER];
};
/* ZIO helper functions */
extern int tdc_zio_register_device(struct spec_tdc *tdc);
extern void tdc_zio_remove(struct spec_tdc *tdc);
extern int tdc_zio_init(void);
extern void tdc_zio_exit(void);
/* FMC helper functions */
extern int tdc_fmc_init(void);
extern void tdc_fmc_exit(void);
/* ACAM helper functions */
extern void tdc_acam_reset(struct spec_tdc *tdc);
extern int tdc_acam_load_config(struct spec_tdc *tdc, struct tdc_acam_cfg *cfg);
extern int tdc_acam_set_default_config(struct spec_tdc *tdc);
extern int tdc_acam_get_config(struct spec_tdc *tdc, struct tdc_acam_cfg *cfg);
extern u32 tdc_acam_status(struct spec_tdc *tdc);
extern u32 tdc_acam_read_ififo1(struct spec_tdc *tdc);
extern u32 tdc_acam_read_ififo2(struct spec_tdc *tdc);
extern u32 tdc_acam_read_start01(struct spec_tdc *tdc);
/* DMA helper functions */
extern int tdc_dma_setup(struct spec_tdc *tdc, unsigned long src, unsigned long dst, int size);
extern int tdc_dma_start(struct spec_tdc *tdc);
/* Core functions */
extern int tdc_fmc_probe(struct fmc_device *dev);
extern int tdc_fmc_remove(struct fmc_device *dev);
extern void tdc_set_local_utc_time(struct spec_tdc *tdc);
extern void tdc_set_utc_time(struct spec_tdc *tdc, u32 value);
extern void tdc_set_input_enable(struct spec_tdc *tdc, u32 value);
extern void tdc_set_irq_tstamp_thresh(struct spec_tdc *tdc, u32 val);
extern void tdc_set_irq_time_thresh(struct spec_tdc *tdc, u32 val);
extern void tdc_set_dac_word(struct spec_tdc *tdc, u32 val);
extern u32 tdc_get_input_enable(struct spec_tdc *tdc);
extern u32 tdc_get_irq_tstamp_thresh(struct spec_tdc *tdc);
extern u32 tdc_get_irq_time_thresh(struct spec_tdc *tdc);
extern u32 tdc_get_current_utc_time(struct spec_tdc *tdc);
extern u32 tdc_get_circular_buffer_wr_pointer(struct spec_tdc *tdc);
extern u32 tdc_get_dac_word(struct spec_tdc *tdc);
extern void tdc_clear_da_capo_flag(struct spec_tdc *tdc);
extern int tdc_activate_acquisition(struct spec_tdc *tdc);
extern void tdc_deactivate_acquisition(struct spec_tdc *tdc);
#endif
LINUX ?= /lib/modules/$(shell uname -r)/build
ZIO ?= $(M)/../zio
FMC_BUS ?= $(M)/../fmc-bus
SPEC ?= $(M)/../spec-sw
KBUILD_EXTRA_SYMBOLS := $(ZIO)/Module.symvers $(SPEC)/kernel/Module.symvers $(FMC_BUS)/kernel/Module.symvers
ccflags-y = -I$(ZIO)/include -I$M -I$(FMC_BUS)/kernel/include -I$(FMC_BUS)/kernel/include/linux -I$(FMC_BUS)/sdb-lib -I$(SPEC)/kernel
#ccflags-y += -DDEBUG
subdirs-ccflags-y = $(ccflags-y)
obj-m := fmc-tdc.o
fmc-tdc-objs = acam.o calibration.o ft-spec.o ft-core.o onewire.o time.o ft-irq.o ft-zio.o ../fmc-bus/sdb-lib/access.o ../fmc-bus/sdb-lib/glue.o
all: modules
modules_install clean modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) $@
/*
* ACAM TDC-GPX routines support for fmc-tdc driver.
*
* Copyright (C) 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 Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/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;
pr_debug("%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++)
{
if(acam_config[i].reg == 0)
ft->acam_r0 = acam_config[i].value;
ft_writel(ft, acam_config[i].value, TDC_REG_ACAM_CONFIG (acam_config[i].reg));
}
ft_writel(ft, TDC_CTRL_LOAD_ACAM_CFG, TDC_REG_CTRL);
udelay(100);
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_info(&ft->fmc->dev, "%s: ACAM initialization OK.\n", __func__);
return 0;
}
}
dev_err(&ft->fmc->dev, "%s: ACAM PLL doesn't lock\n", __func__);
return -EIO;
}
void dump_acam_regs(struct fmctdc_dev *ft)
{
int i;
ft_writel(ft, TDC_CTRL_READ_ACAM_CFG, TDC_REG_CTRL);
udelay(1000);
for(i = 0; i <= 14 ; i++)
printk("ACAM reg %d: 0x%x\n", i, ft_readl(ft, TDC_REG_ACAM_READBACK(i)));
}
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>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/firmware.h>
#include <linux/jhash.h>
#include "libsdbfs.h"
#include "fmc-tdc.h"
/* 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 */
};
/* sdbfs-related function */
static int ft_read_calibration_eeprom(struct fmc_device *fmc, void *buf, int length)
{
int i, ret = 0;
static struct sdbfs fs;
fs.data = fmc->eeprom;
fs.datalen = fmc->eeprom_len;
/* Look for sdb entry point */
for (i = 0x40; i < fmc->eeprom_len - 0x40; i += 0x40) {
fs.entrypoint = i;
ret = sdbfs_dev_create(&fs, 0);
if (ret == 0)
break;
}
if (ret)
return ret;
/* Open "cali" as a device id, vendor is "FileData" -- big endian */
ret = sdbfs_open_id(&fs, 0x61746144656c6946LL, 0x696c6163);
if (ret)
return ret;
ret = sdbfs_fread(&fs, 0, buf, length);
sdbfs_dev_destroy(&fs);
return ret;
}
/* This is the only thing called by outside */
int ft_handle_eeprom_calibration(struct fmctdc_dev *ft)
{
struct ft_calibration *calib;
struct device *d = &ft->fmc->dev;
int i;
u32 raw_calib[5];
/* Retrieve and validate the calibration */
calib = &ft->calib;
memcpy(calib, &default_calibration, sizeof(struct ft_calibration));
i = ft_read_calibration_eeprom(ft->fmc, raw_calib, sizeof(raw_calib));
/* fixme: there is no calibration validation. Change format? */
/* Translate offsets (they are referenced to channel 0 and in 1/100s of picosecond) to
offsets that could be added to TDC timestamps right away (picoseconds, referenced to WR) */
calib->zero_offset[0] = 0;
for(i = FT_CH_1 + 1; i < FT_NUM_CHANNELS; i++)
calib->zero_offset[i] = le32_to_cpu(raw_calib[i - 1]) / 100 - calib->zero_offset[0];
calib->vcxo_default_tune = le32_to_cpu(raw_calib[4]);
for (i = 0; i < ARRAY_SIZE(calib->zero_offset); i++)
dev_info(d, "calib: zero_offset[%i] = %li\n", i,
(long)calib->zero_offset[i]);
dev_info(d, "calib: vcxo_default_tune %i\n", calib->vcxo_default_tune);
return 0;
}
/*
* 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>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#ifndef __FMC_TDC_H__
#define __FMC_TDC_H__
#ifdef __KERNEL__ /* All the rest is only of kernel users */
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/fmc.h>
#include <linux/version.h>
#endif
#define FT_VERSION 2 /* version of the driver */
/* 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
enum ft_channel_flags {
FT_FLAG_CH_TERMINATED = 0,
FT_FLAG_CH_DO_INPUT,
FT_FLAG_CH_INPUT_READY
};
enum ft_zattr_dev_idx {
FT_ATTR_DEV_VERSION = 0,
FT_ATTR_DEV_SECONDS,
FT_ATTR_DEV_COARSE,
FT_ATTR_DEV_COMMAND, /* see below for commands */
FT_ATTR_DEV_TEMP,
FT_ATTR_DEV_RESERVE_6,
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_SEQ,
FT_ATTR_TDC_TERMINATION,
FT_ATTR_TDC_OFFSET,
FT_ATTR_TDC_USER_OFFSET,
FT_ATTR_TDC_PURGE_FIFO,
FT_ATTR_TDC__LAST,
};
enum ft_command {
FT_CMD_WR_ENABLE = 0,
FT_CMD_WR_DISABLE,
FT_CMD_WR_QUERY,
FT_CMD_IDENTIFY_ON,
FT_CMD_IDENTIFY_OFF
};
/* rest of the file is kernel-only */
#ifdef __KERNEL__
/* Carrier-specific operations (gateware does not fully decouple carrier specific stuff, such as
DMA or resets, from mezzanine-specific operations). */
struct fmctdc_dev;
struct ft_carrier_specific {
char *gateware_name;
int (*init)(struct fmctdc_dev *);
int (*reset_core)(struct fmctdc_dev *);
int (*copy_timestamps) (struct fmctdc_dev *, int base_addr, int size, void *dst );
int (*setup_irqs)(struct fmctdc_dev *, irq_handler_t handler);
int (*disable_irqs)(struct fmctdc_dev *);
int (*ack_irq)(struct fmctdc_dev *);
};
struct ft_calibration { /* All of these are big endian */
/* Input-to-internal-timebase offset in ps. Add to all timestamps. */
int32_t zero_offset[5];
/* Default DAC value for VCXO. Set during init and for local timing */
uint32_t vcxo_default_tune;
};
/* Hardware TDC timestamp */
struct ft_hw_timestamp {
uint32_t bins; /* In BIN (81 ps resolution) */
uint32_t coarse; /* 8 ns resolution */
uint32_t utc; /* 1 second resolution */
uint32_t metadata; /* channel, polarity, etc. */
} __packed;
/* White Rabbit timestamp */
struct ft_wr_timestamp {
uint64_t seconds;
uint32_t coarse;
uint32_t frac;
int seq_id;
int channel;
};
struct ft_sw_fifo {
unsigned long head, tail, count, size;
struct ft_wr_timestamp *t;
};
struct ft_channel_state {
unsigned long flags;
int expected_edge;
int cur_seq_id;
int32_t user_offset;
struct ft_wr_timestamp prev_ts;
struct ft_sw_fifo fifo;
};
/* Main TDC device context */
struct fmctdc_dev {
spinlock_t lock;
int ft_core_base;
int ft_i2c_base;
int ft_owregs_base;
int ft_dma_base;
int ft_carrier_base;
int ft_irq_base;
struct fmc_device *fmc;
struct zio_device *zdev, *hwzdev;
struct timer_list temp_timer;
struct ft_carrier_specific *carrier_specific;
void *carrier_data;
struct ft_calibration calib;
struct tasklet_struct readout_tasklet;
int initialized;
int wr_mode_active;
uint8_t ds18_id[8];
unsigned long next_t;
int temp; /* temperature: scaled by 4 bits */
int temp_ready;
int verbose;
uint32_t acam_r0;
struct ft_channel_state channels[FT_NUM_CHANNELS];
uint32_t cur_wr_ptr, prev_wr_ptr;
struct ft_hw_timestamp *raw_events;
};
extern struct ft_carrier_specific ft_carrier_spec;
static inline uint32_t ft_readl(struct fmctdc_dev *ft, unsigned long reg)
{
return fmc_readl(ft->fmc, ft->ft_core_base + reg);
}
static inline void ft_writel(struct fmctdc_dev *ft, uint32_t v, unsigned long reg)
{
fmc_writel(ft->fmc, v, ft->ft_core_base + reg);
}
int ft_acam_init(struct fmctdc_dev *ft);
void ft_acam_exit(struct fmctdc_dev *ft);
int ft_acam_enable_channel(struct fmctdc_dev *ft, int channel, int enable);
int ft_acam_enable_termination(struct fmctdc_dev *dev, int channel, int enable);
int ft_acam_enable_acquisition(struct fmctdc_dev *ft, int enable);
int ft_onewire_init(struct fmctdc_dev *ft);
void ft_onewire_exit(struct fmctdc_dev *ft);
int ft_read_temp(struct fmctdc_dev *ft, int verbose);
int ft_pll_init(struct fmctdc_dev *ft);
void ft_pll_exit(struct fmctdc_dev *ft);
void ft_ts_apply_offset(struct ft_wr_timestamp *ts, int32_t offset_picos );
void ft_ts_sub (struct ft_wr_timestamp *a, struct ft_wr_timestamp *b);
int ft_set_tai_time(struct fmctdc_dev *ft, uint64_t seconds, uint32_t coarse);
int ft_get_tai_time(struct fmctdc_dev *ft, uint64_t *seconds, uint32_t *coarse);
int ft_enable_wr_mode (struct fmctdc_dev *ft, int enable);
int ft_check_wr_mode (struct fmctdc_dev *ft);
int ft_handle_eeprom_calibration(struct fmctdc_dev *ft);
int ft_irq_init(struct fmctdc_dev *ft);
void ft_irq_exit(struct fmctdc_dev *ft);
int ft_time_init(struct fmctdc_dev *ft);
void ft_time_exit(struct fmctdc_dev *ft);
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);
struct zio_channel;
int ft_read_sw_fifo(struct fmctdc_dev *ft, int channel, struct zio_channel *chan);
int ft_enable_termination(struct fmctdc_dev *ft, int channel, int enable);
#endif
#endif
/*
* 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>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#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/fmc.h>
#include <linux/fmc-sdb.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
/* Module parameters */
static int ft_verbose = 0;
module_param_named(verbose, ft_verbose, int, 0444);
static struct fmc_driver ft_drv; /* forward declaration */
FMC_PARAM_BUSID(ft_drv);
FMC_PARAM_GATEWARE(ft_drv);
static int ft_show_sdb;
module_param_named(show_sdb, ft_show_sdb, int, 0444);
static int ft_buffer_size = 8192;
module_param_named(buffer_size, ft_buffer_size, int, 0444);
static void ft_timer_handler(unsigned long arg)
{
struct fmctdc_dev *ft = (struct fmctdc_dev *) arg;
ft_read_temp(ft, ft->verbose);
mod_timer(&ft->temp_timer, jiffies + 5 * HZ);
}
static int ft_init_channel (struct fmctdc_dev *ft, int channel)
{
struct ft_channel_state *st = &ft->channels[channel - 1];
st->expected_edge = 1;
st->fifo.size = ft_buffer_size;
st->fifo.t = kmalloc(sizeof(struct ft_wr_timestamp) * st->fifo.size, GFP_KERNEL);
if(!st->fifo.t)
return -ENOMEM;
return 0;
}
static void ft_purge_fifo (struct fmctdc_dev *ft, int channel)
{
struct ft_channel_state *st = &ft->channels[channel - 1];
spin_lock(&ft->lock);
st->fifo.head = 0;
st->fifo.tail = 0;
st->fifo.count = 0;
spin_unlock(&ft->lock);
}
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 void ft_enable_acquisition(struct fmctdc_dev *ft, int enable)
{
uint32_t ien, cmd;
int i;
ien = ft_readl(ft, TDC_REG_INPUT_ENABLE);
if(enable)
{
ien |= TDC_INPUT_ENABLE_FLAG;
cmd = TDC_CTRL_EN_ACQ;
} else {
ien &= ~TDC_INPUT_ENABLE_FLAG;
cmd = TDC_CTRL_DIS_ACQ;
}
ft_writel(ft, ien, TDC_REG_INPUT_ENABLE);
ft_writel(ft, TDC_CTRL_CLEAR_DACAPO_FLAG, TDC_REG_CTRL);
ft_writel(ft, cmd, TDC_REG_CTRL);
}
static int ft_channels_init(struct fmctdc_dev *ft)
{
int i, ret;
for (i = FT_CH_1; i <= FT_NUM_CHANNELS; i++)
{
ret = ft_init_channel(ft, i);
if(ret < 0)
return ret;
ft_enable_termination(ft, i, 1);
}
return 0;
}
static void ft_channels_exit(struct fmctdc_dev *ft)
{
int i;
for (i = FT_CH_1; i <= FT_NUM_CHANNELS; i++)
kfree( ft->channels[i - 1].fifo.t);
}
struct ft_modlist {
char *name;
int (*init)(struct fmctdc_dev *);
void (*exit)(struct fmctdc_dev *);
};
static struct ft_modlist init_subsystems [] = {
{ "acam-tdc", ft_acam_init, ft_acam_exit },
{ "onewire", ft_onewire_init, ft_onewire_exit },
{ "time", ft_time_init, ft_time_exit },
{ "channels", ft_channels_init, ft_channels_exit },
{ "zio", ft_zio_init, ft_zio_exit }
};
/* probe and remove are called by the FMC bus core */
int ft_probe(struct fmc_device *fmc)
{
struct ft_modlist *m;
struct fmctdc_dev *ft;
struct device *dev = fmc->hwdev;
char *fwname;
int i, index, ret;
printk("ft-probe executed\n");
ft = kzalloc(sizeof(struct fmctdc_dev), GFP_KERNEL);
if (!ft) {
dev_err(dev, "can't allocate device\n");
return -ENOMEM;
}
ft->raw_events = kzalloc(sizeof(struct ft_hw_timestamp) * FT_BUFFER_EVENTS, GFP_KERNEL);
if (!ft->raw_events) {
dev_err(dev, "can't allocate buffer\n");
return -ENOMEM;
}
index = fmc->op->validate(fmc, &ft_drv);
if (index < 0) {
dev_info(dev, "not using \"%s\" according to "
"modparam\n", KBUILD_MODNAME);
return -ENODEV;
}
fmc->mezzanine_data = ft;
ft->fmc = fmc;
ft->verbose = ft_verbose;
/* apply carrier-specific hacks and workarounds */
if(!strcmp(fmc->carrier_name, "SPEC"))
ft->carrier_specific = &ft_carrier_spec;
else {
dev_err(dev, "unsupported carrier\n");
return -ENODEV;
}
if (ft_drv.gw_n)
fwname = ""; /* reprogram will pick from module parameter */
else
fwname = ft->carrier_specific->gateware_name;
/* reprogram the card, but do not try to read the SDB.
Everything (including the SDB descriptor/bus logic) is clocked
from the FMC oscillator which needs to be bootstrapped by the gateware with
no possibility for the driver to check if something went wrong... */
ret = fmc_reprogram(fmc, &ft_drv, fwname, -1);
if (ret < 0) {
if (ret == -ESRCH) {
dev_info(dev, "%s: no gateware at index %i\n",
KBUILD_MODNAME, index);
return -ENODEV;
}
return ret; /* other error: pass over */
}
dev_info(dev, "Gateware successfully loaded \n");
ret = ft->carrier_specific->init(ft);
if(ret < 0)
return ret;
ret = ft->carrier_specific->reset_core(ft);
if(ret < 0)
return ret;
ret = fmc_scan_sdb_tree(fmc, 0);
if( ret < 0 )
{
dev_err(dev, "%s: no SDB in the bitstream. Are you sure you've provided the correct one?\n", KBUILD_MODNAME);
return ret;
}
/* FIXME: this is obsoleted by fmc-bus internal parameters */
if (ft_show_sdb)
fmc_show_sdb_tree(fmc);
/* Now use SDB to find the base addresses */
ft->ft_core_base = fmc_find_sdb_device(fmc->sdb, 0xce42, 0x604, NULL);
ft->ft_owregs_base = fmc_find_sdb_device(fmc->sdb, 0xce42, 0x602, NULL);
ft->ft_dma_base = fmc_find_sdb_device(fmc->sdb, 0xce42, 0x601, NULL);
ft->ft_carrier_base = fmc_find_sdb_device(fmc->sdb, 0xce42, 0x603, NULL);
ft->ft_irq_base = fmc_find_sdb_device(fmc->sdb, 0xce42, 0x605, NULL);
spin_lock_init(&ft->lock);
/* Retrieve calibration from the eeprom, and validate */
ret = ft_handle_eeprom_calibration(ft);
if (ret < 0)
return ret;
/* 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;
}
ret = ft_irq_init(ft);
if(ret < 0)
goto err;
ft_enable_acquisition(ft, 1);
/* start temperature polling timer */
setup_timer(&ft->temp_timer, ft_timer_handler, (unsigned long)ft);
mod_timer(&ft->temp_timer, jiffies + 5 * HZ);
ft->initialized = 1;
return 0;
err:
while (--m, --i >= 0)
if (m->exit)
m->exit(ft);
return ret;
}
int ft_remove(struct fmc_device *fmc)
{
struct ft_modlist *m;
struct fmctdc_dev *ft = fmc->mezzanine_data;
int i = ARRAY_SIZE(init_subsystems);
if(!ft->initialized)
return 0; /* No init, no exit */
del_timer_sync(&ft->temp_timer);
ft_enable_acquisition(ft, 0);
ft_irq_exit(ft);
while (--i >= 0) {
m = init_subsystems + i;
if (m->exit)
m->exit(ft);
}
return 0;
}
static struct fmc_fru_id ft_fru_id[] = {
{
.product_name = "FmcTdc1ns5cha",
},
};
static struct fmc_driver ft_drv = {
.version = FMC_VERSION,
.driver.name = KBUILD_MODNAME,
.probe = ft_probe,
.remove = ft_remove,
.id_table = {
.fru_id = ft_fru_id,
.fru_id_nr = ARRAY_SIZE(ft_fru_id),
},
};
static int ft_init(void)
{
int ret;
ret = ft_zio_register();
if (ret < 0)
return ret;
ret = fmc_driver_register(&ft_drv);
if (ret < 0) {
ft_zio_unregister();
return ret;
}
return 0;
}
static void ft_exit(void)
{
fmc_driver_unregister(&ft_drv);
ft_zio_unregister();
}
module_init(ft_init);
module_exit(ft_exit);
MODULE_LICENSE("GPL and additional rights"); /* LGPL */
/*
* Interrupt handling and timestamp readout for fmc-tdc driver.
*
* Copyright (C) 2012-2013 CERN (www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
* Author: Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* Author: Miguel Angel Gomez Sexto <magomez@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#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/spinlock.h>
#include <linux/io.h>
#include <linux/zio.h>
#include <linux/zio-trigger.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
int ft_read_sw_fifo(struct fmctdc_dev *ft, int channel, struct zio_channel *chan)
{
struct zio_control *ctrl;
struct zio_ti *ti = chan->cset->ti;
uint32_t *v;
struct ft_wr_timestamp ts;
struct ft_channel_state *st;
unsigned long flags;
st = &ft->channels[channel - 1];
if (!chan->active_block)
return -EAGAIN;
if (!st->fifo.count)
return -EAGAIN;
/* Copy the sample to local storage */
spin_lock_irqsave(&ft->lock, flags);
ts = st->fifo.t[st->fifo.tail];
st->fifo.tail++;
if(st->fifo.tail == st->fifo.size)
st->fifo.tail = 0;
st->fifo.count--;
spin_unlock_irqrestore(&ft->lock, flags);
/* Write the timestamp in the trigger, it will reach the control */
ti->tstamp.tv_sec = ts.seconds;
ti->tstamp.tv_nsec = ts.coarse * 8;
ti->tstamp_extra = ts.frac;
/*
* This is different than it was. We used to fill the active block,
* but now zio copies chan->current_ctrl at a later time, so we
* must fill _those_ attributes instead
*/
/* The input data is written to attribute values in the active block. */
ctrl = chan->current_ctrl;
ctrl->tstamp.secs = ts.seconds;
ctrl->tstamp.ticks = ts.coarse;
ctrl->tstamp.bins = ts.frac;
ctrl->nsamples = 1;
v = ctrl->attr_channel.ext_val;
v[FT_ATTR_TDC_SECONDS] = ts.seconds;
v[FT_ATTR_TDC_COARSE] = ts.coarse;
v[FT_ATTR_TDC_FRAC] = ts.frac;
v[FT_ATTR_TDC_SEQ] = ts.seq_id;
v[FT_ATTR_TDC_OFFSET] = ft->calib.zero_offset[channel - 1];
v[FT_ATTR_TDC_USER_OFFSET] = st->user_offset;
return 0;
}
static inline void enqueue_timestamp ( struct fmctdc_dev *ft, int channel, struct ft_wr_timestamp *ts )
{
struct ft_sw_fifo *fifo = &ft->channels[channel - 1].fifo;
unsigned long flags;
/* fixme: consider independent locks for each channel. */
spin_lock_irqsave(&ft->lock, flags);
fifo->t[ fifo->head ] = *ts;
fifo->head = (fifo->head + 1) % fifo->size;
if(fifo->count < fifo->size)
fifo->count++;
else {
fifo->tail = (fifo->tail + 1) % fifo->size;
}
spin_unlock_irqrestore(&ft->lock, flags);
}
static inline void process_timestamp( struct fmctdc_dev *ft, struct ft_hw_timestamp *hwts, int dacapo_flag )
{
struct ft_channel_state *st;
struct ft_wr_timestamp ts;
int channel, edge, frac;
channel = (hwts->metadata & 0x7) + 1;
edge = hwts->metadata & (1<<4) ? 1 : 0;
st = &ft->channels[channel - 1];
/* first, convert the timestamp from the HDL units (81 ps bins)
to the WR format (where fractional part is 8 ns rescaled to 4096 units) */
ts.channel = channel;
ts.seconds = hwts->utc;
frac = hwts->bins * 81 * 64 / 125; /* reduce fraction to avoid 64-bit division */
ts.coarse = hwts->coarse + frac / 4096;
ts.frac = frac % 4096;
/* the addition above may result with the coarse counter goint out of range: */
if (unlikely(ts.coarse >= 125000000)) {
ts.coarse -= 125000000;
ts.seconds++;
}
/* A trivial state machine to remove glitches, react on rising edge only
and drop pulses that are narrower than 100 ns.
We are waiting for a falling edge, but a rising one occurs - ignore it.
*/
if(unlikely(edge != st->expected_edge))
st->expected_edge = 1; /* wait unconditionally for next rising edge */
else {
if(st->expected_edge == 0) { /* got a falling edge after a rising one */
struct ft_wr_timestamp diff = ts;
ft_ts_sub(&diff, &st->prev_ts);
/* Check timestamp width. Must be at least 100 ns (coarse = 12, frac = 2048) */
if (likely(diff.seconds || diff.coarse > 12 || (diff.coarse == 12 && diff.frac >= 2048)))
{
ft_ts_apply_offset(&ts, ft->calib.zero_offset[channel - 1]);
if(st->user_offset)
ft_ts_apply_offset(&ts, st->user_offset);
/* Got a dacapo flag? make a gap in the sequence ID to indicate
an unknow loss of timestamps */
ts.seq_id = st->cur_seq_id++;
if(dacapo_flag)
{
ts.seq_id++;
st->cur_seq_id++;
}
/* Put the timestamp in the FIFO */
enqueue_timestamp(ft, channel, &ts);
}
} else
st->prev_ts = ts;
st->expected_edge = 1 - st->expected_edge;
}
}
static irqreturn_t ft_irq_handler(int irq, void *dev_id)
{
struct fmc_device *fmc = dev_id;
struct fmctdc_dev *ft = fmc->mezzanine_data;
tasklet_schedule(&ft->readout_tasklet);
return IRQ_HANDLED;
}
static inline int check_lost_events(uint32_t curr_wr_ptr, uint32_t prev_wr_ptr, int *count)
{
uint32_t dacapo_prev, dacapo_curr;
int dacapo_diff, ptr_diff = 0;
dacapo_prev = prev_wr_ptr >> 12;
dacapo_curr = curr_wr_ptr >> 12;
curr_wr_ptr &= 0x00fff; /* Pick last 12 bits */
curr_wr_ptr >>= 4; /* Remove last 4 bits. */
prev_wr_ptr &= 0x00fff; /* Pick last 12 bits */
prev_wr_ptr >>= 4; /* Remove last 4 bits. */
dacapo_diff = dacapo_curr - dacapo_prev;
switch(dacapo_diff) {
case 1:
ptr_diff = curr_wr_ptr - prev_wr_ptr;
if (ptr_diff > 0) {
*count = FT_BUFFER_EVENTS;
return 1; /* We lost data */
}
*count = curr_wr_ptr - prev_wr_ptr + FT_BUFFER_EVENTS;
break;
case 0:
/* We didn't lose data */
*count = curr_wr_ptr - prev_wr_ptr;
break;
default:
/* We lost data for sure. Notify to the user */
*count = FT_BUFFER_EVENTS;
return 1;
}
return 0;
}
static void ft_readout_tasklet(unsigned long arg)
{
struct fmctdc_dev *ft = (struct fmctdc_dev *) arg;
struct fmc_device *fmc = ft->fmc;
struct zio_device *zdev = ft->zdev;
uint32_t rd_ptr;
int count, dacapo, i;
ft->prev_wr_ptr = ft->cur_wr_ptr;
ft->cur_wr_ptr = ft_readl(ft, TDC_REG_BUFFER_PTR);
/* read the timestamps via DMA - we read the whole buffer, it doesn't really matter
for the HW if it's 16 bytes or on 4k page */
if( ft->carrier_specific->copy_timestamps(ft, 0, FT_BUFFER_EVENTS * sizeof(struct ft_hw_timestamp), ft->raw_events) < 0)
return; /* we can do nothing about this */
dacapo = check_lost_events(ft->cur_wr_ptr, ft->prev_wr_ptr, &count);
/* Start reading from the oldest event */
if(count == FT_BUFFER_EVENTS)
rd_ptr = (ft->cur_wr_ptr >> 4) & 0x000ff; /* The oldest is curr_wr_ptr */
else
rd_ptr = (ft->prev_wr_ptr >> 4) & 0x000ff; /* The oldest is prev_wr_ptr */
for ( ; count > 0; count--) {
process_timestamp(ft, &ft->raw_events[rd_ptr], dacapo);
rd_ptr = (rd_ptr + 1) % FT_BUFFER_EVENTS;
}
if(!zdev)
goto out;
for(i = FT_CH_1; i <= FT_NUM_CHANNELS; i++) {
struct ft_channel_state *st = &ft->channels[i-1];
/* FIXME: race condition */
if (test_bit(FT_FLAG_CH_INPUT_READY, &st->flags)) {
struct zio_cset *cset = &zdev->cset[i-1];
/* there is an active block, try reading an accumulated sample */
if (ft_read_sw_fifo(ft, i, cset->chan) == 0) {
clear_bit(FT_FLAG_CH_INPUT_READY, &st->flags);
zio_trigger_data_done(cset);
}
}
}
out:
/* ack the irq */
fmc_writel(ft->fmc, TDC_IRQ_TDC_TSTAMP, ft->ft_irq_base + TDC_REG_IRQ_STATUS);
fmc->op->irq_ack(fmc);
}
int ft_irq_init(struct fmctdc_dev *ft)
{
tasklet_init(&ft->readout_tasklet, ft_readout_tasklet, (unsigned long)ft);
/* disable coalescing, it's currently broken */
ft_writel(ft, 1, TDC_REG_IRQ_THRESHOLD);
ft_writel(ft, 0, TDC_REG_IRQ_TIMEOUT);
/* enable timestamp readout irq */
fmc_writel(ft->fmc, TDC_IRQ_TDC_TSTAMP, ft->ft_irq_base + TDC_REG_IRQ_ENABLE);
/* configure the actual handler via a carrier-specific mechanism */
return ft->carrier_specific->setup_irqs(ft, ft_irq_handler);
}
void ft_irq_exit(struct fmctdc_dev *ft)
{
fmc_writel(ft->fmc, 0, ft->ft_irq_base + TDC_REG_IRQ_ENABLE);
ft->carrier_specific->disable_irqs(ft);
}
\ No newline at end of file
/*
* SPEC-specific workarounds for the fmc-tdc driver.
*
* Copyright (C) 2012-2013 CERN (http://www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/fmc.h>
#include "fmc-tdc.h"
#include "spec.h"
#include "hw/tdc_regs.h"
struct ft_spec_data {
void *buffer;
dma_addr_t dma_addr;
size_t buffer_size;
};
static inline uint32_t dma_readl(struct fmctdc_dev *ft, unsigned long reg)
{
uint32_t rv = fmc_readl(ft->fmc, ft->ft_dma_base + reg);
//printk("dma_readl: addr %x val %x\n", ft->ft_dma_base + reg, rv);
return rv;
}
static inline void dma_writel(struct fmctdc_dev *ft, uint32_t v, unsigned long reg)
{
//printk("dma_writel: addr %x val %x\n", ft->ft_dma_base + reg, v);
fmc_writel(ft->fmc, v, ft->ft_dma_base + reg);
}
static int spec_ft_init ( struct fmctdc_dev *ft )
{
ft->carrier_data = kzalloc(sizeof(struct ft_spec_data), GFP_KERNEL );
if(!ft->carrier_data)
return -ENOMEM;
return 0;
}
static int spec_ft_reset( struct fmctdc_dev *dev )
{
struct spec_dev *spec = (struct spec_dev *) dev->fmc->carrier_data;
dev_info(&dev->fmc->dev, "%s: resetting TDC core through Gennum.\n", __func__);
/* set local bus clock to 160 MHz. The FPGA can't handle more. */
gennum_writel(spec, 0xE001F04C, 0x808);
/* fixme: there is no possibility of doing a software reset of the TDC core
other than through a Gennum config register. This begs for a fix in the
gateware! */
gennum_writel(spec, 0x00021040, GNPCI_SYS_CFG_SYSTEM);
mdelay(10);
gennum_writel(spec, 0x00025000, GNPCI_SYS_CFG_SYSTEM);
msleep(3000); /* it takes a while for the PLL to bootstrap.... or not!
We have no possibility to check :( */
return 0;
}
static int spec_ft_copy_timestamps (struct fmctdc_dev *dev, int base_addr, int size, void *dst )
{
struct ft_spec_data *hw = dev->carrier_data;
uint32_t status;
int i, ret;
hw->dma_addr = dma_map_single(dev->fmc->hwdev, (char *)dst, size, DMA_FROM_DEVICE);
if (dma_mapping_error(dev->fmc->hwdev, hw->dma_addr)) {
dev_err(&dev->fmc->dev, "dma_map_single failed\n");
return -ENOMEM;
}
dma_writel(dev, 0, TDC_REG_DMA_CTRL);
dma_writel(dev, base_addr, TDC_REG_DMA_C_START);
dma_writel(dev, hw->dma_addr & 0xffffffff, TDC_REG_DMA_H_START_L);
dma_writel(dev, ((uint64_t)hw->dma_addr >> 32) & 0x00ffffffff, TDC_REG_DMA_H_START_H);
dma_writel(dev, 0, TDC_REG_DMA_NEXT_L);
dma_writel(dev, 0, TDC_REG_DMA_NEXT_H);
/* Write the DMA length */
dma_writel(dev, size, TDC_REG_DMA_LEN);
/* No chained xfers, PCIe to host */
dma_writel(dev, 0, TDC_REG_DMA_ATTRIB);
/* Start the transfer */
dma_writel(dev, 1, TDC_REG_DMA_CTRL);
udelay(50);
dma_writel(dev, 0, TDC_REG_DMA_CTRL);
/* Don't bother about end-of-DMA IRQ, it only makes the driver unnecessarily complicated. */
for(i = 0; i < 1000; i++)
{
status = dma_readl (dev, TDC_REG_DMA_STAT) & TDC_DMA_STAT_MASK;
if(status == TDC_DMA_STAT_DONE)
{
ret = 0;
break;
} else if (status == TDC_DMA_STAT_ERROR) {
ret = -EIO;
break;
}
udelay(1);
}
if(i == 1000)
{
dev_err(&dev->fmc->dev, "%s: DMA transfer taking way too long. Something's really weird.\n", __func__);
ret = -EIO;
}
dma_sync_single_for_cpu(dev->fmc->hwdev, hw->dma_addr, size, DMA_FROM_DEVICE);
dma_unmap_single(dev->fmc->hwdev, hw->dma_addr, size, DMA_FROM_DEVICE);
return ret;
}
/* Unfortunately, on the spec this is GPIO9, i.e. IRQ(1) */
static struct fmc_gpio ft_gpio_on[] = {
{
.gpio = FMC_GPIO_IRQ(1),
.mode = GPIOF_DIR_IN,
.irqmode = IRQF_TRIGGER_RISING,
}
};
static struct fmc_gpio ft_gpio_off[] = {
{
.gpio = FMC_GPIO_IRQ(1),
.mode = GPIOF_DIR_IN,
.irqmode = 0,
}
};
static int spec_ft_setup_irqs (struct fmctdc_dev *ft, irq_handler_t handler)
{
struct fmc_device *fmc = ft->fmc;
int ret;
ret = fmc->op->irq_request(fmc, handler, "fmc-tdc", IRQF_SHARED);
if(ret < 0)
{
dev_err(&fmc->dev, "Request interrupt failed: %d\n", ret);
return ret;
}
fmc->op->gpio_config(fmc, ft_gpio_on, ARRAY_SIZE(ft_gpio_on));
return 0;
}
static int spec_ft_disable_irqs (struct fmctdc_dev *ft)
{
struct fmc_device *fmc = ft->fmc;
fmc->op->gpio_config(fmc, ft_gpio_off, ARRAY_SIZE(ft_gpio_off));
fmc->op->irq_free(fmc);
return 0;
}
static int spec_ft_ack_irq (struct fmctdc_dev *ft)
{
return 0;
}
struct ft_carrier_specific ft_carrier_spec = {
FT_GATEWARE_SPEC,
spec_ft_init,
spec_ft_reset,
spec_ft_copy_timestamps,
spec_ft_setup_irqs,
spec_ft_disable_irqs,
spec_ft_ack_irq
};
\ No newline at end of file
/*
* 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>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#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 <linux/fmc.h>
#include "fmc-tdc.h"
#define _RW_ (S_IRUGO | S_IWUGO) /* I want 80-col lines so this lazy thing */
/* The sample size. Mandatory, device-wide */
ZIO_ATTR_DEFINE_STD(ZIO_DEV, ft_zattr_dev_std) = {
ZIO_ATTR(zdev, ZIO_ATTR_NBITS, S_IRUGO, 0, 32), /* 32 bits. Really? */
};
/* Extended attributes for the device */
static struct zio_attribute ft_zattr_dev[] = {
ZIO_ATTR_EXT("version", S_IRUGO, FT_ATTR_DEV_VERSION, FT_VERSION),
ZIO_ATTR_EXT("seconds", _RW_, FT_ATTR_DEV_SECONDS, 0),
ZIO_ATTR_EXT("coarse", _RW_, FT_ATTR_DEV_COARSE, 0),
ZIO_ATTR_EXT("command", S_IWUGO, FT_ATTR_DEV_COMMAND, 0),
ZIO_ATTR_EXT("temperature", _RW_, FT_ATTR_DEV_TEMP, 0)
};
/* Extended attributes for the TDC (== input) cset */
static struct zio_attribute ft_zattr_input[] = {
ZIO_ATTR_EXT("seconds", S_IRUGO, FT_ATTR_TDC_SECONDS, 0),
ZIO_ATTR_EXT("coarse", S_IRUGO, FT_ATTR_TDC_COARSE, 0),
ZIO_ATTR_EXT("frac", S_IRUGO, FT_ATTR_TDC_FRAC, 0),
ZIO_ATTR_EXT("seq_id", S_IRUGO, FT_ATTR_TDC_SEQ, 0),
ZIO_ATTR_EXT("termination", _RW_, FT_ATTR_TDC_TERMINATION, 0),
ZIO_ATTR_EXT("offset", S_IRUGO, FT_ATTR_TDC_OFFSET, 0),
ZIO_ATTR_EXT("user-offset", _RW_, FT_ATTR_TDC_USER_OFFSET, 0),
ZIO_ATTR_EXT("purge-fifo", S_IWUGO, FT_ATTR_TDC_PURGE_FIFO, 0)
};
/* This identifies if our "struct device" is device, input, output */
enum ft_devtype {
FT_TYPE_WHOLEDEV,
FT_TYPE_INPUT
};
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;
}
/* 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_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;
}
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;
if (__ft_get_type(dev) == FT_TYPE_INPUT)
return ft_zio_info_channel(dev, zattr, usr_val);
/* reading temperature */
zdev = to_zio_dev(dev);
attr = zdev->zattr_set.ext_zattr;
ft = zdev->priv_d;
switch(zattr->id)
{
case FT_ATTR_DEV_VERSION:
return 0;
case FT_ATTR_DEV_TEMP:
if (ft->temp_ready) {
attr[FT_ATTR_DEV_TEMP].value = ft->temp;
return 0;
} else
return -EAGAIN;
case FT_ATTR_DEV_COARSE:
case FT_ATTR_DEV_SECONDS:
{
uint64_t seconds;
uint32_t coarse;
if( ft_get_tai_time(ft, &seconds, &coarse) < 0)
return -EAGAIN;
attr[FT_ATTR_DEV_COARSE].value = coarse;
attr[FT_ATTR_DEV_SECONDS].value = (uint32_t) seconds;
return 0;
}
}
return -EINVAL;
}
/* TDC input attributes: the flags */
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;
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);
return 0;
case FT_ATTR_TDC_USER_OFFSET:
spin_lock(&ft->lock);
st->user_offset = usr_val;
spin_unlock(&ft->lock);
return 0;
case FT_ATTR_TDC_PURGE_FIFO:
spin_lock(&ft->lock);
st->fifo.head = st->fifo.tail = st->fifo.count = 0;
spin_unlock(&ft->lock);
return 0;
}
return -EINVAL;
}
/*
* The input method may return immediately, because input is
* asynchronous. The data_done callback is invoked when the block is
* full.
*/
static int ft_zio_input(struct zio_cset *cset)
{
struct fmctdc_dev *ft;
struct ft_channel_state *st;
ft = cset->zdev->priv_d;
if(!ft->initialized)
return -EAGAIN;
st = &ft->channels[ cset->index ];
/* Ready for input. If there's already something, return it now */
if (ft_read_sw_fifo(ft, cset->index + 1, cset->chan) == 0) {
return 0; /* don't call data_done, let the caller do it */
}
/* Mark the active block is valid, and return EAGAIN */
set_bit(FT_FLAG_CH_INPUT_READY, &st->flags);
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)
{
/* current gw does not allow changing time when acquisition is enabled */
dev_err(&ft->fmc->dev, "%s: no time setting supported due to bugs in gateware.\n", __func__);
/*return ft_set_tai_time( ft, attr[FT_ATTR_DEV_SECONDS].value,
attr[FT_ATTR_DEV_COARSE].value
);*/
return -ENOTSUPP;
}
/* Not command, nothing to do */
if (zattr->id != FT_ATTR_DEV_COMMAND)
return 0;
switch(usr_val) {
case FT_CMD_WR_ENABLE:
case FT_CMD_WR_DISABLE:
case FT_CMD_WR_QUERY:
dev_warn(&ft->fmc->dev, "%s: sorry, no White Rabbit support yet.", __func__);
return -ENOTSUPP;
default:
return -EINVAL;
}
}
/*
* 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;
/* link the new device from the fd structure */
ft = zdev->priv_d;
ft->zdev = zdev;
/* We don't have csets at this point, so don't do anything more */
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,
};
#define DECLARE_CHANNEL(ch_name) \
{\
ZIO_SET_OBJ_NAME( ch_name ),\
.raw_io = ft_zio_input,\
.n_chan = 1,\
.ssize = 4, /* FIXME: 0? */\
.flags = ZIO_DIR_INPUT | ZIO_CSET_TYPE_TIME,\
.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 = "user",
.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[] = {
{"ft", &ft_tmpl},
{},
};
static struct zio_driver ft_zdrv = {
.driver = {
.name = "ft",
.owner = THIS_MODULE,
},
.id_table = ft_table,
.probe = ft_zio_probe,
};
/* 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)
{
int err = 0;
int dev_id;
ft->hwzdev = zio_allocate_device();
if (IS_ERR(ft->hwzdev))
return PTR_ERR(ft->hwzdev);
/* Mandatory fields */
ft->hwzdev->owner = THIS_MODULE;
ft->hwzdev->priv_d = ft;
dev_id = ft->fmc->device_id;
err = zio_register_device(ft->hwzdev, "ft", dev_id);
if (err) {
zio_free_device(ft->hwzdev);
return err;
}
return 0;
}
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
......@@ -10,75 +10,25 @@
*/
#ifndef __TDC_REGISTERS_H
#define __TDC_REGISTERS_H
/* Gennum chip register */
#define TDC_PCI_SYS_CFG_SYSTEM 0x800
#define TDC_PCI_CLK_CSR 0x808
/* Gennum core registers for DMA transactions */
#define TDC_DMA_CTRL_R 0x0
#define TDC_DMA_STAT_R 0x4
#define TDC_DMA_C_START_R 0x8
#define TDC_DMA_H_START_L_R 0xC
#define TDC_DMA_H_START_H_R 0x10
#define TDC_DMA_LEN_R 0x14
#define TDC_DMA_NEXT_L_R 0x18
#define TDC_DMA_NEXT_H_R 0x1C
#define TDC_DMA_ATTRIB_R 0x20
#define TDC_DMA_STAT_IDLE BIT(0)
#define TDC_DMA_STAT_DONE BIT(1)
#define TDC_DMA_STAT_BUSY BIT(2)
#define TDC_DMA_STAT_ERR BIT(3)
#define TDC_DMA_STAT_ABORT BIT(4)
/* ACAM GPX chip registers available */
#define TDC_ACAM_CFG_REG_0 0x20000
#define TDC_ACAM_CFG_REG_1 0x20004
#define TDC_ACAM_CFG_REG_2 0x20008
#define TDC_ACAM_CFG_REG_3 0x2000C
#define TDC_ACAM_CFG_REG_4 0x20010
#define TDC_ACAM_CFG_REG_5 0x20014
#define TDC_ACAM_CFG_REG_6 0x20018
#define TDC_ACAM_CFG_REG_7 0x2001C
#define TDC_ACAM_CFG_REG_11 0x2002C
#define TDC_ACAM_CFG_REG_12 0x20030
#define TDC_ACAM_CFG_REG_14 0x20038
/* ACAM GPX chip registers available (Read-only) */
#define TDC_ACAM_RDBACK_REG_0 0x20040
#define TDC_ACAM_RDBACK_REG_1 0x20044
#define TDC_ACAM_RDBACK_REG_2 0x20048
#define TDC_ACAM_RDBACK_REG_3 0x2004C
#define TDC_ACAM_RDBACK_REG_4 0x20050
#define TDC_ACAM_RDBACK_REG_5 0x20054
#define TDC_ACAM_RDBACK_REG_6 0x20058
#define TDC_ACAM_RDBACK_REG_7 0x2005C
#define TDC_ACAM_RDBACK_REG_8 0x20060
#define TDC_ACAM_RDBACK_REG_9 0x20064
#define TDC_ACAM_RDBACK_REG_10 0x20068
#define TDC_ACAM_RDBACK_REG_11 0x2006C
#define TDC_ACAM_RDBACK_REG_12 0x20070
#define TDC_ACAM_RDBACK_REG_14 0x20078
#define TDC_REG_ACAM_READBACK(index) (0x0040 + (index * 4))
#define TDC_REG_ACAM_CONFIG(index) (0x0000 + (index * 4))
/* TDC core registers */
#define TDC_START_UTC_R 0x20080
#define TDC_INPUT_ENABLE_R 0x20084
#define TDC_DELAY_START_R 0x20088
#define TDC_DELAY_1HZ_PULSE_R 0x2008C
#define TDC_IRQ_TSTAMP_THRESH_R 0x20090
#define TDC_IRQ_TIME_THRESH_R 0x20094
#define TDC_DAC_WORD_R 0x20098
#define TDC_CURRENT_UTC_R 0x200A0
#define TDC_IRQ_CODE_R 0x200A4
#define TDC_CIRCULAR_BUF_PTR_R 0x200A8
#define TDC_STATUS_R 0x200AC
#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_CTRL 0x00fc
#define TDC_CTRL_REG 0x200FC
#define TDC_CTRL_EN_ACQ BIT(0)
#define TDC_CTRL_DIS_ACQ BIT(1)
/* 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)
......@@ -90,23 +40,17 @@
#define TDC_CTRL_CLEAR_DACAPO_FLAG BIT(10)
#define TDC_CTRL_CONFIG_DAC BIT(11)
/* TDC_REG_INPUT_ENABLE bits */
#define TDC_INPUT_ENABLE_FLAG BIT(7)
/* IRQ register*/
#define TDC_IRQ_REG 0xA0000
#define TDC_IRQ_STATUS_REG 0xA0004
#define TDC_IRQ_ENABLE_REG 0xA0008
#define TDC_IRQ_GNUM_CORE_0 BIT(0)
#define TDC_IRQ_GNUM_CORE_1 BIT(1)
#define TDC_IRQ_TDC_TSTAMP BIT(2)
#define TDC_IRQ_TDC_TIME_THRESH BIT(3)
/* Other registers */
#define TDC_CARRIER_1WIRE 0x40000
#define TDC_MEZZANINE_I2C 0x60000
#define TDC_MEZZANINE_1WIRE 0x80000
/* IRQ controler registers */
#define TDC_REG_IRQ_MULTI 0x0
#define TDC_REG_IRQ_STATUS 0x4
#define TDC_REG_IRQ_ENABLE 0x8
/* Constants */
#define TDC_CHAN_NUMBER 5
/* IRQ status/enable bits */
#define TDC_IRQ_TDC_TSTAMP BIT(2)
#define TDC_IRQ_TDC_ERROR BIT(4)
#define TDC_EVENT_BUFFER_SIZE 256
#define TDC_EVENT_CHANNEL_MASK 0xF
......@@ -115,4 +59,26 @@
#define TDC_EVENT_FIFO_EF_MASK 0xF000
#define TDC_EVENT_DACAPO_FLAG BIT(0)
/* Carrier CSRs */
#define TDC_REG_CARRIER_CTL0 0x0
#define TDC_REG_CARRIER_CTL1 0x4
#define TDC_CARRIER_CTL0_PLL_STAT BIT(4)
/* Gennum DMA registers (not defined in the SPEC driver headers) */
#define TDC_REG_DMA_CTRL 0x0
#define TDC_REG_DMA_STAT 0x4
#define TDC_REG_DMA_C_START 0x8
#define TDC_REG_DMA_H_START_L 0x0c
#define TDC_REG_DMA_H_START_H 0x10
#define TDC_REG_DMA_NEXT_L 0x18
#define TDC_REG_DMA_NEXT_H 0x1c
#define TDC_REG_DMA_LEN 0x14
#define TDC_REG_DMA_ATTRIB 0x20
/* TDC_REG_DMA_STAT bits */
#define TDC_DMA_STAT_MASK 0x7
#define TDC_DMA_STAT_DONE 0x1
#define TDC_DMA_STAT_ERROR 0x3
#endif /* __TDC_REGISTERS_H */
/*
* Access to a DS182x 1-Wire thermometers.
*
* Copyright (C) 2012-2013 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/delay.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
#define R_CSR 0x0
#define R_CDR 0x4
#define CSR_DAT_MSK (1<<0)
#define CSR_RST_MSK (1<<1)
#define CSR_OVD_MSK (1<<2)
#define CSR_CYC_MSK (1<<3)
#define CSR_PWR_MSK (1<<4)
#define CSR_IRQ_MSK (1<<6)
#define CSR_IEN_MSK (1<<7)
#define CSR_SEL_OFS 8
#define CSR_SEL_MSK (0xF<<8)
#define CSR_POWER_OFS 16
#define CSR_POWER_MSK (0xFFFF<<16)
#define CDR_NOR_MSK (0xFFFF<<0)
#define CDR_OVD_OFS 16
#define CDR_OVD_MSK (0xFFFF<<16)
#define CLK_DIV_NOR (624)
#define CLK_DIV_OVD (124)
#define CMD_ROM_SEARCH 0xF0
#define CMD_ROM_READ 0x33
#define CMD_ROM_MATCH 0x55
#define CMD_ROM_SKIP 0xCC
#define CMD_ROM_ALARM_SEARCH 0xEC
#define CMD_CONVERT_TEMP 0x44
#define CMD_WRITE_SCRATCHPAD 0x4E
#define CMD_READ_SCRATCHPAD 0xBE
#define CMD_COPY_SCRATCHPAD 0x48
#define CMD_RECALL_EEPROM 0xB8
#define CMD_READ_POWER_SUPPLY 0xB4
#define FT_OW_PORT 0 /* what is this slow? */
static void ow_writel(struct fmctdc_dev *ft, uint32_t val, unsigned long reg)
{
fmc_writel(ft->fmc, val, ft->ft_owregs_base + reg);
}
static uint32_t ow_readl(struct fmctdc_dev *ft, unsigned long reg)
{
return fmc_readl(ft->fmc, ft->ft_owregs_base + reg);
}
static int ow_reset(struct fmctdc_dev *ft, int port)
{
uint32_t reg, data;
data = ((port << CSR_SEL_OFS) & CSR_SEL_MSK)
| CSR_CYC_MSK | CSR_RST_MSK;
ow_writel(ft, data, R_CSR);
while(ow_readl(ft, R_CSR) & CSR_CYC_MSK)
/* FIXME: timeout */;
reg = ow_readl(ft, R_CSR);
return ~reg & CSR_DAT_MSK;
}
static int slot(struct fmctdc_dev *ft, int port, int bit)
{
uint32_t reg, data;
data = ((port<<CSR_SEL_OFS) & CSR_SEL_MSK)
| CSR_CYC_MSK | (bit & CSR_DAT_MSK);
ow_writel(ft, data, R_CSR);
while(ow_readl(ft, R_CSR) & CSR_CYC_MSK)
/* FIXME: timeout */;
reg = ow_readl(ft, R_CSR);
return reg & CSR_DAT_MSK;
}
static int read_bit(struct fmctdc_dev *ft, int port)
{
return slot(ft, port, 0x1);
}
static int write_bit(struct fmctdc_dev *ft, int port, int bit)
{
return slot(ft, port, bit);
}
static int ow_read_byte(struct fmctdc_dev *ft, int port)
{
int byte = 0, i;
for(i = 0; i < 8; i++)
byte |= (read_bit(ft, port) << i);
return byte;
}
static int ow_write_byte(struct fmctdc_dev *ft, int port, int byte)
{
int data = 0;
int i;
for (i = 0; i < 8; i++){
data |= write_bit(ft, port, (byte & 0x1)) << i;
byte >>= 1;
}
return 0; /* success */
}
static int ow_write_block(struct fmctdc_dev *ft, int port, uint8_t *block, int len)
{
int i;
for(i = 0; i < len; i++)
ow_write_byte(ft, port, block[i]);
return 0;
}
static int ow_read_block(struct fmctdc_dev *ft, int port, uint8_t *block, int len)
{
int i;
for(i = 0; i < len; i++)
block[i] = ow_read_byte(ft, port);
return 0;
}
static int ds18x_read_serial(struct fmctdc_dev *ft)
{
if(!ow_reset(ft, 0)) {
dev_err(&ft->fmc->dev, "Failure in resetting one-wire channel\n");
return -EIO;
}
ow_write_byte(ft, FT_OW_PORT, CMD_ROM_READ);
return ow_read_block(ft, FT_OW_PORT, ft->ds18_id, 8);
}
static int ds18x_access(struct fmctdc_dev *ft)
{
if(!ow_reset(ft, 0))
goto out;
if (0) {
/* select the rom among several of them */
if (ow_write_byte(ft, FT_OW_PORT, CMD_ROM_MATCH) < 0)
goto out;
return ow_write_block(ft, FT_OW_PORT, ft->ds18_id, 8);
} else {
/* we have one only, so skip rom */
return ow_write_byte(ft, FT_OW_PORT, CMD_ROM_SKIP);
}
out:
dev_err(&ft->fmc->dev, "Failure in one-wire communication\n");
return -EIO;
}
static void __temp_command_and_next_t(struct fmctdc_dev *ft, int cfg_reg)
{
int ms;
ds18x_access(ft);
ow_write_byte(ft, FT_OW_PORT, CMD_CONVERT_TEMP);
/* The conversion takes some time, so mark when will it be ready */
ms = 94 * ( 1 << (cfg_reg >> 5));
ft->next_t = jiffies + msecs_to_jiffies(ms);
}
int ft_read_temp(struct fmctdc_dev *ft, int verbose)
{
int i, temp;
unsigned long j;
uint8_t data[9];
struct device *dev = &ft->fmc->dev;
/* If first conversion, ask for it first */
if (ft->next_t == 0)
__temp_command_and_next_t(ft, 0x7f /* we ignore: max time */);
/* Wait for it to be ready: (FIXME: we need a time policy here) */
j = jiffies;
if (time_before(j, ft->next_t)) {
/* If we cannot sleep, return the previous value */
if (in_atomic())
return ft->temp;
msleep(jiffies_to_msecs(ft->next_t - j));
}
ds18x_access(ft);
ow_write_byte(ft, FT_OW_PORT, CMD_READ_SCRATCHPAD);
ow_read_block(ft, FT_OW_PORT, data, 9);
if (verbose > 1) {
dev_info(dev, "%s: Scratchpad: ", __func__);
for (i = 0; i < 9; i++)
printk("%02x%c", data[i], i == 8 ? '\n' : ':');
}
temp = ((int)data[1] << 8) | ((int)data[0]);
if(temp & 0x1000)
temp = -0x10000 + temp;
ft->temp = temp;
ft->temp_ready = 1;
if (verbose) {
dev_info(dev, "%s: Temperature 0x%x (%i bits: %i.%03i)\n", __func__,
temp, 9 + (data[4] >> 5),
temp / 16, (temp & 0xf) * 1000 / 16);
}
__temp_command_and_next_t(ft, data[4]); /* start next conversion */
return temp;
}
int ft_onewire_init(struct fmctdc_dev *ft)
{
int i;
ow_writel(ft, ((CLK_DIV_NOR & CDR_NOR_MSK)
| (( CLK_DIV_OVD << CDR_OVD_OFS) & CDR_OVD_MSK)),
R_CDR);
if(ds18x_read_serial(ft) < 0)
return -EIO;
if (ft->verbose) {
dev_info(&ft->fmc->dev, "%s: Found DS18xx sensor: ", __func__);
for (i = 0; i < 8; i++)
printk("%02x%c", ft->ds18_id[i], i == 7 ? '\n' : ':');
}
/* read the temperature once, to ensure it works, and print it */
ft_read_temp(ft, 2);
return 0;
}
void ft_onewire_exit(struct fmctdc_dev *fd)
{
/* Nothing to do */
}
/*
* Time-related routines for fmc-tdc driver.
*
* Copyright (C) 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 Lesser General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "fmc-tdc.h"
#include "hw/tdc_regs.h"
void ft_ts_from_picos ( uint32_t picos, struct ft_wr_timestamp *result )
{
result->frac = picos % 4096;
picos -= picos % 4096;
picos /= 4096;
result->coarse = picos % 125000000;
picos -= picos % 125000000;
picos /= 125000000;
result->seconds = picos;
}
void ft_ts_add (struct ft_wr_timestamp *a, struct ft_wr_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_wr_timestamp *a, struct ft_wr_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_wr_timestamp *ts, int32_t offset_picos )
{
struct ft_wr_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);
}
int ft_set_tai_time(struct fmctdc_dev *ft, uint64_t seconds, uint32_t coarse)
{
if(ft->verbose)
dev_info(&ft->fmc->dev, "setting TAI time to %lld:%d\n", seconds, coarse);
ft_writel(ft, seconds & 0xffffffff, TDC_REG_CURRENT_UTC);
ft_writel(ft, TDC_CTRL_LOAD_UTC, TDC_REG_CTRL);
return 0;
}
int ft_get_tai_time(struct fmctdc_dev *ft, uint64_t *seconds, uint32_t *coarse)
{
*seconds = ft_readl(ft, TDC_REG_CURRENT_UTC);
*coarse = 0;
return 0;
}
int ft_enable_wr_mode (struct fmctdc_dev *ft, int enable)
{
return -ENOTSUPP;
}
int ft_check_wr_mode (struct fmctdc_dev *ft)
{
return -ENOTSUPP;
}
int ft_time_init(struct fmctdc_dev *ft)
{
/* program the VCXO DAC to the default calibration value */
ft_writel(ft, ft->calib.vcxo_default_tune, TDC_REG_DAC_TUNE);
ft_writel(ft, TDC_CTRL_CONFIG_DAC, TDC_REG_CTRL);
return ft_set_tai_time(ft, 0, 0);
}
void ft_time_exit(struct fmctdc_dev *ft)
{
}
\ No newline at end of file
ZIO ?= $(HOME)/zio
# This is not a kbuild Makefile. It is a plain Makefile so it can be copied
LIB = libtdc.a
LOBJ := libtdc.o
LIB = libfmctdc.a
LOBJ := fmctdc-lib.o
CFLAGS = -Wall -ggdb -I.. -I$(ZIO)/include
LDFLAGS = -L. -ltdc
CFLAGS = -Wall -ggdb -O2 -I../kernel -I../zio/include
LDFLAGS = -L. -lfmctdc
modules all: lib
TEST_OBJS = fmctdc-test.o
modules all: lib testprog
lib: $(LIB)
testprog: $(TEST_OBJS)
$(CC) -o fmctdc-test $(TEST_OBJS) $(LIB)
ln -sf fmctdc-test fmctdc-identify
ln -sf fmctdc-test fmctdc-read
ln -sf fmctdc-test fmctdc-termination
ln -sf fmctdc-test fmctdc-time
ln -sf fmctdc-test fmctdc-list
%: %.c $(LIB)
$(CC) $(CFLAGS) $*.c $(LDFLAGS) -o $@
$(LIB): $(LOBJ)
ar r $@ $^
libtdc.so: libtdc.c
$(CC) -fPIC -shared $(CFLAGS) -o $@ $^
clean:
rm -f $(LIB) libtdc.so *.o *~
rm -f $(LIB) .depend *.o *~
.depend: Makefile $(wildcard *.c *.h ../*.h)
$(CC) $(CFLAGS) -M $(LOBJ:.o=.c) -o $@
install modules_install:
-include .depend
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library.
*
* Copyright (C) 2012-2013 CERN (www.cern.ch)
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <glob.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>
#define FMCTDC_INTERNAL
#include "fmctdc-lib.h"
#include <fmc-tdc.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
static struct __fmctdc_board *ft_boards;
static int ft_nboards;
/* Init the library: return the number of boards found */
int fmctdc_init(void)
{
glob_t glob_dev, glob_sys;
struct __fmctdc_board *b;
int i, j;
uint32_t v;
/* Look for boards in /dev: old and new pathnames: only one matches */
glob("/dev/ft-*-0-0-ctrl", 0, NULL, &glob_dev);
glob("/dev/zio/ft-*-0-0-ctrl", GLOB_APPEND, NULL, &glob_dev);
glob("/dev/zio-ft-*-0-0-ctrl", GLOB_APPEND, NULL, &glob_dev);
glob("/dev/zio/zio-ft-*-0-0-ctrl", GLOB_APPEND, NULL, &glob_dev);
/* And look in /sys as well */
glob("/sys/bus/zio/devices/ft-*", 0, NULL, &glob_sys);
glob("/sys/bus/zio/devices/zio-ft-*", GLOB_APPEND , NULL, &glob_sys);
assert(glob_dev.gl_pathc == glob_sys.gl_pathc);
/* Allocate as needed */
ft_nboards = glob_dev.gl_pathc;
if (!ft_nboards) {
ft_boards = NULL;
return 0;
}
ft_boards = calloc(glob_dev.gl_pathc, sizeof(ft_boards[0]));
if (!ft_boards) {
globfree(&glob_dev);
globfree(&glob_sys);
return -1;
}
for (i = 0, b = ft_boards; i < ft_nboards; i++, b++) {
b->sysbase = strdup(glob_sys.gl_pathv[i]);
b->devbase = strdup(glob_dev.gl_pathv[i]);
/* trim the "-0-0-ctrl" at the end */
b->devbase[strlen(b->devbase) - strlen("-0-0-ctrl")] = '\0';
/* extract dev_id */
sscanf(b->sysbase, "%*[^f]ft-%x", &b->dev_id);
for (j = 0; j < ARRAY_SIZE(b->fdc); j++) {
b->fdc[j] = -1;
b->fdd[j] = -1;
}
if (fmctdc_is_verbose()) {
fprintf(stderr, "%s: %04x %s %s\n", __func__,
b->dev_id, b->sysbase, b->devbase);
}
}
globfree(&glob_dev);
globfree(&glob_sys);
/* Now, if at least one board is there, check the version */
if (ft_nboards == 0)
return 0;
if (fmctdc_sysfs_get(ft_boards, "version", &v) < 0)
return -1;
if (v != FT_VERSION) {
fprintf(stderr, "%s: version mismatch, lib(%i) != drv(%i)\n",
__func__, FT_VERSION, v);
errno = EIO;
return -1;
}
return ft_nboards;
}
/* Free and check */
void fmctdc_exit(void)
{
struct __fmctdc_board *b;
int i, j, err;
for (i = 0, err = 0, b = ft_boards; i < ft_nboards; i++, b++) {
for (j = 0; j < ARRAY_SIZE(b->fdc); j++) {
if (b->fdc[j] >= 0) {
close(b->fdc[j]);
b->fdc[j] = -1;
err++;
}
if (b->fdd[j] >= 0) {
close(b->fdd[j]);
b->fdd[j] = -1;
err++;
}
}
if (err)
fprintf(stderr, "%s: device %s was still open\n",
__func__, b->devbase);
free(b->sysbase);
free(b->devbase);
}
if(ft_nboards)
free(ft_boards);
}
/* Open one specific device. -1 arguments mean "not installed" */
struct fmctdc_board *fmctdc_open(int offset, int dev_id)
{
struct __fmctdc_board *b = NULL;
uint32_t nsamples = 1;
int i;
if (offset >= ft_nboards) {
errno = ENODEV;
return NULL;
}
if (offset >= 0) {
b = ft_boards + offset;
if (dev_id >= 0 && dev_id != b->dev_id) {
errno = EINVAL;
return NULL;
}
goto found;
}
if (dev_id < 0) {
errno = EINVAL;
return NULL;
}
for (i = 0, b = ft_boards; i < ft_nboards; i++, b++)
if (b->dev_id == dev_id)
goto found;
errno = ENODEV;
return NULL;
found:
/* 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);
return (void *)b;
}
/* Open one specific device by logical unit number (CERN/CO-like) */
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];
int 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 (sscanf(dev_id_str, "%4x", &dev_id) != 1) {
errno = ENODEV;
return NULL;
}
return fmctdc_open(-1, dev_id);
}
int fmctdc_close(struct fmctdc_board *userb)
{
__define_board(b, userb);
int j;
for (j = 0; j < ARRAY_SIZE(b->fdc); j++) {
if (b->fdc[j] >= 0)
close(b->fdc[j]);
b->fdc[j] = -1;
if (b->fdd[j] >= 0)
close(b->fdd[j]);
b->fdd[j] = -1;
}
return 0;
}
int fmctdc_wr_mode(struct fmctdc_board *userb, int on)
{
__define_board(b, userb);
if (on)
return __fmctdc_command(b, FT_CMD_WR_ENABLE);
else
return __fmctdc_command(b, FT_CMD_WR_DISABLE);
}
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 errno;
}
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;
}
int fmctdc_identify_card(struct fmctdc_board *userb, int blink_led)
{
__define_board(b, userb);
if (blink_led)
return __fmctdc_command(b, FT_CMD_IDENTIFY_ON);
else
return __fmctdc_command(b, FT_CMD_IDENTIFY_OFF);
}
int fmctdc_set_termination(struct fmctdc_board *userb, int channel, int on)
{
__define_board(b, userb);
uint32_t val;
char attr[32];
if(channel < FMCTDC_CH_1 || channel > FMCTDC_NUM_CHANNELS)
return -EINVAL;
snprintf(attr, sizeof(attr), "fd-ch%d/termination", channel);
val = on ? 1 : 0;
return fmctdc_sysfs_set(b, attr, &val);
}
int fmctdc_get_termination(struct fmctdc_board *userb, int channel)
{
__define_board(b, userb);
uint32_t val;
char attr[32];
int ret;
if(channel < FMCTDC_CH_1 || channel > FMCTDC_NUM_CHANNELS)
return -EINVAL;
snprintf(attr, sizeof(attr), "fd-ch%d/termination", channel);
ret = fmctdc_sysfs_get(b, attr, &val);
if(ret)
return ret;
return val;
}
static int __fmctdc_open_channel(struct __fmctdc_board *b, int channel)
{
char fname[128];
if (b->fdc[channel - 1] <= 0) {
snprintf(fname, sizeof(fname), "%s-%d-0-ctrl", b->devbase, channel);
b->fdc[channel - 1] = open(fname, O_RDONLY | O_NONBLOCK);
}
return b->fdc[channel - 1];
}
int fmctdc_fileno_channel(struct fmctdc_board *userb, int channel)
{
__define_board(b, userb);
return __fmctdc_open_channel(b, channel);
}
/* "read" behaves like the system call and obeys O_NONBLOCK */
int fmctdc_read(struct fmctdc_board *userb, int channel, struct fmctdc_time *t, int n,
int flags)
{
__define_board(b, userb);
struct zio_control ctrl;
uint32_t *attrs;
int i, j, fd;
fd_set set;
if(channel < FMCTDC_CH_1 || channel > FMCTDC_NUM_CHANNELS)
return -EINVAL;
fd = __fmctdc_open_channel(b, channel);
if (fd < 0)
return fd; /* errno already set */
for (i = 0; i < n;) {
j = read(fd, &ctrl, sizeof(ctrl));
if (j < 0 && errno != EAGAIN)
return -1;
if (j == sizeof(ctrl)) {
/* one sample: pick it */
attrs = ctrl.attr_channel.ext_val;
t->seconds = ctrl.tstamp.secs;
t->coarse = ctrl.tstamp.ticks;
t->frac = ctrl.tstamp.bins;
t->seq_id = attrs[FT_ATTR_TDC_SEQ];
i++;
continue;
}
if (j > 0) {
errno = EIO;
return -1;
}
/* so, it's EAGAIN: if we already got something, we are done */
if (i)
return i;
/* EAGAIN at first sample */
if (j < 0 && flags == O_NONBLOCK)
return -1;
/* So, first sample and blocking read. Wait.. */
FD_ZERO(&set);
FD_SET(fd, &set);
if (select(fd+1, &set, NULL, NULL, NULL) < 0)
return -1;
continue;
}
return i;
}
/* "fread" behaves like stdio: it reads all the samples */
int fmctdc_fread(struct fmctdc_board *userb, int channel, struct fmctdc_time *t, int n)
{
int i, loop;
for (i = 0; i < n; ) {
loop = fmctdc_read(userb, channel, t + i, n - i, 0);
if (loop < 0)
return -1;
i += loop;
}
return i;
}
/*
* The "official" fmc-tdc API
*
* Copyright (C) 2012-2013 CERN (www.cern.ch)
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#ifndef __FMCTDC_LIB_H__
#define __FMCTDC_LIB_H__
#include <stdint.h>
enum fmctdc_channel {
FMCTDC_CH_1 = 1,
FMCTDC_CH_2 = 2,
FMCTDC_CH_3 = 3,
FMCTDC_CH_4 = 4,
FMCTDC_CH_5 = 5,
FMCTDC_NUM_CHANNELS = 5
};
/* Opaque data type used as token */
struct fmctdc_board;
struct fmctdc_time {
uint64_t seconds;
uint32_t coarse;
uint32_t frac;
uint32_t seq_id;
};
extern int fmctdc_init(void);
extern void fmctdc_exit(void);
extern struct fmctdc_board *fmctdc_open(int offset, int dev_id);
extern struct fmctdc_board *fmctdc_open_by_lun(int lun);
extern int fmctdc_close(struct fmctdc_board *);
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_set_termination(struct fmctdc_board *b, int channel, int enable);
extern int fmctdc_get_termination(struct fmctdc_board *b, int channel);
extern int fmctdc_purge_fifo(struct fmctdc_board *b, int channel);
extern int fmctdc_identify_card(struct fmctdc_board *b, int blink_led);
extern int fmctdc_fread(struct fmctdc_board *b, int channel, struct fmctdc_time *t, int n);
extern int fmctdc_fileno_channel(struct fmctdc_board *b, int channel);
extern int fmctdc_read(struct fmctdc_board *b, int channel, struct fmctdc_time *t, int n,
int flags);
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);
#ifdef FMCTDC_INTERNAL /* Libray users should ignore what follows */
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/* Internal structure */
struct __fmctdc_board {
int dev_id;
char *devbase;
char *sysbase;
int fdc[5]; /* The 5 control channels */
int fdd[5]; /* The 5 data channels */
};
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, "%i", 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, "%i\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_INTERNAL */
#endif /* __fmctdc_H__ */
/*
* The fmc-tdc (a.k.a. FmcTdc1ns5cha) library test program.
*
* Author: Tomasz Włostowski <tomasz.wlostowski@cern.ch>
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#define FMCTDC_INTERNAL /* hack... */
#include "fmctdc-lib.h"
int n_boards;
struct fmctdc_board *brd;
int go_identify(int argc, char **argv);
int go_list(int argc, char **argv);
int go_read(int argc, char **argv);
int go_time(int argc, char **argv);
int go_termination(int argc, char **argv);
int go_test(int argc, char **argv);
static struct subprogram {
char *name;
int (*go)(int argc, char **argv);
} subprograms[] = {
{ "fmctdc-identify", go_identify },
{ "fmctdc-list", go_list },
{ NULL, NULL },
} ;
void usage_msg(const char *name, const char *msg)
{
printf("usage: %s %s\n", name, msg);
exit(0);
}
int open_board(char *dev_id_str)
{
unsigned int dev_id;
if(sscanf(dev_id_str, "%04x", &dev_id) != 1)
return -1;
brd = fmctdc_open(-1, dev_id);
if(!brd)
{
fprintf(stderr, "can't open device %s: %s\n", dev_id_str, strerror(errno));
return -1;
}
return 0;
}
int go_identify(int argc, char **argv)
{
if (argc >= 2 && !strcmp(argv[1], "-h"))
{
printf("%s: identifies a given fmc-tdc device in a crate by blinking its status LED.\n", argv[0]);
printf("usage: %s [-h] <device>\n", argv[0]);
return 0;
}
if(argc < 2)
{
printf("usage: %s [-h] <device>\n", argv[0]);
return 0;
}
if(open_board(argv[1]) < 0)
return -1;
printf("Blinking the status LED. Press ENTER to stop.\n");
fmctdc_identify_card(brd, 1);
getchar();
fmctdc_identify_card(brd, 0);
return 0;
}
int go_list(int argc, char **argv)
{
int i;
if (argc >= 2 && !strcmp(argv[1], "-h"))
{
printf("%s: lists all installed fmc-tdc boards.\n", argv[0]);
printf("usage: %s [-h]\n", argv[0]);
return 0;
}
printf("Found %i board(s): \n", n_boards);
for (i = 0; i < n_boards; i++)
{
struct __fmctdc_board *b;
struct fmctdc_board *ub;
ub = fmctdc_open(i, -1);
b = (typeof(b))ub;
printf("%04x, %s, %s\n", b->dev_id, b->devbase, b->sysbase);
}
return 0;
}
int main(int argc, char **argv)
{
int rv = 0;
struct subprogram *sp;
n_boards = fmctdc_init();
if (n_boards < 0) {
fprintf(stderr, "%s: fdelay_init(): %s\n", argv[0],
strerror(errno));
exit(1);
}
for(sp = subprograms; sp->name; sp++)
if (!strcmp(sp->name, basename(argv[0]))) {
rv = sp->go(argc, argv);
break;
}
// rv = go_test(argc, argv);
fmctdc_exit();
return rv;
}
\ No newline at end of file
#include <glob.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/select.h>
#include <linux/zio.h>
#include <linux/zio-user.h>
#include "libtdc.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define TDC_INPUT_ENABLE_FLAG (1 << 7)
static inline int __tdc_sysfs_get_lun(char *sysbase, uint32_t *resp)
{
char path[128];
FILE *f;
sprintf(path, "%s/%s", sysbase, "lun");
f = fopen(path, "r");
if (!f)
return -1;
errno = 0;
if (fscanf(f, "%i", resp) != 1) {
fclose(f);
if (!errno)
errno = EINVAL;
return -1;
}
fclose(f);
return 0;
}
static inline int __tdc_sysfs_get(struct tdc_board *b, char *name,
uint32_t *resp)
{
char path[128];
FILE *f;
sprintf(path, "%s/%s", b->sysbase, name);
f = fopen(path, "r");
if (!f)
return -1;
errno = 0;
if (fscanf(f, "%i", resp) != 1) {
fclose(f);
if (!errno)
errno = EINVAL;
return -1;
}
fclose(f);
return 0;
}
static inline int __tdc_sysfs_set(struct tdc_board *b, char *name,
uint32_t value)
{
char path[128];
char s[16];
int fd, ret, len;
sprintf(path, "%s/%s", b->sysbase, name);
len = sprintf(s, "%i\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;
}
static int __tdc_sysfs_set_buffer_size(struct tdc_board *b, uint32_t value)
{
char path[128];
int i, ret;
for (i = 0; i < 5; i++) {
sprintf(path, "tdc-cset%d/chan0/buffer/max-buffer-len", i);
ret = __tdc_sysfs_set(b, path, value);
if(ret) {
fprintf(stderr, "Error changing the max-buffer-len value to %d\n", value);
return ret;
}
}
return 0;
}
struct tdc_board *tdc_open(int lun)
{
glob_t glob_dev, glob_sys;
struct tdc_board *b = NULL;
int tdc_nboards;
int ret;
uint32_t dev_lun;
int i, j;
/* Look for boards in /dev: old and new pathnames: only one matches */
glob("/dev/tdc-*-0-0-ctrl", 0, NULL, &glob_dev);
glob("/dev/zio/tdc-*-0-0-ctrl", GLOB_APPEND, NULL, &glob_dev);
/* And look in /sys as well */
glob("/sys/bus/zio/devices/tdc-*",0 , NULL, &glob_sys);
assert(glob_dev.gl_pathc == glob_sys.gl_pathc);
/* Check that there are boards found */
tdc_nboards = glob_dev.gl_pathc;
if (!tdc_nboards) {
fprintf (stderr, "No boards found!\n");
errno = ENODEV;
return NULL;
}
for (i = 0; i < tdc_nboards; i++) {
ret = __tdc_sysfs_get_lun(glob_sys.gl_pathv[i], &dev_lun);
/* Unable to get lun */
if (ret < 0) {
fprintf(stderr, "Unable to get lun for device %s\n",
glob_sys.gl_pathv[i]);
continue;
}
/* lun doesn't match */
if (dev_lun != lun)
continue;
/* lun found */
b = malloc(sizeof(struct tdc_board));
b->lun = lun;
b->sysbase = strdup(glob_sys.gl_pathv[i]);
b->devbase = strdup(glob_dev.gl_pathv[i]);
b->chan_config = 0;
/* trim the "-0-0-ctrl" at the end */
b->devbase[strlen(b->devbase) - strlen("-0-0-ctrl")] = '\0';
/* extract dev_id */
sscanf(b->sysbase, "%*[^f]tdc-%x", &b->dev_id);
for (j = 0; j < ARRAY_SIZE(b->ctrl); j++) {
b->ctrl[j] = -1;
b->data[j] = -1;
}
__tdc_sysfs_set_buffer_size(b, 256);
break;
}
globfree(&glob_dev);
globfree(&glob_sys);
if (!b)
errno = ENODEV;
return b;
}
int tdc_close(struct tdc_board *b)
{
int j;
for (j = 0; j < ARRAY_SIZE(b->ctrl); j++) {
if (b->ctrl[j] >= 0) {
close(b->ctrl[j]);
fprintf(stderr, "Device %s was still open\n",
b->devbase);
}
b->ctrl[j] = -1;
if (b->data[j] >= 0) {
close(b->data[j]);
fprintf(stderr, "Device %s was still open\n",
b->devbase);
}
b->data[j] = -1;
}
free(b->sysbase);
free(b->devbase);
free(b);
return 0;
}
struct tdc_time *tdc_zalloc(unsigned int events)
{
return calloc(events, sizeof(struct tdc_time));
}
void tdc_free(struct tdc_time *buffer)
{
free(buffer);
}
int tdc_start_acquisition(struct tdc_board *b)
{
int ret, tries = 0;
do {
ret = __tdc_sysfs_set(b, "activate_acquisition", 1);
if (ret) {
__tdc_sysfs_set(b, "reset_acam", 1);
tries++;
}
} while ((ret !=0) && (tries < 10));
return ret;
}
int tdc_stop_acquisition(struct tdc_board *b)
{
return __tdc_sysfs_set(b, "activate_acquisition", 0);
}
int tdc_set_host_utc_time(struct tdc_board *b)
{
/* -1 means that the driver will load the host time */
return __tdc_sysfs_set(b, "set_utc_time", -1);
}
int tdc_set_utc_time(struct tdc_board *b, uint32_t utc)
{
/* a value different from -1 is an UTC */
return __tdc_sysfs_set(b, "set_utc_time", utc);
}
int tdc_get_utc_time(struct tdc_board *b, uint32_t *utc)
{
return __tdc_sysfs_get(b, "current_utc_time", utc);
}
int tdc_set_dac_word(struct tdc_board *b, uint32_t dw)
{
return __tdc_sysfs_set(b, "dac_word", dw);
}
int tdc_get_dac_word(struct tdc_board *b, uint32_t *dw)
{
return __tdc_sysfs_get(b, "dac_word", dw);
}
int tdc_set_time_threshold(struct tdc_board *b, uint32_t thres)
{
return __tdc_sysfs_set(b, "time_thresh", thres);
}
int tdc_get_time_threshold(struct tdc_board *b, uint32_t *thres)
{
return __tdc_sysfs_get(b, "time_thresh", thres);
}
int tdc_set_timestamp_threshold(struct tdc_board *b, uint32_t thres)
{
if (thres > 64) {
errno = EINVAL;
return -1;
}
return __tdc_sysfs_set(b, "tstamp_thresh", thres * 2);
}
int tdc_get_timestamp_threshold(struct tdc_board *b, uint32_t *thres)
{
uint32_t value;
int res;
res = __tdc_sysfs_get(b, "tstamp_thresh", &value);
if (!res)
*thres = value / 2;
return res;
}
int tdc_set_channels_term(struct tdc_board *b, uint32_t config)
{
int res = 0;
/* Clear other bits than the 5 smaller */
config = config & 0x1f;
b->chan_config = (b->chan_config & TDC_INPUT_ENABLE_FLAG) | config;
/* Hardware deactivation */
res = __tdc_sysfs_set(b, "channel_term", b->chan_config);
if (res) {
fprintf(stderr, "Error setting chan config in hardware\n");
return res;
}
return res;
}
int tdc_get_channels_term(struct tdc_board *b, uint32_t *config)
{
*config = b->chan_config & ~TDC_INPUT_ENABLE_FLAG;
return 0;
}
int tdc_activate_channels(struct tdc_board *b)
{
b->chan_config |= TDC_INPUT_ENABLE_FLAG;
return __tdc_sysfs_set(b, "channel_term", b->chan_config);
}
int tdc_deactivate_channels(struct tdc_board *b)
{
b->chan_config &= ~TDC_INPUT_ENABLE_FLAG;
return __tdc_sysfs_set(b, "channel_term", b->chan_config);
}
int tdc_get_circular_buffer_pointer(struct tdc_board *b, uint32_t *ptr)
{
return __tdc_sysfs_get(b, "get_wr_pointer", ptr);
}
int tdc_clear_dacapo_flag(struct tdc_board *b)
{
return __tdc_sysfs_set(b, "clear_dacapo_flag", 1);
}
static int __tdc_chan_to_decimal(int chan)
{
int ret = -1;
while (chan > 0) {
chan = chan >> 1;
ret++;
}
return ret;
}
static int __tdc_valid_channel(struct tdc_board *b, int chan)
{
if (chan < 0 || chan > 4) {
fprintf(stderr, "%s: Invalid channel: %i\n",
__func__, chan);
errno = EINVAL;
return 0;
}
if (!(b->chan_config & TDC_INPUT_ENABLE_FLAG) ) {
fprintf(stderr, "%s: All channels disabled\n",
__func__);
errno = EINVAL;
return 0;
}
return 1;
}
static int __tdc_open_file(struct tdc_board *b, int chan)
{
char fname[128];
sprintf(fname, "%s-%i-0-ctrl", b->devbase, chan);
/* open file */
if (b->ctrl[chan] <= 0) {
b->ctrl[chan] = open(fname, O_RDONLY | O_NONBLOCK);
}
if (b->ctrl[chan] < 0)
fprintf(stderr, "%s: Error opening file: %s\n",
__func__, fname);
return b->ctrl[chan];
}
static int __tdc_close_file(struct tdc_board *b, int chan)
{
int ret;
ret = close(b->ctrl[chan]);
if (ret) {
char fname[128];
sprintf(fname, "%s-%i-0-ctrl", b->devbase, chan);
fprintf(stderr, "%s: Error closing file: %s\n",
__func__, fname);
return -1;
}
b->ctrl[chan] = -1;
return 0;
}
int tdc_read(struct tdc_board *b, int chan, struct tdc_time *t,
int n, int flags)
{
struct zio_control ctrl;
int fd, i, j;
fd_set set;
int ret = 0;
int chan_dec;
chan_dec = __tdc_chan_to_decimal(chan);
if (!__tdc_valid_channel(b, chan_dec))
return -1;
fd = __tdc_open_file(b, chan_dec);
if (fd < 0)
return -1;
for (i = 0; i < n; ) {
j = read(fd, &ctrl, sizeof(ctrl));
/* one register read */
if (j == sizeof(ctrl)) {
t[i].utc = ctrl.tstamp.secs;
t[i].ticks = ctrl.tstamp.ticks;
t[i].bins = ctrl.tstamp.bins;
t[i].da_capo = ctrl.flags;
i++;
ret = i;
continue;
}
/* some bytes read but not complete structure */
if (j > 0) {
errno = EIO;
ret = -1;
break;
}
/* from here on, an error was returned */
/* real error, so exit */
if (errno != EAGAIN) {
ret = -1;
break;
}
/* EAGAIN: if we can't block, return elements read */
if (flags == O_NONBLOCK) {
ret = i;
break;
}
/* blocking read */
FD_ZERO(&set);
FD_SET(fd, &set);
if (select(fd+1, &set, NULL, NULL, NULL) < 0) {
ret = -1;
break;
}
}
__tdc_close_file(b, chan_dec);
return ret;
}
/**
* @file libtdc.h
*
* @brief FMC TDC driver library interface
*
* This file describes the external interface to the FMCTDC
* driver and provides the definitions for proper communication
* with the device
*
* Copyright (c) 2012 CERN
* @author Samuel Iglesias Gonsalvez <siglesias@igalia.com>
* @date Oct 24th 2012
*
* @section license_sec License
* Released under the GPL v2. (and only v2, not any later version)
*/
/*! \file */
/*!
* \mainpage FMC TDC Device Driver
* \author Samuel Iglesias Gonsalvez, Igalia S.L.
* \version 24 Oct 2012
*
* An FPGA Mezzanine Card (FMC) with a Time to Digital Converter chip to perform
* one-shot sub-nanosecond time interval measurements.
*
* HW OHWR project: http://www.ohwr.org/projects/fmc-tdc
*
* SW OHWR project: http://www.ohwr.org/projects/fmc-tdc-sw
*
* Copyright (c) 2012 CERN
*/
#ifndef __TDC_LIB_H__
#define __TDC_LIB_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*!
* This struct is used as argument in almost all the functions.
* The user should not modify its attributes!
*/
struct tdc_board {
int dev_id;
int lun;
char *devbase;
char *sysbase;
uint32_t chan_config; /* Channel activation */
int ctrl[5]; /* The 5 control channels */
int data[5]; /* The 5 data channels */
};
/*!
* This struct is used in tdc_read(). It is the event buffer.
*/
struct tdc_time {
uint64_t utc;
uint64_t ticks;
uint64_t bins;
uint32_t da_capo;
};
/*!
* This enum can be used in tdc_{s,g}et_channels_term() functions.
*/
enum {
CHAN0 = 1 << 0,
CHAN1 = 1 << 1,
CHAN2 = 1 << 2,
CHAN3 = 1 << 3,
CHAN4 = 1 << 4
};
/**
* @brief Get a handle for a FMCTDC device
*
* @param lun - LUN for fmc-tdc card
*
* @return - pointer to struct tdc_board.
*
*/
extern struct tdc_board *tdc_open(int lun);
/**
* @brief Close a FMCTDC device
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_close(struct tdc_board *b);
/**
* @brief Allocate a event buffer.
*
* @param events - number of elements of the buffer
*
* @return - struct tdc_time pointer.
*
*/
extern struct tdc_time *tdc_zalloc(unsigned int events);
/**
* @brief Free a event buffer.
*
* @param buffer - Buffer to be free'd
*
*/
extern void tdc_free(struct tdc_time *buffer);
/**
* @brief Start acquiring events
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_start_acquisition(struct tdc_board *b);
/**
* @brief Stop acquiring events
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_stop_acquisition(struct tdc_board *b);
/**
* @brief Set host UTC time to FMCTDC board
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_host_utc_time(struct tdc_board *b);
/**
* @brief Set UTC time to FMCTDC board
*
* @param b - pointer to struct tdc_board
* @param utc - UTC value to be written, in seconds. EPOC format.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_utc_time(struct tdc_board *b, uint32_t utc);
/**
* @brief Get UTC time to FMCTDC board
*
* @param b - pointer to struct tdc_board
* @param utc - Read UTC value, in seconds. EPOC format.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_utc_time(struct tdc_board *b, uint32_t *utc);
/**
* @brief Set DAC to FMCTDC board
*
* @param b - pointer to struct tdc_board
* @param dw - DAC word value to be written.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_dac_word(struct tdc_board *b, uint32_t dw);
/**
* @brief Set DAC to FMCTDC board
*
* @param b - pointer to struct tdc_board
* @param dw - DAC word value to be read.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_dac_word(struct tdc_board *b, uint32_t *dw);
/**
* @brief Set time threshold
*
* The time threshold is the maximum time we will not have an IRQ if there is no
* enough number of events acquired (configured by timestamp threshold). This
* value must be a 32 bits positive value.
*
* It doens't means that we have always pending events.
*
* @param b - pointer to struct tdc_board
* @param thres - Time threshold, in seconds.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_time_threshold(struct tdc_board *b, uint32_t thres);
/**
* @brief Get time threshold
*
* The time threshold is the maximum time we will not have an IRQ if there is no
* enough number of events acquired (configured by timestamp threshold).
*
* It doens't means that we have always pending events.
*
* @param b - pointer to struct tdc_board
* @param thres - Time threshold, in seconds.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_time_threshold(struct tdc_board *b, uint32_t *thres);
/**
* @brief Set timestamp threshold
*
* The timestamp threshold is the number of events we want per interrupt.
* The value must be between 0 and 64.
*
* @param b - pointer to struct tdc_board
* @param thres - Timestamp threshold, in seconds.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_timestamp_threshold(struct tdc_board *b, uint32_t thres);
/**
* @brief Get timestamp threshold
*
* The timestamp threshold is the number of events we want per interrupt.
*
* @param b - pointer to struct tdc_board
* @param thres - Timestamp threshold, in seconds.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_timestamp_threshold(struct tdc_board *b, uint32_t *thres);
/**
* @brief Set channel termination resistor (50 Ohms)
*
* @param b - pointer to struct tdc_board
* @param config - value to enable the channels. The value is combination of the CHAN0, CHAN1, CHAN2, CHAN3 and CHAN4 enumeration values.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_set_channels_term(struct tdc_board *b, uint32_t config);
/**
* @brief Get channel termination resistor (50 Ohms)
*
* @param b - pointer to struct tdc_board
* @param config - value to enable the channels. The value is combination of the CHAN0, CHAN1, CHAN2, CHAN3 and CHAN4 enumeration values.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_channels_term(struct tdc_board *b, uint32_t *config);
/**
* @brief Enable all channels to acquire
*
* It is needed to execute this function before calling tdc_start_acquisition().
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_activate_channels(struct tdc_board *b);
/**
* @brief Disable all channels to acquire
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_deactivate_channels(struct tdc_board *b);
/**
* @brief Get circular buffer pointer value.
*
* @param b - pointer to struct tdc_board
* @param ptr - circular buffer pointer value with dacapo register included.
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_get_circular_buffer_pointer(struct tdc_board *b, uint32_t *ptr);
/**
* @brief Clear Dacapo register.
*
* @param b - pointer to struct tdc_board
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_clear_dacapo_flag(struct tdc_board *b);
/**
* @brief Read events
*
* @param b - pointer to struct tdc_board
* @param chan - chan number, using the CHAN0, CHAN1, CHAN2, CHAN3 and CHAN4 enumeration.
* @param t - buffer
* @param n - number of events to read.
* @param flags - flags: 0 (blocking read) or O_NONBLOCK (non blocking read).
*
* @return >0 - on success, device file descriptor number
* @return <0 - if failure
*
*/
extern int tdc_read(struct tdc_board *b, int chan, struct tdc_time *t,
int n, int flags);
#ifdef __cplusplus
}
#endif
#endif
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