Commit c107af64 authored by Federico Vaga's avatar Federico Vaga

Merge branch 'release/v2.2'

parents f0f1224b 655c0b9f
......@@ -59,9 +59,9 @@ author = 'Federico Vaga <federico.vaga@cern.ch>'
# built documents.
#
# The short X.Y version.
version = '2.1'
version = '2.2'
# The full version, including alpha/beta/rc tags.
release = '2.1.0'
release = '2.2.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......@@ -201,4 +201,9 @@ breathe_projects = {
"adc-lib":"doxygen-lib-output/xml/",
}
breathe_projects_source = {
"adc-lib" : ( "../lib/", ["adc-lib.h",
"route.c"])
}
breathe_default_project = "adc-lib"
The Library API
================
Enumerations And Constants
-----------------------------
.. doxygenenum:: adc_supported_boards
.. doxygenenum:: adc_configuration_type
.. doxygenenum:: adc_configuration_trigger_ext
.. doxygenenum:: adc_configuration_trigger_thr
.. doxygenenum:: adc_configuration_acquisition
.. doxygenenum:: adc_configuration_channel
.. doxygenenum:: adc_configuration_board
Board Specific
''''''''''''''
FMC ADC 100M 14 bit 4 Channel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. doxygenenum:: adc_configuration_100m14b4cha
Data Structures
---------------
.. doxygenstruct:: adc_conf
:members:
.. doxygenstruct:: adc_buffer
:members:
.. doxygenstruct:: adc_timestamp
:members:
Functions
----------
.. doxygenfunction:: adc_init
.. doxygenfunction:: adc_exit
.. doxygenfunction:: adc_strerror
.. doxygenfunction:: adc_open
.. doxygenfunction:: adc_open_by_lun
.. doxygenfunction:: adc_close
.. doxygenfunction:: adc_apply_config
.. doxygenfunction:: adc_retrieve_config
.. doxygenfunction:: adc_set_conf_mask
.. doxygenfunction:: adc_set_conf_mask_all
.. doxygenfunction:: adc_set_conf
.. doxygenfunction:: adc_get_conf
.. doxygenfunction:: adc_reset_conf
.. doxygenfunction:: adc_get_capabilities
.. doxygenfunction:: adc_set_param
.. doxygenfunction:: adc_get_param
.. doxygenfunction:: adc_acq_start
.. doxygenfunction:: adc_acq_poll
.. doxygenfunction:: adc_acq_stop
.. doxygenfunction:: adc_request_buffer
.. doxygenfunction:: adc_release_buffer
.. doxygenfunction:: adc_fill_buffer
.. doxygenfunction:: adc_tstamp_buffer
.. doxygenfunction:: adc_trigger_fire
.. autodoxygenfile:: adc-lib.h
......@@ -23,19 +23,17 @@ Initialization and Cleanup
--------------------------
The library may keep internal information, so the application should
call its initialization function. After use, it should call the
exit function to release internal data, but it is not mandatory to
do that at program termination, because the operating system releases
anything in any case -- the library doesn't leave unexpected files
in persistent storage.
call its initialization function :cpp:func:`adc_init()`. After use,
it should call the exit function :cpp:func:`adc_exit()` to release
any internal data.
.. doxygenfunction:: adc_init
:outline:
.. note::
.. doxygenfunction:: adc_exit
:outline:
:cpp:func:`adc_exit()` is not mandatory, the operating system
releases anything in any case -- the library doesn't leave unexpected
files in persistent storage.
The functions don't do anything at this point, but they may be
These functions don't do anything at this point, but they may be
implemented in later releases. For example, the library may scan the
system and cache the list of peripheral cards found, to make
later *open* calls faster. For this reason it is **recommended**
......@@ -52,17 +50,14 @@ Error Reporting
Each library function returns values according to standard *libc*
conventions: -1 or NULL (for functions returning ``int`` or pointers,
resp.) is an error indication. When error happens, the ``errno``
resp.) is an error indication. When error happens, the :manpage:`errno`
variable is set appropriately.
The ``errno`` values can be standard Posix items like ``EINVAL``, or
The :manpage:`errno` values can be standard Posix items like ``EINVAL``, or
library-specific values, for example ``ADC_ENOSET`` (*Cannot set requested
item*). All library-specific error values have a value greater than
1024, to prevent collision with standard values. To convert
such values to a string please use the function:
.. doxygenfunction:: adc_strerror
:outline:
such values to a string please use :cpp:func:`adc_strerror()`
Following an example from the ``example.c`` code available under ``tools``
......@@ -74,21 +69,16 @@ Following an example from the ``example.c`` code available under ``tools``
Opening and closing
--------------------
Each device must be opened before use, and it should be closed after
use. It is not mandatory, but it is recommended, to close if the process
is going to terminate, as the library has no persistent storage to clean
up -- but there may be persistent buffer storage allocated, and
*adc_close* may release it in future versions.
.. doxygenfunction:: adc_open
:outline:
.. doxygenfunction:: adc_close
:outline:
Each device must be opened before use by calling :cpp:func:`adc_open()`,
and it should be closed after use by calling :cpp:func:`adc_close()`.
.. doxygendefine:: ADC_F_FLUSH
.. note ::
.. doxygendefine:: ADC_F_VERBOSE
:cpp:func:`adc_close()` is not mandatory, but it is recommended, to
close if the process is going to terminate, as the library has no
persistent storage to clean up -- but there may be persistent buffer
storage allocated, and :cpp:func:`adc_close()` may release it in
future versions.
Following an example from the ``example.c`` code available under ``tools``
......@@ -96,9 +86,11 @@ Following an example from the ``example.c`` code available under ``tools``
:language: c
:lines: 65-74
As you can see in the example the ``adc_open`` parameters ``totalsamples``
and ``nbuffer`` are optional. The purpose of these parameters is to give
an hint to the library about the resources that the user will ask for.
As you can see in the example the :cpp:func:`adc_open()` parameters
``totalsamples`` and ``nbuffer`` are optional. The purpose of these
parameters is to give an hint to the library about the resources that
the user will ask for.
Managing big acquisitions (hundreds of megabytes, or gigabytes) requires
some pre-allocation of the data, and sometimes this configuration happens
at device level, so it's good to have the information at open time. On
......@@ -106,19 +98,15 @@ the other hand a multi-shot acquisition requires all buffers to be available
at the same time and also this may require some pre-allocation at the device
level.
The data structure returned by ``adc_open`` is an opaque pointer used as token
to access the API functions. The user is not supposed to use or modify this
pointer.
The data structure returned by :cpp:func:`adc_open()` is an opaque pointer
used as token to access the API functions. The user is not supposed to use
or modify this pointer.
Another kind of open function has been provided to satisfy CERN's developers
needs. This is the open by LUN (*Logic Unit Number*); here the LUN concept
reflects the *CERN BE-CO* one.
.. doxygenfunction:: adc_open_by_lun
:outline:
The usage is exactly the same as ``adc_open`` only that it uses the LUN
instead of the device ID.
needs. Function :cpp:func:`adc_open_by_lun()` is the open by LUN
(*Logic Unit Number*); here the LUN concept reflects the *CERN BE-CO* one.
The usage is exactly the same as :cpp:func:`adc_open()` only that it uses
the LUN instead of the device ID.
Configuration
......@@ -131,10 +119,8 @@ configuration is something specific to each board.
Configuration is performed by passing parameters as 32-bit numbers.
The library defines arrays of such parameters, one for each
aspect of the overall problem (triggers, data, and so on).
.. doxygenenum:: adc_configuration_type
:outline:
aspect of the overall problem (triggers, data, and so on)
:cpp:type:`adc_configuration_type`.
Each item in the array has a symbolic name, and each array is associated
with a bit-mask that specifies which parameters have been set.
......@@ -142,30 +128,19 @@ The choice of arrays is driven by the need for generic structures
that can be used unchanged with different hardware cards.
The position of each parameter in the array is fixed, and as boards are added
to this library we may need to add new values for unforeseen requirements.
For each type in :cpp:type:`adc_configuration_type` there is a set of
defined options: :cpp:type:`adc_configuration_board`,
:cpp:type:`adc_configuration_channel`,
:cpp:type:`adc_configuration_acquisition`,
:cpp:type:`adc_configuration_custom`,
:cpp:type:`adc_configuration_trigger_ext`,
:cpp:type:`adc_configuration_trigger_thr`,
:cpp:type:`adc_configuration_trigger_tim`.
.. warning:: Rembember that some of the following options may have
device-specific meaning.
.. doxygenenum:: adc_configuration_board
:outline:
.. doxygenenum:: adc_configuration_channel
:outline:
.. doxygenenum:: adc_configuration_acquisition
:outline:
.. doxygenenum:: adc_trigger_polarity
:outline:
.. doxygenenum:: adc_configuration_trigger_ext
:outline:
.. warning:: Rembember that most options have device-specific meaning.
When unsure, refer to the driver manual for their meaning.
.. doxygenenum:: adc_configuration_trigger_thr
:outline:
.. doxygenenum:: adc_configuration_trigger_tim
:outline:
:cpp:type:`adc_trigger_polarity`
While there is a little overhead in parsing the generic structures,
configuration is not something that happens in hot paths, and we see
......@@ -173,39 +148,30 @@ no problems in that. Also, please note that setting the configuration
and applying it are different steps, and the configuration data structures
can be prepared and saved for later use.
.. doxygenstruct:: adc_conf
.. doxygenfunction:: adc_apply_config
:outline:
.. doxygenfunction:: adc_retrieve_config
:outline:
The users are free to access the ``adc_conf`` structure to set up the
Users are free to access the :cpp:type:`adc_conf` structure to set up the
configuration. To help a bit this configuration process, especially when
it is done dynamically, the library offers some helpers to *touch* this
data structure. The main problem that the following functions solve is the
fact that for eack value you have also to set a bit in the configuration
bitmask. If this is not done correctly the result can be unpredictable.
.. doxygenfunction:: adc_set_conf_mask
:outline:
- :cpp:func:`adc_set_conf_mask()`
- :cpp:func:`adc_set_conf_mask_all()`
.. doxygenfunction:: adc_set_conf_mask_all
:outline:
- :cpp:func:`adc_set_conf()`
.. doxygenfunction:: adc_set_conf
:outline:
- :cpp:func:`adc_get_conf()`
.. doxygenfunction:: adc_get_conf
:outline:
- :cpp:func:`adc_reset_conf()`
.. doxygenfunction:: adc_reset_conf
:outline:
- :cpp:func:`adc_get_capabilities()`
.. doxygenfunction:: adc_get_capabilities
:outline:
Once the configuration structure :cpp:type:`adc_conf` is can be sent
to the hardware with :cpp:func:`adc_apply_config()`. The hardware configuration
can be read back with :cpp:func:`adc_retrieve_config()`. If one of these
functions fail while configuring some values, then the flag
:cpp:any:`ADC_CONF_F_ERROR` will be set in :cpp:member:`adc_conf::flags`
Following an example from the ``example.c`` code available under ``tools``
......@@ -219,18 +185,17 @@ Following an example from the ``example.c`` code available under ``tools``
:language: c
:lines: 186-203
There are also the correspondent functions to set/get more than one
configuration at time:
:cpp:func:`adc_apply_config_n()`, :cpp:func:`adc_retrieve_config_n()`.
To handle the exceptional cases where parameters are actually *strings*
(``char *``) or the library does not support them (e.g. board-specific
parameter), the library added the support to single access parameters.
The parameter's values can be both a *string* (``char *``) or a
*number* (``int``). An higher priority is given to a *string* when both are
asked at the same time.
.. doxygenfunction:: adc_set_param
:outline:
.. doxygenfunction:: adc_get_param
:outline:
*number* (``int``). User can use set values with :cpp:func:`adc_set_param()`
and get values with :cpp:func:`adc_get_param()`
::
......@@ -250,15 +215,50 @@ asked at the same time.
err = adc_get_param(adc, "identifier", NULL, &v_int);
.. _`lib:usr:cfg:ac`:
Offset Auto Clear
'''''''''''''''''
It may happen that due to temperature or other external factors there are
constant offsets on channels. This library, through function
:cpp:func:`adc_offset_auto_clear()` offers a mechanism to be able
to compensate these offsets.::
err = adc_offset_auto_clear(adc, 0x0);
During this procedure, by default, the library will disconnect any input
signal and connect the channel to the local ground level. By using the flag
:cpp:any:`ADC_OFFSET_AC_F_MANUAL` it is possible to use the current input signal
and compensate it.
During this process, the library may change the ADC configuration.
It is possible to ask the library to restore any previous configuration by
using the flag :cpp:any:`ADC_OFFSET_AC_F_RESTORE`.
If this feature is not supported in hardware or in the driver, it is possible
to ask for a software implementation by using the flag
:cpp:any:`ADC_OFFSET_AC_F_SOFTWARE`. In this case the idea is to run some
acquisitions, compute the average and apply this value as offset compensation.
If on a particular board the offset auto-clear needs extra parameters, you
have to set them using :ref:`board specific configurations<lib:usr:cfg:cus>`.
.. warning:: For some boards, once this offset has been configured and
applied, it will stay as long as the ADC *session* is alive. This means
that on :cpp:func:`adc_close()` the configuration is lost.
.. _`lib:usr:cfg:cus`:
Board Specific Configuration
''''''''''''''''''''''''''''
The library tries to be generic and at the same time being able to satisfy most
of the needs, but sometimes it fails in this and some board-specific
configuration is necessary. This is the pupose of the ``ADC_CONF_TYPE_CUS``
configuration type. When a board has some specificity it shall provide a
dedicated header in order to export them; this shall include an ``enum``
describing the configuration values.
configuration is necessary. This is the pupose of the
:cpp:any:`ADC_CONF_TYPE_CUS` configuration type. When a board has some
specificity it shall provide a dedicated header in order to export them;
this shall include an ``enum`` describing the configuration values.
::
......@@ -280,39 +280,49 @@ describing the configuration values.
err = adc_get_conf(&cfg, ADC_SPECIAL_CUS_OPT3, &opt3);
As described above, for a board specific configuration it is reccomended to use
the functions ``adc_set_param`` and ``adc_get_param``. But anyway, the library
includes dedicated header files for each board type that exports helpers for
the most common parameters.
the functions :cpp:func:`adc_set_param()` and :cpp:func:`adc_get_param()`.
But anyway, the library includes dedicated header files for each board type
that exports helpers for the most common parameters.
FMC ADC 100M 14 bit 4 Channel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This card has some parameters which are not described in the generic API. In
order to use them you shall include the header file ``fmc-adc-100m14b4cha.h``.
Here you can get the dedicated symbols for the configuration.
.. doxygenenum:: adc_configuration_100m14b4cha
Here you can get the dedicated symbols for the custom configuration options
:cpp:type:`adc_configuration_100m14b4cha`
Today, within this context, the ``route_to`` options does not have any meaning
since the specifc parameters for this card are global, so there is no need for
any routing mechanism.
Following some special values defined for this board.
.. doxygenenum:: adc_configuration_100m14b4cha_channel_range
The :ref:`zero offset<lib:usr:cfg:ac>` value is valid only within a *session*.
Every time you invoke :cpp:func:`adc_open()` this particular offset get
reset.
Acquisition
-----------
As we already said, the `configuration`_ is the most important step in the
acquisition process. Once the card is configured, the actual management of an
acquisition is limited to very few operations: *start*, *stop* and *poll*.
acquisition is limited to very few operations:
.. doxygenfunction:: adc_acq_start
:outline:
*start*
The ADC starts acquiring data. Depending on the board, this may means
that the board is waiting for a trigger event to happen.
The user can start the acquisition with :cpp:func:`adc_acq_start()`
.. doxygenfunction:: adc_acq_poll
:outline:
*stop*
The ADC stops an on going acquisition. The user can stop the acquisition
with :cpp:func:`adc_acq_stop()`
.. doxygenfunction:: adc_acq_stop
:outline:
*poll*
The user can check if any data is ready to be fetched. The user can use
a special poll function :cpp:func:`adc_acq_poll()`
The full acquisition flow is represented in the following picture
......@@ -343,7 +353,8 @@ Following an example from the ``example.c`` code available under ``tools``
Buffers
-------
Each acquisition run is relying on a buffer. The buffer includes both
Each acquisition run is relying on a buffer. Buffers are described
by the data structure :cpp:type:`adc_buffer`. This includes both
*data* and *meta-data*. The layout of data is depending on the
card and its configuration; the meta-data part depends on the type of
driver currently running for the card. For example, if the driver is
......@@ -352,24 +363,16 @@ to a ZIO control block. The idea is that all information is available
to the user, who can use it at will or ignore it and just use generic
interfaces.
.. doxygenstruct:: adc_buffer
:members:
Buffers are generally allocated by the library, but the application
can provide its own allocator if it really wants to. The reason for
using library-driven allocation is that the library knows better. If,
for example, the driver offers *mmap* support, the library can
choose to map acquired data instead of calling *malloc*. However,
for the rare case where the application knows better, it can override
this. Also, the application should tell the number of buffers and
expected data size at the beginning, so the library can properly
configure the underlying driver, if this is needed.
.. doxygenfunction:: adc_request_buffer
:outline:
.. doxygenfunction:: adc_release_buffer
:outline:
Buffers are generally allocated by the library with
:cpp:func:`adc_request_buffer()` and released with
:cpp:func:`adc_release_buffer()`, but the application can provide its
own allocator if it really wants to. The reason for using library-driven
allocation is that the library knows better. If, for example, the driver
offers *mmap* support, the library can choose to map acquired data
instead of calling *malloc*. However, for the rare case where
the application knows better, it can override this. Also, the application
should tell the number of buffers and expected data size at the beginning,
so the library can properly configure the underlying driver, if this is needed.
By accepting a user-defined allocator, we allow customized management
of the data area in the most flexible way. The application may have
......@@ -391,13 +394,10 @@ samples in the buffer may be bigger than the number of samples that will
actually be acquired later.
Once an acquisition successfully complete it is possible to fill the buffer
with the acquisition data and its meta-data (if any).
.. doxygenfunction:: adc_fill_buffer
:outline:
.. doxygenfunction:: adc_tstamp_buffer
:outline:
with the acquisition data and its meta-data (if any) with
:cpp:func:`adc_fill_buffer()`. It is also possible to extract
the acquisition timestamp :cpp:type:`adc_timestamp` with
:cpp:func:`adc_tstamp_buffer()`
Following an example from the ``example.c`` code available under ``tools``
......@@ -433,12 +433,29 @@ for most of them the extraction is driver-specific.
The details about the actual content of ``zio_control`` is described
in the driver documentation.
Fixup
'''''
It may happen that low-level (HDL, hardware) bugs are found and they cannot
be fixed. This library offers the possibility to compensate for those errors.
For each type of board we can have, or not, a *fixup* algorithm that is able
to fix the data in the buffer and compensate the bugs.
There two possibilities to run the *fixup* algorithm over a given buffer.
The first one is to pass the flag :cpp:any:`ADC_F_FIXUP` to
:cpp:func:`adc_fill_buffer()`.::
err = adc_fill_buffer(adc, buff, ADC_F_FIXUP, &timeout);
The second one is to run it explicitly by invoking
:cpp:func:`adc_buffer_fixup()`.::
err = adc_buffer_fixup(buf);
Time Stamps
-------------
The time-stamp structure is defined as follows:
.. doxygenstruct:: adc_timestamp
The time-stamp structure is defined by :cpp:type:`adc_timestamp`
This is the same structure as used by the `ZIO framework`_, but it is not
specific to ZIO -- the choice made there was just the best of breed,
......@@ -446,10 +463,7 @@ agreed upon in a discussion held at CERN within the BE-CO-HT section.
Currently, time-stamps are only used in association with buffers: after
an acquisition is over and saved to a buffer, the user can ask for
the time-stamp of the acquired buffers.
.. doxygenfunction:: adc_tstamp_buffer
:outline:
Following an example from the ``example.c`` code available under ``tools``
......@@ -464,11 +478,10 @@ The software trigger is something that people may need for testing and many
cards provide support for it. Since this is pure software, there is no need
for this library to handle the configuration which is left to its users.
The user will define all the triggering criteria and then it will use this
library to actually do the trigger.
.. doxygenfunction:: adc_has_trigger_fire
library to actually do the trigger by invoking :cpp:func:`adc_trigger_fire()`.
.. doxygenfunction:: adc_trigger_fire
It is also possible to check if software triggering is actually supported
by the board in use by calling :cpp:func:`adc_has_trigger_fire()`.
::
......
......@@ -23,6 +23,7 @@ LOBJ += init.o
LOBJ += config-zio.o
LOBJ += buffer-zio.o
LOBJ += lib.o
LOBJ += lib-math.o
LOBJ += adc-ziofake.o
LOBJ += adc-genericfake.o
LOBJ += adc-zio.o
......
......@@ -330,6 +330,20 @@ static int adc_genfake_fill_buffer(struct adc_dev *dev,
return 0;
}
static int adc_genfake_buffer_get_sample(struct adc_buffer *buf,
unsigned int chan,
unsigned int acq_sample,
int32_t *value)
{
if (chan >= 1) {
errno = EINVAL;
return -1;
}
*value = ((int8_t *)buf->data)[acq_sample];
return 0;
}
static struct adc_operations fa_generic_fake_op = {
.open = adc_genfake_open,
......@@ -349,6 +363,7 @@ static struct adc_operations fa_generic_fake_op = {
.fill_buffer = adc_genfake_fill_buffer,
.tstamp_buffer = adc_genfake_tstamp_buffer,
.release_buffer = adc_genfake_release_buffer,
.buffer_get_sample = adc_genfake_buffer_get_sample,
};
#define ADC_GENERICFAKE_BRD_MASK (1LL << ADC_CONF_BRD_N_CHAN)
......
......@@ -23,6 +23,20 @@ extern "C" {
#include "adc-lib.h"
#define ADC_CONF_100M14B4CHA_CHN_RANGE_N 3
/**
* List of known voltage ranges to be used with the configuration option
* ADC_CONF_CHN_RANGE
*/
enum adc_configuration_100m14b4cha_channel_range {
ADC_CONF_100M14B4CHA_CHN_RANGE_OPEN_DRAIN= 0,
ADC_CONF_100M14B4CHA_CHN_RANGE_100mV= 0x23,
ADC_CONF_100M14B4CHA_CHN_RANGE_1V= 0x11,
ADC_CONF_100M14B4CHA_CHN_RANGE_10V= 0x45,
ADC_CONF_100M14B4CHA_CHN_RANGE_100mV_CAL= 0x42,
ADC_CONF_100M14B4CHA_CHN_RANGE_1V_CAL= 0x40,
ADC_CONF_100M14B4CHA_CHN_RANGE_10V_CAL= 0x44,
};
/**
* List of possible buffer types (options for ADC_CONF_100M14B4CHA_BUF_TYPE)
......
......@@ -45,6 +45,9 @@ struct adc_operations {
typeof(adc_release_buffer) *release_buffer; /**< @related adc_release_buffer */
typeof(adc_trigger_fire) *trigger_fire; /**< @related adc_trigger_fire */
typeof(adc_buffer_get_sample) *buffer_get_sample; /**< @related adc_buffer_get_sample */
typeof(adc_buffer_fixup) *buffer_fixup; /**< @related adc_buffer_fixup*/
typeof(adc_offset_auto_clear) *offset_auto_clear; /**< @related adc_offset_auto_clear */
};
......@@ -88,6 +91,7 @@ struct __adc_dev_zio {
char *sysbase; /**< base path to device sysfs */
unsigned long samplesize; /**< size of 1 sample */
unsigned long pagesize; /**< size of 1 page */
void *priv; /**< specific ZIO board data */
/* Mandatory field */
struct adc_gid gid; /**< general ADC descriptor */
};
......@@ -181,6 +185,10 @@ int adc_zio_get_param(struct adc_dev *dev, char *name,
int adc_zio_sysfs_set(struct __adc_dev_zio *fa, char *name,
uint32_t *value);
int adc_offset_auto_clear_sw_avg(struct adc_dev *dev,
unsigned long flags,
int32_t *offset);
/*adc-genericfake*/
......
......@@ -26,6 +26,12 @@ extern "C" {
#define ADC_ENOMASK 1030
#define ADC_EDISABLED 1031
#define ADC_EROUTE 1032
#define ADC_ENOP_SWTRG 1033
#define ADC_ENOP_OFFCLR 1034
#define ADC_ENOP_OFFCLRHW 1035
#define ADC_ENOP_OFFCLRSW 1036
#define ADC_OFF_AC_RESTORE_S 1037
#define ADC_OFF_AC_RESTORE_R 1038
/**
* Opaque type. any instance of this should be used as token
......@@ -64,6 +70,7 @@ struct adc_buffer {
void *mapaddr; /**< mmap address */
unsigned long maplen; /**< mmap length */
unsigned long flags; /**< internal to the library */
void *priv; /**< library private date */
};
......@@ -242,6 +249,13 @@ enum adc_configuration_type {
#define __ADC_CONF_LEN 64 /* number of allocated items in each structure */
/**
* It enumerates all possible flags for adc_conf.flags
*/
enum adc_conf_flags {
ADC_CONF_F_ERROR=(1ULL << 0), /**< configuration failed */
};
/**
* ADC configuration descriptor.
*/
......@@ -250,7 +264,7 @@ struct adc_conf {
uint32_t dev_type; /**< device type */
uint32_t route_to; /**< internal route to a particular sub-device
(e.g. a channel) */
uint32_t flags; /**< FIXME how to identify invalid? */
uint32_t flags; /**< flags from adc_conf_flags */
uint64_t mask; /**< capabilities mask, 1 when the correspondent
value in ``value[]`` is valid */
uint32_t value[__ADC_CONF_LEN]; /**< array of configuration value.
......@@ -265,6 +279,30 @@ struct adc_conf {
(used by adc_open) */
#define ADC_F_VERBOSE 0x00020000 /**< Flag used to verbose on stdout/stderr
(usable by any function)*/
#define ADC_F_FIXUP 0x00400000 /**< Flag used to fixup a buffer when
filling it (usable by adc_fill_buffer) */
/**
* Enumeration of all possible flags to driver the auto-clear offset
*/
enum adc_offset_auto_clear_flags {
ADC_OFFSET_AC_F_MANUAL=0x00000001, /**< the signal will be acquired with
the last configuration set */
ADC_OFFSET_AC_F_RESTORE=0x00000002, /**< restore previous
configuration when done. It
does not have any effect when
MANUAL is active*/
ADC_OFFSET_AC_F_SOFTWARE=0x00000004, /**< use software mechanism,
it implies manual
configuration */
ADC_OFFSET_AC_F_ZERO=0x00000008, /**< it sets the zero-offset to zero;
which means that it removes any
previously set zero-offset. */
__ADC_OFFSET_AC_F_MASK=(ADC_OFFSET_AC_F_MANUAL |
ADC_OFFSET_AC_F_RESTORE |
ADC_OFFSET_AC_F_SOFTWARE |
ADC_OFFSET_AC_F_ZERO), /**< used internally */
};
/**
* @defgroup dev Basic
......@@ -287,6 +325,8 @@ extern struct adc_dev *adc_open_by_lun(char *name, int lun,
extern int adc_close(struct adc_dev *dev);
extern int adc_trigger_fire(struct adc_dev *dev);
extern int adc_has_trigger_fire(struct adc_dev *dev);
extern int adc_offset_auto_clear(struct adc_dev *dev,
unsigned long flags);
/**@}*/
......@@ -357,8 +397,13 @@ extern int adc_reset_conf(struct adc_dev *dev, unsigned int flags,
struct adc_conf *conf);
extern int adc_apply_config(struct adc_dev *dev, unsigned int flags,
struct adc_conf *conf);
extern int adc_apply_config_n(struct adc_dev *dev, unsigned int flags,
struct adc_conf *conf, unsigned int n);
extern int adc_retrieve_config(struct adc_dev *dev,
struct adc_conf *conf);
extern int adc_retrieve_config_n(struct adc_dev *dev,
struct adc_conf *conf,
unsigned int n);
extern uint64_t adc_get_capabilities(struct adc_dev *dev,
enum adc_configuration_type type);
extern int adc_get_param(struct adc_dev *dev, char *name,
......@@ -396,6 +441,21 @@ extern struct adc_timestamp *adc_tstamp_buffer(struct adc_buffer *buf,
extern int adc_release_buffer(struct adc_dev *dev,
struct adc_buffer *buf,
void (*free_fn)(void *));
extern int adc_buffer_get_sample(struct adc_buffer *buf,
unsigned int chan,
unsigned int acq_sample,
int32_t *value);
extern int adc_buffer_fixup(struct adc_buffer *buf);
/**@}*/
/**
* @defgroup buf_math Buffer Math
* Mathematical operations on buffers
* @{
*/
extern int adc_buffer_math_avg(struct adc_buffer *buf,
unsigned int chan,
int32_t *avg);
/**@}*/
/* libfmcadc version string */
......
......@@ -34,6 +34,20 @@ static struct adc_dev *adc_ziofake_open(const struct adc_board_type *b,
return dev;
}
static int adc_ziofake_buffer_get_sample(struct adc_buffer *buf,
unsigned int chan,
unsigned int acq_sample,
int32_t *value)
{
if (chan >= 1) {
errno = EINVAL;
return -1;
}
*value = ((int8_t *)buf->data)[acq_sample];
return 0;
}
#define ADC_ZIO_ACQ_MASK (1LL << ADC_CONF_ACQ_N_SHOTS) | \
(1LL << ADC_CONF_ACQ_POST_SAMP) | \
......@@ -62,6 +76,7 @@ static struct adc_operations fa_zio_fake_op = {
.fill_buffer = adc_zio_fill_buffer,
.tstamp_buffer = adc_zio_tstamp_buffer,
.release_buffer = adc_zio_release_buffer,
.buffer_get_sample = adc_ziofake_buffer_get_sample,
};
struct adc_board_type adc_ziofake = {
......
......@@ -36,11 +36,54 @@
#define ADC_CONF_GET 0
#define ADC_CONF_SET 1
#define __ADC_CONF_CHN_OFFSET_ZERO 200
#define ADC_100M_4CH_14BIT_ACQ_MASK (1LL << ADC_CONF_ACQ_N_SHOTS) | \
(1LL << ADC_CONF_ACQ_POST_SAMP) | \
(1LL << ADC_CONF_ACQ_PRE_SAMP) | \
(1LL << ADC_CONF_ACQ_UNDERSAMPLE) | \
(1LL << ADC_CONF_ACQ_FREQ_HZ) | \
(1LL << ADC_CONF_ACQ_N_BITS)
#define ADC_100M_4CH_14BIT_CHN_MASK (1LL << ADC_CONF_CHN_RANGE) | \
(1LL << ADC_CONF_CHN_TERMINATION) | \
(1LL << ADC_CONF_CHN_OFFSET) | \
(1LL << ADC_CONF_CHN_SATURATION)
#define ADC_100M_4CH_14BIT_BRD_MASK (1LL << ADC_CONF_BRD_STATE_MACHINE_STATUS) | \
(1LL << ADC_CONF_BRD_N_CHAN) | \
(1LL << ADC_CONF_BRD_N_TRG_EXT) | \
(1LL << ADC_CONF_BRD_N_TRG_THR) | \
(1LL << ADC_CONF_BRD_N_TRG_TIM)
#define ADC_100M_4CH_14BIT_CUS_MASK (1ULL << ADC_CONF_100M14B4CHA_BUF_TYPE) | \
(1ULL << ADC_CONF_100M14B4CHA_TRG_SW_EN) | \
(1ULL << ADC_CONF_100M14B4CHA_ACQ_MSHOT_MAX) | \
(1ULL << ADC_CONF_100M14B4CHA_BUF_SIZE_KB)
#define ADC_100M_4CH_14BIT_TRG_EXT_MASK (1LL << ADC_CONF_TRG_EXT_ENABLE) | \
(1LL << ADC_CONF_TRG_EXT_POLARITY) | \
(1LL << ADC_CONF_TRG_EXT_DELAY)
#define ADC_100M_4CH_14BIT_TRG_THR_MASK (1LL << ADC_CONF_TRG_THR_ENABLE) | \
(1LL << ADC_CONF_TRG_THR_POLARITY) | \
(1LL << ADC_CONF_TRG_THR_DELAY) | \
(1LL << ADC_CONF_TRG_THR_THRESHOLD) | \
(1LL << ADC_CONF_TRG_THR_HYSTERESIS)
#define ADC_100M_4CH_14BIT_TRG_TIM_MASK 0x0
static typeof(adc_get_param) *adc_param[] = {
[ADC_CONF_GET] = adc_get_param,
[ADC_CONF_SET] = adc_set_param,
};
/**
* ADC internal data
*/
struct adc_100m14b4cha {
/**< keep track of all zero-offset values for each range
(100mV 1V 10V) */
int32_t offset_zero[FA100M14B4C_NCHAN][ADC_CONF_100M14B4CHA_CHN_RANGE_N];
};
static int adc_100m14b4cha_offset_zero_set(struct adc_dev *dev);
/**
* According to totalsamples and nbuffers it resize the buffer to feet
......@@ -133,10 +176,35 @@ static struct adc_dev *adc_100m14b4cha_open(const struct adc_board_type *b,
/* try to size it, do not report errors */
adc_100m14b4cha_buffer_resize(dev, totalsamples, nbuffer);
dev = (void *)&fa->gid;
fa->priv = malloc(sizeof(struct adc_100m14b4cha));
if (!fa->priv)
return NULL;
memset(fa->priv, 0, sizeof(struct adc_100m14b4cha));
err = adc_100m14b4cha_offset_zero_set(dev);
if (err)
goto err;
out:
return dev;
err:
free(fa->priv);
return NULL;
}
/**
* @copydoc adc_close
*/
static int adc_100m14b4cha_close(struct adc_dev *dev)
{
struct __adc_dev_zio *fa = to_dev_zio(dev);
free(fa->priv);
return adc_zio_close(dev);
}
/**
* @copydoc adc_acq_start
......@@ -319,6 +387,7 @@ static int adc_100m14b4cha_config_chn(struct adc_dev *adc,
{
struct __adc_dev_zio *fa = to_dev_zio(adc);
char path[1024];
int err;
if (source > ADC_100M14B4CHA_MAX_CHN_SRC - 1) {
errno = ADC_EROUTE;
......@@ -335,6 +404,9 @@ static int adc_100m14b4cha_config_chn(struct adc_dev *adc,
case ADC_CONF_CHN_OFFSET:
sprintf(path, "cset%d/ch%d-offset", fa->cset, source);
break;
case __ADC_CONF_CHN_OFFSET_ZERO:
sprintf(path, "cset%d/ch%d-offset-zero", fa->cset, source);
break;
case ADC_CONF_CHN_SATURATION:
sprintf(path, "cset%d/ch%d-saturation", fa->cset, source);
break;
......@@ -342,7 +414,17 @@ static int adc_100m14b4cha_config_chn(struct adc_dev *adc,
errno = ADC_ENOCAP;
return -1;
}
return adc_param[direction](adc, path, NULL, (int *)value);
err = adc_param[direction](adc, path, NULL, (int *)value);
if (!err && direction == ADC_CONF_SET &&
index == ADC_CONF_CHN_RANGE) {
/*
* Whenever the user changes voltage range we need to adjust
* the zero-offset value (if any)
*/
return adc_100m14b4cha_offset_zero_set(adc);
}
return err;
}
......@@ -719,38 +801,474 @@ static int adc_100m14b4cha_trigger_fire(struct adc_dev *dev)
NULL, &value);
}
#define ADC_100M_4CH_14BIT_ACQ_MASK (1LL << ADC_CONF_ACQ_N_SHOTS) | \
(1LL << ADC_CONF_ACQ_POST_SAMP) | \
(1LL << ADC_CONF_ACQ_PRE_SAMP) | \
(1LL << ADC_CONF_ACQ_UNDERSAMPLE) | \
(1LL << ADC_CONF_ACQ_FREQ_HZ) | \
(1LL << ADC_CONF_ACQ_N_BITS)
#define ADC_100M_4CH_14BIT_CHN_MASK (1LL << ADC_CONF_CHN_RANGE) | \
(1LL << ADC_CONF_CHN_TERMINATION) | \
(1LL << ADC_CONF_CHN_OFFSET) | \
(1LL << ADC_CONF_CHN_SATURATION)
#define ADC_100M_4CH_14BIT_BRD_MASK (1LL << ADC_CONF_BRD_STATE_MACHINE_STATUS) | \
(1LL << ADC_CONF_BRD_N_CHAN) | \
(1LL << ADC_CONF_BRD_N_TRG_EXT) | \
(1LL << ADC_CONF_BRD_N_TRG_THR) | \
(1LL << ADC_CONF_BRD_N_TRG_TIM)
#define ADC_100M_4CH_14BIT_CUS_MASK (1ULL << ADC_CONF_100M14B4CHA_BUF_TYPE) | \
(1ULL << ADC_CONF_100M14B4CHA_TRG_SW_EN) | \
(1ULL << ADC_CONF_100M14B4CHA_ACQ_MSHOT_MAX) | \
(1ULL << ADC_CONF_100M14B4CHA_BUF_SIZE_KB)
#define ADC_100M_4CH_14BIT_TRG_EXT_MASK (1LL << ADC_CONF_TRG_EXT_ENABLE) | \
(1LL << ADC_CONF_TRG_EXT_POLARITY) | \
(1LL << ADC_CONF_TRG_EXT_DELAY)
#define ADC_100M_4CH_14BIT_TRG_THR_MASK (1LL << ADC_CONF_TRG_THR_ENABLE) | \
(1LL << ADC_CONF_TRG_THR_POLARITY) | \
(1LL << ADC_CONF_TRG_THR_DELAY) | \
(1LL << ADC_CONF_TRG_THR_THRESHOLD) | \
(1LL << ADC_CONF_TRG_THR_HYSTERESIS)
#define ADC_100M_4CH_14BIT_TRG_TIM_MASK 0x0
static int adc_100m14b4cha_buffer_get_sample(struct adc_buffer *buf,
unsigned int chan,
unsigned int acq_sample,
int32_t *value)
{
if (chan >= FA100M14B4C_NCHAN) {
errno = EINVAL;
return -1;
}
*value = ((int16_t *)buf->data)[acq_sample * FA100M14B4C_NCHAN + chan];
return 0;
}
struct tmp_cfg_store {
uint32_t trg_source;
struct adc_conf chn[FA100M14B4C_NCHAN];
struct adc_conf acq;
};
static int __cfg_offac_save(struct adc_dev *dev,
struct tmp_cfg_store *cfg,
unsigned long flags)
{
int err, i;
if ((flags & ADC_OFFSET_AC_F_MANUAL) ||
!(flags & ADC_OFFSET_AC_F_RESTORE))
return 0;
cfg->acq.type = ADC_CONF_TYPE_ACQ;
cfg->acq.mask = (1ULL << ADC_CONF_ACQ_N_SHOTS) |
(1ULL << ADC_CONF_ACQ_PRE_SAMP) |
(1ULL << ADC_CONF_ACQ_POST_SAMP) |
(1ULL << ADC_CONF_ACQ_UNDERSAMPLE);
err = adc_retrieve_config(dev, &cfg->acq);
if (err)
goto err;
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
memset(&cfg->chn[i], 0, sizeof(struct adc_conf));
cfg->chn[i].type = ADC_CONF_TYPE_CHN;
cfg->chn[i].mask = ADC_100M_4CH_14BIT_CHN_MASK;
}
err = adc_retrieve_config_n(dev, cfg->chn, FA100M14B4C_NCHAN);
if (err)
goto err;
err = adc_get_param(dev, "cset0/trigger/source", NULL,
(int *)&cfg->trg_source);
if (err)
goto err;
return 0;
err:
errno = ADC_OFF_AC_RESTORE_S;
return err;
}
static int __cfg_offac_restore(struct adc_dev *dev,
struct tmp_cfg_store *cfg,
unsigned long flags)
{
int err = 0;
if ((flags & ADC_OFFSET_AC_F_MANUAL) ||
!(flags & ADC_OFFSET_AC_F_RESTORE))
return 0;
err |= adc_apply_config(dev, 0, &cfg->acq);
err |= adc_apply_config_n(dev, 0, cfg->chn, FA100M14B4C_NCHAN);
err |= adc_set_param(dev, "cset0/trigger/source", NULL,
(int *)&cfg->trg_source);
if (err)
errno = ADC_OFF_AC_RESTORE_R;
return err;
}
/*
* Following what the configuration for the offset clear computation
*/
#define CFG_OFFAC_NSAMPLES 100000
#define CFG_OFFAC_N 6
static struct adc_conf cfg_offac[CFG_OFFAC_N] ={
{
.type = ADC_CONF_TYPE_ACQ,
.mask = (1ULL << ADC_CONF_ACQ_N_SHOTS) |
(1ULL << ADC_CONF_ACQ_PRE_SAMP) |
(1ULL << ADC_CONF_ACQ_POST_SAMP) |
(1ULL << ADC_CONF_ACQ_UNDERSAMPLE),
.value = {
[ADC_CONF_ACQ_N_SHOTS] = 1,
[ADC_CONF_ACQ_PRE_SAMP] = 0,
[ADC_CONF_ACQ_POST_SAMP] = CFG_OFFAC_NSAMPLES,
[ADC_CONF_ACQ_UNDERSAMPLE] = 0,
},
},
{
.type = ADC_CONF_TYPE_CUS,
.mask = (1ULL << ADC_CONF_100M14B4CHA_TRG_SW_EN) |
(1ULL << ADC_CONF_100M14B4CHA_BUF_SIZE_KB),
.value = {
[ADC_CONF_100M14B4CHA_TRG_SW_EN] = 1,
[ADC_CONF_100M14B4CHA_BUF_SIZE_KB] = 2048,
},
},
{
.type= ADC_CONF_TYPE_CHN,
.route_to = 0,
.mask = (1LL << ADC_CONF_CHN_TERMINATION) |
(1LL << ADC_CONF_CHN_OFFSET) |
(1LL << ADC_CONF_CHN_SATURATION),
.value = {
[ADC_CONF_CHN_TERMINATION] = 0,
[ADC_CONF_CHN_OFFSET] = 0,
[ADC_CONF_CHN_SATURATION] = 0x7FFF,
},
},
{
.type= ADC_CONF_TYPE_CHN,
.route_to = 1,
.mask = (1LL << ADC_CONF_CHN_TERMINATION) |
(1LL << ADC_CONF_CHN_OFFSET) |
(1LL << ADC_CONF_CHN_SATURATION),
.value = {
[ADC_CONF_CHN_TERMINATION] = 0,
[ADC_CONF_CHN_OFFSET] = 0,
[ADC_CONF_CHN_SATURATION] = 0x7FFF,
},
},
{
.type= ADC_CONF_TYPE_CHN,
.route_to = 2,
.mask = (1LL << ADC_CONF_CHN_TERMINATION) |
(1LL << ADC_CONF_CHN_OFFSET) |
(1LL << ADC_CONF_CHN_SATURATION),
.value = {
[ADC_CONF_CHN_TERMINATION] = 0,
[ADC_CONF_CHN_OFFSET] = 0,
[ADC_CONF_CHN_SATURATION] = 0x7FFF,
},
},
{
.type= ADC_CONF_TYPE_CHN,
.route_to = 3,
.mask = (1LL << ADC_CONF_CHN_TERMINATION) |
(1LL << ADC_CONF_CHN_OFFSET) |
(1LL << ADC_CONF_CHN_SATURATION),
.value = {
[ADC_CONF_CHN_TERMINATION] = 0,
[ADC_CONF_CHN_OFFSET] = 0,
[ADC_CONF_CHN_SATURATION] = 0x7FFF,
},
}
};
/**
* It configures the ADC for the offset auto-clear
* @param[in] dev ADC token
* @param[in] chan channel number [0, 4]
* @param[in] flags options
*
* @return 0 on success, otherwise -1 and errno is appropriately set
*
* pre-sample: 0
* post-sample: 100000
* undersample: 0
* nshots: 1
* offset: 0
* range: open-drain
* sw-trg: enable
* buf-size: 2MiB
*/
static int __cfg_offac_apply(struct adc_dev *dev,
unsigned long flags)
{
int err, trgsrc = FA100M14B4C_TRG_SRC_SW, i;
uint32_t offset = 0;
/* zero-offset needs to be cleard always */
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
err = adc_100m14b4cha_config_chn(dev, i,
__ADC_CONF_CHN_OFFSET_ZERO,
&offset, ADC_CONF_SET);
if (err)
return err;
}
if (flags & ADC_OFFSET_AC_F_MANUAL)
return 0;
err = adc_set_param(dev, "cset0/trigger/source", NULL,
&trgsrc);
if (err)
return err;
err = adc_apply_config_n(dev, 0 , cfg_offac, CFG_OFFAC_N);
if (err)
return err;
return 0;
}
/**
* It converts an hardware value to uV
* @param[in] val value to be converted (hardware format)
* @param[in] range switch configuration read from the hardware
*
* @return uV value
*/
static int32_t __convert_hw_to_uv(int32_t val, unsigned int range)
{
int32_t result_int, result_frac, range_uV, factor;
switch (range) {
case ADC_CONF_100M14B4CHA_CHN_RANGE_10V_CAL:
case ADC_CONF_100M14B4CHA_CHN_RANGE_10V:
/* 10V */
range_uV = 10000000;
break;
case ADC_CONF_100M14B4CHA_CHN_RANGE_100mV_CAL:
case ADC_CONF_100M14B4CHA_CHN_RANGE_100mV:
/* 100mV */
range_uV = 100000;
break;
case ADC_CONF_100M14B4CHA_CHN_RANGE_1V_CAL:
case ADC_CONF_100M14B4CHA_CHN_RANGE_1V:
case ADC_CONF_100M14B4CHA_CHN_RANGE_OPEN_DRAIN:
/* 1V */
range_uV = 1000000;
break;
default:
fprintf(stderr, "Invalid switch configuration 0x%x\n", range);
return 0;
}
factor = (range_uV / BIT(14));
result_int = ((val >> 2) * factor);
result_frac = ((val & 0x3) * 10) * (factor / 1000);
return result_int + result_frac;
}
static int __offz_rng_idx_get(enum adc_configuration_100m14b4cha_channel_range range)
{
int idx = 0;
switch (range) {
case ADC_CONF_100M14B4CHA_CHN_RANGE_100mV:
case ADC_CONF_100M14B4CHA_CHN_RANGE_100mV_CAL:
idx = 0;
break;
case ADC_CONF_100M14B4CHA_CHN_RANGE_1V:
case ADC_CONF_100M14B4CHA_CHN_RANGE_1V_CAL:
case ADC_CONF_100M14B4CHA_CHN_RANGE_OPEN_DRAIN:
idx = 1;
break;
case ADC_CONF_100M14B4CHA_CHN_RANGE_10V:
case ADC_CONF_100M14B4CHA_CHN_RANGE_10V_CAL:
idx = 2;
break;
}
return idx;
}
/**
* According to the current voltage range, it sets the zero-offset
* value
*/
static int adc_100m14b4cha_offset_zero_set(struct adc_dev *dev)
{
struct __adc_dev_zio *fa = to_dev_zio(dev);
struct adc_100m14b4cha *adcdata = fa->priv;
int i, err = 0;
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
uint32_t offset, range;
int ret;
ret = adc_100m14b4cha_config_chn(dev, i,
ADC_CONF_CHN_RANGE,
&range,
ADC_CONF_GET);
if (ret) {
err |= ret;
continue;
}
offset = adcdata->offset_zero[i][__offz_rng_idx_get(range)];
err |= adc_100m14b4cha_config_chn(dev, i,
__ADC_CONF_CHN_OFFSET_ZERO,
&offset, ADC_CONF_SET);
}
return err ? -1 : 0;
}
/**
* It sets a voltage range on all channels
* @param[in] dev ADC token
* @param[in] range voltage range
* @return 0 on success
*/
static int adc_100m14b4cha_range_set_all(struct adc_dev *dev,
enum adc_configuration_100m14b4cha_channel_range range)
{
int i, err;
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
err = adc_100m14b4cha_config_chn(dev, i,
ADC_CONF_CHN_RANGE,
&range,
ADC_CONF_SET);
if (err)
return err;
}
return 0;
}
/**
* @copydoc adc_offset_auto_clear
*
* MANUAL MODE (user configuration)
*/
static int adc_100m14b4cha_offac_sw_avg_man(struct adc_dev *dev,
unsigned long flags)
{
struct __adc_dev_zio *fa = to_dev_zio(dev);
struct adc_100m14b4cha *adcdata = fa->priv;
int32_t offset[FA100M14B4C_NCHAN];
uint32_t ranges[FA100M14B4C_NCHAN];
int err, i;
memset(ranges, 0, sizeof(ranges));
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
err = adc_100m14b4cha_config_chn(dev, i,
ADC_CONF_CHN_RANGE,
&ranges[i],
ADC_CONF_GET);
if (err)
return err;
}
err = __cfg_offac_apply(dev, flags);
if (err)
return err;
err = adc_offset_auto_clear_sw_avg(dev, flags, offset);
if (err)
return err;
/*
* Since the Hw does (Vin - Vdac), to compensate a
* positive offset we need to apply a positive value
* to the DAC. For this reason we do not need to revert
* that value: the offset sign measured it is fine
*/
for (i = 0; i < FA100M14B4C_NCHAN; ++i) {
int range = __offz_rng_idx_get(ranges[i]);
offset[i] = __convert_hw_to_uv(offset[i], ranges[i]);
adcdata->offset_zero[i][range] = offset[i];
}
return 0;
}
/**
* @copydoc adc_offset_auto_clear
*
* AUTOMATIC MODE
*/
static int adc_100m14b4cha_offac_sw_avg_auto(struct adc_dev *dev,
unsigned long flags)
{
struct tmp_cfg_store tmpcfg;
struct __adc_dev_zio *fa = to_dev_zio(dev);
struct adc_100m14b4cha *adcdata = fa->priv;
uint32_t ranges[ADC_CONF_100M14B4CHA_CHN_RANGE_N] = {
ADC_CONF_100M14B4CHA_CHN_RANGE_100mV_CAL,
ADC_CONF_100M14B4CHA_CHN_RANGE_1V_CAL,
ADC_CONF_100M14B4CHA_CHN_RANGE_10V_CAL,
};
int i, err = 0, err_rst;
err = __cfg_offac_save(dev, &tmpcfg, flags);
if (err)
return err;
err = __cfg_offac_apply(dev, flags);
if (err)
goto out;
/* Loop over all ranges and do the zero-offset calibration */
for (i = 0; i < ADC_CONF_100M14B4CHA_CHN_RANGE_N; ++i) {
unsigned int range = ranges[i];
int32_t offset[FA100M14B4C_NCHAN];
int k;
err = adc_100m14b4cha_range_set_all(dev, range);
if (err)
goto out;
err = adc_offset_auto_clear_sw_avg(dev, flags, offset);
if (err)
return err;
/*
* Since the Hw does (Vin - Vdac), to compensate a
* positive offset we need to apply a positive value
* to the DAC. For this reason we do not need to revert
* that value: the offset sign measured it is fine
*/
for (k = 0; k < FA100M14B4C_NCHAN; ++k) {
offset[k] = __convert_hw_to_uv(offset[k], range);
adcdata->offset_zero[k][i] = offset[k];
}
}
out:
err_rst = __cfg_offac_restore(dev, &tmpcfg, flags);
if (err_rst) {
errno = ADC_OFF_AC_RESTORE_R;
return err_rst;
}
return err;
}
/**
* @copydoc adc_offset_auto_clear
*/
static int adc_100m14b4cha_offset_auto_clear(struct adc_dev *dev,
unsigned long flags)
{
struct __adc_dev_zio *fa = to_dev_zio(dev);
struct adc_100m14b4cha *adcdata = fa->priv;
int err;
/* this board does not have a hardware mechanism */
flags |= ADC_OFFSET_AC_F_SOFTWARE;
if (flags & ADC_OFFSET_AC_F_ZERO) {
memset(adcdata->offset_zero, 0,
sizeof(int32_t) * ADC_CONF_100M14B4CHA_CHN_RANGE_N * FA100M14B4C_NCHAN);
goto out;
}
if (flags & ADC_OFFSET_AC_F_MANUAL)
err = adc_100m14b4cha_offac_sw_avg_man(dev, flags);
else
err = adc_100m14b4cha_offac_sw_avg_auto(dev, flags);
if (err)
return err;
out:
return adc_100m14b4cha_offset_zero_set(dev);
}
static struct adc_operations fa_100ms_4ch_14bit_op = {
.open = adc_100m14b4cha_open,
.close = adc_zio_close,
.close = adc_100m14b4cha_close,
.acq_start = adc_100m14b4cha_acq_start,
.acq_poll = adc_zio_acq_poll,
......@@ -767,6 +1285,10 @@ static struct adc_operations fa_100ms_4ch_14bit_op = {
.tstamp_buffer = adc_zio_tstamp_buffer,
.release_buffer = adc_zio_release_buffer,
.trigger_fire = adc_100m14b4cha_trigger_fire,
.buffer_get_sample = adc_100m14b4cha_buffer_get_sample,
.buffer_fixup = NULL,
.offset_auto_clear = adc_100m14b4cha_offset_auto_clear,
};
struct adc_board_type fmcadc_100ms_4ch_14bit = {
......
/*
* Routing public functions to device-specific code
*
* Copyright (C) 2018 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@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 or, at your
* option, any later version.
*/
#include <errno.h>
#include <string.h>
#include "adc-lib.h"
#include "adc-lib-int.h"
/**
* It computes the avarege voltage within the given buffer
* @param[in] buf data set to use
* @param[in] chan channel number [0, NCHAN]
* @param[out] avg the computer avarage. The scale depends on the
* configuration,
* @return 0 on success, -1 on error and errno is set appropriately
* EINVAL: if the buffer is invalid, or channel is invalid
*/
int adc_buffer_math_avg(struct adc_buffer *buf,
unsigned int chan,
int32_t *avg)
{
struct adc_conf cfg_brd;
uint32_t nchan = 0;
int i, err;
int64_t total = 0;
memset(&cfg_brd, 0, sizeof(struct adc_conf));
cfg_brd.type = ADC_CONF_TYPE_BRD;
adc_set_conf_mask_all(&cfg_brd, buf->dev);
err = adc_retrieve_config(buf->dev, &cfg_brd);
if (err)
return err;
adc_get_conf(&cfg_brd, ADC_CONF_BRD_N_CHAN, &nchan);
if (chan >= nchan) {
errno = EINVAL;
return -1;
}
for (i = 0; i < buf->nsamples; ++i) {
int32_t val;
err = adc_buffer_get_sample(buf, chan, i, &val);
if (err)
return err;
total += val;
}
*avg = total / buf->nsamples;
return 0;
}
......@@ -9,6 +9,7 @@
* version 2 as published by the Free Software Foundation or, at your
* option, any later version.
*/
#include <errno.h>
#include <string.h>
#include "adc-lib.h"
#include "adc-lib-int.h"
......@@ -31,6 +32,12 @@ static struct adc_errors {
{ ADC_ENOMASK, "Missing configuration mask"},
{ ADC_EDISABLED, "Trigger is disabled: I/O aborted"},
{ ADC_EROUTE, "Cannot route correctly the configuration"},
{ADC_ENOP_SWTRG, "Operation not supported: software trigger"},
{ADC_ENOP_OFFCLR, "Operation not supported: offset auto-clear"},
{ADC_ENOP_OFFCLRHW, "Operation not supported: offset auto-clear hardware"},
{ADC_ENOP_OFFCLRSW, "Operation not supported: offset auto-clear software"},
{ADC_OFF_AC_RESTORE_S, "Offset auto-clear: cannot store configuration"},
{ADC_OFF_AC_RESTORE_R, "Offset auto-clear: cannot restore configuration"},
{ 0, }
};
......@@ -86,3 +93,96 @@ uint64_t adc_get_capabilities(struct adc_dev *dev,
return b->board->capabilities[type];
}
/**
* It computes what are the necessary offsets to apply on channels
* in order to clear a constant offset.
* @param[in] dev ADC device token
* @param[in] flags options @see adc_offset_auto_clear_flags
* @param[out] offset vector of compensation offsets (one for each channel)
* @return 0 on success, -1 on error and errno is set appropriately
* EINVAL: invalid flags value
* ADC_ENOP_SWTRG: when software trigger is missing
*
* The configuration is board dependent, so here we just run and apply
* the compensation offset. The configuration is left to the user.
*
* Since this function uses software trigger, the user should disable
* all trigger sources except the software one.
*
* NOTE: offset is a vector, so when you call this function be careful
* about the size of that parameter, it must big enough to host a value
* for each board channel.
*/
int adc_offset_auto_clear_sw_avg(struct adc_dev *dev,
unsigned long flags,
int32_t *offset)
{
struct adc_conf cfg;
struct adc_buffer *buf;
struct timeval tv = {0, 0};
uint32_t nchan, pre, post;
int err, err_stop, i;
unsigned int nsamples;
if (!(flags & ADC_OFFSET_AC_F_SOFTWARE)) {
errno = EINVAL;
return -1;
}
if (!adc_has_trigger_fire(dev)) {
errno = ADC_ENOP_SWTRG;
return -1;
}
memset(&cfg, 0, sizeof(struct adc_conf));
cfg.type = ADC_CONF_TYPE_ACQ;
adc_set_conf_mask(&cfg, ADC_CONF_ACQ_PRE_SAMP);
adc_set_conf_mask(&cfg, ADC_CONF_ACQ_POST_SAMP);
err = adc_retrieve_config(dev, &cfg);
if (err)
return err;
adc_get_conf(&cfg, ADC_CONF_ACQ_PRE_SAMP, &pre);
adc_get_conf(&cfg, ADC_CONF_ACQ_POST_SAMP, &post);
nsamples = pre + post;
memset(&cfg, 0, sizeof(struct adc_conf));
cfg.type = ADC_CONF_TYPE_BRD;
adc_set_conf_mask(&cfg, ADC_CONF_BRD_N_CHAN);
err = adc_retrieve_config(dev, &cfg);
if (err)
return err;
adc_get_conf(&cfg, ADC_CONF_BRD_N_CHAN, &nchan);
buf = adc_request_buffer(dev, nsamples, NULL, 0);
if (!buf)
return -1;
err = adc_acq_start(dev, ADC_F_FLUSH, &tv);
if (err)
goto out;
err = adc_trigger_fire(dev);
if (err)
goto out;
tv.tv_sec = 10;
err = adc_fill_buffer(dev, buf, 0, &tv);
if (err)
goto out;
for (i = 0; i < nchan; ++i) {
err = adc_buffer_math_avg(buf, i, &offset[i]);
if (err)
goto out;
}
out:
adc_release_buffer(dev, buf, NULL);
err_stop = adc_acq_stop(dev, 0);
if (err_stop)
return err_stop;
return err;
}
......@@ -75,6 +75,8 @@ static const struct adc_board_type *find_board(char *name)
* It should be passed around but not be looked into.
*
* @todo Open should choose the buffer type
*
* It initialize the board, this can be different board to board.
*/
struct adc_dev *adc_open(char *name, unsigned int dev_id,
unsigned long totalsamples,
......@@ -254,13 +256,19 @@ int adc_acq_stop(struct adc_dev *dev, unsigned int flags)
* @param[in] flags
* @param[in] conf configuration to apply
* @return 0 on success, -1 on error and errno is set appropriately
*
* If the configuration cannot be applied entirely, then this function
* will set ADC_CONF_F_ERROR in conf->flags
*/
int adc_apply_config(struct adc_dev *dev, unsigned int flags,
struct adc_conf *conf)
struct adc_conf *conf)
{
struct adc_gid *g = (struct adc_gid *)dev;
const struct adc_board_type *b = g->board;
uint64_t cap_mask;
int err;
conf->flags &= ~ADC_CONF_F_ERROR;
if (!b->adc_op->apply_config) {
errno = ADC_ENOP;
......@@ -282,9 +290,40 @@ int adc_apply_config(struct adc_dev *dev, unsigned int flags,
errno = ADC_ENOCAP;
return -1;
}
return b->adc_op->apply_config(dev, flags, conf);
err = b->adc_op->apply_config(dev, flags, conf);
if (err)
conf->flags |= ADC_CONF_F_ERROR;
return err;
}
/**
* It applies the given configuration to the device
* @param[in] dev ADC device token
* @param[in] flags
* @param[in] conf list of configurations to apply
* @param[in] n number of valid configurations in conf
* @return 0 on success, -1 on error and errno is set appropriately
*
* If a configuration cannot be applied entirely, then this function
* will set ADC_CONF_F_ERROR in conf[i].flags
*/
int adc_apply_config_n(struct adc_dev *dev, unsigned int flags,
struct adc_conf *conf, unsigned int n)
{
unsigned int i;
int err_last = 0;
for (i = 0; i < n; ++i) {
int err = adc_apply_config(dev, flags, &conf[i]);
if (err) {
err_last = err;
continue;
}
}
return err_last;
}
/**
* It retrieve the current device configuration
......@@ -292,12 +331,18 @@ int adc_apply_config(struct adc_dev *dev, unsigned int flags,
* @param[in,out] conf configuration descriptor to fill with configuration
* values.
* @return 0 on success, -1 on error and errno is set appropriately
*
* If the configuration cannot be retrieved entirely, then this function
* will set ADC_CONF_F_ERROR in conf->flags
*/
int adc_retrieve_config(struct adc_dev *dev, struct adc_conf *conf)
{
struct adc_gid *g = (struct adc_gid *)dev;
const struct adc_board_type *b = g->board;
uint64_t cap_mask;
int err;
conf->flags &= ~ADC_CONF_F_ERROR;
if (!b->adc_op->retrieve_config) {
errno = ADC_ENOP;
......@@ -319,9 +364,39 @@ int adc_retrieve_config(struct adc_dev *dev, struct adc_conf *conf)
errno = ADC_ENOCAP;
return -1;
}
return b->adc_op->retrieve_config(dev, conf);
err = b->adc_op->retrieve_config(dev, conf);
if (err)
conf->flags |= ADC_CONF_F_ERROR;
return err;
}
/**
* It retrieve the current device configuration
* @param[in] dev ADC device token
* @param[in,out] conf configuration descriptor to fill with configuration
* values.
* @param[in] n number of valid configurations in conf
* @return 0 on success, -1 on error and errno is set appropriately
*
* If a configuration cannot be retrieved entirely, then this function
* will set ADC_CONF_F_ERROR in conf[i].flags
*/
int adc_retrieve_config_n(struct adc_dev *dev, struct adc_conf *conf,
unsigned int n)
{
unsigned int i;
int err_last = 0;
for (i = 0; i < n; ++i) {
int err = adc_retrieve_config(dev, &conf[i]);
if (err) {
err_last = err;
continue;
}
}
return err_last;
}
/**
* It get a single parameter from the device
......@@ -402,6 +477,7 @@ struct adc_buffer *adc_request_buffer(struct adc_dev *dev,
* @param[in] dev ADC device token
* @param[in] buf the buffer to fill
* @param[in] flags used to control how to fill the buffer
* (ADC_F_FIXUP)
* @param[in] timeout maximum time to fill the buffer. The behavior
* is similar to *select(1)*. If NULL, there is no timeout and
* the function will wait until the data is ready.
......@@ -419,13 +495,21 @@ int adc_fill_buffer(struct adc_dev *dev,
{
struct adc_gid *g = (struct adc_gid *)dev;
const struct adc_board_type *b = g->board;
int err;
if (!b->adc_op->fill_buffer) {
errno = ADC_ENOP;
return -1;
}
return b->adc_op->fill_buffer(dev, buf, flags, timeout);
err = b->adc_op->fill_buffer(dev, buf, flags, timeout);
if (err)
return err;
if (flags & ADC_F_FIXUP)
return adc_buffer_fixup(buf);
return 0;
}
......@@ -499,6 +583,7 @@ int adc_has_trigger_fire(struct adc_dev *dev)
* It forces the board to trigger the acquisition
* @param[in] dev ADC device token
* @return 0 on success, -1 on error and errno is set appropriately
* ADC_ENOP_SWTRG: when software trigger is not supported
*/
int adc_trigger_fire(struct adc_dev *dev)
{
......@@ -506,8 +591,101 @@ int adc_trigger_fire(struct adc_dev *dev)
const struct adc_board_type *b = g->board;
if (!b->adc_op->trigger_fire) {
errno = ADC_ENOP;
errno = ADC_ENOP_SWTRG;
return -1;
}
return b->adc_op->trigger_fire(dev);
}
/**
* It gets a sample from the buffer
* @param[in] buf buffer to use
* @param[in] chan which channel
* @param[in] acq_sample acquisition sample
* @param[out] value
* @return 0 on success, -1 on error and errno is set appropriately
* ADC_ENOP if the operation is not supported by the board that
* acquired the buffer
*/
int adc_buffer_get_sample(struct adc_buffer *buf,
unsigned int chan,
unsigned int acq_sample,
int32_t *value)
{
struct adc_gid *g = (struct adc_gid *)buf->dev;
const struct adc_board_type *b = g->board;
if (!b->adc_op->buffer_get_sample) {
errno = ADC_ENOP;
return -1;
}
if (acq_sample >= buf->nsamples) {
errno = EINVAL;
return -1;
}
return b->adc_op->buffer_get_sample(buf, chan, acq_sample, value);
}
/**
* It fixes the given buffer if there is any need
* @param[in, out] buf acquistion buffer (filled)
* @return 0 on success, -1 on error and errno is set appropriately
*
* This is used by board specifc code to compensate some known error
*/
int adc_buffer_fixup(struct adc_buffer *buf)
{
struct adc_gid *g = (struct adc_gid *)buf->dev;
const struct adc_board_type *b = g->board;
if (b->adc_op->buffer_fixup)
return b->adc_op->buffer_fixup(buf);
return 0;
}
/**
* It checks if the board support offset auto-clear
* @param[in] dev ADC device token
* @return 1 when it does support offset auto clear; 0 when it does not
*/
int adc_has_offset_auto_clear(struct adc_dev *dev)
{
struct adc_gid *g = (struct adc_gid *)dev;
const struct adc_board_type *b = g->board;
return !!b->adc_op->offset_auto_clear;
}
/**
* It clears eventual offsets on all channel
* @param[in] dev ADC device token
* @param[in] flags options @see adc_offset_auto_clear_flags
* @return 0 on success, -1 on error and errno is set appropriately
* EINVAL: invalid flags value
* ADC_ENOP_OFFCLR: when offset auto-clear is not supported
*
* NOTE: The function may overwrite your current configuration (unless
* you use a flag) and it may remove any trace of previous acquisitions.
*
* Offset configuration is always overwritten (no matter what flag you use)
*/
int adc_offset_auto_clear(struct adc_dev *dev,
unsigned long flags)
{
struct adc_gid *g = (struct adc_gid *)dev;
const struct adc_board_type *b = g->board;
if (!adc_has_offset_auto_clear(dev)) {
errno = ADC_ENOP_OFFCLR;
return -1;
}
if (flags & ~__ADC_OFFSET_AC_F_MASK) {
errno = EINVAL;
return -1;
}
return b->adc_op->offset_auto_clear(dev, flags);
}
......@@ -22,46 +22,95 @@
#include <unistd.h>
#include <inttypes.h>
#include <adc-lib.h>
#include <adc-lib-100m14b4cha.h>
static int arg_show_config = 0;
static int arg_no_read = 0;
static int arg_plot = 0;
static int arg_x_display = 0;
static int arg_trgsw_delay = 0;
static int arg_trgsw = 0;
static int fixup = 0;
static int statistics = 0;
static char git_version[] = "version: " GIT_VERSION;
static const char *help_msg =
"adc-acq -D <device-name>@0x<device-id> [OPTIONS]\n\n";
static const char *help_msg_opt =
" --device|-D <device-name>@<device-id>: unique device identifier (e.g.: \"fmc-adc@0x0400\")\n"
" --acquisition|-a <parameters> configure the acquisition\n"
" <pre-sample>,<post-sample>,<n-shots>,<undersample>\n"
" --trg-ext <parameters> configure an external trigger\n"
" <idx>,<enable>,<polarity>,<delay>\n"
" --trg-thr <parameters> configure a threshold trigger\n"
" <idx>,<enable>,<polarity>,<threshold>,<hysteresis>,<delay>\n"
" --trg-tim <parameters> configure a timing trigger\n"
" <NOT IMPLEMENTED YET>\n"
" --trg-sw <parameters> configure a software trigger\n"
" <delay_seconds>\n"
" --off-clr <ac-mode>,<idx> run offset-clear on a given channel\n"
" --channel| -c <parameters> configure an acquisition channel\n"
" <channel>,<termination>,<range>,<offset>,<saturation>\n"
" --timeout|-T <millisec> timeout for acquisition\n"
" --binary|-B <file> save binary to <file>\n"
" --multi-binary|-M <file> save two files per shot: "
"<file>.0000.ctrl etc\n"
" --dont-read|-N config-only\n"
" --loop|-l <num> number of loop before exiting\n"
" --show-data|-s <num> how many data to display: "
">0 from head, <0 from tail\n"
" --graph|-g <idx> plot the desired channel\n"
" --X11|-X Gnuplot will use X connection\n"
" --stats It prints some statistics\n"
" --version|-V print version information\n"
" --help|-h show this help\n"
"\n";
static const char *help_msg_opt_desc =
"Parameter descriptions (alphabetic order)\n"
" <ac-mode>\n"
" Offset auto-clear mode mode, a: automatic (use library configuration), m: manual (use user configuration)\n"
" <enable>\n"
" enabled: 1, disabled: 0\n"
" <idx>\n"
" resource index [0, N]\n"
" <n-shots>\n"
" number of consecutive acquisitions\n"
" <offset>*\n"
" offset to apply on input channel\n"
" <polarity>\n"
" positive edge/slope: 0, negative edge/slope: 1\n"
" <post-sample>\n"
" number of samples to be acquired after trigger (including the trigger)\n"
" <pre-sample>\n"
" number of samples to be acquired before trigger\n"
" <range>*\n"
" voltage range to use\n"
" <saturation>*\n"
" saturation value for an input channel\n"
" <termination>\n"
" enabled: 1, disabled: 0\n"
" <threshold>*\n"
" <adc-value> that trigger the acquisition\n"
" <undersample>\n"
" number of samples to skip during the acquisition\n"
"\n"
" * The meaning depends on the board in used\n"
"\n";
static void fald_help()
{
int i;
printf("\nfald-acq [OPTIONS] -D <device-name>@0x<device-id>\n\n");
printf(" <device-name>: name of the device type to open\n");
fputs(help_msg, stderr);
fprintf(stderr, " <device-name>: name of the device type to open\n");
for (i = 0; i < __ADC_SUPPORTED_BOARDS_LAST_INDEX; i++)
printf(" %s\n", adc_board_name[i]);
printf(" --device|-D <device-name>@<device-id>: unique device identifier (e.g.: \"fmc-adc@0x0400\")\n");
printf(" --acquisition|-a <parameters> configure the acquisition\n");
printf(" <pre-sample>,<post-sample>,<n-shots>,<undersample>\n");
printf(" --trg-ext <parameters> configure an external trigger\n");
printf(" <idx>,<enable>,<polarity>,<delay>\n");
printf(" --trg-thr <parameters> configure a threshold trigger\n");
printf(" <idx>,<enable>,<polarity>,<threshold>,<hysteresis>,<delay>\n");
printf(" --trg-tim <parameters> configure a timing trigger\n");
printf(" <NOT IMPLEMENTED YET>\n");
printf(" --channel| -c <parameters> configure an acquisition channel\n");
printf(" <channel>,<termination>,<range>,<offset>,<saturation>\n");
printf(" --timeout|-T <millisec> timeout for acquisition\n");
printf(" --binary|-B <file> save binary to <file>\n");
printf(" --multi-binary|-M <file> save two files per shot: "
"<file>.0000.ctrl etc\n");
printf(" --dont-read|-N config-only\n");
printf(" --loop|-l <num> number of loop before exiting\n");
printf(" --show-data|-s <num> how many data to display: "
">0 from head, <0 from tail\n");
printf(" --graph|-g <chnum> plot the desired channel\n");
printf(" --X11|-X Gnuplot will use X connection\n");
printf(" --version|-V print version information\n");
printf(" --help|-h show this help\n\n");
fprintf(stderr, " %s\n", adc_board_name[i]);
fputs(help_msg_opt, stderr);
fputs(help_msg_opt_desc, stderr);
}
......@@ -69,6 +118,8 @@ enum fald_acq_options {
FALD_ACQ_OPT_TRG_EXT = 1024,
FALD_ACQ_OPT_TRG_THR,
FALD_ACQ_OPT_TRG_TIM,
FALD_ACQ_OPT_TRG_SW,
FALD_ACQ_OPT_OFF_CLR,
};
static struct option options[] = {
......@@ -77,8 +128,13 @@ static struct option options[] = {
{"trg-ext", required_argument, 0, FALD_ACQ_OPT_TRG_EXT},
{"trg-thr", required_argument, 0, FALD_ACQ_OPT_TRG_THR},
{"trg-tim", required_argument, 0, FALD_ACQ_OPT_TRG_TIM},
{"trg-sw", required_argument, 0, FALD_ACQ_OPT_TRG_SW},
{"channel", required_argument, 0, 'c'},
{"timeout", required_argument, 0, 'T'},
{"off-clr", required_argument, 0, FALD_ACQ_OPT_OFF_CLR},
{"fixup", no_argument, &fixup, 1},
{"stats", no_argument, &statistics, 1},
/* new options, to help stress-test */
{"binary", required_argument, 0, 'B'},
......@@ -114,9 +170,7 @@ static int binmode;
static int timeout = -1;
static int loop = 1;
static char *basefile;
static unsigned int nsamples = 0;
static char *_argv[16];
static uint32_t nchan;
/* default is 1 V*/
static double bit_scale = 0.5/(1<<15);
......@@ -440,15 +494,15 @@ static int fald_acq_channel_configuration(struct adc_dev *adc, char *param)
*/
switch (range) {
case 100:
range = 0x23;
range = ADC_CONF_100M14B4CHA_CHN_RANGE_100mV;
bit_scale = 0.05/(1<<15);
break;
case 1:
range = 0x11;
range = ADC_CONF_100M14B4CHA_CHN_RANGE_1V;
bit_scale = 0.5/(1<<15);
break;
case 10:
range = 0x45;
range = ADC_CONF_100M14B4CHA_CHN_RANGE_10V;
bit_scale = 5.0/(1<<15);
break;
}
......@@ -464,6 +518,46 @@ static int fald_acq_channel_configuration(struct adc_dev *adc, char *param)
return adc_apply_config(adc, 0 , &cfg);
}
/**
* It configures a software trigger according to the parameters' string
* @param[in] adc The ADC device token
* @param[in] param a parameter string in the form
* "<delay>"
* @return 0 on success, otherwise -1 and errno is appropriately set
*
* The parameters are not mandatory. If not provided, the application will
* not try to set them (there is not a default value)
*/
static int fald_trg_software_configuration(struct adc_dev *adc, char *param)
{
uint32_t delay;
int ret;
ret = sscanf(param, "%"SCNu32, &delay);
if (ret <= 0) { /* channel is mandatory */
errno = EINVAL;
return -1;
}
if (!adc_has_trigger_fire(adc)) {
fprintf(stderr, "Software trigger not supported\n");
errno = ADC_ENOP;
return -1;
}
arg_trgsw = 1;
switch (ret) {
case 1:
arg_trgsw_delay = delay;
break;
default:
errno = EINVAL;
return -1;
}
return 0;
}
/**
* It configures a external trigger according to the parameters' string
......@@ -578,6 +672,59 @@ static int fald_trg_timer_configuration(struct adc_dev *adc, char *param)
return adc_apply_config(adc, 0 , &cfg);
}
/**
* It peforms the auto-clear offset according to the command line options
* @param[in] adc The ADC device token
* @param[in] argc number of arguments
* @param[in] argv arguments
* @return 0 on success, otherwise -1 and errno is appropriately set
*/
static int adc_acq_offset_auto_clear(struct adc_dev *adc,
int argc, char *argv[])
{
char offclr_mode;
unsigned long flags;
int c, opt_index, ret;
optind = 1; /* set to 1 to make getopt_long happy */
/* Parse options */
while ((c = getopt_long(argc, argv, GETOPT_STRING, options, &opt_index))
>= 0 ) {
switch (c) {
case FALD_ACQ_OPT_OFF_CLR:
ret = sscanf(optarg, "%c", &offclr_mode);
if (ret <= 0) {
errno = EINVAL;
return -1;
}
flags = 0;
switch (offclr_mode) {
case 'm':
flags |= ADC_OFFSET_AC_F_MANUAL;
break;
case 'a':
flags |= ADC_OFFSET_AC_F_RESTORE;
break;
case 'z':
flags |= ADC_OFFSET_AC_F_ZERO;
break;
default:
fprintf(stderr,
"%s: invalid offset auto-clear mode '%c' (valid modes: {a: automatic, m: manual})",
_argv[0], offclr_mode);
return -1;
}
ret = adc_offset_auto_clear(adc, flags);
if (ret < 0)
return ret;
break;
default:
break;
}
}
return 0;
}
/**
* It parses command line arguments and consequential it configures the device
......@@ -634,6 +781,14 @@ static int fald_acq_parse_args_and_configure(struct adc_dev *adc, int argc, char
return -1;
}
break;
case FALD_ACQ_OPT_TRG_SW:
err = fald_trg_software_configuration(adc, optarg);
if (err) {
fprintf(stderr, "%s: Cannot configure software trigger: %s\n",
_argv[0], adc_strerror(errno));
return -1;
}
break;
case 'T':
timeout = atoi(optarg);
break;
......@@ -656,6 +811,10 @@ static int fald_acq_parse_args_and_configure(struct adc_dev *adc, int argc, char
}
}
err = adc_acq_offset_auto_clear(adc, argc, argv);
if (err)
return -1;
fald_acq_print_config(adc);
return 0;
......@@ -669,35 +828,31 @@ static int fald_acq_parse_args_and_configure(struct adc_dev *adc, int argc, char
* @param[in] acq_cfg acquisition configuration associated to the buffer
*/
static void fald_acq_print_data(struct adc_buffer *buf,
struct adc_conf *acq_brd,
struct adc_conf *acq_cfg,
unsigned int n)
{
int j, ch;
int16_t *data; /* FMC-ADC-100M sample size is 14bit, 16bit for ZIO */
uint32_t pre;
uint32_t pre, nchan;
if (n == 0)
return;
if (nsamples != buf->nsamples) {
fprintf(stdout, "discrepancy between nsamples: %d and buf->nsamples: %d\n",
nsamples, buf->nsamples);
return;
}
adc_get_conf(acq_brd, ADC_CONF_BRD_N_CHAN, &nchan);
adc_get_conf(acq_cfg, ADC_CONF_ACQ_PRE_SAMP, &pre);
data = buf->data;
/* Print data */
for (j = 0; j < nsamples; j++) {
for (j = 0; j < buf->nsamples; j++) {
if ( (n > 0 && j < n) ||
(n < 0 && (nsamples - j) <= (-n)) ) {
(n < 0 && (buf->nsamples - j) <= (-n)) ) {
printf("%5i ", j - pre);
for (ch = 0; ch < nchan; ch++)
printf("%7i", *(data++));
for (ch = 0; ch < nchan; ch++) {
int32_t sample;
adc_buffer_get_sample(buf, ch, j, &sample);
printf("%7i", sample);
}
printf("\n");
} else {
data += nchan;
}
}
}
......@@ -712,7 +867,7 @@ static int fald_acq_write_single(struct adc_buffer *buf)
char fname[PATH_MAX];
int16_t *data; /* FMC-ADC-100M sample size is 14bit, 16bit for ZIO */
FILE *f;
int err = 0;
int err = 0, n;
sprintf(fname, "%s", basefile);
f = fopen(fname, "a");
......@@ -727,7 +882,8 @@ static int fald_acq_write_single(struct adc_buffer *buf)
err++;
#endif
data = buf->data;
if (fwrite(data, buf->samplesize, nsamples, f) != nsamples)
n = fwrite(data, buf->samplesize, buf->nsamples, f);
if (n != buf->nsamples)
err++;
if (err) {
......@@ -752,6 +908,8 @@ static int fald_acq_write_multiple(struct adc_buffer *buf,
char fname[PATH_MAX];
uint16_t *data;
FILE *f;
int n;
#ifdef FIXME
/* FIXME zio control is specific. The library should provide
at least the metadata size so that automatic tools can at least
......@@ -778,8 +936,8 @@ static int fald_acq_write_multiple(struct adc_buffer *buf,
return -1;
}
data = buf->data;
if (fwrite(data, buf->samplesize, nsamples, f)
!= nsamples) {
n = fwrite(data, buf->samplesize, buf->nsamples, f);
if (n != buf->nsamples) {
fprintf(stderr, "write(%s): short write\n", fname);
return -1;
}
......@@ -801,7 +959,7 @@ static void fald_acq_plot_data(struct adc_buffer *buf, unsigned int ch)
char cmd[256];
snprintf(fname, sizeof(fname), "/tmp/fmcadc.0x%04x.ch%d.dat", devid, ch);
if (write_file(fname, ch, data, (nsamples)/4) < 0) {
if (write_file(fname, ch, data, (buf->nsamples)/4) < 0) {
printf("Cannot plot data. Write data into file %s failed.\n", fname);
return;
}
......@@ -813,6 +971,31 @@ static void fald_acq_plot_data(struct adc_buffer *buf, unsigned int ch)
system(cmd);
}
/**
* It prints data using json format
*/
static void adc_acq_statistics(struct adc_buffer *buf, unsigned int max_chan)
{
int32_t avg;
int i, err;
fprintf(stdout, "{\"statistics\": [");
for (i = 0; i < max_chan; ++i) {
fprintf(stdout, "{ \"chan\": %d", i);
err = adc_buffer_math_avg(buf, i, &avg);
if (err) {
fprintf(stdout, ", \"average\": -1");
} else {
fprintf(stdout, ", \"average\": %"PRId32, avg);
}
if (i == max_chan - 1)
fprintf(stdout, "}");
else
fprintf(stdout, "}, ");
}
fprintf(stdout, "]}\n");
}
/**
* It process the buffer
......@@ -820,15 +1003,24 @@ static void fald_acq_plot_data(struct adc_buffer *buf, unsigned int ch)
* @param[in] iteration loop iteration number
*/
static void fald_acq_process_buffer(struct adc_buffer *buf,
struct adc_conf *cfg_brd,
struct adc_conf *cfg_acq,
unsigned int iteration)
{
struct adc_timestamp ts;
uint32_t nchan;
int w;
adc_tstamp_buffer(buf, &ts);
/* print/store data*/
switch(binmode) {
default:
fald_acq_print_data(buf, cfg_acq, show_ndata);
fprintf(stderr,
"time: {secs:%"PRIu64" ticks:%"PRIu64" bins:%"PRIu64"}\n",
ts.secs, ts.ticks, ts.bins);
fald_acq_print_data(buf, cfg_brd, cfg_acq, show_ndata);
break;
case 1:
fald_acq_write_single(buf);
......@@ -838,6 +1030,11 @@ static void fald_acq_process_buffer(struct adc_buffer *buf,
break;
}
adc_get_conf(cfg_brd, ADC_CONF_BRD_N_CHAN, &nchan);
if (statistics)
adc_acq_statistics(buf, nchan);
if (arg_plot)
for (w = 0; w < nchan; ++w)
fald_acq_plot_data(buf, w + 1);
......@@ -886,9 +1083,6 @@ static int fald_acq_parse_args_basic(int argc, char *argv[])
}
memset(devname, 0, len);
strncpy(devname, optarg, len - 1);
fprintf(stdout, "%s: %s -> type:%s, devid:%s\n",
_argv[0], optarg, devname, strdevid);
break;
}
case 'V':
......@@ -906,144 +1100,224 @@ static int fald_acq_parse_args_basic(int argc, char *argv[])
return 0;
}
int main(int argc, char *argv[])
/**
* Within this function we do a single acquisition
* @param[in] adc ADC device token
* @param[in] buf buffer to use
* @param[in] cfg_acq acquisition configuration
* @param[in] iteration acquisition number
* @return 0 on success
*/
static int adc_acq_acquisition(struct adc_dev *adc,
struct adc_buffer *buf,
struct adc_conf *cfg_brd,
struct adc_conf *cfg_acq,
unsigned int iteration)
{
struct adc_dev *adc;
struct adc_buffer *buf;
struct adc_timestamp ts;
struct timeval tv = {0, 0};
struct adc_conf cfg_brd, cfg_acq;
uint32_t pre, post, bits, nshots;
int i, k, err;
uint32_t nshots, pre, post;
int err, k;
fprintf(stderr, "Acquisition %i\n", iteration + 1);
err = adc_init();
if (err)
exit(EXIT_FAILURE);
/* Stop any pending acquisition */
err = adc_acq_stop(adc, 0);
if (err) {
fprintf(stderr,
"Cannot stop pending acquisition: (%d) %s\n",
errno, adc_strerror(errno));
goto err;
}
/* set local _argv[0] with pg name */
_argv[0] = argv[0];
err = fald_acq_parse_args_basic(argc, argv);
if (err)
exit(err);
err = adc_acq_start(adc, ADC_F_FLUSH, &tv);
if (err) {
fprintf(stderr, "Cannot start acquisition: (%d) %s\n",
errno, adc_strerror(errno));
goto err;
}
if (!devname) {
fprintf(stderr, "%s: device is a mandatory argument (%s@0x%x)\n",
argv[0], devname, devid);
fald_help();
goto err_devid_invalid;
adc_get_conf(cfg_acq, ADC_CONF_ACQ_PRE_SAMP, &pre);
adc_get_conf(cfg_acq, ADC_CONF_ACQ_POST_SAMP, &post);
adc_get_conf(cfg_acq, ADC_CONF_ACQ_N_SHOTS, &nshots);
for (k = 0; k < nshots; ++k) {
fprintf(stderr,
"shot: %i/%i, nsamples: %d\n",
k + 1, nshots, pre + post);
if (arg_trgsw) {
sleep(arg_trgsw_delay);
err = adc_trigger_fire(adc);
if (err)
goto err;
}
tv.tv_sec = 60;
err = adc_fill_buffer(adc, buf,
fixup ? ADC_F_FIXUP: 0,
&tv);
if (err) {
fprintf(stderr,
"Failed to retrieve for data: (%d) %s\n",
errno, adc_strerror(errno));
goto err;
}
fald_acq_process_buffer(buf, cfg_brd, cfg_acq, iteration);
}
return 0;
/* Open the ADC */
adc = adc_open(devname, devid, 0, 0, ADC_F_FLUSH);
if (!adc) {
fprintf(stderr, "%s: cannot open device: %s\n",
argv[0], adc_strerror(errno));
goto err_open;
err:
return err;
}
/**
* Within this function we repeat the same acquisition a number of times
* @param[in] adc ADC device token
* @param[in] buf buffer to use
* @param[in] cfg_acq acquisition configuration
* @param[in] n number of acquisition to do
* @return 0 on success
*/
static int adc_acq_acquisition_n(struct adc_dev *adc,
struct adc_conf *cfg_brd,
struct adc_conf *cfg_acq,
unsigned int n)
{
struct adc_buffer *buf;
uint32_t pre, post;
int i, err = 0;
adc_get_conf(cfg_acq, ADC_CONF_ACQ_PRE_SAMP, &pre);
adc_get_conf(cfg_acq, ADC_CONF_ACQ_POST_SAMP, &post);
buf = adc_request_buffer(adc, pre + post, NULL /* alloc */, 0);
if (!buf) {
fprintf(stderr, "Cannot allocate buffer (%s)\n",
adc_strerror(errno));
return -1;
}
for (i = 0; i < n && arg_no_read == 0; ++i) {
err = adc_acq_acquisition(adc, buf, cfg_brd, cfg_acq, i);
if (err)
break;
}
adc_release_buffer(adc, buf, NULL);
return err;
}
/**
* It configures an acquisition using user's parameters
* @param[in] adc ADC device token
* @param[in] argc number of parameters
* @param[in] argv user's parameters
* @param[out] cfg_acq acquisition configuration
* @return 0 on success
*/
static int adc_acq_configure(struct adc_dev *adc,
int argc, char *argv[],
struct adc_conf *cfg_brd,
struct adc_conf *cfg_acq)
{
int err = 0;
/* get the new given trigger and acq config
Only the ones provided will override the current ones */
err = fald_acq_parse_args_and_configure(adc, argc, argv);
if (err)
goto err_parse;
return err;
/*
* We already know the configuration, but here we will retrieve it
* from the library anyway as an example on how to get the
* configuration
* Within this program we already know the configuration (we
* applied it before), but here we will retrieve it from the
* library anyway because the library might have changed something
*/
memset(&cfg_brd, 0, sizeof(struct adc_conf));
cfg_brd.type = ADC_CONF_TYPE_BRD;
adc_set_conf_mask_all(&cfg_brd, adc);
err = adc_retrieve_config(adc, &cfg_brd);
memset(cfg_brd, 0, sizeof(struct adc_conf));
cfg_brd->type = ADC_CONF_TYPE_BRD;
adc_set_conf_mask_all(cfg_brd, adc);
err = adc_retrieve_config(adc, cfg_brd);
if (err) {
fprintf(stderr,
"Cannot retrive the board configuration: (%d) %s\n",
"Cannot retrive board configuration: (%d) %s\n",
errno, adc_strerror(errno));
goto err_conf_get;
return err;
}
memset(&cfg_acq, 0, sizeof(struct adc_conf));
cfg_acq.type = ADC_CONF_TYPE_ACQ;
adc_set_conf_mask_all(&cfg_acq, adc);
err = adc_retrieve_config(adc, &cfg_acq);
memset(cfg_acq, 0, sizeof(struct adc_conf));
cfg_acq->type = ADC_CONF_TYPE_ACQ;
adc_set_conf_mask_all(cfg_acq, adc);
err = adc_retrieve_config(adc, cfg_acq);
if (err) {
fprintf(stderr,
"Cannot retrive the acquisition configuration: (%d) %s\n",
"Cannot retrive acquisition configuration: (%d) %s\n",
errno, adc_strerror(errno));
goto err_conf_get;
return err;
}
adc_get_conf(&cfg_brd, ADC_CONF_BRD_N_CHAN, &nchan);
adc_get_conf(&cfg_acq, ADC_CONF_ACQ_PRE_SAMP, &pre);
adc_get_conf(&cfg_acq, ADC_CONF_ACQ_POST_SAMP, &post);
adc_get_conf(&cfg_acq, ADC_CONF_ACQ_N_BITS, &bits);
adc_get_conf(&cfg_acq, ADC_CONF_ACQ_N_SHOTS, &nshots);
nsamples = pre + post;
buf = adc_request_buffer(adc, nsamples, NULL /* alloc */, 0);
if (!buf) {
fprintf(stderr, "Cannot allocate buffer (%s)\n",
adc_strerror(errno));
exit(1);
}
return err;
}
for (i = 0; i < loop && arg_no_read == 0; ++i) {
fprintf(stderr, "Acquisition %i\n", i + 1);
/* Stop any pending acquisition */
err = adc_acq_stop(adc, 0);
if (err) {
fprintf(stderr, "Cannot stop pending acquisition: (%d) %s\n",
errno, adc_strerror(errno));
continue;
}
/**
* It cleans up our internal mess on exit
*/
static void adc_acq_cleanup(void)
{
free(devname);
}
err = adc_acq_start(adc, ADC_F_FLUSH, &tv);
if (err) {
fprintf(stderr, "Cannot start acquisition: (%d) %s\n",
errno, adc_strerror(errno));
continue;
}
for (k = 0; k < nshots; ++k) {
fprintf(stderr,
"iteration: %d, shot: %i/%i, nsamples: %d\n",
i, k + 1, nshots, nsamples);
int main(int argc, char *argv[])
{
struct adc_dev *adc;
struct adc_conf cfg_acq, cfg_brd;
int err;
err = adc_acq_poll(adc, 0 , NULL);
if (err) {
fprintf(stderr, "Failed to wait for data: (%d) %s\n",
errno, adc_strerror(errno));
break;
}
atexit(adc_acq_cleanup);
err = adc_fill_buffer(adc, buf, 0, NULL);
if (err) {
fprintf(stderr, "Failed to retrieve for data: (%d) %s\n",
errno, adc_strerror(errno));
break;
}
adc_tstamp_buffer(buf, &ts);
fprintf(stderr,
"time: {secs:%"PRIu64" ticks:%"PRIu64" bins:%"PRIu64"}\n",
ts.secs, ts.ticks, ts.bins);
/* set local _argv[0] with pg name */
_argv[0] = argv[0];
err = fald_acq_parse_args_basic(argc, argv);
if (err)
exit(err);
fald_acq_process_buffer(buf, &cfg_acq, i);
}
if (!devname) {
fprintf(stderr,
"%s: device is a mandatory argument (%s@0x%x)\n",
argv[0], devname, devid);
fald_help();
goto err_devid_invalid;
}
adc_release_buffer(adc, buf, NULL);
err = adc_init();
if (err)
exit(EXIT_FAILURE);
/* Open the ADC */
adc = adc_open(devname, devid, 0, 0, ADC_F_FLUSH);
if (!adc) {
fprintf(stderr, "%s: cannot open device: %s\n",
argv[0], adc_strerror(errno));
goto err_open;
}
err = adc_acq_configure(adc, argc, argv, &cfg_brd, &cfg_acq);
if (err)
goto err_cfg;
err = adc_acq_acquisition_n(adc, &cfg_brd, &cfg_acq, loop);
if (err)
goto err_acq;
adc_close(adc);
adc_exit();
free(devname);
exit(EXIT_SUCCESS);
err_conf_get:
err_parse:
err_acq:
err_cfg:
adc_close(adc);
err_open:
err_devid_invalid:
adc_exit();
free(devname);
exit(EXIT_FAILURE);
}
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