Re: [PATCH 1/3] iio: adc: palmas_gpadc: add support for iio threshold events

From: Jonathan Cameron
Date: Sun Mar 26 2023 - 12:36:05 EST


On Sun, 19 Mar 2023 23:39:06 +0100
Patrik Dahlström <risca@xxxxxxxxxxxxxx> 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. The gpadc
> driver had limited support for this through the adc_wakeup{1,2}_data
> platform data. This however only allow a fixed threshold to be set at
> boot, and would only enable it when entering sleep mode.
>
> This change hooks into the IIO events system and exposes to userspace
> the ability to configure threshold values for each channel individually,
> but only allow up to 2 such thresholds to be enabled at any given time.

Add a comment here on what happens if userspace tries to set more than two.
It's not as obvious as you'd think as we have some drivers that use a fifo
approach so on setting the third event they push the oldest one out.

>
> The logic around suspend and resume had to be adjusted so that user
> space configuration don't get reset on resume. Instead, any configured
> adc auto wakeup gets enabled during probe.
>
> Enabling a threshold from userspace will overwrite the adc wakeup
> configuration set during probe. Depending on how you look at it, this
> could also mean we allow userspace to update the adc wakeup thresholds.

I'm not sure I read the code right, but can you end up enabling a wakeup
that wasn't previously present? That seems likely something we should
not be doing after boot.

One option here would be to make it either wakeup is supported, or events
are supported. I suspect no one uses the wakeup anyway so that shouldn't
matter much (+ you remove it in next patch - do that first and this code
becomes more obvious).


A few trivial comments inline.
>
> Signed-off-by: Patrik Dahlström <risca@xxxxxxxxxxxxxx>

