Commit afb0d6aa authored by Federico Vaga's avatar Federico Vaga

lib: implement offset auto-clear

It adds a new board operation for auto-clear any offset. If a board
does not support this feature in its hardware or driver, then it can
use the software implementation. The software implementation does an
acquisition on disconnected channel and compute the signal average;
we assume that this average is a constant noise, then we apply this
value to the DAC to clear it on the ADC input.
Signed-off-by: Federico Vaga's avatarFederico Vaga <federico.vaga@cern.ch>
parent 2a2c7344
......@@ -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
......
......@@ -184,6 +184,12 @@ 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 int chan,
unsigned int nsamples,
unsigned long flags,
int32_t *offset);
/*adc-genericfake*/
......
......@@ -30,6 +30,8 @@ extern "C" {
#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
......@@ -431,6 +433,16 @@ extern int adc_buffer_get_sample(struct adc_buffer *buf,
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 */
extern const char * const libadc_version_s;
......
......@@ -36,6 +36,8 @@
#define ADC_CONF_GET 0
#define ADC_CONF_SET 1
#define __ADC_CONF_CHN_OFFSET_ZERO 200
static typeof(adc_get_param) *adc_param[] = {
[ADC_CONF_GET] = adc_get_param,
[ADC_CONF_SET] = adc_set_param,
......@@ -335,6 +337,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;
......@@ -734,12 +739,292 @@ static int adc_100m14b4cha_buffer_get_sample(struct adc_buffer *buf,
return 0;
}
struct tmp_cfg_store {
uint32_t trg_source;
uint32_t pre;
uint32_t post;
uint32_t nshots;
uint32_t undersample;
uint32_t chan;
uint32_t range;
uint32_t termination;
uint32_t saturation;
uint32_t offset;
};
static int __cfg_offac_save(struct adc_dev *dev,
unsigned int chan,
struct tmp_cfg_store *cfg,
unsigned long flags)
{
int err;
if ((flags & ADC_OFFSET_AC_F_MANUAL) ||
!(flags & ADC_OFFSET_AC_F_RESTORE))
return 0;
cfg->chan = chan;
err = adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_PRE_SAMP,
&cfg->pre, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_POST_SAMP,
&cfg->post, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_N_SHOTS,
&cfg->nshots, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_UNDERSAMPLE,
&cfg->undersample, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_RANGE,
&cfg->range, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_TERMINATION,
&cfg->termination, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_SATURATION,
&cfg->saturation, ADC_CONF_GET);
if (err)
goto err;
err = adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_OFFSET,
&cfg->offset, ADC_CONF_GET);
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_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_PRE_SAMP,
&cfg->pre, ADC_CONF_SET);
err |= adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_POST_SAMP,
&cfg->post, ADC_CONF_SET);
err |= adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_N_SHOTS,
&cfg->nshots, ADC_CONF_SET);
err |= adc_100m14b4cha_config_acq(dev, ADC_CONF_ACQ_UNDERSAMPLE,
&cfg->undersample, ADC_CONF_SET);
err |= adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_RANGE,
&cfg->range, ADC_CONF_SET);
err |= adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_TERMINATION,
&cfg->termination, ADC_CONF_SET);
err |= adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_SATURATION,
&cfg->saturation, ADC_CONF_SET);
err |= adc_100m14b4cha_config_chn(dev, cfg->chan,
ADC_CONF_CHN_OFFSET,
&cfg->offset, ADC_CONF_SET);
err |= adc_set_param(dev, "cset0/trigger/source", NULL,
(int *)&cfg->trg_source);
if (err)
errno = ADC_OFF_AC_RESTORE_R;
return err;
}
#define CFG_OFFAC_NSAMPLES 100000
static struct adc_conf cfg_off_acq = {
.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,
},
};
static struct adc_conf cfg_off_cus = {
.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,
},
};
/**
* 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 int chan,
unsigned long flags)
{
int err, trgsrc = FA100M14B4C_TRG_SRC_SW;
uint32_t range = 0, offset = 0;
if (flags & ADC_OFFSET_AC_F_MANUAL)
return 0;
err = adc_set_param(dev, "cset0/trigger/source", NULL,
&trgsrc);
if (err)
return -1;
err = adc_apply_config(dev, 0 , &cfg_off_acq);
if (err)
return -1;
err = adc_apply_config(dev, 0 , &cfg_off_cus);
if (err)
return -1;
err = adc_100m14b4cha_config_chn(dev, chan,
ADC_CONF_CHN_RANGE,
&range, ADC_CONF_SET);
if (err)
return -1;
err = adc_100m14b4cha_config_chn(dev, chan,
ADC_CONF_CHN_OFFSET,
&offset, ADC_CONF_SET);
if (err)
return -1;
err = adc_100m14b4cha_config_chn(dev, chan,
__ADC_CONF_CHN_OFFSET_ZERO,
&offset, ADC_CONF_SET);
if (err)
return -1;
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 0x45:
range_uV = 10000000;
break;
case 0x23:
range_uV = 100000;
break;
case 0x11:
case 0x00:
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 adc_100m14b4cha_offset_auto_clear(struct adc_dev *dev,
unsigned int chan,
unsigned long flags)
{
errno = EPERM;
return -1;
struct tmp_cfg_store tmpcfg;
int32_t offset;
int err, err_rst;
uint32_t range;
flags |= ADC_OFFSET_AC_F_SOFTWARE;
err = __cfg_offac_save(dev, chan, &tmpcfg, flags);
if (err)
return err;
err = __cfg_offac_apply(dev, chan, flags);
if (err)
goto err;
err = adc_offset_auto_clear_sw_avg(dev, chan, CFG_OFFAC_NSAMPLES,
flags,
&offset);
if (err)
goto err;
err = adc_100m14b4cha_config_chn(dev, chan, ADC_CONF_CHN_RANGE,
&range, ADC_CONF_GET);
if (err)
goto err;
err_rst = __cfg_offac_restore(dev, &tmpcfg, flags);
if (err_rst) {
errno = ADC_OFF_AC_RESTORE_R;
return err_rst;
}
/*
* 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
*/
offset = __convert_hw_to_uv(offset, range);
return adc_100m14b4cha_config_chn(dev, chan,
__ADC_CONF_CHN_OFFSET_ZERO,
(uint32_t *)&offset, ADC_CONF_SET);
err:
err_rst = __cfg_offac_restore(dev, &tmpcfg, flags);
if (err_rst) {
errno = ADC_OFF_AC_RESTORE_R;
return err_rst;
}
return err;
}
#define ADC_100M_4CH_14BIT_ACQ_MASK (1LL << ADC_CONF_ACQ_N_SHOTS) | \
......
/*
* 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"
......@@ -35,6 +36,8 @@ static struct adc_errors {
{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, }
};
......@@ -90,3 +93,72 @@ uint64_t adc_get_capabilities(struct adc_dev *dev,
return b->board->capabilities[type];
}
/**
* It computes what is the necessary offset to apply on a given channel
* in order to clear a constant offset on a channel
* approach.
* @param[in] dev ADC device token
* @param[in] chan channel number
* @param[in] flags options @see adc_offset_auto_clear_flags
* @param[out] offset compensation offset
* @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.
*/
int adc_offset_auto_clear_sw_avg(struct adc_dev *dev,
unsigned int chan,
unsigned int nsamples,
unsigned long flags,
int32_t *offset)
{
struct adc_buffer *buf;
struct timeval tv = {0, 0};
int err, err_stop;
if (!(flags & ADC_OFFSET_AC_F_SOFTWARE)) {
errno = EINVAL;
return -1;
}
if (!adc_has_trigger_fire(dev)) {
errno = ADC_ENOP_SWTRG;
return -1;
}
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;
err = adc_buffer_math_avg(buf, chan, offset);
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;
}
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