Re: [PATCH 4/4] iio: adc: stm32-dfsdm: add scale and offset support

Olivier MOYSAN
2020-02-14T14:49:54Z

Hi Jonathan,

On 2/14/20 2:11 PM, Jonathan Cameron wrote:
> On Tue, 11 Feb 2020 15:19:01 +0000
> Olivier MOYSAN <olivier.moysan@st.com> wrote:
>
>> Hi Jonathan,
>>
>> On 2/8/20 5:18 PM, Jonathan Cameron wrote:
>>> On Tue, 4 Feb 2020 11:10:08 +0100
>>> Olivier Moysan <olivier.moysan@st.com> wrote:
>>>
>>>> Add scale and offset attributes support to STM32 DFSDM.
>>>>
>>>> Signed-off-by: Olivier Moysan <olivier.moysan@st.com>
>>> Hmm. I can't remember this history of this but we've kind of
>>> ended up backwards wrt to other consumer drivers.
>>>
>>> In some sense this is similar to the analog gyroscopes. In those
>>> the consumer driver is the gyroscope which is consuming the raw
>>> readings from an ADC connected to the channel. This results
>>> in us getting readings reported by the gyroscope driver.
>>>
>>> Here we have a sigma delta convertor consuming the pulse train
>>> from a sigma delta device. So the channels are reported by
>>> the sigma delta receiver, whereas i think the nearest equivalent
>>> to the analog voltage outputing gyroscopes would have been if
>>> we had reported the channel values at the sigma delta converter.
>> The DFSDM driver is currently used as a consumer of the sd modulator.
>> The scale and offset values of the channels are already computed by
>> the DFSDM driver, and provided by this driver to the IIO ABI.
>> However, the DFSDM has no voltage reference, so it has to retrieve
>> it from sd-modulator channels, for the scale factor computation.
>>
>>                                     scale  offset
>>                                       ^      ^
>>                                       |      |       IIO ABI
>> +-------------------------------------------------------------+
>>          +---------------+          +-------------+
>>          |sd driver      |          |DFSDM driver |
>>          +---------------+          +-------------+
>> +-------------------------------------------------------------+
>>                                                          HW
>>          +---------------+          +-------------+
>> +------->+ sd-modulator  +--------->+ DFSDM +-------->
>> analog   +------+--------+          +-------------+ output
>> input           ^
>>                 | vref
>>                 +
>>
>>
>> Is it the topology your are expecting ?
> It's not the one we'd expect if we are aligning with similar cases
> elsewhere in IIO. For example, if we attach an analog accelerometer
> to an ADC, we report the accel channels on the accelerometer not the
> ADC. The equivalent would be to see the DFSDM as providing a
> conversion service to the SD device which is actually executing
> the measurement and has the input channels.
>
>
>          scale  offset raw
>            ^      ^ ^
>            |      |     |    IIO ABI
> +-------------------------------------------------------------+
>          +---------------+          +-------------+
>          |sd driver      |          |DFSDM driver |
>          +---------------+          +-------------+
> +-------------------------------------------------------------+
>                                                          HW
>          +---------------+          +-------------+
> +------->+ sd-modulator  +--------->+ DFSDM +-------->
> analog   +------+--------+          +-------------+ output
> input           ^
>                 | vref
>>                 +
>>
Thanks for your clarification.
ok, moving to this logic is a significant change.
I need to evaluate further the impact on the dfsdm driver.