>
> @@ -280,6 +326,9 @@ static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
> {
> int ret;
>
> + if (palmas_gpadc_channel_is_freerunning(adc, adc_chan))
> + return 0; // ADC already running

/* */

...

>
> +static int palmas_gpadc_get_high_threshold_raw(struct palmas_gpadc *adc,
> + struct palmas_adc_event *ev)
> +{
> + const int INL = 2;
> + const int adc_chan = ev->channel;
> + const int orig = adc->thresh_data[adc_chan].high_thresh;
> + int val = orig;
> + int gain_drift;
> + int offset_drift;
> +
> + if (!val)
> + return 0;
> +
> + val = (val * 1000) / adc->adc_info[adc_chan].gain;
> +
> + if (!adc->adc_info[adc_chan].is_uncalibrated) {
> + val = (val * adc->adc_info[adc_chan].gain_error +
> + adc->adc_info[adc_chan].offset) /
> + 1000;
> + gain_drift = 1002;
> + offset_drift = 2;
> + }
> + else {
> + gain_drift = 1022;
> + offset_drift = 36;
> + }
> +
> + // add tolerance to threshold
> + val = ((val + INL) * gain_drift) / 1000 + offset_drift;
> +
> + // clamp to max possible value
/* clamp .. */
val = min(val, 0xFFF);


> + if (val > 0xFFF)
> + val = 0xFFF;
> +
> + return val;
> +}
> +
> +static int palmas_gpadc_get_low_threshold_raw(struct palmas_gpadc *adc,
> + struct palmas_adc_event *ev)
> +{
> + const int INL = 2;
> + const int adc_chan = ev->channel;
> + const int orig = adc->thresh_data[adc_chan].low_thresh;
> + int val = orig;
> + int gain_drift;
> + int offset_drift;
> +
> + if (!val)
> + return val;
> +
> + val = (val * 1000) / adc->adc_info[adc_chan].gain;
> +
> + if (!adc->adc_info[adc_chan].is_uncalibrated) {
> + val = (val * adc->adc_info[adc_chan].gain_error -
> + adc->adc_info[adc_chan].offset) /
> + 1000;
> + gain_drift = 998;
> + offset_drift = 2;
> + }
> + else {
> + gain_drift = 978;
> + offset_drift = 36;
> + }
> +
> + // calculate tolerances
/* */

+ I think this could do with more information on why a tweak is needed.

> + val = ((val - INL) * gain_drift) / 1000 - offset_drift;
> +
> + // clamp to minimum 0

/* */ for all comments.

val = max(0, val); then comment may not be needed.

> + if (val < 0)
> + val = 0;
> +
> + return val;
> +}

> +static int palmas_gpadc_reconfigure_event_channels(struct palmas_gpadc *adc)
> +{
> + bool was_enabled = adc->wakeup1_enable || adc->wakeup2_enable;
> + bool enable;
> +
> + adc->wakeup1_enable = adc->event0.channel == -1 ? false : true;
> + adc->wakeup2_enable = adc->event1.channel == -1 ? false : true;
> +
> + enable = adc->wakeup1_enable || adc->wakeup2_enable;
> + if (!was_enabled && enable)
> + device_wakeup_enable(adc->dev);
> + else if (was_enabled && !enable)
> + device_wakeup_disable(adc->dev);
> +
> + if (!enable)
> + return palmas_adc_wakeup_reset(adc);
> +
> + // adjust levels

/* adjust levels */

> + if (adc->wakeup1_enable)
> + palmas_adc_event_to_wakeup(adc, &adc->event0, &adc->wakeup1_data);
> + if (adc->wakeup2_enable)
> + palmas_adc_event_to_wakeup(adc, &adc->event1, &adc->wakeup2_data);
> +
> + return palmas_adc_wakeup_configure(adc);
> +}
> +
> +static int palmas_gpadc_enable_event_config(struct palmas_gpadc *adc,
> + const struct iio_chan_spec *chan, enum iio_event_direction dir)
> +{
> + struct palmas_adc_event *ev;
> + int adc_chan = chan->channel;
> +
> + if (palmas_gpadc_get_event_channel(adc, adc_chan, dir))
> + /* already enabled */
> + return 0;
> +
> + if (adc->event0.channel == -1)

I'd add brackets for all legs of this if / else once one of them needs
it. Tends to end up more readable.

> + ev = &adc->event0;
> + else if (adc->event1.channel == -1) {
> + /* event0 has to be the lowest channel */
> + if (adc_chan < adc->event0.channel) {
> + adc->event1 = adc->event0;
> + ev = &adc->event0;
> + }
> + else
> + ev = &adc->event1;
> + }

} else /*...

> + else /* both AUTO channels already in use */ {
> + dev_warn(adc->dev, "event0 - %d, event1 - %d\n",
> + adc->event0.channel, adc->event1.channel);
> + return -EBUSY;
> + }
> +
> + ev->channel = adc_chan;
> + ev->direction = dir;
> +
> + return palmas_gpadc_reconfigure_event_channels(adc);
> +}

> +
> +static int palmas_gpadc_write_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)

Prefer parameters aligned just after (

> +{
...


>
> static int palmas_gpadc_probe(struct platform_device *pdev)

...

> /* set the current source 0 (value 0/5/15/20 uA => 0..3) */
> if (gpadc_pdata->ch0_current <= 1)
> adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> @@ -610,20 +998,23 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> return dev_err_probe(adc->dev, ret,
> "iio_device_register() failed\n");
>
> - device_set_wakeup_capable(&pdev->dev, 1);
> for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
> if (!(adc->adc_info[i].is_uncalibrated))
> palmas_gpadc_calibrate(adc, i);
> }
>
> + device_set_wakeup_capable(&pdev->dev, 1);
> if (adc->wakeup1_enable || adc->wakeup2_enable) {
> - device_wakeup_enable(&pdev->dev);
> - ret = devm_add_action_or_reset(&pdev->dev,
> - palmas_disable_wakeup,
> - &pdev->dev);
> + ret = palmas_adc_wakeup_configure(adc);
> if (ret)
> return ret;
> + device_wakeup_enable(&pdev->dev);

> }
> + ret = devm_add_action_or_reset(&pdev->dev,

Add a comment for this to explain why it might need disabling even if
it wasn't enabled above. I think if you just drop wakeup support in
general that will be fine given we have no known users.


> + palmas_disable_wakeup,
> + adc);
> + if (ret)
> + return ret;
>
> return 0;
> }