Re: [PATCH v4 4/8] iio: adc: ad7606: Add PWM support for conversion trigger

From: Nuno Sá
Date: Wed Oct 09 2024 - 10:29:54 EST


On Wed, 2024-10-09 at 09:19 +0000, Guillaume Stols wrote:
> Until now, the conversion were triggered by setting high the GPIO
> connected to the convst pin. This commit gives the possibility to
> connect the convst pin to a PWM.
> Connecting a PWM allows to have a better control on the samplerate,
> but it must be handled with care, as it is completely decorrelated of
> the driver's busy pin handling.
> Hence it is not recommended to be used "as is" but must be exploited
> in conjunction with IIO backend, and for now only a mock functionality
> is enabled, i.e PWM never swings, but is used as a GPIO, i.e duty_cycle
> == period equals high state, duty_cycle == 0 equals low state.
>
> This mock functionality will be disabled after the IIO backend usecase
> is introduced.
>
> Signed-off-by: Guillaume Stols <gstols@xxxxxxxxxxxx>
> ---

Hi Guillaume,

Looks overall good, just some minor stuff

>  drivers/iio/adc/ad7606.c | 143 +++++++++++++++++++++++++++++++++++++++++++----
>  drivers/iio/adc/ad7606.h |   2 +
>  2 files changed, 135 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/iio/adc/ad7606.c b/drivers/iio/adc/ad7606.c
> index 71362eafe838..5b276d087ec3 100644
> --- a/drivers/iio/adc/ad7606.c
> +++ b/drivers/iio/adc/ad7606.c
> @@ -13,10 +13,12 @@
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/property.h>
> +#include <linux/pwm.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/sched.h>
>  #include <linux/slab.h>
>  #include <linux/sysfs.h>
> +#include <linux/units.h>
>  #include <linux/util_macros.h>
>  
>  #include <linux/iio/buffer.h>
> @@ -299,6 +301,82 @@ static int ad7606_reg_access(struct iio_dev *indio_dev,
>   }
>  }
>  
> +static int ad7606_pwm_set_high(struct ad7606_state *st)
> +{
> + struct pwm_state cnvst_pwm_state;
> + int ret;
> +
> + if (!st->cnvst_pwm)
> + return -EINVAL;
> +

Maybe consider doing the check before calling the API (for the cases that need it)?
It seems at least that in a couple of cases you actually already know that the PWM
must be here (since you check for the gpio presence)...

> + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state);
> + cnvst_pwm_state.enabled = true;
> + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period;
> +
> + ret = pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state);
> + /* sleep 2 µS to let finish the current pulse */
> + fsleep(2);
> +
> + return ret;
> +}
> +
> +static int ad7606_pwm_set_low(struct ad7606_state *st)
> +{
> + struct pwm_state cnvst_pwm_state;
> + int ret;
> +
> + if (!st->cnvst_pwm)
> + return -EINVAL;
> +

Same deal...

> + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state);
> + cnvst_pwm_state.enabled = true;
> + cnvst_pwm_state.duty_cycle = 0;
> +
> + ret = pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state);
> + /* sleep 2 µS to let finish the current pulse */
> + fsleep(2);
> +
> + return ret;
> +}
> +
> +static bool ad7606_pwm_is_swinging(struct ad7606_state *st)
> +{
> + struct pwm_state cnvst_pwm_state;
> +
> + if (!st->cnvst_pwm)
> + return false;
> +

This one also seems to be redundant? ad7606_set_sampling_freq() should be only called
from a context where the PWM exists right?

> + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state);
> +
> + return cnvst_pwm_state.duty_cycle != cnvst_pwm_state.period &&
> +        cnvst_pwm_state.duty_cycle != 0;
> +}
> +
> +static int ad7606_set_sampling_freq(struct ad7606_state *st, unsigned long freq)
> +{
> + struct pwm_state cnvst_pwm_state;
> + bool is_swinging = ad7606_pwm_is_swinging(st);
> + bool is_high;
> +
> + if (freq == 0)
> + return -EINVAL;
> +
> + /* Retrieve the previous state. */
> + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state);
> + is_high = cnvst_pwm_state.duty_cycle == cnvst_pwm_state.period;
> +
> + cnvst_pwm_state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq);
> + cnvst_pwm_state.polarity = PWM_POLARITY_NORMAL;
> + if (is_high)
> + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period;
> + else if (is_swinging)
> + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period / 2;
> + else
> + cnvst_pwm_state.duty_cycle = 0;
> +
> + return pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state);
> +}
> +
>  static int ad7606_read_samples(struct ad7606_state *st)
>  {
>   unsigned int num = st->chip_info->num_channels - 1;
> @@ -325,6 +403,7 @@ static irqreturn_t ad7606_trigger_handler(int irq, void *p)
>   iio_trigger_notify_done(indio_dev->trig);
>   /* The rising edge of the CONVST signal starts a new conversion. */
>   gpiod_set_value(st->gpio_convst, 1);
> + ad7606_pwm_set_high(st);
>  
>   return IRQ_HANDLED;
>  }
> @@ -337,7 +416,13 @@ static int ad7606_scan_direct(struct iio_dev *indio_dev,
> unsigned int ch,
>   const struct iio_chan_spec *chan;
>   int ret;
>  
> - gpiod_set_value(st->gpio_convst, 1);
> + if (st->gpio_convst) {
> + gpiod_set_value(st->gpio_convst, 1);
> + } else {
> + ret = ad7606_pwm_set_high(st);
> + if (ret < 0)
> + return ret;
> + }
>   ret = wait_for_completion_timeout(&st->completion,
>     msecs_to_jiffies(1000));
>   if (!ret) {
> @@ -363,6 +448,11 @@ static int ad7606_scan_direct(struct iio_dev *indio_dev,
> unsigned int ch,
>   }
>  
>  error_ret:
> + if (!st->gpio_convst) {
> + ret = ad7606_pwm_set_low(st);
> + if (ret < 0)
> + return ret;
> + }
>   gpiod_set_value(st->gpio_convst, 0);
>  
>   return ret;
> @@ -662,8 +752,9 @@ static int ad7606_request_gpios(struct ad7606_state *st)
>  {
>   struct device *dev = st->dev;
>  
> - st->gpio_convst = devm_gpiod_get(dev, "adi,conversion-start",
> - GPIOD_OUT_LOW);
> + st->gpio_convst = devm_gpiod_get_optional(dev, "adi,conversion-start",
> +   GPIOD_OUT_LOW);
> +
>   if (IS_ERR(st->gpio_convst))
>   return PTR_ERR(st->gpio_convst);
>  
> @@ -708,6 +799,7 @@ static irqreturn_t ad7606_interrupt(int irq, void *dev_id)
>  
>   if (iio_buffer_enabled(indio_dev)) {
>   gpiod_set_value(st->gpio_convst, 0);
> + ad7606_pwm_set_low(st);
>   iio_trigger_poll_nested(st->trig);
>   } else {
>   complete(&st->completion);
> @@ -732,6 +824,7 @@ static int ad7606_buffer_postenable(struct iio_dev *indio_dev)
>   struct ad7606_state *st = iio_priv(indio_dev);
>  
>   gpiod_set_value(st->gpio_convst, 1);
> + ad7606_pwm_set_high(st);

error handling?
>  
>   return 0;
>  }
> @@ -741,6 +834,7 @@ static int ad7606_buffer_predisable(struct iio_dev *indio_dev)
>   struct ad7606_state *st = iio_priv(indio_dev);
>  
>   gpiod_set_value(st->gpio_convst, 0);
> + ad7606_pwm_set_low(st);
>

error handling?

>   return 0;
>  }
> @@ -874,6 +968,11 @@ static int ad7606_chan_scales_setup(struct iio_dev *indio_dev)
>   return 0;
>  }
>  
> +static void ad7606_pwm_disable(void *data)
> +{
> + pwm_disable(data);
> +}
> +
>  int ad7606_probe(struct device *dev, int irq, void __iomem *base_address,
>   const char *name, unsigned int id,
>   const struct ad7606_bus_ops *bops)
> @@ -950,6 +1049,31 @@ int ad7606_probe(struct device *dev, int irq, void __iomem
> *base_address,
>   if (ret)
>   return ret;
>  
> + /* If convst pin is not defined, setup PWM. */
> + if (!st->gpio_convst) {
> + st->cnvst_pwm = devm_pwm_get(dev, NULL);
> + if (IS_ERR(st->cnvst_pwm))
> + return PTR_ERR(st->cnvst_pwm);
> +
> + /* The PWM is initialized at 1MHz to have a fast enough GPIO
> emulation. */
> + ret = ad7606_set_sampling_freq(st, 1 * MEGA);
> + if (ret)
> + return ret;
> +
> + ret = ad7606_pwm_set_low(st);
> + if (ret)
> + return ret;
> +
> + /*
> + * PWM is not disabled when sampling stops, but instead its duty
> cycle is set
> + * to 0% to be sure we have a "low" state. After we unload the
> driver, let's
> + * disable the PWM.
> + */
> + ret = devm_add_action_or_reset(dev, ad7606_pwm_disable,
> +        st->cnvst_pwm);
> + if (ret)
> + return ret;
> + }
>   st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
>     indio_dev->name,
>     iio_device_id(indio_dev));
> @@ -963,6 +1087,12 @@ int ad7606_probe(struct device *dev, int irq, void __iomem
> *base_address,
>   return ret;
>  
>   indio_dev->trig = iio_trigger_get(st->trig);
> + ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> +       &iio_pollfunc_store_time,
> +       &ad7606_trigger_handler,
> +       &ad7606_buffer_ops);
> + if (ret)
> + return ret;
>

The above change seems unrelated?

- Nuno Sá