linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Olivier MOYSAN <olivier.moysan@foss.st.com>
Cc: Alexandre Torgue <alexandre.torgue@foss.st.com>,
	Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>,
	Fabrice Gasnier <fabrice.gasnier@st.com>,
	Lars-Peter Clausen <lars@metafoo.de>,
	Maxime Coquelin <mcoquelin.stm32@gmail.com>,
	Rob Herring <robh+dt@kernel.org>, <devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-iio@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<linux-stm32@st-md-mailman.stormreply.com>
Subject: Re: [PATCH 6/7] iio: adc: stm32-adc: add vrefint calibration support
Date: Sun, 26 Sep 2021 13:09:36 +0100	[thread overview]
Message-ID: <20210926130936.6a8a3b5a@jic23-huawei> (raw)
In-Reply-To: <3284242f-b85f-3f96-c165-bf12325136c3@foss.st.com>

On Wed, 22 Sep 2021 09:53:58 +0200
Olivier MOYSAN <olivier.moysan@foss.st.com> wrote:

> Hi Jonathan,
> 
> On 9/18/21 8:42 PM, Jonathan Cameron wrote:
> > On Wed, 15 Sep 2021 12:02:45 +0200
> > Olivier MOYSAN <olivier.moysan@foss.st.com> wrote:
> >   
> >> Hi Jonathan,
> >>
> >> On 9/11/21 6:28 PM, Jonathan Cameron wrote:  
> >>> On Wed, 8 Sep 2021 17:54:51 +0200
> >>> Olivier Moysan <olivier.moysan@foss.st.com> wrote:
> >>>      
> >>>> Add support of vrefint calibration.
> >>>> If a channel is labeled as vrefint, get vrefint calibration
> >>>> from non volatile memory for this channel.
> >>>> A conversion on vrefint channel allows to update scale
> >>>> factor according to vrefint deviation, compared to vrefint
> >>>> calibration value.  
> >>>
> >>> As I mention inline, whilst technically the ABI doesn't demand it
> >>> the expectation of much of userspace software is that _scale is
> >>> pseudo constant - that is it doesn't tend to change very often and when
> >>> it does it's normally because someone deliberately made it change.
> >>> As such most software reads it just once.
> >>>
> >>> Normally we work around this by applying the maths in kernel and
> >>> not exposing the scale at all. Is this something that could be done here?
> >>>
> >>> Jonathan
> >>>      
> >>>>
> >>>> Signed-off-by: Olivier Moysan <olivier.moysan@foss.st.com>
> >>>> ---
> >>>>    drivers/iio/adc/stm32-adc.c | 88 ++++++++++++++++++++++++++++++++++---
> >>>>    1 file changed, 82 insertions(+), 6 deletions(-)
> >>>>
> >>>> diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
> >>>> index ef3d2af98025..9e52a7de9b16 100644
> >>>> --- a/drivers/iio/adc/stm32-adc.c
> >>>> +++ b/drivers/iio/adc/stm32-adc.c
> >>>> @@ -21,6 +21,7 @@
> >>>>    #include <linux/io.h>
> >>>>    #include <linux/iopoll.h>
> >>>>    #include <linux/module.h>
> >>>> +#include <linux/nvmem-consumer.h>
> >>>>    #include <linux/platform_device.h>
> >>>>    #include <linux/pm_runtime.h>
> >>>>    #include <linux/of.h>
> >>>> @@ -42,6 +43,7 @@
> >>>>    #define STM32_ADC_TIMEOUT	(msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
> >>>>    #define STM32_ADC_HW_STOP_DELAY_MS	100
> >>>>    #define STM32_ADC_CHAN_NONE		-1
> >>>> +#define STM32_ADC_VREFINT_VOLTAGE	3300
> >>>>    
> >>>>    #define STM32_DMA_BUFFER_SIZE		PAGE_SIZE
> >>>>    
> >>>> @@ -79,6 +81,7 @@ enum stm32_adc_extsel {
> >>>>    };
> >>>>    
> >>>>    enum stm32_adc_int_ch {
> >>>> +	STM32_ADC_INT_CH_NONE = -1,
> >>>>    	STM32_ADC_INT_CH_VDDCORE,
> >>>>    	STM32_ADC_INT_CH_VREFINT,
> >>>>    	STM32_ADC_INT_CH_VBAT,
> >>>> @@ -137,6 +140,16 @@ struct stm32_adc_regs {
> >>>>    	int shift;
> >>>>    };
> >>>>    
> >>>> +/**
> >>>> + * struct stm32_adc_vrefint - stm32 ADC internal reference voltage data
> >>>> + * @vrefint_cal:	vrefint calibration value from nvmem
> >>>> + * @vrefint_data:	vrefint actual value
> >>>> + */
> >>>> +struct stm32_adc_vrefint {
> >>>> +	u32 vrefint_cal;
> >>>> +	u32 vrefint_data;
> >>>> +};
> >>>> +
> >>>>    /**
> >>>>     * struct stm32_adc_regspec - stm32 registers definition
> >>>>     * @dr:			data register offset
> >>>> @@ -186,6 +199,7 @@ struct stm32_adc;
> >>>>     * @unprepare:		optional unprepare routine (disable, power-down)
> >>>>     * @irq_clear:		routine to clear irqs
> >>>>     * @smp_cycles:		programmable sampling time (ADC clock cycles)
> >>>> + * @ts_vrefint_ns:	vrefint minimum sampling time in ns
> >>>>     */
> >>>>    struct stm32_adc_cfg {
> >>>>    	const struct stm32_adc_regspec	*regs;
> >>>> @@ -199,6 +213,7 @@ struct stm32_adc_cfg {
> >>>>    	void (*unprepare)(struct iio_dev *);
> >>>>    	void (*irq_clear)(struct iio_dev *indio_dev, u32 msk);
> >>>>    	const unsigned int *smp_cycles;
> >>>> +	const unsigned int ts_vrefint_ns;
> >>>>    };
> >>>>    
> >>>>    /**
> >>>> @@ -223,6 +238,7 @@ struct stm32_adc_cfg {
> >>>>     * @pcsel:		bitmask to preselect channels on some devices
> >>>>     * @smpr_val:		sampling time settings (e.g. smpr1 / smpr2)
> >>>>     * @cal:		optional calibration data on some devices
> >>>> + * @vrefint:		internal reference voltage data
> >>>>     * @chan_name:		channel name array
> >>>>     * @num_diff:		number of differential channels
> >>>>     * @int_ch:		internal channel indexes array
> >>>> @@ -248,6 +264,7 @@ struct stm32_adc {
> >>>>    	u32			pcsel;
> >>>>    	u32			smpr_val[2];
> >>>>    	struct stm32_adc_calib	cal;
> >>>> +	struct stm32_adc_vrefint vrefint;
> >>>>    	char			chan_name[STM32_ADC_CH_MAX][STM32_ADC_CH_SZ];
> >>>>    	u32			num_diff;
> >>>>    	int			int_ch[STM32_ADC_INT_CH_NB];
> >>>> @@ -1331,15 +1348,35 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev,
> >>>>    			ret = stm32_adc_single_conv(indio_dev, chan, val);
> >>>>    		else
> >>>>    			ret = -EINVAL;
> >>>> +
> >>>> +		/* If channel mask corresponds to vrefint, store data */
> >>>> +		if (adc->int_ch[STM32_ADC_INT_CH_VREFINT] == chan->channel)
> >>>> +			adc->vrefint.vrefint_data = *val;
> >>>> +
> >>>>    		iio_device_release_direct_mode(indio_dev);
> >>>>    		return ret;
> >>>>    
> >>>>    	case IIO_CHAN_INFO_SCALE:
> >>>>    		if (chan->differential) {
> >>>> -			*val = adc->common->vref_mv * 2;
> >>>> +			if (adc->vrefint.vrefint_data &&
> >>>> +			    adc->vrefint.vrefint_cal) {
> >>>> +				*val = STM32_ADC_VREFINT_VOLTAGE * 2 *
> >>>> +				       adc->vrefint.vrefint_cal /
> >>>> +				       adc->vrefint.vrefint_data;  
> >>>
> >>> Ah.. Dynamic scale.  This is always awkward when it occurs.
> >>> Given most / possibly all userspace software assumes a pseudo static scale
> >>> (not data dependent) we normally hide this by doing the maths internal to the
> >>> driver - sometimes meaning we need to present the particular channel as processed
> >>> not raw.
> >>>
> >>> Is the expectation here that vrefint_data is actually very nearly constant? If
> >>> so then what you have here may be fine as anyone not aware the scale might change
> >>> will get very nearly the right value anyway.
> >>>      
> >>
> >> The need here is to compare the measured value of vrefint with the
> >> calibrated value saved in non volatile memory. The ratio between these
> >> two values can be used as a correction factor for the acquisitions on
> >> all other channels.
> >>
> >> The vrefint data is expected to be close to the saved vrefint
> >> calibration value, and it should not vary strongly over time.
> >> So, yes, we can indeed consider the scale as a pseudo constant. If the
> >> scale is not updated, the deviation with actual value should remain
> >> limited, as well.  
> > 
> > Ok, so in that case we could probably get away with having it as you have
> > here, though for maximum precision we'd need userspace to occasionally check
> > the scale.
> >   
> >>
> >> You suggest above to hide scale tuning through processed channels.
> >> If I follow this logic, when vrefint channel is available, all channels
> >> should be defined as processed channels (excepted vrefint channel)
> >> In this case no scale is exposed for these channels, and the vrefint
> >> calibration ratio can be used to provide converted data directly.
> >> Do you prefer this implementation ?  
> >   
> >>
> >> In this case I wonder how buffered data have to be managed. These data
> >> are still provided as raw data, but the scale factor is not more
> >> available to convert them. I guess that these data have to be converted
> >> internally also, either in dma callback or irq handler.
> >> Is this correct ?  
> > 
> > This is one of the holes in what IIO does today.  Without meta data in the
> > buffer (which is hard to define in a clean fashion) it is hard to have
> > a compact representation of the data in the presence of dynamic scaling.
> > The vast majority of devices don't inherently support such scaling so
> > this is only occasionally a problem.
> > 
> > To support this at the moment you would indeed need to scale the data
> > before pushing it to the buffer which is obviously really ugly.
> > 
> > My gut feeling here is there are three possible approaches.
> > 
> > 1) Ignore the dynamic nature of the calibration and pretend it's static.
> > 2) Add an explicit 'calibration' sysfs attribute.
> >     This is a fairly common model for other sensor types which don't do
> >     dynamic calibration but instead require to you to start some special
> >     calibration sequence.
> >     As the calibration is not updated, except on explicit userspace action
> >     we can assume that the scale is static unless userspace is aware of
> >     the dynamic aspect.
> > 3) Add a userspace control to turn on dynamic calibration.  That makes it
> >     opt in.  Everything will work reasonably well without it turned on
> >     as we'll hopefully have a static estimate of scale which is good enough.
> >     If aware software is using the device, it can enable this mode and
> >     sample the scale as often as it wants to.
> > 
> > I slightly favour option 3.  What do you think?  If we ever figure out
> > the meta data question for buffered case then we can make that work on top
> > of this.
> > 
> > Jonathan  
> 
> This discussion made me revisit the calibration aspects in the ADC driver.
> 
> We have three types of calibration in ADC:
> 
> - Linear calibration: this calibration is not voltage or temperature 
> dependent. So, it can be done once at boot time, as this is done currently.
> 
> - offset calibration: this calibration has a voltage and temperature 
> dependency. This calibration is currently done once at boot. But it 
> would be relevant to allow application to request a new offset 
> calibration, when supply or temperature change over time.
> Here the 'calibration' sysfs attribute you suggested in option 2, would 
> be convenient I think. I plan to submit this improvement in a separate 
> patch.
> 
> - vref compensation: the vrefint channel offers a way to evaluate vref 
> deviation. Here I need to change a bit the logic. I think that putting 
> intelligence in the driver is not the best way at the end. This hides 
> voltage deviation information, where it could be useful to check if an 
> offset calibration is needed. Moreover we get a lack of consistency 
> between raw and buffered data.
> It looks that processed type is the good way to expose vrefint channel. 
> This allows to provide the actual vref voltage. And we can let the 
> application decide how to manage this information. (trigger offset 
> calibration / compensate raw data)
> I'm going to send a v2 serie with this change for vrefint support
> (plus the corrections for other comments)

Sounds like a good compromise solution to me.  It's not great that we
end up with a 'somewhat' device specific solution, but as I've not previously
seen this particular form of calibration I am not that bothered by it
being device specific.

As ever, these stm32 parts continue to cut a new trail!

Thanks,

Jonathan

> 
> Regards
> Olivier
> 
> >>
> >> Regards
> >> Olivier
> >>  
> >>>> +			} else {
> >>>> +				*val = adc->common->vref_mv * 2;
> >>>> +			}
> >>>>    			*val2 = chan->scan_type.realbits;
> >>>>    		} else {
> >>>> -			*val = adc->common->vref_mv;
> >>>> +			/* Use vrefint data if available */
> >>>> +			if (adc->vrefint.vrefint_data &&
> >>>> +			    adc->vrefint.vrefint_cal) {
> >>>> +				*val = STM32_ADC_VREFINT_VOLTAGE *
> >>>> +				       adc->vrefint.vrefint_cal /
> >>>> +				       adc->vrefint.vrefint_data;
> >>>> +			} else {
> >>>> +				*val = adc->common->vref_mv;
> >>>> +			}
> >>>>    			*val2 = chan->scan_type.realbits;
> >>>>    		}
> >>>>    		return IIO_VAL_FRACTIONAL_LOG2;
> >>>> @@ -1907,6 +1944,35 @@ static int stm32_adc_legacy_chan_init(struct iio_dev *indio_dev,
> >>>>    	return scan_index;
> >>>>    }
> >>>>    
> >>>> +static int stm32_adc_get_int_ch(struct iio_dev *indio_dev, const char *ch_name,
> >>>> +				int chan)  
> >>>
> >>> Naming would suggest to me that it would return a channel rather than setting it
> >>> inside adc->int_ch[i]  Perhaps something like st32_adc_populate_int_ch() ?
> >>>
> >>>      
> >>>> +{
> >>>> +	struct stm32_adc *adc = iio_priv(indio_dev);
> >>>> +	u16 vrefint;
> >>>> +	int i, ret;
> >>>> +
> >>>> +	for (i = 0; i < STM32_ADC_INT_CH_NB; i++) {
> >>>> +		if (!strncmp(stm32_adc_ic[i].name, ch_name, STM32_ADC_CH_SZ)) {
> >>>> +			adc->int_ch[i] = chan;
> >>>> +			/* If channel is vrefint get calibration data. */
> >>>> +			if (stm32_adc_ic[i].idx == STM32_ADC_INT_CH_VREFINT) {  
> >>>
> >>> I would reduce indentation by reversing the logic.
> >>>
> >>> 			if (stm32_adc_ic[i].idx != STM32_ADC_INT_CH_VREFINT)
> >>> 				continue;
> >>>
> >>> 			ret =  
> >>>> +				ret = nvmem_cell_read_u16(&indio_dev->dev, "vrefint", &vrefint);
> >>>> +				if (ret && ret != -ENOENT && ret != -EOPNOTSUPP) {
> >>>> +					dev_err(&indio_dev->dev, "nvmem access error %d\n", ret);
> >>>> +					return ret;
> >>>> +				}
> >>>> +				if (ret == -ENOENT)
> >>>> +					dev_dbg(&indio_dev->dev,
> >>>> +						"vrefint calibration not found\n");
> >>>> +				else
> >>>> +					adc->vrefint.vrefint_cal = vrefint;
> >>>> +			}
> >>>> +		}
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>>    static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev,
> >>>>    				       struct stm32_adc *adc,
> >>>>    				       struct iio_chan_spec *channels)
> >>>> @@ -1938,10 +2004,9 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev,
> >>>>    				return -EINVAL;
> >>>>    			}
> >>>>    			strncpy(adc->chan_name[val], name, STM32_ADC_CH_SZ);
> >>>> -			for (i = 0; i < STM32_ADC_INT_CH_NB; i++) {
> >>>> -				if (!strncmp(stm32_adc_ic[i].name, name, STM32_ADC_CH_SZ))
> >>>> -					adc->int_ch[i] = val;
> >>>> -			}
> >>>> +			ret = stm32_adc_get_int_ch(indio_dev, name, val);
> >>>> +			if (ret)
> >>>> +				goto err;
> >>>>    		} else if (ret != -EINVAL) {
> >>>>    			dev_err(&indio_dev->dev, "Invalid label %d\n", ret);
> >>>>    			goto err;
> >>>> @@ -2044,6 +2109,16 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev, bool timestamping)
> >>>>    		 */
> >>>>    		of_property_read_u32_index(node, "st,min-sample-time-nsecs",
> >>>>    					   i, &smp);
> >>>> +
> >>>> +		/*
> >>>> +		 * For vrefint channel, ensure that the sampling time cannot
> >>>> +		 * be lower than the one specified in the datasheet
> >>>> +		 */
> >>>> +		if (channels[i].channel == adc->int_ch[STM32_ADC_INT_CH_VREFINT] &&
> >>>> +		    smp < adc->cfg->ts_vrefint_ns) {
> >>>> +			smp = adc->cfg->ts_vrefint_ns;
> >>>> +		}  
> >>>
> >>> 		if (channels[i].channel == adc->int_ch[STM32_ADC_INT_CH_VREFINT])
> >>> 			smp = max(smp, adc->cfg->ts_vrefint_ns);
> >>>      
> >>>> +
> >>>>    		/* Prepare sampling time settings */
> >>>>    		stm32_adc_smpr_init(adc, channels[i].channel, smp);
> >>>>    	}
> >>>> @@ -2350,6 +2425,7 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
> >>>>    	.unprepare = stm32h7_adc_unprepare,
> >>>>    	.smp_cycles = stm32h7_adc_smp_cycles,
> >>>>    	.irq_clear = stm32h7_adc_irq_clear,
> >>>> +	.ts_vrefint_ns = 4300,
> >>>>    };
> >>>>    
> >>>>    static const struct of_device_id stm32_adc_of_match[] = {  
> >>>      
> >   


  reply	other threads:[~2021-09-26 12:05 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-08 15:54 [PATCH 0/7] add internal channels support Olivier Moysan
2021-09-08 15:54 ` [PATCH 1/7] dt-bindings: iio: adc: add generic channel binding Olivier Moysan
2021-09-11 15:51   ` Jonathan Cameron
2021-09-21 17:08   ` Rob Herring
2021-09-08 15:54 ` [PATCH 2/7] dt-bindings: iio: adc: add nvmem support for vrefint internal channel Olivier Moysan
2021-09-21 17:10   ` Rob Herring
2021-09-08 15:54 ` [PATCH 3/7] iio: adc stm32-adc: split channel init into several routines Olivier Moysan
2021-09-08 15:54 ` [PATCH 4/7] iio: adc: stm32-adc: add support of generic channels binding Olivier Moysan
2021-09-11 16:08   ` Jonathan Cameron
2021-09-08 15:54 ` [PATCH 5/7] iio: adc: stm32-adc: add support of internal channels Olivier Moysan
2021-09-11 16:17   ` Jonathan Cameron
2021-09-08 15:54 ` [PATCH 6/7] iio: adc: stm32-adc: add vrefint calibration support Olivier Moysan
2021-09-11 16:28   ` Jonathan Cameron
2021-09-15 10:02     ` Olivier MOYSAN
2021-09-18 18:42       ` Jonathan Cameron
2021-09-22  7:53         ` Olivier MOYSAN
2021-09-26 12:09           ` Jonathan Cameron [this message]
2021-09-08 15:54 ` [PATCH 7/7] iio: adc: stm32-adc: use generic binding for sample-time Olivier Moysan
2021-09-11 15:44 ` [PATCH 0/7] add internal channels support Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210926130936.6a8a3b5a@jic23-huawei \
    --to=jic23@kernel.org \
    --cc=alexandre.torgue@foss.st.com \
    --cc=arnaud.pouliquen@foss.st.com \
    --cc=devicetree@vger.kernel.org \
    --cc=fabrice.gasnier@st.com \
    --cc=lars@metafoo.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-stm32@st-md-mailman.stormreply.com \
    --cc=mcoquelin.stm32@gmail.com \
    --cc=olivier.moysan@foss.st.com \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).