Thread (17 messages) 17 messages, 3 authors, 2021-02-09

RE: [PATCH 2/4] iio: adis16480: fix pps mode sampling frequency math

From: "Sa, Nuno" <Nuno.Sa@analog.com>
Date: 2021-01-26 04:39:34
Also in: linux-iio

From: Jonathan Cameron <jic23@kernel.org>
Sent: Sunday, January 24, 2021 2:43 PM
To: Sa, Nuno <Nuno.Sa@analog.com>
Cc: devicetree@vger.kernel.org; linux-iio@vger.kernel.org; Rob
Herring [off-list ref]; Peter Meerwald-Stadler
[off-list ref]; Lars-Peter Clausen [off-list ref];
Hennerich, Michael [off-list ref]; Ardelean,
Alexandru [off-list ref]
Subject: Re: [PATCH 2/4] iio: adis16480: fix pps mode sampling
frequency math

[External]

On Thu, 21 Jan 2021 12:49:52 +0100
Nuno Sá [off-list ref] wrote:
quoted
When using PPS mode, the input clock needs to be scaled so that we
have
quoted
an IMU sample rate between (optimally) 4000 and 4250. After this,
we can
quoted
use the decimation filter to lower the sampling rate in order to get
what
quoted
the user wants. Optimally, the user sample rate is a multiple of both
the
quoted
IMU sample rate and the input clock. Hence, calculating the
sync_scale
quoted
dynamically gives us better chances of achieving a perfect/integer
value
quoted
for DEC_RATE. The math here is:
 1. lcm of the input clock and the desired output rate.
 2. get the highest multiple of the previous result lower than the adis
    max rate.
 3. The last result becomes the IMU sample rate. Use that to calculate
    SYNC_SCALE and DEC_RATE (to get the user output rate).

Fixes: 326e2357553d3 ("iio: imu: adis16480: Add support for external
clock")
quoted
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
As this is a fix, please move the refactor of the lock to after this patch.
We don't really want to need to backport that patch in order to apply
this
to older kernels.
Hmm, completely missed this... will do that.

- Nuno Sá
I'll reply to the cover letter as to what might make sense to do for
the case where we are potentially running the sensor too slow.

Otherwise, patch looks fine to me.

Jonathan
quoted
---
 drivers/iio/imu/adis16480.c | 120 ++++++++++++++++++++++++++-
---------
quoted
 1 file changed, 86 insertions(+), 34 deletions(-)
diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c
index dfe86c589325..7620822f3350 100644
--- a/drivers/iio/imu/adis16480.c
+++ b/drivers/iio/imu/adis16480.c
@@ -17,6 +17,7 @@
 #include <linux/slab.h>
 #include <linux/sysfs.h>
 #include <linux/module.h>
+#include <linux/lcm.h>

 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
@@ -312,7 +313,8 @@ static int adis16480_debugfs_init(struct
iio_dev *indio_dev)
quoted
 static int adis16480_set_freq(struct iio_dev *indio_dev, int val, int
val2)
quoted
 {
 	struct adis16480 *st = iio_priv(indio_dev);
-	unsigned int t, reg;
+	unsigned int t, sample_rate = st->clk_freq;
+	int ret;

 	if (val < 0 || val2 < 0)
 		return -EINVAL;
@@ -321,28 +323,63 @@ static int adis16480_set_freq(struct iio_dev
*indio_dev, int val, int val2)
quoted
 	if (t == 0)
 		return -EINVAL;

+	adis_dev_lock(&st->adis);
 	/*
-	 * When using PPS mode, the rate of data collection is equal to
the
quoted
-	 * product of the external clock frequency and the scale factor
in the
quoted
-	 * SYNC_SCALE register.
-	 * When using sync mode, or internal clock, the output data
rate is
quoted
-	 * equal with  the clock frequency divided by DEC_RATE + 1.
+	 * When using PPS mode, the input clock needs to be scaled so
that we have an IMU
quoted
+	 * sample rate between (optimally) 4000 and 4250. After this,
we can use the
quoted
+	 * decimation filter to lower the sampling rate in order to get
what the user wants.
quoted
+	 * Optimally, the user sample rate is a multiple of both the IMU
sample rate and
quoted
+	 * the input clock. Hence, calculating the sync_scale dynamically
gives us better
quoted
+	 * chances of achieving a perfect/integer value for DEC_RATE.
The math here is:
quoted
+	 *	1. lcm of the input clock and the desired output rate.
+	 *	2. get the highest multiple of the previous result lower
than the adis max rate.
quoted
+	 *	3. The last result becomes the IMU sample rate. Use
that to calculate SYNC_SCALE
quoted
+	 *	   and DEC_RATE (to get the user output rate)
 	 */
 	if (st->clk_mode == ADIS16480_CLK_PPS) {
-		t = t / st->clk_freq;
-		reg = ADIS16495_REG_SYNC_SCALE;
-	} else {
-		t = st->clk_freq / t;
-		reg = ADIS16480_REG_DEC_RATE;
+		unsigned long scaled_rate = lcm(st->clk_freq, t);
+		int sync_scale;
+		struct device *dev = &st->adis.spi->dev;
+
+		/*
+		 * If lcm is bigger than the IMU maximum sampling rate
there's no perfect
quoted
+		 * solution. In this case, we get the highest multiple of
the input clock
quoted
+		 * lower that the IMU max sample rate.
+		 */
+		if (scaled_rate > st->chip_info->int_clk)
+			scaled_rate = st->chip_info->int_clk / st-
clk_freq * st->clk_freq;
+		else
+			scaled_rate = st->chip_info->int_clk /
scaled_rate * scaled_rate;
quoted
+
+		/*
+		 * This is not an hard requirement but it's not advised to
run the IMU
quoted
+		 * with a sample rate lower than 4000Hz due to possible
undersampling
quoted
+		 * issues so we will log a warning here. We could even
force the rate
quoted
+		 * to 4000 but some users might really want this...
+		 */
+		if (scaled_rate < 4000000)
+			dev_warn(dev, "Possible undersampling issues
due to sampling rate=%lu < 4000\n",
quoted
+				 scaled_rate / 1000);
+
+		sync_scale = scaled_rate / st->clk_freq;
+		ret = __adis_write_reg_16(&st->adis,
ADIS16495_REG_SYNC_SCALE, sync_scale);
quoted
+		if (ret)
+			goto error;
+
+		sample_rate = scaled_rate;
 	}

+	t = DIV_ROUND_CLOSEST(sample_rate, t);
+	if (t)
+		t--;
+
 	if (t > st->chip_info->max_dec_rate)
 		t = st->chip_info->max_dec_rate;

-	if ((t != 0) && (st->clk_mode != ADIS16480_CLK_PPS))
-		t--;
-
-	return adis_write_reg_16(&st->adis, reg, t);
+	ret = __adis_write_reg_16(&st->adis,
ADIS16480_REG_DEC_RATE, t);
quoted
+error:
+	adis_dev_unlock(&st->adis);
+	return ret;
 }

 static int adis16480_get_freq(struct iio_dev *indio_dev, int *val, int
*val2)
quoted
@@ -350,34 +387,35 @@ static int adis16480_get_freq(struct iio_dev
*indio_dev, int *val, int *val2)
quoted
 	struct adis16480 *st = iio_priv(indio_dev);
 	uint16_t t;
 	int ret;
-	unsigned int freq;
-	unsigned int reg;
+	unsigned int freq, sample_rate = st->clk_freq;

-	if (st->clk_mode == ADIS16480_CLK_PPS)
-		reg = ADIS16495_REG_SYNC_SCALE;
-	else
-		reg = ADIS16480_REG_DEC_RATE;
+	adis_dev_lock(&st->adis);

-	ret = adis_read_reg_16(&st->adis, reg, &t);
+	if (st->clk_mode == ADIS16480_CLK_PPS) {
+		u16 sync_scale;
+
+		ret = __adis_read_reg_16(&st->adis,
ADIS16495_REG_SYNC_SCALE, &sync_scale);
quoted
+		if (ret)
+			goto error;
+
+		sample_rate = st->clk_freq * sync_scale;
+	}
+
+	ret = __adis_read_reg_16(&st->adis,
ADIS16480_REG_DEC_RATE, &t);
quoted
 	if (ret)
-		return ret;
+		goto error;

-	/*
-	 * When using PPS mode, the rate of data collection is equal to
the
quoted
-	 * product of the external clock frequency and the scale factor
in the
quoted
-	 * SYNC_SCALE register.
-	 * When using sync mode, or internal clock, the output data
rate is
quoted
-	 * equal with  the clock frequency divided by DEC_RATE + 1.
-	 */
-	if (st->clk_mode == ADIS16480_CLK_PPS)
-		freq = st->clk_freq * t;
-	else
-		freq = st->clk_freq / (t + 1);
+	adis_dev_unlock(&st->adis);
+
+	freq = DIV_ROUND_CLOSEST(sample_rate, (t + 1));

 	*val = freq / 1000;
 	*val2 = (freq % 1000) * 1000;

 	return IIO_VAL_INT_PLUS_MICRO;
+error:
+	adis_dev_unlock(&st->adis);
+	return ret;
 }

 enum {
@@ -1278,6 +1316,20 @@ static int adis16480_probe(struct
spi_device *spi)
quoted
 		st->clk_freq = clk_get_rate(st->ext_clk);
 		st->clk_freq *= 1000; /* micro */
+		if (st->clk_mode == ADIS16480_CLK_PPS) {
+			u16 sync_scale;
+
+			/*
+			 * In PPS mode, the IMU sample rate is the
clk_freq * sync_scale. Hence,
quoted
+			 * default the IMU sample rate to the highest
multiple of the input clock
quoted
+			 * lower than the IMU max sample rate. The
internal sample rate is the
quoted
+			 * max...
+			 */
+			sync_scale = st->chip_info->int_clk / st-
clk_freq;
+			ret = __adis_write_reg_16(&st->adis,
ADIS16495_REG_SYNC_SCALE, sync_scale);
quoted
+			if (ret)
+				return ret;
+		}
 	} else {
 		st->clk_freq = st->chip_info->int_clk;
 	}
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help