Re: [PATCH v3 6/7] iio: adc: palmas: add support for iio threshold events

Jonathan Cameron
2023-04-07T17:04:38Z

On Wed, 5 Apr 2023 23:22:32 +0200
Patrik Dahlström <risca@dalakolonin.se> wrote:

> The palmas gpadc block has support for monitoring up to 2 ADC channels
> and issue an interrupt if they reach past a set threshold. This change
> hooks into the IIO events system and exposes to userspace the ability to
> configure these threshold values for each channel, but only allow up to
> 2 such thresholds to be enabled at any given time. Trying to enable a
> third channel will result in an error.
>
> Userspace is expected to input calibrated, as opposed to raw, values as
> threshold. However, it is not enough to do the opposite of what is done
> when converting the other way around. To account for tolerances in the
> ADC, the calculated raw threshold should be adjusted based on the ADC
> specifications for the device. These specifications include the integral
> nonlinearity (INL), offset, and gain error. To adjust the high
> threshold, use the following equation:
>
> (calibrated value + INL) * Gain error + offset = maximum value [1]
>
> Likewise, use the following equation for the low threshold:
>
> (calibrated value - INL) * Gain error - offset = minimum value
>
> The gain error is a combination of gain error, as listed in the
> datasheet, and gain error drift due to temperature and supply. The exact
> values for these specifications vary between palmas devices. This patch
> sets the values found in TWL6035, TWL6037 datasheet.
>
> [1] TI Application Report, SLIA087A, Guide to Using the GPADC in
> TPS65903x, TPS65917-Q1, TPS65919-Q1, and TPS65916 Devices.
>
> Signed-off-by: Patrik Dahlström <risca@dalakolonin.se>
Hi Patrik,

A few really trivial formatting things inline. If we don't end up
with a v4 for other reasons I can tidy this stuff up whilst applying.

Jonathan

>
> +/**

Not kernel-doc so /* only
Even if it were the indent for the following should align the * with the first * not
the second one.

> + * The high and low threshold values are calculated based on the advice given
> + * in TI Application Report SLIA087A, "Guide to Using the GPADC in PS65903x,
> + * TPS65917-Q1, TPS65919-Q1, and TPS65916 Devices". This document recommend
> + * taking ADC tolerances into account and is based on the device integral non-
> + * linearity (INL), offset error and gain error:
> + *
> + * raw high threshold = (ideal threshold + INL) * gain error + offset error
> + *
> + * The gain error include both gain error, as specified in the datasheet, and
> + * the gain error drift. These paramenters vary depending on device and whether
> + * the the channel is calibrated (trimmed) or not.
> + */
> +static int palmas_gpadc_threshold_with_tolerance(int val, const int INL,
> + const int gain_error,
> + const int offset_error)
> +{
> + val = ((val + INL) * (1000 + gain_error)) / 1000 + offset_error;
> +
> + return clamp(val, 0, 0xFFF);
> +}
> +
> +/**

/*

> + * The values below are taken from the datasheet of TWL6035, TWL6037.
> + * todo: get max INL, gain error, and offset error from OF.
> + */
> +static int palmas_gpadc_get_high_threshold_raw(struct palmas_gpadc *adc,
> + struct palmas_adc_event *ev)
> +{
> + const int adc_chan = ev->channel;
> + int val = adc->thresholds[adc_chan].high;
> + /* integral nonlinearity, measured in LSB */
> + const int max_INL = 2;
> + /* measured in LSB */
> + int max_offset_error;
> + /* 0.2% when calibrated */
> + int max_gain_error = 2;
> +
> + val = (val * 1000) / adc->adc_info[adc_chan].gain;
> +
> + if (adc->adc_info[adc_chan].is_uncalibrated) {
> + /* 2% worse */
> + max_gain_error += 20;
> + max_offset_error = 36;
> + } else {
> + val = (val * adc->adc_info[adc_chan].gain_error +
> + adc->adc_info[adc_chan].offset) /
> + 1000;
> + max_offset_error = 2;
> + }
> +
> + return palmas_gpadc_threshold_with_tolerance(val,
> + max_INL,
> + max_gain_error,
> + max_offset_error);
> +}
> +
> +/**

This isn't kernel-doc so just /*

> + * The values below are taken from the datasheet of TWL6035, TWL6037.
> + * todo: get min INL, gain error, and offset error from OF.
> + */
> +static int palmas_gpadc_get_low_threshold_raw(struct palmas_gpadc *adc,
> + struct palmas_adc_event *ev)
> +{
> + const int adc_chan = ev->channel;
> + int val = adc->thresholds[adc_chan].low;
> + /* integral nonlinearity, measured in LSB */
> + const int min_INL = -2;
> + /* measured in LSB */
> + int min_offset_error;
> + /* -0.6% when calibrated */
> + int min_gain_error = -6;
> +
> + val = (val * 1000) / adc->adc_info[adc_chan].gain;
> +
> + if (adc->adc_info[adc_chan].is_uncalibrated) {
> + /* 2% worse */
> + min_gain_error -= 20;
> + min_offset_error = -36;
> + } else {
> + val = (val * adc->adc_info[adc_chan].gain_error -
> + adc->adc_info[adc_chan].offset) /
> + 1000;
> + min_offset_error = -2;
> + }
> +
> + return palmas_gpadc_threshold_with_tolerance(val,
> + min_INL,
> + min_gain_error,
> + min_offset_error);
> +}
> +
> static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> {
> @@ -437,8 +586,221 @@ static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> return ret;
> }
>
> +static int palmas_gpadc_read_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir)
> +{
> + struct palmas_gpadc *adc = iio_priv(indio_dev);
> + int adc_chan = chan->channel;
> + int ret = 0;
> +
> + if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
> + return -EINVAL;
> +
> + mutex_lock(&adc->lock);
> +
> + if (palmas_gpadc_get_event(adc, adc_chan, dir)) {
> + ret = 1;

Trivial: No brackets needed here for kernel style.

> + }
> +
> + mutex_unlock(&adc->lock);
> +
> + return ret;
> +}
...

> +static int palmas_gpadc_write_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + int state)
> +{
> + struct palmas_gpadc *adc = iio_priv(indio_dev);
> + int adc_chan = chan->channel;
> + int ret = 0;

This initial value isn't used so shouldn't be set.
One of the static analysis tools will spot this so if we don't tidy it up
now chances of it getting 'fixed' later is high. Better to avoid the
overhead of such a patch.

> +
> + if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
> + return -EINVAL;
> +
> + mutex_lock(&adc->lock);
> +
> + if (state)
> + ret = palmas_gpadc_enable_event_config(adc, chan, dir);
> + else
> + ret = palmas_gpadc_disable_event_config(adc, chan, dir);
> +
> + mutex_unlock(&adc->lock);
> +
> + return ret;
> +}
> +
> +static int palmas_gpadc_read_event_value(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info,
> + int *val, int *val2)
> +{
> + struct palmas_gpadc *adc = iio_priv(indio_dev);
> + int adc_chan = chan->channel;
> + int ret = 0;

Trivial: I can't see a path where this initial value is used so it
shouldn't be initialized here.

> +
> + if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
> + return -EINVAL;
> +
> + mutex_lock(&adc->lock);
> +
> + switch (info) {
> + case IIO_EV_INFO_VALUE:
> + *val = (dir == IIO_EV_DIR_RISING) ?
> + adc->thresholds[adc_chan].high :
> + adc->thresholds[adc_chan].low;
> + ret = IIO_VAL_INT;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + mutex_unlock(&adc->lock);
> +
> + return ret;
> +}
> +