Regards
Olivier
>> If not, I probably missedsomething. Could you please clarify this point ?
>>
>> Regards
>> Olivier
>>> This wasn't really an issue when the only values available were
>>> raw, but if we are adding scale and offset, they are things that
>>> belong to the ad1201 for example, not the upstream stm32-dfsdm unit.
>>>
>>> Thinking of it another way, we don't report an SPI ADC output in
>>> the driver for the SPI master.
>>>
>>> Could we flip it around without breaking anything?
>>>
>>> Jonathan
>>>
>>>> ---
>>>> drivers/iio/adc/stm32-dfsdm-adc.c | 105 +++++++++++++++++++++++++++++-
>>>> 1 file changed, 102 insertions(+), 3 deletions(-)
>>>>
>>>> diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c
>>>> index 07b9dfdf8e76..b85fd3e90496 100644
>>>> --- a/drivers/iio/adc/stm32-dfsdm-adc.c
>>>> +++ b/drivers/iio/adc/stm32-dfsdm-adc.c
>>>> @@ -10,6 +10,7 @@
>>>> #include <linux/dma-mapping.h>
>>>> #include <linux/iio/adc/stm32-dfsdm-adc.h>
>>>> #include <linux/iio/buffer.h>
>>>> +#include <linux/iio/consumer.h>
>>>> #include <linux/iio/hw-consumer.h>
>>>> #include <linux/iio/sysfs.h>
>>>> #include <linux/iio/timer/stm32-lptim-trigger.h>
>>>> @@ -67,6 +68,13 @@ struct stm32_dfsdm_dev_data {
>>>> const struct regmap_config *regmap_cfg;
>>>> };
>>>>
>>>> +struct stm32_dfsdm_sd_chan_info {
>>>> + int scale_val;
>>>> + int scale_val2;
>>>> + int offset;
>>>> + unsigned int differential;
>>>> +};
>>>> +
>>>> struct stm32_dfsdm_adc {
>>>> struct stm32_dfsdm *dfsdm;
>>>> const struct stm32_dfsdm_dev_data *dev_data;
>>>> @@ -79,6 +87,7 @@ struct stm32_dfsdm_adc {
>>>> struct iio_hw_consumer *hwc;
>>>> struct completion completion;
>>>> u32 *buffer;
>>>> + struct stm32_dfsdm_sd_chan_info *sd_chan;
>>>>
>>>> /* Audio specific */
>>>> unsigned int spi_freq; /* SPI bus clock frequency */
>>>> @@ -1271,7 +1280,10 @@ static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev,
>>>> int *val2, long mask)
>>>> {
>>>> struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>>>> - int ret;
>>>> + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id];
>>>> + struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast];
>>>> + u32 max = flo->max << (flo->lshift - chan->scan_type.shift);
>>>> + int ret, idx = chan->scan_index;
>>>>
>>>> switch (mask) {
>>>> case IIO_CHAN_INFO_RAW:
>>>> @@ -1307,6 +1319,41 @@ static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev,
>>>> *val = adc->sample_freq;
>>>>
>>>> return IIO_VAL_INT;
>>>> +
>>>> + case IIO_CHAN_INFO_SCALE:
>>>> + /*
>>>> + * Scale is expressed in mV.
>>>> + * When fast mode is disabled, actual resolution may be lower
>>>> + * than 2^n, where n=realbits-1.
>>>> + * This leads to underestimating input voltage. To
>>>> + * compensate this deviation, the voltage reference can be
>>>> + * corrected with a factor = realbits resolution / actual max
>>>> + */
>>>> + *val = div_u64((u64)adc->sd_chan[idx].scale_val *
>>>> + (u64)BIT(DFSDM_DATA_RES - 1), max);
>>>> + *val2 = chan->scan_type.realbits;
>>>> + if (adc->sd_chan[idx].differential)
>>>> + *val *= 2;
>>>> +
>>>> + return IIO_VAL_FRACTIONAL_LOG2;
>>>> +
>>>> + case IIO_CHAN_INFO_OFFSET:
>>>> + /*
>>>> + * DFSDM output data are in the range [-2^n,2^n-1],
>>>> + * with n=realbits-1.
>>>> + * - Differential modulator:
>>>> + * Offset correspond to SD modulator offset.
>>>> + * - Single ended modulator:
>>>> + * Input is in [0V,Vref] range, where 0V corresponds to -2^n.
>>>> + * Add 2^n to offset. (i.e. middle of input range)
>>>> + * offset = offset(sd) * vref / res(sd) * max / vref.
>>>> + */
>>>> + *val = div_u64((u64)max * adc->sd_chan[idx].offset,
>>>> + BIT(adc->sd_chan[idx].scale_val2 - 1));
>>>> + if (!adc->sd_chan[idx].differential)
>>>> + *val += max;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> }
>>>>
>>>> return -EINVAL;
>>>> @@ -1430,7 +1477,9 @@ static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
>>>> * IIO_CHAN_INFO_RAW: used to compute regular conversion
>>>> * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling
>>>> */
>>>> - ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
>>>> + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>>> + BIT(IIO_CHAN_INFO_SCALE) |
>>>> + BIT(IIO_CHAN_INFO_OFFSET);
>>>> ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
>>>> BIT(IIO_CHAN_INFO_SAMP_FREQ);
>>>>
>>>> @@ -1481,8 +1530,10 @@ static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev)
>>>> {
>>>> struct iio_chan_spec *ch;
>>>> struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>>>> + struct iio_channel *channels, *chan;
>>>> + struct stm32_dfsdm_sd_chan_info *sd_chan;
>>>> int num_ch;
>>>> - int ret, chan_idx;
>>>> + int ret, chan_idx, val2;
>>>>
>>>> adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING;
>>>> ret = stm32_dfsdm_compute_all_osrs(indio_dev, adc->oversamp);
>>>> @@ -1506,6 +1557,22 @@ static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev)
>>>> if (!ch)
>>>> return -ENOMEM;
>>>>
>>>> + /* Get SD modulator channels */
>>>> + channels = iio_channel_get_all(&indio_dev->dev);
>>>> + if (IS_ERR(channels)) {
>>>> + dev_err(&indio_dev->dev, "Failed to get channel %ld\n",
>>>> + PTR_ERR(channels));
>>>> + return PTR_ERR(channels);
>>>> + }
>>>> + chan = &channels[0];
>>>> +
>>>> + adc->sd_chan = devm_kzalloc(&indio_dev->dev,
>>>> + sizeof(*adc->sd_chan) * num_ch, GFP_KERNEL);
>>>> + if (!adc->sd_chan)
>>>> + return -ENOMEM;
>>>> +
>>>> + sd_chan = adc->sd_chan;
>>>> +
>>>> for (chan_idx = 0; chan_idx < num_ch; chan_idx++) {
>>>> ch[chan_idx].scan_index = chan_idx;
>>>> ret = stm32_dfsdm_adc_chan_init_one(indio_dev, &ch[chan_idx]);
>>>> @@ -1513,6 +1580,38 @@ static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev)
>>>> dev_err(&indio_dev->dev, "Channels init failed\n");
>>>> return ret;
>>>> }
>>>> +
>>>> + if (!chan->indio_dev)
>>>> + return -EINVAL;
>>>> +
>>>> + ret = iio_read_channel_scale(chan, &sd_chan->scale_val,
>>>> + &sd_chan->scale_val2);
>>>> + if (ret < 0) {
>>>> + dev_err(&indio_dev->dev,
>>>> + "Failed to get channel %d scale\n", chan_idx);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_OFFSET)) {
>>>> + ret = iio_read_channel_offset(chan, &sd_chan->offset,
>>>> + &val2);
>>>> + if (ret < 0) {
>>>> + dev_err(&indio_dev->dev,
>>>> + "Failed to get channel %d offset\n",
>>>> + chan_idx);
>>>> + return ret;
>>>> + }
>>>> + }
>>>> +
>>>> + sd_chan->differential = chan->channel->differential;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "Channel %d %s scale ref=%d offset=%d",
>>>> + chan_idx, chan->channel->differential ?
>>>> + "differential" : "single-ended",
>>>> + sd_chan->scale_val, sd_chan->offset);
>>>> +
>>>> + chan++;
>>>> + sd_chan++;
>>>> }
>>>>
>>>> indio_dev->num_channels = num_ch;