* [PATCH v2 1/3] iio: add IIO_VAL_DECIMAL64_{ATTO,ZEPTO,YOCTO} formats
2026-10-25 5:48 [PATCH v2 0/3] iio: cdc: add Texas Instruments FDC1004 driver Victor Chernyakin
@ 2026-10-25 5:48 ` Victor Chernyakin
2026-10-25 19:41 ` Andy Shevchenko
2026-10-25 5:48 ` [PATCH v2 2/3] dt-bindings: iio: cdc: add Texas Instruments FDC1004 Victor Chernyakin
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Victor Chernyakin @ 2026-10-25 5:48 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, linux-iio, linux-kernel, Victor Chernyakin
The highest-precision format IIO has right now is IIO_VAL_DECIMAL64_FEMTO,
which gives 15 decimal digits of precision after the decimal point. This
is not enough for the upcoming TI FDC1004 driver, which needs 22 digits
of precision to exactly express its scale value. This patch adds higher-
precision formats, up to IIO_VAL_DECIMAL64_YOCTO (24 digits of precision).
Signed-off-by: Victor Chernyakin <chernyakin.victor.j@outlook.com>
---
drivers/iio/industrialio-core.c | 28 ++++++++++++++++++++++++----
drivers/iio/test/iio-test-format.c | 16 ++++++++++++++++
include/linux/iio/types.h | 3 +++
3 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 767a7794624a..c1c890eab6e9 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -653,6 +653,14 @@ int iio_read_mount_matrix(struct device *dev, struct iio_mount_matrix *matrix)
}
EXPORT_SYMBOL(iio_read_mount_matrix);
+/* An abs() function that doesn't have undefined behavior for LLONG_MIN. */
+static inline u64 abs_s64_no_overflow(s64 x)
+{
+ return x < 0 ? -(u64)x : (u64)x;
+}
+
+#define GREATEST_POWER_OF_10_THAT_FITS_IN_S64 18
+
static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type,
int size, const int *vals)
{
@@ -716,17 +724,26 @@ static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type,
case IIO_VAL_DECIMAL64_NANO:
case IIO_VAL_DECIMAL64_PICO:
case IIO_VAL_DECIMAL64_FEMTO:
+ case IIO_VAL_DECIMAL64_ATTO:
+ case IIO_VAL_DECIMAL64_ZEPTO:
+ case IIO_VAL_DECIMAL64_YOCTO:
{
int scale = type - IIO_VAL_DECIMAL64_BASE;
s64 frac;
- tmp2 = div64_s64_rem(iio_val_s64_compose(vals[0], vals[1]),
- int_pow(10, scale), &frac);
+ if (scale <= GREATEST_POWER_OF_10_THAT_FITS_IN_S64) {
+ tmp2 = div64_s64_rem(iio_val_s64_compose(vals[0], vals[1]),
+ int_pow(10, scale), &frac);
+ } else {
+ tmp2 = 0;
+ frac = iio_val_s64_compose(vals[0], vals[1]);
+ }
+
if (tmp2 == 0 && frac < 0)
l += sysfs_emit_at(buf, offset, "-");
- l += sysfs_emit_at(buf, offset + l, "%lld.%0*lld", tmp2, scale,
- abs(frac));
+ l += sysfs_emit_at(buf, offset + l, "%lld.%0*llu", tmp2, scale,
+ abs_s64_no_overflow(frac));
return l;
}
default:
@@ -1034,6 +1051,9 @@ static ssize_t iio_write_channel_info(struct device *dev,
case IIO_VAL_DECIMAL64_NANO:
case IIO_VAL_DECIMAL64_PICO:
case IIO_VAL_DECIMAL64_FEMTO:
+ case IIO_VAL_DECIMAL64_ATTO:
+ case IIO_VAL_DECIMAL64_ZEPTO:
+ case IIO_VAL_DECIMAL64_YOCTO:
dec_scale = type - IIO_VAL_DECIMAL64_BASE;
fallthrough;
case IIO_VAL_INT_64:
diff --git a/drivers/iio/test/iio-test-format.c b/drivers/iio/test/iio-test-format.c
index e5d0a2ac4670..43a80952a50f 100644
--- a/drivers/iio/test/iio-test-format.c
+++ b/drivers/iio/test/iio-test-format.c
@@ -286,6 +286,14 @@ static void iio_test_iio_format_value_decimal_64(struct kunit *test)
/* limits */
iio_val_s64_decompose(LLONG_MAX, &values[0], &values[1]);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_YOCTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000009223372036854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_ZEPTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.009223372036854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_ATTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9.223372036854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_FEMTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223.372036854775807\n");
ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372.036854775807\n");
ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
@@ -294,6 +302,14 @@ static void iio_test_iio_format_value_decimal_64(struct kunit *test)
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854.775807\n");
iio_val_s64_decompose(LLONG_MIN, &values[0], &values[1]);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_YOCTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.000009223372036854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_ZEPTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.009223372036854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_ATTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9.223372036854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_FEMTO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223.372036854775808\n");
ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372.036854775808\n");
ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
diff --git a/include/linux/iio/types.h b/include/linux/iio/types.h
index d8944c9e5e90..2ef9b2a8b119 100644
--- a/include/linux/iio/types.h
+++ b/include/linux/iio/types.h
@@ -43,6 +43,9 @@ enum iio_event_info {
#define IIO_VAL_DECIMAL64_NANO (IIO_VAL_DECIMAL64_BASE + 9)
#define IIO_VAL_DECIMAL64_PICO (IIO_VAL_DECIMAL64_BASE + 12)
#define IIO_VAL_DECIMAL64_FEMTO (IIO_VAL_DECIMAL64_BASE + 15)
+#define IIO_VAL_DECIMAL64_ATTO (IIO_VAL_DECIMAL64_BASE + 18)
+#define IIO_VAL_DECIMAL64_ZEPTO (IIO_VAL_DECIMAL64_BASE + 21)
+#define IIO_VAL_DECIMAL64_YOCTO (IIO_VAL_DECIMAL64_BASE + 24)
static inline s64 iio_val_s64_compose(s32 val0, s32 val1)
{
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 3/3] iio: cdc: add Texas Instruments FDC1004 driver
2026-10-25 5:48 [PATCH v2 0/3] iio: cdc: add Texas Instruments FDC1004 driver Victor Chernyakin
2026-10-25 5:48 ` [PATCH v2 1/3] iio: add IIO_VAL_DECIMAL64_{ATTO,ZEPTO,YOCTO} formats Victor Chernyakin
2026-10-25 5:48 ` [PATCH v2 2/3] dt-bindings: iio: cdc: add Texas Instruments FDC1004 Victor Chernyakin
@ 2026-10-25 5:48 ` Victor Chernyakin
2026-10-25 21:24 ` Andy Shevchenko
2026-10-25 20:47 ` [PATCH v2 0/3] " Andy Shevchenko
2026-10-25 22:39 ` [PATCH v2 0/3] " Jonathan Cameron
4 siblings, 1 reply; 10+ messages in thread
From: Victor Chernyakin @ 2026-10-25 5:48 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Nuno Sá,
Andy Shevchenko, linux-iio, linux-kernel, Victor Chernyakin
The FDC1004 is a high-precision 4-channel capacitive sensor that talks
over I2C. It can report the values of each of its 4 channels individually,
as well as the difference between any two of them.
This initial driver allows performing single reads and configuring
the sensor's various settings (sample frequency, calibration
gain/offset, etc.). It could be extended with support for triggered
buffers, repeated measurement mode, and/or power management.
Signed-off-by: Victor Chernyakin <chernyakin.victor.j@outlook.com>
---
MAINTAINERS | 1 +
drivers/iio/cdc/Kconfig | 10 +
drivers/iio/cdc/Makefile | 1 +
drivers/iio/cdc/fdc1004.c | 486 ++++++++++++++++++++++++++++++++++++++
4 files changed, 498 insertions(+)
create mode 100644 drivers/iio/cdc/fdc1004.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 69c23ae21016..4d926c47ad8d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27392,6 +27392,7 @@ TI FDC1004 DRIVER
M: Victor Chernyakin <chernyakin.victor.j@outlook.com>
S: Maintained
F: Documentation/devicetree/bindings/iio/cdc/ti,fdc1004.yaml
+F: drivers/iio/cdc/fdc1004.c
TI FLASH MEDIA MEMORYSTICK/MMC DRIVERS
M: Alex Dubov <oakad@yahoo.com>
diff --git a/drivers/iio/cdc/Kconfig b/drivers/iio/cdc/Kconfig
index e0a5ce66a984..6b9ea2cd37bd 100644
--- a/drivers/iio/cdc/Kconfig
+++ b/drivers/iio/cdc/Kconfig
@@ -24,4 +24,14 @@ config AD7746
To compile this driver as a module, choose M here: the
module will be called ad7746.
+config FDC1004
+ tristate "Texas Instruments FDC1004 capacitive sensor driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Texas Instruments FDC1004
+ capacitive sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called fdc1004.
+
endmenu
diff --git a/drivers/iio/cdc/Makefile b/drivers/iio/cdc/Makefile
index 41db756d8020..88253f33c5a7 100644
--- a/drivers/iio/cdc/Makefile
+++ b/drivers/iio/cdc/Makefile
@@ -5,3 +5,4 @@
obj-$(CONFIG_AD7150) += ad7150.o
obj-$(CONFIG_AD7746) += ad7746.o
+obj-$(CONFIG_FDC1004) += fdc1004.o
diff --git a/drivers/iio/cdc/fdc1004.c b/drivers/iio/cdc/fdc1004.c
new file mode 100644
index 000000000000..319a754ca1ce
--- /dev/null
+++ b/drivers/iio/cdc/fdc1004.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments FDC1004 capacitive sensor driver
+ *
+ * Copyright (c) 2026 Victor Chernyakin <chernyakin.victor.j@outlook.com>
+ *
+ * Datasheet: https://www.ti.com/lit/gpn/FDC1004
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/iio/iio.h>
+
+#define FDC1004_REG_MEASn_MSB(n) (2 * (n))
+#define FDC1004_REG_MEASn_LSB(n) (2 * (n) + 1)
+#define FDC1004_REG_CONF_MEASn(n) (0x08 + (n))
+#define FDC1004_REG_FDC_CONF 0x0C
+#define FDC1004_REG_OFFSET_CAL_CINn(n) (0x0D + (n))
+#define FDC1004_REG_GAIN_CAL_CINn(n) (0x11 + (n))
+
+#define FDC1004_CONF_MEAS_CHA_MASK GENMASK(15, 13)
+#define FDC1004_CONF_MEAS_CHB_MASK GENMASK(12, 10)
+#define FDC1004_CONF_MEAS_CHB_CAPDAC 0b100
+#define FDC1004_CONF_MEAS_CAPDAC_MASK GENMASK(9, 5)
+
+#define FDC1004_FDC_CONF_DONE_n_MASK(n) BIT(3 - (n))
+#define FDC1004_FDC_CONF_MEAS_n_MASK(n) BIT(7 - (n))
+#define FDC1004_FDC_CONF_RATE_MASK GENMASK(11, 10)
+#define FDC1004_FDC_CONF_RST_MASK BIT(15)
+
+static const int fdc1004_samp_freqs[] = { 100, 200, 400 };
+
+/*
+ * MEASn_{MSB,LSB}: 24 bits, signed. Format: IIO_VAL_DECIMAL64_YOCTO.
+ * 1 step = 1/(2^19) pF = 1/(1000 * 2^19) nF = 0.000000001907348632812500 nF.
+ */
+static const s64 fdc1004_scale = 1907348632812500;
+
+/*
+ * CAPDAC field: 5 bits, unsigned. Format: IIO_VAL_INT.
+ * 1 step = 3.125 pF = 3.125 / 1/(2^19) raw value steps = 1638400 raw value steps.
+ */
+static const int fdc1004_offset[] = {
+ 0x00 * 1638400, /* min */
+ 0x01 * 1638400, /* step */
+ 0x1F * 1638400, /* max */
+};
+
+/*
+ * OFFSET_CAL_CINn: 16 bits, signed. Format: IIO_VAL_DECIMAL64_FEMTO.
+ * 1 step = 1/(2^11) pF = 1/(1000 * 2^11) nF = 0.000000488281250 nF.
+ */
+static const s64 fdc1004_calibbias[] = {
+ -0x8000 * 488281250UL, /* min */
+ +0x0001 * 488281250UL, /* step */
+ +0x7FFF * 488281250UL, /* max */
+};
+
+/*
+ * GAIN_CAL_CINn: 16 bits, unsigned. Format: IIO_VAL_DECIMAL64_FEMTO.
+ * 1 step = 1/(2^14) pF/pF = 0.000061035156250 pF/pF.
+ */
+static const s64 fdc1004_calibscale[] = {
+ 0x0000 * 61035156250UL, /* min */
+ 0x0001 * 61035156250UL, /* step */
+ 0xFFFF * 61035156250UL, /* max */
+};
+
+static const struct regmap_range fdc1004_readable_reg_ranges[] = {
+ regmap_reg_range(FDC1004_REG_MEASn_MSB(0), FDC1004_REG_GAIN_CAL_CINn(3)),
+};
+
+static const struct regmap_access_table fdc1004_readable_reg_table = {
+ .yes_ranges = fdc1004_readable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fdc1004_readable_reg_ranges),
+};
+
+static const struct regmap_range fdc1004_writeable_reg_ranges[] = {
+ regmap_reg_range(FDC1004_REG_CONF_MEASn(0), FDC1004_REG_GAIN_CAL_CINn(3)),
+};
+
+static const struct regmap_access_table fdc1004_writeable_reg_table = {
+ .yes_ranges = fdc1004_writeable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fdc1004_writeable_reg_ranges),
+};
+
+static const struct regmap_range fdc1004_volatile_reg_ranges[] = {
+ regmap_reg_range(FDC1004_REG_MEASn_MSB(0), FDC1004_REG_MEASn_LSB(3)),
+ regmap_reg_range(FDC1004_REG_FDC_CONF, FDC1004_REG_FDC_CONF),
+};
+
+static const struct regmap_access_table fdc1004_volatile_reg_table = {
+ .yes_ranges = fdc1004_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fdc1004_volatile_reg_ranges),
+};
+
+static const struct regmap_config fdc1004_regmap_config = {
+ .name = "fdc1004",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = FDC1004_REG_GAIN_CAL_CINn(3),
+ .rd_table = &fdc1004_readable_reg_table,
+ .wr_table = &fdc1004_writeable_reg_table,
+ .volatile_table = &fdc1004_volatile_reg_table,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+struct fdc1004_data {
+ struct regmap *regmap;
+
+ /*
+ * Serializes accesses to the chip's measurement and config
+ * registers, and to the other struct members below.
+ */
+ struct mutex lock;
+
+ u8 offsets[4];
+ u8 samp_freq_index;
+};
+
+static int fdc1004_soft_reset(struct fdc1004_data *data)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_write(data->regmap, FDC1004_REG_FDC_CONF,
+ FDC1004_FDC_CONF_RST_MASK);
+ if (ret)
+ return ret;
+
+ return regmap_read_poll_timeout(data->regmap, FDC1004_REG_FDC_CONF, val,
+ !(ret & FDC1004_FDC_CONF_RST_MASK),
+ 1 * USEC_PER_MSEC, 200 * USEC_PER_MSEC);
+}
+
+static int fdc1004_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct fdc1004_data *data = iio_priv(indio_dev);
+ unsigned int reg_val;
+ u16 reg_vals[2];
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ iio_val_s64_decompose(fdc1004_scale, val, val2);
+ return IIO_VAL_DECIMAL64_YOCTO;
+ case IIO_CHAN_INFO_RAW: {
+ guard(mutex)(&data->lock);
+
+ /* Configure the measurement we want. */
+ reg_val = FIELD_PREP(FDC1004_CONF_MEAS_CHA_MASK, chan->channel);
+ reg_val |= FIELD_PREP(FDC1004_CONF_MEAS_CHB_MASK, chan->channel2);
+ reg_val |= FIELD_PREP(FDC1004_CONF_MEAS_CAPDAC_MASK,
+ data->offsets[chan->channel]);
+ ret = regmap_write(data->regmap, FDC1004_REG_CONF_MEASn(0), reg_val);
+ if (ret)
+ return ret;
+
+ /* Start the measurement. */
+ reg_val = FDC1004_FDC_CONF_MEAS_n_MASK(0);
+ /* "+ 1" because the chip wants a 1-indexed value. */
+ reg_val |= FIELD_PREP(FDC1004_FDC_CONF_RATE_MASK,
+ data->samp_freq_index + 1);
+ ret = regmap_write(data->regmap, FDC1004_REG_FDC_CONF, reg_val);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for the measurement to complete.
+ * The slowest sampling frequency the chip supports is 100 SPS,
+ * i.e. 1 sample every 10 ms, so our timeout can't be shorter than
+ * that. Go with 40 ms to be safe.
+ */
+ ret = regmap_read_poll_timeout(
+ data->regmap, FDC1004_REG_FDC_CONF, reg_val,
+ reg_val & FDC1004_FDC_CONF_DONE_n_MASK(0),
+ 1 * USEC_PER_MSEC, 40 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+ /* The datasheet says to read MSB first, LSB second. */
+ ret = regmap_bulk_read(data->regmap, FDC1004_REG_MEASn_MSB(0),
+ reg_vals, ARRAY_SIZE(reg_vals));
+ if (ret)
+ return ret;
+
+ *val = (sign_extend32(reg_vals[0], 15) << 8) | (reg_vals[1] >> 8);
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&data->lock);
+
+ *val = fdc1004_samp_freqs[data->samp_freq_index];
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_OFFSET: {
+ guard(mutex)(&data->lock);
+
+ *val = data->offsets[chan->channel] * fdc1004_offset[1];
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = regmap_read(data->regmap,
+ FDC1004_REG_OFFSET_CAL_CINn(chan->channel),
+ ®_val);
+ if (ret)
+ return ret;
+
+ iio_val_s64_decompose(sign_extend64(reg_val, 15) *
+ fdc1004_calibbias[1], val, val2);
+ return IIO_VAL_DECIMAL64_FEMTO;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ ret = regmap_read(data->regmap,
+ FDC1004_REG_GAIN_CAL_CINn(chan->channel),
+ ®_val);
+ if (ret)
+ return ret;
+
+ iio_val_s64_decompose(reg_val * fdc1004_calibscale[1], val, val2);
+ return IIO_VAL_DECIMAL64_FEMTO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const int fdc1004_calibbias_decomposed[] = {
+ lower_32_bits(fdc1004_calibbias[0]), upper_32_bits(fdc1004_calibbias[0]),
+ lower_32_bits(fdc1004_calibbias[1]), upper_32_bits(fdc1004_calibbias[1]),
+ lower_32_bits(fdc1004_calibbias[2]), upper_32_bits(fdc1004_calibbias[2]),
+};
+
+static const int fdc1004_calibscale_decomposed[] = {
+ lower_32_bits(fdc1004_calibscale[0]), upper_32_bits(fdc1004_calibscale[0]),
+ lower_32_bits(fdc1004_calibscale[1]), upper_32_bits(fdc1004_calibscale[1]),
+ lower_32_bits(fdc1004_calibscale[2]), upper_32_bits(fdc1004_calibscale[2]),
+};
+
+static int fdc1004_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ const int **vals, int *type, int *length,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = fdc1004_samp_freqs;
+ *length = ARRAY_SIZE(fdc1004_samp_freqs);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_OFFSET:
+ *vals = fdc1004_offset;
+ *length = ARRAY_SIZE(fdc1004_offset);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ *vals = fdc1004_calibbias_decomposed;
+ *length = ARRAY_SIZE(fdc1004_calibbias_decomposed);
+ *type = IIO_VAL_DECIMAL64_FEMTO;
+ return IIO_AVAIL_RANGE;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ *vals = fdc1004_calibscale_decomposed;
+ *length = ARRAY_SIZE(fdc1004_calibscale_decomposed);
+ *type = IIO_VAL_DECIMAL64_FEMTO;
+ return IIO_AVAIL_RANGE;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fdc1004_write_raw_get_fmt(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ case IIO_CHAN_INFO_OFFSET:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ case IIO_CHAN_INFO_CALIBSCALE:
+ return IIO_VAL_DECIMAL64_FEMTO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fdc1004_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long mask)
+{
+ struct fdc1004_data *data = iio_priv(indio_dev);
+ s64 val64;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (unsigned int i = 0; i < ARRAY_SIZE(fdc1004_samp_freqs); i++) {
+ if (fdc1004_samp_freqs[i] == val) {
+ guard(mutex)(&data->lock);
+
+ data->samp_freq_index = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_OFFSET: {
+ if (val < fdc1004_offset[0] || val > fdc1004_offset[2] ||
+ val % fdc1004_offset[1])
+ return -EINVAL;
+
+ guard(mutex)(&data->lock);
+
+ data->offsets[chan->channel] = val / fdc1004_offset[1];
+ return 0;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ val64 = iio_val_s64_compose(val, val2);
+ if (val64 < fdc1004_calibbias[0] || val64 > fdc1004_calibbias[2])
+ return -EINVAL;
+
+ return regmap_write(data->regmap,
+ FDC1004_REG_OFFSET_CAL_CINn(chan->channel),
+ DIV_ROUND_CLOSEST(val64, fdc1004_calibbias[1]));
+ case IIO_CHAN_INFO_CALIBSCALE:
+ val64 = iio_val_s64_compose(val, val2);
+ if (val64 < fdc1004_calibscale[0] || val64 > fdc1004_calibscale[2])
+ return -EINVAL;
+
+ return regmap_write(data->regmap,
+ FDC1004_REG_GAIN_CAL_CINn(chan->channel),
+ DIV_ROUND_CLOSEST(val64, fdc1004_calibscale[1]));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fdc1004_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct fdc1004_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ if (readval)
+ return regmap_read(data->regmap, reg, readval);
+
+ return regmap_write(data->regmap, reg, writeval);
+}
+
+#define FDC1004_SINGLE_ENDED_CAPACITANCE_CHANNEL(_channel) \
+ { \
+ .type = IIO_CAPACITANCE, \
+ .indexed = true, \
+ .channel = _channel, \
+ .channel2 = FDC1004_CONF_MEAS_CHB_CAPDAC, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_OFFSET) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_OFFSET) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ }
+
+#define FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(_chan1, _chan2) \
+ { \
+ .type = IIO_CAPACITANCE, \
+ .indexed = true, \
+ .differential = true, \
+ .channel = _chan1, \
+ .channel2 = _chan2, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ }
+
+static const struct iio_chan_spec fdc1004_channels[] = {
+ FDC1004_SINGLE_ENDED_CAPACITANCE_CHANNEL(0),
+ FDC1004_SINGLE_ENDED_CAPACITANCE_CHANNEL(1),
+ FDC1004_SINGLE_ENDED_CAPACITANCE_CHANNEL(2),
+ FDC1004_SINGLE_ENDED_CAPACITANCE_CHANNEL(3),
+
+ /* The sensor only supports differential measurements where CHA < CHB. */
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(0, 1),
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(0, 2),
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(0, 3),
+
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(1, 2),
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(1, 3),
+
+ FDC1004_DIFFERENTIAL_CAPACITANCE_CHANNEL(2, 3),
+};
+
+static const struct iio_info fdc1004_info = {
+ .read_raw = fdc1004_read_raw,
+ .read_avail = fdc1004_read_avail,
+ .write_raw = fdc1004_write_raw,
+ .write_raw_get_fmt = fdc1004_write_raw_get_fmt,
+ .debugfs_reg_access = fdc1004_debugfs_reg_access,
+};
+
+static int fdc1004_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct fdc1004_data *data;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+
+ data->regmap = devm_regmap_init_i2c(client, &fdc1004_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ ret = devm_mutex_init(&client->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_get_enable(&client->dev, "vdd");
+ if (ret)
+ return ret;
+
+ ret = fdc1004_soft_reset(data);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "fdc1004";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &fdc1004_info;
+ indio_dev->channels = fdc1004_channels;
+ indio_dev->num_channels = ARRAY_SIZE(fdc1004_channels);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id fdc1004_id[] = {
+ { .name = "fdc1004" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, fdc1004_id);
+
+static const struct of_device_id fdc1004_of_match[] = {
+ { .compatible = "ti,fdc1004" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fdc1004_of_match);
+
+static struct i2c_driver fdc1004_driver = {
+ .driver = {
+ .name = "fdc1004",
+ .of_match_table = fdc1004_of_match,
+ },
+ .probe = fdc1004_probe,
+ .id_table = fdc1004_id,
+};
+module_i2c_driver(fdc1004_driver);
+
+MODULE_AUTHOR("Victor Chernyakin <chernyakin.victor.j@outlook.com>");
+MODULE_DESCRIPTION("Texas Instruments FDC1004 capacitive sensor driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread