[PATCH v3 01/17] iio: core: Increase precision of IIO_VAL_FRACTIONAL_LOG2 when possible

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>

With some high resolution sensors such as the ad7746 the build up of error
when multiplying the _raw and _scale values together can be significant.
Reduce this affect by providing additional resolution in both calculation
and formatting of result. If overflow would occur with a 1e12 multiplier,
fall back to the 1e9 used before this patch and 9 decimal places.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
---
 drivers/iio/industrialio-core.c | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index dc3e1cb9bfbd..8225d0c43010 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -18,6 +18,7 @@
 #include <linux/poll.h>
 #include <linux/property.h>
 #include <linux/sched.h>
+#include <linux/units.h>
 #include <linux/wait.h>
 #include <linux/cdev.h>
 #include <linux/slab.h>
@@ -674,14 +675,28 @@ static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type,
 		else
 			return sysfs_emit_at(buf, offset, "%d.%09u", tmp0,
 					     abs(tmp1));
-	case IIO_VAL_FRACTIONAL_LOG2:
-		tmp2 = shift_right((s64)vals[0] * 1000000000LL, vals[1]);
-		tmp0 = (int)div_s64_rem(tmp2, 1000000000LL, &tmp1);
-		if (tmp0 == 0 && tmp2 < 0)
-			return sysfs_emit_at(buf, offset, "-0.%09u", abs(tmp1));
-		else
-			return sysfs_emit_at(buf, offset, "%d.%09u", tmp0,
-					     abs(tmp1));
+	case IIO_VAL_FRACTIONAL_LOG2: {
+		u64 t1, t2, mult;
+		int integer, precision;
+		bool neg = vals[0] < 0;
+
+		if (vals[0] > ULLONG_MAX / PICO) {
+			mult = NANO;
+			precision = 9;
+		} else {
+			mult = PICO;
+			precision = 12;
+		}
+		t1 = shift_right((u64)abs(vals[0]) * mult, vals[1]);
+		integer = (int)div64_u64_rem(t1, mult, &t2);
+		if (integer == 0 && neg)
+			return sysfs_emit_at(buf, offset, "-0.%0*llu",
+					     precision, abs(t2));
+		if (neg)
+			integer *= -1;
+		return sysfs_emit_at(buf, offset, "%d.%0*llu", integer,
+				     precision, abs(t2));
+	}
 	case IIO_VAL_INT_MULTIPLE:
 	{
 		int i;
-- 
2.36.1




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux