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:
This diff is collapsed.
# 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;
}
This diff is collapsed.
/*
* 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
This diff is collapsed.
This diff is collapsed.
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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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